412 lines
14 KiB
C++
412 lines
14 KiB
C++
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "WakeLockEntryList.h"
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/stringprintf.h>
|
|
|
|
#include <iomanip>
|
|
|
|
using android::base::ParseInt;
|
|
using android::base::ReadFdToString;
|
|
using android::base::Readlink;
|
|
using android::base::StringPrintf;
|
|
|
|
namespace android {
|
|
namespace system {
|
|
namespace suspend {
|
|
namespace V1_0 {
|
|
|
|
static std::ostream& operator<<(std::ostream& out, const WakeLockInfo& entry) {
|
|
const char* sep = " | ";
|
|
const char* notApplicable = "---";
|
|
bool kernelWakelock = entry.isKernelWakelock;
|
|
|
|
// clang-format off
|
|
out << sep
|
|
<< std::left << std::setw(30) << entry.name << sep
|
|
<< std::right << std::setw(6)
|
|
<< ((kernelWakelock) ? notApplicable : std::to_string(entry.pid)) << sep
|
|
<< std::left << std::setw(6) << ((kernelWakelock) ? "Kernel" : "Native") << sep
|
|
<< std::left << std::setw(8) << ((entry.isActive) ? "Active" : "Inactive") << sep
|
|
<< std::right << std::setw(12) << entry.activeCount << sep
|
|
<< std::right << std::setw(12) << std::to_string(entry.totalTime) + "ms" << sep
|
|
<< std::right << std::setw(12) << std::to_string(entry.maxTime) + "ms" << sep
|
|
<< std::right << std::setw(12)
|
|
<< ((kernelWakelock) ? std::to_string(entry.eventCount) : notApplicable) << sep
|
|
<< std::right << std::setw(12)
|
|
<< ((kernelWakelock) ? std::to_string(entry.wakeupCount) : notApplicable) << sep
|
|
<< std::right << std::setw(12)
|
|
<< ((kernelWakelock) ? std::to_string(entry.expireCount) : notApplicable) << sep
|
|
<< std::right << std::setw(20)
|
|
<< ((kernelWakelock) ? std::to_string(entry.preventSuspendTime) + "ms" : notApplicable)
|
|
<< sep
|
|
<< std::right << std::setw(16) << std::to_string(entry.lastChange) + "ms" << sep;
|
|
// clang-format on
|
|
|
|
return out;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& out, const WakeLockEntryList& list) {
|
|
std::vector<WakeLockInfo> wlStats;
|
|
list.getWakeLockStats(&wlStats);
|
|
int width = 194;
|
|
const char* sep = " | ";
|
|
std::stringstream ss;
|
|
ss << " " << std::setfill('-') << std::setw(width) << "\n";
|
|
std::string div = ss.str();
|
|
|
|
out << div;
|
|
|
|
std::stringstream header;
|
|
header << sep << std::right << std::setw(((width - 14) / 2) + 14) << "WAKELOCK STATS"
|
|
<< std::right << std::setw((width - 14) / 2) << sep << "\n";
|
|
out << header.str();
|
|
|
|
out << div;
|
|
|
|
// Col names
|
|
// clang-format off
|
|
out << sep
|
|
<< std::left << std::setw(30) << "NAME" << sep
|
|
<< std::left << std::setw(6) << "PID" << sep
|
|
<< std::left << std::setw(6) << "TYPE" << sep
|
|
<< std::left << std::setw(8) << "STATUS" << sep
|
|
<< std::left << std::setw(12) << "ACTIVE COUNT" << sep
|
|
<< std::left << std::setw(12) << "TOTAL TIME" << sep
|
|
<< std::left << std::setw(12) << "MAX TIME" << sep
|
|
<< std::left << std::setw(12) << "EVENT COUNT" << sep
|
|
<< std::left << std::setw(12) << "WAKEUP COUNT" << sep
|
|
<< std::left << std::setw(12) << "EXPIRE COUNT" << sep
|
|
<< std::left << std::setw(20) << "PREVENT SUSPEND TIME" << sep
|
|
<< std::left << std::setw(16) << "LAST CHANGE" << sep
|
|
<< "\n";
|
|
// clang-format on
|
|
|
|
out << div;
|
|
|
|
// Rows
|
|
for (const WakeLockInfo& entry : wlStats) {
|
|
out << entry << "\n";
|
|
}
|
|
|
|
out << div;
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Returns the monotonic time in milliseconds.
|
|
*/
|
|
TimestampType getTimeNow() {
|
|
timespec monotime;
|
|
clock_gettime(CLOCK_MONOTONIC, &monotime);
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::nanoseconds{monotime.tv_nsec})
|
|
.count() +
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::seconds{monotime.tv_sec})
|
|
.count();
|
|
}
|
|
|
|
WakeLockEntryList::WakeLockEntryList(size_t capacity, unique_fd kernelWakelockStatsFd)
|
|
: mCapacity(capacity), mKernelWakelockStatsFd(std::move(kernelWakelockStatsFd)) {}
|
|
|
|
/**
|
|
* Evicts LRU from back of list if stats is at capacity.
|
|
*/
|
|
void WakeLockEntryList::evictIfFull() {
|
|
if (mStats.size() == mCapacity) {
|
|
auto evictIt = mStats.end();
|
|
std::advance(evictIt, -1);
|
|
auto evictKey = std::make_pair(evictIt->name, evictIt->pid);
|
|
mLookupTable.erase(evictKey);
|
|
mStats.erase(evictIt);
|
|
LOG(ERROR) << "WakeLock Stats: Stats capacity met, consider adjusting capacity to "
|
|
"avoid stats eviction.";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inserts entry as MRU.
|
|
*/
|
|
void WakeLockEntryList::insertEntry(WakeLockInfo entry) {
|
|
auto key = std::make_pair(entry.name, entry.pid);
|
|
mStats.emplace_front(std::move(entry));
|
|
mLookupTable[key] = mStats.begin();
|
|
}
|
|
|
|
/**
|
|
* Removes entry from the stats list.
|
|
*/
|
|
void WakeLockEntryList::deleteEntry(std::list<WakeLockInfo>::iterator entry) {
|
|
auto key = std::make_pair(entry->name, entry->pid);
|
|
mLookupTable.erase(key);
|
|
mStats.erase(entry);
|
|
}
|
|
|
|
/**
|
|
* Creates and returns a native wakelock entry.
|
|
*/
|
|
WakeLockInfo WakeLockEntryList::createNativeEntry(const std::string& name, int pid,
|
|
TimestampType timeNow) const {
|
|
WakeLockInfo info;
|
|
|
|
info.name = name;
|
|
// It only makes sense to create a new entry on initial activation of the lock.
|
|
info.activeCount = 1;
|
|
info.lastChange = timeNow;
|
|
info.maxTime = 0;
|
|
info.totalTime = 0;
|
|
info.isActive = true;
|
|
info.activeTime = 0;
|
|
info.isKernelWakelock = false;
|
|
|
|
info.pid = pid;
|
|
|
|
info.eventCount = 0;
|
|
info.expireCount = 0;
|
|
info.preventSuspendTime = 0;
|
|
info.wakeupCount = 0;
|
|
|
|
return info;
|
|
}
|
|
|
|
/*
|
|
* Checks whether a given directory entry is a stat file we're interested in.
|
|
*/
|
|
static bool isStatFile(const struct dirent* de) {
|
|
const char* statName = de->d_name;
|
|
if (!strcmp(statName, ".") || !strcmp(statName, "..") || !strcmp(statName, "device") ||
|
|
!strcmp(statName, "power") || !strcmp(statName, "subsystem") ||
|
|
!strcmp(statName, "uevent")) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Creates and returns a kernel wakelock entry with data read from mKernelWakelockStatsFd
|
|
*/
|
|
WakeLockInfo WakeLockEntryList::createKernelEntry(const std::string& kwlId) const {
|
|
WakeLockInfo info;
|
|
|
|
info.activeCount = 0;
|
|
info.lastChange = 0;
|
|
info.maxTime = 0;
|
|
info.totalTime = 0;
|
|
info.isActive = false;
|
|
info.activeTime = 0;
|
|
info.isKernelWakelock = true;
|
|
|
|
info.pid = -1; // N/A
|
|
|
|
info.eventCount = 0;
|
|
info.expireCount = 0;
|
|
info.preventSuspendTime = 0;
|
|
info.wakeupCount = 0;
|
|
|
|
unique_fd wakelockFd{TEMP_FAILURE_RETRY(
|
|
openat(mKernelWakelockStatsFd, kwlId.c_str(), O_DIRECTORY | O_CLOEXEC | O_RDONLY))};
|
|
if (wakelockFd < 0) {
|
|
char buf[PATH_MAX];
|
|
ssize_t data_length =
|
|
readlinkat(mKernelWakelockStatsFd, kwlId.c_str(), buf, sizeof(buf) - 1);
|
|
if (data_length <= 0 || strncmp(kwlId.c_str(), buf, kwlId.length()) == 0) {
|
|
buf[0] = '\0';
|
|
}
|
|
PLOG(ERROR) << "Error opening kernel wakelock stats for: " << kwlId << " (" << buf << ")";
|
|
}
|
|
|
|
std::unique_ptr<DIR, decltype(&closedir)> wakelockDp(fdopendir(dup(wakelockFd.get())),
|
|
&closedir);
|
|
if (wakelockDp) {
|
|
struct dirent* de;
|
|
while ((de = readdir(wakelockDp.get()))) {
|
|
if (!isStatFile(de)) {
|
|
continue;
|
|
}
|
|
|
|
std::string statName(de->d_name);
|
|
unique_fd statFd{
|
|
TEMP_FAILURE_RETRY(openat(wakelockFd, statName.c_str(), O_CLOEXEC | O_RDONLY))};
|
|
if (statFd < 0) {
|
|
PLOG(ERROR) << "Error opening " << statName << " for " << kwlId;
|
|
}
|
|
|
|
std::string valStr;
|
|
if (!ReadFdToString(statFd.get(), &valStr)) {
|
|
PLOG(ERROR) << "Error reading " << statName << " for " << kwlId;
|
|
continue;
|
|
}
|
|
|
|
// Trim newline
|
|
valStr.erase(std::remove(valStr.begin(), valStr.end(), '\n'), valStr.end());
|
|
|
|
if (statName == "name") {
|
|
info.name = valStr;
|
|
continue;
|
|
}
|
|
|
|
int64_t statVal;
|
|
if (!ParseInt(valStr, &statVal)) {
|
|
std::string path;
|
|
if (Readlink(StringPrintf("/proc/self/fd/%d", statFd.get()), &path)) {
|
|
LOG(ERROR) << "Unexpected format for wakelock stat value (" << valStr
|
|
<< ") from file: " << path;
|
|
} else {
|
|
LOG(ERROR) << "Unexpected format for wakelock stat value (" << valStr << ")";
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (statName == "active_count") {
|
|
info.activeCount = statVal;
|
|
} else if (statName == "active_time_ms") {
|
|
info.activeTime = statVal;
|
|
} else if (statName == "event_count") {
|
|
info.eventCount = statVal;
|
|
} else if (statName == "expire_count") {
|
|
info.expireCount = statVal;
|
|
} else if (statName == "last_change_ms") {
|
|
info.lastChange = statVal;
|
|
} else if (statName == "max_time_ms") {
|
|
info.maxTime = statVal;
|
|
} else if (statName == "prevent_suspend_time_ms") {
|
|
info.preventSuspendTime = statVal;
|
|
} else if (statName == "total_time_ms") {
|
|
info.totalTime = statVal;
|
|
} else if (statName == "wakeup_count") {
|
|
info.wakeupCount = statVal;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Derived stats
|
|
info.isActive = info.activeTime > 0;
|
|
|
|
return info;
|
|
}
|
|
|
|
void WakeLockEntryList::getKernelWakelockStats(std::vector<WakeLockInfo>* aidl_return) const {
|
|
std::unique_ptr<DIR, decltype(&closedir)> dp(fdopendir(dup(mKernelWakelockStatsFd.get())),
|
|
&closedir);
|
|
if (dp) {
|
|
// rewinddir, else subsequent calls will not get any kernel wakelocks.
|
|
rewinddir(dp.get());
|
|
|
|
struct dirent* de;
|
|
while ((de = readdir(dp.get()))) {
|
|
std::string kwlId(de->d_name);
|
|
if ((kwlId == ".") || (kwlId == "..")) {
|
|
continue;
|
|
}
|
|
WakeLockInfo entry = createKernelEntry(kwlId);
|
|
aidl_return->emplace_back(std::move(entry));
|
|
}
|
|
}
|
|
}
|
|
|
|
void WakeLockEntryList::updateOnAcquire(const std::string& name, int pid) {
|
|
TimestampType timeNow = getTimeNow();
|
|
|
|
std::lock_guard<std::mutex> lock(mStatsLock);
|
|
|
|
auto key = std::make_pair(name, pid);
|
|
auto it = mLookupTable.find(key);
|
|
if (it == mLookupTable.end()) {
|
|
evictIfFull();
|
|
WakeLockInfo newEntry = createNativeEntry(name, pid, timeNow);
|
|
insertEntry(newEntry);
|
|
} else {
|
|
auto staleEntry = it->second;
|
|
WakeLockInfo updatedEntry = *staleEntry;
|
|
|
|
// Update entry
|
|
updatedEntry.isActive = true;
|
|
updatedEntry.activeTime = 0;
|
|
updatedEntry.activeCount++;
|
|
updatedEntry.lastChange = timeNow;
|
|
|
|
deleteEntry(staleEntry);
|
|
insertEntry(std::move(updatedEntry));
|
|
}
|
|
}
|
|
|
|
void WakeLockEntryList::updateOnRelease(const std::string& name, int pid) {
|
|
TimestampType timeNow = getTimeNow();
|
|
|
|
std::lock_guard<std::mutex> lock(mStatsLock);
|
|
|
|
auto key = std::make_pair(name, pid);
|
|
auto it = mLookupTable.find(key);
|
|
if (it == mLookupTable.end()) {
|
|
LOG(INFO) << "WakeLock Stats: A stats entry for, \"" << name
|
|
<< "\" was not found. This is most likely due to it being evicted.";
|
|
} else {
|
|
auto staleEntry = it->second;
|
|
WakeLockInfo updatedEntry = *staleEntry;
|
|
|
|
// Update entry
|
|
TimestampType timeDelta = timeNow - updatedEntry.lastChange;
|
|
updatedEntry.isActive = false;
|
|
updatedEntry.activeTime += timeDelta;
|
|
updatedEntry.maxTime = std::max(updatedEntry.maxTime, updatedEntry.activeTime);
|
|
updatedEntry.activeTime = 0; // No longer active
|
|
updatedEntry.totalTime += timeDelta;
|
|
updatedEntry.lastChange = timeNow;
|
|
|
|
deleteEntry(staleEntry);
|
|
insertEntry(std::move(updatedEntry));
|
|
}
|
|
}
|
|
/**
|
|
* Updates the native wakelock stats based on the current time.
|
|
*/
|
|
void WakeLockEntryList::updateNow() {
|
|
std::lock_guard<std::mutex> lock(mStatsLock);
|
|
|
|
TimestampType timeNow = getTimeNow();
|
|
|
|
for (std::list<WakeLockInfo>::iterator it = mStats.begin(); it != mStats.end(); ++it) {
|
|
if (it->isActive) {
|
|
TimestampType timeDelta = timeNow - it->lastChange;
|
|
it->activeTime += timeDelta;
|
|
it->maxTime = std::max(it->maxTime, it->activeTime);
|
|
it->totalTime += timeDelta;
|
|
it->lastChange = timeNow;
|
|
}
|
|
}
|
|
}
|
|
|
|
void WakeLockEntryList::getWakeLockStats(std::vector<WakeLockInfo>* aidl_return) const {
|
|
// Under no circumstances should the lock be held while getting kernel wakelock stats
|
|
{
|
|
std::lock_guard<std::mutex> lock(mStatsLock);
|
|
for (const WakeLockInfo& entry : mStats) {
|
|
aidl_return->emplace_back(entry);
|
|
}
|
|
}
|
|
getKernelWakelockStats(aidl_return);
|
|
}
|
|
|
|
} // namespace V1_0
|
|
} // namespace suspend
|
|
} // namespace system
|
|
} // namespace android
|