| /* |
| * 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 |