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 = strjoin(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);
619 char h[FORMAT_TIMESPAN_MAX];
621 t = now(CLOCK_MONOTONIC);
623 if (t >= last_refresh + arg_delay || immediate_refresh) {
625 r = refresh(a, b, iteration++);
629 group_hashmap_clear(b);
636 immediate_refresh = false;
643 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
647 log_error("Couldn't read key: %s", strerror(-r));
651 fputs("\r \r", stdout);
657 immediate_refresh = true;
665 arg_order = ORDER_PATH;
669 arg_order = ORDER_TASKS;
673 arg_order = ORDER_CPU;
677 arg_order = ORDER_MEMORY;
681 arg_order = ORDER_IO;
685 if (arg_delay < USEC_PER_SEC)
686 arg_delay += USEC_PER_MSEC*250;
688 arg_delay += USEC_PER_SEC;
690 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
696 if (arg_delay <= USEC_PER_MSEC*500)
697 arg_delay = USEC_PER_MSEC*250;
698 else if (arg_delay < USEC_PER_MSEC*1250)
699 arg_delay -= USEC_PER_MSEC*250;
701 arg_delay -= USEC_PER_SEC;
703 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
711 "\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"
712 "\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");
718 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
725 log_info("Exiting.");
730 group_hashmap_free(a);
731 group_hashmap_free(b);
733 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;