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