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);
354 return refresh_one("blkio", "/", a, b, iteration, 0);
357 static int group_compare(const void*a, const void *b) {
358 const Group *x = *(Group**)a, *y = *(Group**)b;
360 if (path_startswith(y->path, x->path))
362 if (path_startswith(x->path, y->path))
365 if (arg_order == ORDER_CPU) {
366 if (x->cpu_valid && y->cpu_valid) {
368 if (x->cpu_fraction > y->cpu_fraction)
370 else if (x->cpu_fraction < y->cpu_fraction)
372 } else if (x->cpu_valid)
374 else if (y->cpu_valid)
378 if (arg_order == ORDER_TASKS) {
380 if (x->n_tasks_valid && y->n_tasks_valid) {
381 if (x->n_tasks > y->n_tasks)
383 else if (x->n_tasks < y->n_tasks)
385 } else if (x->n_tasks_valid)
387 else if (y->n_tasks_valid)
391 if (arg_order == ORDER_MEMORY) {
392 if (x->memory_valid && y->memory_valid) {
393 if (x->memory > y->memory)
395 else if (x->memory < y->memory)
397 } else if (x->memory_valid)
399 else if (y->memory_valid)
403 if (arg_order == ORDER_IO) {
404 if (x->io_valid && y->io_valid) {
405 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
407 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
409 } else if (x->io_valid)
411 else if (y->io_valid)
415 return strcmp(x->path, y->path);
418 static int display(Hashmap *a) {
422 unsigned rows, n = 0, j;
426 /* Set cursor to top left corner and clear screen */
430 array = alloca(sizeof(Group*) * hashmap_size(a));
432 HASHMAP_FOREACH(g, a, i)
433 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
436 qsort(array, n, sizeof(Group*), group_compare);
438 rows = fd_lines(STDOUT_FILENO);
442 printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
443 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", "Path", arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
444 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks", arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
445 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU", arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
446 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory", arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
447 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
448 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
450 for (j = 0; j < n; j++) {
452 char m[FORMAT_BYTES_MAX];
459 p = ellipsize(g->path, 37, 33);
460 printf("%-37s", p ? p : g->path);
463 if (g->n_tasks_valid)
464 printf(" %7u", g->n_tasks);
469 printf(" %6.1f", g->cpu_fraction*100);
474 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
480 format_bytes(m, sizeof(m), g->io_input_bps));
482 format_bytes(m, sizeof(m), g->io_output_bps));
484 fputs(" - -", stdout);
492 static void help(void) {
494 printf("%s [OPTIONS...]\n\n"
495 "Show top control groups by their resource usage.\n\n"
496 " -h --help Show this help\n"
497 " -p Order by path\n"
498 " -t Order by number of tasks\n"
499 " -c Order by CPU load\n"
500 " -m Order by memory load\n"
501 " -i Order by IO load\n"
502 " -d --delay=DELAY Specify delay\n"
503 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
504 program_invocation_short_name);
507 static int parse_argv(int argc, char *argv[]) {
513 static const struct option options[] = {
514 { "help", no_argument, NULL, 'h' },
515 { "delay", required_argument, NULL, 'd' },
516 { "depth", required_argument, NULL, ARG_DEPTH },
526 while ((c = getopt_long(argc, argv, "hptcmid:", options, NULL)) >= 0) {
535 r = safe_atou(optarg, &arg_depth);
537 log_error("Failed to parse depth parameter.");
544 r = parse_usec(optarg, &arg_delay);
545 if (r < 0 || arg_delay <= 0) {
546 log_error("Failed to parse delay parameter.");
553 arg_order = ORDER_PATH;
557 arg_order = ORDER_TASKS;
561 arg_order = ORDER_CPU;
565 arg_order = ORDER_MEMORY;
569 arg_order = ORDER_IO;
576 log_error("Unknown option code %c", c);
582 log_error("Too many arguments.");
589 int main(int argc, char *argv[]) {
591 Hashmap *a = NULL, *b = NULL;
592 unsigned iteration = 0;
593 usec_t last_refresh = 0;
594 bool quit = false, immediate_refresh = false;
596 log_parse_environment();
599 r = parse_argv(argc, argv);
603 a = hashmap_new(string_hash_func, string_compare_func);
604 b = hashmap_new(string_hash_func, string_compare_func);
606 log_error("Out of memory");
615 char h[FORMAT_TIMESPAN_MAX];
617 t = now(CLOCK_MONOTONIC);
619 if (t >= last_refresh + arg_delay || immediate_refresh) {
621 r = refresh(a, b, iteration++);
625 group_hashmap_clear(b);
632 immediate_refresh = false;
639 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
643 log_error("Couldn't read key: %s", strerror(-r));
647 fputs("\r \r", stdout);
653 immediate_refresh = true;
661 arg_order = ORDER_PATH;
665 arg_order = ORDER_TASKS;
669 arg_order = ORDER_CPU;
673 arg_order = ORDER_MEMORY;
677 arg_order = ORDER_IO;
681 if (arg_delay < USEC_PER_SEC)
682 arg_delay += USEC_PER_MSEC*250;
684 arg_delay += USEC_PER_SEC;
686 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
692 if (arg_delay <= USEC_PER_MSEC*500)
693 arg_delay = USEC_PER_MSEC*250;
694 else if (arg_delay < USEC_PER_MSEC*1250)
695 arg_delay -= USEC_PER_MSEC*250;
697 arg_delay -= USEC_PER_SEC;
699 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
707 "\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"
708 "\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");
714 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
721 log_info("Exiting.");
726 group_hashmap_free(a);
727 group_hashmap_free(b);
729 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;