]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cgtop/cgtop.c
shared: add terminal-util.[ch]
[thirdparty/systemd.git] / src / cgtop / cgtop.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #define __STDC_FORMAT_MACROS
23 #include <errno.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdint.h>
27 #include <unistd.h>
28 #include <alloca.h>
29 #include <getopt.h>
30
31 #include "path-util.h"
32 #include "terminal-util.h"
33 #include "util.h"
34 #include "hashmap.h"
35 #include "cgroup-util.h"
36 #include "build.h"
37 #include "fileio.h"
38
39 typedef struct Group {
40 char *path;
41
42 bool n_tasks_valid:1;
43 bool cpu_valid:1;
44 bool memory_valid:1;
45 bool io_valid:1;
46
47 unsigned n_tasks;
48
49 unsigned cpu_iteration;
50 uint64_t cpu_usage;
51 struct timespec cpu_timestamp;
52 double cpu_fraction;
53
54 uint64_t memory;
55
56 unsigned io_iteration;
57 uint64_t io_input, io_output;
58 struct timespec io_timestamp;
59 uint64_t io_input_bps, io_output_bps;
60 } Group;
61
62 static unsigned arg_depth = 3;
63 static unsigned arg_iterations = 0;
64 static bool arg_batch = false;
65 static usec_t arg_delay = 1*USEC_PER_SEC;
66
67 static enum {
68 ORDER_PATH,
69 ORDER_TASKS,
70 ORDER_CPU,
71 ORDER_MEMORY,
72 ORDER_IO
73 } arg_order = ORDER_CPU;
74
75 static enum {
76 CPU_PERCENT,
77 CPU_TIME,
78 } arg_cpu_type = CPU_PERCENT;
79
80 static void group_free(Group *g) {
81 assert(g);
82
83 free(g->path);
84 free(g);
85 }
86
87 static void group_hashmap_clear(Hashmap *h) {
88 Group *g;
89
90 while ((g = hashmap_steal_first(h)))
91 group_free(g);
92 }
93
94 static void group_hashmap_free(Hashmap *h) {
95 group_hashmap_clear(h);
96 hashmap_free(h);
97 }
98
99 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
100 Group *g;
101 int r;
102 FILE *f = NULL;
103 pid_t pid;
104 unsigned n;
105
106 assert(controller);
107 assert(path);
108 assert(a);
109
110 g = hashmap_get(a, path);
111 if (!g) {
112 g = hashmap_get(b, path);
113 if (!g) {
114 g = new0(Group, 1);
115 if (!g)
116 return -ENOMEM;
117
118 g->path = strdup(path);
119 if (!g->path) {
120 group_free(g);
121 return -ENOMEM;
122 }
123
124 r = hashmap_put(a, g->path, g);
125 if (r < 0) {
126 group_free(g);
127 return r;
128 }
129 } else {
130 r = hashmap_move_one(a, b, path);
131 if (r < 0)
132 return r;
133 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
134 }
135 }
136
137 /* Regardless which controller, let's find the maximum number
138 * of processes in any of it */
139
140 r = cg_enumerate_processes(controller, path, &f);
141 if (r < 0)
142 return r;
143
144 n = 0;
145 while (cg_read_pid(f, &pid) > 0)
146 n++;
147 fclose(f);
148
149 if (n > 0) {
150 if (g->n_tasks_valid)
151 g->n_tasks = MAX(g->n_tasks, n);
152 else
153 g->n_tasks = n;
154
155 g->n_tasks_valid = true;
156 }
157
158 if (streq(controller, "cpuacct")) {
159 uint64_t new_usage;
160 char *p, *v;
161 struct timespec ts;
162
163 r = cg_get_path(controller, path, "cpuacct.usage", &p);
164 if (r < 0)
165 return r;
166
167 r = read_one_line_file(p, &v);
168 free(p);
169 if (r < 0)
170 return r;
171
172 r = safe_atou64(v, &new_usage);
173 free(v);
174 if (r < 0)
175 return r;
176
177 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
178
179 if (g->cpu_iteration == iteration - 1) {
180 uint64_t x, y;
181
182 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
183 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
184
185 y = new_usage - g->cpu_usage;
186
187 if (y > 0) {
188 g->cpu_fraction = (double) y / (double) x;
189 g->cpu_valid = true;
190 }
191 }
192
193 g->cpu_usage = new_usage;
194 g->cpu_timestamp = ts;
195 g->cpu_iteration = iteration;
196
197 } else if (streq(controller, "memory")) {
198 char *p, *v;
199
200 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
201 if (r < 0)
202 return r;
203
204 r = read_one_line_file(p, &v);
205 free(p);
206 if (r < 0)
207 return r;
208
209 r = safe_atou64(v, &g->memory);
210 free(v);
211 if (r < 0)
212 return r;
213
214 if (g->memory > 0)
215 g->memory_valid = true;
216
217 } else if (streq(controller, "blkio")) {
218 char *p;
219 uint64_t wr = 0, rd = 0;
220 struct timespec ts;
221
222 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
223 if (r < 0)
224 return r;
225
226 f = fopen(p, "re");
227 free(p);
228
229 if (!f)
230 return -errno;
231
232 for (;;) {
233 char line[LINE_MAX], *l;
234 uint64_t k, *q;
235
236 if (!fgets(line, sizeof(line), f))
237 break;
238
239 l = strstrip(line);
240 l += strcspn(l, WHITESPACE);
241 l += strspn(l, WHITESPACE);
242
243 if (first_word(l, "Read")) {
244 l += 4;
245 q = &rd;
246 } else if (first_word(l, "Write")) {
247 l += 5;
248 q = &wr;
249 } else
250 continue;
251
252 l += strspn(l, WHITESPACE);
253 r = safe_atou64(l, &k);
254 if (r < 0)
255 continue;
256
257 *q += k;
258 }
259
260 fclose(f);
261
262 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
263
264 if (g->io_iteration == iteration - 1) {
265 uint64_t x, yr, yw;
266
267 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
268 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
269
270 yr = rd - g->io_input;
271 yw = wr - g->io_output;
272
273 if (yr > 0 || yw > 0) {
274 g->io_input_bps = (yr * 1000000000ULL) / x;
275 g->io_output_bps = (yw * 1000000000ULL) / x;
276 g->io_valid = true;
277
278 }
279 }
280
281 g->io_input = rd;
282 g->io_output = wr;
283 g->io_timestamp = ts;
284 g->io_iteration = iteration;
285 }
286
287 return 0;
288 }
289
290 static int refresh_one(
291 const char *controller,
292 const char *path,
293 Hashmap *a,
294 Hashmap *b,
295 unsigned iteration,
296 unsigned depth) {
297
298 DIR *d = NULL;
299 int r;
300
301 assert(controller);
302 assert(path);
303 assert(a);
304
305 if (depth > arg_depth)
306 return 0;
307
308 r = process(controller, path, a, b, iteration);
309 if (r < 0)
310 return r;
311
312 r = cg_enumerate_subgroups(controller, path, &d);
313 if (r < 0) {
314 if (r == -ENOENT)
315 return 0;
316
317 return r;
318 }
319
320 for (;;) {
321 char *fn, *p;
322
323 r = cg_read_subgroup(d, &fn);
324 if (r <= 0)
325 goto finish;
326
327 p = strjoin(path, "/", fn, NULL);
328 free(fn);
329
330 if (!p) {
331 r = -ENOMEM;
332 goto finish;
333 }
334
335 path_kill_slashes(p);
336
337 r = refresh_one(controller, p, a, b, iteration, depth + 1);
338 free(p);
339
340 if (r < 0)
341 goto finish;
342 }
343
344 finish:
345 if (d)
346 closedir(d);
347
348 return r;
349 }
350
351 static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
352 int r;
353
354 assert(a);
355
356 r = refresh_one("name=systemd", "/", a, b, iteration, 0);
357 if (r < 0)
358 if (r != -ENOENT)
359 return r;
360 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
361 if (r < 0)
362 if (r != -ENOENT)
363 return r;
364 r = refresh_one("memory", "/", a, b, iteration, 0);
365 if (r < 0)
366 if (r != -ENOENT)
367 return r;
368
369 r = refresh_one("blkio", "/", a, b, iteration, 0);
370 if (r < 0)
371 if (r != -ENOENT)
372 return r;
373 return 0;
374 }
375
376 static int group_compare(const void*a, const void *b) {
377 const Group *x = *(Group**)a, *y = *(Group**)b;
378
379 if (path_startswith(y->path, x->path))
380 return -1;
381 if (path_startswith(x->path, y->path))
382 return 1;
383
384 if (arg_order == ORDER_CPU) {
385 if (arg_cpu_type == CPU_PERCENT) {
386 if (x->cpu_valid && y->cpu_valid) {
387 if (x->cpu_fraction > y->cpu_fraction)
388 return -1;
389 else if (x->cpu_fraction < y->cpu_fraction)
390 return 1;
391 } else if (x->cpu_valid)
392 return -1;
393 else if (y->cpu_valid)
394 return 1;
395 } else {
396 if (x->cpu_usage > y->cpu_usage)
397 return -1;
398 else if (x->cpu_usage < y->cpu_usage)
399 return 1;
400 }
401 }
402
403 if (arg_order == ORDER_TASKS) {
404
405 if (x->n_tasks_valid && y->n_tasks_valid) {
406 if (x->n_tasks > y->n_tasks)
407 return -1;
408 else if (x->n_tasks < y->n_tasks)
409 return 1;
410 } else if (x->n_tasks_valid)
411 return -1;
412 else if (y->n_tasks_valid)
413 return 1;
414 }
415
416 if (arg_order == ORDER_MEMORY) {
417 if (x->memory_valid && y->memory_valid) {
418 if (x->memory > y->memory)
419 return -1;
420 else if (x->memory < y->memory)
421 return 1;
422 } else if (x->memory_valid)
423 return -1;
424 else if (y->memory_valid)
425 return 1;
426 }
427
428 if (arg_order == ORDER_IO) {
429 if (x->io_valid && y->io_valid) {
430 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
431 return -1;
432 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
433 return 1;
434 } else if (x->io_valid)
435 return -1;
436 else if (y->io_valid)
437 return 1;
438 }
439
440 return strcmp(x->path, y->path);
441 }
442
443 #define ON ANSI_HIGHLIGHT_ON
444 #define OFF ANSI_HIGHLIGHT_OFF
445
446 static int display(Hashmap *a) {
447 Iterator i;
448 Group *g;
449 Group **array;
450 signed path_columns;
451 unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */
452 char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)];
453
454 assert(a);
455
456 /* Set cursor to top left corner and clear screen */
457 if (on_tty())
458 fputs("\033[H"
459 "\033[2J", stdout);
460
461 array = alloca(sizeof(Group*) * hashmap_size(a));
462
463 HASHMAP_FOREACH(g, a, i)
464 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
465 array[n++] = g;
466
467 qsort_safe(array, n, sizeof(Group*), group_compare);
468
469 /* Find the longest names in one run */
470 for (j = 0; j < n; j++) {
471 unsigned cputlen, pathtlen;
472
473 format_timespan(buffer, sizeof(buffer), (nsec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0);
474 cputlen = strlen(buffer);
475 maxtcpu = MAX(maxtcpu, cputlen);
476 pathtlen = strlen(array[j]->path);
477 maxtpath = MAX(maxtpath, pathtlen);
478 }
479
480 if (arg_cpu_type == CPU_PERCENT)
481 snprintf(buffer, sizeof(buffer), "%6s", "%CPU");
482 else
483 snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time");
484
485 rows = lines();
486 if (rows <= 10)
487 rows = 10;
488
489 if (on_tty()) {
490 path_columns = columns() - 36 - strlen(buffer);
491 if (path_columns < 10)
492 path_columns = 10;
493
494 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
495 arg_order == ORDER_PATH ? ON : "", path_columns, "Path",
496 arg_order == ORDER_PATH ? OFF : "",
497 arg_order == ORDER_TASKS ? ON : "", "Tasks",
498 arg_order == ORDER_TASKS ? OFF : "",
499 arg_order == ORDER_CPU ? ON : "", buffer,
500 arg_order == ORDER_CPU ? OFF : "",
501 arg_order == ORDER_MEMORY ? ON : "", "Memory",
502 arg_order == ORDER_MEMORY ? OFF : "",
503 arg_order == ORDER_IO ? ON : "", "Input/s",
504 arg_order == ORDER_IO ? OFF : "",
505 arg_order == ORDER_IO ? ON : "", "Output/s",
506 arg_order == ORDER_IO ? OFF : "");
507 } else
508 path_columns = maxtpath;
509
510 for (j = 0; j < n; j++) {
511 char *p;
512
513 if (on_tty() && j + 5 > rows)
514 break;
515
516 g = array[j];
517
518 p = ellipsize(g->path, path_columns, 33);
519 printf("%-*s", path_columns, p ? p : g->path);
520 free(p);
521
522 if (g->n_tasks_valid)
523 printf(" %7u", g->n_tasks);
524 else
525 fputs(" -", stdout);
526
527 if (arg_cpu_type == CPU_PERCENT) {
528 if (g->cpu_valid)
529 printf(" %6.1f", g->cpu_fraction*100);
530 else
531 fputs(" -", stdout);
532 } else
533 printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (nsec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
534
535 if (g->memory_valid)
536 printf(" %8s", format_bytes(buffer, sizeof(buffer), g->memory));
537 else
538 fputs(" -", stdout);
539
540 if (g->io_valid) {
541 printf(" %8s",
542 format_bytes(buffer, sizeof(buffer), g->io_input_bps));
543 printf(" %8s",
544 format_bytes(buffer, sizeof(buffer), g->io_output_bps));
545 } else
546 fputs(" - -", stdout);
547
548 putchar('\n');
549 }
550
551 return 0;
552 }
553
554 static void help(void) {
555 printf("%s [OPTIONS...]\n\n"
556 "Show top control groups by their resource usage.\n\n"
557 " -h --help Show this help\n"
558 " --version Print version and exit\n"
559 " -p Order by path\n"
560 " -t Order by number of tasks\n"
561 " -c Order by CPU load\n"
562 " -m Order by memory load\n"
563 " -i Order by IO load\n"
564 " --cpu[=TYPE] Show CPU usage as time or percentage (default)\n"
565 " -d --delay=DELAY Delay between updates\n"
566 " -n --iterations=N Run for N iterations before exiting\n"
567 " -b --batch Run in batch mode, accepting no input\n"
568 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
569 , program_invocation_short_name, arg_depth);
570 }
571
572 static int parse_argv(int argc, char *argv[]) {
573
574 enum {
575 ARG_VERSION = 0x100,
576 ARG_DEPTH,
577 ARG_CPU_TYPE
578 };
579
580 static const struct option options[] = {
581 { "help", no_argument, NULL, 'h' },
582 { "version", no_argument, NULL, ARG_VERSION },
583 { "delay", required_argument, NULL, 'd' },
584 { "iterations", required_argument, NULL, 'n' },
585 { "batch", no_argument, NULL, 'b' },
586 { "depth", required_argument, NULL, ARG_DEPTH },
587 { "cpu", optional_argument, NULL, ARG_CPU_TYPE},
588 {}
589 };
590
591 int c;
592 int r;
593
594 assert(argc >= 1);
595 assert(argv);
596
597 while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0)
598
599 switch (c) {
600
601 case 'h':
602 help();
603 return 0;
604
605 case ARG_VERSION:
606 puts(PACKAGE_STRING);
607 puts(SYSTEMD_FEATURES);
608 return 0;
609
610 case ARG_CPU_TYPE:
611 if (optarg) {
612 if (strcmp(optarg, "time") == 0)
613 arg_cpu_type = CPU_TIME;
614 else if (strcmp(optarg, "percentage") == 0)
615 arg_cpu_type = CPU_PERCENT;
616 else
617 return -EINVAL;
618 }
619 break;
620
621 case ARG_DEPTH:
622 r = safe_atou(optarg, &arg_depth);
623 if (r < 0) {
624 log_error("Failed to parse depth parameter.");
625 return -EINVAL;
626 }
627
628 break;
629
630 case 'd':
631 r = parse_sec(optarg, &arg_delay);
632 if (r < 0 || arg_delay <= 0) {
633 log_error("Failed to parse delay parameter.");
634 return -EINVAL;
635 }
636
637 break;
638
639 case 'n':
640 r = safe_atou(optarg, &arg_iterations);
641 if (r < 0) {
642 log_error("Failed to parse iterations parameter.");
643 return -EINVAL;
644 }
645
646 break;
647
648 case 'b':
649 arg_batch = true;
650 break;
651
652 case 'p':
653 arg_order = ORDER_PATH;
654 break;
655
656 case 't':
657 arg_order = ORDER_TASKS;
658 break;
659
660 case 'c':
661 arg_order = ORDER_CPU;
662 break;
663
664 case 'm':
665 arg_order = ORDER_MEMORY;
666 break;
667
668 case 'i':
669 arg_order = ORDER_IO;
670 break;
671
672 case '?':
673 return -EINVAL;
674
675 default:
676 assert_not_reached("Unhandled option");
677 }
678
679 if (optind < argc) {
680 log_error("Too many arguments.");
681 return -EINVAL;
682 }
683
684 return 1;
685 }
686
687 int main(int argc, char *argv[]) {
688 int r;
689 Hashmap *a = NULL, *b = NULL;
690 unsigned iteration = 0;
691 usec_t last_refresh = 0;
692 bool quit = false, immediate_refresh = false;
693
694 log_parse_environment();
695 log_open();
696
697 r = parse_argv(argc, argv);
698 if (r <= 0)
699 goto finish;
700
701 a = hashmap_new(&string_hash_ops);
702 b = hashmap_new(&string_hash_ops);
703 if (!a || !b) {
704 r = log_oom();
705 goto finish;
706 }
707
708 signal(SIGWINCH, columns_lines_cache_reset);
709
710 if (!on_tty())
711 arg_iterations = 1;
712
713 while (!quit) {
714 Hashmap *c;
715 usec_t t;
716 char key;
717 char h[FORMAT_TIMESPAN_MAX];
718
719 t = now(CLOCK_MONOTONIC);
720
721 if (t >= last_refresh + arg_delay || immediate_refresh) {
722
723 r = refresh(a, b, iteration++);
724 if (r < 0)
725 goto finish;
726
727 group_hashmap_clear(b);
728
729 c = a;
730 a = b;
731 b = c;
732
733 last_refresh = t;
734 immediate_refresh = false;
735 }
736
737 r = display(b);
738 if (r < 0)
739 goto finish;
740
741 if (arg_iterations && iteration >= arg_iterations)
742 break;
743
744 if (arg_batch) {
745 usleep(last_refresh + arg_delay - t);
746 } else {
747 r = read_one_char(stdin, &key,
748 last_refresh + arg_delay - t, NULL);
749 if (r == -ETIMEDOUT)
750 continue;
751 if (r < 0) {
752 log_error_errno(r, "Couldn't read key: %m");
753 goto finish;
754 }
755 }
756
757 fputs("\r \r", stdout);
758 fflush(stdout);
759
760 if (arg_batch)
761 continue;
762
763 switch (key) {
764
765 case ' ':
766 immediate_refresh = true;
767 break;
768
769 case 'q':
770 quit = true;
771 break;
772
773 case 'p':
774 arg_order = ORDER_PATH;
775 break;
776
777 case 't':
778 arg_order = ORDER_TASKS;
779 break;
780
781 case 'c':
782 arg_order = ORDER_CPU;
783 break;
784
785 case 'm':
786 arg_order = ORDER_MEMORY;
787 break;
788
789 case 'i':
790 arg_order = ORDER_IO;
791 break;
792
793 case '%':
794 arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
795 break;
796
797 case '+':
798 if (arg_delay < USEC_PER_SEC)
799 arg_delay += USEC_PER_MSEC*250;
800 else
801 arg_delay += USEC_PER_SEC;
802
803 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
804 fflush(stdout);
805 sleep(1);
806 break;
807
808 case '-':
809 if (arg_delay <= USEC_PER_MSEC*500)
810 arg_delay = USEC_PER_MSEC*250;
811 else if (arg_delay < USEC_PER_MSEC*1250)
812 arg_delay -= USEC_PER_MSEC*250;
813 else
814 arg_delay -= USEC_PER_SEC;
815
816 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
817 fflush(stdout);
818 sleep(1);
819 break;
820
821 case '?':
822 case 'h':
823 fprintf(stdout,
824 "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n"
825 "\t<" ON "+" OFF "> Increase delay; <" ON "-" OFF "> Decrease delay; <" ON "%%" OFF "> Toggle time\n"
826 "\t<" ON "q" OFF "> Quit; <" ON "SPACE" OFF "> Refresh");
827 fflush(stdout);
828 sleep(3);
829 break;
830
831 default:
832 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
833 fflush(stdout);
834 sleep(1);
835 break;
836 }
837 }
838
839 r = 0;
840
841 finish:
842 group_hashmap_free(a);
843 group_hashmap_free(b);
844
845 if (r < 0) {
846 log_error_errno(r, "Exiting with failure: %m");
847 return EXIT_FAILURE;
848 }
849
850 return EXIT_SUCCESS;
851 }