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