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 bool arg_batch = false;
60 static usec_t arg_delay = 1*USEC_PER_SEC;
68 } arg_order = ORDER_CPU;
70 static void group_free(Group *g) {
77 static void group_hashmap_clear(Hashmap *h) {
80 while ((g = hashmap_steal_first(h)))
84 static void group_hashmap_free(Hashmap *h) {
85 group_hashmap_clear(h);
89 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
100 g = hashmap_get(a, path);
102 g = hashmap_get(b, path);
108 g->path = strdup(path);
114 r = hashmap_put(a, g->path, g);
120 assert_se(hashmap_move_one(a, b, path) == 0);
121 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
125 /* Regardless which controller, let's find the maximum number
126 * of processes in any of it */
128 r = cg_enumerate_tasks(controller, path, &f);
133 while (cg_read_pid(f, &pid) > 0)
138 if (g->n_tasks_valid)
139 g->n_tasks = MAX(g->n_tasks, n);
143 g->n_tasks_valid = true;
146 if (streq(controller, "cpuacct")) {
151 r = cg_get_path(controller, path, "cpuacct.usage", &p);
155 r = read_one_line_file(p, &v);
160 r = safe_atou64(v, &new_usage);
165 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
167 if (g->cpu_iteration == iteration - 1) {
170 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
171 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
173 y = new_usage - g->cpu_usage;
176 g->cpu_fraction = (double) y / (double) x;
181 g->cpu_usage = new_usage;
182 g->cpu_timestamp = ts;
183 g->cpu_iteration = iteration;
185 } else if (streq(controller, "memory")) {
188 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
192 r = read_one_line_file(p, &v);
197 r = safe_atou64(v, &g->memory);
203 g->memory_valid = true;
205 } else if (streq(controller, "blkio")) {
207 uint64_t wr = 0, rd = 0;
210 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
221 char line[LINE_MAX], *l;
224 if (!fgets(line, sizeof(line), f))
228 l += strcspn(l, WHITESPACE);
229 l += strspn(l, WHITESPACE);
231 if (first_word(l, "Read")) {
234 } else if (first_word(l, "Write")) {
240 l += strspn(l, WHITESPACE);
241 r = safe_atou64(l, &k);
250 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
252 if (g->io_iteration == iteration - 1) {
255 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
256 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
258 yr = rd - g->io_input;
259 yw = wr - g->io_output;
261 if (yr > 0 || yw > 0) {
262 g->io_input_bps = (yr * 1000000000ULL) / x;
263 g->io_output_bps = (yw * 1000000000ULL) / x;
271 g->io_timestamp = ts;
272 g->io_iteration = iteration;
278 static int refresh_one(
279 const char *controller,
293 if (depth > arg_depth)
296 r = process(controller, path, a, b, iteration);
300 r = cg_enumerate_subgroups(controller, path, &d);
311 r = cg_read_subgroup(d, &fn);
315 p = strjoin(path, "/", fn, NULL);
323 path_kill_slashes(p);
325 r = refresh_one(controller, p, a, b, iteration, depth + 1);
339 static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
344 r = refresh_one("name=systemd", "/", a, b, iteration, 0);
348 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
352 r = refresh_one("memory", "/", a, b, iteration, 0);
357 r = refresh_one("blkio", "/", a, b, iteration, 0);
364 static int group_compare(const void*a, const void *b) {
365 const Group *x = *(Group**)a, *y = *(Group**)b;
367 if (path_startswith(y->path, x->path))
369 if (path_startswith(x->path, y->path))
372 if (arg_order == ORDER_CPU) {
373 if (x->cpu_valid && y->cpu_valid) {
375 if (x->cpu_fraction > y->cpu_fraction)
377 else if (x->cpu_fraction < y->cpu_fraction)
379 } else if (x->cpu_valid)
381 else if (y->cpu_valid)
385 if (arg_order == ORDER_TASKS) {
387 if (x->n_tasks_valid && y->n_tasks_valid) {
388 if (x->n_tasks > y->n_tasks)
390 else if (x->n_tasks < y->n_tasks)
392 } else if (x->n_tasks_valid)
394 else if (y->n_tasks_valid)
398 if (arg_order == ORDER_MEMORY) {
399 if (x->memory_valid && y->memory_valid) {
400 if (x->memory > y->memory)
402 else if (x->memory < y->memory)
404 } else if (x->memory_valid)
406 else if (y->memory_valid)
410 if (arg_order == ORDER_IO) {
411 if (x->io_valid && y->io_valid) {
412 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
414 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
416 } else if (x->io_valid)
418 else if (y->io_valid)
422 return strcmp(x->path, y->path);
425 static int display(Hashmap *a) {
429 unsigned rows, n = 0, j;
433 /* Set cursor to top left corner and clear screen */
437 array = alloca(sizeof(Group*) * hashmap_size(a));
439 HASHMAP_FOREACH(g, a, i)
440 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
443 qsort(array, n, sizeof(Group*), group_compare);
445 rows = fd_lines(STDOUT_FILENO);
449 printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
450 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", "Path", arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
451 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks", arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
452 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU", arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
453 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory", arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
454 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
455 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
457 for (j = 0; j < n; j++) {
459 char m[FORMAT_BYTES_MAX];
466 p = ellipsize(g->path, 37, 33);
467 printf("%-37s", p ? p : g->path);
470 if (g->n_tasks_valid)
471 printf(" %7u", g->n_tasks);
476 printf(" %6.1f", g->cpu_fraction*100);
481 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
487 format_bytes(m, sizeof(m), g->io_input_bps));
489 format_bytes(m, sizeof(m), g->io_output_bps));
491 fputs(" - -", stdout);
499 static void help(void) {
501 printf("%s [OPTIONS...]\n\n"
502 "Show top control groups by their resource usage.\n\n"
503 " -h --help Show this help\n"
504 " -p Order by path\n"
505 " -t Order by number of tasks\n"
506 " -c Order by CPU load\n"
507 " -m Order by memory load\n"
508 " -i Order by IO load\n"
509 " -d --delay=DELAY Specify delay\n"
510 " -n --iterations=N Run for N iterations before exiting\n"
511 " -b --batch Run in batch mode, accepting no input\n"
512 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
513 program_invocation_short_name);
516 static int parse_argv(int argc, char *argv[]) {
522 static const struct option options[] = {
523 { "help", no_argument, NULL, 'h' },
524 { "delay", required_argument, NULL, 'd' },
525 { "iterations", required_argument, NULL, 'n' },
526 { "batch", no_argument, NULL, 'b' },
527 { "depth", required_argument, NULL, ARG_DEPTH },
537 while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0) {
546 r = safe_atou(optarg, &arg_depth);
548 log_error("Failed to parse depth parameter.");
555 r = parse_usec(optarg, &arg_delay);
556 if (r < 0 || arg_delay <= 0) {
557 log_error("Failed to parse delay parameter.");
564 r = safe_atou(optarg, &arg_iterations);
566 log_error("Failed to parse iterations parameter.");
577 arg_order = ORDER_PATH;
581 arg_order = ORDER_TASKS;
585 arg_order = ORDER_CPU;
589 arg_order = ORDER_MEMORY;
593 arg_order = ORDER_IO;
600 log_error("Unknown option code %c", c);
606 log_error("Too many arguments.");
613 int main(int argc, char *argv[]) {
615 Hashmap *a = NULL, *b = NULL;
616 unsigned iteration = 0;
617 usec_t last_refresh = 0;
618 bool quit = false, immediate_refresh = false;
620 log_parse_environment();
623 r = parse_argv(argc, argv);
627 a = hashmap_new(string_hash_func, string_compare_func);
628 b = hashmap_new(string_hash_func, string_compare_func);
638 char h[FORMAT_TIMESPAN_MAX];
640 t = now(CLOCK_MONOTONIC);
642 if (t >= last_refresh + arg_delay || immediate_refresh) {
644 r = refresh(a, b, iteration++);
648 group_hashmap_clear(b);
655 immediate_refresh = false;
662 if (arg_iterations && iteration >= arg_iterations)
666 usleep(last_refresh + arg_delay - t);
668 r = read_one_char(stdin, &key,
669 last_refresh + arg_delay - t, NULL);
673 log_error("Couldn't read key: %s", strerror(-r));
678 fputs("\r \r", stdout);
687 immediate_refresh = true;
695 arg_order = ORDER_PATH;
699 arg_order = ORDER_TASKS;
703 arg_order = ORDER_CPU;
707 arg_order = ORDER_MEMORY;
711 arg_order = ORDER_IO;
715 if (arg_delay < USEC_PER_SEC)
716 arg_delay += USEC_PER_MSEC*250;
718 arg_delay += USEC_PER_SEC;
720 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
726 if (arg_delay <= USEC_PER_MSEC*500)
727 arg_delay = USEC_PER_MSEC*250;
728 else if (arg_delay < USEC_PER_MSEC*1250)
729 arg_delay -= USEC_PER_MSEC*250;
731 arg_delay -= USEC_PER_SEC;
733 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
741 "\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"
742 "\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");
748 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
755 log_info("Exiting.");
760 group_hashmap_free(a);
761 group_hashmap_free(b);
763 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;