diff --git a/dumpstate/dump_power.cpp b/dumpstate/dump_power.cpp index 14b9d91..9dcbc43 100644 --- a/dumpstate/dump_power.cpp +++ b/dumpstate/dump_power.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include +#include #include #include #include @@ -23,11 +25,13 @@ #include #include #include +#include +#include #include #include #include "DumpstateUtil.h" - +#include "dump_power.h" void printTitle(const char *msg) { printf("\n------ %s ------\n", msg); @@ -609,6 +613,371 @@ void dumpMitigation() { } } +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; +} + +void readLPFPowerBitResolutions(const char *odpmDir, double *bitResolutions) { + char path[128]; + + for (int i = 0; i < METER_CHANNEL_MAX; i++) { + snprintf(path, 128, "%s/in_power%d_scale", odpmDir, i); + if (!readSysfsToDouble(path, &bitResolutions[i])) { + /* using large negative value to notify this odpm value is invalid */ + bitResolutions[i] = -1000; + } + } +} + +void readLPFChannelNames(const char *odpmEnabledRailsPath, char **lpfChannelNames) { + char *line = NULL; + size_t len = 0; + ssize_t read; + + FILE *fp = fopen(odpmEnabledRailsPath, "r"); + if (fp == NULL) + 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 char *mainPmicNamePath, const char *mainPmicName) { + std::string content; + int ret = 0; + + if (!android::base::ReadFileToString(mainPmicNamePath, &content)) { + printf("Failed to open %s, set device0 as main pmic\n", mainPmicNamePath); + return ret; + } + + if (strcmp(content.c_str(), mainPmicName) != 0) { + ret = 1; + } + + return ret; +} + +void freeLpfChannelNames(char **lpfChannelNames) { + for (int c = 0; c < METER_CHANNEL_MAX; c++){ + free(lpfChannelNames[c]); + } +} + +void printUTC(struct timespec time, const char *stat) { + char timeBuff[128]; + if (strlen(stat) > 0) { + printf("%s: ", stat); + } + std::strftime(timeBuff, sizeof(timeBuff), "%m/%d/%Y_%H:%M:%S", std::localtime(&time.tv_sec)); + printf("%s.%lu",timeBuff, time.tv_nsec); +} + +void printUTC(timeval time, const char *stat) { + char timeBuff[128]; + if (strlen(stat) > 0) { + printf("%s: ", stat); + } + std::strftime(timeBuff, sizeof(timeBuff), "%m/%d/%Y_%H:%M:%S", std::localtime(&time.tv_sec)); + /* convert usec to nsec */ + printf("%s.%lu000",timeBuff, time.tv_usec); +} + +void printODPMChannelSummary(std::vector &odpmData, + double *lpfBitResolutions, char **lpfChannelNames) { + std::vector validTime; + std::vector instPower[METER_CHANNEL_MAX]; + std::vector instPowerMax; + std::vector instPowerMin; + std::vector instPowerList; + std::vector instPowerStd; + + if (odpmData.size() == 0) + return; + + /* initial Max, Min, Sum for sorting*/ + 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 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_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++) { + printf("%s Max: %.2f Min: %.2f Avg: %.2f Std: %.2f\n", lpfChannelNames[c], + instPowerMax[c].value, + instPowerMin[c].value, + instPowerList[c] / n, + instPowerStd[c]); + } + printf("\n"); + + /* print time */ + printf("time "); + for (int i = 0; i < n; i++) { + printUTC(instPower[0][i].time, ""); + printf(" "); + } + printf("\n"); + + /* print instant power by channel */ + for (int c = 0; c < METER_CHANNEL_MAX; c++){ + printf("%s ", lpfChannelNames[c]); + for (int i = 0; i < n; i++) { + printf("%.2f ", instPower[c][i].value); + } + printf("\n"); + } + + printf("\n"); +} + +void printLatency(const struct BrownoutStatsExtend *brownoutStatsExtend) { + /* received latency */ + 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 */ + 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 */ + 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; + + printf("recvLatency %ld.%09ld\n", recvLatency.tv_sec, recvLatency.tv_nsec); + printf("dumpLatency %ld.%09ld\n", dumpLatency.tv_sec, dumpLatency.tv_nsec); + printf("totalLatency %ld.%09ld\n\n", totalLatency.tv_sec, totalLatency.tv_nsec); + +} + +void printBRStatsSummary(const struct BrownoutBinaryStatsConfig cfg, + const struct BrownoutStatsExtend *brownoutStatsExtend) { + int mainPmicID = 0; + mainPmicID = getMainPmicID(cfg.pmic[mainPmicID].PmicNamePath, cfg.pmic[mainPmicID].PmicName); + int subPmicID = !mainPmicID; + double mainLpfBitResolutions[METER_CHANNEL_MAX]; + double subLpfBitResolutions[METER_CHANNEL_MAX]; + char *mainLpfChannelNames[METER_CHANNEL_MAX]; + char *subLpfChannelNames[METER_CHANNEL_MAX]; + std::vector odpmData[2]; + + /* print out the triggered_time in first dump */ + printUTC(brownoutStatsExtend[0].brownoutStats.triggered_time, "triggered_time"); + printf("\n"); + printf("triggered_idx: %d\n", brownoutStatsExtend[0].brownoutStats.triggered_idx); + printLatency(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]); + } + } + } + + /* read odpm resolutions and channel names */ + readLPFPowerBitResolutions(cfg.pmic[mainPmicID].OdpmDir, mainLpfBitResolutions); + readLPFPowerBitResolutions(cfg.pmic[subPmicID].OdpmDir, subLpfBitResolutions); + readLPFChannelNames(cfg.pmic[mainPmicID].OdpmEnabledRailsPath, mainLpfChannelNames); + readLPFChannelNames(cfg.pmic[subPmicID].OdpmEnabledRailsPath, subLpfChannelNames); + + printODPMChannelSummary(odpmData[mainPmicID], mainLpfBitResolutions, mainLpfChannelNames); + printODPMChannelSummary(odpmData[subPmicID], subLpfBitResolutions, subLpfChannelNames); + + freeLpfChannelNames(mainLpfChannelNames); + freeLpfChannelNames(subLpfChannelNames); +} + +void printOdpmInstantData(struct odpm_instant_data odpmInstantData) { + if (odpmInstantData.time.tv_sec == 0 && + odpmInstantData.time.tv_nsec == 0) { + return; + } + printUTC(odpmInstantData.time, ""); + for (int i = 0; i < METER_CHANNEL_MAX; i++){ + printf("%d ", odpmInstantData.value[i]); + } + printf("\n"); +} + +void printBRStats(struct BrownoutStatsExtend *brownoutStatsExtend) { + printUTC(brownoutStatsExtend->brownoutStats.triggered_time, "triggered_time"); + printf("\n"); + printf("triggered_idx: %d\n", brownoutStatsExtend->brownoutStats.triggered_idx); + + printf("main_odpm_instant_data: \n"); + for (int d = 0; d < DATA_LOGGING_LEN; d++) { + printOdpmInstantData(brownoutStatsExtend->brownoutStats.main_odpm_instant_data[d]); + } + printf("sub_odpm_instant_data: \n"); + for (int d = 0; d < DATA_LOGGING_LEN; d++) { + printOdpmInstantData(brownoutStatsExtend->brownoutStats.sub_odpm_instant_data[d]); + } + + printf("\n"); + printf("fvp_stats:\n"); + printf("%s\n\n", brownoutStatsExtend->fvpStats); + printf("pcie_modem:\n"); + printf("%s\n\n", brownoutStatsExtend->pcieModem); + printf("pcie_wifi:\n"); + printf("%s\n\n", brownoutStatsExtend->pcieWifi); + for (int i = 0; i < STATS_MAX_SIZE; i++) { + if (strlen(brownoutStatsExtend->numericStats[i].name) > 0) + printf("%s: %d\n", brownoutStatsExtend->numericStats[i].name, + brownoutStatsExtend->numericStats[i].value); + } + printUTC(brownoutStatsExtend->eventReceivedTime, "eventReceivedTime"); + printf("\n"); + printUTC(brownoutStatsExtend->dumpTime, "dumpTime"); + printf("\n"); + printf("eventIdx: %d\n", brownoutStatsExtend->eventIdx); +} + +void dumpBRStats(const struct BrownoutBinaryStatsConfig cfg, + const char* logPath, const char *title) { + struct BrownoutStatsExtend brownoutStatsExtend[DUMP_TIMES]; + size_t memSize = sizeof(struct BrownoutStatsExtend) * DUMP_TIMES; + + int fd = open(logPath, O_RDONLY); + if (fd < 0) { + printf("Failed to open %s\n", logPath); + return; + } + + size_t logFileSize = lseek(fd, 0, SEEK_END); + if (memSize != logFileSize) { + printf("Invalid log size!\n"); + printf("BrownoutStatsExtend size: %lu\n", memSize); + printf("%s size: %lu\n", title, logFileSize); + close(fd); + return; + } + + char *logFileAddr = (char *) mmap(NULL, logFileSize, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + + memcpy(&brownoutStatsExtend, logFileAddr, logFileSize); + munmap(logFileAddr, logFileSize); + + printTitle(title); + printBRStatsSummary(cfg, brownoutStatsExtend); + + printf("== RAW ==\n"); + for (int i = 0; i < DUMP_TIMES; i++) { + printf("== Dump %d ==\n", i); + printBRStats(&brownoutStatsExtend[i]); + printf("=============\n\n"); + } +} + +void dumpMitigationBinaryStats(const struct BrownoutBinaryStatsConfig cfg) { + if (access(cfg.StoringPath, F_OK) != 0) { + printf("Failed to access %s\n", cfg.StoringPath); + } else { + dumpBRStats(cfg, cfg.StoringPath, cfg.ThismealFileName); + } + if (access(cfg.BackupPath, F_OK) != 0) { + printf("Failed to access %s\n", cfg.StoringPath); + } else { + dumpBRStats(cfg, cfg.BackupPath, cfg.LastmealFileName); + } +} + void dumpMitigationStats() { int ret; const char *directory = "/sys/devices/virtual/pmic/mitigation/last_triggered_count/"; @@ -908,6 +1277,29 @@ void dumpIrqDurationCounts() { } int main() { + const struct BrownoutBinaryStatsConfig cfg = { + .StoringPath = "/data/vendor/mitigation/thismeal.bin", + .BackupPath = "/data/vendor/mitigation/lastmeal.bin", + .ThismealFileName = "thismeal.bin", + .LastmealFileName = "lastmeal.bin", + .pmic = { + /* Main Pmic */ + { + .OdpmDir = "/sys/bus/iio/devices/iio:device0", + .OdpmEnabledRailsPath = "/sys/bus/iio/devices/iio:device0/enabled_rails", + .PmicNamePath = "/sys/bus/iio/devices/iio:device0/name", + .PmicName = "s2mpg14-odpm\n", + }, + /* Sub Pmic */ + { + .OdpmDir = "/sys/bus/iio/devices/iio:device1", + .OdpmEnabledRailsPath = "/sys/bus/iio/devices/iio:device1/enabled_rails", + .PmicNamePath = "/sys/bus/iio/devices/iio:device1/name", + .PmicName = "s2mpg15-odpm\n", + }, + }, + }; + dumpPowerStatsTimes(); dumpAcpmStats(); dumpPowerSupplyStats(); @@ -927,5 +1319,6 @@ int main() { dumpMitigationStats(); dumpMitigationDirs(); dumpIrqDurationCounts(); + dumpMitigationBinaryStats(cfg); } diff --git a/dumpstate/dump_power.h b/dumpstate/dump_power.h new file mode 100644 index 0000000..4c83e8b --- /dev/null +++ b/dumpstate/dump_power.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#ifndef __DUMP_POWER_H +#define __DUMP_POWER_H + +#include "uapi/brownout_stats.h" + +/* BrownoutBinaryStatsConfig */ +#define NSEC_PER_SEC 1000000000L +#define DUMP_TIMES 12 +#define FVP_STATS_SIZE 4096 +#define UP_DOWN_LINK_SIZE 512 +#define STAT_NAME_SIZE 48 +#define STATS_MAX_SIZE 64 + +struct PmicSpecific { + const char *const OdpmDir; + const char *const OdpmEnabledRailsPath; + const char *const PmicNamePath; + const char *const PmicName; +}; + +struct numericStat { + char name[STAT_NAME_SIZE]; + int value; +}; + +struct BrownoutStatsExtend { + struct brownout_stats brownoutStats; + char fvpStats[FVP_STATS_SIZE]; + char pcieModem[UP_DOWN_LINK_SIZE]; + char pcieWifi[UP_DOWN_LINK_SIZE]; + struct numericStat numericStats[STATS_MAX_SIZE]; + timeval eventReceivedTime; + timeval dumpTime; + unsigned int eventIdx; +}; + +struct OdpmInstantPower { + struct timespec time; + double value; +}; + +struct BrownoutBinaryStatsConfig { + const char *const StoringPath; + const char *const BackupPath; + const char *const ThismealFileName; + const char *const LastmealFileName; + const std::vector pmic; +}; + +#endif /* __DUMP_POWER_H */ diff --git a/dumpstate/uapi/brownout_stats.h b/dumpstate/uapi/brownout_stats.h new file mode 100644 index 0000000..b4085c0 --- /dev/null +++ b/dumpstate/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]; +}; +static_assert(sizeof(struct brownout_stats) <= PAGE_SIZE); + +#endif /* __BROWNOUT_STATS_H */