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, path_columns, 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 path_columns = columns_uncached() - 42;
450 if (path_columns < 10)
453 printf("%s%-*s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
454 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", path_columns, "Path",
455 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
456 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks",
457 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
458 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU",
459 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
460 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",
461 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
462 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s",
463 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
464 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s",
465 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
467 for (j = 0; j < n; j++) {
469 char m[FORMAT_BYTES_MAX];
476 p = ellipsize(g->path, path_columns, 33);
477 printf("%-*s", path_columns, p ? p : g->path);
480 if (g->n_tasks_valid)
481 printf(" %7u", g->n_tasks);
486 printf(" %6.1f", g->cpu_fraction*100);
491 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
497 format_bytes(m, sizeof(m), g->io_input_bps));
499 format_bytes(m, sizeof(m), g->io_output_bps));
501 fputs(" - -", stdout);
509 static void help(void) {
511 printf("%s [OPTIONS...]\n\n"
512 "Show top control groups by their resource usage.\n\n"
513 " -h --help Show this help\n"
514 " -p Order by path\n"
515 " -t Order by number of tasks\n"
516 " -c Order by CPU load\n"
517 " -m Order by memory load\n"
518 " -i Order by IO load\n"
519 " -d --delay=DELAY Specify delay\n"
520 " -n --iterations=N Run for N iterations before exiting\n"
521 " -b --batch Run in batch mode, accepting no input\n"
522 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
523 program_invocation_short_name);
526 static int parse_argv(int argc, char *argv[]) {
532 static const struct option options[] = {
533 { "help", no_argument, NULL, 'h' },
534 { "delay", required_argument, NULL, 'd' },
535 { "iterations", required_argument, NULL, 'n' },
536 { "batch", no_argument, NULL, 'b' },
537 { "depth", required_argument, NULL, ARG_DEPTH },
547 while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0) {
556 r = safe_atou(optarg, &arg_depth);
558 log_error("Failed to parse depth parameter.");
565 r = parse_usec(optarg, &arg_delay);
566 if (r < 0 || arg_delay <= 0) {
567 log_error("Failed to parse delay parameter.");
574 r = safe_atou(optarg, &arg_iterations);
576 log_error("Failed to parse iterations parameter.");
587 arg_order = ORDER_PATH;
591 arg_order = ORDER_TASKS;
595 arg_order = ORDER_CPU;
599 arg_order = ORDER_MEMORY;
603 arg_order = ORDER_IO;
610 log_error("Unknown option code %c", c);
616 log_error("Too many arguments.");
623 int main(int argc, char *argv[]) {
625 Hashmap *a = NULL, *b = NULL;
626 unsigned iteration = 0;
627 usec_t last_refresh = 0;
628 bool quit = false, immediate_refresh = false;
630 log_parse_environment();
633 r = parse_argv(argc, argv);
637 a = hashmap_new(string_hash_func, string_compare_func);
638 b = hashmap_new(string_hash_func, string_compare_func);
648 char h[FORMAT_TIMESPAN_MAX];
650 t = now(CLOCK_MONOTONIC);
652 if (t >= last_refresh + arg_delay || immediate_refresh) {
654 r = refresh(a, b, iteration++);
658 group_hashmap_clear(b);
665 immediate_refresh = false;
672 if (arg_iterations && iteration >= arg_iterations)
676 usleep(last_refresh + arg_delay - t);
678 r = read_one_char(stdin, &key,
679 last_refresh + arg_delay - t, NULL);
683 log_error("Couldn't read key: %s", strerror(-r));
688 fputs("\r \r", stdout);
697 immediate_refresh = true;
705 arg_order = ORDER_PATH;
709 arg_order = ORDER_TASKS;
713 arg_order = ORDER_CPU;
717 arg_order = ORDER_MEMORY;
721 arg_order = ORDER_IO;
725 if (arg_delay < USEC_PER_SEC)
726 arg_delay += USEC_PER_MSEC*250;
728 arg_delay += USEC_PER_SEC;
730 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
736 if (arg_delay <= USEC_PER_MSEC*500)
737 arg_delay = USEC_PER_MSEC*250;
738 else if (arg_delay < USEC_PER_MSEC*1250)
739 arg_delay -= USEC_PER_MSEC*250;
741 arg_delay -= USEC_PER_SEC;
743 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
751 "\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"
752 "\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");
758 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
765 log_info("Exiting.");
770 group_hashmap_free(a);
771 group_hashmap_free(b);
773 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;