From 17ce819dc6e74c1e15cd9d7ab63fff7ed5b92f35 Mon Sep 17 00:00:00 2001 From: Alastair Poole Date: Sun, 16 May 2021 12:25:14 +0100 Subject: [PATCH] next: ... --- src/bin/next/machine.c | 72 ++++ src/bin/next/machine.h | 149 +++++++- src/bin/next/machine/cpu.x | 678 +++++++++++++++++++++++++++++++++ src/bin/next/machine/machine.x | 86 +++++ src/bin/next/machine/memory.x | 280 ++++++++++++++ src/bin/next/machine/network.x | 170 +++++++++ src/bin/next/machine/sensors.x | 563 +++++++++++++++++++++++++++ src/bin/next/macros.h | 15 + 8 files changed, 2011 insertions(+), 2 deletions(-) create mode 100644 src/bin/next/machine/cpu.x create mode 100644 src/bin/next/machine/machine.x create mode 100644 src/bin/next/machine/memory.x create mode 100644 src/bin/next/machine/network.x create mode 100644 src/bin/next/machine/sensors.x create mode 100644 src/bin/next/macros.h diff --git a/src/bin/next/machine.c b/src/bin/next/machine.c index a1a4e5f..a0de59c 100644 --- a/src/bin/next/machine.c +++ b/src/bin/next/machine.c @@ -1 +1,73 @@ +/* + * Copyright (c) 2018 Alastair Roy Poole + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(__linux__) +# include +#endif +#include +#include +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) +#define __MacOS__ +# include +# include +# include +# include +# include +# include +#endif + +#if defined(__OpenBSD__) +# include +# include +# include +# include +# include +# include +#endif + +#if defined(__FreeBSD__) || defined(__DragonFly__) +# include +# include +# include +# include +#endif + +#include "macros.h" #include "machine.h" +#include "machine/machine.x" +#include "machine/cpu.x" +#include "machine/memory.x" +#include "machine/sensors.x" +#include "machine/network.x" + diff --git a/src/bin/next/machine.h b/src/bin/next/machine.h index 1763df9..aae6045 100644 --- a/src/bin/next/machine.h +++ b/src/bin/next/machine.h @@ -1,5 +1,150 @@ -#ifndef MACHINE_H -#define MACHINE_H +#ifndef __MACHINE_H__ +#define __MACHINE_H__ +/* All functions and data types implementing these APIs have no additional + * system dependencies deliberately. + * + * See machine.c and the files includes in machine/ sub directory. + */ + +#include +#include + +typedef struct +{ + unsigned long total; + unsigned long idle; + float percent; +} cpu_core_t; + +// Will anyone have more than 8 vdrm/video card devices? +#define MEM_VIDEO_CARD_MAX 8 + +typedef struct +{ + uint64_t total; + uint64_t used; +} meminfo_video_t; + +typedef struct +{ + uint64_t total; + uint64_t used; + uint64_t cached; + uint64_t buffered; + uint64_t shared; + uint64_t swap_total; + uint64_t swap_used; + + uint64_t zfs_arc_used; + + uint64_t video_count; + meminfo_video_t video[MEM_VIDEO_CARD_MAX]; +} meminfo_t; + +typedef struct +{ + char *name; + char *child_name; +#if defined(__linux__) + char *path; +#elif defined(__OpenBSD__) + int mibs[5]; +#endif + double value; + bool invalid; +} sensor_t; + +typedef struct +{ + char *name; + char *vendor; + char *model; + double charge_full; + double charge_current; + uint8_t percent; + bool present; +#if defined(__OpenBSD__) + int mibs[5]; +#endif +} bat_t; + +typedef struct +{ + bool have_ac; + int battery_count; + + bat_t **batteries; +#if defined(__OpenBSD__) + int mibs[5]; +#endif +} power_t; + +typedef struct +{ + char name[255]; + struct + { + uint64_t in; + uint64_t out; + } xfer; +} net_iface_t; + +int +system_cpu_online_count_get(void); + +int +system_cpu_count_get(void); + +cpu_core_t ** +system_cpu_usage_get(int *ncpu); + +cpu_core_t ** +system_cpu_usage_delayed_get(int *ncpu, int usecs); + +cpu_core_t ** +system_cpu_state_get(int *ncpu); + +int +system_cpu_frequency_get(void); + +int +system_cpu_n_frequency_get(int n); + +int +system_cpu_n_temperature_get(int n); + +int +system_cpu_temperature_min_max_get(int *min, int *max); + +int +system_cpu_frequency_min_max_get(int *min, int *max); + +void +system_cpu_topology_get(int *ids, int ncpus); + +void +system_memory_usage_get(meminfo_t *memory); + +sensor_t ** +system_sensors_thermal_get(int *count); + +void +system_sensors_thermal_free(sensor_t **sensors, int count); + +int +system_sensor_thermal_get(sensor_t *sensor); + +void +system_sensor_thermal_free(sensor_t *sensor); + +void +system_power_state_get(power_t *power); + +void +system_power_state_free(power_t *power); + +net_iface_t ** +system_network_ifaces_get(int *n); #endif diff --git a/src/bin/next/machine/cpu.x b/src/bin/next/machine/cpu.x new file mode 100644 index 0000000..8478168 --- /dev/null +++ b/src/bin/next/machine/cpu.x @@ -0,0 +1,678 @@ +/* + * Copyright (c) 2018 Alastair Roy Poole + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if defined(__OpenBSD__) +# define CPU_STATES 6 +#else +# define CPU_STATES 5 +#endif + +static int +cpu_count(void) +{ + static int cores = 0; + + if (cores != 0) + return cores; + +#if defined(__linux__) + char buf[4096]; + FILE *f; + int line = 0; + + f = fopen("/proc/stat", "r"); + if (!f) return 0; + + while (fgets(buf, sizeof(buf), f)) + { + if (line) + { + if (!strncmp(buf, "cpu", 3)) + cores++; + else + break; + } + line++; + } + + fclose(f); +#elif defined(__MacOS__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) + size_t len; + int mib[2] = { CTL_HW, HW_NCPU }; + + len = sizeof(cores); + if (sysctl(mib, 2, &cores, &len, NULL, 0) < 0) + return 0; +#endif + return cores; +} + +int +system_cpu_count_get(void) +{ + return cpu_count(); +} + +int +system_cpu_online_count_get(void) +{ +#if defined(__OpenBSD__) + static int cores = 0; + + if (cores != 0) return cores; + + size_t len; + int mib[2] = { CTL_HW, HW_NCPUONLINE }; + + len = sizeof(cores); + if (sysctl(mib, 2, &cores, &len, NULL, 0) < 0) + return cpu_count(); + + return cores; +#else + return cpu_count(); +#endif +} + +static void +_cpu_state_get(cpu_core_t **cores, int ncpu) +{ + int diff_total, diff_idle; + double ratio, percent; + unsigned long total, idle, used; + cpu_core_t *core; +#if defined(__FreeBSD__) || defined(__DragonFly__) + size_t size; + int i, j; + + if (!ncpu) + return; + size = sizeof(unsigned long) * (CPU_STATES * ncpu); + unsigned long cpu_times[ncpu][CPU_STATES]; + + if (sysctlbyname("kern.cp_times", cpu_times, &size, NULL, 0) < 0) + return; + + for (i = 0; i < ncpu; i++) { + core = cores[i]; + unsigned long *cpu = cpu_times[i]; + + total = 0; + for (j = CP_USER; j <= CP_IDLE; j++) + total += cpu[j]; + + idle = cpu[CP_IDLE]; + + diff_total = total - core->total; + diff_idle = idle - core->idle; + if (diff_total == 0) diff_total = 1; + + ratio = diff_total / 100.0; + used = diff_total - diff_idle; + percent = used / ratio; + + if (percent > 100) percent = 100; + else if (percent < 0) + percent = 0; + + core->percent = percent; + core->total = total; + core->idle = idle; + } +#elif defined(__OpenBSD__) + static struct cpustats cpu_times[CPU_STATES]; + static int cpu_time_mib[] = { CTL_KERN, KERN_CPUSTATS, 0 }; + size_t size; + int i, j; + + memset(&cpu_times, 0, CPU_STATES * sizeof(struct cpustats)); + if (!ncpu) + return; + + for (i = 0; i < ncpu; i++) { + core = cores[i]; + size = sizeof(struct cpustats); + cpu_time_mib[2] = i; + if (sysctl(cpu_time_mib, 3, &cpu_times[i], &size, NULL, 0) < 0) + return; + + total = 0; + for (j = 0; j < CPU_STATES; j++) + total += cpu_times[i].cs_time[j]; + + idle = cpu_times[i].cs_time[CP_IDLE]; + + diff_total = total - core->total; + if (diff_total == 0) diff_total = 1; + + diff_idle = idle - core->idle; + ratio = diff_total / 100.0; + used = diff_total - diff_idle; + percent = used / ratio; + + if (percent > 100) percent = 100; + else if (percent < 0) + percent = 0; + + core->percent = percent; + core->total = total; + core->idle = idle; + } +#elif defined(__linux__) + char *buf, name[128]; + int i; + + buf = file_contents("/proc/stat"); + if (!buf) return; + + for (i = 0; i < ncpu; i++) { + core = cores[i]; + snprintf(name, sizeof(name), "cpu%d", i); + char *line = strstr(buf, name); + if (line) + { + line = strchr(line, ' ') + 1; + unsigned long cpu_times[4] = { 0 }; + + if (4 != sscanf(line, "%lu %lu %lu %lu", &cpu_times[0], + &cpu_times[1], &cpu_times[2], &cpu_times[3])) + return; + + total = cpu_times[0] + cpu_times[1] + cpu_times[2] + cpu_times[3]; + idle = cpu_times[3]; + diff_total = total - core->total; + if (diff_total == 0) diff_total = 1; + + diff_idle = idle - core->idle; + ratio = diff_total / 100.0; + used = diff_total - diff_idle; + percent = used / ratio; + + if (percent > 100) percent = 100; + else if (percent < 0) + percent = 0; + + core->percent = percent; + core->total = total; + core->idle = idle; + } + } + free(buf); +#elif defined(__MacOS__) + mach_msg_type_number_t count; + processor_cpu_load_info_t load; + mach_port_t mach_port; + unsigned int cpu_count; + int i; + + cpu_count = ncpu; + + count = HOST_CPU_LOAD_INFO_COUNT; + mach_port = mach_host_self(); + if (host_processor_info(mach_port, PROCESSOR_CPU_LOAD_INFO, &cpu_count, + (processor_info_array_t *)&load, &count) != KERN_SUCCESS) + exit(-1); + + for (i = 0; i < ncpu; i++) { + core = cores[i]; + + total = load[i].cpu_ticks[CPU_STATE_USER] + + load[i].cpu_ticks[CPU_STATE_SYSTEM] + + load[i].cpu_ticks[CPU_STATE_IDLE] + + load[i].cpu_ticks[CPU_STATE_NICE]; + idle = load[i].cpu_ticks[CPU_STATE_IDLE]; + + diff_total = total - core->total; + if (diff_total == 0) diff_total = 1; + diff_idle = idle - core->idle; + ratio = diff_total / 100.0; + used = diff_total - diff_idle; + percent = used / ratio; + + if (percent > 100) percent = 100; + else if (percent < 0) + percent = 0; + + core->percent = percent; + core->total = total; + core->idle = idle; + } +#endif +} + +cpu_core_t ** +system_cpu_state_get(int *ncpu) +{ + cpu_core_t **cores; + int i; + + *ncpu = cpu_count(); + cores = malloc((*ncpu) * sizeof(cpu_core_t *)); + for (i = 0; i < *ncpu; i++) + cores[i] = calloc(1, sizeof(cpu_core_t)); + + _cpu_state_get(cores, *ncpu); + + return cores; +} + +cpu_core_t ** +system_cpu_usage_delayed_get(int *ncpu, int usecs) +{ + cpu_core_t **cores; + int i; + + *ncpu = cpu_count(); + + cores = malloc((*ncpu) * sizeof(cpu_core_t *)); + + for (i = 0; i < *ncpu; i++) + cores[i] = calloc(1, sizeof(cpu_core_t)); + + _cpu_state_get(cores, *ncpu); + usleep(usecs); + _cpu_state_get(cores, *ncpu); + + return cores; +} + +cpu_core_t ** +system_cpu_usage_get(int *ncpu) +{ + return system_cpu_usage_delayed_get(ncpu, 1000000); +} + +static int _cpu_temp_min = 0; +static int _cpu_temp_max = 100; +static char _core_temps[256][512]; +static char _hwmon_path[256]; + +int +_cpu_n_temperature_read(int n) +{ + int temp = -1; +#if defined(__linux__) + + char *b = file_contents(_core_temps[n]); + if (b) + { + temp = atoi(b) / 1000; + free(b); + } +#elif defined(__FreeBSD__) || defined(__DragonFly__) + int value; + size_t len = sizeof(value); + + if (!_core_temps[n][0]) + snprintf(_core_temps[n], sizeof(_core_temps[n]), "dev.cpu.%d.temperature", n); + + if ((sysctlbyname(_core_temps[n], &value, &len, NULL, 0)) != -1) + { + temp = (value - 2732) / 10; + } +#endif + + return temp; +} + +#if defined(__linux__) + +typedef struct _thermal_drv { + const char *name; + void (*init)(void); + char min; + char max; +} thermal_drv; + +static void +_coretemp_init(void) +{ + char buf[4096]; + int cpu_count = system_cpu_count_get(); + + for (int j = 0; j < cpu_count; j++) + { + snprintf(buf, sizeof(buf), "/sys/devices/system/cpu/cpu%i/topology/core_id", j); + char *b = file_contents(buf); + if (b) + { + int core_id = atoi(b); + snprintf(_core_temps[j], sizeof(_core_temps[j]), "%s/temp%d_input", _hwmon_path, 2 + core_id); + free(b); + } + } +} + +static void +_generic_init(void) +{ + int i, cpu_count = system_cpu_count_get(); + + for (i = 0; i < cpu_count; i++) + snprintf(_core_temps[i], sizeof(_core_temps[i]), "%s/temp1_input", _hwmon_path); +} + +#endif + +int +system_cpu_n_temperature_get(int n) +{ +#if defined(__linux__) + static int init = 0; + + // This list is not exhastive by any means, if you have the + // hardware and can provide a better init, please do. WIP. + // Min max (where applicable) + thermal_drv drivers[] = { + { "coretemp", _coretemp_init, 0, 90 }, /* Intel Coretemp */ + { "k10temp", _generic_init, 0, 90 }, /* AMD K10 */ + { "cpu_thermal", _generic_init, 0, 90 }, /* BCM2835/BCM2711 (RPI3/4) */ + { "cup", _generic_init, 0, 100 }, /* RK3399 */ + { "soc_thermal", _generic_init, 0, 100 }, /* RK3326 */ + { "cpu0_thermal", _generic_init, 0, 100 }, /* AllWinner A64 */ + { "soc_dts0", _generic_init, 0, 90 }, /* Intel Baytrail */ + }; + + if (!init) + { + char buf[4096]; + struct dirent *dh; + DIR *dir; + + memset(&_core_temps, 0, sizeof(_core_temps)); + memset(&_hwmon_path, 0, sizeof(_hwmon_path)); + + dir = opendir("/sys/class/hwmon"); + if (!dir) + { + init = 1; + return -1; + } + + while ((dh = readdir(dir)) != NULL) + { + if (dh->d_name[0] == '.') continue; + snprintf(buf, sizeof(buf), "/sys/class/hwmon/%s", dh->d_name); + char *link = realpath(buf, NULL); + if (!link) continue; + + snprintf(buf, sizeof(buf), "%s/name", link); + char *b = file_contents(buf); + if (b) + { + for (int i = 0; i < sizeof(drivers) / sizeof(thermal_drv); i++) + { + if (!strncmp(b, drivers[i].name, strlen(drivers[i].name))) + { + snprintf(_hwmon_path, sizeof(_hwmon_path), "%s", link); + drivers[i].init(); + } + } + free(b); + } + free(link); + if (_hwmon_path[0]) break; + } + + closedir(dir); + init = 1; + } + + if (!_hwmon_path[0]) return -1; + + return _cpu_n_temperature_read(n); +#elif defined(__FreeBSD__) || defined(__DragonFly__) + static int init = 0; + + if (!init) + { + memset(&_core_temps, 0, sizeof(_core_temps)); + init = 1; + } + + return _cpu_n_temperature_read(n); +#endif + return -1; +} + +int +system_cpu_temperature_min_max_get(int *min, int *max) +{ + + *min = _cpu_temp_min; + *max = _cpu_temp_max; + + return 0; +} + +int +system_cpu_n_frequency_get(int n) +{ +#if defined(__linux__) + int freq = -1; + FILE *f; + char buf[4096]; + int tmp; + + snprintf(buf, sizeof(buf), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", n); + f = fopen(buf, "r"); + if (f) + { + if (fgets(buf, sizeof(buf), f)) + { + tmp = strtol(buf, NULL, 10); + if (!((tmp == LONG_MIN || tmp == LONG_MAX) && errno == ERANGE)) + freq = tmp; + } + fclose(f); + if (freq != -1) return freq; + } + + return freq; +#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) + return system_cpu_frequency_get(); +#endif + + return -1; +} + +int +system_cpu_frequency_min_max_get(int *min, int *max) +{ + int freq_min = 0x7fffffff, freq_max = 0; +#if defined(__linux__) + char *s; + + s = file_contents("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"); + if (s) + { + freq_min = atoi(s); + free(s); + s = file_contents("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"); + if (s) + { + freq_max = atoi(s); + free(s); + *min = freq_min; + *max = freq_max; + if (freq_min < freq_max) + return 0; + } + } + + s = file_contents("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies"); + if (!s) return -1; + + char *t = strtok(s, " "); + while (t) + { + int freq = atoi(t); + if (freq != 0) + { + if (freq > freq_max) freq_max = freq; + if (freq < freq_min) freq_min = freq; + } + t = strtok(NULL, " "); + } + + free(s); + + if (freq_min == 0x7fffffff || freq_max == 0) return -1; + + *min = freq_min; + *max = freq_max; + + return 0; +#elif defined(__FreeBSD__) || defined(__DragonFly__) + char buf[4096]; + size_t len = sizeof(buf); + char *t, *s; + + if (sysctlbyname("dev.cpu.0.freq_levels", buf, &len, NULL, 0) != -1) + { + s = buf; + while (s) + { + t = strchr(s, '/'); + if (!t) break; + *t = '\0'; + int freq = atoi(s) * 1000; + if (freq > freq_max) freq_max = freq; + if (freq < freq_min) freq_min = freq; + + s = strchr(t + 1, ' '); + } + if (freq_min == 0x7fffffff || freq_max == 0) return -1; + + *min = freq_min; + *max = freq_max; + + return 0; + } + +#elif defined(__OpenBSD__) + *min = 0; + *max = 100; + + return 0; +#endif + (void) freq_min; (void) freq_max; + return -1; +} + +int +system_cpu_frequency_get(void) +{ + int freq = -1; + +#if defined(__FreeBSD__) || defined(__DragonFly__) + size_t len = sizeof(freq); + if (sysctlbyname("dev.cpu.0.freq", &freq, &len, NULL, 0) != -1) + freq *= 1000; +#elif defined(__OpenBSD__) + int mib[2] = { CTL_HW, HW_CPUSPEED }; + size_t len = sizeof(freq); + if (sysctl(mib, 2, &freq, &len, NULL, 0) != -1) + freq *= 1000; +#elif defined(__linux__) + FILE *f; + char buf[4096]; + int tmp; + + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", "r"); + if (f) + { + if (fgets(buf, sizeof(buf), f)) + { + tmp = strtol(buf, NULL, 10); + if (!((tmp == LONG_MIN || tmp == LONG_MAX) && errno == ERANGE)) + freq = tmp; + } + fclose(f); + if (freq != -1) return freq; + } + + f = fopen("/proc/cpuinfo", "r"); + if (!f) return freq; + + while (fgets(buf, sizeof(buf), f)) + { + if (!strncasecmp(buf, "cpu MHz", 7)) + { + char *s = strchr(buf, ':') + 1; + tmp = strtol(s, NULL, 10); + if (!((tmp == LONG_MIN || tmp == LONG_MAX) && errno == ERANGE)) + freq = tmp * 1000; + break; + } + } + + fclose(f); +#else + +#endif + return freq; +} + +#if defined(__linux__) + +typedef struct { + short id; + short core_id; +} core_top_t; + +static int +_cmp(const void *a, const void *b) +{ + core_top_t *aa = (core_top_t *) a; + core_top_t *bb = (core_top_t *) b; + + if (aa->core_id == bb->core_id) return 0; + else if (aa->core_id < bb->core_id) return -1; + else return 1; +} + +#endif + +void +system_cpu_topology_get(int *ids, int ncpu) +{ +#if defined(__linux__) + char buf[4096]; + core_top_t *cores = malloc(ncpu * sizeof(core_top_t)); + + for (int i = 0; i < ncpu; i++) + { + cores[i].id = i; + cores[i].core_id = i; + snprintf(buf, sizeof(buf), "/sys/devices/system/cpu/cpu%i/topology/core_id", i); + char *b = file_contents(buf); + if (b) + { + cores[i].core_id = atoi(b); + free(b); + } + } + + qsort(cores, ncpu, sizeof(core_top_t), _cmp); + + for (int i = 0; i < ncpu; i++) + { + ids[i] = cores[i].id; + } + free(cores); +#endif +} diff --git a/src/bin/next/machine/machine.x b/src/bin/next/machine/machine.x new file mode 100644 index 0000000..a68acf4 --- /dev/null +++ b/src/bin/next/machine/machine.x @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018 Alastair Roy Poole + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +char * +file_contents(const char *path) +{ + FILE *f; + char *buf, *tmp; + size_t n = 1, len = 0; + const size_t block = 4096; + + f = fopen(path, "r"); + if (!f) return NULL; + + buf = NULL; + + while ((!feof(f)) && (!ferror(f))) + { + tmp = realloc(buf, ++n * (sizeof(char) * block) + 1); + if (!tmp) return NULL; + buf = tmp; + len += fread(buf + len, sizeof(char), block, f); + } + + if (ferror(f)) + { + free(buf); + fclose(f); + return NULL; + } + fclose(f); + + buf[len] = 0; + + return buf; +} + +void +strimmer(char *s) +{ + char *cp = s; + + while (*cp) + { + if ((*cp == '\r') || (*cp == '\n')) + { + *cp = '\0'; + return; + } + cp++; + } +} + +#if defined(__FreeBSD__) || defined(__DragonFly__) +static long int +_sysctlfromname(const char *name, void *mib, int depth, size_t *len) +{ + long int result; + + if (sysctlnametomib(name, mib, len) < 0) + return -1; + + *len = sizeof(result); + if (sysctl(mib, depth, &result, len, NULL, 0) < 0) + return -1; + + return result; +} + +#endif + diff --git a/src/bin/next/machine/memory.x b/src/bin/next/machine/memory.x new file mode 100644 index 0000000..90fa1e2 --- /dev/null +++ b/src/bin/next/machine/memory.x @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2018 Alastair Roy Poole + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if defined(__linux__) +static unsigned long +_meminfo_parse_line(const char *line) +{ + char *p, *tok; + + p = strchr(line, ':') + 1; + while (isspace(*p)) + p++; + tok = strtok(p, " "); + + return atoll(tok); +} + +#endif + +void +system_memory_usage_get(meminfo_t *memory) +{ + memset(memory, 0, sizeof(meminfo_t)); +#if defined(__linux__) + FILE *f; + unsigned long swap_free = 0, tmp_free = 0, tmp_slab = 0; + char line[256]; + int i, fields = 0; + + f = fopen("/proc/meminfo", "r"); + if (!f) return; + + while (fgets(line, sizeof(line), f) != NULL) + { + if (!strncmp("MemTotal:", line, 9)) + { + memory->total = _meminfo_parse_line(line); + fields++; + } + else if (!strncmp("MemFree:", line, 8)) + { + tmp_free = _meminfo_parse_line(line); + fields++; + } + else if (!strncmp("Cached:", line, 7)) + { + memory->cached = _meminfo_parse_line(line); + fields++; + } + else if (!strncmp("Slab:", line, 5)) + { + tmp_slab = _meminfo_parse_line(line); + fields++; + } + else if (!strncmp("Buffers:", line, 8)) + { + memory->buffered = _meminfo_parse_line(line); + fields++; + } + else if (!strncmp("Shmem:", line, 6)) + { + memory->shared = _meminfo_parse_line(line); + fields++; + } + else if (!strncmp("SwapTotal:", line, 10)) + { + memory->swap_total = _meminfo_parse_line(line); + fields++; + } + else if (!strncmp("SwapFree:", line, 9)) + { + swap_free = _meminfo_parse_line(line); + fields++; + } + + if (fields >= 8) + break; + } + + memory->cached += tmp_slab; + memory->used = memory->total - tmp_free - memory->cached - memory->buffered; + memory->swap_used = memory->swap_total - swap_free; + + memory->total *= 1024; + memory->used *= 1024; + memory->buffered *= 1024; + memory->cached *= 1024; + memory->shared *= 1024; + memory->swap_total *= 1024; + memory->swap_used *= 1024; + + fclose(f); + for (i = 0; i < MEM_VIDEO_CARD_MAX; i++) + { + struct stat st; + char buf[256]; + + // if no more drm devices exist - end of card list + snprintf(buf, sizeof(buf), + "/sys/class/drm/card%i/device", i); + if (stat(buf, &st) != 0) break; + // not all drivers expose this, so video devices with no exposed video + // ram info will appear as 0 sized... much like swap. + snprintf(buf, sizeof(buf), + "/sys/class/drm/card%i/device/mem_info_vram_total", i); + f = fopen(buf, "r"); + if (f) + { + if (fgets(buf, sizeof(buf), f)) + memory->video[i].total = atoll(buf); + fclose(f); + } + snprintf(buf, sizeof(buf), + "/sys/class/drm/card%i/device/mem_info_vram_used", i); + f = fopen(buf, "r"); + if (f) + { + if (fgets(buf, sizeof(buf), f)) + memory->video[i].used = atoll(buf); + fclose(f); + } + } + memory->video_count = i; + +#elif defined(__FreeBSD__) || defined(__DragonFly__) + unsigned int free = 0, active = 0, inactive = 0, wired = 0; + unsigned int cached = 0, buffered = 0, zfs_arc = 0; + long int result = 0; + int page_size = getpagesize(); + int mib[5] = { CTL_HW, HW_PHYSMEM, 0, 0, 0 }; + size_t miblen, len = 0; + + len = sizeof(memory->total); + if (sysctl(mib, 2, &memory->total, &len, NULL, 0) == -1) + return; + + if ((active = _sysctlfromname("vm.stats.vm.v_active_count", mib, 4, &len)) == -1) + return; + + if ((inactive = _sysctlfromname("vm.stats.vm.v_inactive_count", mib, 4, &len)) == -1) + return; + + if ((wired = _sysctlfromname("vm.stats.vm.v_wire_count", mib, 4, &len)) == -1) + return; + + if ((cached = _sysctlfromname("vm.stats.vm.v_cache_count", mib, 4, &len)) == -1) + return; + + if ((free = _sysctlfromname("vm.stats.vm.v_free_count", mib, 4, &len)) == -1) + return; + + if ((buffered = _sysctlfromname("vfs.bufspace", mib, 2, &len)) == -1) + return; + + if ((zfs_arc = _sysctlfromname("kstat.zfs.misc.arcstats.c", mib, 5, &len)) != -1) + memory->zfs_arc_used = zfs_arc; + + memory->used = ((active + wired + cached) * page_size); + memory->buffered = buffered; + memory->cached = (cached * page_size); + + result = _sysctlfromname("vm.swap_total", mib, 2, &len); + if (result == -1) + return; + + memory->swap_total = result; + + miblen = 3; + if (sysctlnametomib("vm.swap_info", mib, &miblen) == -1) return; + + struct xswdev xsw; + + for (int i = 0; ; i++) { + mib[miblen] = i; + len = sizeof(xsw); + if (sysctl(mib, miblen + 1, &xsw, &len, NULL, 0) == -1) + break; + + memory->swap_used += (unsigned long) xsw.xsw_used * page_size; + } +#elif defined(__OpenBSD__) + static int mib[] = { CTL_HW, HW_PHYSMEM64 }; + static int bcstats_mib[] = { CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT }; + struct bcachestats bcstats; + static int uvmexp_mib[] = { CTL_VM, VM_UVMEXP }; + struct uvmexp uvmexp; + int nswap, rnswap; + struct swapent *swdev = NULL; + size_t len; + + len = sizeof(memory->total); + if (sysctl(mib, 2, &memory->total, &len, NULL, 0) == -1) + return; + + len = sizeof(uvmexp); + if (sysctl(uvmexp_mib, 2, &uvmexp, &len, NULL, 0) == -1) + return; + + len = sizeof(bcstats); + if (sysctl(bcstats_mib, 3, &bcstats, &len, NULL, 0) == -1) + return; + + nswap = swapctl(SWAP_NSWAP, 0, 0); + if (nswap == 0) + goto swap_out; + + swdev = calloc(nswap, sizeof(*swdev)); + if (swdev == NULL) + goto swap_out; + + rnswap = swapctl(SWAP_STATS, swdev, nswap); + if (rnswap == -1) + goto swap_out; + + for (int i = 0; i < nswap; i++) { + if (swdev[i].se_flags & SWF_ENABLE) + { + memory->swap_used += (swdev[i].se_inuse / (1024 / DEV_BSIZE)); + memory->swap_total += (swdev[i].se_nblks / (1024 / DEV_BSIZE)); + } + } + + memory->swap_total *= 1024; + memory->swap_used *= 1024; +swap_out: + if (swdev) + free(swdev); + memory->cached = MEMSIZE(uvmexp.pagesize) * MEMSIZE(bcstats.numbufpages); + memory->used = MEMSIZE(uvmexp.pagesize) * MEMSIZE(uvmexp.active); + memory->buffered = MEMSIZE(uvmexp.pagesize) * (MEMSIZE(uvmexp.npages) - MEMSIZE(uvmexp.free)); + memory->shared = MEMSIZE(uvmexp.pagesize) * MEMSIZE(uvmexp.wired); +#elif defined(__MacOS__) + int mib[2] = { CTL_HW, HW_MEMSIZE }; + size_t total; + vm_size_t page_size; + mach_port_t mach_port; + mach_msg_type_number_t count; + vm_statistics64_data_t vm_stats; + struct xsw_usage xsu; + + size_t len = sizeof(size_t); + if (sysctl(mib, 2, &total, &len, NULL, 0) == -1) + return; + mach_port = mach_host_self(); + count = sizeof(vm_stats) / sizeof(natural_t); + + memory->total = total; + + if (host_page_size(mach_port, &page_size) == KERN_SUCCESS && + host_statistics64(mach_port, HOST_VM_INFO, + (host_info64_t)&vm_stats, &count) == KERN_SUCCESS) + { + memory->used = (vm_stats.active_count + vm_stats.wire_count) * page_size; + memory->cached = vm_stats.active_count * page_size; + memory->shared = vm_stats.wire_count * page_size; + memory->buffered = vm_stats.inactive_count * page_size; + } + + total = sizeof(xsu); + if (sysctlbyname("vm.swapusage", &xsu, &total, NULL, 0) != -1) + { + memory->swap_total = xsu.xsu_total; + memory->swap_used = xsu.xsu_used; + } +#endif +} + diff --git a/src/bin/next/machine/network.x b/src/bin/next/machine/network.x new file mode 100644 index 0000000..dc4bd83 --- /dev/null +++ b/src/bin/next/machine/network.x @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2018 Alastair Roy Poole + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if defined(__MacOS__) || defined(__FreeBSD__) || defined(__DragonFly__) +static net_iface_t ** +_freebsd_generic_network_status(int *n) +{ + struct ifmibdata *ifmd; + size_t len; + int i, count; + len = sizeof(count); + + if (sysctlbyname("net.link.generic.system.ifcount", &count, &len, NULL, 0) == -1) + return NULL; + + ifmd = malloc(sizeof(struct ifmibdata)); + if (!ifmd) + return NULL; + + net_iface_t **ifaces = NULL; + + for (i = 1; i <= count; i++) { + int mib[] = { + CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_IFDATA, i, IFDATA_GENERAL + }; + len = sizeof(*ifmd); + if (sysctl(mib, 6, ifmd, &len, NULL, 0) < 0) continue; + + net_iface_t *iface = malloc(sizeof(net_iface_t)); + if (iface) + { + snprintf(iface->name, sizeof(iface->name), "%s", + ifmd->ifmd_name); + iface->xfer.in = ifmd->ifmd_data.ifi_ibytes; + iface->xfer.out = ifmd->ifmd_data.ifi_obytes; + void *t = realloc(ifaces, (1 + 1 + *n) * sizeof(net_iface_t *)); + ifaces = t; + ifaces[(*n)++] = iface; + } + } + free(ifmd); + + return ifaces; +} + +#endif + +#if defined(__OpenBSD__) +static net_iface_t ** +_openbsd_generic_network_status(int *n) +{ + struct ifaddrs *interfaces, *ifa; + + if (getifaddrs(&interfaces) < 0) + return NULL; + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + return NULL; + + net_iface_t **ifaces = NULL; + + for (ifa = interfaces; ifa; ifa = ifa->ifa_next) { + struct ifreq ifreq; + struct if_data if_data; + + // TBC: Interfaces can have multiple addresses. + // Using this netmask check we should be obtaining + // the overall interface data across addresses. + + if (ifa->ifa_netmask != 0) continue; + + ifreq.ifr_data = (void *)&if_data; + strncpy(ifreq.ifr_name, ifa->ifa_name, IFNAMSIZ - 1); + if (ioctl(sock, SIOCGIFDATA, &ifreq) < 0) + return NULL; + + const struct if_data *ifi = &if_data; + if (!LINK_STATE_IS_UP(ifi->ifi_link_state)) + continue; + + net_iface_t *iface = malloc(sizeof(net_iface_t)); + if (iface) + { + snprintf(iface->name, sizeof(iface->name), "%s", + ifa->ifa_name); + iface->xfer.in = ifi->ifi_ibytes; + iface->xfer.out = ifi->ifi_obytes; + void *t = realloc(ifaces, (1 + 1 + *n) * sizeof(net_iface_t *)); + ifaces = t; + ifaces[(*n)++] = iface; + } + } + close(sock); + + return ifaces; +} + +#endif + +#if defined(__linux__) +static net_iface_t ** +_linux_generic_network_status(int *n) +{ + FILE *f; + char buf[4096], name[128]; + unsigned long int tmp_in, tmp_out, dummy; + + f = fopen("/proc/net/dev", "r"); + if (!f) return NULL; + + net_iface_t **ifaces = NULL; + + while (fgets(buf, sizeof(buf), f)) + { + if (17 == sscanf(buf, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu " + "%lu %lu %lu %lu %lu\n", name, &tmp_in, &dummy, + &dummy, &dummy, &dummy, &dummy, &dummy, &dummy, + &tmp_out, &dummy, &dummy, &dummy, &dummy, &dummy, + &dummy, &dummy)) + { + net_iface_t *iface = malloc(sizeof(net_iface_t)); + if (iface) + { + name[strlen(name)-1] = '\0'; + snprintf(iface->name, sizeof(iface->name), "%s", name); + iface->xfer.in = tmp_in; + iface->xfer.out = tmp_out; + void *t = realloc(ifaces, (1 + 1 + *n) * sizeof(net_iface_t *)); + ifaces = t; + ifaces[(*n)++] = iface; + } + } + } + + fclose(f); + + return ifaces; +} + +#endif + +net_iface_t ** +system_network_ifaces_get(int *n) +{ + *n = 0; +#if defined(__linux__) + return _linux_generic_network_status(n); +#elif defined(__MacOS__) || defined(__FreeBSD__) || defined(__DragonFly__) + return _freebsd_generic_network_status(n); +#elif defined(__OpenBSD__) + return _openbsd_generic_network_status(n); +#else + return NULL; +#endif +} + diff --git a/src/bin/next/machine/sensors.x b/src/bin/next/machine/sensors.x new file mode 100644 index 0000000..e561475 --- /dev/null +++ b/src/bin/next/machine/sensors.x @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2018 Alastair Roy Poole + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +void +system_sensor_thermal_free(sensor_t *sensor) +{ + if (sensor->name) + free(sensor->name); + if (sensor->child_name) + free(sensor->child_name); +#if defined(__linux__) + if (sensor->path) + free(sensor->path); +#endif + free(sensor); +} + +void +system_sensors_thermal_free(sensor_t **sensors, int count) +{ + for (int i = 0; i < count; i++) + { + sensor_t *sensor = sensors[i]; + system_sensor_thermal_free(sensor); + } + if (sensors) + free(sensors); +} + +int +system_sensor_thermal_get(sensor_t *sensor) +{ +#if defined(__linux__) + char *d = file_contents(sensor->path); + if (d) + { + double val = atof(d); + if (val) + sensor->value = val /= 1000; + free(d); + return 1; + } + return 0; +#elif defined(__OpenBSD__) + struct sensor snsr; + size_t slen = sizeof(struct sensor); + + if (sysctl(sensor->mibs, 5, &snsr, &slen, NULL, 0) == -1) return 0; + + sensor->value = (snsr.value - 273150000) / 1000000.0; + + return 1; +#elif defined(__FreeBSD__) || defined(__DragonFly__) + int value; + size_t len = sizeof(value); + if ((sysctlbyname(sensor->name, &value, &len, NULL, 0)) != -1) + { + sensor->value = (float) (value - 2732) / 10; + return 1; + } +#endif + return 0; +} + +sensor_t ** +system_sensors_thermal_get(int *sensor_count) +{ + sensor_t **sensors = NULL; + *sensor_count = 0; +#if defined(__OpenBSD__) + sensor_t *sensor; + int mibs[5] = { CTL_HW, HW_SENSORS, 0, 0, 0 }; + int devn, n; + struct sensor snsr; + struct sensordev snsrdev; + size_t slen = sizeof(struct sensor); + size_t sdlen = sizeof(struct sensordev); + + for (devn = 0;; devn++) + { + mibs[2] = devn; + + if (sysctl(mibs, 3, &snsrdev, &sdlen, NULL, 0) == -1) + { + if (errno == ENOENT) break; + continue; + } + + for (n = 0; n < snsrdev.maxnumt[SENSOR_TEMP]; n++) { + mibs[4] = n; + + if (sysctl(mibs, 5, &snsr, &slen, NULL, 0) == -1) + continue; + + if (slen > 0 && (snsr.flags & SENSOR_FINVALID) == 0) + break; + } + + if (sysctl(mibs, 5, &snsr, &slen, NULL, 0) == -1) + continue; + if (snsr.type != SENSOR_TEMP) + continue; + + void *t = realloc(sensors, (1 + *sensor_count) * sizeof(sensor_t *)); + sensors = t; + sensors[(*sensor_count)++] = sensor = calloc(1, sizeof(sensor_t)); + sensor->name = strdup(snsrdev.xname); + sensor->value = (snsr.value - 273150000) / 1000000.0; // (uK -> C) + memcpy(sensor->mibs, &mibs, sizeof(mibs)); + } +#elif defined(__FreeBSD__) || defined(__DragonFly__) + sensor_t *sensor; + int value; + char buf[256]; + size_t len = sizeof(value); + + if ((sysctlbyname("hw.acpi.thermal.tz0.temperature", &value, &len, NULL, 0)) != -1) + { + void *t = realloc(sensors, (1 + *sensor_count) * sizeof(sensor_t *)); + sensors = t; + sensors[(*sensor_count)++] = sensor = calloc(1, sizeof(sensor_t)); + sensor->name = strdup("hw.acpi.thermal.tz0.temperature"); + sensor->value = (float) (value - 2732) / 10; + } + + int n = system_cpu_count_get(); + + for (int i = 0; i < n; i++) + { + len = sizeof(value); + snprintf(buf, sizeof(buf), "dev.cpu.%i.temperature", i); + if ((sysctlbyname(buf, &value, &len, NULL, 0)) != -1) + { + void *t = realloc(sensors, (1 + *sensor_count) * sizeof(sensor_t *)); + sensors = t; + sensors[(*sensor_count)++] = sensor = calloc(1, sizeof(sensor_t)); + sensor->name = strdup(buf); + sensor->value = (float) (value - 2732) / 10; + } + } + +#elif defined(__linux__) + sensor_t *sensor; + DIR *dir; + struct dirent *dh; + char buf[4096]; + int seen[128]; + + dir = opendir("/sys/class/hwmon"); + if (!dir) return NULL; + + while ((dh = readdir(dir)) != NULL) + { + struct dirent **names = NULL; + + snprintf(buf, sizeof(buf), "/sys/class/hwmon/%s", dh->d_name); + char *link = realpath(buf, NULL); + if (!link) continue; + + int n = scandir(link, &names, 0, alphasort); + if (n < 0) continue; + + int idx = 0; + memset(&seen, 0, sizeof(seen)); + + for (int i = 0; i < n; i++) + { + if (!strncmp(names[i]->d_name, "temp", 4)) + { + int id = atoi(names[i]->d_name + 4); + if ((!id) || (id > sizeof(seen))) + { + free(names[i]); + continue; + } + + int found = 0; + + for (int j = 0; seen[j] != 0 && j < sizeof(seen); j++) + if (seen[j] == id) found = 1; + + if (found) + { + free(names[i]); + continue; + } + + void *t = realloc(sensors, (1 + (*sensor_count)) * sizeof(sensor_t *)); + sensors = t; + sensors[(*sensor_count)++] = sensor = calloc(1, sizeof(sensor_t)); + + snprintf(buf, sizeof(buf), "%s/name", link); + sensor->name = file_contents(buf); + if (sensor->name) + strimmer(sensor->name); + + snprintf(buf, sizeof(buf), "%s/temp%d_label", link, id); + sensor->child_name = file_contents(buf); + if (sensor->child_name) + strimmer(sensor->child_name); + + snprintf(buf, sizeof(buf), "%s/temp%d_input", link, id); + sensor->path = strdup(buf); + char *d = file_contents(buf); + if (d) + { + sensor->value = atoi(d); + if (sensor->value) sensor->value /= 1000; + free(d); + } + seen[idx++] = id; + } + free(names[i]); + } + + free(names); + free(link); + } + + closedir(dir); + +#elif defined(__MacOS__) +#endif + return sensors; +} + +static int +_power_battery_count_get(power_t *power) +{ +#if defined(__OpenBSD__) + struct sensordev snsrdev; + size_t sdlen = sizeof(struct sensordev); + int mibs[5] = { CTL_HW, HW_SENSORS, 0, 0, 0 }; + int i, devn; + + for (devn = 0;; devn++) { + mibs[2] = devn; + if (sysctl(mibs, 3, &snsrdev, &sdlen, NULL, 0) == -1) + { + if (errno == ENXIO) + continue; + if (errno == ENOENT) + break; + } + + if (!strncmp(snsrdev.xname, "acpibat", 7)) + { + i = power->battery_count; + + void *t = realloc(power->batteries, 1 + + power->battery_count++ * sizeof(bat_t **)); + power->batteries = t; + power->batteries[i] = calloc(1, sizeof(bat_t)); + power->batteries[i]->name = strdup(snsrdev.xname); + power->batteries[i]->model = strdup("Unknown"); + power->batteries[i]->vendor = strdup("Unknown"); + power->batteries[i]->present = true; + power->batteries[i]->mibs[0] = mibs[0]; + power->batteries[i]->mibs[1] = mibs[1]; + power->batteries[i]->mibs[2] = mibs[2]; + } + if (!strcmp("acpiac0", snsrdev.xname)) + { + power->mibs[0] = mibs[0]; + power->mibs[1] = mibs[1]; + power->mibs[2] = mibs[2]; + } + } +#elif defined(__FreeBSD__) || defined(__DragonFly__) + int n_units, fd; + char name[256]; + + fd = open("/dev/acpi", O_RDONLY); + if (fd != -1) + { + if (ioctl(fd, ACPIIO_BATT_GET_UNITS, &n_units) != -1) + power->battery_count = n_units; + close(fd); + } + + power->batteries = malloc(power->battery_count * sizeof(bat_t **)); + for (int i = 0; i < power->battery_count; i++) { + power->batteries[i] = calloc(1, sizeof(bat_t)); + snprintf(name, sizeof(name), "hw.acpi.battery.%i", i); + power->batteries[i]->name = strdup(name); + power->batteries[i]->present = true; + } +#elif defined(__linux__) + char *type; + struct dirent **names; + char *buf; + char path[PATH_MAX]; + int i, n, id; + + n = scandir("/sys/class/power_supply", &names, 0, alphasort); + if (n < 0) return power->battery_count; + + for (i = 0; i < n; i++) { + snprintf(path, sizeof(path), "/sys/class/power_supply/%s/type", names[i]->d_name); + type = file_contents(path); + if (type) + { + if (!strncmp(type, "Battery", 7)) + { + id = power->battery_count; + void *t = realloc(power->batteries, (1 + + power->battery_count) * sizeof(bat_t **)); + power->batteries = t; + power->batteries[id] = calloc(1, sizeof(bat_t)); + power->batteries[id]->name = strdup(names[i]->d_name); + snprintf(path, sizeof(path), "/sys/class/power_supply/%s/manufacturer", names[i]->d_name); + power->batteries[id]->vendor = file_contents(path); + snprintf(path, sizeof(path), "/sys/class/power_supply/%s/model_name", names[i]->d_name); + power->batteries[id]->model = file_contents(path); + snprintf(path, sizeof(path), "/sys/class/power_supply/%s/present", names[i]->d_name); + power->batteries[id]->present = 1; + buf = file_contents(path); + if (buf) + { + power->batteries[id]->present = atoi(buf); + free(buf); + } + + power->battery_count++; + } + free(type); + } + + free(names[i]); + } + + free(names); +#endif + + return power->battery_count; +} + +static void +_battery_state_get(power_t *power) +{ +#if defined(__OpenBSD__) + int *mib; + double charge_full, charge_current; + size_t slen = sizeof(struct sensor); + struct sensor snsr; + + for (int i = 0; i < power->battery_count; i++) { + charge_full = charge_current = 0; + + mib = power->batteries[i]->mibs; + mib[3] = SENSOR_WATTHOUR; + mib[4] = 0; + + if (sysctl(mib, 5, &snsr, &slen, NULL, 0) != -1) + charge_full = (double)snsr.value; + + mib[3] = SENSOR_WATTHOUR; + mib[4] = 3; + + if (sysctl(mib, 5, &snsr, &slen, NULL, 0) != -1) + charge_current = (double)snsr.value; + + if (charge_current == 0 || charge_full == 0) + { + mib[3] = SENSOR_AMPHOUR; + mib[4] = 0; + + if (sysctl(mib, 5, &snsr, &slen, NULL, 0) != -1) + charge_full = (double)snsr.value; + + mib[3] = SENSOR_AMPHOUR; + mib[4] = 3; + + if (sysctl(mib, 5, &snsr, &slen, NULL, 0) != -1) + charge_current = (double)snsr.value; + } + + power->batteries[i]->charge_full = charge_full; + power->batteries[i]->charge_current = charge_current; + } +#elif defined(__FreeBSD__) || defined(__DragonFly__) + int fd, i; + union acpi_battery_ioctl_arg battio; + + if ((fd = open("/dev/acpi", O_RDONLY)) == -1) return; + + for (i = 0; i < power->battery_count; i++) { + battio.unit = i; + if (ioctl(fd, ACPIIO_BATT_GET_BIX, &battio) != -1) + { + if (battio.bif.lfcap == 0) + power->batteries[i]->charge_full = battio.bif.dcap; + else + power->batteries[i]->charge_full = battio.bif.lfcap; + } + power->batteries[i]->vendor = strdup(battio.bix.oeminfo); + power->batteries[i]->model = strdup(battio.bix.model); + battio.unit = i; + if (ioctl(fd, ACPIIO_BATT_GET_BST, &battio) != -1) + power->batteries[i]->charge_current = battio.bst.cap; + + if (battio.bst.state == ACPI_BATT_STAT_NOT_PRESENT) + power->batteries[i]->present = false; + } + close(fd); + +#elif defined(__linux__) + char path[PATH_MAX]; + struct dirent *dh; + struct stat st; + DIR *dir; + char *buf, *link = NULL, *naming = NULL; + + + for (int i = 0; i < power->battery_count; i++) { + naming = NULL; + + snprintf(path, sizeof(path), "/sys/class/power_supply/%s", power->batteries[i]->name); + + if ((stat(path, &st) < 0) || (!S_ISDIR(st.st_mode))) + continue; + + link = realpath(path, NULL); + if (!link) return; + + dir = opendir(path); + if (!dir) + goto done; + + while ((dh = readdir(dir)) != NULL) + { + char *e; + if (dh->d_name[0] == '.') continue; + + if ((e = strstr(dh->d_name, "_full\0"))) + { + naming = strndup(dh->d_name, e - dh->d_name); + break; + } + } + closedir(dir); + + if (naming) + { + snprintf(path, sizeof(path), "%s/%s_full", link, naming); + buf = file_contents(path); + if (buf) + { + power->batteries[i]->charge_full = atol(buf); + free(buf); + } + snprintf(path, sizeof(path), "%s/%s_now", link, naming); + buf = file_contents(path); + if (buf) + { + power->batteries[i]->charge_current = atol(buf); + free(buf); + } + free(naming); + } + else + { + // Fallback to "coarse" representation. + snprintf(path, sizeof(path), "%s/capacity_level", link); + buf = file_contents(path); + if (buf) + { + power->batteries[i]->charge_full = 100; + if (buf[0] == 'F') + power->batteries[i]->charge_current = 100; + else if (buf[0] == 'H') + power->batteries[i]->charge_current = 75; + else if (buf[0] == 'N') + power->batteries[i]->charge_current = 50; + else if (buf[0] == 'L') + power->batteries[i]->charge_current = 25; + else if (buf[0] == 'C') + power->batteries[i]->charge_current = 5; + free(buf); + } + } + } +done: + if (link) free(link); +#endif +} + +void +system_power_state_get(power_t *power) +{ + int i; + memset(power, 0, sizeof(power_t)); +#if defined(__OpenBSD__) + struct sensor snsr; + size_t slen = sizeof(struct sensor); +#endif + + if (!_power_battery_count_get(power)) + return; + +#if defined(__OpenBSD__) + power->mibs[3] = 9; + power->mibs[4] = 0; + + if (sysctl(power->mibs, 5, &snsr, &slen, NULL, 0) != -1) + power->have_ac = (int)snsr.value; +#elif defined(__FreeBSD__) || defined(__DragonFly__) + int val, fd; + + fd = open("/dev/acpi", O_RDONLY); + if (fd != -1) + { + if (ioctl(fd, ACPIIO_ACAD_GET_STATUS, &val) != -1) + power->have_ac = val; + close(fd); + } +#elif defined(__linux__) + char *buf = file_contents("/sys/class/power_supply/AC/online"); + if (buf) + { + power->have_ac = atoi(buf); + free(buf); + } +#endif + + _battery_state_get(power); + + for (i = 0; i < power->battery_count; i++) { + power->batteries[i]->percent = 100 * + (power->batteries[i]->charge_current / + power->batteries[i]->charge_full); + } +} + +void +system_power_state_free(power_t *power) +{ + for (int i = 0; i < power->battery_count; i++) + { + if (power->batteries[i]->name) + free(power->batteries[i]->name); + if (power->batteries[i]->model) + free(power->batteries[i]->model); + if (power->batteries[i]->vendor) + free(power->batteries[i]->vendor); + free(power->batteries[i]); + } + if (power->batteries) + free(power->batteries); +} diff --git a/src/bin/next/macros.h b/src/bin/next/macros.h new file mode 100644 index 0000000..5e35042 --- /dev/null +++ b/src/bin/next/macros.h @@ -0,0 +1,15 @@ +#ifndef __MACROS_H__ +#define __MACROS_H__ + +#define U64(n) (uint64_t) n +#define I64(n) (int64_t) n +#define U32(n) (uint32_t) n +#define I32(n) (int32_t) n +#define U16(n) (uint16_t) n +#define I16(n) (int16_t) n +#define U8(n) (uint8_t) n +#define I8(n) (int8_t) n + +#define MEMSIZE U64 + +#endif