blob: 2a17a4312e6a11e6c90bbb5cfe3fb391c1fa4526 [file] [log] [blame]
/*
* Copyright (C) 2018 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.
*/
#define LOG_TAG "libpixelpowerstats"
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <pixelpowerstats/PowerStats.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <inttypes.h>
#include <time.h>
namespace android {
namespace hardware {
namespace power {
namespace stats {
namespace V1_0 {
namespace implementation {
void PowerStats::setRailDataProvider(std::unique_ptr<IRailDataProvider> dataProvider) {
mRailDataProvider = std::move(dataProvider);
}
Return<void> PowerStats::getRailInfo(getRailInfo_cb _hidl_cb) {
if (!mRailDataProvider) {
_hidl_cb({}, Status::NOT_SUPPORTED);
return Void();
}
return mRailDataProvider->getRailInfo(_hidl_cb);
}
Return<void> PowerStats::getEnergyData(const hidl_vec<uint32_t> &railIndices,
getEnergyData_cb _hidl_cb) {
if (!mRailDataProvider) {
_hidl_cb({}, Status::NOT_SUPPORTED);
return Void();
}
return mRailDataProvider->getEnergyData(railIndices, _hidl_cb);
}
Return<void> PowerStats::streamEnergyData(uint32_t timeMs, uint32_t samplingRate,
streamEnergyData_cb _hidl_cb) {
if (!mRailDataProvider) {
_hidl_cb({}, 0, 0, Status::NOT_SUPPORTED);
return Void();
}
return mRailDataProvider->streamEnergyData(timeMs, samplingRate, _hidl_cb);
}
uint32_t PowerStats::addPowerEntity(const std::string &name, PowerEntityType type) {
uint32_t id = mPowerEntityInfos.size();
mPowerEntityInfos.push_back({id, name, type});
return id;
}
void PowerStats::addStateResidencyDataProvider(sp<IStateResidencyDataProvider> p) {
std::vector<PowerEntityStateSpace> stateSpaces = p->getStateSpaces();
for (auto stateSpace : stateSpaces) {
mPowerEntityStateSpaces.emplace(stateSpace.powerEntityId, stateSpace);
mStateResidencyDataProviders.emplace(stateSpace.powerEntityId, p);
}
}
Return<void> PowerStats::getPowerEntityInfo(getPowerEntityInfo_cb _hidl_cb) {
// If not configured, return NOT_SUPPORTED
if (mPowerEntityInfos.empty()) {
_hidl_cb({}, Status::NOT_SUPPORTED);
return Void();
}
_hidl_cb(mPowerEntityInfos, Status::SUCCESS);
return Void();
}
Return<void> PowerStats::getPowerEntityStateInfo(const hidl_vec<uint32_t> &powerEntityIds,
getPowerEntityStateInfo_cb _hidl_cb) {
// If not configured, return NOT_SUPPORTED
if (mPowerEntityStateSpaces.empty()) {
_hidl_cb({}, Status::NOT_SUPPORTED);
return Void();
}
std::vector<PowerEntityStateSpace> stateSpaces;
// If powerEntityIds is empty then return state space info for all entities
if (powerEntityIds.size() == 0) {
stateSpaces.reserve(mPowerEntityStateSpaces.size());
for (auto i : mPowerEntityStateSpaces) {
stateSpaces.emplace_back(i.second);
}
_hidl_cb(stateSpaces, Status::SUCCESS);
return Void();
}
// Return state space information only for valid ids
auto ret = Status::SUCCESS;
stateSpaces.reserve(powerEntityIds.size());
for (const uint32_t id : powerEntityIds) {
auto stateSpace = mPowerEntityStateSpaces.find(id);
if (stateSpace != mPowerEntityStateSpaces.end()) {
stateSpaces.emplace_back(stateSpace->second);
} else {
ret = Status::INVALID_INPUT;
}
}
_hidl_cb(stateSpaces, ret);
return Void();
}
Return<void> PowerStats::getPowerEntityStateResidencyData(
const hidl_vec<uint32_t> &powerEntityIds, getPowerEntityStateResidencyData_cb _hidl_cb) {
// If not configured, return NOT_SUPPORTED
if (mStateResidencyDataProviders.empty() || mPowerEntityStateSpaces.empty()) {
_hidl_cb({}, Status::NOT_SUPPORTED);
return Void();
}
// If powerEntityIds is empty then return data for all supported entities
if (powerEntityIds.size() == 0) {
std::vector<uint32_t> ids;
for (auto stateSpace : mPowerEntityStateSpaces) {
ids.emplace_back(stateSpace.first);
}
return getPowerEntityStateResidencyData(ids, _hidl_cb);
}
std::unordered_map<uint32_t, PowerEntityStateResidencyResult> stateResidencies;
std::vector<PowerEntityStateResidencyResult> results;
results.reserve(powerEntityIds.size());
// return results for only the given powerEntityIds
bool invalidInput = false;
bool filesystemError = false;
for (auto id : powerEntityIds) {
auto dataProvider = mStateResidencyDataProviders.find(id);
// skip if the given powerEntityId does not have an associated StateResidencyDataProvider
if (dataProvider == mStateResidencyDataProviders.end()) {
invalidInput = true;
continue;
}
// get the results if we have not already done so.
if (stateResidencies.find(id) == stateResidencies.end()) {
if (!dataProvider->second->getResults(stateResidencies)) {
filesystemError = true;
}
}
// append results
auto stateResidency = stateResidencies.find(id);
if (stateResidency != stateResidencies.end()) {
results.emplace_back(stateResidency->second);
}
}
auto ret = Status::SUCCESS;
if (filesystemError) {
ret = Status::FILESYSTEM_ERROR;
} else if (invalidInput) {
ret = Status::INVALID_INPUT;
}
_hidl_cb(results, ret);
return Void();
}
//
// Debugging utilities to support printing data via debug()
//
static uint64_t getTimeElapsedMs(const struct timespec &now, const struct timespec &then) {
uint64_t thenMs = then.tv_sec * 1000 + (then.tv_nsec / 1000000);
uint64_t nowMs = now.tv_sec * 1000 + (now.tv_nsec / 1000000);
return (nowMs - thenMs);
}
static const char RESIDENCY_HEADER[] =
"\n============= PowerStats HAL 1.0 state residencies ==============\n";
static const char RESIDENCY_FOOTER[] =
"========== End of PowerStats HAL 1.0 state residencies ==========\n";
static bool DumpResidencyDataToFd(
const std::unordered_map<uint32_t, std::string> &entityNames,
const std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> stateNames,
const hidl_vec<PowerEntityStateResidencyResult> &results, int fd) {
std::ostringstream dumpStats;
const char *headerFormat = " %14s %14s %16s %15s %17s\n";
const char *dataFormat =
" %14s %14s %13" PRIu64 " ms %15" PRIu64 " %14" PRIu64 " ms\n";
dumpStats << RESIDENCY_HEADER;
dumpStats << android::base::StringPrintf(headerFormat, "Entity", "State", "Total time",
"Total entries", "Last entry tstamp");
for (auto result : results) {
for (auto stateResidency : result.stateResidencyData) {
dumpStats << android::base::StringPrintf(
dataFormat, entityNames.at(result.powerEntityId).c_str(),
stateNames.at(result.powerEntityId)
.at(stateResidency.powerEntityStateId).c_str(),
stateResidency.totalTimeInStateMs, stateResidency.totalStateEntryCount,
stateResidency.lastEntryTimestampMs);
}
}
dumpStats << RESIDENCY_FOOTER;
return android::base::WriteStringToFd(dumpStats.str(), fd);
}
static bool DumpResidencyDataDiffToFd(
const std::unordered_map<uint32_t, std::string> &entityNames,
const std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> stateNames,
uint64_t elapsedTimeMs, const hidl_vec<PowerEntityStateResidencyResult> &prevResults,
const hidl_vec<PowerEntityStateResidencyResult> &results, int fd) {
std::ostringstream dumpStats;
const char *headerFormat = " %14s %14s %16s (%14s) %15s (%16s) %17s (%14s)\n";
const char *dataFormatWithDelta =
" %14s %14s %13" PRIu64 " ms (%14" PRId64 ") %15" PRIu64 " (%16" PRId64 ")"
" %14" PRIu64 " ms (%14" PRId64 ")\n";
const char *dataFormatWithoutDelta =
" %14s %14s %13" PRIu64 " ms ( none) %15" PRIu64 " ( none)"
" %14" PRIu64 " ms ( none)\n";
dumpStats << RESIDENCY_HEADER;
dumpStats << "Elapsed time: "
<< (elapsedTimeMs == 0 ? "unknown" : std::to_string(elapsedTimeMs)) << " ms\n";
dumpStats << android::base::StringPrintf(headerFormat, "Entity", "State", "Total time",
"Delta ", "Total entries", "Delta ",
"Last entry tstamp", "Delta ");
// Process prevResults into a 2-tier lookup table for easy reference
std::unordered_map<uint32_t, std::unordered_map<uint32_t, PowerEntityStateResidencyData>>
prevResultsMap;
for (auto prevResult : prevResults) {
prevResultsMap.emplace(prevResult.powerEntityId,
std::unordered_map<uint32_t, PowerEntityStateResidencyData>());
for (auto stateResidency : prevResult.stateResidencyData) {
prevResultsMap.at(prevResult.powerEntityId)
.emplace(stateResidency.powerEntityStateId, stateResidency);
}
}
// Iterate over the new result data (one "result" per entity)
for (auto result : results) {
uint32_t entityId = result.powerEntityId;
const char *entityName = entityNames.at(entityId).c_str();
// Look up previous result data for the same entity
auto prevEntityResultIt = prevResultsMap.find(entityId);
// Iterate over individual states within the current entity's new result
for (auto stateResidency : result.stateResidencyData) {
uint32_t stateId = stateResidency.powerEntityStateId;
const char *stateName = stateNames.at(entityId).at(stateId).c_str();
// If a previous result was found for the same entity, see if that
// result also contains data for the current state
bool prevValueFound = false;
if (prevEntityResultIt != prevResultsMap.end()) {
auto prevStateResidencyIt = prevEntityResultIt->second.find(stateId);
// If a previous result was found for the current entity and state, calculate the
// deltas and display them along with new result
if (prevStateResidencyIt != prevEntityResultIt->second.end()) {
int64_t deltaTotalTime = stateResidency.totalTimeInStateMs -
prevStateResidencyIt->second.totalTimeInStateMs;
int64_t deltaTotalCount = stateResidency.totalStateEntryCount -
prevStateResidencyIt->second.totalStateEntryCount;
int64_t deltaTimestamp = stateResidency.lastEntryTimestampMs -
prevStateResidencyIt->second.lastEntryTimestampMs;
dumpStats << android::base::StringPrintf(
dataFormatWithDelta, entityName, stateName,
stateResidency.totalTimeInStateMs, deltaTotalTime,
stateResidency.totalStateEntryCount, deltaTotalCount,
stateResidency.lastEntryTimestampMs, deltaTimestamp);
prevValueFound = true;
}
}
// If no previous result was found for the current entity and state, display the new
// result without deltas
if (!prevValueFound) {
dumpStats << android::base::StringPrintf(
dataFormatWithoutDelta, entityName, stateName,
stateResidency.totalTimeInStateMs, stateResidency.totalStateEntryCount,
stateResidency.lastEntryTimestampMs);
}
}
}
dumpStats << RESIDENCY_FOOTER;
return android::base::WriteStringToFd(dumpStats.str(), fd);
}
void PowerStats::debugStateResidency(const std::unordered_map<uint32_t, std::string> &entityNames,
int fd, bool delta) {
static struct timespec prevDataTime;
static bool prevDataTimeValid = false;
struct timespec dataTime;
bool dataTimeValid;
// Get power entity state space information
Status status;
hidl_vec<PowerEntityStateSpace> stateSpaces;
getPowerEntityStateInfo({}, [&status, &stateSpaces](auto rStateSpaces, auto rStatus) {
status = rStatus;
stateSpaces = rStateSpaces;
});
if (status != Status::SUCCESS) {
LOG(ERROR) << "Error getting state info";
return;
}
// Construct lookup table of powerEntityId, powerEntityStateId to state name
std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> stateNames;
for (auto stateSpace : stateSpaces) {
stateNames.emplace(stateSpace.powerEntityId, std::unordered_map<uint32_t, std::string>());
auto &entityStateNames = stateNames.at(stateSpace.powerEntityId);
for (auto state : stateSpace.states) {
entityStateNames.emplace(state.powerEntityStateId, state.powerEntityStateName);
}
}
// Get power entity state residency data
hidl_vec<PowerEntityStateResidencyResult> results;
getPowerEntityStateResidencyData(
{}, [&status, &results, &dataTime, &dataTimeValid](auto rResults, auto rStatus) {
status = rStatus;
results = rResults;
dataTimeValid = (clock_gettime(CLOCK_BOOTTIME, &dataTime) == 0);
});
// This implementation of getPowerEntityStateResidencyData supports the
// return of partial results if status == FILESYSTEM_ERROR.
if (status != Status::SUCCESS) {
LOG(ERROR) << "Error getting residency data -- Some results missing";
}
if (!delta) {
// If no delta argument was supplied, just dump the latest data
if (!DumpResidencyDataToFd(entityNames, stateNames, results, fd)) {
PLOG(ERROR) << "Failed to dump residency data to fd";
}
} else {
// If the delta argument was supplied, calculate the elapsed time since the previous
// result and then dump the latest data along with elapsed time and deltas
static hidl_vec<PowerEntityStateResidencyResult> prevResults;
uint64_t elapsedTimeMs = 0;
if (dataTimeValid && prevDataTimeValid) {
elapsedTimeMs = getTimeElapsedMs(dataTime, prevDataTime);
}
if (!DumpResidencyDataDiffToFd(entityNames, stateNames, elapsedTimeMs, prevResults,
results, fd)) {
PLOG(ERROR) << "Failed to dump residency data delta to fd";
}
prevResults = results;
prevDataTime = dataTime;
prevDataTimeValid = dataTimeValid;
}
}
static const char ENERGYDATA_HEADER[] =
"\n============= PowerStats HAL 1.0 rail energy data ==============\n";
static const char ENERGYDATA_FOOTER[] =
"========== End of PowerStats HAL 1.0 rail energy data ==========\n";
static bool DumpEnergyDataToFd(
const std::unordered_map<uint32_t, std::pair<std::string, std::string>> &railNames,
const hidl_vec<EnergyData> &energyData, int fd) {
std::ostringstream dumpStats;
const char *headerFormat = " %14s %18s %18s\n";
const char *dataFormat = " %14s %18s %14.2f mWs\n";
dumpStats << ENERGYDATA_HEADER;
dumpStats << android::base::StringPrintf(headerFormat, "Subsys", "Rail", "Cumulative Energy");
for (auto data : energyData) {
dumpStats << android::base::StringPrintf(dataFormat, railNames.at(data.index).first.c_str(),
railNames.at(data.index).second.c_str(),
static_cast<float>(data.energy) / 1000.0);
}
dumpStats << ENERGYDATA_FOOTER;
return android::base::WriteStringToFd(dumpStats.str(), fd);
}
static bool DumpEnergyDataDiffToFd(
const std::unordered_map<uint32_t, std::pair<std::string, std::string>> &railNames,
uint64_t elapsedTimeMs, const hidl_vec<EnergyData> &prevEnergyData,
const hidl_vec<EnergyData> &energyData, int fd) {
std::ostringstream dumpStats;
const char *headerFormat = " %14s %18s %18s (%14s)\n";
const char *dataFormatWithDelta = " %14s %18s %14.2f mWs (%14.2f)\n";
const char *dataFormatWithoutDelta = " %14s %18s %14.2f mWs ( none)\n";
dumpStats << ENERGYDATA_HEADER;
dumpStats << "Elapsed time: "
<< (elapsedTimeMs == 0 ? "unknown" : std::to_string(elapsedTimeMs)) << " ms\n";
dumpStats << android::base::StringPrintf(headerFormat, "Subsys", "Rail", "Cumulative Energy",
"Delta ");
std::unordered_map<uint32_t, uint64_t> prevEnergyDataMap;
for (auto data : prevEnergyData) {
prevEnergyDataMap.emplace(data.index, data.energy);
}
for (auto data : energyData) {
const char *subsysName = railNames.at(data.index).first.c_str();
const char *railName = railNames.at(data.index).second.c_str();
auto prevEnergyDataIt = prevEnergyDataMap.find(data.index);
if (prevEnergyDataIt != prevEnergyDataMap.end()) {
int64_t deltaEnergy = data.energy - prevEnergyDataIt->second;
dumpStats << android::base::StringPrintf(dataFormatWithDelta, subsysName, railName,
static_cast<float>(data.energy) / 1000.0,
static_cast<float>(deltaEnergy) / 1000.0);
} else {
dumpStats << android::base::StringPrintf(dataFormatWithoutDelta, subsysName, railName,
static_cast<float>(data.energy) / 1000.0);
}
}
dumpStats << ENERGYDATA_FOOTER;
return android::base::WriteStringToFd(dumpStats.str(), fd);
}
void PowerStats::debugEnergyData(int fd, bool delta) {
static struct timespec prevDataTime;
static bool prevDataTimeValid = false;
struct timespec dataTime;
bool dataTimeValid = false;
std::unordered_map<uint32_t, std::pair<std::string, std::string>> railNames;
getRailInfo([&railNames](auto infos, auto /* status */) {
// Don't care about the status. infos will be nonempty if rail energy is supported.
for (auto info : infos) {
railNames.emplace(info.index, std::make_pair(info.subsysName, info.railName));
}
});
if (railNames.empty()) {
return;
}
Status status;
hidl_vec<EnergyData> energyData;
getEnergyData(
{}, [&status, &energyData, &dataTime, &dataTimeValid](auto rEnergyData, auto rStatus) {
status = rStatus;
energyData = rEnergyData;
dataTimeValid = (clock_gettime(CLOCK_BOOTTIME, &dataTime) == 0);
});
// getEnergyData returns no results if status != SUCCESS.
if (status != Status::SUCCESS) {
LOG(ERROR) << "Error getting rail data";
return;
}
if (!delta) {
if (!DumpEnergyDataToFd(railNames, energyData, fd)) {
PLOG(ERROR) << "Failed to dump energy data to fd";
}
} else {
// If the delta argument was supplied, calculate the elapsed time since the previous
// result and then dump the latest data along with elapsed time and deltas
static hidl_vec<EnergyData> prevEnergyData;
uint64_t elapsedTimeMs = 0;
if (dataTimeValid && prevDataTimeValid) {
elapsedTimeMs = getTimeElapsedMs(dataTime, prevDataTime);
}
if (!DumpEnergyDataDiffToFd(railNames, elapsedTimeMs, prevEnergyData, energyData, fd)) {
PLOG(ERROR) << "Failed to dump energy data delta to fd";
}
prevEnergyData = energyData;
prevDataTime = dataTime;
prevDataTimeValid = dataTimeValid;
}
}
Return<void> PowerStats::debug(const hidl_handle &handle, const hidl_vec<hidl_string> &args) {
if (handle == nullptr || handle->numFds < 1) {
return Void();
}
int fd = handle->data[0];
bool delta = (args.size() == 1) && (args[0] == "delta");
// Get power entity information, which is common across all supported data categories
Status status;
hidl_vec<PowerEntityInfo> stateInfos;
getPowerEntityInfo([&status, &stateInfos](auto rInfos, auto rStatus) {
status = rStatus;
stateInfos = rInfos;
});
if (status != Status::SUCCESS) {
LOG(ERROR) << "Error getting power entity info";
return Void();
}
// Construct lookup table of powerEntityId to name
std::unordered_map<uint32_t, std::string> entityNames;
for (auto info : stateInfos) {
entityNames.emplace(info.powerEntityId, info.powerEntityName);
}
// Generate debug output for supported data categories
debugStateResidency(entityNames, fd, delta);
// Generate debug output for energy data
debugEnergyData(fd, delta);
fsync(fd);
return Void();
}
} // namespace implementation
} // namespace V1_0
} // namespace stats
} // namespace power
} // namespace hardware
} // namespace android