1 /*
2 * Copyright (C) 2019 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 "WakeLockEntryList.h"
18
19 #include <android-base/file.h>
20 #include <android-base/logging.h>
21 #include <android-base/parseint.h>
22 #include <android-base/stringprintf.h>
23 #include <android/system/suspend/internal/ISuspendControlServiceInternal.h>
24 #include <suspend_service_flags.h>
25
26 #include <iomanip>
27
28 using android::base::ParseInt;
29 using android::base::ReadFdToString;
30 using android::base::Readlink;
31 using android::base::StringPrintf;
32 using suspend_service::flags::fast_kernel_wakelock_reporting;
33
34 using ISCSI = ::android::system::suspend::internal::ISuspendControlServiceInternal;
35
36 namespace android {
37 namespace system {
38 namespace suspend {
39 namespace V1_0 {
40
41 namespace {
42
43 struct BitAndFilename {
44 int32_t bit;
45 std::string filename;
46 };
47
48 const BitAndFilename FIELDS[] = {
49 {-1, "name"},
50 {ISCSI::WAKE_LOCK_INFO_ACTIVE_COUNT, "active_count"},
51 {ISCSI::WAKE_LOCK_INFO_LAST_CHANGE, "last_change_ms"},
52 {ISCSI::WAKE_LOCK_INFO_MAX_TIME, "max_time_ms"},
53 {ISCSI::WAKE_LOCK_INFO_TOTAL_TIME, "total_time_ms"},
54 {ISCSI::WAKE_LOCK_INFO_ACTIVE_TIME, "active_time_ms"},
55 {ISCSI::WAKE_LOCK_INFO_EVENT_COUNT, "event_count"},
56 {ISCSI::WAKE_LOCK_INFO_EXPIRE_COUNT, "expire_count"},
57 {ISCSI::WAKE_LOCK_INFO_PREVENT_SUSPEND_TIME, "prevent_suspend_time_ms"},
58 {ISCSI::WAKE_LOCK_INFO_WAKEUP_COUNT, "wakeup_count"},
59 };
60
61 } // namespace
62
operator <<(std::ostream & out,const WakeLockInfo & entry)63 static std::ostream& operator<<(std::ostream& out, const WakeLockInfo& entry) {
64 const char* sep = " | ";
65 const char* notApplicable = "---";
66 bool kernelWakelock = entry.isKernelWakelock;
67
68 // clang-format off
69 out << sep
70 << std::left << std::setw(30) << entry.name << sep
71 << std::right << std::setw(6)
72 << ((kernelWakelock) ? notApplicable : std::to_string(entry.pid)) << sep
73 << std::left << std::setw(6) << ((kernelWakelock) ? "Kernel" : "Native") << sep
74 << std::left << std::setw(8) << ((entry.isActive) ? "Active" : "Inactive") << sep
75 << std::right << std::setw(12) << entry.activeCount << sep
76 << std::right << std::setw(12) << std::to_string(entry.totalTime) + "ms" << sep
77 << std::right << std::setw(12) << std::to_string(entry.maxTime) + "ms" << sep
78 << std::right << std::setw(12)
79 << ((kernelWakelock) ? std::to_string(entry.eventCount) : notApplicable) << sep
80 << std::right << std::setw(12)
81 << ((kernelWakelock) ? std::to_string(entry.wakeupCount) : notApplicable) << sep
82 << std::right << std::setw(12)
83 << ((kernelWakelock) ? std::to_string(entry.expireCount) : notApplicable) << sep
84 << std::right << std::setw(20)
85 << ((kernelWakelock) ? std::to_string(entry.preventSuspendTime) + "ms" : notApplicable)
86 << sep
87 << std::right << std::setw(16) << std::to_string(entry.lastChange) + "ms" << sep;
88 // clang-format on
89
90 return out;
91 }
92
operator <<(std::ostream & out,const WakeLockEntryList & list)93 std::ostream& operator<<(std::ostream& out, const WakeLockEntryList& list) {
94 std::vector<WakeLockInfo> wlStats;
95 list.getWakeLockStats(ISCSI::WAKE_LOCK_INFO_ALL_FIELDS, &wlStats);
96 int width = 194;
97 const char* sep = " | ";
98 std::stringstream ss;
99 ss << " " << std::setfill('-') << std::setw(width) << "\n";
100 std::string div = ss.str();
101
102 out << div;
103
104 std::stringstream header;
105 header << sep << std::right << std::setw(((width - 14) / 2) + 14) << "WAKELOCK STATS"
106 << std::right << std::setw((width - 14) / 2) << sep << "\n";
107 out << header.str();
108
109 out << div;
110
111 // Col names
112 // clang-format off
113 out << sep
114 << std::left << std::setw(30) << "NAME" << sep
115 << std::left << std::setw(6) << "PID" << sep
116 << std::left << std::setw(6) << "TYPE" << sep
117 << std::left << std::setw(8) << "STATUS" << sep
118 << std::left << std::setw(12) << "ACTIVE COUNT" << sep
119 << std::left << std::setw(12) << "TOTAL TIME" << sep
120 << std::left << std::setw(12) << "MAX TIME" << sep
121 << std::left << std::setw(12) << "EVENT COUNT" << sep
122 << std::left << std::setw(12) << "WAKEUP COUNT" << sep
123 << std::left << std::setw(12) << "EXPIRE COUNT" << sep
124 << std::left << std::setw(20) << "PREVENT SUSPEND TIME" << sep
125 << std::left << std::setw(16) << "LAST CHANGE" << sep
126 << "\n";
127 // clang-format on
128
129 out << div;
130
131 // Rows
132 for (const WakeLockInfo& entry : wlStats) {
133 out << entry << "\n";
134 }
135
136 out << div;
137 return out;
138 }
139
140 /**
141 * Returns the monotonic time in milliseconds.
142 */
getTimeNow()143 TimestampType getTimeNow() {
144 timespec monotime;
145 clock_gettime(CLOCK_MONOTONIC, &monotime);
146 return std::chrono::duration_cast<std::chrono::milliseconds>(
147 std::chrono::nanoseconds{monotime.tv_nsec})
148 .count() +
149 std::chrono::duration_cast<std::chrono::milliseconds>(
150 std::chrono::seconds{monotime.tv_sec})
151 .count();
152 }
153
WakeLockEntryList(size_t capacity,unique_fd kernelWakelockStatsFd)154 WakeLockEntryList::WakeLockEntryList(size_t capacity, unique_fd kernelWakelockStatsFd)
155 : mCapacity(capacity), mKernelWakelockStatsFd(std::move(kernelWakelockStatsFd)) {}
156
157 /**
158 * Evicts LRU from back of list if stats is at capacity.
159 */
evictIfFull()160 void WakeLockEntryList::evictIfFull() {
161 static std::chrono::steady_clock::time_point lastWarningTime{};
162 static std::chrono::steady_clock::time_point lastEvictionTime{};
163 static long evictionCountSinceLastLog = 0;
164
165 if (mStats.size() == mCapacity) {
166 auto evictIt = mStats.end();
167 std::advance(evictIt, -1);
168 auto evictKey = std::make_pair(evictIt->name, evictIt->pid);
169 mLookupTable.erase(evictKey);
170 mStats.erase(evictIt);
171
172 std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
173 long long secondsSinceLastLog =
174 std::chrono::duration_cast<std::chrono::seconds>(now - lastWarningTime).count();
175 evictionCountSinceLastLog++;
176
177 if (secondsSinceLastLog >= 5) {
178 long long secondsSinceLastEvict =
179 std::chrono::duration_cast<std::chrono::seconds>(now - lastEvictionTime).count();
180 LOG(WARNING) << "WakeLock Stats: Stats capacity met " << evictionCountSinceLastLog
181 << " time(s) since last warning (" << secondsSinceLastLog
182 << " seconds ago). An eviction is occurring now, with the previous"
183 << " eviction occurring " << secondsSinceLastEvict
184 << " seconds ago. Consider adjusting capacity to avoid stats eviction.";
185 lastWarningTime = now;
186 evictionCountSinceLastLog = 0; // Reset the count
187 }
188 lastEvictionTime = now;
189 }
190 }
191
192 /**
193 * Inserts entry as MRU.
194 */
insertEntry(WakeLockInfo entry)195 void WakeLockEntryList::insertEntry(WakeLockInfo entry) {
196 auto key = std::make_pair(entry.name, entry.pid);
197 mStats.emplace_front(std::move(entry));
198 mLookupTable[key] = mStats.begin();
199 }
200
201 /**
202 * Removes entry from the stats list.
203 */
deleteEntry(std::list<WakeLockInfo>::iterator entry)204 void WakeLockEntryList::deleteEntry(std::list<WakeLockInfo>::iterator entry) {
205 auto key = std::make_pair(entry->name, entry->pid);
206 mLookupTable.erase(key);
207 mStats.erase(entry);
208 }
209
210 /**
211 * Creates and returns a native wakelock entry.
212 */
createNativeEntry(const std::string & name,int pid,TimestampType timeNow) const213 WakeLockInfo WakeLockEntryList::createNativeEntry(const std::string& name, int pid,
214 TimestampType timeNow) const {
215 WakeLockInfo info;
216
217 info.name = name;
218 // It only makes sense to create a new entry on initial activation of the lock.
219 info.activeCount = 1;
220 info.lastChange = timeNow;
221 info.maxTime = 0;
222 info.totalTime = 0;
223 info.isActive = true;
224 info.activeTime = 0;
225 info.isKernelWakelock = false;
226
227 info.pid = pid;
228
229 info.eventCount = 0;
230 info.expireCount = 0;
231 info.preventSuspendTime = 0;
232 info.wakeupCount = 0;
233
234 return info;
235 }
236
237 /*
238 * Checks whether a given directory entry is a stat file we're interested in.
239 */
isStatFile(const struct dirent * de)240 static bool isStatFile(const struct dirent* de) {
241 const char* statName = de->d_name;
242 if (!strcmp(statName, ".") || !strcmp(statName, "..") || !strcmp(statName, "device") ||
243 !strcmp(statName, "power") || !strcmp(statName, "subsystem") ||
244 !strcmp(statName, "uevent")) {
245 return false;
246 }
247 return true;
248 }
249
250 /*
251 * Creates and returns a kernel wakelock entry with data read from mKernelWakelockStatsFd
252 */
createKernelEntry(const std::string & kwlId) const253 WakeLockInfo WakeLockEntryList::createKernelEntry(const std::string& kwlId) const {
254 WakeLockInfo info;
255
256 info.activeCount = 0;
257 info.lastChange = 0;
258 info.maxTime = 0;
259 info.totalTime = 0;
260 info.isActive = false;
261 info.activeTime = 0;
262 info.isKernelWakelock = true;
263
264 info.pid = -1; // N/A
265
266 info.eventCount = 0;
267 info.expireCount = 0;
268 info.preventSuspendTime = 0;
269 info.wakeupCount = 0;
270
271 unique_fd wakelockFd{TEMP_FAILURE_RETRY(
272 openat(mKernelWakelockStatsFd, kwlId.c_str(), O_DIRECTORY | O_CLOEXEC | O_RDONLY))};
273 if (wakelockFd < 0) {
274 char buf[PATH_MAX];
275 ssize_t data_length =
276 readlinkat(mKernelWakelockStatsFd, kwlId.c_str(), buf, sizeof(buf) - 1);
277 if (data_length <= 0 || strncmp(kwlId.c_str(), buf, kwlId.length()) == 0) {
278 buf[0] = '\0';
279 }
280 PLOG(ERROR) << "Error opening kernel wakelock stats for: " << kwlId << " (" << buf << ")";
281 }
282
283 std::unique_ptr<DIR, decltype(&closedir)> wakelockDp(fdopendir(dup(wakelockFd.get())),
284 &closedir);
285 if (wakelockDp) {
286 struct dirent* de;
287 while ((de = readdir(wakelockDp.get()))) {
288 if (!isStatFile(de)) {
289 continue;
290 }
291
292 std::string statName(de->d_name);
293 unique_fd statFd{
294 TEMP_FAILURE_RETRY(openat(wakelockFd, statName.c_str(), O_CLOEXEC | O_RDONLY))};
295 if (statFd < 0) {
296 PLOG(ERROR) << "Error opening " << statName << " for " << kwlId;
297 continue;
298 }
299
300 std::string valStr;
301 if (!ReadFdToString(statFd.get(), &valStr)) {
302 PLOG(ERROR) << "Error reading " << statName << " for " << kwlId;
303 continue;
304 }
305
306 // Trim newline
307 valStr.erase(std::remove(valStr.begin(), valStr.end(), '\n'), valStr.end());
308
309 if (statName == "name") {
310 info.name = valStr;
311 continue;
312 }
313
314 int64_t statVal;
315 if (!ParseInt(valStr, &statVal)) {
316 std::string path;
317 if (Readlink(StringPrintf("/proc/self/fd/%d", statFd.get()), &path)) {
318 LOG(ERROR) << "Unexpected format for wakelock stat value (" << valStr
319 << ") from file: " << path;
320 } else {
321 LOG(ERROR) << "Unexpected format for wakelock stat value (" << valStr << ")";
322 }
323 continue;
324 }
325
326 if (statName == "active_count") {
327 info.activeCount = statVal;
328 } else if (statName == "active_time_ms") {
329 info.activeTime = statVal;
330 } else if (statName == "event_count") {
331 info.eventCount = statVal;
332 } else if (statName == "expire_count") {
333 info.expireCount = statVal;
334 } else if (statName == "last_change_ms") {
335 info.lastChange = statVal;
336 } else if (statName == "max_time_ms") {
337 info.maxTime = statVal;
338 } else if (statName == "prevent_suspend_time_ms") {
339 info.preventSuspendTime = statVal;
340 } else if (statName == "total_time_ms") {
341 info.totalTime = statVal;
342 } else if (statName == "wakeup_count") {
343 info.wakeupCount = statVal;
344 }
345 }
346 }
347
348 // Derived stats
349 info.isActive = info.activeTime > 0;
350
351 return info;
352 }
353
354 /*
355 * Creates and returns a kernel wakelock entry with data read from mKernelWakelockStatsFd.
356 * Has been micro-optimized to reduce CPU time and wall time.
357 */
createKernelEntry(ScratchSpace * ss,int wakeLockInfoFieldBitMask,const std::string & kwlId) const358 WakeLockInfo WakeLockEntryList::createKernelEntry(ScratchSpace* ss, int wakeLockInfoFieldBitMask,
359 const std::string& kwlId) const {
360 WakeLockInfo info;
361
362 info.activeCount = 0;
363 info.lastChange = 0;
364 info.maxTime = 0;
365 info.totalTime = 0;
366 info.isActive = false;
367 info.activeTime = 0;
368 info.isKernelWakelock = true;
369
370 info.pid = -1; // N/A
371
372 info.eventCount = 0;
373 info.expireCount = 0;
374 info.preventSuspendTime = 0;
375 info.wakeupCount = 0;
376
377 for (const auto& field : FIELDS) {
378 const bool isNameField = field.bit == -1;
379 if (!isNameField && (wakeLockInfoFieldBitMask & field.bit) == 0) {
380 continue;
381 }
382
383 ss->statName = kwlId + "/" + field.filename;
384 int statFd = -1;
385
386 {
387 std::lock_guard<std::mutex> lock(mLock);
388 // Check if we have a valid cached file descriptor.
389 auto it = mFdCache.find(ss->statName);
390 if (it != mFdCache.end() && it->second >= 0) {
391 auto result = lseek(it->second, 0, SEEK_SET);
392 if (result < 0) {
393 PLOG(ERROR) << "Could not seek to start of FD for " << ss->statName;
394 mFdCache.erase(it);
395 PLOG(ERROR) << "Closed the FD.";
396 } else {
397 statFd = it->second;
398 }
399 }
400
401 if (statFd == -1) {
402 unique_fd tmpFd(TEMP_FAILURE_RETRY(
403 openat(mKernelWakelockStatsFd, ss->statName.c_str(), O_CLOEXEC | O_RDONLY)));
404 if (tmpFd < 0) {
405 PLOG(ERROR) << "Error opening " << ss->statName << " for " << kwlId;
406 continue;
407 }
408 statFd = tmpFd;
409 mFdCache.insert(it, {ss->statName, std::move(tmpFd)});
410 }
411 } // mLock is released here
412
413 ss->valStr.clear();
414 ssize_t n;
415 while ((n = TEMP_FAILURE_RETRY(read(statFd, &ss->readBuff[0], sizeof(ss->readBuff)))) > 0) {
416 ss->valStr.append(ss->readBuff, n);
417 }
418 if (n < 0) {
419 PLOG(ERROR) << "Error reading " << ss->statName;
420 {
421 std::lock_guard<std::mutex> lock(mLock);
422 mFdCache.erase(ss->statName);
423 PLOG(ERROR) << "Closed the FD.";
424 }
425 continue;
426 }
427
428 // Trim newline
429 ss->valStr.erase(std::remove(ss->valStr.begin(), ss->valStr.end(), '\n'), ss->valStr.end());
430
431 if (isNameField) {
432 info.name = ss->valStr;
433 continue;
434 }
435
436 int64_t statVal;
437 if (!ParseInt(ss->valStr, &statVal)) {
438 std::string path;
439 if (Readlink(StringPrintf("/proc/self/fd/%d", statFd), &path)) {
440 LOG(ERROR) << "Unexpected format for wakelock stat value (" << ss->valStr
441 << ") from file: " << path;
442 } else {
443 LOG(ERROR) << "Unexpected format for wakelock stat value (" << ss->valStr << ")";
444 }
445 continue;
446 }
447
448 if (field.filename == "active_count") {
449 info.activeCount = statVal;
450 } else if (field.filename == "active_time_ms") {
451 info.activeTime = statVal;
452 } else if (field.filename == "event_count") {
453 info.eventCount = statVal;
454 } else if (field.filename == "expire_count") {
455 info.expireCount = statVal;
456 } else if (field.filename == "last_change_ms") {
457 info.lastChange = statVal;
458 } else if (field.filename == "max_time_ms") {
459 info.maxTime = statVal;
460 } else if (field.filename == "prevent_suspend_time_ms") {
461 info.preventSuspendTime = statVal;
462 } else if (field.filename == "total_time_ms") {
463 info.totalTime = statVal;
464 } else if (field.filename == "wakeup_count") {
465 info.wakeupCount = statVal;
466 }
467 }
468
469 // Derived stats
470 info.isActive = info.activeTime > 0;
471
472 return info;
473 }
474
getKernelWakelockStats(int wakeLockInfoFieldBitMask,std::vector<WakeLockInfo> * aidl_return) const475 void WakeLockEntryList::getKernelWakelockStats(int wakeLockInfoFieldBitMask,
476 std::vector<WakeLockInfo>* aidl_return) const {
477 std::unique_ptr<DIR, decltype(&closedir)> dp(fdopendir(dup(mKernelWakelockStatsFd.get())),
478 &closedir);
479 if (dp) {
480 // rewinddir, else subsequent calls will not get any kernel wakelocks.
481 rewinddir(dp.get());
482
483 ScratchSpace ss;
484 struct dirent* de;
485 while ((de = readdir(dp.get()))) {
486 std::string kwlId(de->d_name);
487 if ((kwlId == ".") || (kwlId == "..")) {
488 continue;
489 }
490 WakeLockInfo entry = fast_kernel_wakelock_reporting()
491 ? createKernelEntry(&ss, wakeLockInfoFieldBitMask, kwlId)
492 : createKernelEntry(kwlId);
493
494 aidl_return->emplace_back(std::move(entry));
495 }
496 }
497 }
498
updateOnAcquire(const std::string & name,int pid)499 void WakeLockEntryList::updateOnAcquire(const std::string& name, int pid) {
500 TimestampType timeNow = getTimeNow();
501
502 std::lock_guard<std::mutex> lock(mLock);
503
504 auto key = std::make_pair(name, pid);
505 auto it = mLookupTable.find(key);
506 if (it == mLookupTable.end()) {
507 evictIfFull();
508 WakeLockInfo newEntry = createNativeEntry(name, pid, timeNow);
509 insertEntry(newEntry);
510 } else {
511 auto staleEntry = it->second;
512 WakeLockInfo updatedEntry = *staleEntry;
513
514 // Update entry
515 updatedEntry.isActive = true;
516 updatedEntry.activeTime = 0;
517 updatedEntry.activeCount++;
518 updatedEntry.lastChange = timeNow;
519
520 deleteEntry(staleEntry);
521 insertEntry(std::move(updatedEntry));
522 }
523 }
524
updateOnRelease(const std::string & name,int pid)525 void WakeLockEntryList::updateOnRelease(const std::string& name, int pid) {
526 TimestampType timeNow = getTimeNow();
527
528 std::lock_guard<std::mutex> lock(mLock);
529
530 auto key = std::make_pair(name, pid);
531 auto it = mLookupTable.find(key);
532 if (it == mLookupTable.end()) {
533 LOG(INFO) << "WakeLock Stats: A stats entry for, \"" << name
534 << "\" was not found. This is most likely due to it being evicted.";
535 } else {
536 auto staleEntry = it->second;
537 WakeLockInfo updatedEntry = *staleEntry;
538
539 // Update entry
540 TimestampType timeDelta = timeNow - updatedEntry.lastChange;
541 if (updatedEntry.activeCount > 0) {
542 updatedEntry.activeCount--;
543 } else {
544 LOG(ERROR) << "WakeLock Stats: Active count attempted to go below zero for "
545 << "wakelock \"" << name << "\". This is unexpected.";
546 }
547 updatedEntry.isActive = (updatedEntry.activeCount > 0);
548 updatedEntry.activeTime += timeDelta;
549 updatedEntry.maxTime = std::max(updatedEntry.maxTime, updatedEntry.activeTime);
550 updatedEntry.activeTime = updatedEntry.isActive ? updatedEntry.activeTime : 0;
551 updatedEntry.totalTime += timeDelta;
552 updatedEntry.lastChange = timeNow;
553
554 deleteEntry(staleEntry);
555 insertEntry(std::move(updatedEntry));
556 }
557 }
558 /**
559 * Updates the native wakelock stats based on the current time.
560 */
updateNow()561 void WakeLockEntryList::updateNow() {
562 std::lock_guard<std::mutex> lock(mLock);
563
564 TimestampType timeNow = getTimeNow();
565
566 for (std::list<WakeLockInfo>::iterator it = mStats.begin(); it != mStats.end(); ++it) {
567 if (it->isActive) {
568 TimestampType timeDelta = timeNow - it->lastChange;
569 it->activeTime += timeDelta;
570 it->maxTime = std::max(it->maxTime, it->activeTime);
571 it->totalTime += timeDelta;
572 it->lastChange = timeNow;
573 }
574 }
575 }
576
getWakeLockStats(int wakeLockInfoFieldBitMask,std::vector<WakeLockInfo> * aidl_return) const577 void WakeLockEntryList::getWakeLockStats(int wakeLockInfoFieldBitMask,
578 std::vector<WakeLockInfo>* aidl_return) const {
579 // Under no circumstances should the lock be held while getting kernel wakelock stats
580 {
581 std::lock_guard<std::mutex> lock(mLock);
582 for (const WakeLockInfo& entry : mStats) {
583 aidl_return->emplace_back(entry);
584 }
585 }
586 getKernelWakelockStats(wakeLockInfoFieldBitMask, aidl_return);
587 }
588
589 } // namespace V1_0
590 } // namespace suspend
591 } // namespace system
592 } // namespace android
593