1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <aidl/android/hardware/power/SessionTag.h>
18 #include <android-base/file.h>
19 #include <gmock/gmock.h>
20 #include <gtest/gtest.h>
21 #include <sys/syscall.h>
22 
23 #include <chrono>
24 #include <iostream>
25 #include <mutex>
26 #include <thread>
27 #include <unordered_map>
28 #include <utility>
29 #include <vector>
30 
31 #include "TestHelper.h"
32 #include "mocks/MockHintManager.h"
33 #include "mocks/MockPowerSessionManager.h"
34 #include "perfmgr/AdpfConfig.h"
35 
36 // define private as public to expose the private members for test.
37 #define private public
38 #include "aidl/PowerHintSession.h"
39 #include "aidl/PowerSessionManager.h"
40 
41 #define gettid() syscall(SYS_gettid)
42 
43 using namespace testing;
44 
45 using std::literals::chrono_literals::operator""ms;
46 using std::literals::chrono_literals::operator""ns;
47 using std::literals::chrono_literals::operator""s;
48 using android::base::ReadFileToString;
49 
50 namespace aidl {
51 namespace google {
52 namespace hardware {
53 namespace power {
54 namespace impl {
55 namespace pixel {
56 
57 using TestingPowerHintSession = PowerHintSession<NiceMock<mock::pixel::MockHintManager>,
58                                                  NiceMock<mock::pixel::MockPowerSessionManager>>;
59 
60 class PowerHintSessionTest : public ::testing::Test {
61   public:
SetUp()62     void SetUp() {
63         // create a list of threads
64         for (int i = 0; i < numOfThreads; i++) {
65             threadIsAlive.emplace_back(true);
66             threadList.emplace_back(std::thread([this, threadInd = i]() {
67                 ALOGI("Test thread %d is running.", (int32_t)gettid());
68                 {
69                     std::lock_guard<std::mutex> lock(m);
70                     threadIds[threadInd] = gettid();
71                 }
72                 while (threadIsAlive[threadInd]) {
73                     std::this_thread::sleep_for(50ms);
74                 }
75                 ALOGI("Test thread %d is closed.", (int32_t)gettid());
76             }));
77         }
78         std::this_thread::sleep_for(50ms);
79 
80         // create two hint sessions
81         for (int i = 0; i < numOfThreads; i++) {
82             if (i <= numOfThreads / 2) {
83                 session1Threads.emplace_back(threadIds[i]);
84             }
85 
86             if (i >= numOfThreads / 2) {
87                 session2Threads.emplace_back(threadIds[i]);
88             }
89         }
90 
91         sess1 = ndk::SharedRefBase::make<PowerHintSession<>>(1, 1, session1Threads, 1000000,
92                                                              SessionTag::OTHER);
93         sess2 = ndk::SharedRefBase::make<PowerHintSession<>>(2, 2, session2Threads, 1000000,
94                                                              SessionTag::OTHER);
95     }
96 
TearDown()97     void TearDown() {
98         for (int i = 0; i < numOfThreads; i++) {
99             if (threadIsAlive[i]) {
100                 threadIsAlive[i] = false;
101                 threadList[i].join();
102             }
103         }
104         threadList.clear();
105         threadIds.clear();
106         threadIsAlive.clear();
107         session1Threads.clear();
108         session2Threads.clear();
109     }
110 
111   protected:
112     static const int numOfThreads = 3;
113     std::vector<std::thread> threadList;
114     std::unordered_map<int, int32_t> threadIds;
115     std::vector<bool> threadIsAlive;
116     std::mutex m;
117     std::vector<int32_t> session1Threads;
118     std::vector<int32_t> session2Threads;
119     std::shared_ptr<PowerHintSession<>> sess1;
120     std::shared_ptr<PowerHintSession<>> sess2;
121 
122     // close the i-th thread in thread list.
closeThread(int i)123     void closeThread(int i) {
124         if (i < 0 || i >= numOfThreads)
125             return;
126         if (threadIsAlive[i]) {
127             threadIsAlive[i] = false;
128             threadList[i].join();
129         }
130     }
131 
132     // Reads the session active flag from a sched dump for a pid. Returns error status and
133     // stores result in isActive.
ReadThreadADPFTag(pid_t pid,bool * isActive)134     bool ReadThreadADPFTag(pid_t pid, bool *isActive) {
135         std::string pidStr = std::to_string(pid);
136         std::string schedDump;
137         *isActive = false;
138 
139         // Store the SchedDump into a string.
140         if (!ReadFileToString("/proc/vendor_sched/dump_task", &schedDump)) {
141             std::cerr << "Error: Could not read /proc/vendor_sched/dump_task." << std::endl;
142             return false;
143         }
144 
145         // Find our pid entry start from the sched dump.
146         // We use rfind since the dump is ordered by PID and we made a new thread recently.
147         size_t pid_position = schedDump.rfind(pidStr);
148         if (pid_position == std::string::npos) {
149             std::cerr << "Error: pid not found in sched dump." << std::endl;
150             return false;
151         }
152 
153         // Find the end boundary of our sched dump entry.
154         size_t entry_end_position = schedDump.find_first_of("\n", pid_position);
155         if (entry_end_position == std::string::npos) {
156             std::cerr << "Error: could not find end of sched dump entry." << std::endl;
157             return false;
158         }
159 
160         // Extract our sched dump entry.
161         std::string threadEntry = schedDump.substr(pid_position, entry_end_position - pid_position);
162 
163         std::istringstream thread_dump_info(threadEntry);
164         std::vector<std::string> thread_vendor_attrs;
165         std::string attr;
166         while (thread_dump_info >> attr) {
167             thread_vendor_attrs.push_back(attr);
168         }
169 
170         const int32_t tag_word_pos = 10;  // The adpf attribute position in dump log.
171         if (thread_vendor_attrs.size() < tag_word_pos + 1) {
172             return false;
173         }
174         *isActive = thread_vendor_attrs[tag_word_pos] == "1";
175         return true;
176     }
177 };
178 
179 class PowerHintSessionMockedTest : public Test {
180   public:
SetUp()181     void SetUp() {
182         mTestConfig = std::make_shared<::android::perfmgr::AdpfConfig>(makeMockConfig());
183         mMockHintManager = NiceMock<mock::pixel::MockHintManager>::GetInstance();
184         ON_CALL(*mMockHintManager, GetAdpfProfile()).WillByDefault(Return(mTestConfig));
185 
186         mMockPowerSessionManager = NiceMock<mock::pixel::MockPowerSessionManager>::getInstance();
187         mHintSession = ndk::SharedRefBase::make<TestingPowerHintSession>(mTgid, mUid, mTids, 1,
188                                                                          SessionTag::OTHER);
189     }
190 
TearDown()191     void TearDown() {
192         Mock::VerifyAndClear(mMockHintManager);
193         Mock::VerifyAndClear(mMockPowerSessionManager);
194         if (mHintSession) {
195             mHintSession->close();
196         }
197     }
198 
199   protected:
200     std::shared_ptr<::android::perfmgr::AdpfConfig> mTestConfig;
201     std::shared_ptr<TestingPowerHintSession> mHintSession;
202     NiceMock<mock::pixel::MockHintManager> *mMockHintManager;
203     NiceMock<mock::pixel::MockPowerSessionManager> *mMockPowerSessionManager;
204 
205     int mTgid = 10000;
206     int mUid = 1001;
207     std::vector<int> mTids = {10000};
208 };
209 
TEST_F(PowerHintSessionTest,removeDeadThread)210 TEST_F(PowerHintSessionTest, removeDeadThread) {
211     ALOGI("Running dead thread test for hint sessions.");
212     auto sessManager = sess1->mPSManager;
213     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
214 
215     // The sessions' thread list doesn't change after thread died until the uclamp
216     // min update is triggered.
217     int deadThreadInd = numOfThreads / 2;
218     auto deadThreadID = threadIds[deadThreadInd];
219     closeThread(deadThreadInd);
220     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
221               session1Threads);
222     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess2->mSessionId].linkedTasks,
223               session2Threads);
224     ASSERT_EQ(sessManager->mSessionTaskMap.mTasks[deadThreadID].size(), 2);
225 
226     // Trigger an update of uclamp min.
227     auto tNow = std::chrono::duration_cast<std::chrono::nanoseconds>(
228                         std::chrono::high_resolution_clock::now().time_since_epoch())
229                         .count();
230     WorkDuration wDur(tNow, 1100000);
231     sess1->reportActualWorkDuration(std::vector<WorkDuration>{wDur});
232     ASSERT_EQ(sessManager->mSessionTaskMap.mTasks[deadThreadID].size(), 1);
233     sess2->reportActualWorkDuration(std::vector<WorkDuration>{wDur});
234     ASSERT_EQ(sessManager->mSessionTaskMap.mTasks.count(deadThreadID), 0);
235     std::erase(session1Threads, deadThreadID);
236     std::erase(session2Threads, deadThreadID);
237     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
238               session1Threads);
239     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess2->mSessionId].linkedTasks,
240               session2Threads);
241 
242     // Close all the threads in session 1.
243     for (int i = 0; i <= numOfThreads / 2; i++) {
244         closeThread(i);
245     }
246     tNow = std::chrono::duration_cast<std::chrono::nanoseconds>(
247                    std::chrono::high_resolution_clock::now().time_since_epoch())
248                    .count();
249     sess1->reportActualWorkDuration(std::vector<WorkDuration>{wDur});
250     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());  // Session still alive
251     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size(), 0);
252 }
253 
TEST_F(PowerHintSessionTest,setThreads)254 TEST_F(PowerHintSessionTest, setThreads) {
255     auto sessManager = sess1->mPSManager;
256     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
257 
258     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
259               session1Threads);
260 
261     std::vector<int32_t> newSess1Threads;
262     for (auto tid : threadIds) {
263         newSess1Threads.emplace_back(tid.second);
264     }
265     sess1->setThreads(newSess1Threads);
266     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
267               newSess1Threads);
268 
269     sess1->close();
270     sess2->close();
271 }
272 
TEST_F(PowerHintSessionTest,pauseResumeSession)273 TEST_F(PowerHintSessionTest, pauseResumeSession) {
274     auto sessManager = sess1->mPSManager;
275     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
276     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size());
277 
278     sess1->pause();
279     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
280     ASSERT_EQ(0, sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size());
281 
282     sess1->resume();
283     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
284               session1Threads);
285     ASSERT_EQ(session1Threads, sess1->mDescriptor->thread_ids);
286     ASSERT_EQ(SessionTag::OTHER, sess1->mDescriptor->tag);
287 
288     sess1->close();
289     sess2->close();
290 }
291 
TEST_F(PowerHintSessionTest,checkPauseResumeTag)292 TEST_F(PowerHintSessionTest, checkPauseResumeTag) {
293     auto sessManager = sess1->mPSManager;
294     bool isActive;
295 
296     // Check we actually start with two PIDs.
297     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size());
298     pid_t threadOnePid = sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks[0];
299     pid_t threadTwoPid = sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks[1];
300 
301     // Start the powerhint session and check the powerhint tags are on.
302     std::this_thread::sleep_for(10ms);
303     ASSERT_TRUE(ReadThreadADPFTag(threadOnePid, &isActive));
304     ASSERT_TRUE(isActive);
305     ASSERT_TRUE(ReadThreadADPFTag(threadTwoPid, &isActive));
306     ASSERT_TRUE(isActive);
307 
308     // Pause session 1, the powerhint session tag for thread 1 should be off.
309     // But, thread two should still have tag on since it is part of session 2.
310     sess1->pause();
311     std::this_thread::sleep_for(10ms);
312     ASSERT_TRUE(ReadThreadADPFTag(threadOnePid, &isActive));
313     ASSERT_TRUE(!isActive);
314     ASSERT_TRUE(ReadThreadADPFTag(threadTwoPid, &isActive));
315     ASSERT_TRUE(isActive);
316 
317     // Resume the powerhint session and check the powerhint sessions are allowed.
318     sess1->resume();
319     std::this_thread::sleep_for(10ms);
320     ASSERT_TRUE(ReadThreadADPFTag(threadOnePid, &isActive));
321     ASSERT_TRUE(isActive);
322     ASSERT_TRUE(ReadThreadADPFTag(threadTwoPid, &isActive));
323     ASSERT_TRUE(isActive);
324 
325     sess1->close();
326     sess2->close();
327 }
328 
TEST_F(PowerHintSessionMockedTest,updateSessionJankState)329 TEST_F(PowerHintSessionMockedTest, updateSessionJankState) {
330     // Low FPS
331     ASSERT_EQ(SessionJankyLevel::LIGHT,
332               mHintSession->updateSessionJankState(SessionJankyLevel::SEVERE, 8, 5.0, true));
333     ASSERT_EQ(SessionJankyLevel::LIGHT,
334               mHintSession->updateSessionJankState(SessionJankyLevel::MODERATE, 8, 5.0, true));
335     ASSERT_EQ(SessionJankyLevel::LIGHT,
336               mHintSession->updateSessionJankState(SessionJankyLevel::LIGHT, 8, 5.0, true));
337     // Light number of jank frames, and high workload duration variance.
338     ASSERT_EQ(SessionJankyLevel::MODERATE,
339               mHintSession->updateSessionJankState(SessionJankyLevel::SEVERE, 1, 5.0, false));
340     ASSERT_EQ(SessionJankyLevel::MODERATE,
341               mHintSession->updateSessionJankState(SessionJankyLevel::MODERATE, 1, 5.0, false));
342     ASSERT_EQ(SessionJankyLevel::LIGHT,
343               mHintSession->updateSessionJankState(SessionJankyLevel::LIGHT, 1, 5.0, false));
344     // Light number of jank frames, and low workload duration variance.
345     ASSERT_EQ(SessionJankyLevel::LIGHT,
346               mHintSession->updateSessionJankState(SessionJankyLevel::SEVERE, 1, 1.0, false));
347     ASSERT_EQ(SessionJankyLevel::LIGHT,
348               mHintSession->updateSessionJankState(SessionJankyLevel::MODERATE, 1, 1.0, false));
349     ASSERT_EQ(SessionJankyLevel::LIGHT,
350               mHintSession->updateSessionJankState(SessionJankyLevel::LIGHT, 1, 1.0, false));
351     // Moderate number of jank frames
352     ASSERT_EQ(SessionJankyLevel::MODERATE,
353               mHintSession->updateSessionJankState(SessionJankyLevel::SEVERE, 4, 5.0, false));
354     ASSERT_EQ(SessionJankyLevel::MODERATE,
355               mHintSession->updateSessionJankState(SessionJankyLevel::MODERATE, 4, 5.0, false));
356     ASSERT_EQ(SessionJankyLevel::MODERATE,
357               mHintSession->updateSessionJankState(SessionJankyLevel::LIGHT, 4, 5.0, false));
358     // Significant number of jank frames
359     ASSERT_EQ(SessionJankyLevel::SEVERE,
360               mHintSession->updateSessionJankState(SessionJankyLevel::SEVERE, 9, 5.0, false));
361     ASSERT_EQ(SessionJankyLevel::SEVERE,
362               mHintSession->updateSessionJankState(SessionJankyLevel::MODERATE, 9, 5.0, false));
363     ASSERT_EQ(SessionJankyLevel::SEVERE,
364               mHintSession->updateSessionJankState(SessionJankyLevel::LIGHT, 9, 5.0, false));
365 }
366 
367 }  // namespace pixel
368 }  // namespace impl
369 }  // namespace power
370 }  // namespace hardware
371 }  // namespace google
372 }  // namespace aidl
373