Blob


1 /*
2 * Copyright (c) 2024 Benjamin Stürz <benni@stuerz.xyz>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 #include <sys/types.h>
17 #include <sys/time.h>
18 #include <sys/ioctl.h>
19 #include <sys/sched.h>
20 #include <sys/sensors.h>
21 #include <sys/sysctl.h>
22 #include <machine/apmvar.h>
24 #include <err.h>
25 #include <fcntl.h>
26 #include <limits.h>
27 #include <locale.h>
28 #include <ncurses.h>
29 #include <stdbool.h>
30 #include <stdint.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
36 #define MAXSENSORS 16
37 #define LOGLEN 512
38 #define MIN(a, b) ((a) < (b) ? (a) : (b))
40 static int power_sensors[MAXSENSORS];
41 static int cputemp_sensor = -1;
42 static int num_power_sensors = 0;
43 static int entries = 0;
45 enum {
46 G_CPUUSAGE,
47 G_CPUSPEED,
48 G_CPUTEMP,
49 G_BATTERY,
50 G_POWER,
51 NGRAPH,
52 };
54 struct graph {
55 WINDOW *win;
56 int log[LOGLEN];
57 bool hide;
58 };
60 struct display {
61 struct graph g[NGRAPH];
63 struct cpu_usage *cpus;
64 int fd_apm, ncpu;
65 int ngraph;
66 int width;
67 int delay;
68 bool running;
69 };
71 struct cpu_usage {
72 struct cpustats new;
73 struct cpustats old;
74 struct cpustats diffs;
75 };
77 static void
78 find_sensors (void)
79 {
80 for (int i = 0; i < MAXSENSORS; ++i) {
81 const int mib[3] = { CTL_HW, HW_SENSORS, i };
82 struct sensordev dev;
83 size_t sdlen = sizeof dev;
85 if (sysctl (mib, 3, &dev, &sdlen, NULL, 0) == -1)
86 continue;
88 if (dev.maxnumt[SENSOR_WATTS] > 0) {
89 power_sensors[num_power_sensors++] = i;
90 }
92 if (dev.maxnumt[SENSOR_TEMP] > 0
93 && memcmp (dev.xname, "cpu", 3) == 0
94 && cputemp_sensor == -1) {
95 cputemp_sensor = i;
96 }
97 }
98 }
100 static int
101 num_cpu (void)
103 const int mib[] = { CTL_HW, HW_NCPU };
104 int ncpu = 0;
105 size_t size = sizeof ncpu;
107 if (sysctl (mib, 2, &ncpu, &size, NULL, 0) < 0)
108 return 1;
110 return ncpu;
113 static int64_t
114 percentages (struct cpustats *out, struct cpu_usage *cpu)
116 int64_t total_change = 0;
118 for (int i = 0; i < 6; ++i) {
119 int64_t change = cpu->new.cs_time[i] - cpu->old.cs_time[i];
120 if (change < 0)
121 change = INT64_MAX - cpu->old.cs_time[i] - cpu->new.cs_time[i];
123 cpu->diffs.cs_time[i] = change;
124 total_change += change;
125 cpu->old.cs_time[i] = cpu->new.cs_time[i];
128 if (total_change == 0)
129 total_change = 1;
131 for (int i = 0; i < 6; ++i) {
132 out->cs_time[i] = (cpu->diffs.cs_time[i] * 1000 + total_change / 2) / total_change;
135 return total_change;
138 static int
139 cpuspeed (void)
141 const int mib[2] = { CTL_HW, HW_CPUSPEED };
142 int speed;
143 size_t len = sizeof speed;
145 if (sysctl (mib, 2, &speed, &len, NULL, 0) == -1)
146 return -1;
148 return speed;
151 static int
152 cputemp (void)
154 const int mib[5] = { CTL_HW, HW_SENSORS, cputemp_sensor, SENSOR_TEMP, 0 };
155 struct sensor sensor;
156 size_t len = sizeof sensor;
157 int ret;
159 ret = sysctl (mib, 5, &sensor, &len, NULL, 0);
161 return (ret == -1 || sensor.status & SENSOR_FINVALID) ? -1 : ((sensor.value - 273150000) / 1000000);
164 static int
165 cpu (int ncpu, struct cpu_usage *cpus)
167 uint64_t sum = 0;
169 for (int i = 0; i < ncpu; ++i) {
170 const int mib[] = { CTL_KERN, KERN_CPUSTATS, i };
171 struct cpustats tmp;
172 size_t size = sizeof tmp;
174 if (sysctl (mib, 3, &cpus[i].new, &size, NULL, 0) < 0)
175 continue;
177 percentages (&tmp, &cpus[i]);
178 sum += 1000 - tmp.cs_time[5];
181 return sum / ncpu / 10;
184 static int
185 bat (int fd)
187 struct apm_power_info info;
189 if (fd < 0 || ioctl (fd, APM_IOC_GETPOWER, &info) != 0)
190 return 0;
192 return info.battery_life;
195 static int
196 power (void)
198 int64_t total = 0;
200 for (int i = 0; i < num_power_sensors; ++i) {
201 const int mib[5] = { CTL_HW, HW_SENSORS, power_sensors[i], SENSOR_WATTS, 0 };
202 struct sensor sensor;
203 size_t slen = sizeof sensor;
205 if (sysctl (mib, 5, &sensor, &slen, NULL, 0) == -1)
206 continue;
208 if (sensor.flags & SENSOR_FINVALID)
209 continue;
211 total += sensor.value;
214 return total / 1000;
217 static void
218 create_win (struct display *dpy)
220 int j = 0, w, h, wh;
222 dpy->ngraph = 0;
223 for (int i = 0; i < NGRAPH; ++i) {
224 if (!dpy->g[i].hide)
225 ++dpy->ngraph;
228 getmaxyx (stdscr, h, w);
229 dpy->width = MIN (w - 2, LOGLEN);
230 --h;
231 wh = h / dpy->ngraph;
233 if (entries > dpy->width) {
234 const int diff = entries - dpy->width;
235 for (int i = 0; i < NGRAPH; ++i) {
236 struct graph *g = &dpy->g[i];
237 memmove (g->log, g->log + diff, dpy->width * sizeof (g->log[0]));
239 entries = dpy->width;
242 for (int i = 0; i < NGRAPH; ++i) {
243 struct graph *g = &dpy->g[i];
244 if (g->win != NULL)
245 delwin (g->win);
246 g->win = g->hide ? NULL : newwin (wh, w, 1 + wh * j++, 0);
250 static void
251 draw_graph (struct graph *g, const char *fmt, int min, int max)
253 int w, h;
255 if (g->hide || g->win == NULL)
256 return;
258 getmaxyx (g->win, h, w);
259 w -= 2;
260 h -= 3;
262 werase (g->win);
264 for (int i = 0; i < entries; ++i) {
265 int v = g->log[i];
266 if (v < min)
267 min = v;
268 if (v > max)
269 max = v;
272 if (min < max) {
273 for (int i = 0; i < entries; ++i) {
274 int v = g->log[i];
275 int y = (int)((float)(v - min) / (max - min) * h);
276 mvwaddch (g->win, h - y + 1, i + 1, 'X');
280 box (g->win, 0, 0);
282 mvwprintw (g->win, 0, 3, fmt, g->log[entries - 1]);
283 wprintw (g->win, " (min: %d/max: %d)", min, max);
287 static void
288 draw (struct display *dpy)
290 erase ();
292 draw_graph (&dpy->g[G_CPUUSAGE], "CPU: %d%%", 0, 100);
293 draw_graph (&dpy->g[G_CPUTEMP], "CPU: %d C", 0, 100);
294 draw_graph (&dpy->g[G_CPUSPEED], "CPU: %d MHz", INT_MAX, INT_MIN);
295 draw_graph (&dpy->g[G_BATTERY], "BAT: %d%%", 0, 100);
296 draw_graph (&dpy->g[G_POWER], "PWR: %dmW", INT_MAX, INT_MIN);
298 mvprintw (0, 0, "Panels: ");
299 for (int i = 0; i < NGRAPH; ++i) {
300 addch (dpy->g[i].hide ? ' ' : ('1' + i));
303 printw (" %c delay=%d", dpy->running ? 'R' : 'S', dpy->delay);
305 wnoutrefresh (stdscr);
306 for (int i = 0; i < NGRAPH; ++i)
307 wnoutrefresh (dpy->g[i].win);
308 doupdate ();
311 static void
312 update_graph (int w, struct graph *g, int value)
314 if (entries == w) {
315 memmove (g->log, g->log + 1, (LOGLEN - 1) * sizeof (g->log[0]));
316 g->log[entries - 1] = value;
317 } else {
318 g->log[entries] = value;
322 static void
323 update (struct display *dpy)
325 int w = dpy->width;
327 update_graph (w, &dpy->g[G_CPUUSAGE], cpu (dpy->ncpu, dpy->cpus));
328 update_graph (w, &dpy->g[G_CPUTEMP], cputemp ());
329 update_graph (w, &dpy->g[G_CPUSPEED], cpuspeed ());
330 update_graph (w, &dpy->g[G_BATTERY], bat (dpy->fd_apm));
331 update_graph (w, &dpy->g[G_POWER], power ());
333 entries++;
334 if (entries > w)
335 entries = w;
338 static int
339 usage (void)
341 fputs ("usage: apmtop [-V] [-d delay]\n", stderr);
342 return 1;
345 int
346 main (int argc, char *argv[])
348 struct display dpy;
349 int option;
351 // pledge(2) doesn't work, because apmtop(1) needs sysctl(2).
352 unveil ("/usr/share/terminfo", "r");
353 unveil ("/dev/apm", "r");
354 unveil (NULL, NULL);
356 memset (&dpy, 0, sizeof (dpy));
357 dpy.delay = 10;
359 while ((option = getopt (argc, argv, "d:V")) != -1) {
360 const char *errstr;
361 switch (option) {
362 case 'd':
363 dpy.delay = (int)strtonum (optarg, 1, 100, &errstr);
364 if (errstr != NULL)
365 errx (1, "invalid delay '%s': %s", optarg, errstr);
366 break;
367 case 'V':
368 puts ("apmtop-" VERSION);
369 return 1;
370 default:
371 return usage ();
375 argc -= optind;
376 //argv += optind;
377 if (argc > 0)
378 return usage ();
380 // Configure the "display".
381 dpy.ncpu = num_cpu ();
382 dpy.cpus = calloc (dpy.ncpu, sizeof (struct cpu_usage));
383 dpy.fd_apm = open ("/dev/apm", O_RDONLY);
384 if (dpy.fd_apm < 0)
385 warn ("failed to open /dev/apm");
386 dpy.running = true;
387 find_sensors ();
389 // Configure ncurses.
390 setlocale (LC_ALL, "");
391 initscr ();
392 cbreak ();
393 noecho ();
394 intrflush (stdscr, FALSE);
395 keypad (stdscr, TRUE);
396 halfdelay (dpy.delay);
397 curs_set (0);
399 // Bootstrap the process.
400 entries = 0;
401 create_win (&dpy);
402 update (&dpy);
403 draw (&dpy);
405 while (1) {
406 struct graph *g;
407 int ch;
409 ch = getch ();
411 switch (ch) {
412 case 'q':
413 goto done;
414 case KEY_RESIZE:
415 create_win (&dpy);
416 break;
417 case '1':
418 case '2':
419 case '3':
420 case '4':
421 case '5':
422 g = &dpy.g[ch - '1'];
423 if (dpy.ngraph == 1 && !g->hide)
424 break;
425 g->hide = !g->hide;
426 create_win (&dpy);
427 break;
428 case '+':
429 ++dpy.delay;
430 halfdelay (dpy.delay);
431 break;
432 case '-':
433 if (dpy.delay > 1)
434 --dpy.delay;
435 halfdelay (dpy.delay);
436 break;
437 case ' ':
438 dpy.running = !dpy.running;
439 break;
440 case ERR:
441 if (dpy.running)
442 update (&dpy);
443 break;
446 draw (&dpy);
449 done:
450 endwin ();
451 free (dpy.cpus);
452 return 0;