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"
34 typedef struct Group {
44 unsigned cpu_iteration;
46 struct timespec cpu_timestamp;
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;
57 static unsigned arg_depth = 3;
58 static unsigned arg_iterations = 0;
59 static usec_t arg_delay = 1*USEC_PER_SEC;
67 } arg_order = ORDER_CPU;
69 static void group_free(Group *g) {
76 static void group_hashmap_clear(Hashmap *h) {
79 while ((g = hashmap_steal_first(h)))
83 static void group_hashmap_free(Hashmap *h) {
84 group_hashmap_clear(h);
88 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
99 g = hashmap_get(a, path);
101 g = hashmap_get(b, path);
107 g->path = strdup(path);
113 r = hashmap_put(a, g->path, g);
119 assert_se(hashmap_move_one(a, b, path) == 0);
120 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
124 /* Regardless which controller, let's find the maximum number
125 * of processes in any of it */
127 r = cg_enumerate_tasks(controller, path, &f);
132 while (cg_read_pid(f, &pid) > 0)
137 if (g->n_tasks_valid)
138 g->n_tasks = MAX(g->n_tasks, n);
142 g->n_tasks_valid = true;
145 if (streq(controller, "cpuacct")) {
150 r = cg_get_path(controller, path, "cpuacct.usage", &p);
154 r = read_one_line_file(p, &v);
159 r = safe_atou64(v, &new_usage);
164 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
166 if (g->cpu_iteration == iteration - 1) {
169 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
170 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
172 y = new_usage - g->cpu_usage;
175 g->cpu_fraction = (double) y / (double) x;
180 g->cpu_usage = new_usage;
181 g->cpu_timestamp = ts;
182 g->cpu_iteration = iteration;
184 } else if (streq(controller, "memory")) {
187 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
191 r = read_one_line_file(p, &v);
196 r = safe_atou64(v, &g->memory);
202 g->memory_valid = true;
204 } else if (streq(controller, "blkio")) {
206 uint64_t wr = 0, rd = 0;
209 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
220 char line[LINE_MAX], *l;
223 if (!fgets(line, sizeof(line), f))
227 l += strcspn(l, WHITESPACE);
228 l += strspn(l, WHITESPACE);
230 if (first_word(l, "Read")) {
233 } else if (first_word(l, "Write")) {
239 l += strspn(l, WHITESPACE);
240 r = safe_atou64(l, &k);
249 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
251 if (g->io_iteration == iteration - 1) {
254 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
255 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
257 yr = rd - g->io_input;
258 yw = wr - g->io_output;
260 if (yr > 0 || yw > 0) {
261 g->io_input_bps = (yr * 1000000000ULL) / x;
262 g->io_output_bps = (yw * 1000000000ULL) / x;
270 g->io_timestamp = ts;
271 g->io_iteration = iteration;
277 static int refresh_one(
278 const char *controller,
292 if (depth > arg_depth)
295 r = process(controller, path, a, b, iteration);
299 r = cg_enumerate_subgroups(controller, path, &d);
310 r = cg_read_subgroup(d, &fn);
314 p = strjoin(path, "/", fn, NULL);
322 path_kill_slashes(p);
324 r = refresh_one(controller, p, a, b, iteration, depth + 1);
338 static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
343 r = refresh_one("name=systemd", "/", a, b, iteration, 0);
347 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
351 r = refresh_one("memory", "/", a, b, iteration, 0);
356 r = refresh_one("blkio", "/", a, b, iteration, 0);
363 static int group_compare(const void*a, const void *b) {
364 const Group *x = *(Group**)a, *y = *(Group**)b;
366 if (path_startswith(y->path, x->path))
368 if (path_startswith(x->path, y->path))
371 if (arg_order == ORDER_CPU) {
372 if (x->cpu_valid && y->cpu_valid) {
374 if (x->cpu_fraction > y->cpu_fraction)
376 else if (x->cpu_fraction < y->cpu_fraction)
378 } else if (x->cpu_valid)
380 else if (y->cpu_valid)
384 if (arg_order == ORDER_TASKS) {
386 if (x->n_tasks_valid && y->n_tasks_valid) {
387 if (x->n_tasks > y->n_tasks)
389 else if (x->n_tasks < y->n_tasks)
391 } else if (x->n_tasks_valid)
393 else if (y->n_tasks_valid)
397 if (arg_order == ORDER_MEMORY) {
398 if (x->memory_valid && y->memory_valid) {
399 if (x->memory > y->memory)
401 else if (x->memory < y->memory)
403 } else if (x->memory_valid)
405 else if (y->memory_valid)
409 if (arg_order == ORDER_IO) {
410 if (x->io_valid && y->io_valid) {
411 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
413 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
415 } else if (x->io_valid)
417 else if (y->io_valid)
421 return strcmp(x->path, y->path);
424 static int display(Hashmap *a) {
428 unsigned rows, n = 0, j;
432 /* Set cursor to top left corner and clear screen */
436 array = alloca(sizeof(Group*) * hashmap_size(a));
438 HASHMAP_FOREACH(g, a, i)
439 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
442 qsort(array, n, sizeof(Group*), group_compare);
444 rows = fd_lines(STDOUT_FILENO);
448 printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
449 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", "Path", arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
450 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks", arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
451 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU", arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
452 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory", arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
453 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
454 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
456 for (j = 0; j < n; j++) {
458 char m[FORMAT_BYTES_MAX];
465 p = ellipsize(g->path, 37, 33);
466 printf("%-37s", p ? p : g->path);
469 if (g->n_tasks_valid)
470 printf(" %7u", g->n_tasks);
475 printf(" %6.1f", g->cpu_fraction*100);
480 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
486 format_bytes(m, sizeof(m), g->io_input_bps));
488 format_bytes(m, sizeof(m), g->io_output_bps));
490 fputs(" - -", stdout);
498 static void help(void) {
500 printf("%s [OPTIONS...]\n\n"
501 "Show top control groups by their resource usage.\n\n"
502 " -h --help Show this help\n"
503 " -p Order by path\n"
504 " -t Order by number of tasks\n"
505 " -c Order by CPU load\n"
506 " -m Order by memory load\n"
507 " -i Order by IO load\n"
508 " -d --delay=DELAY Specify delay\n"
509 " -n --iterations=N Run for N iterations before exiting\n"
510 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
511 program_invocation_short_name);
514 static int parse_argv(int argc, char *argv[]) {
520 static const struct option options[] = {
521 { "help", no_argument, NULL, 'h' },
522 { "delay", required_argument, NULL, 'd' },
523 { "iterations", required_argument, NULL, 'n' },
524 { "depth", required_argument, NULL, ARG_DEPTH },
534 while ((c = getopt_long(argc, argv, "hptcmin:d:", options, NULL)) >= 0) {
543 r = safe_atou(optarg, &arg_depth);
545 log_error("Failed to parse depth parameter.");
552 r = parse_usec(optarg, &arg_delay);
553 if (r < 0 || arg_delay <= 0) {
554 log_error("Failed to parse delay parameter.");
561 r = safe_atou(optarg, &arg_iterations);
563 log_error("Failed to parse iterations parameter.");
570 arg_order = ORDER_PATH;
574 arg_order = ORDER_TASKS;
578 arg_order = ORDER_CPU;
582 arg_order = ORDER_MEMORY;
586 arg_order = ORDER_IO;
593 log_error("Unknown option code %c", c);
599 log_error("Too many arguments.");
606 int main(int argc, char *argv[]) {
608 Hashmap *a = NULL, *b = NULL;
609 unsigned iteration = 0;
610 usec_t last_refresh = 0;
611 bool quit = false, immediate_refresh = false;
613 log_parse_environment();
616 r = parse_argv(argc, argv);
620 a = hashmap_new(string_hash_func, string_compare_func);
621 b = hashmap_new(string_hash_func, string_compare_func);
631 char h[FORMAT_TIMESPAN_MAX];
633 t = now(CLOCK_MONOTONIC);
635 if (t >= last_refresh + arg_delay || immediate_refresh) {
637 r = refresh(a, b, iteration++);
641 group_hashmap_clear(b);
648 immediate_refresh = false;
655 if (arg_iterations && iteration >= arg_iterations)
658 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
662 log_error("Couldn't read key: %s", strerror(-r));
666 fputs("\r \r", stdout);
672 immediate_refresh = true;
680 arg_order = ORDER_PATH;
684 arg_order = ORDER_TASKS;
688 arg_order = ORDER_CPU;
692 arg_order = ORDER_MEMORY;
696 arg_order = ORDER_IO;
700 if (arg_delay < USEC_PER_SEC)
701 arg_delay += USEC_PER_MSEC*250;
703 arg_delay += USEC_PER_SEC;
705 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
711 if (arg_delay <= USEC_PER_MSEC*500)
712 arg_delay = USEC_PER_MSEC*250;
713 else if (arg_delay < USEC_PER_MSEC*1250)
714 arg_delay -= USEC_PER_MSEC*250;
716 arg_delay -= USEC_PER_SEC;
718 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
726 "\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"
727 "\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");
733 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
740 log_info("Exiting.");
745 group_hashmap_free(a);
746 group_hashmap_free(b);
748 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;