]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cgtop/cgtop.c
cgtop: use full terminal width
[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 #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 "path-util.h"
30 #include "util.h"
31 #include "hashmap.h"
32 #include "cgroup-util.h"
33
34 typedef 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
57 static unsigned arg_depth = 3;
58 static unsigned arg_iterations = 0;
59 static bool arg_batch = false;
60 static usec_t arg_delay = 1*USEC_PER_SEC;
61
62 static enum {
63 ORDER_PATH,
64 ORDER_TASKS,
65 ORDER_CPU,
66 ORDER_MEMORY,
67 ORDER_IO
68 } arg_order = ORDER_CPU;
69
70 static void group_free(Group *g) {
71 assert(g);
72
73 free(g->path);
74 free(g);
75 }
76
77 static void group_hashmap_clear(Hashmap *h) {
78 Group *g;
79
80 while ((g = hashmap_steal_first(h)))
81 group_free(g);
82 }
83
84 static void group_hashmap_free(Hashmap *h) {
85 group_hashmap_clear(h);
86 hashmap_free(h);
87 }
88
89 static 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
278 static 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
315 p = strjoin(path, "/", fn, NULL);
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
332 finish:
333 if (d)
334 closedir(d);
335
336 return r;
337 }
338
339 static 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)
346 if (r != -ENOENT)
347 return r;
348 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
349 if (r < 0)
350 if (r != -ENOENT)
351 return r;
352 r = refresh_one("memory", "/", a, b, iteration, 0);
353 if (r < 0)
354 if (r != -ENOENT)
355 return r;
356
357 r = refresh_one("blkio", "/", a, b, iteration, 0);
358 if (r < 0)
359 if (r != -ENOENT)
360 return r;
361 return 0;
362 }
363
364 static 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
425 static int display(Hashmap *a) {
426 Iterator i;
427 Group *g;
428 Group **array;
429 unsigned rows, path_columns, 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 path_columns = columns_uncached() - 42;
450 if (path_columns < 10)
451 path_columns = 10;
452
453 printf("%s%-*s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
454 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", path_columns, "Path",
455 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
456 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks",
457 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
458 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU",
459 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
460 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",
461 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
462 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s",
463 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
464 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s",
465 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
466
467 for (j = 0; j < n; j++) {
468 char *p;
469 char m[FORMAT_BYTES_MAX];
470
471 if (j + 5 > rows)
472 break;
473
474 g = array[j];
475
476 p = ellipsize(g->path, path_columns, 33);
477 printf("%-*s", path_columns, p ? p : g->path);
478 free(p);
479
480 if (g->n_tasks_valid)
481 printf(" %7u", g->n_tasks);
482 else
483 fputs(" -", stdout);
484
485 if (g->cpu_valid)
486 printf(" %6.1f", g->cpu_fraction*100);
487 else
488 fputs(" -", stdout);
489
490 if (g->memory_valid)
491 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
492 else
493 fputs(" -", stdout);
494
495 if (g->io_valid) {
496 printf(" %8s",
497 format_bytes(m, sizeof(m), g->io_input_bps));
498 printf(" %8s",
499 format_bytes(m, sizeof(m), g->io_output_bps));
500 } else
501 fputs(" - -", stdout);
502
503 putchar('\n');
504 }
505
506 return 0;
507 }
508
509 static void help(void) {
510
511 printf("%s [OPTIONS...]\n\n"
512 "Show top control groups by their resource usage.\n\n"
513 " -h --help Show this help\n"
514 " -p Order by path\n"
515 " -t Order by number of tasks\n"
516 " -c Order by CPU load\n"
517 " -m Order by memory load\n"
518 " -i Order by IO load\n"
519 " -d --delay=DELAY Specify delay\n"
520 " -n --iterations=N Run for N iterations before exiting\n"
521 " -b --batch Run in batch mode, accepting no input\n"
522 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
523 program_invocation_short_name);
524 }
525
526 static int parse_argv(int argc, char *argv[]) {
527
528 enum {
529 ARG_DEPTH = 0x100
530 };
531
532 static const struct option options[] = {
533 { "help", no_argument, NULL, 'h' },
534 { "delay", required_argument, NULL, 'd' },
535 { "iterations", required_argument, NULL, 'n' },
536 { "batch", no_argument, NULL, 'b' },
537 { "depth", required_argument, NULL, ARG_DEPTH },
538 { NULL, 0, NULL, 0 }
539 };
540
541 int c;
542 int r;
543
544 assert(argc >= 1);
545 assert(argv);
546
547 while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0) {
548
549 switch (c) {
550
551 case 'h':
552 help();
553 return 0;
554
555 case ARG_DEPTH:
556 r = safe_atou(optarg, &arg_depth);
557 if (r < 0) {
558 log_error("Failed to parse depth parameter.");
559 return -EINVAL;
560 }
561
562 break;
563
564 case 'd':
565 r = parse_usec(optarg, &arg_delay);
566 if (r < 0 || arg_delay <= 0) {
567 log_error("Failed to parse delay parameter.");
568 return -EINVAL;
569 }
570
571 break;
572
573 case 'n':
574 r = safe_atou(optarg, &arg_iterations);
575 if (r < 0) {
576 log_error("Failed to parse iterations parameter.");
577 return -EINVAL;
578 }
579
580 break;
581
582 case 'b':
583 arg_batch = true;
584 break;
585
586 case 'p':
587 arg_order = ORDER_PATH;
588 break;
589
590 case 't':
591 arg_order = ORDER_TASKS;
592 break;
593
594 case 'c':
595 arg_order = ORDER_CPU;
596 break;
597
598 case 'm':
599 arg_order = ORDER_MEMORY;
600 break;
601
602 case 'i':
603 arg_order = ORDER_IO;
604 break;
605
606 case '?':
607 return -EINVAL;
608
609 default:
610 log_error("Unknown option code %c", c);
611 return -EINVAL;
612 }
613 }
614
615 if (optind < argc) {
616 log_error("Too many arguments.");
617 return -EINVAL;
618 }
619
620 return 1;
621 }
622
623 int main(int argc, char *argv[]) {
624 int r;
625 Hashmap *a = NULL, *b = NULL;
626 unsigned iteration = 0;
627 usec_t last_refresh = 0;
628 bool quit = false, immediate_refresh = false;
629
630 log_parse_environment();
631 log_open();
632
633 r = parse_argv(argc, argv);
634 if (r <= 0)
635 goto finish;
636
637 a = hashmap_new(string_hash_func, string_compare_func);
638 b = hashmap_new(string_hash_func, string_compare_func);
639 if (!a || !b) {
640 r = log_oom();
641 goto finish;
642 }
643
644 while (!quit) {
645 Hashmap *c;
646 usec_t t;
647 char key;
648 char h[FORMAT_TIMESPAN_MAX];
649
650 t = now(CLOCK_MONOTONIC);
651
652 if (t >= last_refresh + arg_delay || immediate_refresh) {
653
654 r = refresh(a, b, iteration++);
655 if (r < 0)
656 goto finish;
657
658 group_hashmap_clear(b);
659
660 c = a;
661 a = b;
662 b = c;
663
664 last_refresh = t;
665 immediate_refresh = false;
666 }
667
668 r = display(b);
669 if (r < 0)
670 goto finish;
671
672 if (arg_iterations && iteration >= arg_iterations)
673 break;
674
675 if (arg_batch) {
676 usleep(last_refresh + arg_delay - t);
677 } else {
678 r = read_one_char(stdin, &key,
679 last_refresh + arg_delay - t, NULL);
680 if (r == -ETIMEDOUT)
681 continue;
682 if (r < 0) {
683 log_error("Couldn't read key: %s", strerror(-r));
684 goto finish;
685 }
686 }
687
688 fputs("\r \r", stdout);
689 fflush(stdout);
690
691 if (arg_batch)
692 continue;
693
694 switch (key) {
695
696 case ' ':
697 immediate_refresh = true;
698 break;
699
700 case 'q':
701 quit = true;
702 break;
703
704 case 'p':
705 arg_order = ORDER_PATH;
706 break;
707
708 case 't':
709 arg_order = ORDER_TASKS;
710 break;
711
712 case 'c':
713 arg_order = ORDER_CPU;
714 break;
715
716 case 'm':
717 arg_order = ORDER_MEMORY;
718 break;
719
720 case 'i':
721 arg_order = ORDER_IO;
722 break;
723
724 case '+':
725 if (arg_delay < USEC_PER_SEC)
726 arg_delay += USEC_PER_MSEC*250;
727 else
728 arg_delay += USEC_PER_SEC;
729
730 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
731 fflush(stdout);
732 sleep(1);
733 break;
734
735 case '-':
736 if (arg_delay <= USEC_PER_MSEC*500)
737 arg_delay = USEC_PER_MSEC*250;
738 else if (arg_delay < USEC_PER_MSEC*1250)
739 arg_delay -= USEC_PER_MSEC*250;
740 else
741 arg_delay -= USEC_PER_SEC;
742
743 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
744 fflush(stdout);
745 sleep(1);
746 break;
747
748 case '?':
749 case 'h':
750 fprintf(stdout,
751 "\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"
752 "\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");
753 fflush(stdout);
754 sleep(3);
755 break;
756
757 default:
758 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
759 fflush(stdout);
760 sleep(1);
761 break;
762 }
763 }
764
765 log_info("Exiting.");
766
767 r = 0;
768
769 finish:
770 group_hashmap_free(a);
771 group_hashmap_free(b);
772
773 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
774 }