1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
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.
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.
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/>.
29 #include "path-util.h"
32 #include "cgroup-util.h"
36 typedef struct Group {
46 unsigned cpu_iteration;
48 struct timespec cpu_timestamp;
53 unsigned io_iteration;
54 uint64_t io_input, io_output;
55 struct timespec io_timestamp;
56 uint64_t io_input_bps, io_output_bps;
59 static unsigned arg_depth = 3;
60 static unsigned arg_iterations = 0;
61 static bool arg_batch = false;
62 static usec_t arg_delay = 1*USEC_PER_SEC;
70 } arg_order = ORDER_CPU;
72 static void group_free(Group *g) {
79 static void group_hashmap_clear(Hashmap *h) {
82 while ((g = hashmap_steal_first(h)))
86 static void group_hashmap_free(Hashmap *h) {
87 group_hashmap_clear(h);
91 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
102 g = hashmap_get(a, path);
104 g = hashmap_get(b, path);
110 g->path = strdup(path);
116 r = hashmap_put(a, g->path, g);
122 assert_se(hashmap_move_one(a, b, path) == 0);
123 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
127 /* Regardless which controller, let's find the maximum number
128 * of processes in any of it */
130 r = cg_enumerate_tasks(controller, path, &f);
135 while (cg_read_pid(f, &pid) > 0)
140 if (g->n_tasks_valid)
141 g->n_tasks = MAX(g->n_tasks, n);
145 g->n_tasks_valid = true;
148 if (streq(controller, "cpuacct")) {
153 r = cg_get_path(controller, path, "cpuacct.usage", &p);
157 r = read_one_line_file(p, &v);
162 r = safe_atou64(v, &new_usage);
167 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
169 if (g->cpu_iteration == iteration - 1) {
172 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
173 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
175 y = new_usage - g->cpu_usage;
178 g->cpu_fraction = (double) y / (double) x;
183 g->cpu_usage = new_usage;
184 g->cpu_timestamp = ts;
185 g->cpu_iteration = iteration;
187 } else if (streq(controller, "memory")) {
190 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
194 r = read_one_line_file(p, &v);
199 r = safe_atou64(v, &g->memory);
205 g->memory_valid = true;
207 } else if (streq(controller, "blkio")) {
209 uint64_t wr = 0, rd = 0;
212 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
223 char line[LINE_MAX], *l;
226 if (!fgets(line, sizeof(line), f))
230 l += strcspn(l, WHITESPACE);
231 l += strspn(l, WHITESPACE);
233 if (first_word(l, "Read")) {
236 } else if (first_word(l, "Write")) {
242 l += strspn(l, WHITESPACE);
243 r = safe_atou64(l, &k);
252 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
254 if (g->io_iteration == iteration - 1) {
257 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
258 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
260 yr = rd - g->io_input;
261 yw = wr - g->io_output;
263 if (yr > 0 || yw > 0) {
264 g->io_input_bps = (yr * 1000000000ULL) / x;
265 g->io_output_bps = (yw * 1000000000ULL) / x;
273 g->io_timestamp = ts;
274 g->io_iteration = iteration;
280 static int refresh_one(
281 const char *controller,
295 if (depth > arg_depth)
298 r = process(controller, path, a, b, iteration);
302 r = cg_enumerate_subgroups(controller, path, &d);
313 r = cg_read_subgroup(d, &fn);
317 p = strjoin(path, "/", fn, NULL);
325 path_kill_slashes(p);
327 r = refresh_one(controller, p, a, b, iteration, depth + 1);
341 static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
346 r = refresh_one("name=systemd", "/", a, b, iteration, 0);
350 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
354 r = refresh_one("memory", "/", a, b, iteration, 0);
359 r = refresh_one("blkio", "/", a, b, iteration, 0);
366 static int group_compare(const void*a, const void *b) {
367 const Group *x = *(Group**)a, *y = *(Group**)b;
369 if (path_startswith(y->path, x->path))
371 if (path_startswith(x->path, y->path))
374 if (arg_order == ORDER_CPU) {
375 if (x->cpu_valid && y->cpu_valid) {
377 if (x->cpu_fraction > y->cpu_fraction)
379 else if (x->cpu_fraction < y->cpu_fraction)
381 } else if (x->cpu_valid)
383 else if (y->cpu_valid)
387 if (arg_order == ORDER_TASKS) {
389 if (x->n_tasks_valid && y->n_tasks_valid) {
390 if (x->n_tasks > y->n_tasks)
392 else if (x->n_tasks < y->n_tasks)
394 } else if (x->n_tasks_valid)
396 else if (y->n_tasks_valid)
400 if (arg_order == ORDER_MEMORY) {
401 if (x->memory_valid && y->memory_valid) {
402 if (x->memory > y->memory)
404 else if (x->memory < y->memory)
406 } else if (x->memory_valid)
408 else if (y->memory_valid)
412 if (arg_order == ORDER_IO) {
413 if (x->io_valid && y->io_valid) {
414 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
416 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
418 } else if (x->io_valid)
420 else if (y->io_valid)
424 return strcmp(x->path, y->path);
427 static int display(Hashmap *a) {
431 unsigned rows, path_columns, n = 0, j;
435 /* Set cursor to top left corner and clear screen */
439 array = alloca(sizeof(Group*) * hashmap_size(a));
441 HASHMAP_FOREACH(g, a, i)
442 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
445 qsort(array, n, sizeof(Group*), group_compare);
451 path_columns = columns() - 42;
452 if (path_columns < 10)
455 printf("%s%-*s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
456 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", path_columns, "Path",
457 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
458 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks",
459 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
460 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU",
461 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
462 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",
463 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
464 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s",
465 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
466 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s",
467 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
469 for (j = 0; j < n; j++) {
471 char m[FORMAT_BYTES_MAX];
478 p = ellipsize(g->path, path_columns, 33);
479 printf("%-*s", path_columns, p ? p : g->path);
482 if (g->n_tasks_valid)
483 printf(" %7u", g->n_tasks);
488 printf(" %6.1f", g->cpu_fraction*100);
493 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
499 format_bytes(m, sizeof(m), g->io_input_bps));
501 format_bytes(m, sizeof(m), g->io_output_bps));
503 fputs(" - -", stdout);
511 static void help(void) {
513 printf("%s [OPTIONS...]\n\n"
514 "Show top control groups by their resource usage.\n\n"
515 " -h --help Show this help\n"
516 " --version Print version and exit\n"
517 " -p Order by path\n"
518 " -t Order by number of tasks\n"
519 " -c Order by CPU load\n"
520 " -m Order by memory load\n"
521 " -i Order by IO load\n"
522 " -d --delay=DELAY Specify delay\n"
523 " -n --iterations=N Run for N iterations before exiting\n"
524 " -b --batch Run in batch mode, accepting no input\n"
525 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
526 program_invocation_short_name);
529 static void version(void) {
530 puts(PACKAGE_STRING " cgtop");
533 static int parse_argv(int argc, char *argv[]) {
540 static const struct option options[] = {
541 { "help", no_argument, NULL, 'h' },
542 { "version", no_argument, NULL, ARG_VERSION },
543 { "delay", required_argument, NULL, 'd' },
544 { "iterations", required_argument, NULL, 'n' },
545 { "batch", no_argument, NULL, 'b' },
546 { "depth", required_argument, NULL, ARG_DEPTH },
556 while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0) {
569 r = safe_atou(optarg, &arg_depth);
571 log_error("Failed to parse depth parameter.");
578 r = parse_usec(optarg, &arg_delay);
579 if (r < 0 || arg_delay <= 0) {
580 log_error("Failed to parse delay parameter.");
587 r = safe_atou(optarg, &arg_iterations);
589 log_error("Failed to parse iterations parameter.");
600 arg_order = ORDER_PATH;
604 arg_order = ORDER_TASKS;
608 arg_order = ORDER_CPU;
612 arg_order = ORDER_MEMORY;
616 arg_order = ORDER_IO;
623 log_error("Unknown option code %c", c);
629 log_error("Too many arguments.");
636 int main(int argc, char *argv[]) {
638 Hashmap *a = NULL, *b = NULL;
639 unsigned iteration = 0;
640 usec_t last_refresh = 0;
641 bool quit = false, immediate_refresh = false;
643 log_parse_environment();
646 r = parse_argv(argc, argv);
650 a = hashmap_new(string_hash_func, string_compare_func);
651 b = hashmap_new(string_hash_func, string_compare_func);
657 signal(SIGWINCH, columns_lines_cache_reset);
663 char h[FORMAT_TIMESPAN_MAX];
665 t = now(CLOCK_MONOTONIC);
667 if (t >= last_refresh + arg_delay || immediate_refresh) {
669 r = refresh(a, b, iteration++);
673 group_hashmap_clear(b);
680 immediate_refresh = false;
687 if (arg_iterations && iteration >= arg_iterations)
691 usleep(last_refresh + arg_delay - t);
693 r = read_one_char(stdin, &key,
694 last_refresh + arg_delay - t, NULL);
698 log_error("Couldn't read key: %s", strerror(-r));
703 fputs("\r \r", stdout);
712 immediate_refresh = true;
720 arg_order = ORDER_PATH;
724 arg_order = ORDER_TASKS;
728 arg_order = ORDER_CPU;
732 arg_order = ORDER_MEMORY;
736 arg_order = ORDER_IO;
740 if (arg_delay < USEC_PER_SEC)
741 arg_delay += USEC_PER_MSEC*250;
743 arg_delay += USEC_PER_SEC;
745 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
751 if (arg_delay <= USEC_PER_MSEC*500)
752 arg_delay = USEC_PER_MSEC*250;
753 else if (arg_delay < USEC_PER_MSEC*1250)
754 arg_delay -= USEC_PER_MSEC*250;
756 arg_delay -= USEC_PER_SEC;
758 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
766 "\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"
767 "\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");
773 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
780 log_info("Exiting.");
785 group_hashmap_free(a);
786 group_hashmap_free(b);
789 log_error("Exiting with failure: %s", strerror(-r));