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/>.
31 #include "cgroup-util.h"
33 typedef struct Group {
43 unsigned cpu_iteration;
45 struct timespec cpu_timestamp;
50 unsigned io_iteration;
51 uint64_t io_input, io_output;
52 struct timespec io_timestamp;
53 uint64_t io_input_bps, io_output_bps;
56 static unsigned arg_depth = 2;
57 static usec_t arg_delay = 1*USEC_PER_SEC;
65 } arg_order = ORDER_CPU;
67 static void group_free(Group *g) {
74 static void group_hashmap_clear(Hashmap *h) {
77 while ((g = hashmap_steal_first(h)))
81 static void group_hashmap_free(Hashmap *h) {
82 group_hashmap_clear(h);
86 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
97 g = hashmap_get(a, path);
99 g = hashmap_get(b, path);
105 g->path = strdup(path);
111 r = hashmap_put(a, g->path, g);
117 assert_se(hashmap_move_one(a, b, path) == 0);
118 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
122 /* Regardless which controller, let's find the maximum number
123 * of processes in any of it */
125 r = cg_enumerate_tasks(controller, path, &f);
130 while (cg_read_pid(f, &pid) > 0)
135 if (g->n_tasks_valid)
136 g->n_tasks = MAX(g->n_tasks, n);
140 g->n_tasks_valid = true;
143 if (streq(controller, "cpuacct")) {
148 r = cg_get_path(controller, path, "cpuacct.usage", &p);
152 r = read_one_line_file(p, &v);
157 r = safe_atou64(v, &new_usage);
162 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
164 if (g->cpu_iteration == iteration - 1) {
167 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
168 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
170 y = new_usage - g->cpu_usage;
173 g->cpu_fraction = (double) y / (double) x;
178 g->cpu_usage = new_usage;
179 g->cpu_timestamp = ts;
180 g->cpu_iteration = iteration;
182 } else if (streq(controller, "memory")) {
185 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
189 r = read_one_line_file(p, &v);
194 r = safe_atou64(v, &g->memory);
200 g->memory_valid = true;
202 } else if (streq(controller, "blkio")) {
204 uint64_t wr = 0, rd = 0;
207 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
218 char line[LINE_MAX], *l;
221 if (!fgets(line, sizeof(line), f))
225 l += strcspn(l, WHITESPACE);
226 l += strspn(l, WHITESPACE);
228 if (first_word(l, "Read")) {
231 } else if (first_word(l, "Write")) {
237 l += strspn(l, WHITESPACE);
238 r = safe_atou64(l, &k);
247 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
249 if (g->io_iteration == iteration - 1) {
252 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
253 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
255 yr = rd - g->io_input;
256 yw = wr - g->io_output;
258 if (yr > 0 || yw > 0) {
259 g->io_input_bps = (yr * 1000000000ULL) / x;
260 g->io_output_bps = (yw * 1000000000ULL) / x;
268 g->io_timestamp = ts;
269 g->io_iteration = iteration;
275 static int refresh_one(
276 const char *controller,
290 if (depth > arg_depth)
293 r = process(controller, path, a, b, iteration);
297 r = cg_enumerate_subgroups(controller, path, &d);
308 r = cg_read_subgroup(d, &fn);
312 p = join(path, "/", fn, NULL);
320 path_kill_slashes(p);
322 r = refresh_one(controller, p, a, b, iteration, depth + 1);
336 static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
341 r = refresh_one("name=systemd", "/", a, b, iteration, 0);
345 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
349 r = refresh_one("memory", "/", a, b, iteration, 0);
353 return refresh_one("blkio", "/", a, b, iteration, 0);
356 static int group_compare(const void*a, const void *b) {
357 const Group *x = *(Group**)a, *y = *(Group**)b;
359 if (path_startswith(y->path, x->path))
361 if (path_startswith(x->path, y->path))
364 if (arg_order == ORDER_CPU) {
365 if (x->cpu_valid && y->cpu_valid) {
367 if (x->cpu_fraction > y->cpu_fraction)
369 else if (x->cpu_fraction < y->cpu_fraction)
371 } else if (x->cpu_valid)
373 else if (y->cpu_valid)
377 if (arg_order == ORDER_TASKS) {
379 if (x->n_tasks_valid && y->n_tasks_valid) {
380 if (x->n_tasks > y->n_tasks)
382 else if (x->n_tasks < y->n_tasks)
384 } else if (x->n_tasks_valid)
386 else if (y->n_tasks_valid)
390 if (arg_order == ORDER_MEMORY) {
391 if (x->memory_valid && y->memory_valid) {
392 if (x->memory > y->memory)
394 else if (x->memory < y->memory)
396 } else if (x->memory_valid)
398 else if (y->memory_valid)
402 if (arg_order == ORDER_IO) {
403 if (x->io_valid && y->io_valid) {
404 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
406 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
408 } else if (x->io_valid)
410 else if (y->io_valid)
414 return strcmp(x->path, y->path);
417 static int display(Hashmap *a) {
421 unsigned rows, n = 0, j;
425 /* Set cursor to top left corner and clear screen */
429 array = alloca(sizeof(Group*) * hashmap_size(a));
431 HASHMAP_FOREACH(g, a, i)
432 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
435 qsort(array, n, sizeof(Group*), group_compare);
437 rows = fd_lines(STDOUT_FILENO);
441 printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
442 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", "Path", arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
443 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks", arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
444 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU", arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
445 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory", arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
446 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
447 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
449 for (j = 0; j < n; j++) {
451 char m[FORMAT_BYTES_MAX];
458 p = ellipsize(g->path, 37, 33);
459 printf("%-37s", p ? p : g->path);
462 if (g->n_tasks_valid)
463 printf(" %7u", g->n_tasks);
468 printf(" %6.1f", g->cpu_fraction*100);
473 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
479 format_bytes(m, sizeof(m), g->io_input_bps));
481 format_bytes(m, sizeof(m), g->io_output_bps));
483 fputs(" - -", stdout);
491 static void help(void) {
493 printf("%s [OPTIONS...]\n\n"
494 "Show top control groups by their resource usage.\n\n"
495 " -h --help Show this help\n"
496 " -p Order by path\n"
497 " -t Order by number of tasks\n"
498 " -c Order by CPU load\n"
499 " -m Order by memory load\n"
500 " -i Order by IO load\n"
501 " -d --delay=DELAY Specify delay\n"
502 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
503 program_invocation_short_name);
506 static int parse_argv(int argc, char *argv[]) {
512 static const struct option options[] = {
513 { "help", no_argument, NULL, 'h' },
514 { "delay", required_argument, NULL, 'd' },
515 { "depth", required_argument, NULL, ARG_DEPTH },
525 while ((c = getopt_long(argc, argv, "hptcmid:", options, NULL)) >= 0) {
534 r = safe_atou(optarg, &arg_depth);
536 log_error("Failed to parse depth parameter.");
543 r = parse_usec(optarg, &arg_delay);
544 if (r < 0 || arg_delay <= 0) {
545 log_error("Failed to parse delay parameter.");
552 arg_order = ORDER_PATH;
556 arg_order = ORDER_TASKS;
560 arg_order = ORDER_CPU;
564 arg_order = ORDER_MEMORY;
568 arg_order = ORDER_IO;
575 log_error("Unknown option code %c", c);
581 log_error("Too many arguments.");
588 int main(int argc, char *argv[]) {
590 Hashmap *a = NULL, *b = NULL;
591 unsigned iteration = 0;
592 usec_t last_refresh = 0;
593 bool quit = false, immediate_refresh = false;
595 log_parse_environment();
598 r = parse_argv(argc, argv);
602 a = hashmap_new(string_hash_func, string_compare_func);
603 b = hashmap_new(string_hash_func, string_compare_func);
605 log_error("Out of memory");
614 char h[FORMAT_TIMESPAN_MAX];
616 t = now(CLOCK_MONOTONIC);
618 if (t >= last_refresh + arg_delay || immediate_refresh) {
620 r = refresh(a, b, iteration++);
624 group_hashmap_clear(b);
631 immediate_refresh = false;
638 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
642 log_error("Couldn't read key: %s", strerror(-r));
646 fputs("\r \r", stdout);
652 immediate_refresh = true;
660 arg_order = ORDER_PATH;
664 arg_order = ORDER_TASKS;
668 arg_order = ORDER_CPU;
672 arg_order = ORDER_MEMORY;
676 arg_order = ORDER_IO;
680 if (arg_delay < USEC_PER_SEC)
681 arg_delay += USEC_PER_MSEC*250;
683 arg_delay += USEC_PER_SEC;
685 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
691 if (arg_delay <= USEC_PER_MSEC*500)
692 arg_delay = USEC_PER_MSEC*250;
693 else if (arg_delay < USEC_PER_MSEC*1250)
694 arg_delay -= USEC_PER_MSEC*250;
696 arg_delay -= USEC_PER_SEC;
698 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
706 "\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"
707 "\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");
713 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
720 log_info("Exiting.");
725 group_hashmap_free(a);
726 group_hashmap_free(b);
728 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;