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 usec_t arg_delay = 1*USEC_PER_SEC;
66 } arg_order = ORDER_CPU;
68 static void group_free(Group *g) {
75 static void group_hashmap_clear(Hashmap *h) {
78 while ((g = hashmap_steal_first(h)))
82 static void group_hashmap_free(Hashmap *h) {
83 group_hashmap_clear(h);
87 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
98 g = hashmap_get(a, path);
100 g = hashmap_get(b, path);
106 g->path = strdup(path);
112 r = hashmap_put(a, g->path, g);
118 assert_se(hashmap_move_one(a, b, path) == 0);
119 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
123 /* Regardless which controller, let's find the maximum number
124 * of processes in any of it */
126 r = cg_enumerate_tasks(controller, path, &f);
131 while (cg_read_pid(f, &pid) > 0)
136 if (g->n_tasks_valid)
137 g->n_tasks = MAX(g->n_tasks, n);
141 g->n_tasks_valid = true;
144 if (streq(controller, "cpuacct")) {
149 r = cg_get_path(controller, path, "cpuacct.usage", &p);
153 r = read_one_line_file(p, &v);
158 r = safe_atou64(v, &new_usage);
163 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
165 if (g->cpu_iteration == iteration - 1) {
168 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
169 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
171 y = new_usage - g->cpu_usage;
174 g->cpu_fraction = (double) y / (double) x;
179 g->cpu_usage = new_usage;
180 g->cpu_timestamp = ts;
181 g->cpu_iteration = iteration;
183 } else if (streq(controller, "memory")) {
186 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
190 r = read_one_line_file(p, &v);
195 r = safe_atou64(v, &g->memory);
201 g->memory_valid = true;
203 } else if (streq(controller, "blkio")) {
205 uint64_t wr = 0, rd = 0;
208 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
219 char line[LINE_MAX], *l;
222 if (!fgets(line, sizeof(line), f))
226 l += strcspn(l, WHITESPACE);
227 l += strspn(l, WHITESPACE);
229 if (first_word(l, "Read")) {
232 } else if (first_word(l, "Write")) {
238 l += strspn(l, WHITESPACE);
239 r = safe_atou64(l, &k);
248 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
250 if (g->io_iteration == iteration - 1) {
253 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
254 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
256 yr = rd - g->io_input;
257 yw = wr - g->io_output;
259 if (yr > 0 || yw > 0) {
260 g->io_input_bps = (yr * 1000000000ULL) / x;
261 g->io_output_bps = (yw * 1000000000ULL) / x;
269 g->io_timestamp = ts;
270 g->io_iteration = iteration;
276 static int refresh_one(
277 const char *controller,
291 if (depth > arg_depth)
294 r = process(controller, path, a, b, iteration);
298 r = cg_enumerate_subgroups(controller, path, &d);
309 r = cg_read_subgroup(d, &fn);
313 p = join(path, "/", fn, NULL);
321 path_kill_slashes(p);
323 r = refresh_one(controller, p, a, b, iteration, depth + 1);
337 static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
342 r = refresh_one("name=systemd", "/", a, b, iteration, 0);
346 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
350 r = refresh_one("memory", "/", a, b, iteration, 0);
355 r = refresh_one("blkio", "/", a, b, iteration, 0);
362 static int group_compare(const void*a, const void *b) {
363 const Group *x = *(Group**)a, *y = *(Group**)b;
365 if (path_startswith(y->path, x->path))
367 if (path_startswith(x->path, y->path))
370 if (arg_order == ORDER_CPU) {
371 if (x->cpu_valid && y->cpu_valid) {
373 if (x->cpu_fraction > y->cpu_fraction)
375 else if (x->cpu_fraction < y->cpu_fraction)
377 } else if (x->cpu_valid)
379 else if (y->cpu_valid)
383 if (arg_order == ORDER_TASKS) {
385 if (x->n_tasks_valid && y->n_tasks_valid) {
386 if (x->n_tasks > y->n_tasks)
388 else if (x->n_tasks < y->n_tasks)
390 } else if (x->n_tasks_valid)
392 else if (y->n_tasks_valid)
396 if (arg_order == ORDER_MEMORY) {
397 if (x->memory_valid && y->memory_valid) {
398 if (x->memory > y->memory)
400 else if (x->memory < y->memory)
402 } else if (x->memory_valid)
404 else if (y->memory_valid)
408 if (arg_order == ORDER_IO) {
409 if (x->io_valid && y->io_valid) {
410 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
412 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
414 } else if (x->io_valid)
416 else if (y->io_valid)
420 return strcmp(x->path, y->path);
423 static int display(Hashmap *a) {
427 unsigned rows, n = 0, j;
431 /* Set cursor to top left corner and clear screen */
435 array = alloca(sizeof(Group*) * hashmap_size(a));
437 HASHMAP_FOREACH(g, a, i)
438 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
441 qsort(array, n, sizeof(Group*), group_compare);
443 rows = fd_lines(STDOUT_FILENO);
447 printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
448 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", "Path", arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
449 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks", arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
450 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU", arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
451 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory", arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
452 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
453 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
455 for (j = 0; j < n; j++) {
457 char m[FORMAT_BYTES_MAX];
464 p = ellipsize(g->path, 37, 33);
465 printf("%-37s", p ? p : g->path);
468 if (g->n_tasks_valid)
469 printf(" %7u", g->n_tasks);
474 printf(" %6.1f", g->cpu_fraction*100);
479 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
485 format_bytes(m, sizeof(m), g->io_input_bps));
487 format_bytes(m, sizeof(m), g->io_output_bps));
489 fputs(" - -", stdout);
497 static void help(void) {
499 printf("%s [OPTIONS...]\n\n"
500 "Show top control groups by their resource usage.\n\n"
501 " -h --help Show this help\n"
502 " -p Order by path\n"
503 " -t Order by number of tasks\n"
504 " -c Order by CPU load\n"
505 " -m Order by memory load\n"
506 " -i Order by IO load\n"
507 " -d --delay=DELAY Specify delay\n"
508 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
509 program_invocation_short_name);
512 static int parse_argv(int argc, char *argv[]) {
518 static const struct option options[] = {
519 { "help", no_argument, NULL, 'h' },
520 { "delay", required_argument, NULL, 'd' },
521 { "depth", required_argument, NULL, ARG_DEPTH },
531 while ((c = getopt_long(argc, argv, "hptcmid:", options, NULL)) >= 0) {
540 r = safe_atou(optarg, &arg_depth);
542 log_error("Failed to parse depth parameter.");
549 r = parse_usec(optarg, &arg_delay);
550 if (r < 0 || arg_delay <= 0) {
551 log_error("Failed to parse delay parameter.");
558 arg_order = ORDER_PATH;
562 arg_order = ORDER_TASKS;
566 arg_order = ORDER_CPU;
570 arg_order = ORDER_MEMORY;
574 arg_order = ORDER_IO;
581 log_error("Unknown option code %c", c);
587 log_error("Too many arguments.");
594 int main(int argc, char *argv[]) {
596 Hashmap *a = NULL, *b = NULL;
597 unsigned iteration = 0;
598 usec_t last_refresh = 0;
599 bool quit = false, immediate_refresh = false;
601 log_parse_environment();
604 r = parse_argv(argc, argv);
608 a = hashmap_new(string_hash_func, string_compare_func);
609 b = hashmap_new(string_hash_func, string_compare_func);
611 log_error("Out of memory");
620 char h[FORMAT_TIMESPAN_MAX];
622 t = now(CLOCK_MONOTONIC);
624 if (t >= last_refresh + arg_delay || immediate_refresh) {
626 r = refresh(a, b, iteration++);
630 group_hashmap_clear(b);
637 immediate_refresh = false;
644 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
648 log_error("Couldn't read key: %s", strerror(-r));
652 fputs("\r \r", stdout);
658 immediate_refresh = true;
666 arg_order = ORDER_PATH;
670 arg_order = ORDER_TASKS;
674 arg_order = ORDER_CPU;
678 arg_order = ORDER_MEMORY;
682 arg_order = ORDER_IO;
686 if (arg_delay < USEC_PER_SEC)
687 arg_delay += USEC_PER_MSEC*250;
689 arg_delay += USEC_PER_SEC;
691 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
697 if (arg_delay <= USEC_PER_MSEC*500)
698 arg_delay = USEC_PER_MSEC*250;
699 else if (arg_delay < USEC_PER_MSEC*1250)
700 arg_delay -= USEC_PER_MSEC*250;
702 arg_delay -= USEC_PER_SEC;
704 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
712 "\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"
713 "\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");
719 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
726 log_info("Exiting.");
731 group_hashmap_free(a);
732 group_hashmap_free(b);
734 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;