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