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