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