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