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