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/>.
22 #define __STDC_FORMAT_MACROS
31 #include "path-util.h"
34 #include "cgroup-util.h"
38 typedef struct Group {
48 unsigned cpu_iteration;
50 struct timespec cpu_timestamp;
55 unsigned io_iteration;
56 uint64_t io_input, io_output;
57 struct timespec io_timestamp;
58 uint64_t io_input_bps, io_output_bps;
61 static unsigned arg_depth = 3;
62 static unsigned arg_iterations = 0;
63 static bool arg_batch = false;
64 static usec_t arg_delay = 1*USEC_PER_SEC;
72 } arg_order = ORDER_CPU;
77 } arg_cpu_type = CPU_PERCENT;
79 static void group_free(Group *g) {
86 static void group_hashmap_clear(Hashmap *h) {
89 while ((g = hashmap_steal_first(h)))
93 static void group_hashmap_free(Hashmap *h) {
94 group_hashmap_clear(h);
98 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
109 g = hashmap_get(a, path);
111 g = hashmap_get(b, path);
117 g->path = strdup(path);
123 r = hashmap_put(a, g->path, g);
129 r = hashmap_move_one(a, b, path);
132 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
136 /* Regardless which controller, let's find the maximum number
137 * of processes in any of it */
139 r = cg_enumerate_processes(controller, path, &f);
144 while (cg_read_pid(f, &pid) > 0)
149 if (g->n_tasks_valid)
150 g->n_tasks = MAX(g->n_tasks, n);
154 g->n_tasks_valid = true;
157 if (streq(controller, "cpuacct")) {
162 r = cg_get_path(controller, path, "cpuacct.usage", &p);
166 r = read_one_line_file(p, &v);
171 r = safe_atou64(v, &new_usage);
176 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
178 if (g->cpu_iteration == iteration - 1) {
181 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
182 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
184 y = new_usage - g->cpu_usage;
187 g->cpu_fraction = (double) y / (double) x;
192 g->cpu_usage = new_usage;
193 g->cpu_timestamp = ts;
194 g->cpu_iteration = iteration;
196 } else if (streq(controller, "memory")) {
199 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
203 r = read_one_line_file(p, &v);
208 r = safe_atou64(v, &g->memory);
214 g->memory_valid = true;
216 } else if (streq(controller, "blkio")) {
218 uint64_t wr = 0, rd = 0;
221 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
232 char line[LINE_MAX], *l;
235 if (!fgets(line, sizeof(line), f))
239 l += strcspn(l, WHITESPACE);
240 l += strspn(l, WHITESPACE);
242 if (first_word(l, "Read")) {
245 } else if (first_word(l, "Write")) {
251 l += strspn(l, WHITESPACE);
252 r = safe_atou64(l, &k);
261 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
263 if (g->io_iteration == iteration - 1) {
266 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
267 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
269 yr = rd - g->io_input;
270 yw = wr - g->io_output;
272 if (yr > 0 || yw > 0) {
273 g->io_input_bps = (yr * 1000000000ULL) / x;
274 g->io_output_bps = (yw * 1000000000ULL) / x;
282 g->io_timestamp = ts;
283 g->io_iteration = iteration;
289 static int refresh_one(
290 const char *controller,
304 if (depth > arg_depth)
307 r = process(controller, path, a, b, iteration);
311 r = cg_enumerate_subgroups(controller, path, &d);
322 r = cg_read_subgroup(d, &fn);
326 p = strjoin(path, "/", fn, NULL);
334 path_kill_slashes(p);
336 r = refresh_one(controller, p, a, b, iteration, depth + 1);
350 static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
355 r = refresh_one("name=systemd", "/", a, b, iteration, 0);
359 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
363 r = refresh_one("memory", "/", a, b, iteration, 0);
368 r = refresh_one("blkio", "/", a, b, iteration, 0);
375 static int group_compare(const void*a, const void *b) {
376 const Group *x = *(Group**)a, *y = *(Group**)b;
378 if (path_startswith(y->path, x->path))
380 if (path_startswith(x->path, y->path))
383 if (arg_order == ORDER_CPU) {
384 if (arg_cpu_type == CPU_PERCENT) {
385 if (x->cpu_valid && y->cpu_valid) {
386 if (x->cpu_fraction > y->cpu_fraction)
388 else if (x->cpu_fraction < y->cpu_fraction)
390 } else if (x->cpu_valid)
392 else if (y->cpu_valid)
395 if (x->cpu_usage > y->cpu_usage)
397 else if (x->cpu_usage < y->cpu_usage)
402 if (arg_order == ORDER_TASKS) {
404 if (x->n_tasks_valid && y->n_tasks_valid) {
405 if (x->n_tasks > y->n_tasks)
407 else if (x->n_tasks < y->n_tasks)
409 } else if (x->n_tasks_valid)
411 else if (y->n_tasks_valid)
415 if (arg_order == ORDER_MEMORY) {
416 if (x->memory_valid && y->memory_valid) {
417 if (x->memory > y->memory)
419 else if (x->memory < y->memory)
421 } else if (x->memory_valid)
423 else if (y->memory_valid)
427 if (arg_order == ORDER_IO) {
428 if (x->io_valid && y->io_valid) {
429 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
431 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
433 } else if (x->io_valid)
435 else if (y->io_valid)
439 return strcmp(x->path, y->path);
442 #define ON ANSI_HIGHLIGHT_ON
443 #define OFF ANSI_HIGHLIGHT_OFF
445 static int display(Hashmap *a) {
450 unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 0;
451 char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)];
455 /* Set cursor to top left corner and clear screen */
460 array = alloca(sizeof(Group*) * hashmap_size(a));
462 HASHMAP_FOREACH(g, a, i)
463 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
466 qsort_safe(array, n, sizeof(Group*), group_compare);
468 /* Find the longest names in one run */
469 for (j = 0; j < n; j++) {
470 unsigned cputlen, pathtlen;
472 format_timespan(buffer, sizeof(buffer), (nsec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0);
473 cputlen = strlen(buffer);
474 maxtcpu = MAX(maxtcpu, cputlen);
475 pathtlen = strlen(array[j]->path);
476 maxtpath = MAX(maxtpath, pathtlen);
479 if (arg_cpu_type == CPU_PERCENT)
480 snprintf(buffer, sizeof(buffer), "%6s", "%CPU");
482 snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time");
489 path_columns = columns() - 36 - strlen(buffer);
490 if (path_columns < 10)
493 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
494 arg_order == ORDER_PATH ? ON : "", path_columns, "Path",
495 arg_order == ORDER_PATH ? OFF : "",
496 arg_order == ORDER_TASKS ? ON : "", "Tasks",
497 arg_order == ORDER_TASKS ? OFF : "",
498 arg_order == ORDER_CPU ? ON : "", buffer,
499 arg_order == ORDER_CPU ? OFF : "",
500 arg_order == ORDER_MEMORY ? ON : "", "Memory",
501 arg_order == ORDER_MEMORY ? OFF : "",
502 arg_order == ORDER_IO ? ON : "", "Input/s",
503 arg_order == ORDER_IO ? OFF : "",
504 arg_order == ORDER_IO ? ON : "", "Output/s",
505 arg_order == ORDER_IO ? OFF : "");
507 path_columns = maxtpath;
509 for (j = 0; j < n; j++) {
512 if (on_tty() && j + 5 > rows)
517 p = ellipsize(g->path, path_columns, 33);
518 printf("%-*s", path_columns, p ? p : g->path);
521 if (g->n_tasks_valid)
522 printf(" %7u", g->n_tasks);
526 if (arg_cpu_type == CPU_PERCENT) {
528 printf(" %6.1f", g->cpu_fraction*100);
532 printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (nsec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
535 printf(" %8s", format_bytes(buffer, sizeof(buffer), g->memory));
541 format_bytes(buffer, sizeof(buffer), g->io_input_bps));
543 format_bytes(buffer, sizeof(buffer), g->io_output_bps));
545 fputs(" - -", stdout);
553 static void help(void) {
554 printf("%s [OPTIONS...]\n\n"
555 "Show top control groups by their resource usage.\n\n"
556 " -h --help Show this help\n"
557 " --version Print version and exit\n"
558 " -p Order by path\n"
559 " -t Order by number of tasks\n"
560 " -c Order by CPU load\n"
561 " -m Order by memory load\n"
562 " -i Order by IO load\n"
563 " --cpu[=TYPE] Show CPU usage as time or percentage (default)\n"
564 " -d --delay=DELAY Delay between updates\n"
565 " -n --iterations=N Run for N iterations before exiting\n"
566 " -b --batch Run in batch mode, accepting no input\n"
567 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
568 , program_invocation_short_name, arg_depth);
571 static int parse_argv(int argc, char *argv[]) {
579 static const struct option options[] = {
580 { "help", no_argument, NULL, 'h' },
581 { "version", no_argument, NULL, ARG_VERSION },
582 { "delay", required_argument, NULL, 'd' },
583 { "iterations", required_argument, NULL, 'n' },
584 { "batch", no_argument, NULL, 'b' },
585 { "depth", required_argument, NULL, ARG_DEPTH },
586 { "cpu", optional_argument, NULL, ARG_CPU_TYPE},
596 while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0)
605 puts(PACKAGE_STRING);
606 puts(SYSTEMD_FEATURES);
611 if (strcmp(optarg, "time") == 0)
612 arg_cpu_type = CPU_TIME;
613 else if (strcmp(optarg, "percentage") == 0)
614 arg_cpu_type = CPU_PERCENT;
621 r = safe_atou(optarg, &arg_depth);
623 log_error("Failed to parse depth parameter.");
630 r = parse_sec(optarg, &arg_delay);
631 if (r < 0 || arg_delay <= 0) {
632 log_error("Failed to parse delay parameter.");
639 r = safe_atou(optarg, &arg_iterations);
641 log_error("Failed to parse iterations parameter.");
652 arg_order = ORDER_PATH;
656 arg_order = ORDER_TASKS;
660 arg_order = ORDER_CPU;
664 arg_order = ORDER_MEMORY;
668 arg_order = ORDER_IO;
675 assert_not_reached("Unhandled option");
679 log_error("Too many arguments.");
686 int main(int argc, char *argv[]) {
688 Hashmap *a = NULL, *b = NULL;
689 unsigned iteration = 0;
690 usec_t last_refresh = 0;
691 bool quit = false, immediate_refresh = false;
693 log_parse_environment();
696 r = parse_argv(argc, argv);
700 a = hashmap_new(&string_hash_ops);
701 b = hashmap_new(&string_hash_ops);
707 signal(SIGWINCH, columns_lines_cache_reset);
716 char h[FORMAT_TIMESPAN_MAX];
718 t = now(CLOCK_MONOTONIC);
720 if (t >= last_refresh + arg_delay || immediate_refresh) {
722 r = refresh(a, b, iteration++);
726 group_hashmap_clear(b);
733 immediate_refresh = false;
740 if (arg_iterations && iteration >= arg_iterations)
744 usleep(last_refresh + arg_delay - t);
746 r = read_one_char(stdin, &key,
747 last_refresh + arg_delay - t, NULL);
751 log_error_errno(r, "Couldn't read key: %m");
756 fputs("\r \r", stdout);
765 immediate_refresh = true;
773 arg_order = ORDER_PATH;
777 arg_order = ORDER_TASKS;
781 arg_order = ORDER_CPU;
785 arg_order = ORDER_MEMORY;
789 arg_order = ORDER_IO;
793 arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
797 if (arg_delay < USEC_PER_SEC)
798 arg_delay += USEC_PER_MSEC*250;
800 arg_delay += USEC_PER_SEC;
802 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
808 if (arg_delay <= USEC_PER_MSEC*500)
809 arg_delay = USEC_PER_MSEC*250;
810 else if (arg_delay < USEC_PER_MSEC*1250)
811 arg_delay -= USEC_PER_MSEC*250;
813 arg_delay -= USEC_PER_SEC;
815 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
823 "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n"
824 "\t<" ON "+" OFF "> Increase delay; <" ON "-" OFF "> Decrease delay; <" ON "%%" OFF "> Toggle time\n"
825 "\t<" ON "q" OFF "> Quit; <" ON "SPACE" OFF "> Refresh");
831 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
841 group_hashmap_free(a);
842 group_hashmap_free(b);
845 log_error_errno(r, "Exiting with failure: %m");