commit 8f16cb3ee7a8516f90d4d7109d26d49f0cf9255d from: Benjamin Stürz date: Thu Jan 04 20:15:23 2024 UTC Initial commit commit - /dev/null commit + 8f16cb3ee7a8516f90d4d7109d26d49f0cf9255d blob - /dev/null blob + 58c2e41aa282594470482a9727a5ac2f119db777 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,13 @@ +CFLAGS = -Wall -g -Og +LIBS = -lutil -lncurses + +all: apmtop + +run: apmtop + ./apmtop + +clean: + rm -f apmtop *.core + +apmtop: apmtop.c + ${CC} -o $@ apmtop.c ${CFLAGS} ${LIBS} blob - /dev/null blob + 56149b80cc8e9511fe07dc44643972135aa79857 (mode 644) --- /dev/null +++ apmtop.c @@ -0,0 +1,399 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXSENSORS 16 +#define LOGLEN 512 +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static int power_sensors[MAXSENSORS]; +static int cputemp_sensor = -1; +static int num_power_sensors = 0; +static int entries = 0; + +enum { + G_CPUUSAGE, + G_CPUSPEED, + G_CPUTEMP, + G_BATTERY, + G_POWER, + NGRAPH, +}; + +struct graph { + WINDOW *win; + int log[LOGLEN]; + bool hide; +}; + +struct display { + struct graph g[NGRAPH]; + + struct cpu_usage *cpus; + int fd_apm, ncpu; + int ngraph; + int width; + int delay; + bool running; +}; + +struct cpu_usage { + struct cpustats new; + struct cpustats old; + struct cpustats diffs; +}; + +static void +find_sensors (void) +{ + for (int i = 0; i < MAXSENSORS; ++i) { + const int mib[3] = { CTL_HW, HW_SENSORS, i }; + struct sensordev dev; + size_t sdlen = sizeof dev; + + if (sysctl (mib, 3, &dev, &sdlen, NULL, 0) == -1) + continue; + + if (dev.maxnumt[SENSOR_WATTS] > 0) { + power_sensors[num_power_sensors++] = i; + } + + if (dev.maxnumt[SENSOR_TEMP] > 0 + && memcmp (dev.xname, "cpu", 3) == 0 + && cputemp_sensor == -1) { + cputemp_sensor = i; + } + } +} + +static int +num_cpu (void) +{ + const int mib[] = { CTL_HW, HW_NCPU }; + int ncpu = 0; + size_t size = sizeof ncpu; + + if (sysctl (mib, 2, &ncpu, &size, NULL, 0) < 0) + return 1; + + return ncpu; +} + +static int64_t +percentages (struct cpustats *out, struct cpu_usage *cpu) +{ + int64_t total_change = 0; + + for (int i = 0; i < 6; ++i) { + int64_t change = cpu->new.cs_time[i] - cpu->old.cs_time[i]; + if (change < 0) + change = INT64_MAX - cpu->old.cs_time[i] - cpu->new.cs_time[i]; + + cpu->diffs.cs_time[i] = change; + total_change += change; + cpu->old.cs_time[i] = cpu->new.cs_time[i]; + } + + if (total_change == 0) + total_change = 1; + + for (int i = 0; i < 6; ++i) { + out->cs_time[i] = (cpu->diffs.cs_time[i] * 1000 + total_change / 2) / total_change; + } + + return total_change; +} + +static int +cpuspeed (void) +{ + const int mib[2] = { CTL_HW, HW_CPUSPEED }; + int speed; + size_t len = sizeof speed; + + if (sysctl (mib, 2, &speed, &len, NULL, 0) == -1) + return -1; + + return speed; +} + +static int +cputemp (void) +{ + const int mib[5] = { CTL_HW, HW_SENSORS, cputemp_sensor, SENSOR_TEMP, 0 }; + struct sensor sensor; + size_t len = sizeof sensor; + int ret; + + ret = sysctl (mib, 5, &sensor, &len, NULL, 0); + + return (ret == -1 || sensor.status & SENSOR_FINVALID) ? -1 : sensor.value / 10000000; +} + +static int +cpu (int ncpu, struct cpu_usage *cpus) +{ + uint64_t sum = 0; + + for (int i = 0; i < ncpu; ++i) { + const int mib[] = { CTL_KERN, KERN_CPUSTATS, i }; + struct cpustats tmp; + size_t size = sizeof tmp; + + if (sysctl (mib, 3, &cpus[i].new, &size, NULL, 0) < 0) + continue; + + percentages (&tmp, &cpus[i]); + sum += 1000 - tmp.cs_time[5]; + } + + return sum / ncpu / 10; +} + +static int +bat (int fd) +{ + struct apm_power_info info; + + if (fd < 0 || ioctl (fd, APM_IOC_GETPOWER, &info) != 0) + return 0; + + return info.battery_life; +} + +static int +power (void) +{ + int64_t total = 0; + + for (int i = 0; i < num_power_sensors; ++i) { + const int mib[5] = { CTL_HW, HW_SENSORS, power_sensors[i], SENSOR_WATTS, 0 }; + struct sensor sensor; + size_t slen = sizeof sensor; + + if (sysctl (mib, 5, &sensor, &slen, NULL, 0) == -1) + continue; + + if (sensor.flags & SENSOR_FINVALID) + continue; + + total += sensor.value; + } + + return total / 1000; +} + +static void +create_win (struct display *dpy) +{ + int j = 0, w, h, wh; + + dpy->ngraph = 0; + for (int i = 0; i < NGRAPH; ++i) { + if (!dpy->g[i].hide) + ++dpy->ngraph; + } + + getmaxyx (stdscr, h, w); + dpy->width = MIN (w - 2, LOGLEN); + --h; + wh = h / dpy->ngraph; + + if (entries > dpy->width) { + const int diff = entries - dpy->width; + for (int i = 0; i < NGRAPH; ++i) { + struct graph *g = &dpy->g[i]; + memmove (g->log, g->log + diff, dpy->width * sizeof (g->log[0])); + } + entries = dpy->width; + } + + for (int i = 0; i < NGRAPH; ++i) { + struct graph *g = &dpy->g[i]; + if (g->win != NULL) + delwin (g->win); + g->win = g->hide ? NULL : newwin (wh, w, 1 + wh * j++, 0); + } +} + +static void +draw_graph (struct graph *g, const char *fmt, int min, int max) +{ + int w, h; + + if (g->hide || g->win == NULL) + return; + + getmaxyx (g->win, h, w); + w -= 2; + h -= 3; + + wclear (g->win); + + for (int i = 0; i < entries; ++i) { + int v = g->log[i]; + if (v < min) + min = v; + if (v > max) + max = v; + } + + if (min < max) { + for (int i = 0; i < entries; ++i) { + int v = g->log[i]; + int y = (int)((float)(v - min) / (max - min) * h); + mvwaddch (g->win, h - y + 1, i + 1, 'X'); + } + } + + box (g->win, 0, 0); + + mvwprintw (g->win, 0, 3, fmt, g->log[entries - 1]); + wprintw (g->win, " (min: %d/max: %d)", min, max); + +} + +static void +draw (struct display *dpy) +{ + clear (); + + draw_graph (&dpy->g[G_CPUUSAGE], "CPU: %d%%", 0, 100); + draw_graph (&dpy->g[G_CPUTEMP], "CPU.: %d C", 0, 100); + draw_graph (&dpy->g[G_CPUSPEED], "CPU: %d MHz", INT_MAX, INT_MIN); + draw_graph (&dpy->g[G_BATTERY], "BAT: %d%%", 0, 100); + draw_graph (&dpy->g[G_POWER], "PWR: %dmW", INT_MAX, INT_MIN); + + mvprintw (0, 0, "Panels: "); + for (int i = 0; i < NGRAPH; ++i) { + addch (dpy->g[i].hide ? ' ' : ('1' + i)); + } + + printw (" %c delay=%d", dpy->running ? 'R' : 'S', dpy->delay); + + refresh (); + for (int i = 0; i < NGRAPH; ++i) + wnoutrefresh (dpy->g[i].win); + doupdate (); + move (0, 0); + +} + +static void +update_graph (int w, struct graph *g, int value) +{ + if (entries == w) { + memmove (g->log, g->log + 1, (LOGLEN - 1) * sizeof (g->log[0])); + g->log[entries - 1] = value; + } else { + g->log[entries] = value; + } + fprintf (stderr, "entries=%d, w=%d, value=%d\n", entries, w, value); +} + +static void +update (struct display *dpy) +{ + int w = dpy->width; + + update_graph (w, &dpy->g[G_CPUUSAGE], cpu (dpy->ncpu, dpy->cpus)); + update_graph (w, &dpy->g[G_CPUTEMP], cputemp ()); + update_graph (w, &dpy->g[G_CPUSPEED], cpuspeed ()); + update_graph (w, &dpy->g[G_BATTERY], bat (dpy->fd_apm)); + update_graph (w, &dpy->g[G_POWER], power ()); + + entries++; + if (entries > w) + entries = w; +} + +int +main (int argc, char *argv[]) +{ + struct display dpy; + + memset (&dpy, 0, sizeof (dpy)); + dpy.ncpu = num_cpu (); + dpy.cpus = calloc (dpy.ncpu, sizeof (struct cpu_usage)); + dpy.fd_apm = open ("/dev/apm", O_RDONLY); + dpy.delay = 10; + dpy.running = true; + find_sensors (); + + setlocale (LC_ALL, ""); + initscr (); + cbreak (); + noecho (); + intrflush (stdscr, FALSE); + keypad (stdscr, TRUE); + halfdelay (dpy.delay); + + entries = 0; + create_win (&dpy); + update (&dpy); + draw (&dpy); + + while (1) { + struct graph *g; + int ch; + + ch = getch (); + + switch (ch) { + case 'q': + goto done; + case KEY_RESIZE: + create_win (&dpy); + break; + case '1': + case '2': + case '3': + case '4': + case '5': + g = &dpy.g[ch - '1']; + if (dpy.ngraph == 1 && !g->hide) + break; + g->hide = !g->hide; + create_win (&dpy); + break; + case '+': + ++dpy.delay; + halfdelay (dpy.delay); + break; + case '-': + if (dpy.delay > 1) + --dpy.delay; + halfdelay (dpy.delay); + break; + case ' ': + dpy.running = !dpy.running; + break; + case ERR: + if (dpy.running) + update (&dpy); + break; + } + + draw (&dpy); + } + +done: + endwin (); + return 0; +}