/* * Copyright (c) 2024 Benjamin Stürz * * 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 #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 - 273150000) / 1000000); } 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; werase (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) { erase (); 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); wnoutrefresh (stdscr); for (int i = 0; i < NGRAPH; ++i) wnoutrefresh (dpy->g[i].win); doupdate (); } 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; } } 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; } static int usage (void) { fputs ("usage: apmtop [-V] [-d delay]\n", stderr); return 1; } int main (int argc, char *argv[]) { struct display dpy; int option; // pledge(2) doesn't work, because apmtop(1) needs sysctl(2). unveil ("/usr/share/terminfo", "r"); unveil ("/dev/apm", "r"); unveil (NULL, NULL); memset (&dpy, 0, sizeof (dpy)); dpy.delay = 10; while ((option = getopt (argc, argv, "d:V")) != -1) { const char *errstr; switch (option) { case 'd': dpy.delay = (int)strtonum (optarg, 1, 100, &errstr); if (errstr != NULL) errx (1, "invalid delay '%s': %s", optarg, errstr); break; case 'V': puts ("apmtop-" VERSION); return 1; default: return usage (); } } argc -= optind; //argv += optind; if (argc > 0) return usage (); // Configure the "display". dpy.ncpu = num_cpu (); dpy.cpus = calloc (dpy.ncpu, sizeof (struct cpu_usage)); dpy.fd_apm = open ("/dev/apm", O_RDONLY); if (dpy.fd_apm < 0) warn ("failed to open /dev/apm"); dpy.running = true; find_sensors (); // Configure ncurses. setlocale (LC_ALL, ""); initscr (); cbreak (); noecho (); intrflush (stdscr, FALSE); keypad (stdscr, TRUE); halfdelay (dpy.delay); curs_set (0); // Bootstrap the process. 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 (); free (dpy.cpus); return 0; }