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 <machine/apmvar.h>
18 #include <sys/signal.h>
19 #include <sys/sched.h>
20 #include <sys/sysctl.h>
21 #include <sys/ioctl.h>
22 #include <sys/sensors.h>
24 #include <stdbool.h>
25 #include <ncurses.h>
26 #include <locale.h>
27 #include <limits.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <stdint.h>
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <fcntl.h>
34 #include <time.h>
35 #include <err.h>
37 #define MAXSENSORS 16
38 #define LOGLEN 512
39 #define MIN(a, b) ((a) < (b) ? (a) : (b))
41 static int power_sensors[MAXSENSORS];
42 static int cputemp_sensor = -1;
43 static int num_power_sensors = 0;
44 static int entries = 0;
46 enum {
47 G_CPUUSAGE,
48 G_CPUSPEED,
49 G_CPUTEMP,
50 G_BATTERY,
51 G_POWER,
52 NGRAPH,
53 };
55 struct graph {
56 WINDOW *win;
57 int log[LOGLEN];
58 bool hide;
59 };
61 struct display {
62 struct graph g[NGRAPH];
64 struct cpu_usage *cpus;
65 int fd_apm, ncpu;
66 int ngraph;
67 int width;
68 int delay;
69 bool running;
70 };
72 struct cpu_usage {
73 struct cpustats new;
74 struct cpustats old;
75 struct cpustats diffs;
76 };
78 static void
79 find_sensors (void)
80 {
81 for (int i = 0; i < MAXSENSORS; ++i) {
82 const int mib[3] = { CTL_HW, HW_SENSORS, i };
83 struct sensordev dev;
84 size_t sdlen = sizeof dev;
86 if (sysctl (mib, 3, &dev, &sdlen, NULL, 0) == -1)
87 continue;
89 if (dev.maxnumt[SENSOR_WATTS] > 0) {
90 power_sensors[num_power_sensors++] = i;
91 }
93 if (dev.maxnumt[SENSOR_TEMP] > 0
94 && memcmp (dev.xname, "cpu", 3) == 0
95 && cputemp_sensor == -1) {
96 cputemp_sensor = i;
97 }
98 }
99 }
101 static int
102 num_cpu (void)
104 const int mib[] = { CTL_HW, HW_NCPU };
105 int ncpu = 0;
106 size_t size = sizeof ncpu;
108 if (sysctl (mib, 2, &ncpu, &size, NULL, 0) < 0)
109 return 1;
111 return ncpu;
114 static int64_t
115 percentages (struct cpustats *out, struct cpu_usage *cpu)
117 int64_t total_change = 0;
119 for (int i = 0; i < 6; ++i) {
120 int64_t change = cpu->new.cs_time[i] - cpu->old.cs_time[i];
121 if (change < 0)
122 change = INT64_MAX - cpu->old.cs_time[i] - cpu->new.cs_time[i];
124 cpu->diffs.cs_time[i] = change;
125 total_change += change;
126 cpu->old.cs_time[i] = cpu->new.cs_time[i];
129 if (total_change == 0)
130 total_change = 1;
132 for (int i = 0; i < 6; ++i) {
133 out->cs_time[i] = (cpu->diffs.cs_time[i] * 1000 + total_change / 2) / total_change;
136 return total_change;
139 static int
140 cpuspeed (void)
142 const int mib[2] = { CTL_HW, HW_CPUSPEED };
143 int speed;
144 size_t len = sizeof speed;
146 if (sysctl (mib, 2, &speed, &len, NULL, 0) == -1)
147 return -1;
149 return speed;
152 static int
153 cputemp (void)
155 const int mib[5] = { CTL_HW, HW_SENSORS, cputemp_sensor, SENSOR_TEMP, 0 };
156 struct sensor sensor;
157 size_t len = sizeof sensor;
158 int ret;
160 ret = sysctl (mib, 5, &sensor, &len, NULL, 0);
162 return (ret == -1 || sensor.status & SENSOR_FINVALID) ? -1 : sensor.value / 10000000;
165 static int
166 cpu (int ncpu, struct cpu_usage *cpus)
168 uint64_t sum = 0;
170 for (int i = 0; i < ncpu; ++i) {
171 const int mib[] = { CTL_KERN, KERN_CPUSTATS, i };
172 struct cpustats tmp;
173 size_t size = sizeof tmp;
175 if (sysctl (mib, 3, &cpus[i].new, &size, NULL, 0) < 0)
176 continue;
178 percentages (&tmp, &cpus[i]);
179 sum += 1000 - tmp.cs_time[5];
182 return sum / ncpu / 10;
185 static int
186 bat (int fd)
188 struct apm_power_info info;
190 if (fd < 0 || ioctl (fd, APM_IOC_GETPOWER, &info) != 0)
191 return 0;
193 return info.battery_life;
196 static int
197 power (void)
199 int64_t total = 0;
201 for (int i = 0; i < num_power_sensors; ++i) {
202 const int mib[5] = { CTL_HW, HW_SENSORS, power_sensors[i], SENSOR_WATTS, 0 };
203 struct sensor sensor;
204 size_t slen = sizeof sensor;
206 if (sysctl (mib, 5, &sensor, &slen, NULL, 0) == -1)
207 continue;
209 if (sensor.flags & SENSOR_FINVALID)
210 continue;
212 total += sensor.value;
215 return total / 1000;
218 static void
219 create_win (struct display *dpy)
221 int j = 0, w, h, wh;
223 dpy->ngraph = 0;
224 for (int i = 0; i < NGRAPH; ++i) {
225 if (!dpy->g[i].hide)
226 ++dpy->ngraph;
229 getmaxyx (stdscr, h, w);
230 dpy->width = MIN (w - 2, LOGLEN);
231 --h;
232 wh = h / dpy->ngraph;
234 if (entries > dpy->width) {
235 const int diff = entries - dpy->width;
236 for (int i = 0; i < NGRAPH; ++i) {
237 struct graph *g = &dpy->g[i];
238 memmove (g->log, g->log + diff, dpy->width * sizeof (g->log[0]));
240 entries = dpy->width;
243 for (int i = 0; i < NGRAPH; ++i) {
244 struct graph *g = &dpy->g[i];
245 if (g->win != NULL)
246 delwin (g->win);
247 g->win = g->hide ? NULL : newwin (wh, w, 1 + wh * j++, 0);
251 static void
252 draw_graph (struct graph *g, const char *fmt, int min, int max)
254 int w, h;
256 if (g->hide || g->win == NULL)
257 return;
259 getmaxyx (g->win, h, w);
260 w -= 2;
261 h -= 3;
263 wclear (g->win);
265 for (int i = 0; i < entries; ++i) {
266 int v = g->log[i];
267 if (v < min)
268 min = v;
269 if (v > max)
270 max = v;
273 if (min < max) {
274 for (int i = 0; i < entries; ++i) {
275 int v = g->log[i];
276 int y = (int)((float)(v - min) / (max - min) * h);
277 mvwaddch (g->win, h - y + 1, i + 1, 'X');
281 box (g->win, 0, 0);
283 mvwprintw (g->win, 0, 3, fmt, g->log[entries - 1]);
284 wprintw (g->win, " (min: %d/max: %d)", min, max);
288 static void
289 draw (struct display *dpy)
291 clear ();
293 draw_graph (&dpy->g[G_CPUUSAGE], "CPU: %d%%", 0, 100);
294 draw_graph (&dpy->g[G_CPUTEMP], "CPU.: %d C", 0, 100);
295 draw_graph (&dpy->g[G_CPUSPEED], "CPU: %d MHz", INT_MAX, INT_MIN);
296 draw_graph (&dpy->g[G_BATTERY], "BAT: %d%%", 0, 100);
297 draw_graph (&dpy->g[G_POWER], "PWR: %dmW", INT_MAX, INT_MIN);
299 mvprintw (0, 0, "Panels: ");
300 for (int i = 0; i < NGRAPH; ++i) {
301 addch (dpy->g[i].hide ? ' ' : ('1' + i));
304 printw (" %c delay=%d", dpy->running ? 'R' : 'S', dpy->delay);
306 refresh ();
307 for (int i = 0; i < NGRAPH; ++i)
308 wnoutrefresh (dpy->g[i].win);
309 doupdate ();
310 move (0, 0);
314 static void
315 update_graph (int w, struct graph *g, int value)
317 if (entries == w) {
318 memmove (g->log, g->log + 1, (LOGLEN - 1) * sizeof (g->log[0]));
319 g->log[entries - 1] = value;
320 } else {
321 g->log[entries] = value;
323 fprintf (stderr, "entries=%d, w=%d, value=%d\n", entries, w, value);
326 static void
327 update (struct display *dpy)
329 int w = dpy->width;
331 update_graph (w, &dpy->g[G_CPUUSAGE], cpu (dpy->ncpu, dpy->cpus));
332 update_graph (w, &dpy->g[G_CPUTEMP], cputemp ());
333 update_graph (w, &dpy->g[G_CPUSPEED], cpuspeed ());
334 update_graph (w, &dpy->g[G_BATTERY], bat (dpy->fd_apm));
335 update_graph (w, &dpy->g[G_POWER], power ());
337 entries++;
338 if (entries > w)
339 entries = w;
342 int
343 main (int argc, char *argv[])
345 struct display dpy;
347 // pledge(2) doesn't work, because apmtop(1) needs sysctl(2).
348 unveil ("/usr/share/terminfo", "r");
349 unveil (NULL, NULL);
351 memset (&dpy, 0, sizeof (dpy));
352 dpy.ncpu = num_cpu ();
353 dpy.cpus = calloc (dpy.ncpu, sizeof (struct cpu_usage));
354 dpy.fd_apm = open ("/dev/apm", O_RDONLY);
355 dpy.delay = 10;
356 dpy.running = true;
357 find_sensors ();
359 setlocale (LC_ALL, "");
360 initscr ();
361 cbreak ();
362 noecho ();
363 intrflush (stdscr, FALSE);
364 keypad (stdscr, TRUE);
365 halfdelay (dpy.delay);
367 entries = 0;
368 create_win (&dpy);
369 update (&dpy);
370 draw (&dpy);
372 while (1) {
373 struct graph *g;
374 int ch;
376 ch = getch ();
378 switch (ch) {
379 case 'q':
380 goto done;
381 case KEY_RESIZE:
382 create_win (&dpy);
383 break;
384 case '1':
385 case '2':
386 case '3':
387 case '4':
388 case '5':
389 g = &dpy.g[ch - '1'];
390 if (dpy.ngraph == 1 && !g->hide)
391 break;
392 g->hide = !g->hide;
393 create_win (&dpy);
394 break;
395 case '+':
396 ++dpy.delay;
397 halfdelay (dpy.delay);
398 break;
399 case '-':
400 if (dpy.delay > 1)
401 --dpy.delay;
402 halfdelay (dpy.delay);
403 break;
404 case ' ':
405 dpy.running = !dpy.running;
406 break;
407 case ERR:
408 if (dpy.running)
409 update (&dpy);
410 break;
413 draw (&dpy);
416 done:
417 endwin ();
418 return 0;