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