• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <mutex>
25 #include <thread>
26 #include <unordered_map>
27 #include <utility>
28 #include <vector>
29 
30 #include "TestHelper.h"
31 #include "mocks/MockHintManager.h"
32 #include "mocks/MockPowerSessionManager.h"
33 #include "perfmgr/AdpfConfig.h"
34 
35 // define private as public to expose the private members for test.
36 #define private public
37 #include "aidl/PowerHintSession.h"
38 #include "aidl/PowerSessionManager.h"
39 
40 #define gettid() syscall(SYS_gettid)
41 
42 using namespace testing;
43 
44 using std::literals::chrono_literals::operator""ms;
45 using std::literals::chrono_literals::operator""ns;
46 using std::literals::chrono_literals::operator""s;
47 using android::base::ReadFileToString;
48 
49 namespace aidl {
50 namespace google {
51 namespace hardware {
52 namespace power {
53 namespace impl {
54 namespace pixel {
55 
56 using TestingPowerHintSession = PowerHintSession<NiceMock<mock::pixel::MockHintManager>,
57                                                  NiceMock<mock::pixel::MockPowerSessionManager>>;
58 
59 class PowerHintSessionTest : public ::testing::Test {
60   public:
SetUp()61     void SetUp() {
62         // create a list of threads
63         for (int i = 0; i < numOfThreads; i++) {
64             threadIsAlive.emplace_back(true);
65             threadList.emplace_back(std::thread([this, threadInd = i]() {
66                 ALOGI("Test thread %d is running.", (int32_t)gettid());
67                 {
68                     std::lock_guard<std::mutex> lock(m);
69                     threadIds[threadInd] = gettid();
70                 }
71                 while (threadIsAlive[threadInd]) {
72                     std::this_thread::sleep_for(50ms);
73                 }
74                 ALOGI("Test thread %d is closed.", (int32_t)gettid());
75             }));
76         }
77         std::this_thread::sleep_for(50ms);
78 
79         // create two hint sessions
80         for (int i = 0; i < numOfThreads; i++) {
81             if (i <= numOfThreads / 2) {
82                 session1Threads.emplace_back(threadIds[i]);
83             }
84 
85             if (i >= numOfThreads / 2) {
86                 session2Threads.emplace_back(threadIds[i]);
87             }
88         }
89 
90         sess1 = ndk::SharedRefBase::make<PowerHintSession<>>(1, 1, session1Threads, 1000000,
91                                                              SessionTag::OTHER);
92         sess2 = ndk::SharedRefBase::make<PowerHintSession<>>(2, 2, session2Threads, 1000000,
93                                                              SessionTag::OTHER);
94     }
95 
TearDown()96     void TearDown() {
97         for (int i = 0; i < numOfThreads; i++) {
98             if (threadIsAlive[i]) {
99                 threadIsAlive[i] = false;
100                 threadList[i].join();
101             }
102         }
103         threadList.clear();
104         threadIds.clear();
105         threadIsAlive.clear();
106         session1Threads.clear();
107         session2Threads.clear();
108     }
109 
110   protected:
111     static const int numOfThreads = 3;
112     std::vector<std::thread> threadList;
113     std::unordered_map<int, int32_t> threadIds;
114     std::vector<bool> threadIsAlive;
115     std::mutex m;
116     std::vector<int32_t> session1Threads;
117     std::vector<int32_t> session2Threads;
118     std::shared_ptr<PowerHintSession<>> sess1;
119     std::shared_ptr<PowerHintSession<>> sess2;
120 
121     // close the i-th thread in thread list.
closeThread(int i)122     void closeThread(int i) {
123         if (i < 0 || i >= numOfThreads)
124             return;
125         if (threadIsAlive[i]) {
126             threadIsAlive[i] = false;
127             threadList[i].join();
128         }
129     }
130 
131     // Reads the session active flag from a sched dump for a pid. Returns error status and
132     // stores result in isActive.
ReadThreadADPFTag(pid_t pid,bool * isActive)133     bool ReadThreadADPFTag(pid_t pid, bool *isActive) {
134         std::string pidStr = std::to_string(pid);
135         std::string schedDump;
136         *isActive = false;
137 
138         // Store the SchedDump into a string.
139         if (!ReadFileToString("/proc/vendor_sched/dump_task", &schedDump)) {
140             std::cerr << "Error: Could not read /proc/vendor_sched/dump_task." << std::endl;
141             return false;
142         }
143 
144         // Find our pid entry start from the sched dump.
145         // We use rfind since the dump is ordered by PID and we made a new thread recently.
146         size_t pid_position = schedDump.rfind(pidStr);
147         if (pid_position == std::string::npos) {
148             std::cerr << "Error: pid not found in sched dump." << std::endl;
149             return false;
150         }
151 
152         // Find the end boundary of our sched dump entry.
153         size_t entry_end_position = schedDump.find_first_of("\n", pid_position);
154         if (entry_end_position == std::string::npos) {
155             std::cerr << "Error: could not find end of sched dump entry." << std::endl;
156             return false;
157         }
158 
159         // Extract our sched dump entry.
160         std::string threadEntry = schedDump.substr(pid_position, entry_end_position - pid_position);
161         if (threadEntry.size() < 3) {
162             std::cerr << "Error: sched dump entry invalid." << std::endl;
163             return false;
164         }
165 
166         // We do reverse array access since the first entries have variable length.
167         char powerSessionActiveFlag = threadEntry[threadEntry.size() - 3];
168         if (powerSessionActiveFlag == '1') {
169             *isActive = true;
170         }
171 
172         // At this point, we have found a valid entry with SessionAllowed == bool, so we return
173         // success status.
174         return true;
175     }
176 };
177 
178 class PowerHintSessionMockedTest : public Test {
179   public:
SetUp()180     void SetUp() {
181         mTestConfig = std::make_shared<::android::perfmgr::AdpfConfig>(makeMockConfig());
182         mMockHintManager = NiceMock<mock::pixel::MockHintManager>::GetInstance();
183         ON_CALL(*mMockHintManager, GetAdpfProfile()).WillByDefault(Return(mTestConfig));
184 
185         mMockPowerSessionManager = NiceMock<mock::pixel::MockPowerSessionManager>::getInstance();
186         mHintSession = ndk::SharedRefBase::make<TestingPowerHintSession>(mTgid, mUid, mTids, 1,
187                                                                          SessionTag::OTHER);
188     }
189 
TearDown()190     void TearDown() {
191         Mock::VerifyAndClear(mMockHintManager);
192         Mock::VerifyAndClear(mMockPowerSessionManager);
193         if (mHintSession) {
194             mHintSession->close();
195         }
196     }
197 
198   protected:
199     std::shared_ptr<::android::perfmgr::AdpfConfig> mTestConfig;
200     std::shared_ptr<TestingPowerHintSession> mHintSession;
201     NiceMock<mock::pixel::MockHintManager> *mMockHintManager;
202     NiceMock<mock::pixel::MockPowerSessionManager> *mMockPowerSessionManager;
203 
204     int mTgid = 10000;
205     int mUid = 1001;
206     std::vector<int> mTids = {10000};
207 };
208 
TEST_F(PowerHintSessionTest,removeDeadThread)209 TEST_F(PowerHintSessionTest, removeDeadThread) {
210     ALOGI("Running dead thread test for hint sessions.");
211     auto sessManager = sess1->mPSManager;
212     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
213 
214     // The sessions' thread list doesn't change after thread died until the uclamp
215     // min update is triggered.
216     int deadThreadInd = numOfThreads / 2;
217     auto deadThreadID = threadIds[deadThreadInd];
218     closeThread(deadThreadInd);
219     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
220               session1Threads);
221     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess2->mSessionId].linkedTasks,
222               session2Threads);
223     ASSERT_EQ(sessManager->mSessionTaskMap.mTasks[deadThreadID].size(), 2);
224 
225     // Trigger an update of uclamp min.
226     auto tNow = std::chrono::duration_cast<std::chrono::nanoseconds>(
227                         std::chrono::high_resolution_clock::now().time_since_epoch())
228                         .count();
229     WorkDuration wDur(tNow, 1100000);
230     sess1->reportActualWorkDuration(std::vector<WorkDuration>{wDur});
231     ASSERT_EQ(sessManager->mSessionTaskMap.mTasks[deadThreadID].size(), 1);
232     sess2->reportActualWorkDuration(std::vector<WorkDuration>{wDur});
233     ASSERT_EQ(sessManager->mSessionTaskMap.mTasks.count(deadThreadID), 0);
234     std::erase(session1Threads, deadThreadID);
235     std::erase(session2Threads, deadThreadID);
236     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
237               session1Threads);
238     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess2->mSessionId].linkedTasks,
239               session2Threads);
240 
241     // Close all the threads in session 1.
242     for (int i = 0; i <= numOfThreads / 2; i++) {
243         closeThread(i);
244     }
245     tNow = std::chrono::duration_cast<std::chrono::nanoseconds>(
246                    std::chrono::high_resolution_clock::now().time_since_epoch())
247                    .count();
248     sess1->reportActualWorkDuration(std::vector<WorkDuration>{wDur});
249     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());  // Session still alive
250     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size(), 0);
251 }
252 
TEST_F(PowerHintSessionTest,setThreads)253 TEST_F(PowerHintSessionTest, setThreads) {
254     auto sessManager = sess1->mPSManager;
255     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
256 
257     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
258               session1Threads);
259 
260     std::vector<int32_t> newSess1Threads;
261     for (auto tid : threadIds) {
262         newSess1Threads.emplace_back(tid.second);
263     }
264     sess1->setThreads(newSess1Threads);
265     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
266               newSess1Threads);
267 
268     sess1->close();
269     sess2->close();
270 }
271 
TEST_F(PowerHintSessionTest,pauseResumeSession)272 TEST_F(PowerHintSessionTest, pauseResumeSession) {
273     auto sessManager = sess1->mPSManager;
274     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
275     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size());
276 
277     sess1->pause();
278     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size());
279     ASSERT_EQ(0, sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size());
280 
281     sess1->resume();
282     ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks,
283               session1Threads);
284     ASSERT_EQ(session1Threads, sess1->mDescriptor->thread_ids);
285     ASSERT_EQ(SessionTag::OTHER, sess1->mDescriptor->tag);
286 
287     sess1->close();
288     sess2->close();
289 }
290 
TEST_F(PowerHintSessionTest,checkPauseResumeTag)291 TEST_F(PowerHintSessionTest, checkPauseResumeTag) {
292     auto sessManager = sess1->mPSManager;
293     bool isActive;
294 
295     // Check we actually start with two PIDs.
296     ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size());
297     pid_t threadOnePid = sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks[0];
298     pid_t threadTwoPid = sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks[1];
299 
300     // Start the powerhint session and check the powerhint tags are on.
301     std::this_thread::sleep_for(10ms);
302     ASSERT_TRUE(ReadThreadADPFTag(threadOnePid, &isActive));
303     ASSERT_TRUE(isActive);
304     ASSERT_TRUE(ReadThreadADPFTag(threadTwoPid, &isActive));
305     ASSERT_TRUE(isActive);
306 
307     // Pause session 1, the powerhint session tag for thread 1 should be off.
308     // But, thread two should still have tag on since it is part of session 2.
309     sess1->pause();
310     std::this_thread::sleep_for(10ms);
311     ASSERT_TRUE(ReadThreadADPFTag(threadOnePid, &isActive));
312     ASSERT_TRUE(!isActive);
313     ASSERT_TRUE(ReadThreadADPFTag(threadTwoPid, &isActive));
314     ASSERT_TRUE(isActive);
315 
316     // Resume the powerhint session and check the powerhint sessions are allowed.
317     sess1->resume();
318     std::this_thread::sleep_for(10ms);
319     ASSERT_TRUE(ReadThreadADPFTag(threadOnePid, &isActive));
320     ASSERT_TRUE(isActive);
321     ASSERT_TRUE(ReadThreadADPFTag(threadTwoPid, &isActive));
322     ASSERT_TRUE(isActive);
323 
324     sess1->close();
325     sess2->close();
326 }
327 
328 }  // namespace pixel
329 }  // namespace impl
330 }  // namespace power
331 }  // namespace hardware
332 }  // namespace google
333 }  // namespace aidl
334