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"
35 typedef struct Group {
45 unsigned cpu_iteration;
47 struct timespec cpu_timestamp;
52 unsigned io_iteration;
53 uint64_t io_input, io_output;
54 struct timespec io_timestamp;
55 uint64_t io_input_bps, io_output_bps;
58 static unsigned arg_depth = 3;
59 static unsigned arg_iterations = 0;
60 static bool arg_batch = false;
61 static usec_t arg_delay = 1*USEC_PER_SEC;
69 } arg_order = ORDER_CPU;
71 static void group_free(Group *g) {
78 static void group_hashmap_clear(Hashmap *h) {
81 while ((g = hashmap_steal_first(h)))
85 static void group_hashmap_free(Hashmap *h) {
86 group_hashmap_clear(h);
90 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
101 g = hashmap_get(a, path);
103 g = hashmap_get(b, path);
109 g->path = strdup(path);
115 r = hashmap_put(a, g->path, g);
121 assert_se(hashmap_move_one(a, b, path) == 0);
122 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
126 /* Regardless which controller, let's find the maximum number
127 * of processes in any of it */
129 r = cg_enumerate_tasks(controller, path, &f);
134 while (cg_read_pid(f, &pid) > 0)
139 if (g->n_tasks_valid)
140 g->n_tasks = MAX(g->n_tasks, n);
144 g->n_tasks_valid = true;
147 if (streq(controller, "cpuacct")) {
152 r = cg_get_path(controller, path, "cpuacct.usage", &p);
156 r = read_one_line_file(p, &v);
161 r = safe_atou64(v, &new_usage);
166 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
168 if (g->cpu_iteration == iteration - 1) {
171 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
172 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
174 y = new_usage - g->cpu_usage;
177 g->cpu_fraction = (double) y / (double) x;
182 g->cpu_usage = new_usage;
183 g->cpu_timestamp = ts;
184 g->cpu_iteration = iteration;
186 } else if (streq(controller, "memory")) {
189 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
193 r = read_one_line_file(p, &v);
198 r = safe_atou64(v, &g->memory);
204 g->memory_valid = true;
206 } else if (streq(controller, "blkio")) {
208 uint64_t wr = 0, rd = 0;
211 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
222 char line[LINE_MAX], *l;
225 if (!fgets(line, sizeof(line), f))
229 l += strcspn(l, WHITESPACE);
230 l += strspn(l, WHITESPACE);
232 if (first_word(l, "Read")) {
235 } else if (first_word(l, "Write")) {
241 l += strspn(l, WHITESPACE);
242 r = safe_atou64(l, &k);
251 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
253 if (g->io_iteration == iteration - 1) {
256 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
257 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
259 yr = rd - g->io_input;
260 yw = wr - g->io_output;
262 if (yr > 0 || yw > 0) {
263 g->io_input_bps = (yr * 1000000000ULL) / x;
264 g->io_output_bps = (yw * 1000000000ULL) / x;
272 g->io_timestamp = ts;
273 g->io_iteration = iteration;
279 static int refresh_one(
280 const char *controller,
294 if (depth > arg_depth)
297 r = process(controller, path, a, b, iteration);
301 r = cg_enumerate_subgroups(controller, path, &d);
312 r = cg_read_subgroup(d, &fn);
316 p = strjoin(path, "/", fn, NULL);
324 path_kill_slashes(p);
326 r = refresh_one(controller, p, a, b, iteration, depth + 1);
340 static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
345 r = refresh_one("name=systemd", "/", a, b, iteration, 0);
349 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
353 r = refresh_one("memory", "/", a, b, iteration, 0);
358 r = refresh_one("blkio", "/", a, b, iteration, 0);
365 static int group_compare(const void*a, const void *b) {
366 const Group *x = *(Group**)a, *y = *(Group**)b;
368 if (path_startswith(y->path, x->path))
370 if (path_startswith(x->path, y->path))
373 if (arg_order == ORDER_CPU) {
374 if (x->cpu_valid && y->cpu_valid) {
376 if (x->cpu_fraction > y->cpu_fraction)
378 else if (x->cpu_fraction < y->cpu_fraction)
380 } else if (x->cpu_valid)
382 else if (y->cpu_valid)
386 if (arg_order == ORDER_TASKS) {
388 if (x->n_tasks_valid && y->n_tasks_valid) {
389 if (x->n_tasks > y->n_tasks)
391 else if (x->n_tasks < y->n_tasks)
393 } else if (x->n_tasks_valid)
395 else if (y->n_tasks_valid)
399 if (arg_order == ORDER_MEMORY) {
400 if (x->memory_valid && y->memory_valid) {
401 if (x->memory > y->memory)
403 else if (x->memory < y->memory)
405 } else if (x->memory_valid)
407 else if (y->memory_valid)
411 if (arg_order == ORDER_IO) {
412 if (x->io_valid && y->io_valid) {
413 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
415 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
417 } else if (x->io_valid)
419 else if (y->io_valid)
423 return strcmp(x->path, y->path);
426 static int display(Hashmap *a) {
430 unsigned rows, path_columns, n = 0, j;
434 /* Set cursor to top left corner and clear screen */
438 array = alloca(sizeof(Group*) * hashmap_size(a));
440 HASHMAP_FOREACH(g, a, i)
441 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
444 qsort(array, n, sizeof(Group*), group_compare);
450 path_columns = columns() - 42;
451 if (path_columns < 10)
454 printf("%s%-*s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
455 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", path_columns, "Path",
456 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
457 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks",
458 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
459 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU",
460 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
461 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",
462 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
463 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s",
464 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
465 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s",
466 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
468 for (j = 0; j < n; j++) {
470 char m[FORMAT_BYTES_MAX];
477 p = ellipsize(g->path, path_columns, 33);
478 printf("%-*s", path_columns, p ? p : g->path);
481 if (g->n_tasks_valid)
482 printf(" %7u", g->n_tasks);
487 printf(" %6.1f", g->cpu_fraction*100);
492 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
498 format_bytes(m, sizeof(m), g->io_input_bps));
500 format_bytes(m, sizeof(m), g->io_output_bps));
502 fputs(" - -", stdout);
510 static void help(void) {
512 printf("%s [OPTIONS...]\n\n"
513 "Show top control groups by their resource usage.\n\n"
514 " -h --help Show this help\n"
515 " --version Print version and exit\n"
516 " -p Order by path\n"
517 " -t Order by number of tasks\n"
518 " -c Order by CPU load\n"
519 " -m Order by memory load\n"
520 " -i Order by IO load\n"
521 " -d --delay=DELAY Specify delay\n"
522 " -n --iterations=N Run for N iterations before exiting\n"
523 " -b --batch Run in batch mode, accepting no input\n"
524 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
525 program_invocation_short_name);
528 static void version(void) {
529 puts(PACKAGE_STRING " cgtop");
532 static int parse_argv(int argc, char *argv[]) {
539 static const struct option options[] = {
540 { "help", no_argument, NULL, 'h' },
541 { "version", no_argument, NULL, ARG_VERSION },
542 { "delay", required_argument, NULL, 'd' },
543 { "iterations", required_argument, NULL, 'n' },
544 { "batch", no_argument, NULL, 'b' },
545 { "depth", required_argument, NULL, ARG_DEPTH },
555 while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0) {
568 r = safe_atou(optarg, &arg_depth);
570 log_error("Failed to parse depth parameter.");
577 r = parse_usec(optarg, &arg_delay);
578 if (r < 0 || arg_delay <= 0) {
579 log_error("Failed to parse delay parameter.");
586 r = safe_atou(optarg, &arg_iterations);
588 log_error("Failed to parse iterations parameter.");
599 arg_order = ORDER_PATH;
603 arg_order = ORDER_TASKS;
607 arg_order = ORDER_CPU;
611 arg_order = ORDER_MEMORY;
615 arg_order = ORDER_IO;
622 log_error("Unknown option code %c", c);
628 log_error("Too many arguments.");
635 int main(int argc, char *argv[]) {
637 Hashmap *a = NULL, *b = NULL;
638 unsigned iteration = 0;
639 usec_t last_refresh = 0;
640 bool quit = false, immediate_refresh = false;
642 log_parse_environment();
645 r = parse_argv(argc, argv);
649 a = hashmap_new(string_hash_func, string_compare_func);
650 b = hashmap_new(string_hash_func, string_compare_func);
656 signal(SIGWINCH, columns_lines_cache_reset);
662 char h[FORMAT_TIMESPAN_MAX];
664 t = now(CLOCK_MONOTONIC);
666 if (t >= last_refresh + arg_delay || immediate_refresh) {
668 r = refresh(a, b, iteration++);
672 group_hashmap_clear(b);
679 immediate_refresh = false;
686 if (arg_iterations && iteration >= arg_iterations)
690 usleep(last_refresh + arg_delay - t);
692 r = read_one_char(stdin, &key,
693 last_refresh + arg_delay - t, NULL);
697 log_error("Couldn't read key: %s", strerror(-r));
702 fputs("\r \r", stdout);
711 immediate_refresh = true;
719 arg_order = ORDER_PATH;
723 arg_order = ORDER_TASKS;
727 arg_order = ORDER_CPU;
731 arg_order = ORDER_MEMORY;
735 arg_order = ORDER_IO;
739 if (arg_delay < USEC_PER_SEC)
740 arg_delay += USEC_PER_MSEC*250;
742 arg_delay += USEC_PER_SEC;
744 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
750 if (arg_delay <= USEC_PER_MSEC*500)
751 arg_delay = USEC_PER_MSEC*250;
752 else if (arg_delay < USEC_PER_MSEC*1250)
753 arg_delay -= USEC_PER_MSEC*250;
755 arg_delay -= USEC_PER_SEC;
757 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
765 "\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"
766 "\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");
772 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
779 log_info("Exiting.");
784 group_hashmap_free(a);
785 group_hashmap_free(b);
788 log_error("Exiting with failure: %s", strerror(-r));