diff options
author | Nathan Scott <nathans@redhat.com> | 2020-11-17 18:12:38 +1100 |
---|---|---|
committer | Nathan Scott <nathans@redhat.com> | 2020-11-18 10:17:33 +1100 |
commit | ea9622b8c9444d92007f24fc54597f83c498f11d (patch) | |
tree | 13cb45909d2c873b7b09ec59580d6a8db4676b0b /linux/Platform.c | |
parent | e3af8d0d0851dd6ce25446a7f9a99e2127795a78 (diff) |
Merge individual Battery.[ch] files into Platform.[ch]
Consistent with everything else involving platform-specific
calls from core htop code.
Diffstat (limited to 'linux/Platform.c')
-rw-r--r-- | linux/Platform.c | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/linux/Platform.c b/linux/Platform.c index 97d713ae..f5baa24c 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -17,6 +17,8 @@ in the source distribution for its full text. #include <limits.h> #include <math.h> #include <stdio.h> +#include <errno.h> +#include <unistd.h> #include "BatteryMeter.h" #include "ClockMeter.h" @@ -293,6 +295,7 @@ void Platform_setZfsCompressedArcValues(Meter* this) { ZfsCompressedArcMeter_readStats(this, &(lpl->zfs)); } + char* Platform_getProcessEnv(pid_t pid) { char procname[128]; xSnprintf(procname, sizeof(procname), PROCDIR "/%d/environ", pid); @@ -544,3 +547,323 @@ bool Platform_getNetworkIO(unsigned long int* bytesReceived, *packetsTransmitted = packetsTransmittedSum; return true; } + +// Linux battery reading by Ian P. Hands (iphands@gmail.com, ihands@redhat.com). + +#define MAX_BATTERIES 64 +#define PROC_BATTERY_DIR PROCDIR "/acpi/battery" +#define PROC_POWERSUPPLY_DIR PROCDIR "/acpi/ac_adapter" +#define SYS_POWERSUPPLY_DIR "/sys/class/power_supply" + +// ---------------------------------------- +// READ FROM /proc +// ---------------------------------------- + +static unsigned long int parseBatInfo(const char* fileName, const unsigned short int lineNum, const unsigned short int wordNum) { + const char batteryPath[] = PROC_BATTERY_DIR; + DIR* batteryDir = opendir(batteryPath); + if (!batteryDir) + return 0; + + char* batteries[MAX_BATTERIES]; + unsigned int nBatteries = 0; + memset(batteries, 0, MAX_BATTERIES * sizeof(char*)); + + while (nBatteries < MAX_BATTERIES) { + struct dirent* dirEntry = readdir(batteryDir); + if (!dirEntry) + break; + + char* entryName = dirEntry->d_name; + if (!String_startsWith(entryName, "BAT")) + continue; + + batteries[nBatteries] = xStrdup(entryName); + nBatteries++; + } + closedir(batteryDir); + + unsigned long int total = 0; + for (unsigned int i = 0; i < nBatteries; i++) { + char infoPath[30]; + xSnprintf(infoPath, sizeof infoPath, "%s%s/%s", batteryPath, batteries[i], fileName); + + FILE* file = fopen(infoPath, "r"); + if (!file) + break; + + char* line = NULL; + for (unsigned short int j = 0; j < lineNum; j++) { + free(line); + line = String_readLine(file); + if (!line) + break; + } + + fclose(file); + + if (!line) + break; + + char* foundNumStr = String_getToken(line, wordNum); + const unsigned long int foundNum = atoi(foundNumStr); + free(foundNumStr); + free(line); + + total += foundNum; + } + + for (unsigned int i = 0; i < nBatteries; i++) + free(batteries[i]); + + return total; +} + +static ACPresence procAcpiCheck(void) { + ACPresence isOn = AC_ERROR; + const char* power_supplyPath = PROC_POWERSUPPLY_DIR; + DIR* dir = opendir(power_supplyPath); + if (!dir) + return AC_ERROR; + + for (;;) { + struct dirent* dirEntry = readdir(dir); + if (!dirEntry) + break; + + const char* entryName = dirEntry->d_name; + + if (entryName[0] != 'A') + continue; + + char statePath[256]; + xSnprintf(statePath, sizeof(statePath), "%s/%s/state", power_supplyPath, entryName); + FILE* file = fopen(statePath, "r"); + if (!file) { + isOn = AC_ERROR; + continue; + } + char* line = String_readLine(file); + + fclose(file); + + if (!line) + continue; + + char* isOnline = String_getToken(line, 2); + free(line); + + if (String_eq(isOnline, "on-line")) + isOn = AC_PRESENT; + else + isOn = AC_ABSENT; + free(isOnline); + if (isOn == AC_PRESENT) + break; + } + + if (dir) + closedir(dir); + + return isOn; +} + +static double Platform_Battery_getProcBatInfo(void) { + const unsigned long int totalFull = parseBatInfo("info", 3, 4); + if (totalFull == 0) + return NAN; + + const unsigned long int totalRemain = parseBatInfo("state", 5, 3); + if (totalRemain == 0) + return NAN; + + return totalRemain * 100.0 / (double) totalFull; +} + +static void Platform_Battery_getProcData(double* level, ACPresence* isOnAC) { + *isOnAC = procAcpiCheck(); + *level = AC_ERROR != *isOnAC ? Platform_Battery_getProcBatInfo() : NAN; +} + +// ---------------------------------------- +// READ FROM /sys +// ---------------------------------------- + +static inline ssize_t xread(int fd, void* buf, size_t count) { + // Read some bytes. Retry on EINTR and when we don't get as many bytes as we requested. + size_t alreadyRead = 0; + for (;;) { + ssize_t res = read(fd, buf, count); + if (res == -1) { + if (errno == EINTR) + continue; + return -1; + } + + if (res > 0) { + buf = ((char*)buf) + res; + count -= res; + alreadyRead += res; + } + + if (count == 0 || res == 0) + return alreadyRead; + } +} + +static void Platform_Battery_getSysData(double* level, ACPresence* isOnAC) { + + *level = NAN; + *isOnAC = AC_ERROR; + + DIR* dir = opendir(SYS_POWERSUPPLY_DIR); + if (!dir) + return; + + unsigned long int totalFull = 0; + unsigned long int totalRemain = 0; + + for (;;) { + struct dirent* dirEntry = readdir(dir); + if (!dirEntry) + break; + + const char* entryName = dirEntry->d_name; + char filePath[256]; + + xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/type", entryName); + int fd1 = open(filePath, O_RDONLY); + if (fd1 == -1) + continue; + + char type[8]; + ssize_t typelen = xread(fd1, type, 7); + close(fd1); + if (typelen < 1) + continue; + + if (type[0] == 'B' && type[1] == 'a' && type[2] == 't') { + xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/uevent", entryName); + int fd2 = open(filePath, O_RDONLY); + if (fd2 == -1) { + closedir(dir); + return; + } + char buffer[1024]; + ssize_t buflen = xread(fd2, buffer, 1023); + close(fd2); + if (buflen < 1) { + closedir(dir); + return; + } + buffer[buflen] = '\0'; + char* buf = buffer; + char* line = NULL; + bool full = false; + bool now = false; + int fullSize = 0; + double capacityLevel = NAN; + + #define match(str,prefix) \ + (String_startsWith(str,prefix) ? (str) + strlen(prefix) : NULL) + + while ((line = strsep(&buf, "\n")) != NULL) { + const char* ps = match(line, "POWER_SUPPLY_"); + if (!ps) + continue; + const char* capacity = match(ps, "CAPACITY="); + if (capacity) + capacityLevel = atoi(capacity) / 100.0; + const char* energy = match(ps, "ENERGY_"); + if (!energy) + energy = match(ps, "CHARGE_"); + if (!energy) + continue; + const char* value = (!full) ? match(energy, "FULL=") : NULL; + if (value) { + fullSize = atoi(value); + totalFull += fullSize; + full = true; + if (now) + break; + continue; + } + value = (!now) ? match(energy, "NOW=") : NULL; + if (value) { + totalRemain += atoi(value); + now = true; + if (full) + break; + continue; + } + } + + #undef match + + if (!now && full && !isnan(capacityLevel)) + totalRemain += (capacityLevel * fullSize); + + } else if (entryName[0] == 'A') { + if (*isOnAC != AC_ERROR) + continue; + + xSnprintf(filePath, sizeof filePath, SYS_POWERSUPPLY_DIR "/%s/online", entryName); + int fd3 = open(filePath, O_RDONLY); + if (fd3 == -1) { + closedir(dir); + return; + } + char buffer[2] = ""; + for (;;) { + ssize_t res = read(fd3, buffer, 1); + if (res == -1 && errno == EINTR) + continue; + break; + } + close(fd3); + if (buffer[0] == '0') + *isOnAC = AC_ABSENT; + else if (buffer[0] == '1') + *isOnAC = AC_PRESENT; + } + } + closedir(dir); + + *level = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN; +} + +static enum { BAT_PROC, BAT_SYS, BAT_ERR } Platform_Battery_method = BAT_PROC; + +static time_t Platform_Battery_cacheTime; +static double Platform_Battery_cacheLevel = NAN; +static ACPresence Platform_Battery_cacheIsOnAC; + +void Platform_getBattery(double* level, ACPresence* isOnAC) { + time_t now = time(NULL); + // update battery reading is slow. Update it each 10 seconds only. + if (now < Platform_Battery_cacheTime + 10) { + *level = Platform_Battery_cacheLevel; + *isOnAC = Platform_Battery_cacheIsOnAC; + return; + } + + if (Platform_Battery_method == BAT_PROC) { + Platform_Battery_getProcData(level, isOnAC); + if (isnan(*level)) + Platform_Battery_method = BAT_SYS; + } + if (Platform_Battery_method == BAT_SYS) { + Platform_Battery_getSysData(level, isOnAC); + if (isnan(*level)) + Platform_Battery_method = BAT_ERR; + } + if (Platform_Battery_method == BAT_ERR) { + *level = NAN; + *isOnAC = AC_ERROR; + } else { + *level = CLAMP(*level, 0.0, 100.0); + } + Platform_Battery_cacheLevel = *level; + Platform_Battery_cacheIsOnAC = *isOnAC; + Platform_Battery_cacheTime = now; +} |