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