diff options
author | Xin Li <delphij@google.com> | 2024-03-06 09:30:01 -0800 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2024-03-06 09:30:01 -0800 |
commit | 7ce1bf3229894cf8792b17ebd25404f194ebb8a3 (patch) | |
tree | 6054814f4a387f298ba278c89ee779ffa6e67e82 | |
parent | e4127a7e5bb05096c138ae44a035f2bf52fecc2b (diff) | |
parent | 7f749634995ddf3820f404359c68f8456964ef22 (diff) | |
download | pixel-7ce1bf3229894cf8792b17ebd25404f194ebb8a3.tar.gz |
Bug: 319669529
Merged-In: I8f1863cbde90b5bba929629615d0b11d11961650
Change-Id: I6cb962702e9f1473287e1da429272399b3ca93bf
97 files changed, 6829 insertions, 957 deletions
diff --git a/atrace/OWNERS b/atrace/OWNERS index 05e234ae..a7801e6d 100644 --- a/atrace/OWNERS +++ b/atrace/OWNERS @@ -1,2 +1,4 @@ wvw@google.com -namhyung@google.com +jenhaochen@google.com +paillon@google.com +liumartin@google.com diff --git a/battery_mitigation/Android.bp b/battery_mitigation/Android.bp index 1a5d6c6e..6b5e1228 100644 --- a/battery_mitigation/Android.bp +++ b/battery_mitigation/Android.bp @@ -24,6 +24,7 @@ cc_library { vendor_available: true, srcs: [ "BatteryMitigation.cpp", + "BatteryMitigationService.cpp", "MitigationThermalManager.cpp", ], static_libs: [ diff --git a/battery_mitigation/BatteryMitigation.cpp b/battery_mitigation/BatteryMitigation.cpp index 6aadaa48..10c944cc 100644 --- a/battery_mitigation/BatteryMitigation.cpp +++ b/battery_mitigation/BatteryMitigation.cpp @@ -18,7 +18,6 @@ #include <sstream> -#define MAX_BROWNOUT_DATA_AGE_MINUTES 5 #define ONE_SECOND_IN_US 1000000 namespace android { @@ -26,9 +25,11 @@ namespace hardware { namespace google { namespace pixel { +using android::base::ReadFileToString; + BatteryMitigation::BatteryMitigation(const struct MitigationConfig::Config &cfg) { - mThermalMgr = &MitigationThermalManager::getInstance(); - mThermalMgr->updateConfig(cfg); + mThermalMgr = &MitigationThermalManager::getInstance(); + mThermalMgr->updateConfig(cfg); } bool BatteryMitigation::isMitigationLogTimeValid(std::chrono::system_clock::time_point startTime, @@ -36,7 +37,7 @@ bool BatteryMitigation::isMitigationLogTimeValid(std::chrono::system_clock::time const char *const timestampFormat, const std::regex pattern) { std::string logFile; - if (!android::base::ReadFileToString(logFilePath, &logFile)) { + if (!ReadFileToString(logFilePath, &logFile)) { return false; } std::istringstream content(logFile); @@ -66,7 +67,7 @@ bool BatteryMitigation::isMitigationLogTimeValid(std::chrono::system_clock::time auto delta = epoch_startTime - epoch_logFileTime; auto delta_minutes = delta / 60; - if ((delta_minutes < MAX_BROWNOUT_DATA_AGE_MINUTES) && (delta_minutes >= 0)) { + if (delta_minutes >= 0) { return true; } } diff --git a/battery_mitigation/BatteryMitigationService.cpp b/battery_mitigation/BatteryMitigationService.cpp new file mode 100644 index 00000000..3357fdfa --- /dev/null +++ b/battery_mitigation/BatteryMitigationService.cpp @@ -0,0 +1,1128 @@ +/* + * Copyright (C) 2023 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 <battery_mitigation/BatteryMitigationService.h> + +namespace android { +namespace hardware { +namespace google { +namespace pixel { + +using android::base::ReadFileToString; +using android::base::StartsWith; +using android::hardware::google::pixel::MitigationConfig; + +const struct BrownoutStatsCSVFields brownoutStatsCSVFields = { + .triggered_time = "triggered_timestamp", + .triggered_idx = "triggered_irq", + .battery_soc = "battery_soc", + .battery_temp = "battery_temp", + .battery_cycle = "battery_cycle", + .voltage_now = "voltage_now", + .current_now = "current_now", + .cpu0_freq = "dvfs_channel1", + .cpu1_freq = "dvfs_channel2", + .cpu2_freq = "dvfs_channel3", + .gpu_freq = "dvfs_channel4", + .tpu_freq = "dvfs_channel5", + .aur_freq = "dvfs_channel6", + .odpm_prefix = "odpm_channel_", +}; + +BatteryMitigationService::BatteryMitigationService( + const struct MitigationConfig::EventThreadConfig &eventThreadCfg) + :cfg(eventThreadCfg) { + initTotalNumericSysfsPaths(); + initPmicRelated(); +} + +BatteryMitigationService::~BatteryMitigationService() { + stopEventThread(threadStop, wakeupEventFd, brownoutEventThread); + tearDownBrownoutEventThread(); + stopEventThread(triggerThreadStop, triggeredStateWakeupEventFd, eventThread); + tearDownTriggerEventThread(); +} + +bool BatteryMitigationService::isBrownoutStatsBinarySupported() { + if (access(cfg.TriggeredIdxPath, F_OK) == 0 && + access(cfg.BrownoutStatsPath, F_OK) == 0) { + return true; + } + return false; +} + +bool readSysfsToInt(const std::string &path, int *val) { + std::string file_contents; + + if (!ReadFileToString(path, &file_contents)) { + return false; + } else if (StartsWith(file_contents, "0x")) { + if (sscanf(file_contents.c_str(), "0x%x", val) != 1) { + return false; + } + } else if (sscanf(file_contents.c_str(), "%d", val) != 1) { + return false; + } + return true; +} + +bool readSysfsToDouble(const std::string &path, double *val) { + std::string file_contents; + + if (!android::base::ReadFileToString(path, &file_contents)) { + return false; + } else if (sscanf(file_contents.c_str(), "%lf", val) != 1) { + return false; + } + return true; +} + +int getFilesInDir(const char *directory, std::vector<std::string> *files) { + std::string content; + struct dirent *entry; + + DIR *dir = opendir(directory); + if (dir == NULL) + return -1; + + files->clear(); + while ((entry = readdir(dir)) != NULL) + files->push_back(entry->d_name); + closedir(dir); + + return 0; +} + +void addNumericSysfsStatPathinDir( + std::string numericSysfsStatDir, + std::vector<MitigationConfig::numericSysfs> totalNumericSysfsStatPaths) { + std::vector<std::string> files; + if (getFilesInDir(numericSysfsStatDir.c_str(), &files) < 0) { + return; + } + for (auto &file : files) { + std::string fullPath = numericSysfsStatDir + file; + totalNumericSysfsStatPaths.push_back({file, fullPath}); + } +} + +void BatteryMitigationService::initTotalNumericSysfsPaths() { + totalNumericSysfsStatPaths.assign(cfg.NumericSysfsStatPaths.begin(), + cfg.NumericSysfsStatPaths.end()); + for (const auto &sysfsStat : cfg.NumericSysfsStatDirs) { + addNumericSysfsStatPathinDir(sysfsStat.path.c_str(), totalNumericSysfsStatPaths); + } + + /* Append first available path in PlatformSpecific */ + for (const auto &sysfsStatList : cfg.PlatformSpecific.NumericSysfsStatPaths) { + for (const auto &sysfsStatPath : sysfsStatList.paths) { + if (access(sysfsStatPath.c_str(), F_OK) == 0) { + totalNumericSysfsStatPaths.push_back({sysfsStatList.name, sysfsStatPath}); + break; + } + } + } + for (const auto &sysfsStatList : cfg.PlatformSpecific.NumericSysfsStatDirs) { + for (const auto &sysfsStatPath : sysfsStatList.paths) { + if (access(sysfsStatPath.c_str(), F_OK) == 0) { + addNumericSysfsStatPathinDir(sysfsStatPath, totalNumericSysfsStatPaths); + break; + } + } + } +} + +int BatteryMitigationService::readNumericStats(struct BrownoutStatsExtend *brownoutStatsExtend) { + int i = 0; + + if (i >= STATS_MAX_SIZE) + return 0; + + for (const auto &sysfsStat : totalNumericSysfsStatPaths) { + snprintf(brownoutStatsExtend->numericStats[i].name, + STAT_NAME_SIZE, "%s", sysfsStat.name.c_str()); + if (!readSysfsToInt(sysfsStat.path, + &brownoutStatsExtend->numericStats[i].value)) { + continue; + } + if (++i == STATS_MAX_SIZE) { + LOG(DEBUG) << "STATS_MAX_SIZE not enough for NumericStats"; + break; + } + } + + return i; +} + + +void BatteryMitigationService::startBrownoutEventThread() { + if (isBrownoutStatsBinarySupported()) { + brownoutEventThread = std::thread(&BatteryMitigationService::BrownoutEventThread, this); + eventThread = std::thread(&BatteryMitigationService::TriggerEventThread, this); + } +} + +void BatteryMitigationService::stopEventThread(std::atomic_bool &thread_stop, int wakeup_event_fd, + std::thread &event_thread) { + if (!thread_stop.load()) { + thread_stop.store(true); + uint64_t flag = 1; + /* wakeup epoll_wait */ + write(wakeup_event_fd, &flag, sizeof(flag)); + + if (event_thread.joinable()) { + event_thread.join(); + } + } +} + +void BatteryMitigationService::tearDownTriggerEventThread() { + triggerThreadStop.store(true); + close(triggeredStateWakeupEventFd); + close(triggeredStateEpollFd); + LOOP_TRIG_STATS(idx) { + close(triggeredStateFd[idx]); + } +} + +int getMmapAddr(int &fd, const char *const path, size_t memSize, char **addr) { + fd = open(path, O_RDWR | O_CREAT, (mode_t) 0644); + if (fd < 0) { + return fd; + } + lseek(fd, memSize - 1, SEEK_SET); + write(fd, "", 1); + *addr = (char *)mmap(NULL, memSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + return 0; +} + +int BatteryMitigationService::initTrigFd() { + int ret; + struct epoll_event trigEvent[MAX_EVENT]; + struct epoll_event wakeupEvent; + + LOOP_TRIG_STATS(idx) { + triggeredStateFd[idx] = open(cfg.triggeredStatePath[idx], O_RDONLY); + if (triggeredStateFd[idx] < 0) { + for (int i = idx - 1; i >= 0; i--) { + close(triggeredStateFd[i]); + } + return triggeredStateFd[idx]; + } + } + + triggeredStateEpollFd = epoll_create(MAX_EVENT + 1); + if (triggeredStateEpollFd < 0) { + LOOP_TRIG_STATS(idx) { + close(triggeredStateFd[idx]); + } + return triggeredStateEpollFd; + } + + triggeredStateWakeupEventFd = eventfd(0, 0); + if (triggeredStateWakeupEventFd < 0) { + close(triggeredStateEpollFd); + LOOP_TRIG_STATS(idx) { + close(triggeredStateFd[idx]); + } + return triggeredStateWakeupEventFd; + } + + LOOP_TRIG_STATS(i) { + trigEvent[i] = epoll_event(); + trigEvent[i].data.fd = triggeredStateFd[i]; + trigEvent[i].events = EPOLLET; + ret = epoll_ctl(triggeredStateEpollFd, EPOLL_CTL_ADD, triggeredStateFd[i], &trigEvent[i]); + if (ret < 0) { + close(triggeredStateWakeupEventFd); + close(triggeredStateEpollFd); + LOOP_TRIG_STATS(idx) { + close(triggeredStateFd[idx]); + } + return ret; + } + } + + wakeupEvent = epoll_event(); + wakeupEvent.data.fd = triggeredStateWakeupEventFd; + wakeupEvent.events = EPOLLIN | EPOLLWAKEUP; + ret = epoll_ctl(triggeredStateEpollFd, EPOLL_CTL_ADD, triggeredStateWakeupEventFd, + &wakeupEvent); + if (ret < 0) { + close(triggeredStateWakeupEventFd); + close(triggeredStateEpollFd); + LOOP_TRIG_STATS(idx) { + close(triggeredStateFd[idx]); + } + return ret; + } + + return 0; +} + +void BatteryMitigationService::TriggerEventThread() { + int requestedFd; + char buf[BUF_SIZE]; + struct epoll_event events[EPOLL_MAXEVENTS]; + std::smatch match; + if (initTrigFd() != 0) { + LOG(DEBUG) << "failed to init Trig FD"; + tearDownTriggerEventThread(); + return; + } + + LOOP_TRIG_STATS(idx) { + read(triggeredStateFd[idx], buf, BUF_SIZE); + } + + while (!triggerThreadStop.load()) { + requestedFd = epoll_wait(triggeredStateEpollFd, events, EPOLL_MAXEVENTS, -1); + if (requestedFd <= 0) { + /* ensure epoll_wait can sleep in the next loop */ + LOOP_TRIG_STATS(idx) { + read(triggeredStateFd[idx], buf, BUF_SIZE); + } + continue; + } + std::string state; + for (int i = 0; i < requestedFd; i++) { + /* triggeredStateFd[i]: triggeredState event from kernel */ + /* triggeredStateWakeupEventFd: wakeup epoll_wait to stop thread properly */ + LOOP_TRIG_STATS(idx) { + if (events[i].data.fd == triggeredStateFd[idx]) { + read(triggeredStateFd[idx], buf, BUF_SIZE); + if (ReadFileToString(cfg.triggeredStatePath[idx], &state)) { + size_t pos = state.find("_"); + std::string tState = state.substr(0, pos); + std::string tModule = state.substr(pos + 1); + LOG(INFO) << idx << " triggered, current state: " << tState << ". throttle " + << tModule; + /* b/299700579 launch throttling on targeted module */ + } + break; + } + } + /* b/299700579 handle wakeupEvent here if we need to do something after this loop */ + } + } +} + +int BatteryMitigationService::initFd() { + int ret; + struct epoll_event triggeredIdxEvent, wakeupEvent; + + brownoutStatsFd = open(cfg.BrownoutStatsPath, O_RDONLY); + if (brownoutStatsFd < 0) { + return brownoutStatsFd; + } + + storingFd = open(cfg.StoringPath, O_RDWR | O_CREAT | O_TRUNC, (mode_t) 0644); + if (storingFd < 0) { + return storingFd; + } + + triggeredIdxFd = open(cfg.TriggeredIdxPath, O_RDONLY); + if (triggeredIdxFd < 0) { + return triggeredIdxFd; + } + + triggeredIdxEpollFd = epoll_create(2); + if (triggeredIdxEpollFd < 0) { + return triggeredIdxEpollFd; + } + + wakeupEventFd = eventfd(0, 0); + if (wakeupEventFd < 0) { + return wakeupEventFd; + } + + triggeredIdxEvent = epoll_event(); + triggeredIdxEvent.data.fd = triggeredIdxFd; + triggeredIdxEvent.events = EPOLLERR | EPOLLWAKEUP; + ret = epoll_ctl(triggeredIdxEpollFd, EPOLL_CTL_ADD, triggeredIdxFd, &triggeredIdxEvent); + if (ret < 0) { + return ret; + } + + wakeupEvent = epoll_event(); + wakeupEvent.data.fd = wakeupEventFd; + wakeupEvent.events = EPOLLIN | EPOLLWAKEUP; + ret = epoll_ctl(triggeredIdxEpollFd, EPOLL_CTL_ADD, wakeupEventFd, &wakeupEvent); + if (ret < 0) { + return ret; + } + + return 0; +} + +void BatteryMitigationService::BrownoutEventThread() { + int requestedFd; + int triggeredIdx; + char buf[BUF_SIZE]; + struct timeval eventReceivedTime; + struct timeval statStoredTime; + struct epoll_event events[EPOLL_MAXEVENTS]; + struct BrownoutStatsExtend brownoutStatsExtend; + size_t brownoutStatsSize = sizeof(struct brownout_stats); + size_t brownoutStatsExtendSize = sizeof(struct BrownoutStatsExtend); + bool stopByEvent = false; + /* BrownoutEventThread store multiple brownout event (BROWNOUT_EVENT_BUF_SIZE) + * and each event contains several dumps (DUMP_TIMES). + */ + int brownoutEventCounter = 0; + + if (initFd() != 0) { + LOG(DEBUG) << "failed to init FD"; + tearDownBrownoutEventThread(); + return; + } + + /* allow epoll_wait sleep in the first loop */ + read(triggeredIdxFd, buf, BUF_SIZE); + + while (!threadStop.load()) { + requestedFd = epoll_wait(triggeredIdxEpollFd, events, EPOLL_MAXEVENTS, -1); + if (requestedFd <= 0) { + /* ensure epoll_wait can sleep in the next loop */ + read(triggeredIdxFd, buf, BUF_SIZE); + continue; + } + /* triggeredIdxFd: brownout event from kernel */ + /* wakeupEventFd: wakeup epoll_wait to stop thread properly */ + for (int i = 0; i < requestedFd; i++) { + if (events[i].data.fd == triggeredIdxFd) { + break; + } else { + stopByEvent = true; + } + } + if (stopByEvent) { + break; + } + + /* record brownout event idx and received time */ + gettimeofday(&eventReceivedTime, NULL); + lseek(triggeredIdxFd, 0, SEEK_SET); + if (read(triggeredIdxFd, buf, BUF_SIZE) == -1) { + continue; + } + triggeredIdx = atoi(buf); + if (triggeredIdx >= TRIGGERED_SOURCE_MAX || triggeredIdx < 0) { + continue; + } + + /* dump brownout related stats */ + std::string stats; + for (int i = 0; i < DUMP_TIMES; i++) { + memset(&brownoutStatsExtend, 0 , brownoutStatsExtendSize); + + /* storing by string due the stats msg too complicate */ + if (ReadFileToString(cfg.FvpStatsPath, &stats)) { + snprintf(brownoutStatsExtend.fvpStats, FVP_STATS_SIZE, "%s", stats.c_str()); + } + + /* storing numericStats */ + readNumericStats(&brownoutStatsExtend); + + /* storing brownoutStats */ + lseek(brownoutStatsFd, 0, SEEK_SET); + read(brownoutStatsFd, &brownoutStatsExtend.brownoutStats, brownoutStatsSize); + gettimeofday(&statStoredTime, NULL); + brownoutStatsExtend.dumpTime = statStoredTime; + brownoutStatsExtend.eventReceivedTime = eventReceivedTime; + brownoutStatsExtend.eventIdx = triggeredIdx; + + /* write to file */ + lseek(storingFd, + brownoutStatsExtendSize * (brownoutEventCounter * DUMP_TIMES + i), SEEK_SET); + write(storingFd, &brownoutStatsExtend, brownoutStatsExtendSize); + fsync(storingFd); + } + + if (++brownoutEventCounter == BROWNOUT_EVENT_BUF_SIZE) { + brownoutEventCounter = 0; + } + } +} + +void readLPFPowerBitResolutions(const char *odpmDir, double *bitResolutions) { + char path[BUF_SIZE]; + + for (int i = 0; i < METER_CHANNEL_MAX; i++) { + snprintf(path, BUF_SIZE, "%s/in_power%d_scale", odpmDir, i); + if (!readSysfsToDouble(path, &bitResolutions[i])) { + bitResolutions[i] = 0; + } + } +} + +void readLPFChannelNames(const char *odpmEnabledRailsPath, char **lpfChannelNames) { + char *line = NULL; + size_t len = 0; + ssize_t read; + + FILE *fp = fopen(odpmEnabledRailsPath, "r"); + if (!fp) + return; + + int c = 0; + while ((read = getline(&line, &len, fp)) != -1 && read != 0) { + lpfChannelNames[c] = (char *)malloc(read); + if (lpfChannelNames[c] != nullptr) { + snprintf(lpfChannelNames[c], read, "%s", line); + } + if (++c == METER_CHANNEL_MAX) + break; + } + fclose(fp); + + if (line) + free(line); +} + +int getMainPmicID(const std::string &mainPmicNamePath, const std::string &subPmicNamePath) { + std::string content; + int ret = 0; + int mainPmicVer, subPmicVer; + + if (!ReadFileToString(mainPmicNamePath, &content)) { + LOG(DEBUG) << "Failed to open " << mainPmicNamePath << " set device0 as main pmic"; + return ret; + } + /* ODPM pmic name: s2mpgXX-odpm */ + mainPmicVer = atoi(content.substr(5, 2).c_str()); + + if (!ReadFileToString(subPmicNamePath, &content)) { + LOG(DEBUG) << "Failed to open " << subPmicNamePath << " set device0 as main pmic"; + return ret; + } + subPmicVer = atoi(content.substr(5, 2).c_str()); + + if (mainPmicVer > subPmicVer) { + ret = 1; + } + + return ret; +} + +void freeLpfChannelNames(char **lpfChannelNames) { + for (int c = 0; c < METER_CHANNEL_MAX; c++) { + free(lpfChannelNames[c]); + } +} + +void BatteryMitigationService::tearDownBrownoutEventThread() { + close(triggeredIdxFd); + close(brownoutStatsFd); + close(triggeredIdxEpollFd); + close(wakeupEventFd); + close(storingFd); + threadStop.store(true); + freeLpfChannelNames(mainLpfChannelNames); + freeLpfChannelNames(subLpfChannelNames); + +} + +void BatteryMitigationService::initPmicRelated() { + mainPmicID = 0; + mainPmicID = getMainPmicID(cfg.PmicCommon[mainPmicID].PmicNamePath, + cfg.PmicCommon[subPmicID].PmicNamePath); + subPmicID = !mainPmicID; + + /* read odpm resolutions and channel names */ + readLPFPowerBitResolutions(cfg.PmicCommon[mainPmicID].OdpmDir, mainLpfBitResolutions); + readLPFPowerBitResolutions(cfg.PmicCommon[subPmicID].OdpmDir, subLpfBitResolutions); + readLPFChannelNames(cfg.PmicCommon[mainPmicID].OdpmEnabledRailsPath, mainLpfChannelNames); + readLPFChannelNames(cfg.PmicCommon[subPmicID].OdpmEnabledRailsPath, subLpfChannelNames); + +} + +void printUTC(FILE *fp, struct timespec time, const char *stat) { + if (!fp) { + return; + } + char timeBuff[BUF_SIZE]; + if (strlen(stat) > 0) { + fprintf(fp, "%s: ", stat); + } + std::strftime(timeBuff, BUF_SIZE, "%Y-%m-%d %H:%M:%S", std::localtime(&time.tv_sec)); + fprintf(fp, "%s.%09ld",timeBuff, time.tv_nsec); +} + +void printUTC(FILE *fp, timeval time, const char *stat) { + if (!fp) { + return; + } + char timeBuff[BUF_SIZE]; + if (strlen(stat) > 0) { + fprintf(fp, "%s: ", stat); + } + std::strftime(timeBuff, BUF_SIZE, "%Y-%m-%d %H:%M:%S", std::localtime(&time.tv_sec)); + /* convert usec to nsec */ + fprintf(fp, "%s.%06ld000",timeBuff, time.tv_usec); +} + +void printODPMInstantDataSummary(FILE *fp, std::vector<odpm_instant_data> &odpmData, + double *lpfBitResolutions, char **lpfChannelNames) { + if (!fp) { + return; + } + std::vector<struct timespec> validTime; + std::vector<OdpmInstantPower> instPower[METER_CHANNEL_MAX]; + std::vector<OdpmInstantPower> instPowerMax; + std::vector<OdpmInstantPower> instPowerMin; + std::vector<double> instPowerList; + std::vector<double> instPowerStd; + + if (odpmData.size() == 0) + return; + + /* initial Max, Min, Sum for sorting */ + struct timespec curTime = odpmData[0].time; + validTime.emplace_back(curTime); + for (int c = 0; c < METER_CHANNEL_MAX; c++) { + double power = lpfBitResolutions[c] * odpmData[0].value[c]; + instPower[c].emplace_back((OdpmInstantPower){curTime, power}); + instPowerMax.emplace_back((OdpmInstantPower){curTime, power}); + instPowerMin.emplace_back((OdpmInstantPower){curTime, power}); + instPowerList.emplace_back(power); + } + + for (auto lpf = (odpmData.begin() + 1); lpf != odpmData.end(); lpf++) { + curTime = lpf->time; + /* remove duplicate data by checking the odpm instant data dump time */ + auto it = std::find_if(validTime.begin(), validTime.end(), + [&_ts = curTime] (const struct timespec &ts) -> + bool {return _ts.tv_sec == ts.tv_sec && + _ts.tv_nsec == ts.tv_nsec;}); + if (it == validTime.end()) { + validTime.emplace_back(curTime); + for (int c = 0; c < METER_CHANNEL_MAX; c++){ + double power = lpfBitResolutions[c] * lpf->value[c]; + instPower[c].emplace_back((OdpmInstantPower){curTime, power}); + instPowerList[c] += power; + if (power > instPowerMax[c].value) { + instPowerMax[c].value = power; + instPowerMax[c].time = curTime; + } + if (power < instPowerMin[c].value) { + instPowerMin[c].value = power; + instPowerMin[c].time = curTime; + } + } + } + } + + int n = validTime.size(); + for (int c = 0; c < METER_CHANNEL_MAX; c++) { + /* sort instant power by time */ + std::sort(instPower[c].begin(), instPower[c].end(), + [] (const auto &i, const auto &j) + {return i.time.tv_sec < j.time.tv_sec || + (i.time.tv_sec == j.time.tv_sec && + i.time.tv_nsec < j.time.tv_nsec);}); + /* compute std for each channel */ + double avg = instPowerList[c] / n; + double mse = 0; + for (int i = 0; i < n; i++) { + mse += pow(instPower[c][i].value - avg, 2); + } + instPowerStd.emplace_back(pow(mse / n, 0.5)); + } + + /* print Max, Min, Avg, Std */ + for (int c = 0; c < METER_CHANNEL_MAX; c++) { + fprintf(fp, "%s Max: %.2f Min: %.2f Avg: %.2f Std: %.2f\n", lpfChannelNames[c], + instPowerMax[c].value, + instPowerMin[c].value, + instPowerList[c] / n, + instPowerStd[c]); + } + fprintf(fp, "\n"); + + /* print time */ + fprintf(fp, "time "); + for (int i = 0; i < n; i++) { + printUTC(fp, instPower[0][i].time, ""); + fprintf(fp, " "); + } + fprintf(fp, "\n"); + + /* print instant power by channel */ + for (int c = 0; c < METER_CHANNEL_MAX; c++){ + fprintf(fp, "%s ", lpfChannelNames[c]); + for (int i = 0; i < n; i++) { + fprintf(fp, "%.2f ", instPower[c][i].value); + } + fprintf(fp, "\n"); + } + + fprintf(fp, "\n"); +} + +void printLatency(FILE *fp, struct BrownoutStatsExtend *brownoutStatsExtend) { + if (!fp) { + return; + } + /* received latency */ + struct timespec recvLatency; + recvLatency.tv_sec = brownoutStatsExtend[0].eventReceivedTime.tv_sec - \ + brownoutStatsExtend[0].brownoutStats.triggered_time.tv_sec; + + signed long long temp = brownoutStatsExtend[0].eventReceivedTime.tv_usec * 1000; + if (temp >= brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec) + recvLatency.tv_nsec = brownoutStatsExtend[0].eventReceivedTime.tv_usec * 1000 - \ + brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec; + else + recvLatency.tv_nsec = NSEC_PER_SEC - \ + brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec \ + + brownoutStatsExtend[0].eventReceivedTime.tv_usec * 1000; + + /* dump latency */ + struct timespec dumpLatency; + dumpLatency.tv_sec = brownoutStatsExtend[0].dumpTime.tv_sec - \ + brownoutStatsExtend[0].eventReceivedTime.tv_sec; + + temp = brownoutStatsExtend[0].dumpTime.tv_usec; + if (temp >= brownoutStatsExtend[0].eventReceivedTime.tv_usec) + dumpLatency.tv_nsec = (brownoutStatsExtend[0].dumpTime.tv_usec - \ + brownoutStatsExtend[0].eventReceivedTime.tv_usec) * 1000; + else + dumpLatency.tv_nsec = NSEC_PER_SEC - \ + brownoutStatsExtend[0].eventReceivedTime.tv_usec * 1000 + \ + brownoutStatsExtend[0].dumpTime.tv_usec * 1000; + + /* total latency */ + struct timespec totalLatency; + totalLatency.tv_sec = brownoutStatsExtend[0].dumpTime.tv_sec - \ + brownoutStatsExtend[0].brownoutStats.triggered_time.tv_sec; + temp = brownoutStatsExtend[0].dumpTime.tv_usec * 1000; + if (temp >= brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec) + totalLatency.tv_nsec = brownoutStatsExtend[0].dumpTime.tv_usec * 1000 - \ + brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec; + else + totalLatency.tv_nsec = NSEC_PER_SEC - \ + brownoutStatsExtend[0].brownoutStats.triggered_time.tv_nsec + \ + brownoutStatsExtend[0].dumpTime.tv_usec * 1000; + + fprintf(fp, "recvLatency %ld.%09ld\n", recvLatency.tv_sec, recvLatency.tv_nsec); + fprintf(fp, "dumpLatency %ld.%09ld\n", dumpLatency.tv_sec, dumpLatency.tv_nsec); + fprintf(fp, "totalLatency %ld.%09ld\n\n", totalLatency.tv_sec, totalLatency.tv_nsec); + +} + +void BatteryMitigationService::printBrownoutStatsExtendSummary( + FILE *fp, struct BrownoutStatsExtend *brownoutStatsExtend) { + if (!fp) { + return; + } + std::vector<odpm_instant_data> odpmData[PMIC_NUM]; + + /* print out the triggered_time in first dump */ + printUTC(fp, brownoutStatsExtend[0].brownoutStats.triggered_time, "triggered_time"); + fprintf(fp, "\n"); + fprintf(fp, "triggered_idx: %d\n", brownoutStatsExtend[0].brownoutStats.triggered_idx); + printLatency(fp, brownoutStatsExtend); + + /* skip time invalid odpm instant data */ + for (int i = 0; i < DUMP_TIMES; i++) { + for (int d = 0; d < DATA_LOGGING_LEN; d++) { + if (brownoutStatsExtend[i].brownoutStats.main_odpm_instant_data[d].time.tv_sec != 0) { + odpmData[mainPmicID].emplace_back(brownoutStatsExtend[i].brownoutStats.main_odpm_instant_data[d]); + } + if (brownoutStatsExtend[i].brownoutStats.sub_odpm_instant_data[d].time.tv_sec != 0) { + odpmData[subPmicID].emplace_back(brownoutStatsExtend[i].brownoutStats.sub_odpm_instant_data[d]); + } + } + } + + printODPMInstantDataSummary(fp, + odpmData[mainPmicID], mainLpfBitResolutions, mainLpfChannelNames); + printODPMInstantDataSummary(fp, + odpmData[subPmicID], subLpfBitResolutions, subLpfChannelNames); + +} + +void printOdpmInstantData(FILE *fp, struct odpm_instant_data odpmInstantData) { + if (!fp) { + return; + } + if (odpmInstantData.time.tv_sec == 0 && + odpmInstantData.time.tv_nsec == 0) { + return; + } + printUTC(fp, odpmInstantData.time, ""); + fprintf(fp, " "); + for (int i = 0; i < METER_CHANNEL_MAX; i++){ + fprintf(fp, "%d ", odpmInstantData.value[i]); + } + fprintf(fp, "\n"); +} + +void parseFvpStats(const char *stats, std::vector<numericStat> &result) { + std::string fvpStats(stats); + std::string line; + std::istringstream iss(fvpStats); + size_t pos; + while (getline(iss, line, '\n')) { + if (line.find("time_ns") == std::string::npos) { + if (line.find("cur_freq:") == std::string::npos) { + result.emplace_back(); + snprintf(result.back().name, STAT_NAME_SIZE, "%s", line.c_str()); + continue; + } else if (result.size() > 0 && (pos = line.find(" ")) != std::string::npos) { + sscanf(line.substr(pos, line.size()).c_str(), "%d", &result.back().value); + } + } + } + +} + +void printBrownoutStatsExtendRaw(FILE *fp, struct BrownoutStatsExtend *brownoutStatsExtend) { + if (!fp) { + return; + } + printUTC(fp, brownoutStatsExtend->brownoutStats.triggered_time, "triggered_time"); + fprintf(fp, "\n"); + fprintf(fp, "triggered_idx: %d\n", brownoutStatsExtend->brownoutStats.triggered_idx); + + fprintf(fp, "main_odpm_instant_data:\n"); + for (int d = 0; d < DATA_LOGGING_LEN; d++) { + printOdpmInstantData(fp, brownoutStatsExtend->brownoutStats.main_odpm_instant_data[d]); + } + fprintf(fp, "sub_odpm_instant_data:\n"); + for (int d = 0; d < DATA_LOGGING_LEN; d++) { + printOdpmInstantData(fp, brownoutStatsExtend->brownoutStats.sub_odpm_instant_data[d]); + } + fprintf(fp, "mitigation_state:\n"); + for (int d = 0; d < DATA_LOGGING_LEN; d++) { + fprintf(fp, "%d ", brownoutStatsExtend->brownoutStats.triggered_state[d]); + } + + fprintf(fp, "\n"); + fprintf(fp, "fvp_stats:\n"); + std::vector<numericStat> fvpStats; + parseFvpStats(brownoutStatsExtend->fvpStats, fvpStats); + for (const auto &fvpStat : fvpStats) { + fprintf(fp, "%s_freq: %d\n", fvpStat.name, fvpStat.value); + } + for (int i = 0; i < STATS_MAX_SIZE; i++) { + if (strlen(brownoutStatsExtend->numericStats[i].name) > 0) + fprintf(fp, "%s: %d\n", brownoutStatsExtend->numericStats[i].name, + brownoutStatsExtend->numericStats[i].value); + } + printUTC(fp, brownoutStatsExtend->eventReceivedTime, "eventReceivedTime"); + fprintf(fp, "\n"); + printUTC(fp, brownoutStatsExtend->dumpTime, "dumpTime"); + fprintf(fp, "\n"); + fprintf(fp, "eventIdx: %d\n", brownoutStatsExtend->eventIdx); +} + +bool getValueFromNumericStats(const char *statName, int *value, + struct numericStat *numericStats) { + for (int i = 0; i < STATS_MAX_SIZE; i++) { + if (strcmp(numericStats[i].name, statName) == 0) { + *value = numericStats[i].value; + return true; + } + } + return false; +} + +void setMaxNumericStat(const char *statName, int *maxValue, + struct numericStat *numericStats) { + int statValue; + if (getValueFromNumericStats(statName, + &statValue, + numericStats) && + statValue > *maxValue) { + *maxValue = statValue; + } +} + +void setMinNumericStat(const char *statName, int *minValue, + struct numericStat *numericStats) { + int statValue; + if (getValueFromNumericStats(statName, + &statValue, + numericStats) && + statValue < *minValue) { + *minValue = statValue; + } +} + +/* fvp_stats constins MIF, CL0-2, TPU, AUR */ +void setMinNumericStat(const char *statName, int *minValue, + std::vector<numericStat> &fvpStats) { + auto it = std::find_if(fvpStats.begin(), fvpStats.end(), + [&name = statName] (const struct numericStat &ns) -> + bool {return strcmp(ns.name, name) == 0;}); + if (it != fvpStats.end()) { + if (*minValue > (*it).value) { + *minValue = (*it).value; + } + } +} + +void initBrownoutStatsCSVRow(struct BrownoutStatsCSVRow *row) { + memset(row, 0, sizeof(struct BrownoutStatsCSVRow)); + row->min_battery_soc = INT_MAX; + row->min_battery_cycle = INT_MAX; + row->min_voltage_now = INT_MAX; + row->min_cpu0_freq = INT_MAX; + row->min_cpu1_freq = INT_MAX; + row->min_cpu2_freq = INT_MAX; + row->min_gpu_freq = INT_MAX; + row->min_tpu_freq = INT_MAX; + row->min_aur_freq = INT_MAX; +} + +bool readBrownoutStatsExtend(const char *storingPath, + struct BrownoutStatsExtend *brownoutStatsExtends) { + int fd = open(storingPath, O_RDONLY); + if (fd < 0) { + LOG(DEBUG) << "Failed to open " << storingPath; + return false; + } + + size_t logFileSize = lseek(fd, 0, SEEK_END); + char *logFileAddr = (char *) mmap(NULL, logFileSize, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + + memcpy(brownoutStatsExtends, logFileAddr, logFileSize); + munmap(logFileAddr, logFileSize); + + return true; +} + +void BatteryMitigationService::getBrownoutStatsCSVRow( + struct BrownoutStatsExtend *brownoutStatsExtendPerEvent, + struct BrownoutStatsCSVRow *row) { + initBrownoutStatsCSVRow(row); + for (int i = 0; i < DUMP_TIMES; i++) { + if (i == 0) { + /* triggered_time */ + row->triggered_time = brownoutStatsExtendPerEvent[0].brownoutStats.triggered_time; + /* triggered_idx */ + row->triggered_idx = brownoutStatsExtendPerEvent[0].brownoutStats.triggered_idx; + } + double power; + for (int d = 0; d < DATA_LOGGING_LEN; d++) { + for (int c = 0; c < METER_CHANNEL_MAX; c++) { + /* max_main_odpm_instant_power */ + power = + brownoutStatsExtendPerEvent[i].brownoutStats.main_odpm_instant_data[d].value[c] * + mainLpfBitResolutions[c]; + if (power > row->max_main_odpm_instant_power[c]) { + row->max_main_odpm_instant_power[c] = power; + } + /* max_sub_odpm_instant_power */ + power = + brownoutStatsExtendPerEvent[i].brownoutStats.sub_odpm_instant_data[d].value[c] * + subLpfBitResolutions[c]; + if (power > row->max_sub_odpm_instant_power[c]) { + row->max_sub_odpm_instant_power[c] = power; + } + } + } + /* min_battery_soc */ + setMinNumericStat("battery_soc", + &row->min_battery_soc, + brownoutStatsExtendPerEvent[i].numericStats); + /* max_battery_temp */ + setMaxNumericStat("battery_temp", + &row->max_battery_temp, + brownoutStatsExtendPerEvent[i].numericStats); + /* min_battery_cycle */ + setMinNumericStat("battery_cycle", + &row->min_battery_cycle, + brownoutStatsExtendPerEvent[i].numericStats); + /* min_voltage_now */ + setMinNumericStat("voltage_now", + &row->min_voltage_now, + brownoutStatsExtendPerEvent[i].numericStats); + /* max_current_now */ + setMaxNumericStat("current_now", + &row->max_current_now, + brownoutStatsExtendPerEvent[i].numericStats); + /* min_cpu0_freq */ + setMinNumericStat("cpu0_freq", + &row->min_cpu0_freq, + brownoutStatsExtendPerEvent[i].numericStats); + /* min_cpu1_freq */ + setMinNumericStat("cpu1_freq", + &row->min_cpu1_freq, + brownoutStatsExtendPerEvent[i].numericStats); + /* min_cpu2_freq */ + setMinNumericStat("cpu2_freq", + &row->min_cpu2_freq, + brownoutStatsExtendPerEvent[i].numericStats); + /* min_gpu_freq */ + setMinNumericStat("gpu_freq", + &row->min_gpu_freq, + brownoutStatsExtendPerEvent[i].numericStats); + + std::vector<numericStat> fvpStats; + parseFvpStats(brownoutStatsExtendPerEvent[i].fvpStats, fvpStats); + /* min_tpu_freq */ + setMinNumericStat("TPU", &row->min_tpu_freq, fvpStats); + /* min_aur_freq */ + setMinNumericStat("AUR", &row->min_aur_freq, fvpStats); + } + +} + +bool BatteryMitigationService::genLastmealCSV(const char *parsedMealCSVPath) { + if (access(cfg.StoringPath, F_OK) != 0) { + LOG(DEBUG) << "Failed to access " << cfg.StoringPath; + return false; + } + + struct BrownoutStatsExtend brownoutStatsExtends[BROWNOUT_EVENT_BUF_SIZE][DUMP_TIMES]; + if (!readBrownoutStatsExtend(cfg.StoringPath, brownoutStatsExtends[0])) { + return false; + } + + std::vector<struct BrownoutStatsCSVRow> rows; + for (int e = 0; e < BROWNOUT_EVENT_BUF_SIZE; e++) { + if (brownoutStatsExtends[e][0].brownoutStats.triggered_time.tv_sec != 0) { + rows.emplace_back(); + getBrownoutStatsCSVRow(brownoutStatsExtends[e], &rows.back()); + } + } + /* sort rows by time */ + std::sort(rows.begin(), rows.end(), + [] (const auto &i, const auto &j) + {return i.triggered_time.tv_sec < j.triggered_time.tv_sec || + (i.triggered_time.tv_sec == j.triggered_time.tv_sec && + i.triggered_time.tv_nsec < j.triggered_time.tv_nsec);}); + + FILE *fp = nullptr; + fp = fopen(parsedMealCSVPath, "w"); + if (!fp) + return false; + + /* print csv fields */ + fprintf(fp, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,", brownoutStatsCSVFields.triggered_time, + brownoutStatsCSVFields.triggered_idx, brownoutStatsCSVFields.battery_soc, + brownoutStatsCSVFields.battery_temp, brownoutStatsCSVFields.battery_cycle, + brownoutStatsCSVFields.voltage_now, brownoutStatsCSVFields.current_now, + brownoutStatsCSVFields.cpu0_freq, brownoutStatsCSVFields.cpu1_freq, + brownoutStatsCSVFields.cpu2_freq, brownoutStatsCSVFields.gpu_freq, + brownoutStatsCSVFields.tpu_freq, brownoutStatsCSVFields.aur_freq); + for (int c = 1; c <= METER_CHANNEL_MAX; c++) { + fprintf(fp, "%s%02d,", brownoutStatsCSVFields.odpm_prefix, c); + } + for (int c = 1; c <= METER_CHANNEL_MAX; c++) { + fprintf(fp, "%s%02d,", brownoutStatsCSVFields.odpm_prefix, c + METER_CHANNEL_MAX); + } + fprintf(fp, "\n"); + + /* print csv rows */ + for (auto row = rows.begin(); row != rows.end(); row++) { + printUTC(fp, row->triggered_time, ""); + fprintf(fp, ","); + fprintf(fp, "%d,", row->triggered_idx); + fprintf(fp, "%d,", row->min_battery_soc); + fprintf(fp, "%d,", row->max_battery_temp); + fprintf(fp, "%d,", row->min_battery_cycle); + fprintf(fp, "%d,", row->min_voltage_now); + fprintf(fp, "%d,", row->max_current_now); + fprintf(fp, "%d,", row->min_cpu0_freq); + fprintf(fp, "%d,", row->min_cpu1_freq); + fprintf(fp, "%d,", row->min_cpu2_freq); + fprintf(fp, "%d,", row->min_gpu_freq); + fprintf(fp, "%d,", row->min_tpu_freq); + fprintf(fp, "%d,", row->min_aur_freq); + for (int c = 0; c < METER_CHANNEL_MAX; c++) { + fprintf(fp, "%d,", int(row->max_main_odpm_instant_power[c] * 1000)); + } + for (int c = 0; c < METER_CHANNEL_MAX; c++) { + fprintf(fp, "%d,", int(row->max_sub_odpm_instant_power[c] * 1000)); + } + fprintf(fp, "\n"); + } + fclose(fp); + + return true; +} + +bool BatteryMitigationService::isTimeValid(const char *storingPath, + std::chrono::system_clock::time_point startTime) { + struct BrownoutStatsExtend brownoutStatsExtends[BROWNOUT_EVENT_BUF_SIZE][DUMP_TIMES]; + if (!readBrownoutStatsExtend(storingPath, brownoutStatsExtends[0])) { + return false; + } + time_t sec = + std::chrono::time_point_cast<std::chrono::seconds>(startTime).time_since_epoch().count(); + + for (int e = 0; e < BROWNOUT_EVENT_BUF_SIZE; e++) { + if (brownoutStatsExtends[e][0].dumpTime.tv_sec == 0 && + brownoutStatsExtends[e][0].dumpTime.tv_usec == 0) { + continue; + } else if (brownoutStatsExtends[e][0].dumpTime.tv_sec < sec) { + return true; + } + } + + return false; +} + +bool BatteryMitigationService::parseBrownoutStatsExtend(FILE *fp) { + if (!fp) { + return false; + } + struct BrownoutStatsExtend brownoutStatsExtends[BROWNOUT_EVENT_BUF_SIZE][DUMP_TIMES]; + if (!readBrownoutStatsExtend(cfg.StoringPath, brownoutStatsExtends[0])) { + return false; + } + + for (int e = 0; e < BROWNOUT_EVENT_BUF_SIZE; e++) { + if (brownoutStatsExtends[e][0].dumpTime.tv_sec == 0 && + brownoutStatsExtends[e][0].dumpTime.tv_usec == 0) { + continue; + } + printBrownoutStatsExtendSummary(fp, brownoutStatsExtends[e]); + fprintf(fp, "=== RAW ===\n"); + for (int i = 0; i < DUMP_TIMES; i++) { + fprintf(fp, "=== Dump %d-%d ===\n", e, i); + printBrownoutStatsExtendRaw(fp, &brownoutStatsExtends[e][i]); + fprintf(fp, "=============\n\n"); + } + } + return true; +} + +bool BatteryMitigationService::genParsedMeal(const char *parsedMealPath) { + if (access(cfg.StoringPath, F_OK) != 0) { + LOG(DEBUG) << "Failed to access " << cfg.StoringPath; + return false; + } + + FILE *fp = nullptr; + fp = fopen(parsedMealPath, "w"); + if (!fp || !parseBrownoutStatsExtend(fp)) { + return false; + } + fclose(fp); + + return true; +} + +} // namespace pixel +} // namespace google +} // namespace hardware +} // namespace android diff --git a/battery_mitigation/include/battery_mitigation/BatteryMitigation.h b/battery_mitigation/include/battery_mitigation/BatteryMitigation.h index d3ce433d..de22b9af 100644 --- a/battery_mitigation/include/battery_mitigation/BatteryMitigation.h +++ b/battery_mitigation/include/battery_mitigation/BatteryMitigation.h @@ -16,6 +16,7 @@ #pragma once + #include <utils/RefBase.h> #include "MitigationThermalManager.h" diff --git a/battery_mitigation/include/battery_mitigation/BatteryMitigationService.h b/battery_mitigation/include/battery_mitigation/BatteryMitigationService.h new file mode 100644 index 00000000..9c697140 --- /dev/null +++ b/battery_mitigation/include/battery_mitigation/BatteryMitigationService.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2023 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. + */ + +#pragma once + +#include <android-base/chrono_utils.h> +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/properties.h> +#include <android-base/strings.h> +#include <errno.h> +#include <sys/epoll.h> +#include <sys/eventfd.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/system_properties.h> +#include <unistd.h> +#include <utils/RefBase.h> + +#include <algorithm> +#include <cmath> +#include <map> +#include <regex> +#include <thread> + +#include "MitigationConfig.h" + +#define MIN_SUPPORTED_PLATFORM 2 /* CDT */ +#define MAX_SUPPORTED_PLATFORM 5 +#define NSEC_PER_SEC 1000000000L +#define BROWNOUT_EVENT_BUF_SIZE 10 +#define DUMP_TIMES 12 +#define EPOLL_MAXEVENTS 12 +#define BUF_SIZE 128 +#define FVP_STATS_SIZE 4096 +#define STAT_NAME_SIZE 48 +#define STATS_MAX_SIZE 64 +#define PMIC_NUM 2 +#define LOOP_TRIG_STATS(idx) for (int idx = 0; idx < MAX_EVENT; idx++) + +namespace android { +namespace hardware { +namespace google { +namespace pixel { + +using ::android::sp; +using android::hardware::google::pixel::MitigationConfig; + +struct numericStat { + char name[STAT_NAME_SIZE]; + int value; +}; + +struct OdpmInstantPower { + struct timespec time; + double value; +}; + +struct BrownoutStatsCSVFields { + const char *const triggered_time; + const char *const triggered_idx; + const char *const battery_soc; + const char *const battery_temp; + const char *const battery_cycle; + const char *const voltage_now; + const char *const current_now; + const char *const cpu0_freq; + const char *const cpu1_freq; + const char *const cpu2_freq; + const char *const gpu_freq; + const char *const tpu_freq; + const char *const aur_freq; + const char *const odpm_prefix; +}; + +struct BrownoutStatsCSVRow { + struct timespec triggered_time; + int triggered_idx; + int min_battery_soc; + int max_battery_temp; + int min_battery_cycle; + int min_voltage_now; + int max_current_now; + int min_cpu0_freq; + int min_cpu1_freq; + int min_cpu2_freq; + int min_gpu_freq; + int min_tpu_freq; + int min_aur_freq; + + double max_main_odpm_instant_power[METER_CHANNEL_MAX]; + double max_sub_odpm_instant_power[METER_CHANNEL_MAX]; +}; + +struct BrownoutStatsExtend { + struct brownout_stats brownoutStats; + char fvpStats[FVP_STATS_SIZE]; + struct numericStat numericStats[STATS_MAX_SIZE]; + timeval eventReceivedTime; + timeval dumpTime; + unsigned int eventIdx; +}; + +class BatteryMitigationService : public RefBase { + public: + BatteryMitigationService(const struct MitigationConfig::EventThreadConfig &eventThreadCfg); + ~BatteryMitigationService(); + + void startBrownoutEventThread(); + void stopEventThread(std::atomic_bool &thread_stop, int wakeup_event_fd, + std::thread &event_thread); + bool isBrownoutStatsBinarySupported(); + bool isPlatformSupported(); + bool isTimeValid(const char*, std::chrono::system_clock::time_point); + bool genParsedMeal(const char*); + bool genLastmealCSV(const char*); + private: + struct MitigationConfig::EventThreadConfig cfg; + + int storingFd; + int triggeredStateFd[MAX_EVENT]; + int triggeredStateEpollFd; + int triggeredStateWakeupEventFd; + std::thread eventThread; + std::atomic_bool triggerThreadStop{false}; + int brownoutStatsFd; + int triggeredIdxFd; + int triggeredIdxEpollFd; + int wakeupEventFd; + std::thread brownoutEventThread; + std::atomic_bool threadStop{false}; + + int mainPmicID; + int subPmicID; + double mainLpfBitResolutions[METER_CHANNEL_MAX]; + double subLpfBitResolutions[METER_CHANNEL_MAX]; + char *mainLpfChannelNames[METER_CHANNEL_MAX]; + char *subLpfChannelNames[METER_CHANNEL_MAX]; + std::vector<MitigationConfig::numericSysfs> totalNumericSysfsStatPaths; + + void BrownoutEventThread(); + void TriggerEventThread(); + void initTotalNumericSysfsPaths(); + void initPmicRelated(); + int initFd(); + int initTrigFd(); + void tearDownBrownoutEventThread(); + void tearDownTriggerEventThread(); + int readNumericStats(struct BrownoutStatsExtend*); + bool parseBrownoutStatsExtend(FILE *); + void printBrownoutStatsExtendSummary(FILE *, struct BrownoutStatsExtend *); + void getBrownoutStatsCSVRow(struct BrownoutStatsExtend *, struct BrownoutStatsCSVRow *); +}; + +} // namespace pixel +} // namespace google +} // namespace hardware +} // namespace android diff --git a/battery_mitigation/include/battery_mitigation/MitigationConfig.h b/battery_mitigation/include/battery_mitigation/MitigationConfig.h index 825e30c6..2062d29d 100644 --- a/battery_mitigation/include/battery_mitigation/MitigationConfig.h +++ b/battery_mitigation/include/battery_mitigation/MitigationConfig.h @@ -17,11 +17,22 @@ #ifndef HARDWARE_GOOGLE_PIXEL_BATTERY_MITIGATION_CONFIG_H #define HARDWARE_GOOGLE_PIXEL_BATTERY_MITIGATION_CONFIG_H +#include "uapi/brownout_stats.h" + namespace android { namespace hardware { namespace google { namespace pixel { +enum TriggeredEvent { + UVLO1, + UVLO2, + OILO1, + OILO2, + SMPL, + MAX_EVENT, +}; + class MitigationConfig { public: struct Config { @@ -32,6 +43,42 @@ class MitigationConfig { const char *const TimestampFormat; }; + struct numericSysfs { + std::string name; + std::string path; + }; + + struct numericSysfsList { + std::string name; + std::vector<std::string> paths; + }; + + struct platformSpecific { + const std::vector<numericSysfsList> NumericSysfsStatPaths; + const std::vector<numericSysfsList> NumericSysfsStatDirs; + }; + + struct pmicCommon { + const char *const OdpmDir; + const char *const OdpmEnabledRailsPath; + const char *const PmicNamePath; + }; + + struct EventThreadConfig { + const std::vector<numericSysfs> NumericSysfsStatPaths; + const std::vector<numericSysfs> NumericSysfsStatDirs; + const char *const TriggeredIdxPath; + const char *const triggeredStatePath[MAX_EVENT]; + const char *const BrownoutStatsPath; + const char *const StoringPath; + const char *const ParsedThismealPath; + const char *const ParsedLastmealPath; + const char *const ParsedLastmealCSVPath; + const char *const FvpStatsPath; + const std::vector<pmicCommon> PmicCommon; + const platformSpecific PlatformSpecific; + }; + MitigationConfig(const struct Config &cfg); private: diff --git a/battery_mitigation/include/battery_mitigation/uapi/brownout_stats.h b/battery_mitigation/include/battery_mitigation/uapi/brownout_stats.h new file mode 100644 index 00000000..c913e40b --- /dev/null +++ b/battery_mitigation/include/battery_mitigation/uapi/brownout_stats.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __BROWNOUT_STATS_H +#define __BROWNOUT_STATS_H + +#define METER_CHANNEL_MAX 12 +#define DATA_LOGGING_LEN 20 +#define TRIGGERED_SOURCE_MAX 17 + +struct odpm_instant_data { + struct timespec time; + unsigned int value[METER_CHANNEL_MAX]; +}; + +/* Notice: sysfs only allocates a buffer of PAGE_SIZE + * so the sizeof brownout_stats should be smaller than that + */ +struct brownout_stats { + struct timespec triggered_time; + unsigned int triggered_idx; + + struct odpm_instant_data main_odpm_instant_data[DATA_LOGGING_LEN]; + struct odpm_instant_data sub_odpm_instant_data[DATA_LOGGING_LEN]; + unsigned int triggered_state[DATA_LOGGING_LEN]; +}; + +#endif /* __BROWNOUT_STATS_H */ diff --git a/common/init.pixel.rc b/common/init.pixel.rc index 25b14fe6..dc499c3b 100644 --- a/common/init.pixel.rc +++ b/common/init.pixel.rc @@ -17,10 +17,3 @@ on property:ota.warm_reset=1 on property:ota.warm_reset=0 write /sys/module/msm_poweroff/parameters/warm_reset 0 - -on init - copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks - -# Migrate tasks again in case kernel threads are created during boot -on property:sys.boot_completed=1 - copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks diff --git a/common/pixel-common-device.mk b/common/pixel-common-device.mk index 5df9cda2..043bc0e0 100644 --- a/common/pixel-common-device.mk +++ b/common/pixel-common-device.mk @@ -57,3 +57,8 @@ PRODUCT_PROPERTY_OVERRIDES += \ # Virtual fingerprint HAL PRODUCT_PACKAGES_DEBUG += com.android.hardware.biometrics.fingerprint.virtual +# Virtual face HAL +ifeq ($(RELEASE_AIDL_USE_UNFROZEN), true) +PRODUCT_PACKAGES_DEBUG += com.android.hardware.biometrics.face.virtual +endif + diff --git a/gpu_probe/Android.bp b/gpu_probe/Android.bp new file mode 100644 index 00000000..370014c7 --- /dev/null +++ b/gpu_probe/Android.bp @@ -0,0 +1,33 @@ +// +// Copyright (C) 2023 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_binary { + name: "gpu_probe", + srcs: [ + "main.rs", + ], + rustlibs: [ + "liblibloading", + "libandroid_logger", + "liblog_rust", + ], + shared_libs: ["libdl"], + vendor: true, + init_rc: [ "gpu_probe.rc" ], +} diff --git a/gpu_probe/gpu_probe.rc b/gpu_probe/gpu_probe.rc new file mode 100644 index 00000000..6cee644b --- /dev/null +++ b/gpu_probe/gpu_probe.rc @@ -0,0 +1,9 @@ +# Start gpu_probe service for Pixel devices. + +service gpu_probe /vendor/bin/gpu_probe + class hal + oneshot + disabled + user system + capabilities SYS_ADMIN + group graphics diff --git a/gpu_probe/main.rs b/gpu_probe/main.rs new file mode 100644 index 00000000..d417180b --- /dev/null +++ b/gpu_probe/main.rs @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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. + */ + +fn main() { + android_logger::init_once( + android_logger::Config::default() + .with_tag("gpu_probe") + .with_max_level(log::LevelFilter::Info), + ); + + log::info!("Starting pixel gpu_probe"); + std::panic::set_hook(Box::new(|panic_msg| { + log::error!("{}", panic_msg); + })); + unsafe { + let gpudataproducer_library = + libloading::Library::new("/vendor/lib64/libgpudataproducer.so").unwrap(); + let start: libloading::Symbol<fn() -> ()> = gpudataproducer_library.get(b"start").unwrap(); + start(); + }; +} diff --git a/health/ChargerDetect.cpp b/health/ChargerDetect.cpp index 26d926ff..a2615294 100644 --- a/health/ChargerDetect.cpp +++ b/health/ChargerDetect.cpp @@ -70,7 +70,7 @@ void ChargerDetect::populateTcpmPsyName(std::string* tcpmPsyName) { while ((entry = readdir(dir.get()))) { const char* name = entry->d_name; - KLOG_INFO(LOG_TAG, "Psy name:%s", name); + KLOG_DEBUG(LOG_TAG, "Psy name:%s", name); if (strstr(name, kTcpmPsyFilter)) { *tcpmPsyName = name; } @@ -119,7 +119,7 @@ void ChargerDetect::onlineUpdate(HealthInfo *health_info) { if (tcpmPsyName.empty()) { populateTcpmPsyName(&tcpmPsyName); - KLOG_INFO(LOG_TAG, "TcpmPsyName:%s\n", tcpmPsyName.c_str()); + KLOG_DEBUG(LOG_TAG, "TcpmPsyName:%s\n", tcpmPsyName.c_str()); } if (!getIntField(kUsbOnlinePath)) { @@ -150,7 +150,7 @@ void ChargerDetect::onlineUpdate(HealthInfo *health_info) { return; } - KLOG_INFO(LOG_TAG, "TcpmPsy Usbtype:%s\n", usbPsyType.c_str()); + KLOG_DEBUG(LOG_TAG, "TcpmPsy Usbtype:%s\n", usbPsyType.c_str()); return; } diff --git a/misc_writer/include/misc_writer/misc_writer.h b/misc_writer/include/misc_writer/misc_writer.h index ba1b6857..7757640d 100644 --- a/misc_writer/include/misc_writer/misc_writer.h +++ b/misc_writer/include/misc_writer/misc_writer.h @@ -40,6 +40,9 @@ enum class MiscWriterActions : int32_t { kWriteTimeOffset, kSetMaxRamSize, kClearMaxRamSize, + kWriteTimeRtcOffset, + kWriteTimeMinRtc, + kSetSotaConfig, kUnset = -1, }; @@ -62,6 +65,12 @@ class MiscWriter { static constexpr uint32_t kMaxRamSizeOffsetInVendorSpace = 192; static constexpr char kMaxRamSize[] = "max-ram-size="; static constexpr uint32_t kSotaStateOffsetInVendorSpace = 224; + static constexpr uint32_t kRTimeRtcOffsetValOffsetInVendorSpace = 264; + static constexpr char kTimeRtcOffset[] = "timertcoffset="; + static constexpr uint32_t kRTimeMinRtcValOffsetInVendorSpace = 296; + static constexpr char kTimeMinRtc[] = "timeminrtc="; + static constexpr uint32_t kFaceauthEvalValOffsetInVendorSpace = 328; + static constexpr uint32_t kSotaScheduleShipmodeOffsetInVendorSpace = 360; // Minimum and maximum valid value for max-ram-size static constexpr int32_t kRamSizeDefault = -1; diff --git a/misc_writer/misc_writer.cpp b/misc_writer/misc_writer.cpp index 216b1883..710c2ffe 100644 --- a/misc_writer/misc_writer.cpp +++ b/misc_writer/misc_writer.cpp @@ -93,6 +93,18 @@ bool MiscWriter::PerformAction(std::optional<size_t> override_offset) { ? std::string(kMaxRamSize).append(stringdata_).append("\n") : std::string(32, 0); break; + case MiscWriterActions::kWriteTimeRtcOffset: + offset = override_offset.value_or(kRTimeRtcOffsetValOffsetInVendorSpace); + content = std::string(kTimeRtcOffset) + stringdata_; + content.resize(32); + break; + case MiscWriterActions::kWriteTimeMinRtc: + offset = override_offset.value_or(kRTimeMinRtcValOffsetInVendorSpace); + content = std::string(kTimeMinRtc) + stringdata_; + content.resize(32); + break; + case MiscWriterActions::kSetSotaConfig: + goto sota_config; case MiscWriterActions::kUnset: LOG(ERROR) << "The misc writer action must be set"; return false; @@ -104,7 +116,8 @@ bool MiscWriter::PerformAction(std::optional<size_t> override_offset) { return false; } - if (action_ == MiscWriterActions::kSetSotaFlag) { +sota_config: + if (action_ == MiscWriterActions::kSetSotaFlag || action_ == MiscWriterActions::kSetSotaConfig) { content = ::android::base::GetProperty("persist.vendor.nfc.factoryota.state", ""); if (content.size() != 0 && content.size() <= 40) { offset = kSotaStateOffsetInVendorSpace; @@ -114,6 +127,15 @@ bool MiscWriter::PerformAction(std::optional<size_t> override_offset) { return false; } } + content = ::android::base::GetProperty("persist.vendor.nfc.factoryota.schedule_shipmode", ""); + if (content.size() != 0 && content.size() <= 32) { + offset = kSotaScheduleShipmodeOffsetInVendorSpace; + if (std::string err; + !WriteMiscPartitionVendorSpace(content.data(), content.size(), offset, &err)) { + LOG(ERROR) << "Failed to write " << content << " at offset " << offset << " : " << err; + return false; + } + } } return true; diff --git a/misc_writer/misc_writer_main.cpp b/misc_writer/misc_writer_main.cpp index 0af45a91..f3546ef6 100644 --- a/misc_writer/misc_writer_main.cpp +++ b/misc_writer/misc_writer_main.cpp @@ -42,6 +42,7 @@ static int Usage(std::string_view name) { std::cerr << " --clear-dark-theme Clear the dark theme flag\n"; std::cerr << " --set-sota Write the silent OTA flag\n"; std::cerr << " --clear-sota Clear the silent OTA flag\n"; + std::cerr << " --set-sota-config Set the silent OTA configs\n"; std::cerr << " --set-enable-pkvm Write the enable pKVM flag\n"; std::cerr << " --set-disable-pkvm Write the disable pKVM flag\n"; std::cerr << " --set-wrist-orientation <0-3> Write the wrist orientation flag\n"; @@ -50,6 +51,8 @@ static int Usage(std::string_view name) { std::cerr << " --set-timeoffset Write the time offset value (tz_time - utc_time)\n"; std::cerr << " --set-max-ram-size <2048-65536> Write the sw limit max ram size in MB\n"; std::cerr << " --set-max-ram-size <-1> Clear the sw limit max ram size\n"; + std::cerr << " --set-timertcoffset Write the time offset value (utc_time - rtc_time)\n"; + std::cerr << " --set-minrtc Write the minimum expected rtc value for tilb\n"; std::cerr << "Writes the given hex string to the specified offset in vendor space in /misc " "partition.\nDefault offset is used for each action unless " "--override-vendor-space-offset is specified.\n"; @@ -71,6 +74,9 @@ int main(int argc, char** argv) { { "set-timeformat", required_argument, nullptr, 0}, { "set-timeoffset", required_argument, nullptr, 0}, { "set-max-ram-size", required_argument, nullptr, 0}, + { "set-timertcoffset", required_argument, nullptr, 0}, + { "set-minrtc", required_argument, nullptr, 0}, + { "set-sota-config", no_argument, nullptr, 0 }, { nullptr, 0, nullptr, 0 }, }; @@ -82,6 +88,7 @@ int main(int argc, char** argv) { { "set-enable-pkvm", MiscWriterActions::kSetEnablePkvmFlag }, { "set-disable-pkvm", MiscWriterActions::kSetDisablePkvmFlag }, { "clear-wrist-orientation", MiscWriterActions::kClearWristOrientationFlag }, + { "set-sota-config", MiscWriterActions::kSetSotaConfig }, }; std::unique_ptr<MiscWriter> misc_writer; @@ -173,6 +180,30 @@ int main(int argc, char** argv) { misc_writer = std::make_unique<MiscWriter>(MiscWriterActions::kSetMaxRamSize, std::to_string(max_ram_size)); } + } else if (option_name == "set-timertcoffset"s) { + long long int timertcoffset = strtoll(optarg, NULL, 10); + if (0 == timertcoffset) { + LOG(ERROR) << "Failed to parse the timertcoffset:" << optarg; + return Usage(argv[0]); + } + if (misc_writer) { + LOG(ERROR) << "Misc writer action has already been set"; + return Usage(argv[0]); + } + misc_writer = std::make_unique<MiscWriter>(MiscWriterActions::kWriteTimeRtcOffset, + std::to_string(timertcoffset)); + } else if (option_name == "set-minrtc"s) { + long long int minrtc = strtoll(optarg, NULL, 10); + if (0 == minrtc) { + LOG(ERROR) << "Failed to parse the minrtc:" << optarg; + return Usage(argv[0]); + } + if (misc_writer) { + LOG(ERROR) << "Misc writer action has already been set"; + return Usage(argv[0]); + } + misc_writer = std::make_unique<MiscWriter>(MiscWriterActions::kWriteTimeMinRtc, + std::to_string(minrtc)); } else if (auto iter = action_map.find(option_name); iter != action_map.end()) { if (misc_writer) { LOG(ERROR) << "Misc writer action has already been set"; diff --git a/mm/pixel-mm-gki.rc b/mm/pixel-mm-gki.rc index 52603d5b..694396e4 100644 --- a/mm/pixel-mm-gki.rc +++ b/mm/pixel-mm-gki.rc @@ -51,6 +51,10 @@ on property:sys.boot_completed=1 write /sys/kernel/tracing/instances/pixel/events/dmabuf_heap/dma_buf_release/enable 1 write /sys/kernel/tracing/instances/pixel/events/trusty/trusty_dma_buf_put/enable 1 + # Allow max_usage_kb to be reset by system processes + chown system system /sys/kernel/vendor_mm/gcma_heap/trusty:faceauth_rawimage_heap/max_usage_kb + chmod 0660 /sys/kernel/vendor_mm/gcma_heap/trusty:faceauth_rawimage_heap/max_usage_kb + # turns off tracing right before bugreporting to keep more traces on property:init.svc.dumpstatez=running write /sys/kernel/tracing/instances/pixel/tracing_on 0 diff --git a/pixelstats/Android.bp b/pixelstats/Android.bp index a0e55a36..92fc08af 100644 --- a/pixelstats/Android.bp +++ b/pixelstats/Android.bp @@ -131,7 +131,7 @@ cc_library_static { generated_headers: ["pixelstatsatoms.h"], export_generated_headers: ["pixelstatsatoms.h"], shared_libs: [ - "android.frameworks.stats-V1-ndk", + "android.frameworks.stats-V2-ndk", ] } @@ -167,7 +167,7 @@ cc_library { "-Werror", ], shared_libs: [ - "android.frameworks.stats-V1-ndk", + "android.frameworks.stats-V2-ndk", "libbase", "libbinder_ndk", "libcutils", @@ -179,7 +179,7 @@ cc_library { "pixelatoms-cpp", ], export_shared_lib_headers: [ - "android.frameworks.stats-V1-ndk", + "android.frameworks.stats-V2-ndk", "pixelatoms-cpp", ], static_libs: [ diff --git a/pixelstats/BatteryEEPROMReporter.cpp b/pixelstats/BatteryEEPROMReporter.cpp index d5ed720a..b8060a75 100644 --- a/pixelstats/BatteryEEPROMReporter.cpp +++ b/pixelstats/BatteryEEPROMReporter.cpp @@ -38,6 +38,7 @@ using android::hardware::google::pixel::PixelAtoms::BatteryEEPROM; #define LINESIZE 71 #define LINESIZE_V2 31 +#define LINESIZE_MAX17201_HIST 80 BatteryEEPROMReporter::BatteryEEPROMReporter() {} @@ -58,7 +59,6 @@ void BatteryEEPROMReporter::checkAndReport(const std::shared_ptr<IStats> &stats_ ALOGE("Unable to read %s - %s", path.c_str(), strerror(errno)); return; } - ALOGD("checkAndReport: %s", file_contents.c_str()); int16_t i, num; struct BatteryHistory hist; @@ -269,6 +269,112 @@ void BatteryEEPROMReporter::reportEvent(const std::shared_ptr<IStats> &stats_cli ALOGE("Unable to report BatteryEEPROM to Stats service"); } +void BatteryEEPROMReporter::checkAndReportGMSR(const std::shared_ptr<IStats> &stats_client, + const std::string &path) { + struct BatteryHistory gmsr; + std::string file_contents; + int16_t num; + + if (path.empty()) + return; + + if (!ReadFileToString(path, &file_contents)) { + ALOGE("Unable to read gmsr path: %s - %s", path.c_str(), strerror(errno)); + return; + } + + gmsr.checksum = 0xFFFF; + if (path.find("max77779") == std::string::npos) { + num = sscanf(file_contents.c_str(), "rcomp0\t:%4" SCNx16 "\ntempco\t:%4" SCNx16 + "\nfullcaprep\t:%4" SCNx16 "\ncycles\t:%4" SCNx16 "\nfullcapnom\t:%4" SCNx16 + "\nqresidual00\t:%4" SCNx16 "\nqresidual10\t:%4" SCNx16 + "\nqresidual20\t:%4" SCNx16 "\nqresidual30\t:%4" SCNx16 + "\ncv_mixcap\t:%4" SCNx16 "\nhalftime\t:%4" SCNx16, + &gmsr.rcomp0, &gmsr.tempco, &gmsr.full_rep, &gmsr.cycle_cnt, &gmsr.full_cap, + &gmsr.max_vbatt, &gmsr.min_vbatt, &gmsr.max_ibatt, &gmsr.min_ibatt, + &gmsr.esr, &gmsr.rslow); + if (num != kNum77759GMSRFields) { + ALOGE("Couldn't process 77759GMSR. num=%d\n", num); + return; + } + } else { + num = sscanf(file_contents.c_str(), "rcomp0\t:%4" SCNx16 "\ntempco\t:%4" SCNx16 + "\nfullcaprep\t:%4" SCNx16 "\ncycles\t:%4" SCNx16 "\nfullcapnom\t:%4" SCNx16, + &gmsr.rcomp0, &gmsr.tempco, &gmsr.full_rep, &gmsr.cycle_cnt, &gmsr.full_cap); + if (num != kNum77779GMSRFields) { + ALOGE("Couldn't process 77779GMSR. num=%d\n", num); + return; + } + } + + reportEvent(stats_client, gmsr); +} + +void BatteryEEPROMReporter::checkAndReportMaxfgHistory(const std::shared_ptr<IStats> &stats_client, + const std::string &path) { + std::string file_contents; + int16_t i; + + if (path.empty()) + return; + + if (!ReadFileToString(path, &file_contents)) { + ALOGD("Unable to read maxfg_hist path: %s - %s", path.c_str(), strerror(errno)); + return; + } + + std::string hist_each; + const int kHistTotalLen = file_contents.size(); + + ALOGD("checkAndReportMaxfgHistory:size=%d\n%s", kHistTotalLen, file_contents.c_str()); + + for (i = 0; i < kHistTotalLen; i++) { + struct BatteryHistory maxfg_hist; + uint16_t nQRTable00, nQRTable10, nQRTable20, nQRTable30, nCycles, nFullCapNom; + uint16_t nRComp0, nTempCo, nIAvgEmpty, nFullCapRep, nVoltTemp, nMaxMinCurr, nMaxMinVolt; + uint16_t nMaxMinTemp, nSOC, nTimerH; + int16_t num; + size_t hist_offset = i * LINESIZE_MAX17201_HIST; + + if (hist_offset >= file_contents.size()) + break; + + hist_each = file_contents.substr(hist_offset, LINESIZE_MAX17201_HIST); + num = sscanf(hist_each.c_str(), "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 + "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 + "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16 "%4" SCNx16, + &nQRTable00, &nQRTable10, &nQRTable20, &nQRTable30, &nCycles, &nFullCapNom, + &nRComp0, &nTempCo, &nIAvgEmpty, &nFullCapRep, &nVoltTemp, &nMaxMinCurr, + &nMaxMinVolt, &nMaxMinTemp, &nSOC, &nTimerH); + + if (num != kNum17201HISTFields) { + ALOGE("Couldn't process %s (num=%d)", hist_each.c_str(), num); + continue; + } + + /* not assign: nQRTable00, nQRTable10, nQRTable20, nQRTable30 */ + maxfg_hist.reserve = 0xFF; + maxfg_hist.tempco = nTempCo; + maxfg_hist.rcomp0 = nRComp0; + maxfg_hist.full_rep = nFullCapNom; + maxfg_hist.full_cap = nFullCapRep; + maxfg_hist.cycle_cnt = nCycles * 16 / 100; // LSB: 16%; + maxfg_hist.timer_h = (nTimerH * 32 / 10) / 24; // LSB: 3.2 hours + maxfg_hist.batt_soc = (nSOC >> 8) & 0x00FF; + maxfg_hist.msoc = nSOC & 0x00FF; + maxfg_hist.max_ibatt = ((nMaxMinCurr >> 8) & 0x00FF) * 80; + maxfg_hist.min_ibatt = (nMaxMinCurr & 0x00FF) * 80 * (-1); + maxfg_hist.max_vbatt = ((nMaxMinVolt >> 8) & 0x00FF) * 20; + maxfg_hist.min_vbatt = (nMaxMinVolt & 0x00FF) * 20; + maxfg_hist.max_temp = (nMaxMinTemp >> 8) & 0x00FF; + maxfg_hist.min_temp = nMaxMinTemp & 0x00FF; + maxfg_hist.esr = nIAvgEmpty; + maxfg_hist.rslow = nVoltTemp; + + reportEvent(stats_client, maxfg_hist); + } +} + } // namespace pixel } // namespace google } // namespace hardware diff --git a/pixelstats/BrownoutDetectedReporter.cpp b/pixelstats/BrownoutDetectedReporter.cpp index e22d2df7..a5f4e39a 100644 --- a/pixelstats/BrownoutDetectedReporter.cpp +++ b/pixelstats/BrownoutDetectedReporter.cpp @@ -218,6 +218,92 @@ long BrownoutDetectedReporter::parseTimestamp(std::string timestamp) { return 0; } +int BrownoutDetectedReporter::brownoutReasonCheck(const std::string &brownoutReasonProp) { + std::string reason = android::base::GetProperty(brownoutReasonProp.c_str(), ""); + if (reason.empty()) { + // Brownout not found + return -1; + } + auto key = kBrownoutReason.find(reason); + if (key == kBrownoutReason.end()) { + return -1; + } + return key->second; +} + +int parseIRQ(const std::string &element) { + int idx = atoi(element.c_str()); + if (idx == SMPL_WARN) { + return BrownoutDetected::SMPL_WARN; + } else if (idx == UVLO1) { + return BrownoutDetected::UVLO1; + } else if (idx == UVLO2) { + return BrownoutDetected::UVLO2; + } else if (idx == BATOILO) { + return BrownoutDetected::BATOILO; + } else if (idx == BATOILO2) { + return BrownoutDetected::BATOILO2; + } + return -1; +} + +void BrownoutDetectedReporter::logBrownoutCsv(const std::shared_ptr<IStats> &stats_client, + const std::string &CsvFilePath, + const std::string &brownoutReasonProp) { + std::string csvFile; + if (!android::base::ReadFileToString(CsvFilePath, &csvFile)) { + return; + } + std::istringstream content(csvFile); + std::string line; + struct BrownoutDetectedInfo max_value = {}; + max_value.voltage_now_ = DEFAULT_BATTERY_VOLT; + max_value.battery_soc_ = DEFAULT_BATTERY_SOC; + max_value.battery_temp_ = DEFAULT_BATTERY_TEMP; + std::smatch pattern_match; + max_value.brownout_reason_ = brownoutReasonCheck(brownoutReasonProp); + if (max_value.brownout_reason_ < 0) { + return; + } + bool isAlreadyUpdated = false; + std::vector<std::vector<std::string>> rows; + int row_num = 0; + while (std::getline(content, line)) { + if (std::regex_match(line, pattern_match, kAlreadyUpdatedPattern)) { + isAlreadyUpdated = true; + break; + } + row_num++; + if (row_num == 1) { + continue; + } + std::vector<std::string> row; + std::stringstream ss(line); + std::string field; + while (getline(ss, field, ',')) { + row.push_back(field); + } + + max_value.triggered_timestamp_ = parseTimestamp(row[TIMESTAMP_IDX].c_str()); + max_value.triggered_irq_ = parseIRQ(row[IRQ_IDX]); + max_value.battery_soc_ = atoi(row[SOC_IDX].c_str()); + max_value.battery_temp_ = atoi(row[TEMP_IDX].c_str()); + max_value.battery_cycle_ = atoi(row[CYCLE_IDX].c_str()); + max_value.voltage_now_ = atoi(row[VOLTAGE_IDX].c_str()); + for (int i = 0; i < DVFS_MAX_IDX; i++) { + max_value.dvfs_value_[i] = atoi(row[i + DVFS_CHANNEL_0].c_str()); + } + for (int i = 0; i < ODPM_MAX_IDX; i++) { + max_value.odpm_value_[i] = atoi(row[i + ODPM_CHANNEL_0].c_str()); + } + } + if (!isAlreadyUpdated && max_value.battery_temp_ != DEFAULT_BATTERY_TEMP) { + std::string file_content = "LASTMEAL_UPDATED\n" + csvFile; + android::base::WriteStringToFile(file_content, CsvFilePath); + uploadData(stats_client, max_value); + } +} + void BrownoutDetectedReporter::logBrownout(const std::shared_ptr<IStats> &stats_client, const std::string &logFilePath, const std::string &brownoutReasonProp) { @@ -233,17 +319,10 @@ void BrownoutDetectedReporter::logBrownout(const std::shared_ptr<IStats> &stats_ max_value.battery_temp_ = DEFAULT_BATTERY_TEMP; std::smatch pattern_match; int odpm_index = 0, dvfs_index = 0; - std::string reason = android::base::GetProperty(brownoutReasonProp.c_str(), ""); - if (reason.empty()) { - // Brownout not found - return; - } - - auto key = kBrownoutReason.find(reason); - if (key == kBrownoutReason.end()) { + max_value.brownout_reason_ = brownoutReasonCheck(brownoutReasonProp); + if (max_value.brownout_reason_ < 0) { return; } - max_value.brownout_reason_ = key->second; bool isAlreadyUpdated = false; while (std::getline(content, line)) { if (std::regex_match(line, pattern_match, kAlreadyUpdatedPattern)) { @@ -277,7 +356,7 @@ void BrownoutDetectedReporter::logBrownout(const std::shared_ptr<IStats> &stats_ max_value.triggered_timestamp_ = parseTimestamp(line.c_str()); continue; } - if (updateIfFound(line, kBatterySocPattern, &max_value.battery_soc_, kUpdateMax)) { + if (updateIfFound(line, kBatterySocPattern, &max_value.battery_soc_, kUpdateMin)) { continue; } if (updateIfFound(line, kBatteryTempPattern, &max_value.battery_temp_, kUpdateMin)) { diff --git a/pixelstats/ChargeStatsReporter.cpp b/pixelstats/ChargeStatsReporter.cpp index 2354acd4..39d91203 100644 --- a/pixelstats/ChargeStatsReporter.cpp +++ b/pixelstats/ChargeStatsReporter.cpp @@ -234,12 +234,9 @@ bool ChargeStatsReporter::shouldReportEvent(void) { void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_client, const std::string &path) { std::string file_contents, line, wfile_contents, wline_at, wline_ac, pca_file_contents, - pca_line, thermal_file_contents, gcharger_file_contents; + pca_line, thermal_file_contents, gcharger_file_contents, gdbatt_file_contents; std::istringstream ss; - bool has_wireless = wireless_charge_stats_.CheckWirelessContentsAndAck(&wfile_contents); - bool has_pca = pca_charge_stats_.CheckPcaContentsAndAck(&pca_file_contents); - bool has_thermal = checkContentsAndAck(&thermal_file_contents, kThermalChargeMetricsPath); - bool has_gcharger = checkContentsAndAck(&gcharger_file_contents, kGChargerMetricsPath); + bool has_wireless, has_pca, has_thermal, has_gcharger, has_dual_batt; if (!ReadFileToString(path.c_str(), &file_contents)) { ALOGE("Unable to read %s - %s", path.c_str(), strerror(errno)); @@ -262,6 +259,7 @@ void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_cl return; } + has_pca = pca_charge_stats_.CheckPcaContentsAndAck(&pca_file_contents); if (has_pca) { std::istringstream pca_ss; @@ -269,6 +267,7 @@ void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_cl std::getline(pca_ss, pca_line); } + has_wireless = wireless_charge_stats_.CheckWirelessContentsAndAck(&wfile_contents); if (has_wireless) { std::istringstream wss; @@ -287,6 +286,7 @@ void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_cl ReportVoltageTierStats(stats_client, line.c_str(), has_wireless, wfile_contents); } + has_thermal = checkContentsAndAck(&thermal_file_contents, kThermalChargeMetricsPath); if (has_thermal) { std::istringstream wss; wss.str(thermal_file_contents); @@ -295,6 +295,7 @@ void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_cl } } + has_gcharger = checkContentsAndAck(&gcharger_file_contents, kGChargerMetricsPath); if (has_gcharger) { std::istringstream wss; wss.str(gcharger_file_contents); @@ -302,6 +303,15 @@ void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_cl ReportVoltageTierStats(stats_client, line.c_str()); } } + + has_dual_batt = checkContentsAndAck(&gdbatt_file_contents, kGDualBattMetricsPath); + if (has_dual_batt) { + std::istringstream wss; + wss.str(gdbatt_file_contents); + while (std::getline(wss, line)) { + ReportVoltageTierStats(stats_client, line.c_str()); + } + } } bool ChargeStatsReporter::checkContentsAndAck(std::string *file_contents, const std::string &path) { diff --git a/pixelstats/StatsHelper.cpp b/pixelstats/StatsHelper.cpp index 1d8d16b4..62af17cb 100644 --- a/pixelstats/StatsHelper.cpp +++ b/pixelstats/StatsHelper.cpp @@ -201,6 +201,31 @@ void reportUsbPortOverheat(const std::shared_ptr<IStats> &stats_client, ALOGE("Unable to report VendorUsbPortOverheat to Stats service"); } +void reportUsbDataSessionEvent(const std::shared_ptr<IStats> &stats_client, + const PixelAtoms::VendorUsbDataSessionEvent &usb_data_event) { + // Load values array + std::vector<VendorAtomValue> values(4); + VendorAtomValue tmp; + tmp.set<VendorAtomValue::intValue>(usb_data_event.usb_role()); + values[0] = tmp; + tmp.set<VendorAtomValue::repeatedIntValue>(std::vector<int32_t>( + usb_data_event.usb_states().begin(), usb_data_event.usb_states().end())); + values[1] = tmp; + tmp.set<VendorAtomValue::repeatedLongValue>(std::vector<int64_t>( + usb_data_event.elapsed_time_ms().begin(), usb_data_event.elapsed_time_ms().end())); + values[2] = tmp; + tmp.set<VendorAtomValue::longValue>(usb_data_event.duration_ms()); + values[3] = tmp; + + // Send vendor atom to IStats HAL + VendorAtom event = {.reverseDomainName = "", + .atomId = PixelAtoms::Atom::kVendorUsbDataSessionEvent, + .values = std::move(values)}; + const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event); + if (!ret.isOk()) + ALOGE("Unable to report VendorUsbDataSessionEvent to Stats service"); +} + } // namespace pixel } // namespace google } // namespace hardware diff --git a/pixelstats/SysfsCollector.cpp b/pixelstats/SysfsCollector.cpp index 4460ca1c..bce967ca 100644 --- a/pixelstats/SysfsCollector.cpp +++ b/pixelstats/SysfsCollector.cpp @@ -62,7 +62,9 @@ using android::hardware::google::pixel::PixelAtoms::StorageUfsHealth; using android::hardware::google::pixel::PixelAtoms::StorageUfsResetCount; using android::hardware::google::pixel::PixelAtoms::ThermalDfsStats; using android::hardware::google::pixel::PixelAtoms::VendorAudioAdaptedInfoStatsReported; +using android::hardware::google::pixel::PixelAtoms::VendorAudioBtMediaStatsReported; using android::hardware::google::pixel::PixelAtoms::VendorAudioHardwareStatsReported; +using android::hardware::google::pixel::PixelAtoms::VendorAudioOffloadedEffectStatsReported; using android::hardware::google::pixel::PixelAtoms::VendorAudioPcmStatsReported; using android::hardware::google::pixel::PixelAtoms::VendorAudioPdmStatsReported; using android::hardware::google::pixel::PixelAtoms::VendorAudioThirdPartyEffectStatsReported; @@ -97,6 +99,7 @@ SysfsCollector::SysfsCollector(const struct SysfsPaths &sysfs_paths) kZramMmStatPath("/sys/block/zram0/mm_stat"), kZramBdStatPath("/sys/block/zram0/bd_stat"), kEEPROMPath(sysfs_paths.EEPROMPath), + kBrownoutCsvPath(sysfs_paths.BrownoutCsvPath), kBrownoutLogPath(sysfs_paths.BrownoutLogPath), kBrownoutReasonProp(sysfs_paths.BrownoutReasonProp), kPowerMitigationStatsPath(sysfs_paths.MitigationPath), @@ -123,7 +126,12 @@ SysfsCollector::SysfsCollector(const struct SysfsPaths &sysfs_paths) kAdaptedInfoDurationPath(sysfs_paths.AdaptedInfoDurationPath), kPcmLatencyPath(sysfs_paths.PcmLatencyPath), kPcmCountPath(sysfs_paths.PcmCountPath), - kTotalCallCountPath(sysfs_paths.TotalCallCountPath) {} + kTotalCallCountPath(sysfs_paths.TotalCallCountPath), + kOffloadEffectsIdPath(sysfs_paths.OffloadEffectsIdPath), + kOffloadEffectsDurationPath(sysfs_paths.OffloadEffectsDurationPath), + kBluetoothAudioUsagePath(sysfs_paths.BluetoothAudioUsagePath), + kGMSRPath(sysfs_paths.GMSRPath), + kMaxfgHistoryPath("/dev/maxfg_history") {} bool SysfsCollector::ReadFileToInt(const std::string &path, int *val) { return ReadFileToInt(path.c_str(), val); @@ -192,10 +200,17 @@ void SysfsCollector::logBatteryChargeCycles(const std::shared_ptr<IStats> &stats void SysfsCollector::logBatteryEEPROM(const std::shared_ptr<IStats> &stats_client) { if (kEEPROMPath == nullptr || strlen(kEEPROMPath) == 0) { ALOGV("Battery EEPROM path not specified"); - return; + } else { + battery_EEPROM_reporter_.checkAndReport(stats_client, kEEPROMPath); + } + + if (kGMSRPath == nullptr || strlen(kGMSRPath) == 0) { + ALOGV("Battery GMSR path not specified"); + } else { + battery_EEPROM_reporter_.checkAndReportGMSR(stats_client, kGMSRPath); } - battery_EEPROM_reporter_.checkAndReport(stats_client, kEEPROMPath); + battery_EEPROM_reporter_.checkAndReportMaxfgHistory(stats_client, kMaxfgHistoryPath); } /** @@ -958,22 +973,23 @@ void SysfsCollector::reportZramMmStat(const std::shared_ptr<IStats> &stats_clien // The size should be the same as the number of fields in ZramMmStat std::vector<VendorAtomValue> values(6); VendorAtomValue tmp; - tmp.set<VendorAtomValue::intValue>(orig_data_size); + tmp.set<VendorAtomValue::longValue>(orig_data_size); values[ZramMmStat::kOrigDataSizeFieldNumber - kVendorAtomOffset] = tmp; - tmp.set<VendorAtomValue::intValue>(compr_data_size); + tmp.set<VendorAtomValue::longValue>(compr_data_size); values[ZramMmStat::kComprDataSizeFieldNumber - kVendorAtomOffset] = tmp; - tmp.set<VendorAtomValue::intValue>(mem_used_total); + tmp.set<VendorAtomValue::longValue>(mem_used_total); values[ZramMmStat::kMemUsedTotalFieldNumber - kVendorAtomOffset] = tmp; - tmp.set<VendorAtomValue::intValue>(same_pages); + tmp.set<VendorAtomValue::longValue>(same_pages); values[ZramMmStat::kSamePagesFieldNumber - kVendorAtomOffset] = tmp; - tmp.set<VendorAtomValue::intValue>(huge_pages); + tmp.set<VendorAtomValue::longValue>(huge_pages); values[ZramMmStat::kHugePagesFieldNumber - kVendorAtomOffset] = tmp; // Skip the first data to avoid a big spike in this accumulated value. if (prev_huge_pages_since_boot_ == -1) - tmp.set<VendorAtomValue::intValue>(0); + tmp.set<VendorAtomValue::longValue>(0); else - tmp.set<VendorAtomValue::intValue>(huge_pages_since_boot - prev_huge_pages_since_boot_); + tmp.set<VendorAtomValue::longValue>(huge_pages_since_boot - + prev_huge_pages_since_boot_); values[ZramMmStat::kHugePagesSinceBootFieldNumber - kVendorAtomOffset] = tmp; prev_huge_pages_since_boot_ = huge_pages_since_boot; @@ -1012,11 +1028,11 @@ void SysfsCollector::reportZramBdStat(const std::shared_ptr<IStats> &stats_clien // Load values array std::vector<VendorAtomValue> values(3); VendorAtomValue tmp; - tmp.set<VendorAtomValue::intValue>(bd_count); + tmp.set<VendorAtomValue::longValue>(bd_count); values[ZramBdStat::kBdCountFieldNumber - kVendorAtomOffset] = tmp; - tmp.set<VendorAtomValue::intValue>(bd_reads); + tmp.set<VendorAtomValue::longValue>(bd_reads); values[ZramBdStat::kBdReadsFieldNumber - kVendorAtomOffset] = tmp; - tmp.set<VendorAtomValue::intValue>(bd_writes); + tmp.set<VendorAtomValue::longValue>(bd_writes); values[ZramBdStat::kBdWritesFieldNumber - kVendorAtomOffset] = tmp; // Send vendor atom to IStats HAL @@ -1523,6 +1539,147 @@ void SysfsCollector::logPcmUsageStats(const std::shared_ptr<IStats> &stats_clien } /** + * Report audio Offload Effects usage stats duration per day. + */ +void SysfsCollector::logOffloadEffectsStats(const std::shared_ptr<IStats> &stats_client) { + std::string file_contents; + std::vector<int> uuids; + std::vector<int> durations; + + if (kOffloadEffectsIdPath == nullptr) { + ALOGD("Offload Effects ID Path is not specified"); + return; + } + + if (kOffloadEffectsDurationPath == nullptr) { + ALOGD("Offload Effects Duration Path is not specified"); + return; + } + + if (!ReadFileToString(kOffloadEffectsIdPath, &file_contents)) { + ALOGD("Unable to read Offload Effect ID path %s", kOffloadEffectsIdPath); + } else { + std::stringstream file_content_stream(file_contents); + int uuid; + while (file_content_stream.good() && file_content_stream >> uuid) { + uuids.push_back(uuid); + } + } + + if (!ReadFileToString(kOffloadEffectsDurationPath, &file_contents)) { + ALOGD("Unable to read Offload Effect duration path %s", kOffloadEffectsDurationPath); + } else { + std::stringstream file_content_stream(file_contents); + int duration; + while (file_content_stream.good() && file_content_stream >> duration) { + durations.push_back(duration); + } + } + + if (durations.size() * 4 != uuids.size()) { + ALOGD("ID and duration data does not match: %zu and %zu", durations.size(), uuids.size()); + return; + } + + for (int index = 0; index < durations.size(); index++) { + std::vector<VendorAtomValue> values(3); + VendorAtomValue tmp; + int64_t uuid_msb = ((int64_t)uuids[index * 4] << 32 | uuids[index * 4 + 1]); + int64_t uuid_lsb = ((int64_t)uuids[index * 4 + 2] << 32 | uuids[index * 4 + 3]); + + if (uuid_msb == 0 && uuid_lsb == 0) { + continue; + } + + tmp.set<VendorAtomValue::VendorAtomValue::longValue>(uuid_msb); + values[VendorAudioOffloadedEffectStatsReported::kEffectUuidMsbFieldNumber - + kVendorAtomOffset] = tmp; + + tmp.set<VendorAtomValue::VendorAtomValue::longValue>(uuid_lsb); + values[VendorAudioOffloadedEffectStatsReported::kEffectUuidLsbFieldNumber - + kVendorAtomOffset] = tmp; + + tmp.set<VendorAtomValue::intValue>(durations[index]); + values[VendorAudioOffloadedEffectStatsReported::kEffectActiveSecondsPerDayFieldNumber - + kVendorAtomOffset] = tmp; + + // Send vendor atom to IStats HAL + VendorAtom event = {.reverseDomainName = "", + .atomId = PixelAtoms::Atom::kVendorAudioOffloadedEffectStatsReported, + .values = std::move(values)}; + + const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event); + if (!ret.isOk()) { + ALOGE("Unable to report VendorAudioOffloadedEffectStatsReported at index %d", index); + } else { + ALOGD("Reported VendorAudioOffloadedEffectStatsReported successfully at index %d", + index); + } + } +} + +/** + * Report bluetooth audio usage stats. + * This function will report at most 5 atoms showing different instance stats. + */ +void SysfsCollector::logBluetoothAudioUsage(const std::shared_ptr<IStats> &stats_client) { + std::string file_contents; + std::vector<int> duration_per_codec; + + constexpr int num_codec = 5; + + if (kBluetoothAudioUsagePath == nullptr) { + ALOGD("Bluetooth Audio stats path not specified"); + return; + } + + if (!ReadFileToString(kBluetoothAudioUsagePath, &file_contents)) { + ALOGD("Unable to read Bluetooth Audio stats path %s", kBluetoothAudioUsagePath); + } else { + std::stringstream file_content_stream(file_contents); + int duration; + while (file_content_stream.good() && file_content_stream >> duration) { + duration_per_codec.push_back(duration); + } + } + + if (duration_per_codec.size() != num_codec) { + ALOGD("Bluetooth Audio num codec != number of codec. %zu / %d", duration_per_codec.size(), + num_codec); + return; + } + + for (int index = 0; index < num_codec; index++) { + std::vector<VendorAtomValue> values(2); + VendorAtomValue tmp; + + if (duration_per_codec[index] == 0) { + ALOGD("Skipped VendorAudioBtMediaStatsReported at codec:%d", index); + continue; + } + + tmp.set<VendorAtomValue::intValue>(index); + values[VendorAudioBtMediaStatsReported::kBtCodecTypeFieldNumber - kVendorAtomOffset] = tmp; + + tmp.set<VendorAtomValue::intValue>(duration_per_codec[index]); + values[VendorAudioBtMediaStatsReported::kActiveSecondsPerDayFieldNumber - + kVendorAtomOffset] = tmp; + + // Send vendor atom to IStats HAL + VendorAtom event = {.reverseDomainName = "", + .atomId = PixelAtoms::Atom::kVendorAudioBtMediaStatsReported, + .values = std::move(values)}; + + const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event); + if (!ret.isOk()) + ALOGE("Unable to report VendorAudioBtMediaStatsReported at index %d", index); + else + ALOGD("Reporting VendorAudioBtMediaStatsReported: codec:%d, duration:%d", index, + duration_per_codec[index]); + } +} + +/** * Logs the Resume Latency stats. */ void SysfsCollector::logVendorResumeLatencyStats(const std::shared_ptr<IStats> &stats_client) { @@ -1939,6 +2096,8 @@ void SysfsCollector::logPerDay() { logWavesStats(stats_client); logAdaptedInfoStats(stats_client); logPcmUsageStats(stats_client); + logOffloadEffectsStats(stats_client); + logBluetoothAudioUsage(stats_client); } void SysfsCollector::aggregatePer5Min() { @@ -1951,7 +2110,10 @@ void SysfsCollector::logBrownout() { ALOGE("Unable to get AIDL Stats service"); return; } - if (kBrownoutLogPath != nullptr && strlen(kBrownoutLogPath) > 0) + if (kBrownoutCsvPath != nullptr && strlen(kBrownoutCsvPath) > 0) + brownout_detected_reporter_.logBrownoutCsv(stats_client, kBrownoutCsvPath, + kBrownoutReasonProp); + else if (kBrownoutLogPath != nullptr && strlen(kBrownoutLogPath) > 0) brownout_detected_reporter_.logBrownout(stats_client, kBrownoutLogPath, kBrownoutReasonProp); } diff --git a/pixelstats/test/TEST_MAPPING b/pixelstats/TEST_MAPPING index b348cb9c..b348cb9c 100644 --- a/pixelstats/test/TEST_MAPPING +++ b/pixelstats/TEST_MAPPING diff --git a/pixelstats/UeventListener.cpp b/pixelstats/UeventListener.cpp index 8af43a01..6ab91f6f 100644 --- a/pixelstats/UeventListener.cpp +++ b/pixelstats/UeventListener.cpp @@ -45,6 +45,7 @@ #include <cutils/uevent.h> #include <fcntl.h> #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h> +#include <linux/thermal.h> #include <log/log.h> #include <pixelstats/StatsHelper.h> #include <pixelstats/UeventListener.h> @@ -68,6 +69,7 @@ using android::base::ReadFileToString; using android::base::WriteStringToFile; using android::hardware::google::pixel::PixelAtoms::GpuEvent; using android::hardware::google::pixel::PixelAtoms::PdVidPid; +using android::hardware::google::pixel::PixelAtoms::ThermalSensorAbnormalityDetected; using android::hardware::google::pixel::PixelAtoms::VendorHardwareFailed; using android::hardware::google::pixel::PixelAtoms::VendorUsbPortOverheat; @@ -80,6 +82,8 @@ constexpr int32_t VID_GOOGLE = 0x18d1; constexpr int32_t PID_OFFSET = 2; constexpr int32_t PID_LENGTH = 4; constexpr uint32_t PID_P30 = 0x4f05; +constexpr const char *THERMAL_ABNORMAL_INFO_EQ = "THERMAL_ABNORMAL_INFO="; +constexpr const char *THERMAL_ABNORMAL_TYPE_EQ = "THERMAL_ABNORMAL_TYPE="; bool UeventListener::ReadFileToInt(const std::string &path, int *val) { return ReadFileToInt(path.c_str(), val); @@ -300,6 +304,87 @@ void UeventListener::ReportGpuEvent(const std::shared_ptr<IStats> &stats_client, ALOGE("Unable to report GPU event."); } +/** + * Report thermal abnormal event. + * The data is sent as uevent environment parameters: + * 1. THERMAL_ABNORMAL_TYPE={type} + * 2. THERMAL_ABNORMAL_INFO=Name:{name},Val:{val} + * This atom logs data in 3 potential events: + * 1. thermistor or tj temperature reading stuck + * 2. thermistor or tj showing very high temperature reading + * 3. thermistor or tj showing very low temperature reading + */ +void UeventListener::ReportThermalAbnormalEvent(const std::shared_ptr<IStats> &stats_client, + const char *devpath, + const char *thermal_abnormal_event_type, + const char *thermal_abnormal_event_info) { + if (!stats_client || !devpath || + strncmp(devpath, "DEVPATH=/module/pixel_metrics", + strlen("DEVPATH=/module/pixel_metrics")) || + !thermal_abnormal_event_type || !thermal_abnormal_event_info) + return; + ALOGD("Thermal Abnormal Type: %s, Thermal Abnormal Info: %s", thermal_abnormal_event_type, + thermal_abnormal_event_info); + std::vector<std::string> type_msg = android::base::Split(thermal_abnormal_event_type, "="); + std::vector<std::string> info_msg = android::base::Split(thermal_abnormal_event_info, "="); + if (type_msg.size() != 2 || info_msg.size() != 2) { + ALOGE("Invalid msg size for thermal abnormal with type(%zu) and info(%zu)", type_msg.size(), + info_msg.size()); + return; + } + + if (type_msg[0] != "THERMAL_ABNORMAL_TYPE" || info_msg[0] != "THERMAL_ABNORMAL_INFO") { + ALOGE("Invalid msg prefix for thermal abnormal with type(%s) and info(%s)", + type_msg[0].c_str(), info_msg[0].c_str()); + return; + } + + auto abnormality_type = kThermalAbnormalityTypeStrToEnum.find(type_msg[1]); + if (abnormality_type == kThermalAbnormalityTypeStrToEnum.end()) { + ALOGE("Unknown thermal abnormal event type %s", type_msg[1].c_str()); + return; + } + + std::vector<std::string> info_list = android::base::Split(info_msg[1], ","); + if (info_list.size() != 2) { + ALOGE("Thermal abnormal info(%s) split size %zu != 2", info_msg[1].c_str(), + info_list.size()); + return; + } + + const auto &name_msg = info_list[0], val_msg = info_list[1]; + if (!android::base::StartsWith(name_msg, "name:") || + !android::base::StartsWith(val_msg, "val:")) { + ALOGE("Invalid prefix for thermal abnormal info name(%s), val(%s)", name_msg.c_str(), + val_msg.c_str()); + return; + } + + auto name_start_pos = std::strlen("name:"); + auto name = name_msg.substr(name_start_pos); + if (name.length() > THERMAL_NAME_LENGTH) { + ALOGE("Invalid sensor name %s with length %zu > %d", name.c_str(), name.length(), + THERMAL_NAME_LENGTH); + return; + } + + auto val_start_pos = std::strlen("val:"); + auto val_str = val_msg.substr(val_start_pos); + int val; + if (sscanf(val_str.c_str(), "%d", &val) != 1) { + ALOGE("Invalid value for thermal abnormal info: %s", val_str.c_str()); + return; + } + ALOGI("Reporting Thermal Abnormal event of type: %s(%d) for %s with val: %d", + abnormality_type->first.c_str(), abnormality_type->second, name.c_str(), val); + VendorAtom event = {.reverseDomainName = "", + .atomId = PixelAtoms::Atom::kThermalSensorAbnormalityDetected, + .values = {abnormality_type->second, name, val}}; + const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event); + if (!ret.isOk()) + ALOGE("Unable to report Thermal Abnormal event."); +} + bool UeventListener::ProcessUevent() { char msg[UEVENT_MSG_LEN + 2]; char *cp; @@ -308,6 +393,7 @@ bool UeventListener::ProcessUevent() { const char *devpath; bool collect_partner_id = false; const char *gpu_event_type = nullptr, *gpu_event_info = nullptr; + const char *thermal_abnormal_event_type = nullptr, *thermal_abnormal_event_info = nullptr; int n; if (uevent_fd_ < 0) { @@ -367,6 +453,10 @@ bool UeventListener::ProcessUevent() { gpu_event_type = cp; } else if (!strncmp(cp, "GPU_UEVENT_INFO=", strlen("GPU_UEVENT_INFO="))) { gpu_event_info = cp; + } else if (!strncmp(cp, THERMAL_ABNORMAL_TYPE_EQ, strlen(THERMAL_ABNORMAL_TYPE_EQ))) { + thermal_abnormal_event_type = cp; + } else if (!strncmp(cp, THERMAL_ABNORMAL_INFO_EQ, strlen(THERMAL_ABNORMAL_INFO_EQ))) { + thermal_abnormal_event_info = cp; } /* advance to after the next \0 */ while (*cp++) { @@ -387,6 +477,8 @@ bool UeventListener::ProcessUevent() { ReportTypeCPartnerId(stats_client); } ReportGpuEvent(stats_client, driver, gpu_event_type, gpu_event_info); + ReportThermalAbnormalEvent(stats_client, devpath, thermal_abnormal_event_type, + thermal_abnormal_event_info); } if (log_fd_ > 0) { diff --git a/pixelstats/include/pixelstats/BatteryEEPROMReporter.h b/pixelstats/include/pixelstats/BatteryEEPROMReporter.h index 13a203b7..41577127 100644 --- a/pixelstats/include/pixelstats/BatteryEEPROMReporter.h +++ b/pixelstats/include/pixelstats/BatteryEEPROMReporter.h @@ -45,6 +45,9 @@ class BatteryEEPROMReporter { public: BatteryEEPROMReporter(); void checkAndReport(const std::shared_ptr<IStats> &stats_client, const std::string &path); + void checkAndReportGMSR(const std::shared_ptr<IStats> &stats_client, const std::string &path); + void checkAndReportMaxfgHistory(const std::shared_ptr<IStats> &stats_client, + const std::string &path); private: // Proto messages are 1-indexed and VendorAtom field numbers start at 2, so @@ -112,7 +115,7 @@ class BatteryEEPROMReporter { /* The number of elements in struct BatteryHistory for P20 series */ const int kNumBatteryHistoryFields = 19; - /* P21 history format */ + /* P21+ history format */ struct BatteryHistoryExtend { uint16_t tempco; uint16_t rcomp0; @@ -135,6 +138,10 @@ class BatteryEEPROMReporter { bool checkLogEvent(struct BatteryHistory hist); void reportEvent(const std::shared_ptr<IStats> &stats_client, const struct BatteryHistory &hist); + + const int kNum77759GMSRFields = 11; + const int kNum77779GMSRFields = 5; + const int kNum17201HISTFields = 16; }; } // namespace pixel diff --git a/pixelstats/include/pixelstats/BrownoutDetectedReporter.h b/pixelstats/include/pixelstats/BrownoutDetectedReporter.h index 5f460d9f..d6437761 100644 --- a/pixelstats/include/pixelstats/BrownoutDetectedReporter.h +++ b/pixelstats/include/pixelstats/BrownoutDetectedReporter.h @@ -34,6 +34,39 @@ using aidl::android::frameworks::stats::VendorAtomValue; #define ODPM_MAX_IDX 24 #define DVFS_MAX_IDX 6 + +enum CsvIdx { + TIMESTAMP_IDX, + IRQ_IDX, + SOC_IDX, + TEMP_IDX, + CYCLE_IDX, + VOLTAGE_IDX, + CURRENT_IDX, + DVFS_CHANNEL_0 = 7, + ODPM_CHANNEL_0 = 12, +}; + +enum Irq { + SMPL_WARN, + OCP_WARN_CPUCL1, + OCP_WARN_CPUCL2, + SOFT_OCP_WARN_CPUCL1, + SOFT_OCP_WARN_CPUCL2, + OCP_WARN_TPU, + SOFT_OCP_WARN_TPU, + OCP_WARN_GPU, + SOFT_OCP_WARN_GPU, + PMIC_SOC, + UVLO1, + UVLO2, + BATOILO, + BATOILO2, + PMIC_120C, + PMIC_140C, + PMIC_OVERHEAT, +}; + enum Update { kUpdateMax, kUpdateMin }; /** @@ -43,6 +76,9 @@ class BrownoutDetectedReporter { public: void logBrownout(const std::shared_ptr<IStats> &stats_client, const std::string &logFilePath, const std::string &brownoutReasonProp); + void logBrownoutCsv(const std::shared_ptr<IStats> &stats_client, const std::string &logFilePath, + const std::string &brownoutReasonProp); + int brownoutReasonCheck(const std::string &brownoutReasonProp); private: struct BrownoutDetectedInfo { diff --git a/pixelstats/include/pixelstats/ChargeStatsReporter.h b/pixelstats/include/pixelstats/ChargeStatsReporter.h index 62c3abc9..69b6eb9c 100644 --- a/pixelstats/include/pixelstats/ChargeStatsReporter.h +++ b/pixelstats/include/pixelstats/ChargeStatsReporter.h @@ -60,6 +60,8 @@ class ChargeStatsReporter { "/sys/devices/platform/google,charger/thermal_stats"; const std::string kGChargerMetricsPath = "/sys/devices/platform/google,charger/charge_stats"; + + const std::string kGDualBattMetricsPath = "/sys/class/power_supply/dualbatt/dbatt_stats"; }; } // namespace pixel diff --git a/pixelstats/include/pixelstats/StatsHelper.h b/pixelstats/include/pixelstats/StatsHelper.h index 01395491..f9366771 100644 --- a/pixelstats/include/pixelstats/StatsHelper.h +++ b/pixelstats/include/pixelstats/StatsHelper.h @@ -50,6 +50,8 @@ void reportUsbPortOverheat(const std::shared_ptr<IStats> &stats_client, void reportSpeakerHealthStat(const std::shared_ptr<IStats> &stats_client, const PixelAtoms::VendorSpeakerStatsReported &speakerHealthStat); +void reportUsbDataSessionEvent(const std::shared_ptr<IStats> &stats_client, + const PixelAtoms::VendorUsbDataSessionEvent &usb_session); } // namespace pixel } // namespace google } // namespace hardware diff --git a/pixelstats/include/pixelstats/SysfsCollector.h b/pixelstats/include/pixelstats/SysfsCollector.h index 494acd71..e60842a4 100644 --- a/pixelstats/include/pixelstats/SysfsCollector.h +++ b/pixelstats/include/pixelstats/SysfsCollector.h @@ -62,6 +62,7 @@ class SysfsCollector { const char *const EEPROMPath; const char *const MitigationPath; const char *const MitigationDurationPath; + const char *const BrownoutCsvPath; const char *const BrownoutLogPath; const char *const BrownoutReasonProp; const char *const SpeakerTemperaturePath; @@ -87,6 +88,10 @@ class SysfsCollector { const char *const PcmLatencyPath; const char *const PcmCountPath; const char *const TotalCallCountPath; + const char *const OffloadEffectsIdPath; + const char *const OffloadEffectsDurationPath; + const char *const BluetoothAudioUsagePath; + const char *const GMSRPath; }; SysfsCollector(const struct SysfsPaths &paths); @@ -140,6 +145,9 @@ class SysfsCollector { void logWavesStats(const std::shared_ptr<IStats> &stats_client); void logAdaptedInfoStats(const std::shared_ptr<IStats> &stats_client); void logPcmUsageStats(const std::shared_ptr<IStats> &stats_client); + void logOffloadEffectsStats(const std::shared_ptr<IStats> &stats_client); + void logBluetoothAudioUsage(const std::shared_ptr<IStats> &stats_client); + void logBatteryGMSR(const std::shared_ptr<IStats> &stats_client); const char *const kSlowioReadCntPath; const char *const kSlowioWriteCntPath; @@ -159,6 +167,7 @@ class SysfsCollector { const char *const kZramMmStatPath; const char *const kZramBdStatPath; const char *const kEEPROMPath; + const char *const kBrownoutCsvPath; const char *const kBrownoutLogPath; const char *const kBrownoutReasonProp; const char *const kPowerMitigationStatsPath; @@ -186,6 +195,11 @@ class SysfsCollector { const char *const kPcmLatencyPath; const char *const kPcmCountPath; const char *const kTotalCallCountPath; + const char *const kOffloadEffectsIdPath; + const char *const kOffloadEffectsDurationPath; + const char *const kBluetoothAudioUsagePath; + const char *const kGMSRPath; + const char *const kMaxfgHistoryPath; BatteryEEPROMReporter battery_EEPROM_reporter_; MmMetricsReporter mm_metrics_reporter_; diff --git a/pixelstats/include/pixelstats/UeventListener.h b/pixelstats/include/pixelstats/UeventListener.h index 352beb88..6e6f8174 100644 --- a/pixelstats/include/pixelstats/UeventListener.h +++ b/pixelstats/include/pixelstats/UeventListener.h @@ -28,7 +28,6 @@ namespace google { namespace pixel { using aidl::android::frameworks::stats::IStats; - /** * A class to listen for uevents and report reliability events to * the PixelStats HAL. @@ -47,6 +46,7 @@ class UeventListener { const char *const TypeCPartnerPidPath; const char *const WirelessChargerPtmcUevent; // Deprecated. const char *const WirelessChargerPtmcPath; // Deprecated. + const char *const GMSRPath; }; constexpr static const char *const ssoc_details_path = "/sys/class/power_supply/battery/ssoc_details"; @@ -59,6 +59,7 @@ class UeventListener { constexpr static const char *const typec_partner_pid_path_default = "/sys/class/typec/port0-partner/identity/product"; constexpr static const char *const typec_partner_uevent_default = "DEVTYPE=typec_partner"; + constexpr static const char *const gmsr_path = ""; UeventListener(const std::string audio_uevent, const std::string ssoc_details_path = "", const std::string overheat_path = overheat_path_default, @@ -90,7 +91,9 @@ class UeventListener { void ReportTypeCPartnerId(const std::shared_ptr<IStats> &stats_client); void ReportGpuEvent(const std::shared_ptr<IStats> &stats_client, const char *driver, const char *gpu_event_type, const char *gpu_event_info); - + void ReportThermalAbnormalEvent(const std::shared_ptr<IStats> &stats_client, + const char *devpath, const char *thermal_abnormal_event_type, + const char *thermal_abnormal_event_info); const std::string kAudioUevent; const std::string kBatterySSOCPath; const std::string kUsbPortOverheatPath; @@ -98,6 +101,7 @@ class UeventListener { const std::string kTypeCPartnerUevent; const std::string kTypeCPartnerVidPath; const std::string kTypeCPartnerPidPath; + const std::string kBatteryGMSRPath; const std::unordered_map<std::string, PixelAtoms::GpuEvent::GpuEventType> kGpuEventTypeStrToEnum{ @@ -140,6 +144,28 @@ class UeventListener { {"CSF_RESET_FAILED", PixelAtoms::GpuEvent::GpuEventInfo:: GpuEvent_GpuEventInfo_MALI_CSF_RESET_FAILED}}; + const std::unordered_map<std::string, + PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType> + kThermalAbnormalityTypeStrToEnum{ + {"UNKNOWN", PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType:: + ThermalSensorAbnormalityDetected_AbnormalityType_UNKNOWN}, + {"SENSOR_STUCK", + PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType:: + ThermalSensorAbnormalityDetected_AbnormalityType_SENSOR_STUCK}, + {"EXTREME_HIGH_TEMP", + PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType:: + ThermalSensorAbnormalityDetected_AbnormalityType_EXTREME_HIGH_TEMP}, + {"EXTREME_LOW_TEMP", + PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType:: + ThermalSensorAbnormalityDetected_AbnormalityType_EXTREME_LOW_TEMP}, + {"HIGH_RISING_SPEED", + PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType:: + ThermalSensorAbnormalityDetected_AbnormalityType_HIGH_RISING_SPEED}, + {"TEMP_READ_FAIL", + PixelAtoms::ThermalSensorAbnormalityDetected::AbnormalityType:: + ThermalSensorAbnormalityDetected_AbnormalityType_TEMP_READ_FAIL}, + }; + BatteryCapacityReporter battery_capacity_reporter_; ChargeStatsReporter charge_stats_reporter_; diff --git a/pixelstats/pixelatoms.proto b/pixelstats/pixelatoms.proto index 3f5e76f4..c773c2b1 100644 --- a/pixelstats/pixelatoms.proto +++ b/pixelstats/pixelatoms.proto @@ -112,6 +112,10 @@ message Atom { VendorAudioAdaptedInfoStatsReported vendor_audio_adapted_info_stats_reported = 105058; GpuEvent gpu_event = 105059; VendorAudioPcmStatsReported vendor_audio_pcm_stats_reported = 105060; + VendorUsbDataSessionEvent vendor_usb_data_session_event = 105061; + ThermalSensorAbnormalityDetected thermal_sensor_abnormality_detected = 105062; + VendorAudioOffloadedEffectStatsReported vendor_audio_offloaded_effect_stats_reported = 105063; + VendorAudioBtMediaStatsReported vendor_audio_bt_media_stats_reported = 105064; } // AOSP atom ID range ends at 109999 reserved 109997; // reserved for VtsVendorAtomJavaTest test atom @@ -1514,6 +1518,7 @@ message BrownoutDetected { SMPL_WARN = 1; UVLO2 = 2; BATOILO = 3; + BATOILO2 = 4; } enum BrownoutReason { @@ -1990,3 +1995,111 @@ message VendorAudioPcmStatsReported { /* Total active count of the pcm type per day. */ optional int32 pcm_active_counts_per_day = 4; } + +/** + * Keep track of information about a USB data session, which is defined + * as the period when a port enters a data role (either host or device) to + * when the port exits the data role. + */ +message VendorUsbDataSessionEvent { + /* Vendor reverse domain name (expecting "com.google.pixel") */ + optional string reverse_domain_name = 1; + + enum UsbDataRole { + USB_ROLE_UNKNOWN = 0; + USB_ROLE_DEVICE = 1; + USB_ROLE_HOST = 2; + } + /** + * USB device states are key milestones in a USB connection. + * For device data role, a typical transition would be like: + * not attached -> default -> addressed -> configured. + * For host data role, a typical transition would be like + * not attached -> powered -> default -> addressed -> configured. + */ + enum UsbDeviceState { + USB_STATE_UNKNOWN = 0; + USB_STATE_NOT_ATTACHED = 1; + USB_STATE_ATTACHED = 2; + USB_STATE_POWERED = 3; + USB_STATE_DEFAULT = 4; + USB_STATE_ADDRESSED = 5; + USB_STATE_CONFIGURED = 6; + USB_STATE_SUSPENDED = 7; + } + /* USB data role of the data session. */ + optional UsbDataRole usb_role = 2; + /* Usb device state transitions during the data session. */ + repeated UsbDeviceState usb_states = 3; + /** + * Elapsed time from the start of the data session when entering the + * state, mapped 1-1 to the usb_states field. + */ + repeated int64 elapsed_time_ms = 4; + // Duration of the data session. + optional int64 duration_ms = 5; +} + +/* + * Logs the thermal sensor abnormal event when detected. + * Logged from: + * virtual sensors: hardware/google/pixel/thermal/utils/thermal_stats_helper.cpp + * thermistors & SoC: hardware/google/pixel/pixelstats/UeventListener.cpp + */ +message ThermalSensorAbnormalityDetected { + enum AbnormalityType { + UNKNOWN = 0; + SENSOR_STUCK = 1; + EXTREME_HIGH_TEMP = 2; + EXTREME_LOW_TEMP = 3; + HIGH_RISING_SPEED = 4; + TEMP_READ_FAIL = 5; + } + + /* Vendor reverse domain name */ + optional string reverse_domain_name = 1; + /* Type of Thermal Sensor Abnormality */ + optional AbnormalityType type = 2; + /* Name of the problematic sensor */ + optional string sensor = 3; + /* Abnormal temp reading of sensor */ + optional int32 temp = 4; +} + +/** + * Logs the reported vendor audio offloaded effects usage stats. + */ +message VendorAudioOffloadedEffectStatsReported { + /* Vendor reverse domain name */ + optional string reverse_domain_name = 1; + + /* UUID most significant bit */ + optional int64 effect_uuid_msb = 2; + + /* UUID least significant bit */ + optional int64 effect_uuid_lsb = 3; + + /* Active seconds per day. */ + optional int32 effect_active_seconds_per_day = 4; +} + +/* + * Logs the Bluetooth Audio stats. + * Two stats are recorded, count and duration (in ms) per features. + */ +message VendorAudioBtMediaStatsReported { + /* Vendor reverse domain name */ + optional string reverse_domain_name = 1; + + enum Codec { + UNKNOWN = 0; + SBC = 1; + AAC = 2; + OPUS = 3; + LC3 = 4; + } + /* Codec to record. */ + optional Codec bt_codec_type = 2; + /* Total active seconds to record. */ + optional int32 active_seconds_per_day = 3; +} diff --git a/power-libperfmgr/Android.bp b/power-libperfmgr/Android.bp index 2163954c..9ccd6fdd 100644 --- a/power-libperfmgr/Android.bp +++ b/power-libperfmgr/Android.bp @@ -24,6 +24,7 @@ cc_library { "disp-power/DisplayLowPower.cpp", "disp-power/InteractionHandler.cpp", ], + cpp_std: "gnu++20", shared_libs: [ "libbase", "libcutils", @@ -41,14 +42,49 @@ cc_library_headers { export_include_dirs: ["hidl"], } +cc_test { + name: "libadpf_test", + defaults: ["android.hardware.power-ndk_static"], + proprietary: true, + vendor: true, + require_root: true, + srcs: [ + "aidl/tests/BackgroundWorkerTest.cpp", + "aidl/tests/PowerHintSessionTest.cpp", + "aidl/tests/SessionTaskMapTest.cpp", + "aidl/tests/UClampVoterTest.cpp", + "aidl/BackgroundWorker.cpp", + "aidl/PowerHintSession.cpp", + "aidl/PowerSessionManager.cpp", + "aidl/SessionTaskMap.cpp", + "aidl/SessionValueEntry.cpp", + "aidl/UClampVoter.cpp", + ], + cpp_std: "gnu++20", + static_libs: [ + "libgmock", + ], + shared_libs: [ + "liblog", + "libbase", + "libcutils", + "libutils", + "libperfmgr", + "libbinder_ndk", + "libprocessgroup", + "pixel-power-ext-V1-ndk", + ], + test_suites: ["device-tests"], +} + cc_binary { name: "android.hardware.power-service.pixel-libperfmgr", + defaults: ["android.hardware.power-ndk_shared"], relative_install_path: "hw", init_rc: ["aidl/android.hardware.power-service.pixel-libperfmgr.rc"], vintf_fragments: ["aidl/android.hardware.power-service.pixel.xml"], vendor: true, shared_libs: [ - "android.hardware.power-V4-ndk", "libbase", "libcutils", "liblog", @@ -58,22 +94,26 @@ cc_binary { "libperfmgr", "libprocessgroup", "pixel-power-ext-V1-ndk", - "libprotobuf-cpp-full", ], srcs: [ + "aidl/BackgroundWorker.cpp", "aidl/service.cpp", "aidl/Power.cpp", "aidl/PowerExt.cpp", "aidl/PowerHintSession.cpp", "aidl/PowerSessionManager.cpp", + "aidl/UClampVoter.cpp", + "aidl/SessionTaskMap.cpp", + "aidl/SessionValueEntry.cpp", ], + cpp_std: "gnu++20", } cc_binary { name: "sendhint", + defaults: ["android.hardware.power-ndk_shared"], vendor: true, shared_libs: [ - "android.hardware.power-V3-ndk", "libbase", "libcutils", "liblog", diff --git a/power-libperfmgr/aidl/AdpfTypes.h b/power-libperfmgr/aidl/AdpfTypes.h new file mode 100644 index 00000000..bd9dbc9c --- /dev/null +++ b/power-libperfmgr/aidl/AdpfTypes.h @@ -0,0 +1,46 @@ +/* + * Copyright 2023 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. + */ + +#pragma once + +#include <cstdint> + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +enum class AdpfErrorCode : int32_t { ERR_OK = 0, ERR_BAD_STATE = -1, ERR_BAD_ARG = -2 }; + +enum class AdpfHintType : int32_t { + ADPF_VOTE_DEFAULT = 1, + ADPF_CPU_LOAD_UP = 2, + ADPF_CPU_LOAD_RESET = 3, + ADPF_CPU_LOAD_RESUME = 4, + ADPF_VOTE_POWER_EFFICIENCY = 5 +}; + +constexpr int kUclampMin{0}; +constexpr int kUclampMax{1024}; + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/AppDescriptorTrace.h b/power-libperfmgr/aidl/AppDescriptorTrace.h new file mode 100644 index 00000000..1f092695 --- /dev/null +++ b/power-libperfmgr/aidl/AppDescriptorTrace.h @@ -0,0 +1,93 @@ +/* + * Copyright 2023 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. + */ + +#pragma once + +#include <aidl/android/hardware/power/SessionMode.h> +#include <android-base/stringprintf.h> + +#include <string> + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +template <class T> +constexpr size_t enum_size() { + return static_cast<size_t>(*(ndk::enum_range<T>().end() - 1)) + 1; +} + +// The App Hint Descriptor struct manages information necessary +// to calculate the next uclamp min value from the PID function +// and is separate so that it can be used as a pointer for +// easily passing to the pid function +struct AppDescriptorTrace { + AppDescriptorTrace(const std::string &idString) { + using ::android::base::StringPrintf; + trace_pid_err = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.err"); + trace_pid_integral = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.integral"); + trace_pid_derivative = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.derivative"); + trace_pid_pOut = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.pOut"); + trace_pid_iOut = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.iOut"); + trace_pid_dOut = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.dOut"); + trace_pid_output = StringPrintf("adpf.%s-%s", idString.c_str(), "pid.output"); + trace_target = StringPrintf("adpf.%s-%s", idString.c_str(), "target"); + trace_active = StringPrintf("adpf.%s-%s", idString.c_str(), "active"); + trace_add_threads = StringPrintf("adpf.%s-%s", idString.c_str(), "add_threads"); + trace_actl_last = StringPrintf("adpf.%s-%s", idString.c_str(), "act_last"); + trace_min = StringPrintf("adpf.%s-%s", idString.c_str(), "min"); + trace_batch_size = StringPrintf("adpf.%s-%s", idString.c_str(), "batch_size"); + trace_hint_count = StringPrintf("adpf.%s-%s", idString.c_str(), "hint_count"); + trace_hint_overtime = StringPrintf("adpf.%s-%s", idString.c_str(), "hint_overtime"); + trace_is_first_frame = StringPrintf("adpf.%s-%s", idString.c_str(), "is_first_frame"); + trace_session_hint = StringPrintf("adpf.%s-%s", idString.c_str(), "session_hint"); + for (size_t i = 0; i < trace_modes.size(); ++i) { + trace_modes[i] = StringPrintf( + "adpf.%s-%s_mode", idString.c_str(), + toString(static_cast<aidl::android::hardware::power::SessionMode>(i)).c_str()); + } + } + + // Trace values + std::string trace_pid_err; + std::string trace_pid_integral; + std::string trace_pid_derivative; + std::string trace_pid_pOut; + std::string trace_pid_iOut; + std::string trace_pid_dOut; + std::string trace_pid_output; + std::string trace_target; + std::string trace_active; + std::string trace_add_threads; + std::string trace_actl_last; + std::string trace_min; + std::string trace_batch_size; + std::string trace_hint_count; + std::string trace_hint_overtime; + std::string trace_is_first_frame; + std::string trace_session_hint; + std::array<std::string, enum_size<aidl::android::hardware::power::SessionMode>()> trace_modes; +}; + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/BackgroundWorker.cpp b/power-libperfmgr/aidl/BackgroundWorker.cpp new file mode 100644 index 00000000..7e86f70f --- /dev/null +++ b/power-libperfmgr/aidl/BackgroundWorker.cpp @@ -0,0 +1,150 @@ +/* + * Copyright 2023 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 "powerhal-libperfmgr" +#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL) + +#include "BackgroundWorker.h" + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +PriorityQueueWorkerPool::PriorityQueueWorkerPool(size_t threadCount, + const std::string &threadNamePrefix) { + mRunning = true; + mThreadPool.reserve(threadCount); + for (size_t threadId = 0; threadId < threadCount; ++threadId) { + mThreadPool.push_back(std::thread([&, tid = threadId]() { loop(); })); + + if (!threadNamePrefix.empty()) { + const std::string fullThreadName = threadNamePrefix + std::to_string(threadId); + pthread_setname_np(mThreadPool.back().native_handle(), fullThreadName.c_str()); + } + } +} + +PriorityQueueWorkerPool::~PriorityQueueWorkerPool() { + { + std::lock_guard<std::mutex> lock(mMutex); + mRunning = false; + mCv.notify_all(); + } + for (auto &t : mThreadPool) { + if (t.joinable()) { + t.join(); + } + } +} + +void PriorityQueueWorkerPool::addCallback(int64_t templateQueueWorkerId, + std::function<void(int64_t)> callback) { + if (!callback) { + // Don't add callback if it isn't callable to prevent having to check later + return; + } + std::unique_lock<std::shared_mutex> lock(mSharedMutex); + auto itr = mCallbackMap.find(templateQueueWorkerId); + if (itr != mCallbackMap.end()) { + return; + } + mCallbackMap[templateQueueWorkerId] = callback; +} + +void PriorityQueueWorkerPool::removeCallback(int64_t templateQueueWorkerId) { + std::unique_lock<std::shared_mutex> lock(mSharedMutex); + auto itr = mCallbackMap.find(templateQueueWorkerId); + if (itr == mCallbackMap.end()) { + return; + } + mCallbackMap.erase(itr); +} + +void PriorityQueueWorkerPool::schedule(int64_t templateQueueWorkerId, int64_t packageId, + std::chrono::steady_clock::time_point deadline) { + std::unique_lock<std::mutex> lock(mMutex); + mPackageQueue.emplace(deadline, templateQueueWorkerId, packageId); + mCv.notify_all(); +} + +void PriorityQueueWorkerPool::loop() { + Package package; + while (mRunning) { + std::unique_lock<std::mutex> lock(mMutex); + // Default to longest wait possible without overflowing if there is + // nothing to work on in the queue + std::chrono::steady_clock::time_point deadline = + std::chrono::steady_clock::time_point::max(); + + // Use next item to work on deadline if available + if (!mPackageQueue.empty()) { + deadline = mPackageQueue.top().deadline; + } + + // Wait until signal or deadline + mCv.wait_until(lock, deadline, [&]() { + // Check if stop running requested, if so return now + if (!mRunning) + return true; + + // Check if nothing in queue (e.g. spurious wakeup), wait as long as possible again + if (mPackageQueue.empty()) { + deadline = std::chrono::steady_clock::time_point::max(); + return false; + } + + // Something in queue, use that as next deadline + deadline = mPackageQueue.top().deadline; + // Check if deadline is in the future still, continue waiting with updated deadline + if (deadline > std::chrono::steady_clock::now()) + return false; + // Next work entry's deadline is in the past or exactly now, time to work on it + return true; + }); + + if (!mRunning) + break; + if (mPackageQueue.empty()) + continue; + + // Copy work entry from queue and unlock + package = mPackageQueue.top(); + mPackageQueue.pop(); + lock.unlock(); + + // Find callback based on package's callback id + { + std::shared_lock<std::shared_mutex> lockCb(mSharedMutex); + auto callbackItr = mCallbackMap.find(package.templateQueueWorkerId); + if (callbackItr == mCallbackMap.end()) { + // Callback was removed before package could be worked on, that's ok just ignore + continue; + } + // Exceptions disabled so no need to wrap this + callbackItr->second(package.packageId); + } + } +} + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/BackgroundWorker.h b/power-libperfmgr/aidl/BackgroundWorker.h new file mode 100644 index 00000000..5d070e12 --- /dev/null +++ b/power-libperfmgr/aidl/BackgroundWorker.h @@ -0,0 +1,151 @@ +/* + * Copyright 2023 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. + */ + +#pragma once + +#include <chrono> +#include <condition_variable> +#include <functional> +#include <mutex> +#include <queue> +#include <shared_mutex> +#include <string> +#include <thread> +#include <unordered_map> + +#include "AdpfTypes.h" + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +// Background thread processing from priority queue based on time deadline +// This class isn't meant to be used directly, use TemplatePriorityQueueWorker below +class PriorityQueueWorkerPool { + public: + // CTOR + // thread count is number of threads to create in thread pool + // thread name prefix is use for naming threads to help with debugging + PriorityQueueWorkerPool(size_t threadCount, const std::string &threadNamePrefix); + // DTOR + ~PriorityQueueWorkerPool(); + // Map callback id to callback function + void addCallback(int64_t templateQueueWorkerId, std::function<void(int64_t)> callback); + // Unmap callback id with callback function + void removeCallback(int64_t templateQueueWorkerId); + // Schedule work for specific worker id with package id to be run at time deadline + void schedule(int64_t templateQueueWorkerId, int64_t packageId, + std::chrono::steady_clock::time_point deadline); + + private: + // Thread coordination + std::mutex mMutex; + bool mRunning; + std::condition_variable mCv; + std::vector<std::thread> mThreadPool; + void loop(); + + // Work package with worker id to find correct callback in + struct Package { + Package() {} + Package(std::chrono::steady_clock::time_point pDeadline, int64_t pTemplateQueueWorkerId, + int64_t pPackageId) + : deadline(pDeadline), + templateQueueWorkerId(pTemplateQueueWorkerId), + packageId(pPackageId) {} + std::chrono::steady_clock::time_point deadline; + int64_t templateQueueWorkerId{0}; + int64_t packageId{0}; + // Sort time earliest first + bool operator<(const Package &p) const { return deadline > p.deadline; } + }; + std::priority_queue<Package> mPackageQueue; + + // Callback management + std::shared_mutex mSharedMutex; + std::unordered_map<int64_t, std::function<void(int64_t)>> mCallbackMap; +}; + +// Generic templated worker for registering a single std::function callback one time +// and reusing it to reduce memory allocations. Many TemplatePriorityQueueWorkers +// can make use of the same PriorityQueue worker which enables sharing a thread pool +// across callbacks of different types. This class is a template to allow for different +// types of work packages while not requiring virtual calls. +template <typename PACKAGE> +class TemplatePriorityQueueWorker { + public: + // CTOR, callback to run when added work is run, worker to use for adding work to + TemplatePriorityQueueWorker(std::function<void(const PACKAGE &)> cb, + std::shared_ptr<PriorityQueueWorkerPool> worker) + : mCallbackId(reinterpret_cast<std::intptr_t>(this)), mCallback(cb), mWorker(worker) { + if (!mCallback) { + mCallback = [](const auto &) {}; + } + mWorker->addCallback(mCallbackId, [&](int64_t packageId) { process(packageId); }); + } + + // DTOR + ~TemplatePriorityQueueWorker() { mWorker->removeCallback(mCallbackId); } + + void schedule(const PACKAGE &package, + std::chrono::steady_clock::time_point t = std::chrono::steady_clock::now()) { + { + std::lock_guard<std::mutex> lock(mMutex); + ++mPackageIdCounter; + mPackages.emplace(mPackageIdCounter, package); + } + mWorker->schedule(mCallbackId, mPackageIdCounter, t); + } + + private: + int64_t mCallbackId{0}; + std::function<void(const PACKAGE &)> mCallback; + // Must ensure PriorityQueueWorker does not go out of scope before this class does + std::shared_ptr<PriorityQueueWorkerPool> mWorker; + mutable std::mutex mMutex; + // Counter is used as a unique identifier for work packages + int64_t mPackageIdCounter{0}; + // Want a container that is: + // fast to add, fast random find find, fast random removal, + // and with reasonable space efficiency + std::unordered_map<int64_t, PACKAGE> mPackages; + + void process(int64_t packageId) { + PACKAGE package; + { + std::lock_guard<std::mutex> lock(mMutex); + auto itr = mPackages.find(packageId); + if (itr == mPackages.end()) { + // Work id does not have matching entry, drop it + return; + } + + package = itr->second; + mPackages.erase(itr); + } + mCallback(package); + } +}; + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/Power.cpp b/power-libperfmgr/aidl/Power.cpp index 382a7b77..e15d985d 100644 --- a/power-libperfmgr/aidl/Power.cpp +++ b/power-libperfmgr/aidl/Power.cpp @@ -183,6 +183,9 @@ ndk::ScopedAStatus Power::isModeSupported(Mode type, bool *_aidl_return) { if (type == Mode::LOW_POWER) { supported = true; } + if (!supported && HintManager::GetInstance()->IsAdpfProfileSupported(toString(type))) { + supported = true; + } LOG(INFO) << "Power mode " << toString(type) << " isModeSupported: " << supported; *_aidl_return = supported; return ndk::ScopedAStatus::ok(); @@ -231,6 +234,9 @@ ndk::ScopedAStatus Power::setBoost(Boost type, int32_t durationMs) { ndk::ScopedAStatus Power::isBoostSupported(Boost type, bool *_aidl_return) { bool supported = HintManager::GetInstance()->IsHintSupported(toString(type)); + if (!supported && HintManager::GetInstance()->IsAdpfProfileSupported(toString(type))) { + supported = true; + } LOG(INFO) << "Power boost " << toString(type) << " isBoostSupported: " << supported; *_aidl_return = supported; return ndk::ScopedAStatus::ok(); diff --git a/power-libperfmgr/aidl/PowerExt.cpp b/power-libperfmgr/aidl/PowerExt.cpp index f6cd084d..bf0a104a 100644 --- a/power-libperfmgr/aidl/PowerExt.cpp +++ b/power-libperfmgr/aidl/PowerExt.cpp @@ -57,6 +57,10 @@ ndk::ScopedAStatus PowerExt::setMode(const std::string &mode, bool enabled) { ndk::ScopedAStatus PowerExt::isModeSupported(const std::string &mode, bool *_aidl_return) { bool supported = HintManager::GetInstance()->IsHintSupported(mode); + + if (!supported && HintManager::GetInstance()->IsAdpfProfileSupported(mode)) { + supported = true; + } LOG(INFO) << "PowerExt mode " << mode << " isModeSupported: " << supported; *_aidl_return = supported; return ndk::ScopedAStatus::ok(); @@ -82,6 +86,9 @@ ndk::ScopedAStatus PowerExt::setBoost(const std::string &boost, int32_t duration ndk::ScopedAStatus PowerExt::isBoostSupported(const std::string &boost, bool *_aidl_return) { bool supported = HintManager::GetInstance()->IsHintSupported(boost); + if (!supported && HintManager::GetInstance()->IsAdpfProfileSupported(boost)) { + supported = true; + } LOG(INFO) << "PowerExt boost " << boost << " isBoostSupported: " << supported; *_aidl_return = supported; return ndk::ScopedAStatus::ok(); diff --git a/power-libperfmgr/aidl/PowerHintSession.cpp b/power-libperfmgr/aidl/PowerHintSession.cpp index eadc4cf3..0262d40d 100644 --- a/power-libperfmgr/aidl/PowerHintSession.cpp +++ b/power-libperfmgr/aidl/PowerHintSession.cpp @@ -48,6 +48,8 @@ using std::chrono::nanoseconds; namespace { +static std::atomic<int64_t> sSessionIDCounter{0}; + static inline int64_t ns_to_100us(int64_t ns) { return ns / 100000; } @@ -57,7 +59,7 @@ static inline int64_t ns_to_100us(int64_t ns) { int64_t PowerHintSession::convertWorkDurationToBoostByPid( const std::vector<WorkDuration> &actualDurations) { std::shared_ptr<AdpfConfig> adpfConfig = HintManager::GetInstance()->GetAdpfProfile(); - const nanoseconds &targetDuration = mDescriptor->duration; + const nanoseconds &targetDuration = mDescriptor->targetNs; int64_t &integral_error = mDescriptor->integral_error; int64_t &previous_error = mDescriptor->previous_error; uint64_t samplingWindowP = adpfConfig->mSamplingWindowP; @@ -103,52 +105,62 @@ int64_t PowerHintSession::convertWorkDurationToBoostByPid( derivative_sum / dt / (length - d_start)); int64_t output = pOut + iOut + dOut; - if (ATRACE_ENABLED()) { - traceSessionVal("pid.err", err_sum / (length - p_start)); - traceSessionVal("pid.integral", integral_error); - traceSessionVal("pid.derivative", derivative_sum / dt / (length - d_start)); - traceSessionVal("pid.pOut", pOut); - traceSessionVal("pid.iOut", iOut); - traceSessionVal("pid.dOut", dOut); - traceSessionVal("pid.output", output); - } + ATRACE_INT(mAppDescriptorTrace.trace_pid_err.c_str(), err_sum / (length - p_start)); + ATRACE_INT(mAppDescriptorTrace.trace_pid_integral.c_str(), integral_error); + ATRACE_INT(mAppDescriptorTrace.trace_pid_derivative.c_str(), + derivative_sum / dt / (length - d_start)); + ATRACE_INT(mAppDescriptorTrace.trace_pid_pOut.c_str(), pOut); + ATRACE_INT(mAppDescriptorTrace.trace_pid_iOut.c_str(), iOut); + ATRACE_INT(mAppDescriptorTrace.trace_pid_dOut.c_str(), dOut); + ATRACE_INT(mAppDescriptorTrace.trace_pid_output.c_str(), output); return output; } +AppHintDesc::AppHintDesc(int64_t sessionId, int32_t tgid, int32_t uid, + std::chrono::nanoseconds pTargetNs) + : sessionId(sessionId), + tgid(tgid), + uid(uid), + targetNs(pTargetNs), + pidSetPoint(0), + is_active(true), + update_count(0), + integral_error(0), + previous_error(0) {} + PowerHintSession::PowerHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t> &threadIds, - int64_t durationNanos) - : mStaleTimerHandler(sp<StaleTimerHandler>::make(this)), - mBoostTimerHandler(sp<BoostTimerHandler>::make(this)) { - mDescriptor = new AppHintDesc(tgid, uid, threadIds); - mDescriptor->duration = std::chrono::nanoseconds(durationNanos); - mIdString = StringPrintf("%" PRId32 "-%" PRId32 "-%" PRIxPTR, mDescriptor->tgid, - mDescriptor->uid, reinterpret_cast<uintptr_t>(this) & 0xffff); - - mPowerManagerHandler = PowerSessionManager::getInstance(); + int64_t durationNs) + : mPSManager(PowerSessionManager::getInstance()), + mSessionId(++sSessionIDCounter), + mIdString(StringPrintf("%" PRId32 "-%" PRId32 "-%" PRId64, tgid, uid, mSessionId)), + mDescriptor(std::make_shared<AppHintDesc>(mSessionId, tgid, uid, + std::chrono::nanoseconds(durationNs))), + mAppDescriptorTrace(mIdString) { + ATRACE_CALL(); + ATRACE_INT(mAppDescriptorTrace.trace_target.c_str(), mDescriptor->targetNs.count()); + ATRACE_INT(mAppDescriptorTrace.trace_active.c_str(), mDescriptor->is_active.load()); + mLastUpdatedTime.store(std::chrono::steady_clock::now()); - if (ATRACE_ENABLED()) { - traceSessionVal("target", mDescriptor->duration.count()); - traceSessionVal("active", mDescriptor->is_active.load()); - } - PowerSessionManager::getInstance()->addPowerSession(this); + mPSManager->addPowerSession(mIdString, mDescriptor, threadIds); // init boost - sendHint(SessionHint::CPU_LOAD_RESET); + auto adpfConfig = HintManager::GetInstance()->GetAdpfProfile(); + mPSManager->voteSet( + mSessionId, AdpfHintType::ADPF_CPU_LOAD_RESET, adpfConfig->mUclampMinHigh, kUclampMax, + std::chrono::steady_clock::now(), + duration_cast<nanoseconds>(mDescriptor->targetNs * adpfConfig->mStaleTimeFactor / 2.0)); + + mPSManager->voteSet(mSessionId, AdpfHintType::ADPF_VOTE_DEFAULT, adpfConfig->mUclampMinInit, + kUclampMax, std::chrono::steady_clock::now(), mDescriptor->targetNs); ALOGV("PowerHintSession created: %s", mDescriptor->toString().c_str()); } PowerHintSession::~PowerHintSession() { + ATRACE_CALL(); close(); ALOGV("PowerHintSession deleted: %s", mDescriptor->toString().c_str()); - if (ATRACE_ENABLED()) { - traceSessionVal("target", 0); - traceSessionVal("actl_last", 0); - traceSessionVal("active", 0); - } - delete mDescriptor; -} - -void PowerHintSession::traceSessionVal(char const *identifier, int64_t val) const { - ATRACE_INT(StringPrintf("adpf.%s-%s", mIdString.c_str(), identifier).c_str(), val); + ATRACE_INT(mAppDescriptorTrace.trace_target.c_str(), 0); + ATRACE_INT(mAppDescriptorTrace.trace_actl_last.c_str(), 0); + ATRACE_INT(mAppDescriptorTrace.trace_active.c_str(), 0); } bool PowerHintSession::isAppSession() { @@ -156,18 +168,16 @@ bool PowerHintSession::isAppSession() { return mDescriptor->uid >= AID_APP_START; } -void PowerHintSession::updateUniveralBoostMode() { - if (!isAppSession()) { - return; - } - if (ATRACE_ENABLED()) { - const std::string tag = StringPrintf("%s:updateUniveralBoostMode()", mIdString.c_str()); - ATRACE_BEGIN(tag.c_str()); - } - PowerHintMonitor::getInstance()->getLooper()->sendMessage(mPowerManagerHandler, NULL); - if (ATRACE_ENABLED()) { - ATRACE_END(); +void PowerHintSession::updatePidSetPoint(int pidSetPoint, bool updateVote) { + mDescriptor->pidSetPoint = pidSetPoint; + if (updateVote) { + auto adpfConfig = HintManager::GetInstance()->GetAdpfProfile(); + mPSManager->voteSet( + mSessionId, AdpfHintType::ADPF_VOTE_DEFAULT, pidSetPoint, kUclampMax, + std::chrono::steady_clock::now(), + duration_cast<nanoseconds>(mDescriptor->targetNs * adpfConfig->mStaleTimeFactor)); } + ATRACE_INT(mAppDescriptorTrace.trace_min.c_str(), pidSetPoint); } void PowerHintSession::tryToSendPowerHint(std::string hint) { @@ -179,30 +189,10 @@ void PowerHintSession::tryToSendPowerHint(std::string hint) { } } -int PowerHintSession::setSessionUclampMin(int32_t min, bool resetStale) { - { - std::lock_guard<std::mutex> guard(mSessionLock); - mDescriptor->current_min = min; - } - if (min != 0 && resetStale) { - mStaleTimerHandler->updateTimer(); - } - PowerSessionManager::getInstance()->setUclampMin(this, min); - - if (ATRACE_ENABLED()) { - traceSessionVal("min", min); - } - return 0; -} - -int PowerHintSession::getUclampMin() { - return mDescriptor->current_min; -} - void PowerHintSession::dumpToStream(std::ostream &stream) { stream << "ID.Min.Act.Timeout(" << mIdString; - stream << ", " << mDescriptor->current_min; - stream << ", " << mDescriptor->is_active.load(); + stream << ", " << mDescriptor->pidSetPoint; + stream << ", " << mDescriptor->is_active; stream << ", " << isTimeout() << ")"; } @@ -215,12 +205,9 @@ ndk::ScopedAStatus PowerHintSession::pause() { return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); // Reset to default uclamp value. mDescriptor->is_active.store(false); - setStale(); - if (ATRACE_ENABLED()) { - traceSessionVal("active", mDescriptor->is_active.load()); - } - updateUniveralBoostMode(); - PowerSessionManager::getInstance()->removeThreadsFromPowerSession(this); + mPSManager->pause(mSessionId); + ATRACE_INT(mAppDescriptorTrace.trace_active.c_str(), false); + ATRACE_INT(mAppDescriptorTrace.trace_min.c_str(), 0); return ndk::ScopedAStatus::ok(); } @@ -232,13 +219,10 @@ ndk::ScopedAStatus PowerHintSession::resume() { if (mDescriptor->is_active.load()) return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); mDescriptor->is_active.store(true); - PowerSessionManager::getInstance()->addThreadsFromPowerSession(this); // resume boost - setSessionUclampMin(mDescriptor->current_min); - if (ATRACE_ENABLED()) { - traceSessionVal("active", mDescriptor->is_active.load()); - } - updateUniveralBoostMode(); + mPSManager->resume(mSessionId); + ATRACE_INT(mAppDescriptorTrace.trace_active.c_str(), true); + ATRACE_INT(mAppDescriptorTrace.trace_min.c_str(), mDescriptor->pidSetPoint); return ndk::ScopedAStatus::ok(); } @@ -248,12 +232,9 @@ ndk::ScopedAStatus PowerHintSession::close() { return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } // Remove the session from PowerSessionManager first to avoid racing. - PowerSessionManager::getInstance()->removePowerSession(this); - mStaleTimerHandler->setSessionDead(); - mBoostTimerHandler->setSessionDead(); - setSessionUclampMin(0); + mPSManager->removePowerSession(mSessionId); mDescriptor->is_active.store(false); - updateUniveralBoostMode(); + ATRACE_INT(mAppDescriptorTrace.trace_min.c_str(), 0); return ndk::ScopedAStatus::ok(); } @@ -268,12 +249,11 @@ ndk::ScopedAStatus PowerHintSession::updateTargetWorkDuration(int64_t targetDura } targetDurationNanos = targetDurationNanos * HintManager::GetInstance()->GetAdpfProfile()->mTargetTimeFactor; - ALOGV("update target duration: %" PRId64 " ns", targetDurationNanos); - mDescriptor->duration = std::chrono::nanoseconds(targetDurationNanos); - if (ATRACE_ENABLED()) { - traceSessionVal("target", mDescriptor->duration.count()); - } + mDescriptor->targetNs = std::chrono::nanoseconds(targetDurationNanos); + mPSManager->updateTargetWorkDuration(mSessionId, AdpfHintType::ADPF_VOTE_DEFAULT, + mDescriptor->targetNs); + ATRACE_INT(mAppDescriptorTrace.trace_target.c_str(), targetDurationNanos); return ndk::ScopedAStatus::ok(); } @@ -284,53 +264,53 @@ ndk::ScopedAStatus PowerHintSession::reportActualWorkDuration( ALOGE("Error: session is dead"); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } - if (mDescriptor->duration.count() == 0LL) { + if (mDescriptor->targetNs.count() == 0LL) { ALOGE("Expect to call updateTargetWorkDuration() first."); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } - if (actualDurations.size() == 0) { - ALOGE("Error: duration.size() shouldn't be %zu.", actualDurations.size()); + if (actualDurations.empty()) { + ALOGE("Error: durations shouldn't be empty."); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } if (!mDescriptor->is_active.load()) { ALOGE("Error: shouldn't report duration during pause state."); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } - std::shared_ptr<AdpfConfig> adpfConfig = HintManager::GetInstance()->GetAdpfProfile(); + auto adpfConfig = HintManager::GetInstance()->GetAdpfProfile(); mDescriptor->update_count++; bool isFirstFrame = isTimeout(); - if (ATRACE_ENABLED()) { - traceSessionVal("batch_size", actualDurations.size()); - traceSessionVal("actl_last", actualDurations.back().durationNanos); - traceSessionVal("target", mDescriptor->duration.count()); - traceSessionVal("hint.count", mDescriptor->update_count); - traceSessionVal("hint.overtime", - actualDurations.back().durationNanos - mDescriptor->duration.count() > 0); - } + ATRACE_INT(mAppDescriptorTrace.trace_batch_size.c_str(), actualDurations.size()); + ATRACE_INT(mAppDescriptorTrace.trace_actl_last.c_str(), actualDurations.back().durationNanos); + ATRACE_INT(mAppDescriptorTrace.trace_target.c_str(), mDescriptor->targetNs.count()); + ATRACE_INT(mAppDescriptorTrace.trace_hint_count.c_str(), mDescriptor->update_count); + ATRACE_INT(mAppDescriptorTrace.trace_hint_overtime.c_str(), + actualDurations.back().durationNanos - mDescriptor->targetNs.count() > 0); + ATRACE_INT(mAppDescriptorTrace.trace_is_first_frame.c_str(), (isFirstFrame) ? (1) : (0)); mLastUpdatedTime.store(std::chrono::steady_clock::now()); if (isFirstFrame) { if (isAppSession()) { tryToSendPowerHint("ADPF_FIRST_FRAME"); } - updateUniveralBoostMode(); + + mPSManager->updateUniversalBoostMode(); } - disableTemporaryBoost(); + mPSManager->disableBoosts(mSessionId); if (!adpfConfig->mPidOn) { - setSessionUclampMin(adpfConfig->mUclampMinHigh); + updatePidSetPoint(adpfConfig->mUclampMinHigh); return ndk::ScopedAStatus::ok(); } int64_t output = convertWorkDurationToBoostByPid(actualDurations); - /* apply to all the threads in the group */ + // Apply to all the threads in the group int next_min = std::min(static_cast<int>(adpfConfig->mUclampMinHigh), - mDescriptor->current_min + static_cast<int>(output)); + mDescriptor->pidSetPoint + static_cast<int>(output)); next_min = std::max(static_cast<int>(adpfConfig->mUclampMinLow), next_min); - setSessionUclampMin(next_min); + updatePidSetPoint(next_min); return ndk::ScopedAStatus::ok(); } @@ -339,26 +319,38 @@ ndk::ScopedAStatus PowerHintSession::sendHint(SessionHint hint) { ALOGE("Error: session is dead"); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } - disableTemporaryBoost(); - std::shared_ptr<AdpfConfig> adpfConfig = HintManager::GetInstance()->GetAdpfProfile(); + if (mDescriptor->targetNs.count() == 0LL) { + ALOGE("Expect to call updateTargetWorkDuration() first."); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + auto adpfConfig = HintManager::GetInstance()->GetAdpfProfile(); + switch (hint) { case SessionHint::CPU_LOAD_UP: - mNextUclampMin.store(mDescriptor->current_min); - mBoostTimerHandler->updateTimer(mDescriptor->duration * 2); - setSessionUclampMin(adpfConfig->mUclampMinHigh); + updatePidSetPoint(mDescriptor->pidSetPoint); + mPSManager->voteSet(mSessionId, AdpfHintType::ADPF_CPU_LOAD_UP, + adpfConfig->mUclampMinHigh, kUclampMax, + std::chrono::steady_clock::now(), mDescriptor->targetNs * 2); break; case SessionHint::CPU_LOAD_DOWN: - setSessionUclampMin(adpfConfig->mUclampMinLow); + updatePidSetPoint(adpfConfig->mUclampMinLow); break; case SessionHint::CPU_LOAD_RESET: - mNextUclampMin.store(std::max(adpfConfig->mUclampMinInit, - static_cast<uint32_t>(mDescriptor->current_min))); - mBoostTimerHandler->updateTimer(duration_cast<nanoseconds>( - mDescriptor->duration * adpfConfig->mStaleTimeFactor / 2.0)); - setSessionUclampMin(adpfConfig->mUclampMinHigh); + updatePidSetPoint(std::max(adpfConfig->mUclampMinInit, + static_cast<uint32_t>(mDescriptor->pidSetPoint)), + false); + mPSManager->voteSet(mSessionId, AdpfHintType::ADPF_CPU_LOAD_RESET, + adpfConfig->mUclampMinHigh, kUclampMax, + std::chrono::steady_clock::now(), + duration_cast<nanoseconds>(mDescriptor->targetNs * + adpfConfig->mStaleTimeFactor / 2.0)); break; case SessionHint::CPU_LOAD_RESUME: - setSessionUclampMin(mDescriptor->current_min); + mPSManager->voteSet(mSessionId, AdpfHintType::ADPF_CPU_LOAD_RESUME, + mDescriptor->pidSetPoint, kUclampMax, + std::chrono::steady_clock::now(), + duration_cast<nanoseconds>(mDescriptor->targetNs * + adpfConfig->mStaleTimeFactor / 2.0)); break; default: ALOGE("Error: hint is invalid"); @@ -366,10 +358,28 @@ ndk::ScopedAStatus PowerHintSession::sendHint(SessionHint hint) { } tryToSendPowerHint(toString(hint)); mLastUpdatedTime.store(std::chrono::steady_clock::now()); - if (ATRACE_ENABLED()) { - mLastHintSent = static_cast<int>(hint); - traceSessionVal("session_hint", static_cast<int>(hint)); + mLastHintSent = static_cast<int>(hint); + ATRACE_INT(mAppDescriptorTrace.trace_session_hint.c_str(), static_cast<int>(hint)); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus PowerHintSession::setMode(SessionMode mode, bool enabled) { + if (mSessionClosed) { + ALOGE("Error: session is dead"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + switch (mode) { + case SessionMode::POWER_EFFICIENCY: + break; + default: + ALOGE("Error: mode is invalid"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } + + mModes[static_cast<size_t>(mode)] = enabled; + ATRACE_INT(mAppDescriptorTrace.trace_modes[static_cast<size_t>(mode)].c_str(), enabled); + mLastUpdatedTime.store(std::chrono::steady_clock::now()); return ndk::ScopedAStatus::ok(); } @@ -378,38 +388,23 @@ ndk::ScopedAStatus PowerHintSession::setThreads(const std::vector<int32_t> &thre ALOGE("Error: session is dead"); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } - if (threadIds.size() == 0) { - LOG(ERROR) << "Error: threadIds.size() shouldn't be " << threadIds.size(); + if (threadIds.empty()) { + ALOGE("Error: threadIds should not be empty"); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } - PowerSessionManager::getInstance()->removeThreadsFromPowerSession(this); - mDescriptor->threadIds.resize(threadIds.size()); - std::copy(threadIds.begin(), threadIds.end(), back_inserter(mDescriptor->threadIds)); - PowerSessionManager::getInstance()->addThreadsFromPowerSession(this); + mPSManager->setThreadsFromPowerSession(mSessionId, threadIds); // init boost - setSessionUclampMin(HintManager::GetInstance()->GetAdpfProfile()->mUclampMinInit); + updatePidSetPoint(HintManager::GetInstance()->GetAdpfProfile()->mUclampMinInit); return ndk::ScopedAStatus::ok(); } std::string AppHintDesc::toString() const { - std::string out = - StringPrintf("session %" PRIxPTR "\n", reinterpret_cast<uintptr_t>(this) & 0xffff); - const int64_t durationNanos = duration.count(); - out.append(StringPrintf(" duration: %" PRId64 " ns\n", durationNanos)); - out.append(StringPrintf(" uclamp.min: %d \n", current_min)); + std::string out = StringPrintf("session %" PRId64 "\n", sessionId); + out.append( + StringPrintf(" duration: %" PRId64 " ns\n", static_cast<int64_t>(targetNs.count()))); + out.append(StringPrintf(" uclamp.min: %d \n", pidSetPoint)); out.append(StringPrintf(" uid: %d, tgid: %d\n", uid, tgid)); - - out.append(" threadIds: ["); - bool first = true; - for (int tid : threadIds) { - if (!first) { - out.append(", "); - } - out.append(std::to_string(tid)); - first = false; - } - out.append("]\n"); return out; } @@ -422,105 +417,11 @@ bool PowerHintSession::isTimeout() { time_point<steady_clock> staleTime = mLastUpdatedTime.load() + nanoseconds(static_cast<int64_t>( - mDescriptor->duration.count() * + mDescriptor->targetNs.count() * HintManager::GetInstance()->GetAdpfProfile()->mStaleTimeFactor)); return now >= staleTime; } -const std::vector<int32_t> &PowerHintSession::getTidList() const { - return mDescriptor->threadIds; -} - -bool PowerHintSession::disableTemporaryBoost() { - if (ATRACE_ENABLED()) { - if (mLastHintSent != -1) { - mLastHintSent = -1; - traceSessionVal("session_hint", -1); - } - } - - // replace temporary uclamp_min value with true min - std::optional<int> trueMin = mNextUclampMin.load(); - if (trueMin.has_value()) { - std::lock_guard<std::mutex> guard(mSessionLock); - mDescriptor->current_min = *trueMin; - mNextUclampMin.store(std::nullopt); - return true; - } - - return false; -} - -void PowerHintSession::setStale() { - // Make sure any temporary boost is disabled - disableTemporaryBoost(); - // Reset to default uclamp value. - PowerSessionManager::getInstance()->setUclampMin(this, 0); - // Deliver a task to check if all sessions are inactive. - updateUniveralBoostMode(); - if (ATRACE_ENABLED()) { - traceSessionVal("min", 0); - } -} - -void PowerHintSession::SessionTimerHandler::updateTimer(nanoseconds delay) { - mTimeout.store(steady_clock::now() + delay); - { - std::lock_guard<std::mutex> guard(mMessageLock); - sp<MessageHandler> selfPtr = sp<MessageHandler>::fromExisting(this); - PowerHintMonitor::getInstance()->getLooper()->removeMessages(selfPtr); - PowerHintMonitor::getInstance()->getLooper()->sendMessageDelayed(delay.count(), selfPtr, - NULL); - } - if (ATRACE_ENABLED()) { - mSession->traceSessionVal(("timer." + mName).c_str(), 0); - } -} - -void PowerHintSession::SessionTimerHandler::handleMessage(const Message &) { - std::lock_guard<std::mutex> guard(mClosedLock); - if (mIsSessionDead) { - return; - } - time_point now = steady_clock::now(); - int64_t next = (mTimeout.load() - now).count(); - if (next > 0) { - // Schedule for the stale timeout check. - std::lock_guard<std::mutex> guard(mMessageLock); - sp<MessageHandler> selfPtr = sp<MessageHandler>::fromExisting(this); - PowerHintMonitor::getInstance()->getLooper()->removeMessages(selfPtr); - PowerHintMonitor::getInstance()->getLooper()->sendMessageDelayed(next, selfPtr, NULL); - } else { - onTimeout(); - } - if (ATRACE_ENABLED()) { - mSession->traceSessionVal(("timer." + mName).c_str(), next > 0 ? 0 : 1); - } -} - -void PowerHintSession::SessionTimerHandler::setSessionDead() { - std::lock_guard<std::mutex> guard(mClosedLock); - mIsSessionDead = true; - PowerHintMonitor::getInstance()->getLooper()->removeMessages( - sp<MessageHandler>::fromExisting(this)); -} - -void PowerHintSession::StaleTimerHandler::updateTimer() { - SessionTimerHandler::updateTimer(duration_cast<nanoseconds>( - mSession->mDescriptor->duration * - HintManager::GetInstance()->GetAdpfProfile()->mStaleTimeFactor)); -} - -void PowerHintSession::StaleTimerHandler::onTimeout() { - mSession->setStale(); -} - -void PowerHintSession::BoostTimerHandler::onTimeout() { - if (mSession->disableTemporaryBoost()) { - mSession->setSessionUclampMin(mSession->getUclampMin(), false); - } -} - } // namespace pixel } // namespace impl } // namespace power diff --git a/power-libperfmgr/aidl/PowerHintSession.h b/power-libperfmgr/aidl/PowerHintSession.h index e1c2523d..4c304ff2 100644 --- a/power-libperfmgr/aidl/PowerHintSession.h +++ b/power-libperfmgr/aidl/PowerHintSession.h @@ -18,13 +18,16 @@ #include <aidl/android/hardware/power/BnPowerHintSession.h> #include <aidl/android/hardware/power/SessionHint.h> +#include <aidl/android/hardware/power/SessionMode.h> #include <aidl/android/hardware/power/WorkDuration.h> #include <utils/Looper.h> #include <utils/Thread.h> -#include <mutex> +#include <array> #include <unordered_map> +#include "AppDescriptorTrace.h" + namespace aidl { namespace google { namespace hardware { @@ -34,6 +37,7 @@ namespace pixel { using aidl::android::hardware::power::BnPowerHintSession; using aidl::android::hardware::power::SessionHint; +using aidl::android::hardware::power::SessionMode; using aidl::android::hardware::power::WorkDuration; using ::android::Message; using ::android::MessageHandler; @@ -43,23 +47,20 @@ using std::chrono::nanoseconds; using std::chrono::steady_clock; using std::chrono::time_point; +class PowerSessionManager; + +// The App Hint Descriptor struct manages information necessary +// to calculate the next uclamp min value from the PID function +// and is separate so that it can be used as a pointer for +// easily passing to the pid function struct AppHintDesc { - AppHintDesc(int32_t tgid, int32_t uid, std::vector<int32_t> threadIds) - : tgid(tgid), - uid(uid), - threadIds(std::move(threadIds)), - duration(0LL), - current_min(0), - is_active(true), - update_count(0), - integral_error(0), - previous_error(0) {} + AppHintDesc(int64_t sessionId, int32_t tgid, int32_t uid, std::chrono::nanoseconds pTargetNs); std::string toString() const; + int64_t sessionId{0}; const int32_t tgid; const int32_t uid; - std::vector<int32_t> threadIds; - nanoseconds duration; - int current_min; + nanoseconds targetNs; + int pidSetPoint; // status std::atomic<bool> is_active; // pid @@ -68,6 +69,10 @@ struct AppHintDesc { int64_t previous_error; }; +// The Power Hint Session is responsible for providing an +// interface for creating, updating, and closing power hints +// for a Session. Each sesion that is mapped to multiple +// threads (or task ids). class PowerHintSession : public BnPowerHintSession { public: explicit PowerHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t> &threadIds, @@ -80,73 +85,33 @@ class PowerHintSession : public BnPowerHintSession { ndk::ScopedAStatus reportActualWorkDuration( const std::vector<WorkDuration> &actualDurations) override; ndk::ScopedAStatus sendHint(SessionHint hint) override; + ndk::ScopedAStatus setMode(SessionMode mode, bool enabled) override; ndk::ScopedAStatus setThreads(const std::vector<int32_t> &threadIds) override; bool isActive(); bool isTimeout(); - void setStale(); - // Is this hint session for a user application + // Is hint session for a user application bool isAppSession(); - const std::vector<int> &getTidList() const; - int getUclampMin(); void dumpToStream(std::ostream &stream); - // Disable any temporary boost and return to normal operation. It does not - // reset the actual uclamp value, and relies on the caller to do so, to - // prevent double-setting. Returns true if it actually disabled an active boost - bool disableTemporaryBoost(); - - private: - class SessionTimerHandler : public MessageHandler { - public: - SessionTimerHandler(PowerHintSession *session, std::string name) - : mSession(session), mIsSessionDead(false), mName(name) {} - void updateTimer(nanoseconds delay); - void handleMessage(const Message &message) override; - void setSessionDead(); - virtual void onTimeout() = 0; - - protected: - PowerHintSession *mSession; - std::mutex mClosedLock; - std::mutex mMessageLock; - std::atomic<time_point<steady_clock>> mTimeout; - bool mIsSessionDead; - const std::string mName; - }; - - class StaleTimerHandler : public SessionTimerHandler { - public: - StaleTimerHandler(PowerHintSession *session) : SessionTimerHandler(session, "stale") {} - void updateTimer(); - void onTimeout() override; - }; - - class BoostTimerHandler : public SessionTimerHandler { - public: - BoostTimerHandler(PowerHintSession *session) : SessionTimerHandler(session, "boost") {} - void onTimeout() override; - }; - private: - void updateUniveralBoostMode(); - int setSessionUclampMin(int32_t min, bool resetStale = true); void tryToSendPowerHint(std::string hint); + void updatePidSetPoint(int pidSetPoint, bool updateVote = true); int64_t convertWorkDurationToBoostByPid(const std::vector<WorkDuration> &actualDurations); - void traceSessionVal(char const *identifier, int64_t val) const; - AppHintDesc *mDescriptor = nullptr; - sp<StaleTimerHandler> mStaleTimerHandler; - sp<BoostTimerHandler> mBoostTimerHandler; + // Data + sp<PowerSessionManager> mPSManager; + int64_t mSessionId = 0; + std::string mIdString; + std::shared_ptr<AppHintDesc> mDescriptor; + // Trace strings + AppDescriptorTrace mAppDescriptorTrace; std::atomic<time_point<steady_clock>> mLastUpdatedTime; - sp<MessageHandler> mPowerManagerHandler; - std::mutex mSessionLock; std::atomic<bool> mSessionClosed = false; - std::string mIdString; - // Used when setting a temporary boost value to hold the true boost - std::atomic<std::optional<int>> mNextUclampMin; - // To cache the status of whether ADPF hints are supported. + // Are cpu load change related hints are supported std::unordered_map<std::string, std::optional<bool>> mSupportedHints; // Last session hint sent, used for logging int mLastHintSent = -1; + // Use the value of the last enum in enum_range +1 as array size + std::array<bool, enum_size<SessionMode>()> mModes{}; }; } // namespace pixel diff --git a/power-libperfmgr/aidl/PowerSessionManager.cpp b/power-libperfmgr/aidl/PowerSessionManager.cpp index 2d2aad25..d8e77bd7 100644 --- a/power-libperfmgr/aidl/PowerSessionManager.cpp +++ b/power-libperfmgr/aidl/PowerSessionManager.cpp @@ -20,12 +20,16 @@ #include "PowerSessionManager.h" #include <android-base/file.h> +#include <android-base/stringprintf.h> #include <log/log.h> #include <perfmgr/HintManager.h> +#include <private/android_filesystem_config.h> #include <processgroup/processgroup.h> #include <sys/syscall.h> #include <utils/Trace.h> +#include "AdpfTypes.h" + namespace aidl { namespace google { namespace hardware { @@ -33,6 +37,7 @@ namespace power { namespace impl { namespace pixel { +using ::android::base::StringPrintf; using ::android::perfmgr::AdpfConfig; using ::android::perfmgr::HintManager; @@ -51,17 +56,10 @@ struct sched_attr { __u32 sched_util_max; }; -static int sched_setattr(int pid, struct sched_attr *attr, unsigned int flags) { - if (!HintManager::GetInstance()->GetAdpfProfile()->mUclampMinOn) { - ALOGV("PowerSessionManager:%s: skip", __func__); - return 0; - } - return syscall(__NR_sched_setattr, pid, attr, flags); -} - -static void set_uclamp_min(int tid, int min) { +static int set_uclamp_min(int tid, int min) { + static constexpr int32_t kMinUclampValue = 0; static constexpr int32_t kMaxUclampValue = 1024; - min = std::max(0, min); + min = std::max(kMinUclampValue, min); min = std::min(min, kMaxUclampValue); sched_attr attr = {}; @@ -70,19 +68,16 @@ static void set_uclamp_min(int tid, int min) { attr.sched_flags = (SCHED_FLAG_KEEP_ALL | SCHED_FLAG_UTIL_CLAMP_MIN); attr.sched_util_min = min; - int ret = sched_setattr(tid, &attr, 0); + const int ret = syscall(__NR_sched_setattr, tid, attr, 0); if (ret) { - if (errno == ESRCH) { - ALOGV("sched_setattr failed for thread %d, err=%d", tid, errno); - } else { - ALOGW("sched_setattr failed for thread %d, err=%d", tid, errno); - } + ALOGW("sched_setattr failed for thread %d, err=%d", tid, errno); + return errno; } + return 0; } } // namespace void PowerSessionManager::updateHintMode(const std::string &mode, bool enabled) { - ALOGV("PowerSessionManager::updateHintMode: mode: %s, enabled: %d", mode.c_str(), enabled); if (enabled && mode.compare(0, 8, "REFRESH_") == 0) { if (mode.compare("REFRESH_120FPS") == 0) { mDisplayRefreshRate = 120; @@ -107,89 +102,99 @@ int PowerSessionManager::getDisplayRefreshRate() { return mDisplayRefreshRate; } -void PowerSessionManager::addPowerSession(PowerHintSession *session) { - std::lock_guard<std::mutex> guard(mLock); - mSessions.insert(session); - addThreadsFromPowerSessionLocked(session); -} +void PowerSessionManager::addPowerSession(const std::string &idString, + const std::shared_ptr<AppHintDesc> &sessionDescriptor, + const std::vector<int32_t> &threadIds) { + if (!sessionDescriptor) { + ALOGE("sessionDescriptor is null. PowerSessionManager failed to add power session: %s", + idString.c_str()); + return; + } + const auto timeNow = std::chrono::steady_clock::now(); + VoteRange pidVoteRange(false, kUclampMin, kUclampMax, timeNow, sessionDescriptor->targetNs); + + SessionValueEntry sve; + sve.tgid = sessionDescriptor->tgid; + sve.uid = sessionDescriptor->uid; + sve.idString = idString; + sve.isActive = sessionDescriptor->is_active; + sve.isAppSession = sessionDescriptor->uid >= AID_APP_START; + sve.lastUpdatedTime = timeNow; + sve.votes = std::make_shared<Votes>(); + sve.votes->add( + static_cast<std::underlying_type_t<AdpfHintType>>(AdpfHintType::ADPF_VOTE_DEFAULT), + pidVoteRange); + + bool addedRes = false; + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + addedRes = mSessionTaskMap.add(sessionDescriptor->sessionId, sve, {}); + } + if (!addedRes) { + ALOGE("sessionTaskMap failed to add power session: %" PRId64, sessionDescriptor->sessionId); + } -void PowerSessionManager::removePowerSession(PowerHintSession *session) { - std::lock_guard<std::mutex> guard(mLock); - mSessions.erase(session); - removeThreadsFromPowerSessionLocked(session); + setThreadsFromPowerSession(sessionDescriptor->sessionId, threadIds); } -void PowerSessionManager::addThreadsFromPowerSession(PowerHintSession *session) { - std::lock_guard<std::mutex> guard(mLock); - addThreadsFromPowerSessionLocked(session); -} +void PowerSessionManager::removePowerSession(int64_t sessionId) { + // To remove a session we also need to undo the effects the session + // has on currently enabled votes which means setting vote to inactive + // and then forceing a uclamp update to occur + forceSessionActive(sessionId, false); + + std::vector<pid_t> addedThreads; + std::vector<pid_t> removedThreads; + + { + // Wait till end to remove session because it needs to be around for apply U clamp + // to work above since applying the uclamp needs a valid session id + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + mSessionTaskMap.replace(sessionId, {}, &addedThreads, &removedThreads); + mSessionTaskMap.remove(sessionId); + } -void PowerSessionManager::addThreadsFromPowerSessionLocked(PowerHintSession *session) { - for (auto t : session->getTidList()) { - if (mTidSessionListMap[t].empty()) { - if (!SetTaskProfiles(t, {"ResetUclampGrp"})) { - ALOGW("Failed to set ResetUclampGrp task profile for tid:%d", t); - } + for (auto tid : removedThreads) { + if (!SetTaskProfiles(tid, {"NoResetUclampGrp"})) { + ALOGE("Failed to set NoResetUclampGrp task profile for tid:%d", tid); } - mTidSessionListMap[t].insert(session); } } -void PowerSessionManager::removeThreadsFromPowerSession(PowerHintSession *session) { - std::lock_guard<std::mutex> guard(mLock); - removeThreadsFromPowerSessionLocked(session); -} - -void PowerSessionManager::removeThreadsFromPowerSessionLocked(PowerHintSession *session) { - for (auto t : session->getTidList()) { - size_t cnt = mTidSessionListMap[t].erase(session); - if (cnt != 0 && mTidSessionListMap[t].empty()) { - if (!SetTaskProfiles(t, {"NoResetUclampGrp"})) { - ALOGW("Failed to set NoResetUclampGrp task profile for tid:%d", t); - } +void PowerSessionManager::setThreadsFromPowerSession(int64_t sessionId, + const std::vector<int32_t> &threadIds) { + std::vector<pid_t> addedThreads; + std::vector<pid_t> removedThreads; + forceSessionActive(sessionId, false); + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + mSessionTaskMap.replace(sessionId, threadIds, &addedThreads, &removedThreads); + } + for (auto tid : addedThreads) { + if (!SetTaskProfiles(tid, {"ResetUclampGrp"})) { + ALOGE("Failed to set ResetUclampGrp task profile for tid:%d", tid); } } -} - -void PowerSessionManager::setUclampMin(PowerHintSession *session, int val) { - std::lock_guard<std::mutex> guard(mLock); - setUclampMinLocked(session, val); -} - -void PowerSessionManager::setUclampMinLocked(PowerHintSession *session, int val) { - for (auto t : session->getTidList()) { - // Get thex max uclamp.min across sessions which include the tid. - int tidMax = 0; - for (PowerHintSession *s : mTidSessionListMap[t]) { - if (!s->isActive() || s->isTimeout()) - continue; - tidMax = std::max(tidMax, s->getUclampMin()); + for (auto tid : removedThreads) { + if (!SetTaskProfiles(tid, {"NoResetUclampGrp"})) { + ALOGE("Failed to set NoResetUclampGrp task profile for tid:%d", tid); } - set_uclamp_min(t, std::max(val, tidMax)); } + forceSessionActive(sessionId, true); } std::optional<bool> PowerSessionManager::isAnyAppSessionActive() { - std::lock_guard<std::mutex> guard(mLock); - bool active = false; - for (PowerHintSession *s : mSessions) { - // session active and not stale is actually active. - if (s->isActive() && !s->isTimeout() && s->isAppSession()) { - active = true; - break; - } - } - if (active == mActive) { - return std::nullopt; - } else { - mActive = active; + bool isAnyAppSessionActive = false; + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + isAnyAppSessionActive = + mSessionTaskMap.isAnyAppSessionActive(std::chrono::steady_clock::now()); } - - return active; + return isAnyAppSessionActive; } -void PowerSessionManager::handleMessage(const Message &) { - auto active = isAnyAppSessionActive(); +void PowerSessionManager::updateUniversalBoostMode() { + const auto active = isAnyAppSessionActive(); if (!active.has_value()) { return; } @@ -202,26 +207,148 @@ void PowerSessionManager::handleMessage(const Message &) { void PowerSessionManager::dumpToFd(int fd) { std::ostringstream dump_buf; - std::lock_guard<std::mutex> guard(mLock); dump_buf << "========== Begin PowerSessionManager ADPF list ==========\n"; - for (PowerHintSession *s : mSessions) { - s->dumpToStream(dump_buf); - dump_buf << " Tid:Ref["; - for (size_t i = 0, len = s->getTidList().size(); i < len; i++) { - int t = s->getTidList()[i]; - dump_buf << t << ":" << mTidSessionListMap[t].size(); - if (i < len - 1) { - dump_buf << ", "; - } - } - dump_buf << "]\n"; - } + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + mSessionTaskMap.forEachSessionValTasks( + [&](auto /* sessionId */, const auto &sessionVal, const auto &tasks) { + sessionVal.dump(dump_buf); + dump_buf << " Tid:Ref["; + + size_t tasksLen = tasks.size(); + for (auto taskId : tasks) { + dump_buf << taskId << ":"; + const auto &sessionIds = mSessionTaskMap.getSessionIds(taskId); + if (!sessionIds.empty()) { + dump_buf << sessionIds.size(); + } + if (tasksLen > 0) { + dump_buf << ", "; + --tasksLen; + } + } + dump_buf << "]\n"; + }); dump_buf << "========== End PowerSessionManager ADPF list ==========\n"; if (!::android::base::WriteStringToFd(dump_buf.str(), fd)) { ALOGE("Failed to dump one of session list to fd:%d", fd); } } +void PowerSessionManager::pause(int64_t sessionId) { + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + auto sessValPtr = mSessionTaskMap.findSession(sessionId); + if (nullptr == sessValPtr) { + ALOGW("Pause failed, session is null %" PRId64, sessionId); + return; + } + + if (!sessValPtr->isActive) { + ALOGW("Sess(%" PRId64 "), cannot pause, already inActive", sessionId); + return; + } + sessValPtr->isActive = false; + } + applyUclamp(sessionId, std::chrono::steady_clock::now()); + updateUniversalBoostMode(); +} + +void PowerSessionManager::resume(int64_t sessionId) { + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + auto sessValPtr = mSessionTaskMap.findSession(sessionId); + if (nullptr == sessValPtr) { + ALOGW("Resume failed, session is null %" PRId64, sessionId); + return; + } + + if (sessValPtr->isActive) { + ALOGW("Sess(%" PRId64 "), cannot resume, already active", sessionId); + return; + } + sessValPtr->isActive = true; + } + applyUclamp(sessionId, std::chrono::steady_clock::now()); + updateUniversalBoostMode(); +} + +void PowerSessionManager::updateTargetWorkDuration(int64_t sessionId, AdpfHintType voteId, + std::chrono::nanoseconds durationNs) { + int voteIdInt = static_cast<std::underlying_type_t<AdpfHintType>>(voteId); + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + auto sessValPtr = mSessionTaskMap.findSession(sessionId); + if (nullptr == sessValPtr) { + ALOGE("Failed to updateTargetWorkDuration, session val is null id: %" PRId64, sessionId); + return; + } + + sessValPtr->votes->updateDuration(voteIdInt, durationNs); + // Note, for now we are not recalculating and applying uclamp because + // that maintains behavior from before. In the future we may want to + // revisit that decision. +} + +void PowerSessionManager::voteSet(int64_t sessionId, AdpfHintType voteId, int uclampMin, + int uclampMax, std::chrono::steady_clock::time_point startTime, + std::chrono::nanoseconds durationNs) { + const int voteIdInt = static_cast<std::underlying_type_t<AdpfHintType>>(voteId); + const auto timeoutDeadline = startTime + durationNs; + const VoteRange vr(true, uclampMin, uclampMax, startTime, durationNs); + bool scheduleTimeout = false; + + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + auto sessValPtr = mSessionTaskMap.findSession(sessionId); + if (nullptr == sessValPtr) { + // Because of the async nature of some events an event for a session + // that has been removed is a possibility so this is a verbose log + // instead of a warning or error + return; + } + + if (!sessValPtr->votes->voteIsActive(voteIdInt)) { + scheduleTimeout = true; + } + if (timeoutDeadline < sessValPtr->votes->voteTimeout(voteIdInt)) { + scheduleTimeout = true; + } + sessValPtr->votes->add(voteIdInt, vr); + sessValPtr->lastUpdatedTime = startTime; + } + + applyUclamp(sessionId, startTime); // std::chrono::steady_clock::now()); + + if (scheduleTimeout) { + // Sent event to handle stale-vote/timeout in the future + EventSessionTimeout eTimeout; + eTimeout.timeStamp = startTime; // eSet.timeStamp; + eTimeout.sessionId = sessionId; + eTimeout.voteId = voteIdInt; + mEventSessionTimeoutWorker.schedule(eTimeout, timeoutDeadline); + } +} + +void PowerSessionManager::disableBoosts(int64_t sessionId) { + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + auto sessValPtr = mSessionTaskMap.findSession(sessionId); + if (nullptr == sessValPtr) { + // Because of the async nature of some events an event for a session + // that has been removed is a possibility so this is a verbose log + // instead of a warning or error + return; + } + + // sessValPtr->disableBoosts(); + for (auto vid : + {AdpfHintType::ADPF_CPU_LOAD_UP, AdpfHintType::ADPF_CPU_LOAD_RESET, + AdpfHintType::ADPF_CPU_LOAD_RESUME, AdpfHintType::ADPF_VOTE_POWER_EFFICIENCY}) { + auto vint = static_cast<std::underlying_type_t<AdpfHintType>>(vid); + sessValPtr->votes->setUseVote(vint, false); + } + } +} + void PowerSessionManager::enableSystemTopAppBoost() { if (HintManager::GetInstance()->IsHintSupported(kDisableBoostHintName)) { ALOGV("PowerSessionManager::enableSystemTopAppBoost!!"); @@ -236,22 +363,105 @@ void PowerSessionManager::disableSystemTopAppBoost() { } } -// =========== PowerHintMonitor implementation start from here =========== -void PowerHintMonitor::start() { - if (!isRunning()) { - run("PowerHintMonitor", ::android::PRIORITY_HIGHEST); +void PowerSessionManager::handleEvent(const EventSessionTimeout &eventTimeout) { + bool recalcUclamp = false; + const auto tNow = std::chrono::steady_clock::now(); + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + auto sessValPtr = mSessionTaskMap.findSession(eventTimeout.sessionId); + if (nullptr == sessValPtr) { + // It is ok for session timeouts to fire after a session has been + // removed + return; + } + + // To minimize the number of events pushed into the queue, we are using + // the following logic to make use of a single timeout event which will + // requeue itself if the timeout has been changed since it was added to + // the work queue. Requeue Logic: + // if vote active and vote timeout <= sched time + // then deactivate vote and recalc uclamp (near end of function) + // if vote active and vote timeout > sched time + // then requeue timeout event for new deadline (which is vote timeout) + const bool voteIsActive = sessValPtr->votes->voteIsActive(eventTimeout.voteId); + const auto voteTimeout = sessValPtr->votes->voteTimeout(eventTimeout.voteId); + + if (voteIsActive) { + if (voteTimeout <= tNow) { + sessValPtr->votes->setUseVote(eventTimeout.voteId, false); + recalcUclamp = true; + } else { + // Can unlock sooner than we do + auto eventTimeout2 = eventTimeout; + mEventSessionTimeoutWorker.schedule(eventTimeout2, voteTimeout); + } + } + } + + if (!recalcUclamp) { + return; } + + // It is important to use the correct time here, time now is more reasonable + // than trying to use the event's timestamp which will be slightly off given + // the background priority queue introduces latency + applyUclamp(eventTimeout.sessionId, tNow); + updateUniversalBoostMode(); } -bool PowerHintMonitor::threadLoop() { - while (true) { - mLooper->pollOnce(-1); +void PowerSessionManager::applyUclamp(int64_t sessionId, + std::chrono::steady_clock::time_point timePoint) { + const bool uclampMinOn = HintManager::GetInstance()->GetAdpfProfile()->mUclampMinOn; + + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + auto sessValPtr = mSessionTaskMap.findSession(sessionId); + if (nullptr == sessValPtr) { + return; + } + + if (!uclampMinOn) { + ALOGV("PowerSessionManager::set_uclamp_min: skip"); + } else { + auto &threadList = mSessionTaskMap.getTaskIds(sessionId); + auto tidIter = threadList.begin(); + while (tidIter != threadList.end()) { + UclampRange uclampRange; + mSessionTaskMap.getTaskVoteRange(*tidIter, timePoint, &uclampRange.uclampMin, + &uclampRange.uclampMax); + int stat = set_uclamp_min(*tidIter, uclampRange.uclampMin); + if (stat == ESRCH) { + ALOGV("Removing dead thread %d from hint session %s.", *tidIter, + sessValPtr->idString.c_str()); + if (mSessionTaskMap.removeDeadTaskSessionMap(sessionId, *tidIter)) { + ALOGV("Removed dead thread-session map."); + } + tidIter = threadList.erase(tidIter); + } else { + tidIter++; + } + } + } + + sessValPtr->lastUpdatedTime = timePoint; } - return true; } -sp<Looper> PowerHintMonitor::getLooper() { - return mLooper; +void PowerSessionManager::forceSessionActive(int64_t sessionId, bool isActive) { + { + std::lock_guard<std::mutex> lock(mSessionTaskMapMutex); + auto sessValPtr = mSessionTaskMap.findSession(sessionId); + if (nullptr == sessValPtr) { + return; + } + sessValPtr->isActive = isActive; + } + + // As currently written, call needs to occur synchronously so as to ensure + // that the SessionId remains valid and mapped to the proper threads/tasks + // which enables apply u clamp to work correctly + applyUclamp(sessionId, std::chrono::steady_clock::now()); + updateUniversalBoostMode(); } } // namespace pixel diff --git a/power-libperfmgr/aidl/PowerSessionManager.h b/power-libperfmgr/aidl/PowerSessionManager.h index 7c74039a..b55e7475 100644 --- a/power-libperfmgr/aidl/PowerSessionManager.h +++ b/power-libperfmgr/aidl/PowerSessionManager.h @@ -24,7 +24,9 @@ #include <optional> #include <unordered_set> +#include "BackgroundWorker.h" #include "PowerHintSession.h" +#include "SessionTaskMap.h" namespace aidl { namespace google { @@ -35,30 +37,41 @@ namespace pixel { using ::android::Looper; using ::android::Message; -using ::android::MessageHandler; using ::android::Thread; using ::android::perfmgr::HintManager; constexpr char kPowerHalAdpfDisableTopAppBoost[] = "vendor.powerhal.adpf.disable.hint"; -class PowerSessionManager : public MessageHandler { +class PowerSessionManager : public ::android::RefBase { public: - // current hint info + // Update the current hint info void updateHintMode(const std::string &mode, bool enabled); void updateHintBoost(const std::string &boost, int32_t durationMs); int getDisplayRefreshRate(); - // monitoring session status - void addPowerSession(PowerHintSession *session); - void removePowerSession(PowerHintSession *session); - void addThreadsFromPowerSession(PowerHintSession *session); - void addThreadsFromPowerSessionLocked(PowerHintSession *session); - void removeThreadsFromPowerSession(PowerHintSession *session); - void removeThreadsFromPowerSessionLocked(PowerHintSession *session); - void setUclampMin(PowerHintSession *session, int min); - void setUclampMinLocked(PowerHintSession *session, int min); - void handleMessage(const Message &message) override; + // Add and remove power hint session + void addPowerSession(const std::string &idString, + const std::shared_ptr<AppHintDesc> &sessionDescriptor, + const std::vector<int32_t> &threadIds); + void removePowerSession(int64_t sessionId); + // Replace current threads in session with threadIds + void setThreadsFromPowerSession(int64_t sessionId, const std::vector<int32_t> &threadIds); + // Pause and resume power hint session + void pause(int64_t sessionId); + void resume(int64_t sessionId); + + void updateUniversalBoostMode(); void dumpToFd(int fd); + void updateTargetWorkDuration(int64_t sessionId, AdpfHintType voteId, + std::chrono::nanoseconds durationNs); + + // Set vote for power hint session + void voteSet(int64_t sessionId, AdpfHintType voteId, int uclampMin, int uclampMax, + std::chrono::steady_clock::time_point startTime, + std::chrono::nanoseconds durationNs); + + void disableBoosts(int64_t sessionId); + // Singleton static sp<PowerSessionManager> getInstance() { static sp<PowerSessionManager> instance = new PowerSessionManager(); @@ -71,43 +84,38 @@ class PowerSessionManager : public MessageHandler { void enableSystemTopAppBoost(); const std::string kDisableBoostHintName; - std::unordered_set<PowerHintSession *> mSessions; // protected by mLock - std::unordered_map<int, std::unordered_set<PowerHintSession *>> mTidSessionListMap; - bool mActive; // protected by mLock - /** - * mLock to pretect the above data objects opertions. - **/ - std::mutex mLock; int mDisplayRefreshRate; + + // Rewrite specific + mutable std::mutex mSessionTaskMapMutex; + SessionTaskMap mSessionTaskMap; + std::shared_ptr<PriorityQueueWorkerPool> mPriorityQueueWorkerPool; + + // Session timeout + struct EventSessionTimeout { + std::chrono::steady_clock::time_point timeStamp; + int64_t sessionId{0}; + int voteId{0}; + }; + void handleEvent(const EventSessionTimeout &e); + TemplatePriorityQueueWorker<EventSessionTimeout> mEventSessionTimeoutWorker; + + // Calculate uclamp range + void applyUclamp(int64_t sessionId, std::chrono::steady_clock::time_point timePoint); + // Force a session active or in-active, helper for other methods + void forceSessionActive(int64_t sessionId, bool isActive); + // Singleton PowerSessionManager() : kDisableBoostHintName(::android::base::GetProperty(kPowerHalAdpfDisableTopAppBoost, "ADPF_DISABLE_TA_BOOST")), - mActive(false), - mDisplayRefreshRate(60) {} + mDisplayRefreshRate(60), + mPriorityQueueWorkerPool(new PriorityQueueWorkerPool(1, "adpf_handler")), + mEventSessionTimeoutWorker([&](auto e) { handleEvent(e); }, mPriorityQueueWorkerPool) {} PowerSessionManager(PowerSessionManager const &) = delete; void operator=(PowerSessionManager const &) = delete; }; -class PowerHintMonitor : public Thread { - public: - void start(); - bool threadLoop() override; - sp<Looper> getLooper(); - // Singleton - static sp<PowerHintMonitor> getInstance() { - static sp<PowerHintMonitor> instance = new PowerHintMonitor(); - return instance; - } - PowerHintMonitor(PowerHintMonitor const &) = delete; - void operator=(PowerHintMonitor const &) = delete; - - private: - sp<Looper> mLooper; - // Singleton - PowerHintMonitor() : Thread(false), mLooper(new Looper(true)) {} -}; - } // namespace pixel } // namespace impl } // namespace power diff --git a/power-libperfmgr/aidl/SessionTaskMap.cpp b/power-libperfmgr/aidl/SessionTaskMap.cpp new file mode 100644 index 00000000..62dae33c --- /dev/null +++ b/power-libperfmgr/aidl/SessionTaskMap.cpp @@ -0,0 +1,261 @@ +/* + * Copyright 2023 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 "SessionTaskMap.h" + +#include <algorithm> +#include <sstream> + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +bool SessionTaskMap::add(int64_t sessionId, const SessionValueEntry &sv, + const std::vector<pid_t> &taskIds) { + if (mSessions.find(sessionId) != mSessions.end()) { + return false; + } + + auto sessValPtr = std::make_shared<SessionValueEntry>(); + (*sessValPtr) = sv; + sessValPtr->sessionId = sessionId; + + auto &sessEntry = mSessions[sessionId]; + sessEntry.val = sessValPtr; + sessEntry.linkedTasks = taskIds; + + for (auto taskId : taskIds) { + mTasks[taskId].push_back(sessValPtr); + } + return true; +} + +void SessionTaskMap::addVote(int64_t sessionId, int voteId, int uclampMin, int uclampMax, + std::chrono::steady_clock::time_point startTime, + std::chrono::nanoseconds durationNs) { + auto sessItr = mSessions.find(sessionId); + if (sessItr == mSessions.end()) { + return; + } + + sessItr->second.val->votes->add(voteId, + VoteRange(true, uclampMin, uclampMax, startTime, durationNs)); +} + +std::shared_ptr<SessionValueEntry> SessionTaskMap::findSession(int64_t sessionId) { + auto sessItr = mSessions.find(sessionId); + if (sessItr == mSessions.end()) { + return nullptr; + } + return sessItr->second.val; +} + +void SessionTaskMap::getTaskVoteRange(pid_t taskId, std::chrono::steady_clock::time_point timeNow, + int *uclampMin, int *uclampMax) const { + UclampRange uclampRange; + auto taskItr = mTasks.find(taskId); + if (taskItr == mTasks.end()) { + return; + } + + for (const auto &sessInTask : taskItr->second) { + if (!sessInTask->isActive) { + continue; + } + sessInTask->votes->getUclampRange(&uclampRange, timeNow); + } + *uclampMin = uclampRange.uclampMin; + *uclampMax = uclampRange.uclampMax; +} + +std::vector<int64_t> SessionTaskMap::getSessionIds(pid_t taskId) const { + auto itr = mTasks.find(taskId); + if (itr == mTasks.end()) { + static const std::vector<int64_t> emptySessionIdVec; + return emptySessionIdVec; + } + std::vector<int64_t> res; + res.reserve(itr->second.size()); + for (const auto &i : itr->second) { + res.push_back(i->sessionId); + } + return res; +} + +std::vector<pid_t> &SessionTaskMap::getTaskIds(int64_t sessionId) { + auto taskItr = mSessions.find(sessionId); + if (taskItr == mSessions.end()) { + static std::vector<pid_t> emptyTaskIdVec; + return emptyTaskIdVec; + } + return taskItr->second.linkedTasks; +} + +bool SessionTaskMap::isAnyAppSessionActive(std::chrono::steady_clock::time_point timePoint) const { + for (auto &sessionVal : mSessions) { + if (!sessionVal.second.val->isAppSession) { + continue; + } + if (!sessionVal.second.val->isActive) { + continue; + } + if (!sessionVal.second.val->votes->allTimedOut(timePoint)) { + return true; + } + } + return false; +} + +bool SessionTaskMap::remove(int64_t sessionId) { + auto sessItr = mSessions.find(sessionId); + if (sessItr == mSessions.end()) { + return false; + } + + // For each task id in linked tasks need to remove the corresponding + // task to session mapping in the task map + for (const auto taskId : sessItr->second.linkedTasks) { + // Used linked task ids to cleanup + auto taskItr = mTasks.find(taskId); + if (taskItr == mTasks.end()) { + // Inconsisent state + continue; + } + + // Now lookup session id in task's set + auto taskSessItr = + std::find(taskItr->second.begin(), taskItr->second.end(), sessItr->second.val); + if (taskSessItr == taskItr->second.end()) { + // Should not happen + continue; + } + + // Remove session id from task map + taskItr->second.erase(taskSessItr); + if (taskItr->second.empty()) { + mTasks.erase(taskItr); + } + } + + // Now we can safely remove session entirely since there are no more + // mappings in task to session id + mSessions.erase(sessItr); + return true; +} + +bool SessionTaskMap::removeDeadTaskSessionMap(int64_t sessionId, pid_t taskId) { + auto sessItr = mSessions.find(sessionId); + if (sessItr == mSessions.end()) { + return false; + } + + auto taskItr = mTasks.find(taskId); + if (taskItr == mTasks.end()) { + // Inconsisent state + return false; + } + + // Now lookup session id in task's set + auto taskSessItr = + std::find(taskItr->second.begin(), taskItr->second.end(), sessItr->second.val); + if (taskSessItr == taskItr->second.end()) { + // Should not happen + return false; + } + + // Remove session id from task map + taskItr->second.erase(taskSessItr); + if (taskItr->second.empty()) { + mTasks.erase(taskItr); + } + + return true; +} + +bool SessionTaskMap::replace(int64_t sessionId, const std::vector<pid_t> &taskIds, + std::vector<pid_t> *addedThreads, std::vector<pid_t> *removedThreads) { + auto itr = mSessions.find(sessionId); + if (itr == mSessions.end()) { + return false; + } + + // Make copies of val and threads + auto svTmp = itr->second.val; + const auto previousTaskIds = itr->second.linkedTasks; + + // Determine newly added threads + if (addedThreads) { + for (auto tid : taskIds) { + auto taskSessItr = mTasks.find(tid); + if (taskSessItr == mTasks.end()) { + addedThreads->push_back(tid); + } + } + } + + // Remove session from mappings + remove(sessionId); + // Add session value and task mappings + add(sessionId, *svTmp, taskIds); + + // Determine completely removed threads + if (removedThreads) { + for (auto tid : previousTaskIds) { + auto taskSessItr = mTasks.find(tid); + if (taskSessItr == mTasks.end()) { + removedThreads->push_back(tid); + } + } + } + + return true; +} + +size_t SessionTaskMap::sizeSessions() const { + return mSessions.size(); +} + +size_t SessionTaskMap::sizeTasks() const { + return mTasks.size(); +} + +const std::string &SessionTaskMap::idString(int64_t sessionId) const { + auto sessItr = mSessions.find(sessionId); + if (sessItr == mSessions.end()) { + static const std::string emptyString; + return emptyString; + } + return sessItr->second.val->idString; +} + +bool SessionTaskMap::isAppSession(int64_t sessionId) const { + auto sessItr = mSessions.find(sessionId); + if (sessItr == mSessions.end()) { + return false; + } + + return sessItr->second.val->isAppSession; +} + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/SessionTaskMap.h b/power-libperfmgr/aidl/SessionTaskMap.h new file mode 100644 index 00000000..35e7b85a --- /dev/null +++ b/power-libperfmgr/aidl/SessionTaskMap.h @@ -0,0 +1,131 @@ +/* + * Copyright 2023 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. + */ + +#pragma once + +#include <unordered_map> +#include <vector> + +#include "SessionValueEntry.h" + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +/** + * Map session id to a value and link to many task ids + * Maintain consistency between mappings + * e.g. + * Sessions[sid1] -> SessionValueEntry1, [tid1, tid2] + * Tasks[tid1] -> [sid1] + * Tasks[tid2] -> [sid1] + * ... + * Sessions[sid2] -> SessionValueEntry2, [tid2, tid3] + * Tasks[tid1] -> [sid1] + * Tasks[tid2] -> [sid1, sid2] + * Tasks[tid3] -> [sid2] + */ +class SessionTaskMap { + public: + // Add a session with associated tasks to mapping + bool add(int64_t sessionId, const SessionValueEntry &sv, const std::vector<pid_t> &taskIds); + + // Add a vote to a session + void addVote(int64_t sessionId, int voteId, int uclampMin, int uclampMax, + std::chrono::steady_clock::time_point startTime, + std::chrono::nanoseconds durationNs); + + // Find session id and run callback on session value, linked tasks + std::shared_ptr<SessionValueEntry> findSession(int64_t sessionId); + + void getTaskVoteRange(pid_t taskId, std::chrono::steady_clock::time_point timeNow, + int *uclampMin, int *uclampmax) const; + + // Find session ids given a task id if it exists + std::vector<int64_t> getSessionIds(pid_t taskId) const; + + // Get a vec of tasks associated with a session + std::vector<pid_t> &getTaskIds(int64_t sessionId); + + // Return true if any app session is active, false otherwise + bool isAnyAppSessionActive(std::chrono::steady_clock::time_point timePoint) const; + + // Remove a session based on session id + bool remove(int64_t sessionId); + + // Maintain value of session, remove old task mapping add new + bool replace(int64_t sessionId, const std::vector<pid_t> &taskIds, + std::vector<pid_t> *addedThreads, std::vector<pid_t> *removedThreads); + + size_t sizeSessions() const; + + size_t sizeTasks() const; + + // Given task id, for each linked-to session id call fn + template <typename FN> + void forEachSessionInTask(pid_t taskId, FN fn) const { + auto taskSessItr = mTasks.find(taskId); + if (taskSessItr == mTasks.end()) { + return; + } + for (const auto session : taskSessItr->second) { + auto sessionItr = mSessions.find(session->sessionId); + if (sessionItr == mSessions.end()) { + continue; + } + fn(sessionItr->first, *(sessionItr->second.val)); + } + } + + // Iterate over all entries in session map and run callback fn + // fn takes int64_t session id, session entry val, linked task ids + template <typename FN> + void forEachSessionValTasks(FN fn) const { + for (const auto &e : mSessions) { + fn(e.first, *(e.second.val), e.second.linkedTasks); + } + } + + // Returns string id of session + const std::string &idString(int64_t sessionId) const; + + // Returns true if session id is an app session id + bool isAppSession(int64_t sessionId) const; + + // Remove dead task-session map entry + bool removeDeadTaskSessionMap(int64_t sessionId, pid_t taskId); + + private: + // Internal struct to hold per-session data and linked tasks + struct ValEntry { + std::shared_ptr<SessionValueEntry> val; + std::vector<pid_t> linkedTasks; + }; + // Map session id to value + std::unordered_map<int64_t, ValEntry> mSessions; + // Map task id to set of session ids + std::unordered_map<pid_t, std::vector<std::shared_ptr<SessionValueEntry>>> mTasks; +}; + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/SessionValueEntry.cpp b/power-libperfmgr/aidl/SessionValueEntry.cpp new file mode 100644 index 00000000..b420ae8c --- /dev/null +++ b/power-libperfmgr/aidl/SessionValueEntry.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2023 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 "SessionValueEntry.h" + +#include <sstream> + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +std::ostream &SessionValueEntry::dump(std::ostream &os) const { + auto timeNow = std::chrono::steady_clock::now(); + os << "ID.Min.Act(" << idString; + if (votes) { + UclampRange uclampRange; + votes->getUclampRange(&uclampRange, timeNow); + os << ", " << uclampRange.uclampMin; + os << "-" << uclampRange.uclampMax; + } else { + os << ", votes nullptr"; + } + os << ", " << isActive; + return os; +} + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/SessionValueEntry.h b/power-libperfmgr/aidl/SessionValueEntry.h new file mode 100644 index 00000000..f43dba48 --- /dev/null +++ b/power-libperfmgr/aidl/SessionValueEntry.h @@ -0,0 +1,55 @@ +/* + * Copyright 2023 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. + */ + +#pragma once + +#include <ostream> + +#include "AdpfTypes.h" +#include "UClampVoter.h" + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +// Per-power-session values (equivalent to original PowerHintSession) +// Responsible for maintaining the state of the power session via attributes +// Primarily this means actual uclamp value and whether session is active +// (i.e. whether to include this power session uclmap when setting task uclamp) +struct SessionValueEntry { + int64_t sessionId{0}; + // Thread group id + int64_t tgid{0}; + uid_t uid{0}; + std::string idString; + bool isActive{true}; + bool isAppSession{false}; + std::chrono::steady_clock::time_point lastUpdatedTime; + std::shared_ptr<Votes> votes; + + // Write info about power session to ostream for logging and debugging + std::ostream &dump(std::ostream &os) const; +}; + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/UClampVoter.cpp b/power-libperfmgr/aidl/UClampVoter.cpp new file mode 100644 index 00000000..251f5410 --- /dev/null +++ b/power-libperfmgr/aidl/UClampVoter.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2023 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 "UClampVoter.h" + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +static void confine(UclampRange *uclampRange, const VoteRange &vr, + std::chrono::steady_clock::time_point t) { + if (!vr.isTimeInRange(t)) { + return; + } + uclampRange->uclampMin = std::max(uclampRange->uclampMin, vr.uclampMin()); + uclampRange->uclampMax = std::min(uclampRange->uclampMax, vr.uclampMax()); +} + +VoteRange VoteRange::makeMinRange(int uclampMin, std::chrono::steady_clock::time_point startTime, + std::chrono::nanoseconds durationNs) { + VoteRange v(true, uclampMin, kUclampMax, startTime, durationNs); + return v; +} + +std::ostream &operator<<(std::ostream &o, const VoteRange &vr) { + o << "[" << vr.uclampMin() << "," << vr.uclampMax() << "]"; + return o; +} + +Votes::Votes() {} + +void Votes::add(int voteId, const VoteRange &v) { + mVotes[voteId] = v; +} + +void Votes::updateDuration(int voteId, std::chrono::nanoseconds durationNs) { + auto voteItr = mVotes.find(voteId); + if (voteItr != mVotes.end()) { + voteItr->second.updateDuration(durationNs); + } +} + +void Votes::getUclampRange(UclampRange *uclampRange, + std::chrono::steady_clock::time_point t) const { + if (nullptr == uclampRange) { + return; + } + for (const auto &v : mVotes) { + confine(uclampRange, v.second, t); + } +} + +bool Votes::anyTimedOut(std::chrono::steady_clock::time_point t) const { + for (const auto &v : mVotes) { + if (!v.second.isTimeInRange(t)) { + return true; + } + } + return false; +} + +bool Votes::allTimedOut(std::chrono::steady_clock::time_point t) const { + for (const auto &v : mVotes) { + if (v.second.isTimeInRange(t)) { + return false; + } + } + return true; +} + +bool Votes::remove(int voteId) { + auto itr = mVotes.find(voteId); + if (itr == mVotes.end()) { + return false; + } + mVotes.erase(itr); + return true; +} + +bool Votes::setUseVote(int voteId, bool active) { + auto itr = mVotes.find(voteId); + if (itr == mVotes.end()) { + return false; + } + itr->second.setActive(active); + return true; +} + +size_t Votes::size() const { + return mVotes.size(); +} + +bool Votes::voteIsActive(int voteId) { + auto itr = mVotes.find(voteId); + if (itr == mVotes.end()) { + return false; + } + return itr->second.active(); +} + +std::chrono::steady_clock::time_point Votes::voteTimeout(int voteId) { + auto itr = mVotes.find(voteId); + if (itr == mVotes.end()) { + return std::chrono::steady_clock::time_point{}; + } + return itr->second.startTime() + itr->second.durationNs(); +} + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/UClampVoter.h b/power-libperfmgr/aidl/UClampVoter.h new file mode 100644 index 00000000..4b9df33a --- /dev/null +++ b/power-libperfmgr/aidl/UClampVoter.h @@ -0,0 +1,141 @@ +/* + * Copyright 2023 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. + */ + +#pragma once + +#include <chrono> +#include <memory> +#include <ostream> +#include <unordered_map> + +#include "AdpfTypes.h" + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +// -------------------------------------------------------- +// Hold a min and max for acceptable uclamp values +struct UclampRange { + int uclampMin{kUclampMin}; + int uclampMax{kUclampMax}; +}; + +// -------------------------------------------------------- +// Hold a min max range of acceptable votes, is active status, time duration +// info, and helper methods for consistent use +class VoteRange { + public: + VoteRange() {} + + VoteRange(bool active, int uclampMin, int uclampMax, + std::chrono::steady_clock::time_point startTime, std::chrono::nanoseconds durationNs) + : mActive(active), + mUclampRange({std::min(uclampMin, uclampMax), std::max(uclampMin, uclampMax)}), + mStartTime(startTime), + mDurationNs(durationNs) {} + + // Returns true if this vote range is active, false if it is not active + bool active() const { return mActive; } + + // Returns the utilization clamp minimum + int uclampMin() const { return mUclampRange.uclampMin; } + + // Returns the utilization clamp maximum + int uclampMax() const { return mUclampRange.uclampMax; } + + // Returns the start time of this vote range + std::chrono::steady_clock::time_point startTime() const { return mStartTime; } + + // Returns the duration in nanoseconds of the vote range + std::chrono::nanoseconds durationNs() const { return mDurationNs; } + + // Set the is active flag to bool param + void setActive(bool active) { mActive = active; } + + // Update the vote duration + void updateDuration(std::chrono::nanoseconds durationNs) { mDurationNs = durationNs; } + + // Return true if time point parameter in range of startTime to startTime+duration + inline bool isTimeInRange(std::chrono::steady_clock::time_point t) const { + return mActive && ((mStartTime <= t) && ((mStartTime + mDurationNs) >= t)); + } + + // Factory method to make a vote range + static VoteRange makeMinRange(int uclampMin, std::chrono::steady_clock::time_point startTime, + std::chrono::nanoseconds durationNs); + + private: + bool mActive{true}; + UclampRange mUclampRange; + std::chrono::steady_clock::time_point mStartTime{}; + std::chrono::nanoseconds mDurationNs{}; +}; + +// Helper for logging +std::ostream &operator<<(std::ostream &o, const VoteRange &vr); + +// -------------------------------------------------------- +// Thread safe collection of votes that can be used to get +// a clamped range +class Votes { + public: + Votes(); + + // Add a vote and associate with vote id, overwrites existing vote + void add(int voteId, const VoteRange &v); + + // Update the duration of a vote given a vote id + void updateDuration(int voteId, std::chrono::nanoseconds durationNs); + + // Given input UclampRange, and a time point now, increase the min and + // decrease max if this VoteRange is in range, return UclampRange with + // the largest min and the smallest max + void getUclampRange(UclampRange *uclampRange, std::chrono::steady_clock::time_point t) const; + + // Return true if any vote has timed out, otherwise return false + bool anyTimedOut(std::chrono::steady_clock::time_point t) const; + + // Return true if all votes have timed out, otherwise return false + bool allTimedOut(std::chrono::steady_clock::time_point t) const; + + // Remove vote based on vote vote id, return true if remove was successful, + // false if remove failed for example no vote with that id exists + bool remove(int voteId); + + // Turn on/off vote + bool setUseVote(int voteId, bool active); + + // Return number of votes + size_t size() const; + + bool voteIsActive(int voteId); + + std::chrono::steady_clock::time_point voteTimeout(int voteId); + + private: + std::unordered_map<int, VoteRange> mVotes; +}; + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc b/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc index 5dc9c9e9..b6e7b83e 100644 --- a/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc +++ b/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc @@ -7,6 +7,10 @@ service vendor.power-hal-aidl /vendor/bin/hw/android.hardware.power-service.pixe on late-fs start vendor.power-hal-aidl +# Unblock thermalHAL under off mode charge +on charger + start vendor.power-hal-aidl + # Restart powerHAL when framework died on property:init.svc.zygote=restarting && property:vendor.powerhal.state=* setprop vendor.powerhal.state "" diff --git a/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml b/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml index f5dd6b95..418fb83d 100644 --- a/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml +++ b/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml @@ -1,7 +1,7 @@ <manifest version="1.0" type="device"> <hal format="aidl"> <name>android.hardware.power</name> - <version>4</version> + <version>5</version> <fqname>IPower/default</fqname> </hal> </manifest> diff --git a/power-libperfmgr/aidl/service.cpp b/power-libperfmgr/aidl/service.cpp index c3e2dd1d..3d8d6a7c 100644 --- a/power-libperfmgr/aidl/service.cpp +++ b/power-libperfmgr/aidl/service.cpp @@ -33,13 +33,13 @@ using aidl::google::hardware::power::impl::pixel::DisplayLowPower; using aidl::google::hardware::power::impl::pixel::Power; using aidl::google::hardware::power::impl::pixel::PowerExt; -using aidl::google::hardware::power::impl::pixel::PowerHintMonitor; using aidl::google::hardware::power::impl::pixel::PowerSessionManager; using ::android::perfmgr::HintManager; constexpr std::string_view kPowerHalInitProp("vendor.powerhal.init"); int main() { + android::base::SetDefaultTag(LOG_TAG); // Parse config but do not start the looper std::shared_ptr<HintManager> hm = HintManager::GetInstance(); if (!hm) { @@ -69,10 +69,6 @@ int main() { CHECK(status == STATUS_OK); LOG(INFO) << "Pixel Power HAL AIDL Service with Extension is started."; - if (HintManager::GetInstance()->GetAdpfProfile()) { - PowerHintMonitor::getInstance()->start(); - } - std::thread initThread([&]() { ::android::base::WaitForProperty(kPowerHalInitProp.data(), "1"); HintManager::GetInstance()->Start(); diff --git a/power-libperfmgr/aidl/tests/BackgroundWorkerTest.cpp b/power-libperfmgr/aidl/tests/BackgroundWorkerTest.cpp new file mode 100644 index 00000000..14caea8b --- /dev/null +++ b/power-libperfmgr/aidl/tests/BackgroundWorkerTest.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 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 <gtest/gtest.h> + +#include "aidl/BackgroundWorker.h" + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +using std::literals::chrono_literals::operator""s; +using std::literals::chrono_literals::operator""ms; +using std::literals::chrono_literals::operator""ns; + +constexpr double kTIMING_TOLERANCE_MS = std::chrono::milliseconds(25).count(); + +// Use this work to package some work identifier val along with time t of when it was +// processed to validate that the waiting time is adhered to as closely as possible +struct work { + int64_t val{0}; + std::chrono::steady_clock::time_point t; +}; + +auto getDurationMs(std::chrono::steady_clock::time_point endTime, + std::chrono::steady_clock::time_point startTime) { + return std::chrono::duration<double, std::milli>(endTime - startTime); +} + +TEST(PriorityQueueWorkerPool, testSingle) { + const int pqId = 1; + std::condition_variable cv; + std::mutex m; + std::vector<work> vec; + vec.reserve(3); + + auto p = std::make_shared<PriorityQueueWorkerPool>(1, "adpf_"); + p->addCallback(pqId, [&](int64_t packageId) { + std::lock_guard<std::mutex> lock(m); + vec.push_back({packageId, std::chrono::steady_clock::now()}); + cv.notify_all(); + }); + + const auto tNow = std::chrono::steady_clock::now(); + p->schedule(pqId, 500, tNow + 500ms); + p->schedule(pqId, 100, tNow + 100ms); + p->schedule(pqId, 300, tNow + 300ms); + + std::unique_lock<std::mutex> lock(m); + EXPECT_EQ(0, vec.size()); + cv.wait_for(lock, 1500ms, [&]() { return vec.size() == 3; }); + + EXPECT_EQ(3, vec.size()); + EXPECT_EQ(100, vec[0].val); + EXPECT_NEAR(100, getDurationMs(vec[0].t, tNow).count(), kTIMING_TOLERANCE_MS); + EXPECT_EQ(300, vec[1].val); + EXPECT_NEAR(300, getDurationMs(vec[1].t, tNow).count(), kTIMING_TOLERANCE_MS); + EXPECT_EQ(500, vec[2].val); + EXPECT_NEAR(500, getDurationMs(vec[2].t, tNow).count(), kTIMING_TOLERANCE_MS); +} + +TEST(TemplatePriorityQueueWorker, testSingle) { + std::condition_variable cv; + std::mutex m; + std::vector<work> vec; + vec.reserve(3); + + auto p = std::make_shared<PriorityQueueWorkerPool>(1, "adpf_"); + TemplatePriorityQueueWorker<int> worker{ + [&](int i) { + std::lock_guard<std::mutex> lock(m); + vec.push_back({i, std::chrono::steady_clock::now()}); + cv.notify_all(); + }, + p}; + + // Would be nice to have a pause option for testing + const auto tNow = std::chrono::steady_clock::now(); + worker.schedule(303, tNow + 500ms); + worker.schedule(101, tNow + 100ms); + worker.schedule(202, tNow + 300ms); + + std::unique_lock<std::mutex> lock(m); + EXPECT_EQ(0, vec.size()); + cv.wait_for(lock, 1500ms, [&]() { return vec.size() == 3; }); + + EXPECT_EQ(3, vec.size()); + EXPECT_EQ(101, vec[0].val); + EXPECT_NEAR(100, getDurationMs(vec[0].t, tNow).count(), kTIMING_TOLERANCE_MS); + EXPECT_EQ(202, vec[1].val); + EXPECT_NEAR(300, getDurationMs(vec[1].t, tNow).count(), kTIMING_TOLERANCE_MS); + EXPECT_EQ(303, vec[2].val); + EXPECT_NEAR(500, getDurationMs(vec[2].t, tNow).count(), kTIMING_TOLERANCE_MS); +} + +TEST(TemplatePriorityQueueWorker, testDouble) { + std::condition_variable cv; + std::mutex m; + std::vector<work> vec; + vec.reserve(6); + + auto p = std::make_shared<PriorityQueueWorkerPool>(1, "adpf_"); + TemplatePriorityQueueWorker<int> worker1{ + [&](int i) { + std::lock_guard<std::mutex> lock(m); + vec.push_back({i, std::chrono::steady_clock::now()}); + cv.notify_all(); + }, + p}; + + TemplatePriorityQueueWorker<std::string> worker2{ + [&](const std::string &s) { + std::lock_guard<std::mutex> lock(m); + vec.push_back({atoi(s.c_str()), std::chrono::steady_clock::now()}); + cv.notify_all(); + }, + p}; + + // Would be nice to have a pause option for testing + const auto tNow = std::chrono::steady_clock::now(); + worker1.schedule(5, tNow + 300ms); + worker1.schedule(1, tNow + 100ms); + worker1.schedule(3, tNow + 200ms); + worker2.schedule("2", tNow + 150ms); + worker2.schedule("4", tNow + 250ms); + worker2.schedule("6", tNow + 350ms); + + std::unique_lock<std::mutex> lock(m); + EXPECT_EQ(0, vec.size()); + cv.wait_for(lock, 1500ms, [&]() { return vec.size() == 6; }); + + EXPECT_EQ(6, vec.size()); + EXPECT_EQ(1, vec[0].val); + EXPECT_NEAR(100, getDurationMs(vec[0].t, tNow).count(), kTIMING_TOLERANCE_MS); + EXPECT_EQ(2, vec[1].val); + EXPECT_NEAR(150, getDurationMs(vec[1].t, tNow).count(), kTIMING_TOLERANCE_MS); + EXPECT_EQ(3, vec[2].val); + EXPECT_NEAR(200, getDurationMs(vec[2].t, tNow).count(), kTIMING_TOLERANCE_MS); + EXPECT_EQ(4, vec[3].val); + EXPECT_NEAR(250, getDurationMs(vec[3].t, tNow).count(), kTIMING_TOLERANCE_MS); + EXPECT_EQ(5, vec[4].val); + EXPECT_NEAR(300, getDurationMs(vec[4].t, tNow).count(), kTIMING_TOLERANCE_MS); + EXPECT_EQ(6, vec[5].val); + EXPECT_NEAR(350, getDurationMs(vec[5].t, tNow).count(), kTIMING_TOLERANCE_MS); +} + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/tests/PowerHintSessionTest.cpp b/power-libperfmgr/aidl/tests/PowerHintSessionTest.cpp new file mode 100644 index 00000000..110889f6 --- /dev/null +++ b/power-libperfmgr/aidl/tests/PowerHintSessionTest.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2023 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 <gtest/gtest.h> +#include <sys/syscall.h> + +#include <chrono> +#include <mutex> +#include <thread> +#include <unordered_map> +#include <vector> + +// define private as public to expose the private members for test. +#define private public +#include "aidl/PowerHintSession.h" +#include "aidl/PowerSessionManager.h" + +#define gettid() syscall(SYS_gettid) + +using std::literals::chrono_literals::operator""ms; +using std::literals::chrono_literals::operator""ns; +using std::literals::chrono_literals::operator""s; + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +class PowerHintSessionTest : public ::testing::Test { + public: + void SetUp() { + // create a list of threads + for (int i = 0; i < numOfThreads; i++) { + threadIsAlive.emplace_back(true); + threadList.emplace_back(std::thread([this, threadInd = i]() { + ALOGI("Test thread %d is running.", (int32_t)gettid()); + { + std::lock_guard<std::mutex> lock(m); + threadIds[threadInd] = gettid(); + } + while (threadIsAlive[threadInd]) { + std::this_thread::sleep_for(50ms); + } + ALOGI("Test thread %d is closed.", (int32_t)gettid()); + })); + } + std::this_thread::sleep_for(50ms); + + // create two hint sessions + for (int i = 0; i < numOfThreads; i++) { + if (i <= numOfThreads / 2) { + session1Threads.emplace_back(threadIds[i]); + } + + if (i >= numOfThreads / 2) { + session2Threads.emplace_back(threadIds[i]); + } + } + + sess1 = ndk::SharedRefBase::make<PowerHintSession>(1, 1, session1Threads, 1000000); + sess2 = ndk::SharedRefBase::make<PowerHintSession>(2, 2, session2Threads, 1000000); + } + + void TearDown() { + for (int i = 0; i < numOfThreads; i++) { + if (threadIsAlive[i]) { + threadIsAlive[i] = false; + threadList[i].join(); + } + } + threadList.clear(); + threadIds.clear(); + threadIsAlive.clear(); + session1Threads.clear(); + session2Threads.clear(); + } + + protected: + static const int numOfThreads = 3; + std::vector<std::thread> threadList; + std::unordered_map<int, int32_t> threadIds; + std::vector<bool> threadIsAlive; + std::mutex m; + std::vector<int32_t> session1Threads; + std::vector<int32_t> session2Threads; + std::shared_ptr<PowerHintSession> sess1; + std::shared_ptr<PowerHintSession> sess2; + + // close the i-th thread in thread list. + void closeThread(int i) { + if (i < 0 || i >= numOfThreads) + return; + if (threadIsAlive[i]) { + threadIsAlive[i] = false; + threadList[i].join(); + } + } +}; + +TEST_F(PowerHintSessionTest, removeDeadThread) { + ALOGI("Running dead thread test for hint sessions."); + auto sessManager = sess1->mPSManager; + ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size()); + + // The sessions' thread list doesn't change after thread died until the uclamp + // min update is triggered. + int deadThreadInd = numOfThreads / 2; + auto deadThreadID = threadIds[deadThreadInd]; + closeThread(deadThreadInd); + ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks, + session1Threads); + ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess2->mSessionId].linkedTasks, + session2Threads); + ASSERT_EQ(sessManager->mSessionTaskMap.mTasks[deadThreadID].size(), 2); + + // Trigger an update of uclamp min. + auto tNow = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + WorkDuration wDur(tNow, 1100000); + sess1->reportActualWorkDuration(std::vector<WorkDuration>{wDur}); + ASSERT_EQ(sessManager->mSessionTaskMap.mTasks[deadThreadID].size(), 1); + sess2->reportActualWorkDuration(std::vector<WorkDuration>{wDur}); + ASSERT_EQ(sessManager->mSessionTaskMap.mTasks.count(deadThreadID), 0); + std::erase(session1Threads, deadThreadID); + std::erase(session2Threads, deadThreadID); + ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks, + session1Threads); + ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess2->mSessionId].linkedTasks, + session2Threads); + + // Close all the threads in session 1. + for (int i = 0; i <= numOfThreads / 2; i++) { + closeThread(i); + } + tNow = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + sess1->reportActualWorkDuration(std::vector<WorkDuration>{wDur}); + ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size()); // Session still alive + ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks.size(), 0); +} + +TEST_F(PowerHintSessionTest, setThreads) { + auto sessManager = sess1->mPSManager; + ASSERT_EQ(2, sessManager->mSessionTaskMap.mSessions.size()); + + ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks, + session1Threads); + + std::vector<int32_t> newSess1Threads; + for (auto tid : threadIds) { + newSess1Threads.emplace_back(tid.second); + } + sess1->setThreads(newSess1Threads); + ASSERT_EQ(sessManager->mSessionTaskMap.mSessions[sess1->mSessionId].linkedTasks, + newSess1Threads); + + sess1->close(); + sess2->close(); +} + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/tests/SessionTaskMapTest.cpp b/power-libperfmgr/aidl/tests/SessionTaskMapTest.cpp new file mode 100644 index 00000000..7f7dab3f --- /dev/null +++ b/power-libperfmgr/aidl/tests/SessionTaskMapTest.cpp @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2023 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 <gtest/gtest.h> + +#include "aidl/SessionTaskMap.h" + +using std::literals::chrono_literals::operator""ms; +using std::literals::chrono_literals::operator""ns; + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +SessionValueEntry makeSession(int tg) { + SessionValueEntry sv; + sv.tgid = tg; + sv.uid = tg + 1; + sv.idString = "Sess" + std::to_string(tg); + sv.isActive = true; + sv.isAppSession = false; + sv.lastUpdatedTime = std::chrono::steady_clock::now(); + sv.votes = std::make_shared<Votes>(); + return sv; +} + +// Get all sessions associated with taskId +std::vector<int64_t> getSessions(int taskId, const SessionTaskMap &m) { + std::vector<int64_t> sessionIds; + m.forEachSessionInTask( + taskId, [&](int sessionId, const auto & /*sve*/) { sessionIds.push_back(sessionId); }); + std::sort(sessionIds.begin(), sessionIds.end()); + return sessionIds; +} + +// Get all tasks associated with sessionId +std::vector<int> getTasks(int64_t sessionId, const SessionTaskMap &m) { + std::vector<int> tasks; + m.forEachSessionValTasks([&](int64_t sessId, const auto & /*sve*/, const auto &linkedTasks) { + if (sessId != sessionId) + return; + tasks.insert(std::end(tasks), std::begin(linkedTasks), std::end(linkedTasks)); + }); + std::sort(tasks.begin(), tasks.end()); + return tasks; +} + +// Tests ... +TEST(SessionTaskMapTest, add) { + SessionTaskMap m; + EXPECT_TRUE(m.add(1, makeSession(1000), {10, 20, 30})); + EXPECT_TRUE(m.add(2, makeSession(2000), {40, 50})); + EXPECT_TRUE(m.add(3, makeSession(2000), {60})); + EXPECT_FALSE(m.add(3, makeSession(2000), {70})); +} + +TEST(SessionTaskMapTest, threeWayMappingSessions) { + SessionTaskMap m; + m.add(1, makeSession(1000), {10, 20, 30}); + m.add(2, makeSession(2000), {40, 50, 60}); + m.add(3, makeSession(3000), {50}); + + // Check three tasks map properly to sessions + EXPECT_EQ(std::vector<int64_t>({1}), getSessions(10, m)); + EXPECT_EQ(std::vector<int64_t>({1}), getSessions(20, m)); + EXPECT_EQ(std::vector<int64_t>({1}), getSessions(30, m)); + EXPECT_EQ(std::vector<int64_t>({2}), getSessions(40, m)); + EXPECT_EQ(std::vector<int64_t>({2, 3}), getSessions(50, m)); + EXPECT_EQ(std::vector<int64_t>({2}), getSessions(60, m)); +} + +TEST(SessionTaskMapTest, threeWayMappingTasks) { + SessionTaskMap m; + m.add(1, makeSession(1000), {10, 20, 30}); + m.add(2, makeSession(2000), {40, 50, 60}); + m.add(3, makeSession(3000), {50}); + + // Check three sessions map properly to tasks + EXPECT_EQ(std::vector<int>({10, 20, 30}), getTasks(1, m)); + EXPECT_EQ(std::vector<int>({40, 50, 60}), getTasks(2, m)); + EXPECT_EQ(std::vector<int>({50}), getTasks(3, m)); +} + +TEST(SessionTaskMapTest, removeNonExisting) { + SessionTaskMap m; + EXPECT_FALSE(m.remove(1)); +} + +TEST(SessionTaskMapTest, removeMappingSessions) { + SessionTaskMap m; + m.add(1, makeSession(1000), {10, 20, 30}); + m.add(2, makeSession(2000), {40, 50, 60}); + m.add(3, makeSession(3000), {50}); + + // remove + EXPECT_TRUE(m.remove(2)); + + // Check that remaining tasks map correctly to sessions + EXPECT_EQ(std::vector<int64_t>({1}), getSessions(10, m)); + EXPECT_EQ(std::vector<int64_t>({1}), getSessions(20, m)); + EXPECT_EQ(std::vector<int64_t>({1}), getSessions(30, m)); + EXPECT_EQ(std::vector<int64_t>({}), getSessions(40, m)); + EXPECT_EQ(std::vector<int64_t>({3}), getSessions(50, m)); +} + +TEST(SessionTaskMapTest, removeMappingTasks) { + SessionTaskMap m; + EXPECT_FALSE(m.remove(1)); + + m.add(1, makeSession(1000), {10, 20, 30}); + m.add(2, makeSession(2000), {40, 50, 60}); + m.add(3, makeSession(3000), {50}); + + // remove + EXPECT_TRUE(m.remove(2)); + EXPECT_FALSE(m.remove(2)); + + // Check that remaining tasks map correctly to sessions + EXPECT_EQ(std::vector<int>({10, 20, 30}), getTasks(1, m)); + EXPECT_EQ(std::vector<int>({}), getTasks(2, m)); + EXPECT_EQ(std::vector<int>({50}), getTasks(3, m)); +} + +TEST(SessionTaskMapTest, findEmpty) { + SessionTaskMap m; + EXPECT_EQ(nullptr, m.findSession(1)); +} + +TEST(SessionTaskMapTest, findSessionExists) { + SessionTaskMap m; + EXPECT_TRUE(m.add(1, makeSession(1000), {})); + EXPECT_NE(nullptr, m.findSession(1)); +} + +TEST(SessionTaskMapTest, findSessionEmptyExistsEmpty) { + SessionTaskMap m; + EXPECT_EQ(nullptr, m.findSession(1)); + EXPECT_TRUE(m.add(1, makeSession(1000), {})); + EXPECT_NE(nullptr, m.findSession(1)); + EXPECT_TRUE(m.remove(1)); + EXPECT_EQ(nullptr, m.findSession(1)); +} + +TEST(SessionTaskMapTest, sizeTasks) { + SessionTaskMap m; + EXPECT_EQ(0, m.sizeTasks()); + EXPECT_TRUE(m.add(1, makeSession(1000), {10, 20, 30})); + EXPECT_TRUE(m.add(2, makeSession(2000), {40, 50, 60})); + EXPECT_EQ(6, m.sizeTasks()); +} + +TEST(SessionTaskMapTest, sizeSessions) { + SessionTaskMap m; + EXPECT_EQ(0, m.sizeSessions()); + EXPECT_TRUE(m.add(1, makeSession(1000), {10, 20, 30})); + EXPECT_TRUE(m.add(2, makeSession(2000), {40, 50, 60})); + EXPECT_EQ(2, m.sizeSessions()); +} + +TEST(SessionTaskMapTest, replace) { + SessionTaskMap m; + + // Add three sessions where sessions 2 and 3 have shared threads + EXPECT_TRUE(m.add(1, makeSession(1000), {10, 20, 30})); + EXPECT_TRUE(m.add(2, makeSession(2000), {20})); + + std::vector<pid_t> addedThreads; + std::vector<pid_t> removedThreads; + + m.replace(1, {10, 40}, &addedThreads, &removedThreads); + EXPECT_EQ(1, addedThreads.size()); + EXPECT_EQ(40, addedThreads[0]); + + EXPECT_EQ(1, removedThreads.size()); + EXPECT_EQ(30, removedThreads[0]); +} + +TEST(SessionTaskMapTest, remove) { + SessionTaskMap m; + auto tNow = std::chrono::steady_clock::now(); + const int64_t sessionId = 1; + SessionValueEntry sve; + sve.isAppSession = true; + sve.votes = std::make_shared<Votes>(); + sve.votes->add(sessionId, VoteRange::makeMinRange(123, tNow, 400ms)); + m.add(sessionId, sve, {10, 20, 30}); + EXPECT_TRUE(m.isAnyAppSessionActive(tNow)); + EXPECT_TRUE(m.remove(sessionId)); + EXPECT_FALSE(m.isAnyAppSessionActive(tNow)); +} + +TEST(SessionTaskMapTest, isAnyAppActive) { + SessionTaskMap m; + auto tNow = std::chrono::steady_clock::now(); + + EXPECT_FALSE(m.isAnyAppSessionActive(tNow)); + + const int sessionId = 1000; + SessionValueEntry sv; + sv.isActive = true; + sv.isAppSession = true; + sv.lastUpdatedTime = tNow; + sv.votes = std::make_shared<Votes>(); + sv.votes->add(1, VoteRange::makeMinRange(123, tNow, 400ms)); + + EXPECT_TRUE(m.add(sessionId, sv, {10, 20, 30})); + EXPECT_TRUE(m.isAnyAppSessionActive(tNow)); + EXPECT_FALSE(m.isAnyAppSessionActive(tNow + 500ms)); +} + +int getVoteMin(const SessionTaskMap &m, int64_t taskId, std::chrono::steady_clock::time_point t) { + int uclampMin; + int uclampMax; + m.getTaskVoteRange(taskId, t, &uclampMin, &uclampMax); + return uclampMin; +} + +TEST(SessionTaskMapTest, votesEdgeCaseOverlap) { + SessionTaskMap m; + + // Sess 1: 10 + EXPECT_TRUE(m.add(1, makeSession(1000), {10})); + const auto t0 = std::chrono::steady_clock::now(); + const int voteMax = 1000; + + // Session Vote UClamp [Time start----------------Time End] Delta + // 1 1 111 [20----60] 40 + // 1 2 122 [60-85] 25 + // 1 3 133 [60--90] 30 + m.addVote(1, 1, 111, voteMax, t0 + 20ns, 40ns); + m.addVote(1, 2, 122, voteMax, t0 + 60ns, 25ns); + m.addVote(1, 3, 133, voteMax, t0 + 60ns, 30ns); + + // Before any votes active + EXPECT_EQ(0, getVoteMin(m, 10, t0 + 0ns)); + // First vote active + EXPECT_EQ(111, getVoteMin(m, 10, t0 + 20ns)); + // In middle of first vote + EXPECT_EQ(111, getVoteMin(m, 10, t0 + 35ns)); + // First, second, and third votes active + EXPECT_EQ(133, getVoteMin(m, 10, t0 + 60ns)); + // Second and third votes are being used + EXPECT_EQ(133, getVoteMin(m, 10, t0 + 61ns)); + // Third vote is being used + EXPECT_EQ(133, getVoteMin(m, 10, t0 + 86ns)); + // No votes active + EXPECT_EQ(0, getVoteMin(m, 10, t0 + 91ns)); +} + +TEST(SessionTaskMapTest, votesEdgeCaseNoOverlap) { + SessionTaskMap m; + // Sess 2: 30 + EXPECT_TRUE(m.add(2, makeSession(2000), {20})); + const auto t0 = std::chrono::steady_clock::now(); + const int voteMax = 1000; + + // Session Vote UClamp [Time start----------------Time End] Delta + // 2 1 211 [30-55] 25 + // 2 2 222 [100-135] 35 + // 2 3 233 [140-180] 40 + m.addVote(2, 1, 211, voteMax, t0 + 30ns, 25ns); + m.addVote(2, 2, 222, voteMax, t0 + 100ns, 35ns); + m.addVote(2, 3, 233, voteMax, t0 + 140ns, 40ns); + + // No votes active yet + EXPECT_EQ(0, getVoteMin(m, 20, t0 + 0ns)); + // First vote active + EXPECT_EQ(211, getVoteMin(m, 20, t0 + 30ns)); + // Second vote active + EXPECT_EQ(222, getVoteMin(m, 20, t0 + 100ns)); + // Third vote active + EXPECT_EQ(233, getVoteMin(m, 20, t0 + 140ns)); + // No votes active + EXPECT_EQ(0, getVoteMin(m, 20, t0 + 181ns)); +} + +TEST(SessionTaskMapTest, TwoSessionsOneInactive) { + const auto tNow = std::chrono::steady_clock::now(); + SessionTaskMap m; + + { + SessionValueEntry sv; + sv.isActive = true; + sv.isAppSession = true; + sv.lastUpdatedTime = tNow; + sv.votes = std::make_shared<Votes>(); + sv.votes->add(11, VoteRange::makeMinRange(111, tNow, 400ms)); + EXPECT_TRUE(m.add(1001, sv, {10, 20, 30})); + } + + { + SessionValueEntry sv; + sv.isActive = true; + sv.isAppSession = true; + sv.lastUpdatedTime = tNow; + sv.votes = std::make_shared<Votes>(); + sv.votes->add(22, VoteRange::makeMinRange(222, tNow, 400ms)); + EXPECT_TRUE(m.add(2001, sv, {10, 20, 30})); + } + + UclampRange uclampRange; + m.getTaskVoteRange(10, tNow + 10ns, &uclampRange.uclampMin, &uclampRange.uclampMax); + EXPECT_EQ(222, uclampRange.uclampMin); + + auto sessItr = m.findSession(2001); + EXPECT_NE(nullptr, sessItr); + sessItr->isActive = false; + + uclampRange.uclampMin = 0; + uclampRange.uclampMax = 1024; + m.getTaskVoteRange(10, tNow + 10ns, &uclampRange.uclampMin, &uclampRange.uclampMax); + EXPECT_EQ(111, uclampRange.uclampMin); +} + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/aidl/tests/UClampVoterTest.cpp b/power-libperfmgr/aidl/tests/UClampVoterTest.cpp new file mode 100644 index 00000000..53418a4e --- /dev/null +++ b/power-libperfmgr/aidl/tests/UClampVoterTest.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2023 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 <gtest/gtest.h> + +#include "aidl/UClampVoter.h" + +namespace aidl { +namespace google { +namespace hardware { +namespace power { +namespace impl { +namespace pixel { + +using std::literals::chrono_literals::operator""s; +using std::literals::chrono_literals::operator""ms; +using std::literals::chrono_literals::operator""ns; + +TEST(VoteRange, active) { + auto tNow = std::chrono::steady_clock::now(); + VoteRange vr(true, 101, 202, tNow, 200ms); + EXPECT_TRUE(vr.active()); +} + +TEST(VoteRange, inActive) { + auto tNow = std::chrono::steady_clock::now(); + VoteRange vr(false, 101, 202, tNow, 200ms); + EXPECT_FALSE(vr.active()); +} + +TEST(VoteRange, makeMinRange) { + auto tNow = std::chrono::steady_clock::now(); + auto vr = VoteRange::makeMinRange(123, tNow, 210ms); + EXPECT_EQ(123, vr.uclampMin()); + EXPECT_EQ(tNow, vr.startTime()); + EXPECT_EQ(210ms, vr.durationNs()); +} + +TEST(VoteRange, isTimeInRange) { + const auto tNow = std::chrono::steady_clock::now(); + auto voteRange = VoteRange::makeMinRange(234, tNow, 250ms); + EXPECT_EQ(234, voteRange.uclampMin()); + EXPECT_FALSE(voteRange.isTimeInRange(tNow - 1ns)); + EXPECT_TRUE(voteRange.isTimeInRange(tNow)); + EXPECT_TRUE(voteRange.isTimeInRange(tNow + 1ns)); + EXPECT_TRUE(voteRange.isTimeInRange(tNow + 250ms)); + EXPECT_FALSE(voteRange.isTimeInRange(tNow + 250ms + 1ns)); +} + +TEST(VoteRange, isTimeInRangeInActive) { + const auto tNow = std::chrono::steady_clock::now(); + auto voteRange = VoteRange::makeMinRange(345, tNow, 250ms); + voteRange.setActive(false); + EXPECT_FALSE(voteRange.active()); + // Still reports 345 as the min even if inactive + EXPECT_EQ(345, voteRange.uclampMin()); + EXPECT_FALSE(voteRange.isTimeInRange(tNow)); + EXPECT_FALSE(voteRange.isTimeInRange(tNow + 1ns)); + EXPECT_FALSE(voteRange.isTimeInRange(tNow + 250ms)); + EXPECT_FALSE(voteRange.isTimeInRange(tNow + 250ms + 1ns)); +} + +TEST(VoteRange, getUclampRange) { + const auto tNow = std::chrono::steady_clock::now(); + const auto tNext = tNow + 1s; + const auto tEnd1 = tNow + 4000000001ns; + const auto tPrev = tNow - 1s; + + const auto voteFirst = VoteRange::makeMinRange(11, tNow, 4000000000ns); + EXPECT_FALSE(voteFirst.isTimeInRange(tPrev)); + EXPECT_TRUE(voteFirst.isTimeInRange(tNext)); + EXPECT_FALSE(voteFirst.isTimeInRange(tEnd1)); + + Votes votes; + votes.add(1, voteFirst); + UclampRange ur; + + votes.getUclampRange(&ur, tNext); + EXPECT_EQ(11, ur.uclampMin); + EXPECT_EQ(kUclampMax, ur.uclampMax); +} + +TEST(UclampVoter, simple) { + const auto tNow = std::chrono::steady_clock::now(); + + auto votes = std::make_shared<Votes>(); + EXPECT_EQ(0, votes->size()); + + votes->add(1, VoteRange::makeMinRange(11, tNow, 4s)); + EXPECT_EQ(1, votes->size()); + + votes->add(2, VoteRange::makeMinRange(22, tNow, 1s)); + EXPECT_EQ(2, votes->size()); + + UclampRange ur1; + votes->getUclampRange(&ur1, tNow); + EXPECT_EQ(22, ur1.uclampMin); + EXPECT_EQ(kUclampMax, ur1.uclampMax); + + UclampRange ur2; + votes->getUclampRange(&ur2, tNow + 2s); + EXPECT_EQ(11, ur2.uclampMin); + EXPECT_EQ(kUclampMax, ur2.uclampMax); + + UclampRange ur3; + votes->getUclampRange(&ur3, tNow + 5s); + EXPECT_EQ(0, ur3.uclampMin); + EXPECT_EQ(1024, ur3.uclampMax); + + EXPECT_FALSE(votes->allTimedOut(tNow + 200ns)); + EXPECT_TRUE(votes->allTimedOut(tNow + 5s)); + + EXPECT_TRUE(votes->setUseVote(2, false)); + EXPECT_TRUE(votes->anyTimedOut(tNow + 5s)); + EXPECT_TRUE(votes->setUseVote(2, true)); + + EXPECT_FALSE(votes->anyTimedOut(tNow + 200ns)); +} + +TEST(UclampVoter, overwrite) { + const auto tNow = std::chrono::steady_clock::now(); + + auto votes = std::make_shared<Votes>(); + EXPECT_EQ(0, votes->size()); + + votes->add(1, VoteRange::makeMinRange(11, tNow, 4s)); + EXPECT_EQ(1, votes->size()); + + votes->add(2, VoteRange::makeMinRange(22, tNow, 2s)); + EXPECT_EQ(2, votes->size()); + + UclampRange ucr1; + votes->getUclampRange(&ucr1, tNow + 1s); + EXPECT_EQ(22, ucr1.uclampMin); + + votes->add(1, VoteRange::makeMinRange(32, tNow, 5s)); + UclampRange ucr2; + votes->getUclampRange(&ucr2, tNow + 1s); + EXPECT_EQ(32, ucr2.uclampMin); +} + +TEST(UclampVoter, updateDuration) { + const auto tNow = std::chrono::steady_clock::now(); + + auto votes = std::make_shared<Votes>(); + EXPECT_EQ(0, votes->size()); + + votes->add(1, VoteRange::makeMinRange(11, tNow, 4s)); + votes->add(2, VoteRange::makeMinRange(22, tNow, 2s)); + EXPECT_EQ(2, votes->size()); + + EXPECT_TRUE(votes->allTimedOut(tNow + 7s)); + votes->updateDuration(1, 8s); + EXPECT_FALSE(votes->allTimedOut(tNow + 7s)); + votes->updateDuration(5, 10s); + EXPECT_TRUE(votes->allTimedOut(tNow + 9s)); +} + +TEST(UclampVoter, loadVoteTest) { + const int defaultVoteId = 1; + const int loadVoteId = 2; + const int uclampMinDefault = 50; + const int uclampMinInit = 162; + const int uclampMinHigh = 450; + const auto tNow = std::chrono::steady_clock::now(); + UclampRange ucr; + auto votes = std::make_shared<Votes>(); + + // Default: min = 50 (original) + votes->add(defaultVoteId, VoteRange::makeMinRange(uclampMinDefault, tNow, 400ms)); + votes->getUclampRange(&ucr, tNow + 100ms); + EXPECT_EQ(uclampMinDefault, ucr.uclampMin); + + // Default: min = UclampMinInit + votes->add(defaultVoteId, VoteRange::makeMinRange(uclampMinInit, tNow, 400ns)); + // Load: min = uclampMinHigh + votes->add(loadVoteId, VoteRange::makeMinRange(uclampMinHigh, tNow, 250ns)); + + // Check that load is enabled + ucr.uclampMin = 0; + votes->getUclampRange(&ucr, tNow + 100ns); + EXPECT_EQ(uclampMinHigh, ucr.uclampMin); + + // Timeout or restore after 1st frame + // Expect to get 162. + ucr.uclampMin = 0; + votes->getUclampRange(&ucr, tNow + 350ns); + EXPECT_EQ(uclampMinInit, ucr.uclampMin); +} + +} // namespace pixel +} // namespace impl +} // namespace power +} // namespace hardware +} // namespace google +} // namespace aidl diff --git a/power-libperfmgr/disp-power/InteractionHandler.cpp b/power-libperfmgr/disp-power/InteractionHandler.cpp index d2357a6a..9cb7f1ea 100644 --- a/power-libperfmgr/disp-power/InteractionHandler.cpp +++ b/power-libperfmgr/disp-power/InteractionHandler.cpp @@ -48,8 +48,9 @@ namespace { static const bool kDisplayIdleSupport = ::android::base::GetBoolProperty("vendor.powerhal.disp.idle_support", true); -static const std::array<const char *, 2> kDispIdlePath = {"/sys/class/drm/card0/device/idle_state", - "/sys/class/graphics/fb0/idle_state"}; +static const std::array<const char *, 3> kDispIdlePath = {"/sys/class/drm/card0/device/idle_state", + "/sys/class/graphics/fb0/idle_state", + "/sys/class/graphics/fb0/device/idle_state"}; static const uint32_t kWaitMs = ::android::base::GetUintProperty("vendor.powerhal.disp.idle_wait", /*default*/ 100U); static const uint32_t kMinDurationMs = diff --git a/power-libperfmgr/libperfmgr/AdpfConfig.cc b/power-libperfmgr/libperfmgr/AdpfConfig.cc index 36e52091..c017965e 100644 --- a/power-libperfmgr/libperfmgr/AdpfConfig.cc +++ b/power-libperfmgr/libperfmgr/AdpfConfig.cc @@ -56,8 +56,6 @@ void AdpfConfig::dumpToFd(int fd) { dump_buf << "UclampMin_High: " << mUclampMinHigh << "\n"; dump_buf << "UclampMin_Low: " << mUclampMinLow << "\n"; dump_buf << "ReportingRateLimitNs: " << mReportingRateLimitNs << "\n"; - dump_buf << "EarlyBoost_On: " << mEarlyBoostOn << "\n"; - dump_buf << "EarlyBoost_TimeFactor: " << mEarlyBoostTimeFactor << "\n"; dump_buf << "TargetTimeFactor: " << mTargetTimeFactor << "\n"; dump_buf << "StaleTimeFactor: " << mStaleTimeFactor << "\n"; if (!android::base::WriteStringToFd(dump_buf.str(), fd)) { diff --git a/power-libperfmgr/libperfmgr/HintManager.cc b/power-libperfmgr/libperfmgr/HintManager.cc index dd313ef5..bc6b10df 100644 --- a/power-libperfmgr/libperfmgr/HintManager.cc +++ b/power-libperfmgr/libperfmgr/HintManager.cc @@ -58,7 +58,7 @@ bool HintManager::ValidateHint(const std::string& hint_type) const { bool HintManager::IsHintSupported(const std::string& hint_type) const { if (actions_.find(hint_type) == actions_.end()) { - LOG(INFO) << "Hint type not present in actions: " << hint_type; + LOG(DEBUG) << "Hint type not present in actions: " << hint_type; return false; } return true; @@ -644,6 +644,16 @@ std::unordered_map<std::string, Hint> HintManager::ParseActions( return actions_parsed; } +#define ADPF_PARSE(VARIABLE, ENTRY, TYPE) \ + static_assert(std::is_same<decltype(adpfs[i][ENTRY].as##TYPE()), decltype(VARIABLE)>::value, \ + "Parser type mismatch"); \ + if (adpfs[i][ENTRY].empty() || !adpfs[i][ENTRY].is##TYPE()) { \ + LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][" ENTRY "]'s Values"; \ + adpfs_parsed.clear(); \ + return adpfs_parsed; \ + } \ + VARIABLE = adpfs[i][ENTRY].as##TYPE() + std::vector<std::shared_ptr<AdpfConfig>> HintManager::ParseAdpfConfigs( const std::string &json_doc) { // function starts @@ -665,8 +675,6 @@ std::vector<std::shared_ptr<AdpfConfig>> HintManager::ParseAdpfConfigs( uint64_t samplingWindowD; double staleTimeFactor; uint64_t reportingRate; - bool earlyBoostOn; - double earlyBoostTimeFactor; double targetTimeFactor; std::vector<std::shared_ptr<AdpfConfig>> adpfs_parsed; std::set<std::string> name_parsed; @@ -695,162 +703,31 @@ std::vector<std::shared_ptr<AdpfConfig>> HintManager::ParseAdpfConfigs( return adpfs_parsed; } - if (adpfs[i]["PID_On"].empty() || !adpfs[i]["PID_On"].isBool()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_On]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - pidOn = adpfs[i]["PID_On"].asBool(); - - if (adpfs[i]["PID_Po"].empty() || !adpfs[i]["PID_Po"].isDouble()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_Po]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - pidPOver = adpfs[i]["PID_Po"].asDouble(); - - if (adpfs[i]["PID_Pu"].empty() || !adpfs[i]["PID_Pu"].isDouble()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_Pu]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - pidPUnder = adpfs[i]["PID_Pu"].asDouble(); - - if (adpfs[i]["PID_I"].empty() || !adpfs[i]["PID_I"].isDouble()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_I]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - pidI = adpfs[i]["PID_I"].asDouble(); - - if (adpfs[i]["PID_I_Init"].empty() || !adpfs[i]["PID_I_Init"].isInt64()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_I_Init]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - pidIInit = adpfs[i]["PID_I_Init"].asInt64(); - - if (adpfs[i]["PID_I_High"].empty() || !adpfs[i]["PID_I_High"].isInt64()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_I_High]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - pidIHighLimit = adpfs[i]["PID_I_High"].asInt64(); - - if (adpfs[i]["PID_I_Low"].empty() || !adpfs[i]["PID_I_Low"].isInt64()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_I_Low]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - pidILowLimit = adpfs[i]["PID_I_Low"].asInt64(); - - if (adpfs[i]["PID_Do"].empty() || !adpfs[i]["PID_Do"].isDouble()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_Do]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - pidDOver = adpfs[i]["PID_Do"].asDouble(); - - if (adpfs[i]["PID_Du"].empty() || !adpfs[i]["PID_Du"].isDouble()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][PID_Du]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - pidDUnder = adpfs[i]["PID_Du"].asDouble(); - - if (adpfs[i]["UclampMin_On"].empty() || !adpfs[i]["UclampMin_On"].isBool()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][UclampMin_On]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - adpfUclamp = adpfs[i]["UclampMin_On"].asBool(); - - if (adpfs[i]["UclampMin_Init"].empty() || !adpfs[i]["UclampMin_Init"].isInt()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][UclampMin_Init]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - uclampMinInit = adpfs[i]["UclampMin_Init"].asInt(); - - if (adpfs[i]["UclampMin_High"].empty() || !adpfs[i]["UclampMin_High"].isUInt()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][UclampMin_High]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - uclampMinHighLimit = adpfs[i]["UclampMin_High"].asUInt(); - - if (adpfs[i]["UclampMin_Low"].empty() || !adpfs[i]["UclampMin_Low"].isUInt()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][UclampMin_Low]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - uclampMinLowLimit = adpfs[i]["UclampMin_Low"].asUInt(); - - if (adpfs[i]["SamplingWindow_P"].empty() || !adpfs[i]["SamplingWindow_P"].isUInt64()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][SamplingWindow_P]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - samplingWindowP = adpfs[i]["SamplingWindow_P"].asUInt64(); - - if (adpfs[i]["SamplingWindow_I"].empty() || !adpfs[i]["SamplingWindow_I"].isUInt64()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][SamplingWindow_I]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - samplingWindowI = adpfs[i]["SamplingWindow_I"].asUInt64(); - - if (adpfs[i]["SamplingWindow_D"].empty() || !adpfs[i]["SamplingWindow_D"].isUInt64()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][SamplingWindow_D]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - samplingWindowD = adpfs[i]["SamplingWindow_D"].asUInt64(); - - if (adpfs[i]["StaleTimeFactor"].empty() || !adpfs[i]["StaleTimeFactor"].isUInt64()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][StaleTimeFactor]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - staleTimeFactor = adpfs[i]["StaleTimeFactor"].asDouble(); - - if (adpfs[i]["ReportingRateLimitNs"].empty() || - !adpfs[i]["ReportingRateLimitNs"].isInt64()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name - << "][ReportingRateLimitNs]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - reportingRate = adpfs[i]["ReportingRateLimitNs"].asInt64(); - - if (adpfs[i]["EarlyBoost_On"].empty() || !adpfs[i]["EarlyBoost_On"].isBool()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][EarlyBoost_On]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - earlyBoostOn = adpfs[i]["EarlyBoost_On"].asBool(); - - if (adpfs[i]["EarlyBoost_TimeFactor"].empty() || - !adpfs[i]["EarlyBoost_TimeFactor"].isDouble()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name - << "][EarlyBoost_TimeFactor]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - earlyBoostTimeFactor = adpfs[i]["EarlyBoost_TimeFactor"].asDouble(); - - if (adpfs[i]["TargetTimeFactor"].empty() || !adpfs[i]["TargetTimeFactor"].isDouble()) { - LOG(ERROR) << "Failed to read AdpfConfig[" << name << "][TargetTimeFactor]'s Values"; - adpfs_parsed.clear(); - return adpfs_parsed; - } - targetTimeFactor = adpfs[i]["TargetTimeFactor"].asDouble(); + ADPF_PARSE(pidOn, "PID_On", Bool); + ADPF_PARSE(pidPOver, "PID_Po", Double); + ADPF_PARSE(pidPUnder, "PID_Pu", Double); + ADPF_PARSE(pidI, "PID_I", Double); + ADPF_PARSE(pidIInit, "PID_I_Init", Int64); + ADPF_PARSE(pidIHighLimit, "PID_I_High", Int64); + ADPF_PARSE(pidILowLimit, "PID_I_Low", Int64); + ADPF_PARSE(pidDOver, "PID_Do", Double); + ADPF_PARSE(pidDUnder, "PID_Du", Double); + ADPF_PARSE(adpfUclamp, "UclampMin_On", Bool); + ADPF_PARSE(uclampMinInit, "UclampMin_Init", UInt); + ADPF_PARSE(uclampMinHighLimit, "UclampMin_High", UInt); + ADPF_PARSE(uclampMinLowLimit, "UclampMin_Low", UInt); + ADPF_PARSE(samplingWindowP, "SamplingWindow_P", UInt64); + ADPF_PARSE(samplingWindowI, "SamplingWindow_I", UInt64); + ADPF_PARSE(samplingWindowD, "SamplingWindow_D", UInt64); + ADPF_PARSE(staleTimeFactor, "StaleTimeFactor", Double); + ADPF_PARSE(reportingRate, "ReportingRateLimitNs", UInt64); + ADPF_PARSE(targetTimeFactor, "TargetTimeFactor", Double); adpfs_parsed.emplace_back(std::make_shared<AdpfConfig>( name, pidOn, pidPOver, pidPUnder, pidI, pidIInit, pidIHighLimit, pidILowLimit, pidDOver, pidDUnder, adpfUclamp, uclampMinInit, uclampMinHighLimit, uclampMinLowLimit, samplingWindowP, samplingWindowI, samplingWindowD, reportingRate, - earlyBoostOn, earlyBoostTimeFactor, targetTimeFactor, staleTimeFactor)); + targetTimeFactor, staleTimeFactor)); } LOG(INFO) << adpfs_parsed.size() << " AdpfConfigs parsed successfully"; return adpfs_parsed; @@ -872,5 +749,14 @@ bool HintManager::SetAdpfProfile(const std::string &profile_name) { return false; } +bool HintManager::IsAdpfProfileSupported(const std::string &profile_name) const { + for (std::size_t i = 0; i < adpfs_.size(); ++i) { + if (adpfs_[i]->mName == profile_name) { + return true; + } + } + return false; +} + } // namespace perfmgr } // namespace android diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/AdpfConfig.h b/power-libperfmgr/libperfmgr/include/perfmgr/AdpfConfig.h index 7df1bfe7..deab85af 100644 --- a/power-libperfmgr/libperfmgr/include/perfmgr/AdpfConfig.h +++ b/power-libperfmgr/libperfmgr/include/perfmgr/AdpfConfig.h @@ -43,9 +43,6 @@ struct AdpfConfig { uint64_t mSamplingWindowI; uint64_t mSamplingWindowD; int64_t mReportingRateLimitNs; - int64_t mFreezeDurationNs; - bool mEarlyBoostOn; - double mEarlyBoostTimeFactor; double mTargetTimeFactor; // Stale control double mStaleTimeFactor; @@ -59,8 +56,8 @@ struct AdpfConfig { int64_t pidIInit, int64_t pidIHigh, int64_t pidILow, double pidDo, double pidDu, bool uclampMinOn, uint32_t uclampMinInit, uint32_t uclampMinHigh, uint32_t uclampMinLow, uint64_t samplingWindowP, uint64_t samplingWindowI, - uint64_t samplingWindowD, int64_t reportingRateLimitNs, bool earlyBoostOn, - double earlyBoostTimeFactor, double targetTimeFactor, double staleTimeFactor) + uint64_t samplingWindowD, int64_t reportingRateLimitNs, double targetTimeFactor, + double staleTimeFactor) : mName(std::move(name)), mPidOn(pidOn), mPidPo(pidPo), @@ -79,8 +76,6 @@ struct AdpfConfig { mSamplingWindowI(samplingWindowI), mSamplingWindowD(samplingWindowD), mReportingRateLimitNs(reportingRateLimitNs), - mEarlyBoostOn(earlyBoostOn), - mEarlyBoostTimeFactor(earlyBoostTimeFactor), mTargetTimeFactor(targetTimeFactor), mStaleTimeFactor(staleTimeFactor) {} }; diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h b/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h index 13211a18..34726f5e 100644 --- a/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h +++ b/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h @@ -125,6 +125,9 @@ class HintManager { // get current ADPF. std::shared_ptr<AdpfConfig> GetAdpfProfile() const; + // Query if given AdpfProfile supported. + bool IsAdpfProfileSupported(const std::string &name) const; + // Static method to construct HintManager from the JSON config file. static std::unique_ptr<HintManager> GetFromJSON( const std::string& config_path, bool start = true); diff --git a/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc b/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc index 979e7dc0..f323e46e 100644 --- a/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc +++ b/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc @@ -803,10 +803,6 @@ TEST_F(HintManagerTest, ParseAdpfConfigsTest) { EXPECT_EQ(157U, adpfs[1]->mUclampMinLow); EXPECT_EQ(166666660LL, adpfs[0]->mReportingRateLimitNs); EXPECT_EQ(83333330LL, adpfs[1]->mReportingRateLimitNs); - EXPECT_EQ(false, adpfs[0]->mEarlyBoostOn); - EXPECT_EQ(true, adpfs[1]->mEarlyBoostOn); - EXPECT_EQ(0.8, adpfs[0]->mEarlyBoostTimeFactor); - EXPECT_EQ(1.2, adpfs[1]->mEarlyBoostTimeFactor); EXPECT_EQ(1.0, adpfs[0]->mTargetTimeFactor); EXPECT_EQ(1.4, adpfs[1]->mTargetTimeFactor); EXPECT_EQ(10.0, adpfs[0]->mStaleTimeFactor); @@ -851,5 +847,17 @@ TEST_F(HintManagerTest, GetFromJSONAdpfConfigTest) { EXPECT_EQ("REFRESH_120FPS", hm->GetAdpfProfile()->mName); } +TEST_F(HintManagerTest, IsAdpfProfileSupported) { + TemporaryFile json_file; + ASSERT_TRUE(android::base::WriteStringToFile(json_doc_, json_file.path)) << strerror(errno); + std::unique_ptr<HintManager> hm = HintManager::GetFromJSON(json_file.path, false); + EXPECT_NE(nullptr, hm.get()); + + // Check if given AdpfProfile supported + EXPECT_FALSE(hm->IsAdpfProfileSupported("NoSuchProfile")); + EXPECT_TRUE(hm->IsAdpfProfileSupported("REFRESH_60FPS")); + EXPECT_TRUE(hm->IsAdpfProfileSupported("REFRESH_120FPS")); +} + } // namespace perfmgr } // namespace android diff --git a/recovery/Android.bp b/recovery/Android.bp index 4c7e0c7b..cd3526b2 100644 --- a/recovery/Android.bp +++ b/recovery/Android.bp @@ -27,3 +27,27 @@ cc_library_static { "librecovery_ui", ], } + +cc_library_static { + name: "librecovery_ui_pixel_watch", + owner: "google", + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-pedantic", + ], + srcs: [ + "recovery_watch_ui.cpp", + ], + + whole_static_libs: [ + "libmisc_writer", + "libbootloader_message", + ], + + shared_libs: [ + "libbase", + "librecovery_ui", + ], +} diff --git a/recovery/recovery_watch_ui.cpp b/recovery/recovery_watch_ui.cpp new file mode 100644 index 00000000..39a09522 --- /dev/null +++ b/recovery/recovery_watch_ui.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 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 <dlfcn.h> +#include <stdint.h> +#include <string.h> + +#include <string> +#include <string_view> +#include <vector> + +#include <android-base/endian.h> +#include <android-base/logging.h> +#include <android-base/strings.h> +#include <misc_writer/misc_writer.h> +#include <recovery_ui/device.h> +#include <recovery_ui/wear_ui.h> + +namespace android { +namespace hardware { +namespace google { +namespace pixel { + +namespace { + +// Provision Silent OTA(SOTA) flag while reason is "enable-sota" +bool ProvisionSilentOtaFlag(const std::string& reason) { + if (android::base::StartsWith(reason, MiscWriter::kSotaFlag)) { + MiscWriter misc_writer(MiscWriterActions::kSetSotaFlag); + if (!misc_writer.PerformAction()) { + LOG(ERROR) << "Failed to set the silent ota flag"; + return false; + } + LOG(INFO) << "Silent ota flag set successful"; + } + return true; +} + +} // namespace + +class PixelWatchDevice : public ::Device { + public: + explicit PixelWatchDevice(::WearRecoveryUI* const ui) : ::Device(ui) {} + + /** Hook to wipe user data not stored in /data */ + bool PostWipeData() override { + // Try to do everything but report a failure if anything wasn't successful + bool totalSuccess = false; + + // Additional behavior along with wiping data + auto reason = GetReason(); + CHECK(reason.has_value()); + if (!ProvisionSilentOtaFlag(reason.value())) { + totalSuccess = false; + } + + return totalSuccess; + } +}; + +} // namespace pixel +} // namespace google +} // namespace hardware +} // namespace android + +Device *make_device() { + return new ::android::hardware::google::pixel::PixelWatchDevice(new ::WearRecoveryUI); +} diff --git a/thermal/Android.bp b/thermal/Android.bp index 61403b31..afd3c811 100644 --- a/thermal/Android.bp +++ b/thermal/Android.bp @@ -15,6 +15,7 @@ cc_binary { "utils/powerhal_helper.cpp", "utils/thermal_stats_helper.cpp", "utils/thermal_watcher.cpp", + "virtualtemp_estimator/virtualtemp_estimator.cpp", ], vendor: true, relative_install_path: "hw", @@ -31,9 +32,9 @@ cc_binary { "libutils", "libnl", "libbinder_ndk", - "android.frameworks.stats-V1-ndk", + "android.frameworks.stats-V2-ndk", "android.hardware.power-V1-ndk", - "android.hardware.thermal-V1-ndk", + "android.hardware.thermal-V2-ndk", "pixel-power-ext-V1-ndk", "pixelatoms-cpp", ], @@ -41,7 +42,7 @@ cc_binary { "libpixelstats", ], export_shared_lib_headers: [ - "android.frameworks.stats-V1-ndk", + "android.frameworks.stats-V2-ndk", "pixelatoms-cpp", ], cflags: [ @@ -79,6 +80,7 @@ cc_test { "utils/thermal_watcher.cpp", "tests/mock_thermal_helper.cpp", "tests/thermal_looper_test.cpp", + "virtualtemp_estimator/virtualtemp_estimator.cpp", ], shared_libs: [ "libbase", @@ -88,9 +90,9 @@ cc_test { "libnl", "liblog", "libbinder_ndk", - "android.frameworks.stats-V1-ndk", + "android.frameworks.stats-V2-ndk", "android.hardware.power-V1-ndk", - "android.hardware.thermal-V1-ndk", + "android.hardware.thermal-V2-ndk", "pixel-power-ext-V1-ndk", "pixelatoms-cpp", ], @@ -119,3 +121,38 @@ sh_binary { "pixel-thermal-symlinks.rc", ], } + + +cc_binary { + name: "virtualtemp_estimator_test", + srcs: [ + "virtualtemp_estimator/virtualtemp_estimator.cpp", + "virtualtemp_estimator/virtualtemp_estimator_test.cpp" + ], + shared_libs: [ + "libc", + "liblog", + "libcutils", + "libbinder", + "libhidlbase", + "libutils", + "libjsoncpp",], + vendor: true, + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + "-Wunused", + ], + tidy: true, + tidy_checks: [ + "android-*", + "cert-*", + "clang-analyzer-security*", + ], + tidy_checks_as_errors: [ + "android-*", + "clang-analyzer-security*", + "cert-*", + ], +} diff --git a/thermal/Thermal.cpp b/thermal/Thermal.cpp index 4b55ab96..13cdcaa9 100644 --- a/thermal/Thermal.cpp +++ b/thermal/Thermal.cpp @@ -646,15 +646,17 @@ void Thermal::dumpThermalData(int fd) { } } { - dump_buf << "getEmulTemperatures:" << std::endl; + dump_buf << "getEmulSettings:" << std::endl; for (const auto &sensor_status_pair : sensor_status_map) { - if (sensor_status_pair.second.emul_setting == nullptr) { + if (sensor_status_pair.second.override_status.emul_temp == nullptr) { continue; } - dump_buf << " Name: " << sensor_status_pair.first - << " EmulTemp: " << sensor_status_pair.second.emul_setting->emul_temp + dump_buf << " Name: " << sensor_status_pair.first << " EmulTemp: " + << sensor_status_pair.second.override_status.emul_temp->temp << " EmulSeverity: " - << sensor_status_pair.second.emul_setting->emul_severity << std::endl; + << sensor_status_pair.second.override_status.emul_temp->severity + << " maxThrottling: " << std::boolalpha + << sensor_status_pair.second.override_status.max_throttling << std::endl; } } { @@ -774,17 +776,18 @@ binder_status_t Thermal::dump(int fd, const char **args, uint32_t numArgs) { return STATUS_OK; } - if (std::string(args[0]) == "emul_temp") { - return (numArgs != 3 || - !thermal_helper_->emulTemp(std::string(args[1]), std::strtod(args[2], nullptr))) - ? STATUS_BAD_VALUE - : STATUS_OK; - } else if (std::string(args[0]) == "emul_severity") { - return (numArgs != 3 || - !thermal_helper_->emulSeverity(std::string(args[1]), - static_cast<int>(std::strtol(args[2], nullptr, 10)))) - ? STATUS_BAD_VALUE - : STATUS_OK; + if (std::string(args[0]) == "emul_temp" && numArgs >= 3) { + return thermal_helper_->emulTemp( + std::string(args[1]), std::atof(args[2]), + numArgs == 3 ? false : std::string(args[3]) == "max_throttling") + ? STATUS_OK + : STATUS_BAD_VALUE; + } else if (std::string(args[0]) == "emul_severity" && numArgs >= 3) { + return thermal_helper_->emulSeverity( + std::string(args[1]), std::atof(args[2]), + numArgs == 3 ? false : std::string(args[3]) == "max_throttling") + ? STATUS_OK + : STATUS_BAD_VALUE; } else if (std::string(args[0]) == "emul_clear") { return (numArgs != 2 || !thermal_helper_->emulClear(std::string(args[1]))) ? STATUS_BAD_VALUE diff --git a/thermal/android.hardware.thermal-service.pixel.rc b/thermal/android.hardware.thermal-service.pixel.rc index f9f823b8..a1c89fcc 100644 --- a/thermal/android.hardware.thermal-service.pixel.rc +++ b/thermal/android.hardware.thermal-service.pixel.rc @@ -10,5 +10,5 @@ service vendor.thermal-hal /vendor/bin/hw/android.hardware.thermal-service.pixel class hal user system group system - priority -20 + priority -10 disabled diff --git a/thermal/android.hardware.thermal-service.pixel.xml b/thermal/android.hardware.thermal-service.pixel.xml index bdee7446..08dc68ca 100644 --- a/thermal/android.hardware.thermal-service.pixel.xml +++ b/thermal/android.hardware.thermal-service.pixel.xml @@ -1,7 +1,7 @@ <manifest version="1.0" type="device"> <hal format="aidl"> <name>android.hardware.thermal</name> - <version>1</version> + <version>2</version> <fqname>IThermal/default</fqname> </hal> </manifest> diff --git a/thermal/device.mk b/thermal/device.mk deleted file mode 100644 index 1d71d312..00000000 --- a/thermal/device.mk +++ /dev/null @@ -1,13 +0,0 @@ -PRODUCT_PACKAGES += \ - android.hardware.thermal-service.pixel - -# Thermal utils -PRODUCT_PACKAGES += \ - thermal_symlinks - -ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT))) -PRODUCT_PACKAGES += \ - thermal_logd -endif - -BOARD_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/thermal diff --git a/thermal/tests/mock_thermal_helper.h b/thermal/tests/mock_thermal_helper.h index 43ff3286..c706461b 100644 --- a/thermal/tests/mock_thermal_helper.h +++ b/thermal/tests/mock_thermal_helper.h @@ -34,8 +34,8 @@ class MockThermalHelper : public ThermalHelper { (bool, TemperatureType, std::vector<TemperatureThreshold> *), (const, override)); MOCK_METHOD(bool, fillCurrentCoolingDevices, (bool, CoolingType, std::vector<CoolingDevice> *), (const, override)); - MOCK_METHOD(bool, emulTemp, (std::string_view, const float), (override)); - MOCK_METHOD(bool, emulSeverity, (std::string_view, const int), (override)); + MOCK_METHOD(bool, emulTemp, (std::string_view, const float, const bool), (override)); + MOCK_METHOD(bool, emulSeverity, (std::string_view, const int, const bool), (override)); MOCK_METHOD(bool, emulClear, (std::string_view), (override)); MOCK_METHOD(bool, isInitializedOk, (), (const, override)); MOCK_METHOD(bool, readTemperature, diff --git a/thermal/thermal-helper.cpp b/thermal/thermal-helper.cpp index dc4a599c..61de8bcc 100644 --- a/thermal/thermal-helper.cpp +++ b/thermal/thermal-helper.cpp @@ -157,9 +157,11 @@ ThermalHelperImpl::ThermalHelperImpl(const NotificationCallback &cb) ret = false; } - if (!thermal_stats_helper_.initializeStats(config, sensor_info_map_, - cooling_device_info_map_)) { - LOG(FATAL) << "Failed to initialize thermal stats"; + if (ret) { + if (!thermal_stats_helper_.initializeStats(config, sensor_info_map_, + cooling_device_info_map_)) { + LOG(FATAL) << "Failed to initialize thermal stats"; + } } for (auto &name_status_pair : sensor_info_map_) { @@ -169,7 +171,7 @@ ThermalHelperImpl::ThermalHelperImpl(const NotificationCallback &cb) .prev_cold_severity = ThrottlingSeverity::NONE, .last_update_time = boot_clock::time_point::min(), .thermal_cached = {NAN, boot_clock::time_point::min()}, - .emul_setting = nullptr, + .override_status = {nullptr, false, false}, }; if (name_status_pair.second.throttling_info != nullptr) { @@ -286,42 +288,82 @@ bool getThermalZoneTypeById(int tz_id, std::string *type) { return true; } -bool ThermalHelperImpl::emulTemp(std::string_view target_sensor, const float value) { - LOG(INFO) << "Set " << target_sensor.data() << " emul_temp " - << "to " << value; +void ThermalHelperImpl::checkUpdateSensorForEmul(std::string_view target_sensor, + const bool max_throttling) { + // Force update all the sensors which are related to the target emul sensor + for (auto &[sensor_name, sensor_info] : sensor_info_map_) { + if (sensor_info.virtual_sensor_info == nullptr || !sensor_info.is_watch) { + continue; + } + + const auto &linked_sensors = sensor_info.virtual_sensor_info->linked_sensors; + if (std::find(linked_sensors.begin(), linked_sensors.end(), target_sensor) == + linked_sensors.end()) { + continue; + } + + auto &sensor_status = sensor_status_map_.at(sensor_name.data()); + sensor_status.override_status.max_throttling = max_throttling; + sensor_status.override_status.pending_update = true; + + checkUpdateSensorForEmul(sensor_name, max_throttling); + } +} + +bool ThermalHelperImpl::emulTemp(std::string_view target_sensor, const float temp, + const bool max_throttling) { + LOG(INFO) << "Set " << target_sensor.data() << " emul_temp: " << temp + << " max_throttling: " << max_throttling; std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_); + // Check the target sensor is valid if (!sensor_status_map_.count(target_sensor.data())) { LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data(); return false; } - sensor_status_map_.at(target_sensor.data()) - .emul_setting.reset(new EmulSetting{value, -1, true}); + auto &sensor_status = sensor_status_map_.at(target_sensor.data()); + + sensor_status.override_status.emul_temp.reset(new EmulTemp{temp, -1}); + sensor_status.override_status.max_throttling = max_throttling; + sensor_status.override_status.pending_update = true; + + checkUpdateSensorForEmul(target_sensor.data(), max_throttling); thermal_watcher_->wake(); return true; } -bool ThermalHelperImpl::emulSeverity(std::string_view target_sensor, const int severity) { - LOG(INFO) << "Set " << target_sensor.data() << " emul_severity " - << "to " << severity; +bool ThermalHelperImpl::emulSeverity(std::string_view target_sensor, const int severity, + const bool max_throttling) { + LOG(INFO) << "Set " << target_sensor.data() << " emul_severity: " << severity + << " max_throttling: " << max_throttling; std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_); // Check the target sensor is valid - if (!sensor_status_map_.count(target_sensor.data())) { + if (!sensor_status_map_.count(target_sensor.data()) || + !sensor_info_map_.count(target_sensor.data())) { LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data(); return false; } + const auto &sensor_info = sensor_info_map_.at(target_sensor.data()); + // Check the emul severity is valid if (severity > static_cast<int>(kThrottlingSeverityCount)) { LOG(ERROR) << "Invalid emul severity value " << severity; return false; } - sensor_status_map_.at(target_sensor.data()) - .emul_setting.reset(new EmulSetting{NAN, severity, true}); + const auto temp = sensor_info.hot_thresholds[severity] / sensor_info.multiplier; + + auto &sensor_status = sensor_status_map_.at(target_sensor.data()); + + sensor_status.override_status.emul_temp.reset(new EmulTemp{temp, severity}); + sensor_status.override_status.max_throttling = max_throttling; + sensor_status.override_status.pending_update = true; + + checkUpdateSensorForEmul(target_sensor.data(), max_throttling); thermal_watcher_->wake(); return true; @@ -332,19 +374,22 @@ bool ThermalHelperImpl::emulClear(std::string_view target_sensor) { std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_); if (target_sensor == "all") { - for (auto &sensor_status : sensor_status_map_) { - if (sensor_status.second.emul_setting != nullptr) { - sensor_status.second.emul_setting.reset(new EmulSetting{NAN, -1, true}); - } + for (auto &[sensor_name, sensor_status] : sensor_status_map_) { + sensor_status.override_status = { + .emul_temp = nullptr, .max_throttling = false, .pending_update = true}; + checkUpdateSensorForEmul(sensor_name, false); } - } else if (sensor_status_map_.count(target_sensor.data()) && - sensor_status_map_.at(target_sensor.data()).emul_setting != nullptr) { - sensor_status_map_.at(target_sensor.data()) - .emul_setting.reset(new EmulSetting{NAN, -1, true}); + } else if (sensor_status_map_.count(target_sensor.data())) { + auto &sensor_status = sensor_status_map_.at(target_sensor.data()); + sensor_status.override_status = { + .emul_temp = nullptr, .max_throttling = false, .pending_update = true}; + checkUpdateSensorForEmul(target_sensor.data(), false); } else { LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data(); return false; } + + thermal_watcher_->wake(); return true; } @@ -377,8 +422,15 @@ bool ThermalHelperImpl::readTemperature( std::map<std::string, float> sensor_log_map; auto &sensor_status = sensor_status_map_.at(sensor_name.data()); - if (!readThermalSensor(sensor_name, &temp, force_no_cache, &sensor_log_map) || - std::isnan(temp)) { + if (!readThermalSensor(sensor_name, &temp, force_no_cache, &sensor_log_map)) { + LOG(ERROR) << "Failed to read thermal sensor " << sensor_name.data(); + thermal_stats_helper_.reportThermalAbnormality( + ThermalSensorAbnormalityDetected::TEMP_READ_FAIL, sensor_name, std::nullopt); + return false; + } + + if (std::isnan(temp)) { + LOG(INFO) << "Sensor " << sensor_name.data() << " temperature is nan."; return false; } @@ -407,10 +459,11 @@ bool ThermalHelperImpl::readTemperature( *throttling_status = status; } - if (sensor_status.emul_setting != nullptr && sensor_status.emul_setting->emul_severity >= 0) { + if (sensor_status.override_status.emul_temp != nullptr && + sensor_status.override_status.emul_temp->severity >= 0) { std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_); out->throttlingStatus = - static_cast<ThrottlingSeverity>(sensor_status.emul_setting->emul_severity); + static_cast<ThrottlingSeverity>(sensor_status.override_status.emul_temp->severity); } else { out->throttlingStatus = static_cast<size_t>(status.first) > static_cast<size_t>(status.second) @@ -853,12 +906,55 @@ bool ThermalHelperImpl::readDataByType(std::string_view sensor_data, float *read return true; } +float ThermalHelperImpl::runVirtualTempEstimator(std::string_view sensor_name, + std::map<std::string, float> *sensor_log_map) { + std::vector<float> model_inputs; + float estimated_vt = NAN; + constexpr int kCelsius2mC = 1000; + + ATRACE_NAME(StringPrintf("ThermalHelper::runVirtualTempEstimator - %s", sensor_name.data()) + .c_str()); + if (!(sensor_info_map_.count(sensor_name.data()) && + sensor_status_map_.count(sensor_name.data()))) { + LOG(ERROR) << sensor_name << " not part of sensor_info_map_ or sensor_status_map_"; + return NAN; + } + + const auto &sensor_info = sensor_info_map_.at(sensor_name.data()); + if (!sensor_info.virtual_sensor_info->vt_estimator) { + LOG(ERROR) << "vt_estimator not valid for " << sensor_name; + return NAN; + } + + model_inputs.reserve(sensor_info.virtual_sensor_info->linked_sensors.size()); + + for (size_t i = 0; i < sensor_info.virtual_sensor_info->linked_sensors.size(); i++) { + std::string linked_sensor = sensor_info.virtual_sensor_info->linked_sensors[i]; + + if ((*sensor_log_map).count(linked_sensor.data())) { + float value = (*sensor_log_map)[linked_sensor.data()]; + model_inputs.push_back(value / kCelsius2mC); + } else { + LOG(ERROR) << "failed to read sensor: " << linked_sensor; + return NAN; + } + } + + ::thermal::vtestimator::VtEstimatorStatus ret = + sensor_info.virtual_sensor_info->vt_estimator->Estimate(model_inputs, &estimated_vt); + if (ret != ::thermal::vtestimator::kVtEstimatorOk) { + LOG(ERROR) << "Failed to run estimator (ret: " << ret << ") for " << sensor_name; + return NAN; + } + + return (estimated_vt * kCelsius2mC); +} + constexpr int kTranTimeoutParam = 2; bool ThermalHelperImpl::readThermalSensor(std::string_view sensor_name, float *temp, const bool force_no_cache, std::map<std::string, float> *sensor_log_map) { - float temp_val = 0.0; std::string file_reading; boot_clock::time_point now = boot_clock::now(); @@ -873,9 +969,8 @@ bool ThermalHelperImpl::readThermalSensor(std::string_view sensor_name, float *t { std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_); - if (sensor_status.emul_setting != nullptr && - !isnan(sensor_status.emul_setting->emul_temp)) { - *temp = sensor_status.emul_setting->emul_temp; + if (sensor_status.override_status.emul_temp != nullptr) { + *temp = sensor_status.override_status.emul_temp->temp; return true; } } @@ -903,57 +998,75 @@ bool ThermalHelperImpl::readThermalSensor(std::string_view sensor_name, float *t } *temp = std::stof(::android::base::Trim(file_reading)); } else { - for (size_t i = 0; i < sensor_info.virtual_sensor_info->linked_sensors.size(); i++) { - float sensor_reading = 0.0; - // Get the sensor reading data - if (!readDataByType(sensor_info.virtual_sensor_info->linked_sensors[i], &sensor_reading, + const auto &linked_sensors_size = sensor_info.virtual_sensor_info->linked_sensors.size(); + std::vector<float> sensor_readings(linked_sensors_size, 0.0); + + // Calculate temperature of each of the linked sensor + for (size_t i = 0; i < linked_sensors_size; i++) { + if (!readDataByType(sensor_info.virtual_sensor_info->linked_sensors[i], + &sensor_readings[i], sensor_info.virtual_sensor_info->linked_sensors_type[i], force_no_cache, sensor_log_map)) { LOG(ERROR) << "Failed to read " << sensor_name.data() << "'s linked sensor " << sensor_info.virtual_sensor_info->linked_sensors[i]; return false; } - - float coefficient = 0.0; - if (!readDataByType(sensor_info.virtual_sensor_info->coefficients[i], &coefficient, - sensor_info.virtual_sensor_info->coefficients_type[i], - force_no_cache, sensor_log_map)) { - LOG(ERROR) << "Failed to read " << sensor_name.data() << "'s coefficient " - << sensor_info.virtual_sensor_info->coefficients[i]; - return false; - } - - if (std::isnan(sensor_reading) || std::isnan(coefficient)) { + if (std::isnan(sensor_readings[i])) { LOG(INFO) << sensor_name << " data is under collecting"; return true; } + } - switch (sensor_info.virtual_sensor_info->formula) { - case FormulaOption::COUNT_THRESHOLD: - if ((coefficient < 0 && sensor_reading < -coefficient) || - (coefficient >= 0 && sensor_reading >= coefficient)) - temp_val += 1; - break; - case FormulaOption::WEIGHTED_AVG: - temp_val += sensor_reading * coefficient; - break; - case FormulaOption::MAXIMUM: - if (i == 0) - temp_val = std::numeric_limits<float>::lowest(); - if (sensor_reading * coefficient > temp_val) - temp_val = sensor_reading * coefficient; - break; - case FormulaOption::MINIMUM: - if (i == 0) - temp_val = std::numeric_limits<float>::max(); - if (sensor_reading * coefficient < temp_val) - temp_val = sensor_reading * coefficient; - break; - default: - break; + if (sensor_info.virtual_sensor_info->formula == FormulaOption::USE_ML_MODEL) { + *temp = runVirtualTempEstimator(sensor_name, sensor_log_map); + + if (std::isnan(*temp)) { + LOG(ERROR) << "VirtualEstimator returned NAN for " << sensor_name; + return false; + } + } else { + float temp_val = 0.0; + for (size_t i = 0; i < linked_sensors_size; i++) { + float coefficient = 0.0; + if (!readDataByType(sensor_info.virtual_sensor_info->coefficients[i], &coefficient, + sensor_info.virtual_sensor_info->coefficients_type[i], + force_no_cache, sensor_log_map)) { + LOG(ERROR) << "Failed to read " << sensor_name.data() << "'s coefficient " + << sensor_info.virtual_sensor_info->coefficients[i]; + return false; + } + if (std::isnan(coefficient)) { + LOG(INFO) << sensor_name << " data is under collecting"; + return true; + } + switch (sensor_info.virtual_sensor_info->formula) { + case FormulaOption::COUNT_THRESHOLD: + if ((coefficient < 0 && sensor_readings[i] < -coefficient) || + (coefficient >= 0 && sensor_readings[i] >= coefficient)) + temp_val += 1; + break; + case FormulaOption::WEIGHTED_AVG: + temp_val += sensor_readings[i] * coefficient; + break; + case FormulaOption::MAXIMUM: + if (i == 0) + temp_val = std::numeric_limits<float>::lowest(); + if (sensor_readings[i] * coefficient > temp_val) + temp_val = sensor_readings[i] * coefficient; + break; + case FormulaOption::MINIMUM: + if (i == 0) + temp_val = std::numeric_limits<float>::max(); + if (sensor_readings[i] * coefficient < temp_val) + temp_val = sensor_readings[i] * coefficient; + break; + default: + LOG(ERROR) << "Unknown formula type for sensor " << sensor_name.data(); + return false; + } } + *temp = (temp_val + sensor_info.virtual_sensor_info->offset); } - *temp = (temp_val + sensor_info.virtual_sensor_info->offset); } if (!isnan(sensor_info.step_ratio) && !isnan(sensor_status.thermal_cached.temp) && @@ -993,6 +1106,7 @@ std::chrono::milliseconds ThermalHelperImpl::thermalWatcherCallbackFunc( TemperatureThreshold threshold; SensorStatus &sensor_status = name_status_pair.second; const SensorInfo &sensor_info = sensor_info_map_.at(name_status_pair.first); + bool max_throttling = false; // Only handle the sensors in allow list if (!sensor_info.is_watch) { @@ -1046,12 +1160,10 @@ std::chrono::milliseconds ThermalHelperImpl::thermalWatcherCallbackFunc( } { std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_); - if (sensor_status.emul_setting != nullptr && - sensor_status.emul_setting->pending_update) { - force_update = true; - sensor_status.emul_setting->pending_update = false; - LOG(INFO) << "Update " << name_status_pair.first.data() - << " right away with emul setting"; + max_throttling = sensor_status.override_status.max_throttling; + if (sensor_status.override_status.pending_update) { + force_update = sensor_status.override_status.pending_update; + sensor_status.override_status.pending_update = false; } } LOG(VERBOSE) << "sensor " << name_status_pair.first @@ -1110,7 +1222,7 @@ std::chrono::milliseconds ThermalHelperImpl::thermalWatcherCallbackFunc( // update thermal throttling request thermal_throttling_.thermalThrottlingUpdate( temp, sensor_info, sensor_status.severity, time_elapsed_ms, - power_files_.GetPowerStatusMap(), cooling_device_info_map_); + power_files_.GetPowerStatusMap(), cooling_device_info_map_, max_throttling); } thermal_throttling_.computeCoolingDevicesRequest( diff --git a/thermal/thermal-helper.h b/thermal/thermal-helper.h index 6ae13c43..489b792a 100644 --- a/thermal/thermal-helper.h +++ b/thermal/thermal-helper.h @@ -55,9 +55,14 @@ struct ThermalSample { boot_clock::time_point timestamp; }; -struct EmulSetting { - float emul_temp; - int emul_severity; +struct EmulTemp { + float temp; + int severity; +}; + +struct OverrideStatus { + std::unique_ptr<EmulTemp> emul_temp; + bool max_throttling; bool pending_update; }; @@ -67,7 +72,7 @@ struct SensorStatus { ThrottlingSeverity prev_cold_severity; boot_clock::time_point last_update_time; ThermalSample thermal_cached; - std::unique_ptr<EmulSetting> emul_setting; + OverrideStatus override_status; }; class ThermalHelper { @@ -79,8 +84,10 @@ class ThermalHelper { std::vector<TemperatureThreshold> *thresholds) const = 0; virtual bool fillCurrentCoolingDevices(bool filterType, CoolingType type, std::vector<CoolingDevice> *coolingdevices) const = 0; - virtual bool emulTemp(std::string_view target_sensor, const float temp) = 0; - virtual bool emulSeverity(std::string_view target_sensor, const int severity) = 0; + virtual bool emulTemp(std::string_view target_sensor, const float temp, + const bool max_throttling) = 0; + virtual bool emulSeverity(std::string_view target_sensor, const int severity, + const bool max_throttling) = 0; virtual bool emulClear(std::string_view target_sensor) = 0; virtual bool isInitializedOk() const = 0; virtual bool readTemperature( @@ -117,8 +124,10 @@ class ThermalHelperImpl : public ThermalHelper { std::vector<TemperatureThreshold> *thresholds) const override; bool fillCurrentCoolingDevices(bool filterType, CoolingType type, std::vector<CoolingDevice> *coolingdevices) const override; - bool emulTemp(std::string_view target_sensor, const float temp) override; - bool emulSeverity(std::string_view target_sensor, const int severity) override; + bool emulTemp(std::string_view target_sensor, const float temp, + const bool max_throttling) override; + bool emulSeverity(std::string_view target_sensor, const int severity, + const bool max_throttling) override; bool emulClear(std::string_view target_sensor) override; // Disallow copy and assign. @@ -203,10 +212,13 @@ class ThermalHelperImpl : public ThermalHelper { // Read temperature data according to thermal sensor's info bool readThermalSensor(std::string_view sensor_name, float *temp, const bool force_sysfs, std::map<std::string, float> *sensor_log_map); + float runVirtualTempEstimator(std::string_view sensor_name, + std::map<std::string, float> *sensor_log_map); void updateCoolingDevices(const std::vector<std::string> &cooling_devices_to_update); // Check the max CDEV state for cdev_ceiling void maxCoolingRequestCheck( std::unordered_map<std::string, BindedCdevInfo> *binded_cdev_info_map); + void checkUpdateSensorForEmul(std::string_view target_sensor, const bool max_throttling); sp<ThermalWatcher> thermal_watcher_; PowerFiles power_files_; ThermalFiles thermal_sensors_; diff --git a/thermal/utils/power_files.cpp b/thermal/utils/power_files.cpp index 767f434b..2e4a77ff 100644 --- a/thermal/utils/power_files.cpp +++ b/thermal/utils/power_files.cpp @@ -125,8 +125,8 @@ bool PowerFiles::registerPowerRailsToWatch(const Json::Value &config) { if (power_history.size()) { power_status_map_[power_rail_info_pair.first] = { - .power_history = power_history, .last_update_time = boot_clock::time_point::min(), + .power_history = power_history, .last_updated_avg_power = NAN, }; } else { diff --git a/thermal/utils/powerhal_helper.cpp b/thermal/utils/powerhal_helper.cpp index f8ac7d0b..8b633908 100644 --- a/thermal/utils/powerhal_helper.cpp +++ b/thermal/utils/powerhal_helper.cpp @@ -58,7 +58,8 @@ bool PowerHalService::connect() { } const std::string kInstance = std::string(IPower::descriptor) + "/default"; - ndk::SpAIBinder power_binder = ndk::SpAIBinder(AServiceManager_getService(kInstance.c_str())); + ndk::SpAIBinder power_binder = + ndk::SpAIBinder(AServiceManager_waitForService(kInstance.c_str())); ndk::SpAIBinder ext_power_binder; if (power_binder.get() == nullptr) { diff --git a/thermal/utils/thermal_info.cpp b/thermal/utils/thermal_info.cpp index c5535c5f..d03b6d87 100644 --- a/thermal/utils/thermal_info.cpp +++ b/thermal/utils/thermal_info.cpp @@ -129,8 +129,75 @@ bool getFloatFromJsonValues(const Json::Value &values, ThrottlingArray *out, boo *out = ret; return true; } + +bool getTempRangeInfoFromJsonValues(const Json::Value &values, TempRangeInfo *temp_range_info) { + if (values.size() != 2) { + LOG(ERROR) << "Temp Range Values size: " << values.size() << "is invalid."; + return false; + } + + float min_temp = getFloatFromValue(values[0]); + float max_temp = getFloatFromValue(values[1]); + + if (std::isnan(min_temp) || std::isnan(max_temp)) { + LOG(ERROR) << "Illegal temp range: thresholds not defined properly " << min_temp << " : " + << max_temp; + return false; + } + + if (min_temp > max_temp) { + LOG(ERROR) << "Illegal temp range: temp_min_threshold(" << min_temp + << ") > temp_max_threshold(" << max_temp << ")"; + return false; + } + temp_range_info->min_temp_threshold = min_temp; + temp_range_info->max_temp_threshold = max_temp; + LOG(INFO) << "Temp Range Info: " << temp_range_info->min_temp_threshold + << " <= t <= " << temp_range_info->max_temp_threshold; + return true; +} + +bool getTempStuckInfoFromJsonValue(const Json::Value &values, TempStuckInfo *temp_stuck_info) { + if (values["MinStuckDuration"].empty()) { + LOG(ERROR) << "Minimum stuck duration not present."; + return false; + } + int min_stuck_duration_int = getIntFromValue(values["MinStuckDuration"]); + if (min_stuck_duration_int <= 0) { + LOG(ERROR) << "Invalid Minimum stuck duration " << min_stuck_duration_int; + return false; + } + + if (values["MinPollingCount"].empty()) { + LOG(ERROR) << "Minimum polling count not present."; + return false; + } + int min_polling_count = getIntFromValue(values["MinPollingCount"]); + if (min_polling_count <= 0) { + LOG(ERROR) << "Invalid Minimum stuck duration " << min_polling_count; + return false; + } + temp_stuck_info->min_stuck_duration = std::chrono::milliseconds(min_stuck_duration_int); + temp_stuck_info->min_polling_count = min_polling_count; + LOG(INFO) << "Temp Stuck Info: polling_count=" << temp_stuck_info->min_polling_count + << " stuck_duration=" << temp_stuck_info->min_stuck_duration.count(); + return true; +} } // namespace +std::ostream &operator<<(std::ostream &stream, const SensorFusionType &sensor_fusion_type) { + switch (sensor_fusion_type) { + case SensorFusionType::SENSOR: + return stream << "SENSOR"; + case SensorFusionType::ODPM: + return stream << "ODPM"; + case SensorFusionType::CONSTANT: + return stream << "CONSTANT"; + default: + return stream << "UNDEFINED"; + } +} + bool ParseThermalConfig(std::string_view config_path, Json::Value *config) { std::string json_doc; if (!::android::base::ReadFileToString(config_path.data(), &json_doc)) { @@ -165,6 +232,8 @@ bool ParseVirtualSensorInfo(const std::string_view name, const Json::Value &sens std::vector<std::string> coefficients; std::vector<SensorFusionType> coefficients_type; FormulaOption formula = FormulaOption::COUNT_THRESHOLD; + std::string vt_estimator_model_file; + std::unique_ptr<::thermal::vtestimator::VirtualTempEstimator> vt_estimator; Json::Value values = sensor["Combination"]; if (values.size()) { @@ -178,6 +247,21 @@ bool ParseVirtualSensorInfo(const std::string_view name, const Json::Value &sens return false; } + if (sensor["Formula"].asString().compare("COUNT_THRESHOLD") == 0) { + formula = FormulaOption::COUNT_THRESHOLD; + } else if (sensor["Formula"].asString().compare("WEIGHTED_AVG") == 0) { + formula = FormulaOption::WEIGHTED_AVG; + } else if (sensor["Formula"].asString().compare("MAXIMUM") == 0) { + formula = FormulaOption::MAXIMUM; + } else if (sensor["Formula"].asString().compare("MINIMUM") == 0) { + formula = FormulaOption::MINIMUM; + } else if (sensor["Formula"].asString().compare("USE_ML_MODEL") == 0) { + formula = FormulaOption::USE_ML_MODEL; + } else { + LOG(ERROR) << "Sensor[" << name << "]'s Formula is invalid"; + return false; + } + values = sensor["CombinationType"]; if (!values.size()) { linked_sensors_type.reserve(linked_sensors.size()); @@ -216,7 +300,8 @@ bool ParseVirtualSensorInfo(const std::string_view name, const Json::Value &sens LOG(ERROR) << "Sensor[" << name << "] has no Coefficient setting"; return false; } - if (linked_sensors.size() != coefficients.size()) { + if ((linked_sensors.size() != coefficients.size()) && + (formula != FormulaOption::USE_ML_MODEL)) { LOG(ERROR) << "Sensor[" << name << "] has invalid Coefficient size"; return false; } @@ -280,21 +365,43 @@ bool ParseVirtualSensorInfo(const std::string_view name, const Json::Value &sens } } - if (sensor["Formula"].asString().compare("COUNT_THRESHOLD") == 0) { - formula = FormulaOption::COUNT_THRESHOLD; - } else if (sensor["Formula"].asString().compare("WEIGHTED_AVG") == 0) { - formula = FormulaOption::WEIGHTED_AVG; - } else if (sensor["Formula"].asString().compare("MAXIMUM") == 0) { - formula = FormulaOption::MAXIMUM; - } else if (sensor["Formula"].asString().compare("MINIMUM") == 0) { - formula = FormulaOption::MINIMUM; - } else { - LOG(ERROR) << "Sensor[" << name << "]'s Formula is invalid"; - return false; + if (formula == FormulaOption::USE_ML_MODEL) { + if (sensor["ModelPath"].empty()) { + LOG(ERROR) << "Sensor[" << name << "] has no ModelPath"; + return false; + } + + if (!linked_sensors.size()) { + LOG(ERROR) << "Sensor[" << name << "] uses USE_ML_MODEL and has zero linked_sensors"; + return false; + } + + vt_estimator = std::make_unique<::thermal::vtestimator::VirtualTempEstimator>( + linked_sensors.size()); + if (!vt_estimator) { + LOG(ERROR) << "Failed to create vt estimator for Sensor[" << name + << "] with linked sensor size : " << linked_sensors.size(); + return false; + } + + vt_estimator_model_file = "vendor/etc/" + sensor["ModelPath"].asString(); + + ::thermal::vtestimator::VtEstimatorStatus ret = + vt_estimator->Initialize(vt_estimator_model_file.c_str()); + if (ret != ::thermal::vtestimator::kVtEstimatorOk) { + LOG(ERROR) << "Failed to initialize vt estimator for Sensor[" << name + << "] with ModelPath: " << vt_estimator_model_file + << " with ret code : " << ret; + return false; + } + + LOG(INFO) << "Successfully created vt_estimator for Sensor[" << name + << "] with input samples: " << linked_sensors.size(); } - virtual_sensor_info->reset(new VirtualSensorInfo{linked_sensors, linked_sensors_type, - coefficients, coefficients_type, offset, - trigger_sensors, formula}); + + virtual_sensor_info->reset(new VirtualSensorInfo{ + linked_sensors, linked_sensors_type, coefficients, coefficients_type, offset, + trigger_sensors, formula, vt_estimator_model_file, std::move(vt_estimator)}); return true; } @@ -434,14 +541,14 @@ bool ParseBindedCdevInfo(const Json::Value &values, .limit_info = limit_info, .power_thresholds = power_thresholds, .release_logic = release_logic, - .high_power_check = high_power_check, - .throttling_with_power_link = throttling_with_power_link, .cdev_weight_for_pid = cdev_weight_for_pid, .cdev_ceiling = cdev_ceiling, .max_release_step = max_release_step, .max_throttle_step = max_throttle_step, .cdev_floor_with_power_link = cdev_floor_with_power_link, .power_rail = power_rail, + .high_power_check = high_power_check, + .throttling_with_power_link = throttling_with_power_link, .enabled = enabled, }; } @@ -1023,14 +1130,6 @@ bool ParsePowerRailInfo(const Json::Value &config, return false; } - std::string rail; - if (power_rails[i]["Rail"].empty()) { - rail = name; - } else { - rail = power_rails[i]["Rail"].asString(); - } - LOG(INFO) << "PowerRail[" << i << "]'s Rail: " << rail; - std::vector<std::string> linked_power_rails; std::vector<float> coefficient; float offset = 0; @@ -1124,7 +1223,6 @@ bool ParsePowerRailInfo(const Json::Value &config, } (*power_rails_parsed)[name] = { - .rail = rail, .power_sample_count = power_sample_count, .power_sample_delay = power_sample_delay, .virtual_power_rail_info = std::move(virtual_power_rail_info), @@ -1232,44 +1330,180 @@ bool ParseStatsInfo(const Json::Value &stats_config, return true; } -bool ParseStatsConfig(const Json::Value &config, - const std::unordered_map<std::string, SensorInfo> &sensor_info_map_, - const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_, - StatsConfig *stats_config_parsed) { - Json::Value stats_config = config["Stats"]; +bool ParseSensorAbnormalStatsConfig( + const Json::Value &abnormal_stats_config, + const std::unordered_map<std::string, SensorInfo> &sensor_info_map_, + AbnormalStatsInfo *abnormal_stats_info_parsed) { + if (abnormal_stats_config.empty()) { + LOG(INFO) << "No sensors abnormality monitoring info present."; + return true; + } + + Json::Value values; + + std::optional<TempRangeInfo> default_temp_range_info; + std::vector<AbnormalStatsInfo::SensorsTempRangeInfo> sensors_temp_range_infos; + Json::Value outlier_temp_config = abnormal_stats_config["Outlier"]; + if (outlier_temp_config) { + LOG(INFO) << "Start to parse outlier temp config."; + + if (outlier_temp_config["Default"]) { + LOG(INFO) << "Start to parse defaultTempRange."; + if (!getTempRangeInfoFromJsonValues(outlier_temp_config["Default"], + &default_temp_range_info.value())) { + LOG(ERROR) << "Failed to parse default temp range config."; + return false; + } + } + + Json::Value configs = outlier_temp_config["Configs"]; + if (configs) { + std::unordered_set<std::string> sensors_parsed; + for (Json::Value::ArrayIndex i = 0; i < configs.size(); i++) { + LOG(INFO) << "Start to parse temp range config[" << i << "]"; + AbnormalStatsInfo::SensorsTempRangeInfo sensors_temp_range_info; + values = configs[i]["Monitor"]; + if (!values.size()) { + LOG(ERROR) << "Invalid config no sensor list present for outlier temp " + "config."; + return false; + } + for (Json::Value::ArrayIndex j = 0; j < values.size(); j++) { + const std::string &sensor = values[j].asString(); + if (!sensor_info_map_.count(sensor)) { + LOG(ERROR) << "Unknown sensor " << sensor; + return false; + } + auto result = sensors_parsed.insert(sensor); + if (!result.second) { + LOG(ERROR) << "Duplicate Sensor Temp Range Config: " << sensor; + return false; + } + LOG(INFO) << "Monitored sensor [" << j << "]: " << sensor; + sensors_temp_range_info.sensors.push_back(sensor); + } + if (!getTempRangeInfoFromJsonValues(configs[i]["TempRange"], + &sensors_temp_range_info.temp_range_info)) { + LOG(ERROR) << "Failed to parse temp range config."; + return false; + } + sensors_temp_range_infos.push_back(sensors_temp_range_info); + } + } + } + std::optional<TempStuckInfo> default_temp_stuck_info; + std::vector<AbnormalStatsInfo::SensorsTempStuckInfo> sensors_temp_stuck_infos; + Json::Value stuck_temp_config = abnormal_stats_config["Stuck"]; + if (stuck_temp_config) { + LOG(INFO) << "Start to parse stuck temp config."; + + if (stuck_temp_config["Default"]) { + LOG(INFO) << "Start to parse defaultTempStuck."; + if (!getTempStuckInfoFromJsonValue(stuck_temp_config["Default"], + &default_temp_stuck_info.value())) { + LOG(ERROR) << "Failed to parse default temp stuck config."; + return false; + } + } + + Json::Value configs = stuck_temp_config["Configs"]; + if (configs) { + std::unordered_set<std::string> sensors_parsed; + for (Json::Value::ArrayIndex i = 0; i < configs.size(); i++) { + LOG(INFO) << "Start to parse temp stuck config[" << i << "]"; + AbnormalStatsInfo::SensorsTempStuckInfo sensor_temp_stuck_info; + values = configs[i]["Monitor"]; + if (!values.size()) { + LOG(ERROR) << "Invalid config no sensor list present for stuck temp " + "config."; + return false; + } + for (Json::Value::ArrayIndex j = 0; j < values.size(); j++) { + const std::string &sensor = values[j].asString(); + if (!sensor_info_map_.count(sensor)) { + LOG(ERROR) << "Unknown sensor " << sensor; + return false; + } + auto result = sensors_parsed.insert(sensor); + if (!result.second) { + LOG(ERROR) << "Duplicate Sensor Temp Stuck Config: " << sensor; + return false; + } + LOG(INFO) << "Monitored sensor [" << j << "]: " << sensor; + sensor_temp_stuck_info.sensors.push_back(sensor); + } + if (!getTempStuckInfoFromJsonValue(configs[i]["TempStuck"], + &sensor_temp_stuck_info.temp_stuck_info)) { + LOG(ERROR) << "Failed to parse temp stuck config."; + return false; + } + sensors_temp_stuck_infos.push_back(sensor_temp_stuck_info); + } + } + } + *abnormal_stats_info_parsed = { + .default_temp_range_info = default_temp_range_info, + .sensors_temp_range_infos = sensors_temp_range_infos, + .default_temp_stuck_info = default_temp_stuck_info, + .sensors_temp_stuck_infos = sensors_temp_stuck_infos, + }; + return true; +} +bool ParseSensorStatsConfig(const Json::Value &config, + const std::unordered_map<std::string, SensorInfo> &sensor_info_map_, + StatsInfo<float> *sensor_stats_info_parsed, + AbnormalStatsInfo *abnormal_stats_info_parsed) { + Json::Value stats_config = config["Stats"]; if (stats_config.empty()) { LOG(INFO) << "No Stats Config present."; return true; } - + // Parse cooling device user vote + Json::Value sensor_config = stats_config["Sensors"]; + if (sensor_config.empty()) { + LOG(INFO) << "No Sensor Stats Config present."; + return true; + } LOG(INFO) << "Parse Stats Config for Sensor Temp."; // Parse sensor stats config - if (!ParseStatsInfo(stats_config["Sensors"], sensor_info_map_, - &stats_config_parsed->sensor_stats_info, + if (!ParseStatsInfo(stats_config["Sensors"], sensor_info_map_, sensor_stats_info_parsed, std::numeric_limits<float>::lowest())) { LOG(ERROR) << "Failed to parse sensor temp stats info."; - stats_config_parsed->clear(); + sensor_stats_info_parsed->clear(); + return false; + } + if (!ParseSensorAbnormalStatsConfig(sensor_config["Abnormality"], sensor_info_map_, + abnormal_stats_info_parsed)) { + LOG(ERROR) << "Failed to parse sensor abnormal stats config."; return false; } + return true; +} +bool ParseCoolingDeviceStatsConfig( + const Json::Value &config, + const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_, + StatsInfo<int> *cooling_device_request_info_parsed) { + Json::Value stats_config = config["Stats"]; + if (stats_config.empty()) { + LOG(INFO) << "No Stats Config present."; + return true; + } // Parse cooling device user vote if (stats_config["CoolingDevices"].empty()) { LOG(INFO) << "No cooling device stats present."; return true; } - LOG(INFO) << "Parse Stats Config for Sensor CDev Request."; if (!ParseStatsInfo(stats_config["CoolingDevices"]["RecordVotePerSensor"], - cooling_device_info_map_, &stats_config_parsed->cooling_device_request_info, - -1)) { + cooling_device_info_map_, cooling_device_request_info_parsed, -1)) { LOG(ERROR) << "Failed to parse cooling device user vote stats info."; - stats_config_parsed->clear(); + cooling_device_request_info_parsed->clear(); return false; } return true; } - } // namespace implementation } // namespace thermal } // namespace hardware diff --git a/thermal/utils/thermal_info.h b/thermal/utils/thermal_info.h index 12997f26..843424e6 100644 --- a/thermal/utils/thermal_info.h +++ b/thermal/utils/thermal_info.h @@ -27,6 +27,8 @@ #include <unordered_set> #include <variant> +#include "virtualtemp_estimator/virtualtemp_estimator.h" + namespace aidl { namespace android { namespace hardware { @@ -48,11 +50,12 @@ constexpr int kMaxPowerLogPerLine = 6; constexpr int kMaxStatsResidencyCount = 20; constexpr int kMaxStatsThresholdCount = kMaxStatsResidencyCount - 1; -enum FormulaOption : uint32_t { +enum class FormulaOption : uint32_t { COUNT_THRESHOLD = 0, WEIGHTED_AVG, MAXIMUM, MINIMUM, + USE_ML_MODEL }; template <typename T> @@ -93,12 +96,40 @@ struct StatsConfig { } }; -enum SensorFusionType : uint32_t { +struct TempRangeInfo { + int max_temp_threshold; + int min_temp_threshold; +}; + +struct TempStuckInfo { + int min_polling_count; + std::chrono::milliseconds min_stuck_duration; +}; + +struct AbnormalStatsInfo { + struct SensorsTempRangeInfo { + std::vector<std::string> sensors; + TempRangeInfo temp_range_info; + }; + struct SensorsTempStuckInfo { + std::vector<std::string> sensors; + TempStuckInfo temp_stuck_info; + }; + + std::optional<TempRangeInfo> default_temp_range_info; + std::vector<SensorsTempRangeInfo> sensors_temp_range_infos; + std::optional<TempStuckInfo> default_temp_stuck_info; + std::vector<SensorsTempStuckInfo> sensors_temp_stuck_infos; +}; + +enum class SensorFusionType : uint32_t { SENSOR = 0, ODPM, CONSTANT, }; +std::ostream &operator<<(std::ostream &os, const SensorFusionType &sensor_fusion_type); + struct VirtualSensorInfo { std::vector<std::string> linked_sensors; std::vector<SensorFusionType> linked_sensors_type; @@ -108,6 +139,8 @@ struct VirtualSensorInfo { float offset; std::vector<std::string> trigger_sensors; FormulaOption formula; + std::string vt_estimator_model_file; + std::unique_ptr<::thermal::vtestimator::VirtualTempEstimator> vt_estimator; }; struct VirtualPowerRailInfo { @@ -118,7 +151,7 @@ struct VirtualPowerRailInfo { }; // The method when the ODPM power is lower than threshold -enum ReleaseLogic : uint32_t { +enum class ReleaseLogic : uint32_t { INCREASE = 0, // Increase throttling by step DECREASE, // Decrease throttling by step STEPWISE, // Support both increase and decrease logix @@ -195,7 +228,6 @@ struct CdevInfo { }; struct PowerRailInfo { - std::string rail; int power_sample_count; std::chrono::milliseconds power_sample_delay; std::unique_ptr<VirtualPowerRailInfo> virtual_power_rail_info; @@ -208,10 +240,14 @@ bool ParseCoolingDevice(const Json::Value &config, std::unordered_map<std::string, CdevInfo> *cooling_device_parsed); bool ParsePowerRailInfo(const Json::Value &config, std::unordered_map<std::string, PowerRailInfo> *power_rail_parsed); -bool ParseStatsConfig(const Json::Value &config, - const std::unordered_map<std::string, SensorInfo> &sensor_info_map_, - const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_, - StatsConfig *stats_config); +bool ParseSensorStatsConfig(const Json::Value &config, + const std::unordered_map<std::string, SensorInfo> &sensor_info_map_, + StatsInfo<float> *sensor_stats_info_parsed, + AbnormalStatsInfo *abnormal_stats_info_parsed); +bool ParseCoolingDeviceStatsConfig( + const Json::Value &config, + const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_, + StatsInfo<int> *cooling_device_request_info_parsed); } // namespace implementation } // namespace thermal } // namespace hardware diff --git a/thermal/utils/thermal_stats_helper.cpp b/thermal/utils/thermal_stats_helper.cpp index bbd99542..d4571d90 100644 --- a/thermal/utils/thermal_stats_helper.cpp +++ b/thermal/utils/thermal_stats_helper.cpp @@ -18,7 +18,6 @@ #include <android-base/logging.h> #include <android/binder_manager.h> -#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h> #include <algorithm> #include <numeric> @@ -75,26 +74,49 @@ int calculateThresholdBucket(const std::vector<T> &thresholds, T value) { return bucket; } +void resetCurrentTempStatus(CurrTempStatus *curr_temp_status, float new_temp) { + curr_temp_status->temp = new_temp; + curr_temp_status->start_time = boot_clock::now(); + curr_temp_status->repeat_count = 1; +} + } // namespace bool ThermalStatsHelper::initializeStats( const Json::Value &config, const std::unordered_map<std::string, SensorInfo> &sensor_info_map_, const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_) { - StatsConfig stats_config; - if (!ParseStatsConfig(config, sensor_info_map_, cooling_device_info_map_, &stats_config)) { - LOG(ERROR) << "Failed to parse stats config"; + StatsInfo<float> sensor_stats_info; + AbnormalStatsInfo abnormal_stats_info; + if (!ParseSensorStatsConfig(config, sensor_info_map_, &sensor_stats_info, + &abnormal_stats_info)) { + LOG(ERROR) << "Failed to parse sensor stats config"; + return false; + } + StatsInfo<int> cooling_device_request_info; + if (!ParseCoolingDeviceStatsConfig(config, cooling_device_info_map_, + &cooling_device_request_info)) { + LOG(ERROR) << "Failed to parse cooling device stats config"; + return false; + } + if (!initializeSensorTempStats(sensor_stats_info, sensor_info_map_)) { + LOG(ERROR) << "Failed to initialize sensor temp stats"; return false; } - bool is_initialized_ = - initializeSensorTempStats(stats_config.sensor_stats_info, sensor_info_map_) && - initializeSensorCdevRequestStats(stats_config.cooling_device_request_info, - sensor_info_map_, cooling_device_info_map_); - if (is_initialized_) { - last_total_stats_report_time = boot_clock::now(); - LOG(INFO) << "Thermal Stats Initialized Successfully"; + if (!initializeSensorCdevRequestStats(cooling_device_request_info, sensor_info_map_, + cooling_device_info_map_)) { + LOG(ERROR) << "Failed to initialize sensor cooling device request stats"; + return false; + } + if (!initializeSensorAbnormalityStats(abnormal_stats_info, sensor_info_map_)) { + LOG(ERROR) << "Failed to initialize sensor abnormal stats"; + return false; } - return is_initialized_; + + last_total_stats_report_time = boot_clock::now(); + abnormal_stats_reported_per_update_interval = 0; + LOG(INFO) << "Thermal Stats Initialized Successfully"; + return true; } bool ThermalStatsHelper::initializeSensorCdevRequestStats( @@ -161,7 +183,8 @@ bool ThermalStatsHelper::initializeSensorCdevRequestStats( bool ThermalStatsHelper::initializeSensorTempStats( const StatsInfo<float> &sensor_stats_info, const std::unordered_map<std::string, SensorInfo> &sensor_info_map_) { - std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_); + std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_); + auto &temp_stats_map_ = sensor_stats.temp_stats_map_; const int severity_time_in_state_size = kThrottlingSeverityCount; for (const auto &[sensor, sensor_info] : sensor_info_map_) { // Record by severity @@ -169,7 +192,7 @@ bool ThermalStatsHelper::initializeSensorTempStats( isRecordByDefaultThreshold( sensor_stats_info.record_by_default_threshold_all_or_name_set_, sensor)) { // number of buckets = number of severity - sensor_temp_stats_map_[sensor].stats_by_default_threshold = + temp_stats_map_[sensor].stats_by_default_threshold = StatsRecord(severity_time_in_state_size); LOG(INFO) << "Sensor temp stats on basis of severity initialized for [" << sensor << "]"; @@ -178,8 +201,7 @@ bool ThermalStatsHelper::initializeSensorTempStats( // Record by custom threshold if (sensor_stats_info.record_by_threshold.count(sensor)) { for (const auto &threshold_list : sensor_stats_info.record_by_threshold.at(sensor)) { - sensor_temp_stats_map_[sensor].stats_by_custom_threshold.emplace_back( - threshold_list); + temp_stats_map_[sensor].stats_by_custom_threshold.emplace_back(threshold_list); LOG(INFO) << "Sensor temp stats on basis of threshold initialized for [" << sensor << "]"; } @@ -188,6 +210,54 @@ bool ThermalStatsHelper::initializeSensorTempStats( return true; } +bool ThermalStatsHelper::initializeSensorAbnormalityStats( + const AbnormalStatsInfo &abnormal_stats_info, + const std::unordered_map<std::string, SensorInfo> &sensor_info_map_) { + std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_); + auto &temp_range_info_map_ = sensor_stats.temp_range_info_map_; + for (const auto &sensors_temp_range_info : abnormal_stats_info.sensors_temp_range_infos) { + const auto &temp_range_info_ptr = + std::make_shared<TempRangeInfo>(sensors_temp_range_info.temp_range_info); + for (const auto &sensor : sensors_temp_range_info.sensors) { + temp_range_info_map_[sensor] = temp_range_info_ptr; + } + } + auto &temp_stuck_info_map_ = sensor_stats.temp_stuck_info_map_; + for (const auto &sensors_temp_stuck_info : abnormal_stats_info.sensors_temp_stuck_infos) { + const auto &temp_stuck_info_ptr = + std::make_shared<TempStuckInfo>(sensors_temp_stuck_info.temp_stuck_info); + for (const auto &sensor : sensors_temp_stuck_info.sensors) { + temp_stuck_info_map_[sensor] = temp_stuck_info_ptr; + } + } + const auto &default_temp_range_info_ptr = + abnormal_stats_info.default_temp_range_info + ? std::make_shared<TempRangeInfo>( + abnormal_stats_info.default_temp_range_info.value()) + : nullptr; + const auto &default_temp_stuck_info_ptr = + abnormal_stats_info.default_temp_stuck_info + ? std::make_shared<TempStuckInfo>( + abnormal_stats_info.default_temp_stuck_info.value()) + : nullptr; + for (const auto &sensor_info : sensor_info_map_) { + const auto &sensor = sensor_info.first; + if (default_temp_range_info_ptr && !temp_range_info_map_.count(sensor)) + temp_range_info_map_[sensor] = default_temp_range_info_ptr; + if (default_temp_stuck_info_ptr && !temp_stuck_info_map_.count(sensor)) + temp_stuck_info_map_[sensor] = default_temp_stuck_info_ptr; + } + + for (const auto &sensor_temp_stuck_info : temp_stuck_info_map_) { + sensor_stats.curr_temp_status_map_[sensor_temp_stuck_info.first] = { + .temp = std::numeric_limits<float>::min(), + .start_time = boot_clock::time_point::min(), + .repeat_count = 0, + }; + } + return true; +} + void ThermalStatsHelper::updateStatsRecord(StatsRecord *stats_record, int new_state) { const auto now = boot_clock::now(); const auto cur_state_duration = std::chrono::duration_cast<std::chrono::milliseconds>( @@ -231,11 +301,13 @@ void ThermalStatsHelper::updateSensorCdevRequestStats(std::string_view sensor, void ThermalStatsHelper::updateSensorTempStatsByThreshold(std::string_view sensor, float temperature) { - std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_); - if (!sensor_temp_stats_map_.count(sensor.data())) { + std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_); + verifySensorAbnormality(sensor, temperature); + auto &temp_stats_map_ = sensor_stats.temp_stats_map_; + if (!temp_stats_map_.count(sensor.data())) { return; } - auto &sensor_temp_stats = sensor_temp_stats_map_[sensor.data()]; + auto &sensor_temp_stats = temp_stats_map_[sensor.data()]; for (auto &stats_by_threshold : sensor_temp_stats.stats_by_custom_threshold) { int value = calculateThresholdBucket(stats_by_threshold.thresholds, temperature); if (value != stats_by_threshold.stats_record.cur_state) { @@ -256,11 +328,11 @@ void ThermalStatsHelper::updateSensorTempStatsByThreshold(std::string_view senso void ThermalStatsHelper::updateSensorTempStatsBySeverity(std::string_view sensor, const ThrottlingSeverity &severity) { - std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_); - if (sensor_temp_stats_map_.count(sensor.data()) && - sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.has_value()) { - auto &stats_record = - sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.value(); + std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_); + auto &temp_stats_map_ = sensor_stats.temp_stats_map_; + if (temp_stats_map_.count(sensor.data()) && + temp_stats_map_[sensor.data()].stats_by_default_threshold.has_value()) { + auto &stats_record = temp_stats_map_[sensor.data()].stats_by_default_threshold.value(); int value = static_cast<int>(severity); if (value != stats_record.cur_state) { LOG(VERBOSE) << "Updating sensor stats for sensor: " << sensor.data() @@ -270,6 +342,52 @@ void ThermalStatsHelper::updateSensorTempStatsBySeverity(std::string_view sensor } } +void ThermalStatsHelper::verifySensorAbnormality(std::string_view sensor, float temp) { + LOG(VERBOSE) << "Verify sensor abnormality for " << sensor << " with temp " << temp; + if (sensor_stats.temp_range_info_map_.count(sensor.data())) { + const auto &temp_range_info = sensor_stats.temp_range_info_map_[sensor.data()]; + if (temp < temp_range_info->min_temp_threshold) { + LOG(ERROR) << "Outlier Temperature Detected, sensor: " << sensor.data() + << " temp: " << temp << " < " << temp_range_info->min_temp_threshold; + reportThermalAbnormality(ThermalSensorAbnormalityDetected::EXTREME_LOW_TEMP, sensor, + std::round(temp)); + } else if (temp > temp_range_info->max_temp_threshold) { + LOG(ERROR) << "Outlier Temperature Detected, sensor: " << sensor.data() + << " temp: " << temp << " > " << temp_range_info->max_temp_threshold; + reportThermalAbnormality(ThermalSensorAbnormalityDetected::EXTREME_HIGH_TEMP, sensor, + std::round(temp)); + } + } + if (sensor_stats.temp_stuck_info_map_.count(sensor.data())) { + const auto &temp_stuck_info = sensor_stats.temp_stuck_info_map_[sensor.data()]; + auto &curr_temp_status = sensor_stats.curr_temp_status_map_[sensor.data()]; + LOG(VERBOSE) << "Current Temp Status: temp=" << curr_temp_status.temp + << " repeat_count=" << curr_temp_status.repeat_count + << " start_time=" << curr_temp_status.start_time.time_since_epoch().count(); + if (std::fabs(curr_temp_status.temp - temp) <= kPrecisionThreshold) { + curr_temp_status.repeat_count++; + if (temp_stuck_info->min_polling_count <= curr_temp_status.repeat_count) { + auto time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>( + boot_clock::now() - curr_temp_status.start_time); + if (temp_stuck_info->min_stuck_duration <= time_elapsed_ms) { + LOG(ERROR) << "Stuck Temperature Detected, sensor: " << sensor.data() + << " temp: " << temp << " repeated " + << temp_stuck_info->min_polling_count << " times for " + << time_elapsed_ms.count() << "ms"; + if (reportThermalAbnormality(ThermalSensorAbnormalityDetected::SENSOR_STUCK, + sensor, std::round(temp))) { + // reset current status to verify for sensor stuck with start time as + // current polling + resetCurrentTempStatus(&curr_temp_status, temp); + } + } + } + } else { + resetCurrentTempStatus(&curr_temp_status, temp); + } + } +} + int ThermalStatsHelper::reportStats() { const auto curTime = boot_clock::now(); const auto since_last_total_stats_update_ms = @@ -290,13 +408,14 @@ int ThermalStatsHelper::reportStats() { int count_failed_reporting = reportAllSensorTempStats(stats_client) + reportAllSensorCdevRequestStats(stats_client); last_total_stats_report_time = curTime; + abnormal_stats_reported_per_update_interval = 0; return count_failed_reporting; } int ThermalStatsHelper::reportAllSensorTempStats(const std::shared_ptr<IStats> &stats_client) { int count_failed_reporting = 0; - std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_); - for (auto &[sensor, temp_stats] : sensor_temp_stats_map_) { + std::unique_lock<std::shared_mutex> _lock(sensor_stats_mutex_); + for (auto &[sensor, temp_stats] : sensor_stats.temp_stats_map_) { for (size_t threshold_set_idx = 0; threshold_set_idx < temp_stats.stats_by_custom_threshold.size(); threshold_set_idx++) { auto &stats_by_threshold = temp_stats.stats_by_custom_threshold[threshold_set_idx]; @@ -445,6 +564,40 @@ std::vector<int64_t> ThermalStatsHelper::processStatsRecordForReporting(StatsRec return stats_residency; } +bool ThermalStatsHelper::reportThermalAbnormality( + const ThermalSensorAbnormalityDetected::AbnormalityType &type, std::string_view name, + std::optional<int> reading) { + const auto value_str = reading.has_value() ? std::to_string(reading.value()) : "undefined"; + if (abnormal_stats_reported_per_update_interval >= kMaxAbnormalLoggingPerUpdateInterval) { + LOG(ERROR) << "Thermal abnormal atom logging rate limited for " << name.data() + << " with value " << value_str; + return true; + } + const std::shared_ptr<IStats> stats_client = getStatsService(); + if (!stats_client) { + LOG(ERROR) << "Unable to get AIDL Stats service"; + return false; + } + std::vector<VendorAtomValue> values(3); + values[ThermalSensorAbnormalityDetected::kTypeFieldNumber - kVendorAtomOffset] = + VendorAtomValue::make<VendorAtomValue::intValue>(type); + values[ThermalSensorAbnormalityDetected::kSensorFieldNumber - kVendorAtomOffset] = + VendorAtomValue::make<VendorAtomValue::stringValue>(name); + if (reading.has_value()) { + values[ThermalSensorAbnormalityDetected::kTempFieldNumber - kVendorAtomOffset] = + VendorAtomValue::make<VendorAtomValue::intValue>(reading.value()); + } + if (!reportAtom(stats_client, PixelAtoms::Atom::kThermalSensorAbnormalityDetected, + std::move(values))) { + LOG(ERROR) << "Failed to log thermal abnormal atom for " << name.data() << " with value " + << value_str; + return false; + } + LOG(INFO) << "Thermal abnormality reported for " << name.data() << " with value " << value_str; + abnormal_stats_reported_per_update_interval++; + return true; +} + bool ThermalStatsHelper::reportAtom(const std::shared_ptr<IStats> &stats_client, const int32_t &atom_id, std::vector<VendorAtomValue> &&values) { LOG(VERBOSE) << "Reporting thermal stats for atom_id " << atom_id; @@ -467,7 +620,7 @@ StatsRecord ThermalStatsHelper::restoreStatsRecordOnFailure( } std::unordered_map<std::string, SensorTempStats> ThermalStatsHelper::GetSensorTempStatsSnapshot() { - auto sensor_temp_stats_snapshot = sensor_temp_stats_map_; + auto sensor_temp_stats_snapshot = sensor_stats.temp_stats_map_; for (auto &sensor_temp_stats_pair : sensor_temp_stats_snapshot) { for (auto &temp_stats : sensor_temp_stats_pair.second.stats_by_custom_threshold) { // update the last unclosed entry and start new record with same state diff --git a/thermal/utils/thermal_stats_helper.h b/thermal/utils/thermal_stats_helper.h index ef2f811f..fde9e9c7 100644 --- a/thermal/utils/thermal_stats_helper.h +++ b/thermal/utils/thermal_stats_helper.h @@ -19,6 +19,7 @@ #include <aidl/android/frameworks/stats/IStats.h> #include <aidl/android/hardware/thermal/Temperature.h> #include <android-base/chrono_utils.h> +#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h> #include <chrono> #include <shared_mutex> @@ -37,10 +38,18 @@ namespace implementation { using aidl::android::frameworks::stats::IStats; using aidl::android::frameworks::stats::VendorAtomValue; using ::android::base::boot_clock; +using ::android::hardware::google::pixel::PixelAtoms::ThermalSensorAbnormalityDetected; using std::chrono::system_clock; using SystemTimePoint = std::chrono::time_point<std::chrono::system_clock>; +// Number of abnormal atoms to be logged per kUpdateIntervalMs +constexpr int kMaxAbnormalLoggingPerUpdateInterval = 20; constexpr int kMaxStatsReportingFailCount = 3; +// Proto messages are 1-indexed and VendorAtom field numbers start at 2, so +// store everything in the values array at the index of the field number +// -2. +constexpr int kVendorAtomOffset = 2; +constexpr float kPrecisionThreshold = 1e-4; struct StatsRecord { int cur_state; /* temperature / cdev state at current time */ @@ -98,6 +107,23 @@ struct SensorTempStats : ThermalStats<float> { SystemTimePoint min_temp_timestamp = SystemTimePoint::min(); }; +struct CurrTempStatus { + float temp; + boot_clock::time_point start_time; + int repeat_count; +}; + +struct SensorStats { + // Temperature residency stats for each sensor being watched + std::unordered_map<std::string, SensorTempStats> temp_stats_map_; + // Min, Max Temp threshold info for each sensor being monitored + std::unordered_map<std::string, std::shared_ptr<TempRangeInfo>> temp_range_info_map_; + // Temperature Stuck info for each sensor being monitored + std::unordered_map<std::string, std::shared_ptr<TempStuckInfo>> temp_stuck_info_map_; + // Current temperature status for each sensor being monitored for stuck + std::unordered_map<std::string, CurrTempStatus> curr_temp_status_map_; +}; + class ThermalStatsHelper { public: ThermalStatsHelper() = default; @@ -122,6 +148,8 @@ class ThermalStatsHelper { * >0, count represents the number of stats failed to report. */ int reportStats(); + bool reportThermalAbnormality(const ThermalSensorAbnormalityDetected::AbnormalityType &type, + std::string_view name, std::optional<int> reading); // Get a snapshot of Thermal Stats Sensor Map till that point in time std::unordered_map<std::string, SensorTempStats> GetSensorTempStatsSnapshot(); // Get a snapshot of Thermal Stats Sensor Map till that point in time @@ -132,10 +160,9 @@ class ThermalStatsHelper { static constexpr std::chrono::milliseconds kUpdateIntervalMs = std::chrono::duration_cast<std::chrono::milliseconds>(24h); boot_clock::time_point last_total_stats_report_time = boot_clock::time_point::min(); - - mutable std::shared_mutex sensor_temp_stats_map_mutex_; - // Temperature stats for each sensor being watched - std::unordered_map<std::string, SensorTempStats> sensor_temp_stats_map_; + int abnormal_stats_reported_per_update_interval = 0; + mutable std::shared_mutex sensor_stats_mutex_; + SensorStats sensor_stats; mutable std::shared_mutex sensor_cdev_request_stats_map_mutex_; // userVote request stat for the sensor to the corresponding cdev (sensor -> cdev -> // StatsRecord) @@ -149,7 +176,11 @@ class ThermalStatsHelper { const StatsInfo<int> &request_stats_info, const std::unordered_map<std::string, SensorInfo> &sensor_info_map_, const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_); + bool initializeSensorAbnormalityStats( + const AbnormalStatsInfo &abnormal_stats_info, + const std::unordered_map<std::string, SensorInfo> &sensor_info_map_); void updateStatsRecord(StatsRecord *stats_record, int new_state); + void verifySensorAbnormality(std::string_view sensor, float temperature); int reportAllSensorTempStats(const std::shared_ptr<IStats> &stats_client); bool reportSensorTempStats(const std::shared_ptr<IStats> &stats_client, std::string_view sensor, const SensorTempStats &sensor_temp_stats, StatsRecord *stats_record); diff --git a/thermal/utils/thermal_throttling.cpp b/thermal/utils/thermal_throttling.cpp index 57dca169..295f76f6 100644 --- a/thermal/utils/thermal_throttling.cpp +++ b/thermal/utils/thermal_throttling.cpp @@ -187,7 +187,8 @@ bool ThermalThrottling::registerThermalThrottling( // return power budget based on PID algo float ThermalThrottling::updatePowerBudget(const Temperature &temp, const SensorInfo &sensor_info, std::chrono::milliseconds time_elapsed_ms, - ThrottlingSeverity curr_severity) { + ThrottlingSeverity curr_severity, + const bool max_throttling) { float p = 0, d = 0; float power_budget = std::numeric_limits<float>::max(); bool target_changed = false; @@ -210,6 +211,11 @@ float ThermalThrottling::updatePowerBudget(const Temperature &temp, const Sensor // Compute PID float err = sensor_info.hot_thresholds[target_state] - temp.value; + + if (max_throttling && err <= 0) { + return sensor_info.throttling_info->min_alloc_power[target_state]; + } + p = err * (err < 0 ? sensor_info.throttling_info->k_po[target_state] : sensor_info.throttling_info->k_pu[target_state]); @@ -317,7 +323,8 @@ bool ThermalThrottling::allocatePowerToCdev( const Temperature &temp, const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms, const std::unordered_map<std::string, PowerStatus> &power_status_map, - const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) { + const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map, + const bool max_throttling) { float total_weight = 0; float last_updated_avg_power = NAN; float allocated_power = 0; @@ -329,7 +336,8 @@ bool ThermalThrottling::allocatePowerToCdev( std::string log_buf; std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_); - auto total_power_budget = updatePowerBudget(temp, sensor_info, time_elapsed_ms, curr_severity); + auto total_power_budget = + updatePowerBudget(temp, sensor_info, time_elapsed_ms, curr_severity, max_throttling); const auto &profile = thermal_throttling_status_map_[temp.name].profile; if (sensor_info.throttling_info->excluded_power_info_map.size()) { @@ -466,30 +474,34 @@ bool ThermalThrottling::allocatePowerToCdev( thermal_throttling_status_map_[temp.name].pid_cdev_request_map.at( binded_cdev_info_pair.first); - if (binded_cdev_info_pair.second.max_release_step != - std::numeric_limits<int>::max() && - (power_data_invalid || cdev_power_adjustment > 0)) { - if (!power_data_invalid && curr_cdev_vote < max_cdev_vote) { - cdev_power_budget = cdev_info.state2power[curr_cdev_vote]; - LOG(VERBOSE) << temp.name << "'s " << binded_cdev_info_pair.first - << " vote: " << curr_cdev_vote - << " is lower than max cdev vote: " << max_cdev_vote; - } else { - const auto target_state = std::max( - curr_cdev_vote - binded_cdev_info_pair.second.max_release_step, 0); - cdev_power_budget = - std::min(cdev_power_budget, cdev_info.state2power[target_state]); + if (!max_throttling) { + if (binded_cdev_info_pair.second.max_release_step != + std::numeric_limits<int>::max() && + (power_data_invalid || cdev_power_adjustment > 0)) { + if (!power_data_invalid && curr_cdev_vote < max_cdev_vote) { + cdev_power_budget = cdev_info.state2power[curr_cdev_vote]; + LOG(VERBOSE) << temp.name << "'s " << binded_cdev_info_pair.first + << " vote: " << curr_cdev_vote + << " is lower than max cdev vote: " << max_cdev_vote; + } else { + const auto target_state = std::max( + curr_cdev_vote - binded_cdev_info_pair.second.max_release_step, + 0); + cdev_power_budget = std::min(cdev_power_budget, + cdev_info.state2power[target_state]); + } } - } - if (binded_cdev_info_pair.second.max_throttle_step != - std::numeric_limits<int>::max() && - (power_data_invalid || cdev_power_adjustment < 0)) { - const auto target_state = std::min( - curr_cdev_vote + binded_cdev_info_pair.second.max_throttle_step, - cdev_info.max_state); - cdev_power_budget = - std::max(cdev_power_budget, cdev_info.state2power[target_state]); + if (binded_cdev_info_pair.second.max_throttle_step != + std::numeric_limits<int>::max() && + (power_data_invalid || cdev_power_adjustment < 0)) { + const auto target_state = std::min( + curr_cdev_vote + binded_cdev_info_pair.second.max_throttle_step, + binded_cdev_info_pair.second + .cdev_ceiling[static_cast<size_t>(curr_severity)]); + cdev_power_budget = + std::max(cdev_power_budget, cdev_info.state2power[target_state]); + } } thermal_throttling_status_map_[temp.name].pid_power_budget_map.at( @@ -671,7 +683,8 @@ void ThermalThrottling::thermalThrottlingUpdate( const Temperature &temp, const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms, const std::unordered_map<std::string, PowerStatus> &power_status_map, - const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) { + const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map, + const bool max_throttling) { if (!thermal_throttling_status_map_.count(temp.name)) { return; } @@ -682,7 +695,7 @@ void ThermalThrottling::thermalThrottlingUpdate( if (thermal_throttling_status_map_[temp.name].pid_power_budget_map.size()) { if (!allocatePowerToCdev(temp, sensor_info, curr_severity, time_elapsed_ms, - power_status_map, cooling_device_info_map)) { + power_status_map, cooling_device_info_map, max_throttling)) { LOG(ERROR) << "Sensor " << temp.name << " PID request cdev failed"; // Clear the CDEV request if the power budget is failed to be allocated for (auto &pid_cdev_request_pair : @@ -773,6 +786,7 @@ void ThermalThrottling::computeCoolingDevicesRequest( } request_state = std::min(request_state, cdev_ceiling); if (cdev_request_pair.second != request_state) { + ATRACE_INT((atrace_prefix + std::string("-final_request")).c_str(), request_state); if (updateCdevMaxRequestAndNotifyIfChange(cdev_name, cdev_request_pair.second, request_state)) { cooling_devices_to_update->emplace_back(cdev_name); diff --git a/thermal/utils/thermal_throttling.h b/thermal/utils/thermal_throttling.h index 76c41e0f..cac7f8dc 100644 --- a/thermal/utils/thermal_throttling.h +++ b/thermal/utils/thermal_throttling.h @@ -62,17 +62,12 @@ class ThermalThrottling { ThermalThrottling(const ThermalThrottling &) = delete; void operator=(const ThermalThrottling &) = delete; - // Check if the thermal throttling profile need to be switched - void parseProfileProperty(std::string_view sensor_name, const SensorInfo &sensor_info); // Clear throttling data void clearThrottlingData(std::string_view sensor_name, const SensorInfo &sensor_info); // Register map for throttling algo bool registerThermalThrottling( std::string_view sensor_name, const std::shared_ptr<ThrottlingInfo> &throttling_info, const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map); - // Register map for throttling release algo - bool registerThrottlingReleaseToWatch(std::string_view sensor_name, std::string_view cdev_name, - const BindedCdevInfo &binded_cdev_info); // Get throttling status map const std::unordered_map<std::string, ThermalThrottlingStatus> &GetThermalThrottlingStatusMap() const { @@ -84,7 +79,8 @@ class ThermalThrottling { const Temperature &temp, const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms, const std::unordered_map<std::string, PowerStatus> &power_status_map, - const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map); + const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map, + const bool max_throttling = false); // Compute the throttling target from all the sensors' request void computeCoolingDevicesRequest(std::string_view sensor_name, const SensorInfo &sensor_info, @@ -95,10 +91,12 @@ class ThermalThrottling { bool getCdevMaxRequest(std::string_view cdev_name, int *max_state); private: + // Check if the thermal throttling profile need to be switched + void parseProfileProperty(std::string_view sensor_name, const SensorInfo &sensor_info); // PID algo - get the total power budget float updatePowerBudget(const Temperature &temp, const SensorInfo &sensor_info, std::chrono::milliseconds time_elapsed_ms, - ThrottlingSeverity curr_severity); + ThrottlingSeverity curr_severity, const bool max_throttling); // PID algo - return the power number from excluded power rail list float computeExcludedPower(const SensorInfo &sensor_info, @@ -111,7 +109,8 @@ class ThermalThrottling { const Temperature &temp, const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms, const std::unordered_map<std::string, PowerStatus> &power_status_map, - const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map); + const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map, + const bool max_throttling); // PID algo - map the target throttling state according to the power budget void updateCdevRequestByPower( std::string sensor_name, diff --git a/thermal/utils/thermal_watcher.cpp b/thermal/utils/thermal_watcher.cpp index d8bc92e1..f8ca2c2b 100644 --- a/thermal/utils/thermal_watcher.cpp +++ b/thermal/utils/thermal_watcher.cpp @@ -401,7 +401,7 @@ void ThermalWatcher::registerFilesToWatchNl(const std::set<std::string> &sensors bool ThermalWatcher::startWatchingDeviceFiles() { if (cb_) { - auto ret = this->run("FileWatcherThread", ::android::PRIORITY_HIGHEST); + auto ret = this->run("FileWatcherThread", -10); if (ret != ::android::NO_ERROR) { LOG(ERROR) << "ThermalWatcherThread start fail"; return false; diff --git a/thermal/virtualtemp_estimator/virtualtemp_estimator.cpp b/thermal/virtualtemp_estimator/virtualtemp_estimator.cpp new file mode 100644 index 00000000..2dc2185c --- /dev/null +++ b/thermal/virtualtemp_estimator/virtualtemp_estimator.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2023 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 "virtualtemp_estimator.h" + +#include <android-base/logging.h> +#include <dlfcn.h> + +#include <vector> + +namespace thermal { +namespace vtestimator { + +void VirtualTempEstimator::LoadTFLiteWrapper() { + if (!data_) { + LOG(ERROR) << "data_ is nullptr during LoadTFLiteWrapper"; + return; + } + + std::unique_lock<std::mutex> lock(data_->tflite_methods.mutex_); + + void *mLibHandle = dlopen("/vendor/lib64/libthermal_tflite_wrapper.so", 0); + if (mLibHandle == nullptr) { + LOG(ERROR) << "Could not load libthermal_tflite_wrapper library with error: " << dlerror(); + return; + } + + data_->tflite_methods.create = + reinterpret_cast<tflitewrapper_create>(dlsym(mLibHandle, "Create")); + if (!data_->tflite_methods.create) { + LOG(ERROR) << "Could not link and cast tflitewrapper_create with error: " << dlerror(); + } + + data_->tflite_methods.init = reinterpret_cast<tflitewrapper_init>(dlsym(mLibHandle, "Init")); + if (!data_->tflite_methods.init) { + LOG(ERROR) << "Could not link and cast tflitewrapper_init with error: " << dlerror(); + } + + data_->tflite_methods.invoke = + reinterpret_cast<tflitewrapper_invoke>(dlsym(mLibHandle, "Invoke")); + if (!data_->tflite_methods.invoke) { + LOG(ERROR) << "Could not link and cast tflitewrapper_invoke with error: " << dlerror(); + } + + data_->tflite_methods.destroy = + reinterpret_cast<tflitewrapper_destroy>(dlsym(mLibHandle, "Destroy")); + if (!data_->tflite_methods.destroy) { + LOG(ERROR) << "Could not link and cast tflitewrapper_destroy with error: " << dlerror(); + } +} + +VirtualTempEstimator::VirtualTempEstimator(size_t num_input_samples) { + data_ = std::make_unique<VirtualTempEstimatorTFLiteData>(num_input_samples); + LoadTFLiteWrapper(); +} + +VirtualTempEstimator::~VirtualTempEstimator() { + LOG(INFO) << "VirtualTempEstimator destructor"; +} + +VtEstimatorStatus VirtualTempEstimator::Initialize(const char *model_path) { + LOG(INFO) << "Initialize VirtualTempEstimator\n"; + + if (!data_) { + LOG(ERROR) << "data_ is nullptr during Initialize\n"; + return kVtEstimatorInitFailed; + } + + std::unique_lock<std::mutex> lock(data_->tflite_methods.mutex_); + + if (!model_path) { + LOG(ERROR) << "Invalid model_path:" << model_path; + return kVtEstimatorInvalidArgs; + } + + if (!data_->input_buffer || !data_->input_buffer_size) { + LOG(ERROR) << "Invalid data_ members " << model_path + << " input_buffer: " << data_->input_buffer + << " input_buffer_size: " << data_->input_buffer_size; + return kVtEstimatorInitFailed; + } + + if (!data_->tflite_methods.create || !data_->tflite_methods.init || + !data_->tflite_methods.invoke || !data_->tflite_methods.destroy) { + LOG(ERROR) << "Invalid tflite methods"; + return kVtEstimatorInitFailed; + } + + data_->tflite_wrapper = data_->tflite_methods.create(kNumInputTensors, kNumOutputTensors); + if (!data_->tflite_wrapper) { + LOG(ERROR) << "Failed to create tflite wrapper"; + return kVtEstimatorInitFailed; + } + + int ret = data_->tflite_methods.init(data_->tflite_wrapper, model_path); + if (ret) { + LOG(ERROR) << "Failed to Init tflite_wrapper for " << model_path << " (ret: )" << ret + << ")"; + return kVtEstimatorInitFailed; + } + + data_->is_initialized = true; + data_->model_path = model_path; + + LOG(INFO) << "Successfully initialized VirtualTempEstimator for " << model_path; + return kVtEstimatorOk; +} + +VtEstimatorStatus VirtualTempEstimator::Estimate(const std::vector<float> &thermistors, + float *output) { + if (!data_) { + LOG(ERROR) << "data_ is nullptr during Estimate\n"; + return kVtEstimatorInitFailed; + } + + std::unique_lock<std::mutex> lock(data_->tflite_methods.mutex_); + + if (!data_->is_initialized) { + LOG(ERROR) << "data_ not initialized for " << data_->model_path; + return kVtEstimatorInitFailed; + } + + if ((thermistors.size() != data_->input_buffer_size) || (!output)) { + LOG(ERROR) << "Invalid args for " << data_->model_path + << " thermistors.size(): " << thermistors.size() + << " input_buffer_size: " << data_->input_buffer_size << " output: " << output; + return kVtEstimatorInvalidArgs; + } + + // copy input data into input tensors + for (size_t i = 0; i < data_->input_buffer_size; ++i) { + data_->input_buffer[i] = thermistors[i]; + } + + int ret = data_->tflite_methods.invoke(data_->tflite_wrapper, data_->input_buffer, + data_->input_buffer_size, output, 1); + if (ret) { + LOG(ERROR) << "Failed to Invoke for " << data_->model_path << " (ret: " << ret << ")"; + return kVtEstimatorInvokeFailed; + } + + return kVtEstimatorOk; +} + +} // namespace vtestimator +} // namespace thermal diff --git a/thermal/virtualtemp_estimator/virtualtemp_estimator.h b/thermal/virtualtemp_estimator/virtualtemp_estimator.h new file mode 100644 index 00000000..0ae37f9e --- /dev/null +++ b/thermal/virtualtemp_estimator/virtualtemp_estimator.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 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. + */ +#pragma once + +#include <vector> + +#include "virtualtemp_estimator_data.h" + +namespace thermal { +namespace vtestimator { + +enum VtEstimatorStatus { + kVtEstimatorOk = 0, + kVtEstimatorInvalidArgs = 1, + kVtEstimatorInitFailed = 2, + kVtEstimatorInvokeFailed = 3, + kVtEstimatorUnSupported = 4, +}; + +// Class to estimate virtual temperature based on a model +class VirtualTempEstimator { + public: + // Implicit copy-move headers. + VirtualTempEstimator(const VirtualTempEstimator &) = delete; + VirtualTempEstimator(VirtualTempEstimator &&) = default; + VirtualTempEstimator &operator=(const VirtualTempEstimator &) = delete; + VirtualTempEstimator &operator=(VirtualTempEstimator &&) = default; + + VirtualTempEstimator(size_t num_input_samples); + ~VirtualTempEstimator(); + + // Initializes the model provided by model_path. + VtEstimatorStatus Initialize(const char *model_path); + + // Performs the inference on the loaded VT model. + // Output of the inference is returned in output argument + VtEstimatorStatus Estimate(const std::vector<float> &thermistors, float *output); + + private: + void LoadTFLiteWrapper(); + std::unique_ptr<VirtualTempEstimatorTFLiteData> data_; +}; + +} // namespace vtestimator +} // namespace thermal diff --git a/thermal/virtualtemp_estimator/virtualtemp_estimator_data.h b/thermal/virtualtemp_estimator/virtualtemp_estimator_data.h new file mode 100644 index 00000000..935c753b --- /dev/null +++ b/thermal/virtualtemp_estimator/virtualtemp_estimator_data.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 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 <cstddef> +#include <mutex> +#include <string> + +#pragma once + +namespace thermal { +namespace vtestimator { + +// Current version only supports single input/output tensors +constexpr int kNumInputTensors = 1; +constexpr int kNumOutputTensors = 1; + +typedef void *(*tflitewrapper_create)(int num_input_tensors, int num_output_tensors); +typedef bool (*tflitewrapper_init)(void *handle, const char *model_path); +typedef bool (*tflitewrapper_invoke)(void *handle, float *input_samples, int num_input_samples, + float *output_samples, int num_output_samples); +typedef void (*tflitewrapper_destroy)(void *handle); + +struct TFLiteWrapperMethods { + tflitewrapper_create create; + tflitewrapper_init init; + tflitewrapper_invoke invoke; + tflitewrapper_destroy destroy; + mutable std::mutex mutex_; +}; + +struct VirtualTempEstimatorTFLiteData { + VirtualTempEstimatorTFLiteData(size_t num_input_samples) { + input_buffer = new float[num_input_samples]; + input_buffer_size = num_input_samples; + is_initialized = false; + tflite_wrapper = nullptr; + + tflite_methods.create = nullptr; + tflite_methods.init = nullptr; + tflite_methods.invoke = nullptr; + tflite_methods.destroy = nullptr; + } + + void *tflite_wrapper; + float *input_buffer; + size_t input_buffer_size; + std::string model_path; + TFLiteWrapperMethods tflite_methods; + bool is_initialized; + + ~VirtualTempEstimatorTFLiteData() { + if (tflite_wrapper && tflite_methods.destroy) { + tflite_methods.destroy(tflite_wrapper); + } + + if (input_buffer) { + delete input_buffer; + } + } +}; + +} // namespace vtestimator +} // namespace thermal diff --git a/thermal/virtualtemp_estimator/virtualtemp_estimator_test.cpp b/thermal/virtualtemp_estimator/virtualtemp_estimator_test.cpp new file mode 100644 index 00000000..fde99977 --- /dev/null +++ b/thermal/virtualtemp_estimator/virtualtemp_estimator_test.cpp @@ -0,0 +1,416 @@ +// Copyright (C) 2023 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. + +/** + *@file virtualtemp_estimator_test.cc + * Test application to verify virtualtemp estimator + * + */ +// Test application to run and verify virtualtemp estimator interface unit tests + +#include "virtualtemp_estimator.h" + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/parsedouble.h> +#include <android-base/properties.h> +#include <android-base/strings.h> +#include <cutils/properties.h> +#include <cutils/trace.h> +#include <json/reader.h> +#include <json/value.h> +#include <json/writer.h> +#include <log/log.h> +#include <malloc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#include <climits> +#include <fstream> +#include <iostream> + +constexpr std::string_view kDefaultModel("/vendor/etc/vt_estimation_model.tflite"); +constexpr std::string_view kConfigProperty("vendor.thermal.config"); +constexpr std::string_view kConfigDefaultFileName("thermal_info_config.json"); +constexpr int kmillion = 1000000; +constexpr int klog_interval_usec = 10 * kmillion; + +static inline unsigned long get_elapsed_time_usec(struct timeval start, struct timeval end) { + unsigned long elapsed_time = (end.tv_sec - start.tv_sec) * kmillion; + elapsed_time += (end.tv_usec - start.tv_usec); + + return elapsed_time; +} + +static std::vector<std::string> get_input_combination(std::string_view thermal_config_path) { + std::vector<std::string> result; + std::string json_doc; + if (!android::base::ReadFileToString(thermal_config_path.data(), &json_doc)) { + std::cout << "Failed to read JSON config from " << thermal_config_path.data(); + return result; + } + + Json::Value root; + Json::CharReaderBuilder reader_builder; + std::unique_ptr<Json::CharReader> reader(reader_builder.newCharReader()); + std::string errorMessage; + + if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) { + std::cout << "Failed to parse JSON config: " << errorMessage; + return result; + } + + Json::Value sensors = root["Sensors"]; + std::cout << "Sensors size: " << sensors.size() << std::endl; + + for (Json::Value::ArrayIndex i = 0; i < sensors.size(); ++i) { + const std::string &name = sensors[i]["Name"].asString(); + if (name == "VIRTUAL-SKIN-MODEL") { + Json::Value values = sensors[i]["Combination"]; + if (values.size() == 0) { + return result; + } + + std::cout << "Combination : ["; + for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) { + result.push_back(values[j].asString()); + std::cout << result.back() << ", "; + } + std::cout << "]" << std::endl; + } + } + + return result; +} + +static int run_random_input_inference(std::string_view model_path, + std::string_view thermal_config_path, int min_inference_count, + int inference_delay_sec) { + float output; + unsigned long prev_log_time = 0; + thermal::vtestimator::VtEstimatorStatus ret; + std::vector<std::string> input_combination = get_input_combination(thermal_config_path.data()); + int input_size = input_combination.size(); + thermal::vtestimator::VirtualTempEstimator vt_estimator_(input_size); + + std::cout << "Initialize estimator\n"; + ret = vt_estimator_.Initialize(model_path.data()); + if (ret != thermal::vtestimator::kVtEstimatorOk) { + std::cout << "Failed to Initialize estimator (ret: " << ret << ")\n"; + return -1; + } + + struct timeval start_loop_time; + int inference_count = 0; + unsigned long max_inference_time = 0, min_inference_time = ULONG_MAX; + unsigned long sum_inference_time = 0; + float avg_inference_time = 0; + std::vector<unsigned long> inference_times; + + gettimeofday(&start_loop_time, nullptr); + do { + struct timeval begin, end; + std::vector<float> thermistors; + + // preparing random inputs with starting temperature between 20C to 40C + int r = 20 + std::rand() % 20; + for (int i = 0; i < input_size; ++i) { + thermistors.push_back(r + i); + } + + gettimeofday(&begin, nullptr); + ret = vt_estimator_.Estimate(thermistors, &output); + gettimeofday(&end, nullptr); + if (ret != thermal::vtestimator::kVtEstimatorOk) { + std::cout << "Failed to run estimator (ret: " << ret << ")\n"; + return -1; + } + + unsigned long inference_time_usec = get_elapsed_time_usec(begin, end); + + inference_count++; + max_inference_time = std::max(max_inference_time, inference_time_usec); + min_inference_time = std::min(min_inference_time, inference_time_usec); + sum_inference_time += inference_time_usec; + avg_inference_time = sum_inference_time / inference_count; + inference_times.push_back(inference_time_usec); + + unsigned long elapsed_time = get_elapsed_time_usec(start_loop_time, end); + if (elapsed_time - prev_log_time >= klog_interval_usec) { + std::cout << "elapsed_time_sec: " << elapsed_time / kmillion + << " inference_count: " << inference_count + << " min_inference_time: " << min_inference_time + << " max_inference_time: " << max_inference_time + << " avg_inference_time: " << avg_inference_time << std::endl; + prev_log_time = elapsed_time; + } + + if (inference_delay_sec) + sleep(inference_delay_sec); + } while (inference_count < min_inference_count); + + std::cout << "\n\ntotal inference count: " << inference_count << std::endl; + std::cout << "total inference time: " << sum_inference_time << std::endl; + std::cout << "avg_inference_time: " << avg_inference_time << std::endl; + std::cout << "min_inference_time: " << min_inference_time << std::endl; + std::cout << "max_inference_time: " << max_inference_time << std::endl; + + std::sort(inference_times.begin(), inference_times.end()); + std::cout << "\n\n"; + std::cout << "p50: " << inference_times[inference_count * 0.5] << std::endl; + std::cout << "p90: " << inference_times[inference_count * 0.9] << std::endl; + + return 0; +} + +static int run_single_inference(std::string_view model_path, char *input) { + if (!input) { + std::cout << "input is nullptr" << std::endl; + return -1; + } + + std::vector<float> thermistors; + char *ip = input; + char *saveptr; + + std::cout << "Parsing thermistors from input string: "; + ip = strtok_r(ip, " ", &saveptr); + while (ip) { + float thermistor_value; + + if (sscanf(ip, "%f", &thermistor_value) != 1) { + std::cout << "inputs parsing failed"; + } + + std::cout << thermistor_value << " "; + thermistors.push_back(thermistor_value); + + ip = strtok_r(NULL, " ", &saveptr); + } + std::cout << std::endl; + std::cout << "thermistors.size(): " << thermistors.size() << std::endl; + + float output; + thermal::vtestimator::VtEstimatorStatus ret; + thermal::vtestimator::VirtualTempEstimator vt_estimator_(thermistors.size()); + + std::cout << "Initialize estimator\n"; + ret = vt_estimator_.Initialize(model_path.data()); + if (ret != thermal::vtestimator::kVtEstimatorOk) { + std::cout << "Failed to Initialize estimator (ret: " << ret << ")\n"; + return -1; + } + + std::cout << "run estimator\n"; + ret = vt_estimator_.Estimate(thermistors, &output); + if (ret != thermal::vtestimator::kVtEstimatorOk) { + std::cout << "Failed to run estimator (ret: " << ret << ")\n"; + return -1; + } + + std::cout << "output: " << output << std::endl; + return 0; +} + +static int run_batch_process(std::string_view model_path, std::string_view thermal_config_path, + const char *input_file, const char *output_file) { + if (!input_file || !output_file) { + std::cout << "input and output files required for batch process\n"; + return -1; + } + + std::cout << "get_input_combination(): "; + std::vector<std::string> input_combination = get_input_combination(thermal_config_path.data()); + if (input_combination.size() == 0) { + LOG(ERROR) << "Invalid input_combination"; + return -1; + } + + thermal::vtestimator::VtEstimatorStatus ret; + thermal::vtestimator::VirtualTempEstimator vt_estimator_(input_combination.size()); + + std::cout << "Initialize estimator\n"; + ret = vt_estimator_.Initialize(model_path.data()); + if (ret != thermal::vtestimator::kVtEstimatorOk) { + std::cout << "Failed to Initialize estimator (ret: " << ret << ")\n"; + return -1; + } + + std::string json_doc; + if (!android::base::ReadFileToString(input_file, &json_doc)) { + LOG(ERROR) << "Failed to read JSON config from " << input_file; + return -1; + } + Json::Value root; + Json::CharReaderBuilder reader_builder; + std::unique_ptr<Json::CharReader> reader(reader_builder.newCharReader()); + std::string errorMessage; + + if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) { + LOG(ERROR) << "Failed to parse JSON config: " << errorMessage; + return -1; + } + + std::cout << "Number of testcases " << root.size() << std::endl; + + for (auto const &testcase_name : root.getMemberNames()) { + if (testcase_name == "Metadata") { + continue; + } + + Json::Value testcase = root[testcase_name]; + Json::Value model_vt_outputs; + int loop_count = testcase[input_combination[0]].size(); + + std::cout << "tc: " << testcase_name << " count: " << loop_count << std::endl; + for (int i = 0; i < loop_count; ++i) { + std::vector<float> model_inputs; + float model_output; + int num_inputs = input_combination.size(); + + for (int j = 0; j < num_inputs; ++j) { + std::string input_name = input_combination[j]; + std::string value_str = testcase[input_name][std::to_string(i)].asString(); + + std::cout << "tc[" << testcase_name << "] entry[" << i << "] input[" << input_name + << "] value_str[" << value_str << "]\n"; + + float value; + if (android::base::ParseFloat(value_str, &value) == false) { + std::cout << "Failed to parse value_str : " << value_str << " to float\n"; + } + + model_inputs.push_back(value); + } + + ret = vt_estimator_.Estimate(model_inputs, &model_output); + if (ret != thermal::vtestimator::kVtEstimatorOk) { + std::cout << "Failed to run estimator (ret: " << ret << ")\n"; + return -1; + } + + model_vt_outputs[std::to_string(i)] = std::to_string(model_output); + } + + testcase["model_vt"] = model_vt_outputs; + root[testcase_name] = testcase; + std::cout << "completed testcase_name: " << testcase_name << std::endl; + } + + Json::StreamWriterBuilder writer_builder; + writer_builder["indentation"] = ""; + std::unique_ptr<Json::StreamWriter> writer(writer_builder.newStreamWriter()); + std::ofstream output_stream(output_file, std::ofstream::out); + writer->write(root, &output_stream); + + return 0; +} + +void print_usage() { + std::string message = "usage: \n"; + message += "-m : input mode ("; + message += "0: single inference "; + message += "1: json input file "; + message += "2: generate random inputs) \n"; + message += "-p : path to model file \n"; + message += "-t : path to thermal config file \n"; + message += "-i : input samples (mode 0), path to input file (mode 1) \n"; + message += "-o : output file (mode 1) \n"; + message += "-d : delay between inferences in seconds (mode 2) \n"; + message += "-c : inference count (mode 2)"; + + std::cout << message << std::endl; +} + +int main(int argc, char *argv[]) { + int c, mode = -1; + char *input = nullptr, *output = nullptr; + std::string model_path, thermal_config_path; + int min_inference_count = -1; + int inference_delay_sec = 0; + + while ((c = getopt(argc, argv, "hm:p:i:c:o:d:t:")) != -1) switch (c) { + case 'm': + mode = atoi(optarg); + std::cout << "mode: " << mode << std::endl; + break; + case 'p': + model_path = optarg; + std::cout << "model_path: " << model_path << std::endl; + break; + case 't': + thermal_config_path = optarg; + std::cout << "thermal_config_path: " << thermal_config_path << std::endl; + break; + case 'i': + input = optarg; + std::cout << "input: " << input << std::endl; + break; + case 'o': + output = optarg; + std::cout << "output: " << output << std::endl; + break; + case 'c': + min_inference_count = atoi(optarg); + std::cout << "min_inference_count: " << min_inference_count << std::endl; + break; + case 'd': + inference_delay_sec = atoi(optarg); + std::cout << "inference_delay_sec : " << inference_delay_sec << std::endl; + break; + case 'h': + print_usage(); + return 0; + default: + std::cout << "unsupported option " << c << std::endl; + abort(); + } + + if (model_path.empty()) { + model_path = kDefaultModel; + std::cout << "Using default model_path: " << model_path << std::endl; + } + + if (thermal_config_path.empty()) { + thermal_config_path = + "/vendor/etc/" + + android::base::GetProperty(kConfigProperty.data(), kConfigDefaultFileName.data()); + std::cout << "Using default thermal config: " << thermal_config_path << std::endl; + } + + int ret = -1; + switch (mode) { + case 0: + ret = run_single_inference(model_path, input); + break; + case 1: + ret = run_batch_process(model_path, thermal_config_path, input, output); + break; + case 2: + ret = run_random_input_inference(model_path, thermal_config_path, min_inference_count, + inference_delay_sec); + break; + default: + std::cout << "unsupported mode" << std::endl; + print_usage(); + break; + } + + std::cout << "Exiting" << std::endl; + fflush(stdout); + + return ret; +} diff --git a/usb/Android.bp b/usb/Android.bp index 6fec9965..1e05ef2d 100644 --- a/usb/Android.bp +++ b/usb/Android.bp @@ -20,7 +20,7 @@ package { cc_library_static { name: "libpixelusb", - vendor_available: true, + vendor: true, export_include_dirs: [ "hidl/include", "include", @@ -45,12 +45,17 @@ cc_library_static { "libhidlbase", "libutils", "libbinder_ndk", + "pixelatoms-cpp", "android.hardware.usb.gadget@1.0", "android.hardware.thermal@1.0", "android.hardware.thermal@2.0", "android.hardware.thermal-V1-ndk" ], + export_shared_lib_headers: [ + "pixelatoms-cpp", + ], + static_libs: [ "libthermalutils", ] @@ -58,7 +63,7 @@ cc_library_static { cc_library_static { name: "libpixelusb-aidl", - vendor_available: true, + vendor: true, export_include_dirs: [ "aidl/include", "include", @@ -81,12 +86,17 @@ cc_library_static { "libbinder", "libcutils", "libutils", + "pixelatoms-cpp", "android.hardware.usb.gadget-V1-ndk", "android.hardware.thermal@1.0", "android.hardware.thermal@2.0", "android.hardware.thermal-V1-ndk" ], + export_shared_lib_headers: [ + "pixelatoms-cpp", + ], + static_libs: [ "libthermalutils", ] @@ -94,6 +104,7 @@ cc_library_static { cc_fuzz { name: "libpixelusb_gadgetutils_fuzzer", + vendor: true, srcs:[ "UsbGadgetUtils_fuzz.cpp" diff --git a/usb/CommonUtils.cpp b/usb/CommonUtils.cpp index 3a396d7e..6e0f08c9 100644 --- a/usb/CommonUtils.cpp +++ b/usb/CommonUtils.cpp @@ -35,12 +35,37 @@ namespace google { namespace pixel { namespace usb { +// Android metrics requires number of elements in any repeated field cannot exceed 127 elements +constexpr int kWestworldRepeatedFieldSizeLimit = 127; + using ::android::base::GetProperty; using ::android::base::SetProperty; using ::android::base::WriteStringToFile; using ::std::chrono::microseconds; using ::std::chrono::steady_clock; using ::std::literals::chrono_literals::operator""ms; +using android::hardware::google::pixel::PixelAtoms::VendorUsbDataSessionEvent; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDataRole_USB_ROLE_DEVICE; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDataRole_USB_ROLE_HOST; +using android::hardware::google::pixel::PixelAtoms::VendorUsbDataSessionEvent_UsbDeviceState; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_ADDRESSED; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_ATTACHED; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_CONFIGURED; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_DEFAULT; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_NOT_ATTACHED; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_POWERED; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_SUSPENDED; +using android::hardware::google::pixel::PixelAtoms:: + VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_UNKNOWN; int addEpollFd(const base::unique_fd &epfd, const base::unique_fd &fd) { struct epoll_event event; @@ -154,6 +179,52 @@ bool resetGadgetCommon() { return true; } +static VendorUsbDataSessionEvent_UsbDeviceState stringToUsbDeviceStateProto( + const std::string &state) { + if (state == "not attached\n") { + return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_NOT_ATTACHED; + } else if (state == "attached\n") { + return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_ATTACHED; + } else if (state == "powered\n") { + return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_POWERED; + } else if (state == "default\n") { + return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_DEFAULT; + } else if (state == "addressed\n") { + return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_ADDRESSED; + } else if (state == "configured\n") { + return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_CONFIGURED; + } else if (state == "suspended\n") { + return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_SUSPENDED; + } else { + return VendorUsbDataSessionEvent_UsbDeviceState_USB_STATE_UNKNOWN; + } +} + +void BuildVendorUsbDataSessionEvent(bool is_host, boot_clock::time_point currentTime, + boot_clock::time_point startTime, + std::vector<std::string> *states, + std::vector<boot_clock::time_point> *timestamps, + VendorUsbDataSessionEvent *event) { + if (is_host) { + event->set_usb_role(VendorUsbDataSessionEvent_UsbDataRole_USB_ROLE_HOST); + } else { + event->set_usb_role(VendorUsbDataSessionEvent_UsbDataRole_USB_ROLE_DEVICE); + } + + for (int i = 0; i < states->size() && i < kWestworldRepeatedFieldSizeLimit; i++) { + event->add_usb_states(stringToUsbDeviceStateProto(states->at(i))); + } + + for (int i = 0; i < timestamps->size() && i < kWestworldRepeatedFieldSizeLimit; i++) { + event->add_elapsed_time_ms( + std::chrono::duration_cast<std::chrono::milliseconds>(timestamps->at(i) - startTime) + .count()); + } + + event->set_duration_ms( + std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count()); +} + } // namespace usb } // namespace pixel } // namespace google diff --git a/usb/include/pixelusb/CommonUtils.h b/usb/include/pixelusb/CommonUtils.h index 91244f81..dbd59c63 100644 --- a/usb/include/pixelusb/CommonUtils.h +++ b/usb/include/pixelusb/CommonUtils.h @@ -17,7 +17,9 @@ #ifndef HARDWARE_GOOGLE_PIXEL_USB_UTILSCOMMON_H_ #define HARDWARE_GOOGLE_PIXEL_USB_UTILSCOMMON_H_ +#include <android-base/chrono_utils.h> #include <android-base/unique_fd.h> +#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h> #include <string> @@ -57,6 +59,9 @@ constexpr char kUvcEnabled[] = "ro.usb.uvc.enabled"; #define FUNCTION_PATH CONFIG_PATH FUNCTION_NAME #define RNDIS_PATH FUNCTIONS_PATH "gsi.rndis" +using ::android::base::boot_clock; +using android::hardware::google::pixel::PixelAtoms::VendorUsbDataSessionEvent; + // Adds the given fd to the epollfd(epfd). int addEpollFd(const ::android::base::unique_fd &epfd, const ::android::base::unique_fd &fd); // Extracts vendor functions from the vendor init properties. @@ -69,6 +74,12 @@ int linkFunction(const char *function, int index); bool setVidPidCommon(const char *vid, const char *pid); // Pulls down USB gadget. Returns true on success, false on failure bool resetGadgetCommon(); +void BuildVendorUsbDataSessionEvent(bool is_host, boot_clock::time_point currentTime, + boot_clock::time_point startTime, + std::vector<std::string> *states, + std::vector<boot_clock::time_point> *timestamps, + VendorUsbDataSessionEvent *event); + } // namespace usb } // namespace pixel } // namespace google diff --git a/vibrator/common/StatsBase.cpp b/vibrator/common/StatsBase.cpp index 68b8a2dc..a0402b4e 100644 --- a/vibrator/common/StatsBase.cpp +++ b/vibrator/common/StatsBase.cpp @@ -129,21 +129,22 @@ void StatsBase::uploadDiagnostics() { uploadErrorAtoms(); } -void StatsBase::waitForStatsService() const { +std::shared_ptr<IStats> StatsBase::waitForStatsService() const { STATS_TRACE("waitForStatsService()"); if (!AServiceManager_isDeclared(kStatsInstanceName.c_str())) { ALOGE("IStats service '%s' is not registered.", kStatsInstanceName.c_str()); - return; + return nullptr; } ALOGI("Waiting for IStats service '%s' to come up.", kStatsInstanceName.c_str()); - const std::shared_ptr<IStats> statsClient = IStats::fromBinder( + std::shared_ptr<IStats> client = IStats::fromBinder( ndk::SpAIBinder(AServiceManager_waitForService(kStatsInstanceName.c_str()))); - if (!statsClient) { + if (!client) { ALOGE("Failed to get IStats service '%s'.", kStatsInstanceName.c_str()); - return; + return nullptr; } ALOGI("IStats service online."); + return client; } void StatsBase::runReporterThread() { @@ -152,8 +153,6 @@ void StatsBase::runReporterThread() { auto nextUpload = clock::now() + UPLOAD_INTERVAL; auto status = std::cv_status::no_timeout; - waitForStatsService(); - while (!mTerminateReporterThread) { drainAtomQueue(); { @@ -178,15 +177,14 @@ void StatsBase::drainAtomQueue() { std::swap(mAtomQueue, tempQueue); } - std::shared_ptr<IStats> statsClient = IStats::fromBinder( - ndk::SpAIBinder(AServiceManager_waitForService(kStatsInstanceName.c_str()))); - if (!statsClient) { + std::shared_ptr<IStats> client = waitForStatsService(); + if (!client) { ALOGE("Failed to get IStats service. Atoms are dropped."); return; } for (const VendorAtom &atom : tempQueue) { - reportVendorAtom(statsClient, atom); + reportVendorAtom(client, atom); } } diff --git a/vibrator/common/StatsBase.h b/vibrator/common/StatsBase.h index 2e16220a..2b0d9867 100644 --- a/vibrator/common/StatsBase.h +++ b/vibrator/common/StatsBase.h @@ -31,6 +31,7 @@ namespace frameworks { namespace stats { class VendorAtom; +class IStats; } // namespace stats } // namespace frameworks @@ -45,6 +46,7 @@ namespace vibrator { class StatsBase { public: using VendorAtom = ::aidl::android::frameworks::stats::VendorAtom; + using IStats = ::aidl::android::frameworks::stats::IStats; StatsBase(const std::string &instance); ~StatsBase(); @@ -65,7 +67,7 @@ class StatsBase { void runReporterThread(); void reportVendorAtomAsync(const VendorAtom &atom); void uploadDiagnostics(); - void waitForStatsService() const; + std::shared_ptr<IStats> waitForStatsService() const; void drainAtomQueue(); void uploadPlaycountAtoms(); diff --git a/vibrator/cs40l25/Hardware.h b/vibrator/cs40l25/Hardware.h index e2222798..7f06141b 100644 --- a/vibrator/cs40l25/Hardware.h +++ b/vibrator/cs40l25/Hardware.h @@ -198,9 +198,7 @@ class HwCal : public Vibrator::HwCal, private HwCalBase { return true; } bool isChirpEnabled() override { - bool value; - getProperty("chirp.enabled", &value, false); - return value; + return utils::getProperty("persist.vendor.vibrator.hal.chirp.enabled", false); } void debug(int fd) override { HwCalBase::debug(fd); } }; diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h index 50e2cb66..12ddb59b 100644 --- a/vibrator/cs40l26/Hardware.h +++ b/vibrator/cs40l26/Hardware.h @@ -297,6 +297,9 @@ class HwApi : public Vibrator::HwApi, private HwApiBase { *haptic_pcm = NULL; return false; } + bool isPassthroughI2sHapticSupported() override { + return utils::getProperty("ro.vendor.vibrator.hal.passthrough_i2s_supported", false); + } bool uploadOwtEffect(const uint8_t *owtData, const uint32_t numBytes, struct ff_effect *effect, uint32_t *outEffectIndex, int *status) override { ATRACE_NAME(__func__); @@ -516,9 +519,7 @@ class HwCal : public Vibrator::HwCal, private HwCalBase { return true; } bool isChirpEnabled() override { - bool value; - getProperty("chirp.enabled", &value, false); - return value; + return utils::getProperty("persist.vendor.vibrator.hal.chirp.enabled", false); } bool getSupportedPrimitives(uint32_t *value) override { return getProperty("supported_primitives", value, (uint32_t)0); diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp index 8c303ffd..c2fd73a5 100644 --- a/vibrator/cs40l26/Vibrator.cpp +++ b/vibrator/cs40l26/Vibrator.cpp @@ -496,6 +496,8 @@ Vibrator::Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal, mHwApi->setF0CompEnable(mHwCal->isF0CompEnabled()); mHwApi->setRedcCompEnable(mHwCal->isRedcCompEnabled()); + mHasPassthroughHapticDevice = mHwApi->isPassthroughI2sHapticSupported(); + mIsUnderExternalControl = false; mIsChirpEnabled = mHwCal->isChirpEnabled(); @@ -534,7 +536,7 @@ ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) { int32_t ret = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK | IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY | IVibrator::CAP_GET_Q_FACTOR; - if (hasHapticAlsaDevice()) { + if (mHasPassthroughHapticDevice || hasHapticAlsaDevice()) { ret |= IVibrator::CAP_EXTERNAL_CONTROL; } else { mStatsApi->logError(kAlsaFailError); @@ -671,16 +673,21 @@ ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) { ATRACE_NAME("Vibrator::setExternalControl"); setGlobalAmplitude(enabled); - if (mHasHapticAlsaDevice || mConfigHapticAlsaDeviceDone || hasHapticAlsaDevice()) { - if (!mHwApi->setHapticPcmAmp(&mHapticPcm, enabled, mCard, mDevice)) { - mStatsApi->logError(kHwApiError); - ALOGE("Failed to %s haptic pcm device: %d", (enabled ? "enable" : "disable"), mDevice); + if (!mHasPassthroughHapticDevice) { + if (mHasHapticAlsaDevice || mConfigHapticAlsaDeviceDone || + hasHapticAlsaDevice()) { + if (!mHwApi->setHapticPcmAmp(&mHapticPcm, enabled, mCard, + mDevice)) { + mStatsApi->logError(kHwApiError); + ALOGE("Failed to %s haptic pcm device: %d", + (enabled ? "enable" : "disable"), mDevice); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } else { + mStatsApi->logError(kAlsaFailError); + ALOGE("No haptics ALSA device"); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } - } else { - mStatsApi->logError(kAlsaFailError); - ALOGE("No haptics ALSA device"); - return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } mIsUnderExternalControl = enabled; @@ -1569,6 +1576,15 @@ binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) { dprintf(fd, " cs40l26-calib.bin: %s\n", ver.c_str()); verFile.close(); } + verFile.open("/vendor/firmware/cs40l26-dvl.bin", verBinFileMode); + if (verFile.is_open()) { + verFile.seekg(36); + getline(verFile, ver); + ver = ver.substr(0, ver.find('\0') + 1); + ver = ver.substr(ver.rfind('\\') + 1); + dprintf(fd, " cs40l26-dvl.bin: %s\n", ver.c_str()); + verFile.close(); + } dprintf(fd, "\n"); diff --git a/vibrator/cs40l26/Vibrator.h b/vibrator/cs40l26/Vibrator.h index c4a992ff..b2b75150 100644 --- a/vibrator/cs40l26/Vibrator.h +++ b/vibrator/cs40l26/Vibrator.h @@ -100,6 +100,9 @@ class Vibrator : public BnVibrator { // Set haptics PCM amplifier before triggering audio haptics feature virtual bool setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device) = 0; + // Checks to see if the passthrough i2s haptics feature is supported by + // the target device. + virtual bool isPassthroughI2sHapticSupported() = 0; // Set OWT waveform for compose or compose PWLE request virtual bool uploadOwtEffect(const uint8_t *owtData, const uint32_t numBytes, struct ff_effect *effect, uint32_t *outEffectIndex, @@ -256,6 +259,7 @@ class Vibrator : public BnVibrator { int mCard; int mDevice; bool mHasHapticAlsaDevice{false}; + bool mHasPassthroughHapticDevice; bool mIsUnderExternalControl; float mLongEffectScale = 1.0; bool mIsChirpEnabled; diff --git a/vibrator/cs40l26/tests/mocks.h b/vibrator/cs40l26/tests/mocks.h index 53c9a045..0837938c 100644 --- a/vibrator/cs40l26/tests/mocks.h +++ b/vibrator/cs40l26/tests/mocks.h @@ -45,6 +45,7 @@ class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi { MOCK_METHOD2(setFFPlay, bool(int8_t index, bool value)); MOCK_METHOD2(getHapticAlsaDevice, bool(int *card, int *device)); MOCK_METHOD4(setHapticPcmAmp, bool(struct pcm **haptic_pcm, bool enable, int card, int device)); + MOCK_METHOD0(isPassthroughI2sHapticSupported, bool()); MOCK_METHOD5(uploadOwtEffect, bool(const uint8_t *owtData, const uint32_t numBytes, struct ff_effect *effect, uint32_t *outEffectIndex, int *status)); diff --git a/vibrator/cs40l26/tests/test-vibrator.cpp b/vibrator/cs40l26/tests/test-vibrator.cpp index 254a6bdb..0b150955 100644 --- a/vibrator/cs40l26/tests/test-vibrator.cpp +++ b/vibrator/cs40l26/tests/test-vibrator.cpp @@ -303,6 +303,7 @@ class VibratorTest : public Test { EXPECT_CALL(*mMockApi, setMinOnOffInterval(_)).Times(times); EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).Times(times); EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, _, _, _)).Times(times); + EXPECT_CALL(*mMockApi, isPassthroughI2sHapticSupported()).Times(times); EXPECT_CALL(*mMockApi, enableDbc()).Times(times); EXPECT_CALL(*mMockApi, debug(_)).Times(times); @@ -339,9 +340,11 @@ TEST_F(VibratorTest, Constructor) { std::unique_ptr<MockApi> mockapi; std::unique_ptr<MockCal> mockcal; std::unique_ptr<MockStats> mockstats; - std::string f0Val = std::to_string(std::rand()); - std::string redcVal = std::to_string(std::rand()); - std::string qVal = std::to_string(std::rand()); + int min_val = 0xC8000; + int max_val = 0x7FC000; + std::string f0Val = std::to_string(std::rand() % (max_val - min_val + 1) + min_val); + std::string redcVal = std::to_string(std::rand() % (max_val - min_val + 1) + min_val); + std::string qVal = std::to_string(std::rand() % (max_val - min_val + 1) + min_val); uint32_t calVer; uint32_t supportedPrimitivesBits = 0x0; Expectation volGet; @@ -384,6 +387,7 @@ TEST_F(VibratorTest, Constructor) { EXPECT_CALL(*mMockCal, isRedcCompEnabled()).WillOnce(Return(true)); EXPECT_CALL(*mMockApi, setRedcCompEnable(true)).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, isPassthroughI2sHapticSupported()).WillOnce(Return(false)); EXPECT_CALL(*mMockCal, isChirpEnabled()).WillOnce(Return(true)); EXPECT_CALL(*mMockCal, getSupportedPrimitives(_)) .InSequence(supportedPrimitivesSeq) |