/* * 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 #include #include #include #include #include #include #include #include #include #include namespace android { namespace hardware { namespace power { namespace stats { namespace V1_0 { namespace implementation { void PowerStats::setRailDataProvider(std::unique_ptr dataProvider) { mRailDataProvider = std::move(dataProvider); } Return PowerStats::getRailInfo(getRailInfo_cb _hidl_cb) { if (!mRailDataProvider) { _hidl_cb({}, Status::NOT_SUPPORTED); return Void(); } return mRailDataProvider->getRailInfo(_hidl_cb); } Return PowerStats::getEnergyData(const hidl_vec &railIndices, getEnergyData_cb _hidl_cb) { if (!mRailDataProvider) { _hidl_cb({}, Status::NOT_SUPPORTED); return Void(); } return mRailDataProvider->getEnergyData(railIndices, _hidl_cb); } Return 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 p) { std::vector stateSpaces = p->getStateSpaces(); for (auto stateSpace : stateSpaces) { mPowerEntityStateSpaces.emplace(stateSpace.powerEntityId, stateSpace); mStateResidencyDataProviders.emplace(stateSpace.powerEntityId, p); } } Return 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 PowerStats::getPowerEntityStateInfo(const hidl_vec &powerEntityIds, getPowerEntityStateInfo_cb _hidl_cb) { // If not configured, return NOT_SUPPORTED if (mPowerEntityStateSpaces.empty()) { _hidl_cb({}, Status::NOT_SUPPORTED); return Void(); } std::vector 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 PowerStats::getPowerEntityStateResidencyData( const hidl_vec &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 ids; for (auto stateSpace : mPowerEntityStateSpaces) { ids.emplace_back(stateSpace.first); } return getPowerEntityStateResidencyData(ids, _hidl_cb); } std::unordered_map stateResidencies; std::vector 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 &entityNames, const std::unordered_map> stateNames, const hidl_vec &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 &entityNames, const std::unordered_map> stateNames, uint64_t elapsedTimeMs, const hidl_vec &prevResults, const hidl_vec &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> prevResultsMap; for (auto prevResult : prevResults) { prevResultsMap.emplace(prevResult.powerEntityId, std::unordered_map()); 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 &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 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> stateNames; for (auto stateSpace : stateSpaces) { stateNames.emplace(stateSpace.powerEntityId, std::unordered_map()); 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 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 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> &railNames, const hidl_vec &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(data.energy) / 1000.0); } dumpStats << ENERGYDATA_FOOTER; return android::base::WriteStringToFd(dumpStats.str(), fd); } static bool DumpEnergyDataDiffToFd( const std::unordered_map> &railNames, uint64_t elapsedTimeMs, const hidl_vec &prevEnergyData, const hidl_vec &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 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(data.energy) / 1000.0, static_cast(deltaEnergy) / 1000.0); } else { dumpStats << android::base::StringPrintf(dataFormatWithoutDelta, subsysName, railName, static_cast(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> 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; 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 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 PowerStats::debug(const hidl_handle &handle, const hidl_vec &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 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 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