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/>.
31 #include "path-util.h"
32 #include "terminal-util.h"
33 #include "process-util.h"
36 #include "cgroup-util.h"
40 typedef struct Group
{
50 unsigned cpu_iteration
;
57 unsigned io_iteration
;
58 uint64_t io_input
, io_output
;
60 uint64_t io_input_bps
, io_output_bps
;
63 static unsigned arg_depth
= 3;
64 static unsigned arg_iterations
= (unsigned) -1;
65 static bool arg_batch
= false;
66 static bool arg_raw
= false;
67 static usec_t arg_delay
= 1*USEC_PER_SEC
;
68 static bool arg_kernel_threads
= false;
69 static bool arg_recursive
= true;
77 } arg_order
= ORDER_CPU
;
82 } arg_cpu_type
= CPU_PERCENT
;
84 static void group_free(Group
*g
) {
91 static void group_hashmap_clear(Hashmap
*h
) {
94 while ((g
= hashmap_steal_first(h
)))
98 static void group_hashmap_free(Hashmap
*h
) {
99 group_hashmap_clear(h
);
103 static const char *maybe_format_bytes(char *buf
, size_t l
, bool is_valid
, off_t t
) {
107 snprintf(buf
, l
, "%jd", t
);
110 return format_bytes(buf
, l
, t
);
114 const char *controller
,
128 g
= hashmap_get(a
, path
);
130 g
= hashmap_get(b
, path
);
136 g
->path
= strdup(path
);
142 r
= hashmap_put(a
, g
->path
, g
);
148 r
= hashmap_move_one(a
, b
, path
);
152 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
156 if (streq(controller
, SYSTEMD_CGROUP_CONTROLLER
)) {
157 _cleanup_fclose_
FILE *f
= NULL
;
160 r
= cg_enumerate_processes(controller
, path
, &f
);
167 while (cg_read_pid(f
, &pid
) > 0) {
169 if (!arg_kernel_threads
&& is_kernel_thread(pid
) > 0)
176 g
->n_tasks_valid
= true;
178 } else if (streq(controller
, "cpuacct") && cg_unified() <= 0) {
179 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
183 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
187 r
= read_one_line_file(p
, &v
);
193 r
= safe_atou64(v
, &new_usage
);
197 timestamp
= now_nsec(CLOCK_MONOTONIC
);
199 if (g
->cpu_iteration
== iteration
- 1 &&
200 (nsec_t
) new_usage
> g
->cpu_usage
) {
204 x
= timestamp
- g
->cpu_timestamp
;
208 y
= (nsec_t
) new_usage
- g
->cpu_usage
;
209 g
->cpu_fraction
= (double) y
/ (double) x
;
213 g
->cpu_usage
= (nsec_t
) new_usage
;
214 g
->cpu_timestamp
= timestamp
;
215 g
->cpu_iteration
= iteration
;
217 } else if (streq(controller
, "memory")) {
218 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
220 if (cg_unified() <= 0)
221 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
223 r
= cg_get_path(controller
, path
, "memory.current", &p
);
227 r
= read_one_line_file(p
, &v
);
233 r
= safe_atou64(v
, &g
->memory
);
238 g
->memory_valid
= true;
240 } else if (streq(controller
, "blkio") && cg_unified() <= 0) {
241 _cleanup_fclose_
FILE *f
= NULL
;
242 _cleanup_free_
char *p
= NULL
;
243 uint64_t wr
= 0, rd
= 0;
246 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
258 char line
[LINE_MAX
], *l
;
261 if (!fgets(line
, sizeof(line
), f
))
265 l
+= strcspn(l
, WHITESPACE
);
266 l
+= strspn(l
, WHITESPACE
);
268 if (first_word(l
, "Read")) {
271 } else if (first_word(l
, "Write")) {
277 l
+= strspn(l
, WHITESPACE
);
278 r
= safe_atou64(l
, &k
);
285 timestamp
= now_nsec(CLOCK_MONOTONIC
);
287 if (g
->io_iteration
== iteration
- 1) {
290 x
= (uint64_t) (timestamp
- g
->io_timestamp
);
294 if (rd
> g
->io_input
)
295 yr
= rd
- g
->io_input
;
299 if (wr
> g
->io_output
)
300 yw
= wr
- g
->io_output
;
304 if (yr
> 0 || yw
> 0) {
305 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
306 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
313 g
->io_timestamp
= timestamp
;
314 g
->io_iteration
= iteration
;
323 static int refresh_one(
324 const char *controller
,
332 _cleanup_closedir_
DIR *d
= NULL
;
340 if (depth
> arg_depth
)
343 r
= process(controller
, path
, a
, b
, iteration
, &ours
);
347 r
= cg_enumerate_subgroups(controller
, path
, &d
);
354 _cleanup_free_
char *fn
= NULL
, *p
= NULL
;
357 r
= cg_read_subgroup(d
, &fn
);
363 p
= strjoin(path
, "/", fn
, NULL
);
367 path_kill_slashes(p
);
369 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1, &child
);
375 child
->n_tasks_valid
&&
376 streq(controller
, SYSTEMD_CGROUP_CONTROLLER
)) {
378 /* Recursively sum up processes */
380 if (ours
->n_tasks_valid
)
381 ours
->n_tasks
+= child
->n_tasks
;
383 ours
->n_tasks
= child
->n_tasks
;
384 ours
->n_tasks_valid
= true;
395 static int refresh(const char *root
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
400 r
= refresh_one(SYSTEMD_CGROUP_CONTROLLER
, root
, a
, b
, iteration
, 0, NULL
);
403 r
= refresh_one("cpuacct", root
, a
, b
, iteration
, 0, NULL
);
406 r
= refresh_one("memory", root
, a
, b
, iteration
, 0, NULL
);
409 r
= refresh_one("blkio", root
, a
, b
, iteration
, 0, NULL
);
416 static int group_compare(const void*a
, const void *b
) {
417 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
419 if (arg_order
!= ORDER_TASKS
|| arg_recursive
) {
420 /* Let's make sure that the parent is always before
421 * the child. Except when ordering by tasks and
422 * recursive summing is off, since that is actually
423 * not accumulative for all children. */
425 if (path_startswith(y
->path
, x
->path
))
427 if (path_startswith(x
->path
, y
->path
))
437 if (arg_cpu_type
== CPU_PERCENT
) {
438 if (x
->cpu_valid
&& y
->cpu_valid
) {
439 if (x
->cpu_fraction
> y
->cpu_fraction
)
441 else if (x
->cpu_fraction
< y
->cpu_fraction
)
443 } else if (x
->cpu_valid
)
445 else if (y
->cpu_valid
)
448 if (x
->cpu_usage
> y
->cpu_usage
)
450 else if (x
->cpu_usage
< y
->cpu_usage
)
457 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
458 if (x
->n_tasks
> y
->n_tasks
)
460 else if (x
->n_tasks
< y
->n_tasks
)
462 } else if (x
->n_tasks_valid
)
464 else if (y
->n_tasks_valid
)
470 if (x
->memory_valid
&& y
->memory_valid
) {
471 if (x
->memory
> y
->memory
)
473 else if (x
->memory
< y
->memory
)
475 } else if (x
->memory_valid
)
477 else if (y
->memory_valid
)
483 if (x
->io_valid
&& y
->io_valid
) {
484 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
486 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
488 } else if (x
->io_valid
)
490 else if (y
->io_valid
)
494 return path_compare(x
->path
, y
->path
);
497 #define ON ANSI_HIGHLIGHT_ON
498 #define OFF ANSI_HIGHLIGHT_OFF
500 static void display(Hashmap
*a
) {
505 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
506 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
510 /* Set cursor to top left corner and clear screen */
515 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
517 HASHMAP_FOREACH(g
, a
, i
)
518 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
521 qsort_safe(array
, n
, sizeof(Group
*), group_compare
);
523 /* Find the longest names in one run */
524 for (j
= 0; j
< n
; j
++) {
525 unsigned cputlen
, pathtlen
;
527 format_timespan(buffer
, sizeof(buffer
), (usec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
528 cputlen
= strlen(buffer
);
529 maxtcpu
= MAX(maxtcpu
, cputlen
);
531 pathtlen
= strlen(array
[j
]->path
);
532 maxtpath
= MAX(maxtpath
, pathtlen
);
535 if (arg_cpu_type
== CPU_PERCENT
)
536 snprintf(buffer
, sizeof(buffer
), "%6s", "%CPU");
538 snprintf(buffer
, sizeof(buffer
), "%*s", maxtcpu
, "CPU Time");
545 path_columns
= columns() - 36 - strlen(buffer
);
546 if (path_columns
< 10)
549 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
550 arg_order
== ORDER_PATH
? ON
: "", path_columns
, "Control Group",
551 arg_order
== ORDER_PATH
? OFF
: "",
552 arg_order
== ORDER_TASKS
? ON
: "", "Tasks",
553 arg_order
== ORDER_TASKS
? OFF
: "",
554 arg_order
== ORDER_CPU
? ON
: "", buffer
,
555 arg_order
== ORDER_CPU
? OFF
: "",
556 arg_order
== ORDER_MEMORY
? ON
: "", "Memory",
557 arg_order
== ORDER_MEMORY
? OFF
: "",
558 arg_order
== ORDER_IO
? ON
: "", "Input/s",
559 arg_order
== ORDER_IO
? OFF
: "",
560 arg_order
== ORDER_IO
? ON
: "", "Output/s",
561 arg_order
== ORDER_IO
? OFF
: "");
563 path_columns
= maxtpath
;
565 for (j
= 0; j
< n
; j
++) {
566 _cleanup_free_
char *ellipsized
= NULL
;
569 if (on_tty() && j
+ 5 > rows
)
574 path
= isempty(g
->path
) ? "/" : g
->path
;
575 ellipsized
= ellipsize(path
, path_columns
, 33);
576 printf("%-*s", path_columns
, ellipsized
?: path
);
578 if (g
->n_tasks_valid
)
579 printf(" %7u", g
->n_tasks
);
583 if (arg_cpu_type
== CPU_PERCENT
) {
585 printf(" %6.1f", g
->cpu_fraction
*100);
589 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (usec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
591 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
592 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
593 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
599 static void help(void) {
600 printf("%s [OPTIONS...]\n\n"
601 "Show top control groups by their resource usage.\n\n"
602 " -h --help Show this help\n"
603 " --version Show package version\n"
604 " -p --order=path Order by path\n"
605 " -t --order=tasks Order by number of tasks\n"
606 " -c --order=cpu Order by CPU load (default)\n"
607 " -m --order=memory Order by memory load\n"
608 " -i --order=io Order by IO load\n"
609 " -r --raw Provide raw (not human-readable) numbers\n"
610 " --cpu=percentage Show CPU usage as percentage (default)\n"
611 " --cpu=time Show CPU usage as time\n"
612 " -k Include kernel threads in task count\n"
613 " --recursive=BOOL Sum up task count recursively\n"
614 " -d --delay=DELAY Delay between updates\n"
615 " -n --iterations=N Run for N iterations before exiting\n"
616 " -b --batch Run in batch mode, accepting no input\n"
617 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
618 , program_invocation_short_name
, arg_depth
);
621 static int parse_argv(int argc
, char *argv
[]) {
631 static const struct option options
[] = {
632 { "help", no_argument
, NULL
, 'h' },
633 { "version", no_argument
, NULL
, ARG_VERSION
},
634 { "delay", required_argument
, NULL
, 'd' },
635 { "iterations", required_argument
, NULL
, 'n' },
636 { "batch", no_argument
, NULL
, 'b' },
637 { "raw", no_argument
, NULL
, 'r' },
638 { "depth", required_argument
, NULL
, ARG_DEPTH
},
639 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
640 { "order", required_argument
, NULL
, ARG_ORDER
},
641 { "recursive", required_argument
, NULL
, ARG_RECURSIVE
},
650 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:k", options
, NULL
)) >= 0)
659 puts(PACKAGE_STRING
);
660 puts(SYSTEMD_FEATURES
);
665 if (streq(optarg
, "time"))
666 arg_cpu_type
= CPU_TIME
;
667 else if (streq(optarg
, "percentage"))
668 arg_cpu_type
= CPU_PERCENT
;
670 log_error("Unknown argument to --cpu=: %s", optarg
);
674 arg_cpu_type
= CPU_TIME
;
679 r
= safe_atou(optarg
, &arg_depth
);
681 log_error("Failed to parse depth parameter.");
688 r
= parse_sec(optarg
, &arg_delay
);
689 if (r
< 0 || arg_delay
<= 0) {
690 log_error("Failed to parse delay parameter.");
697 r
= safe_atou(optarg
, &arg_iterations
);
699 log_error("Failed to parse iterations parameter.");
714 arg_order
= ORDER_PATH
;
718 arg_order
= ORDER_TASKS
;
722 arg_order
= ORDER_CPU
;
726 arg_order
= ORDER_MEMORY
;
730 arg_order
= ORDER_IO
;
734 if (streq(optarg
, "path"))
735 arg_order
= ORDER_PATH
;
736 else if (streq(optarg
, "tasks"))
737 arg_order
= ORDER_TASKS
;
738 else if (streq(optarg
, "cpu"))
739 arg_order
= ORDER_CPU
;
740 else if (streq(optarg
, "memory"))
741 arg_order
= ORDER_MEMORY
;
742 else if (streq(optarg
, "io"))
743 arg_order
= ORDER_IO
;
745 log_error("Invalid argument to --order=: %s", optarg
);
751 arg_kernel_threads
= true;
755 r
= parse_boolean(optarg
);
757 log_error("Failed to parse --recursive= argument: %s", optarg
);
768 assert_not_reached("Unhandled option");
772 log_error("Too many arguments.");
779 int main(int argc
, char *argv
[]) {
781 Hashmap
*a
= NULL
, *b
= NULL
;
782 unsigned iteration
= 0;
783 usec_t last_refresh
= 0;
784 bool quit
= false, immediate_refresh
= false;
785 _cleanup_free_
char *root
= NULL
;
787 log_parse_environment();
790 r
= parse_argv(argc
, argv
);
794 r
= cg_get_root_path(&root
);
796 log_error_errno(r
, "Failed to get root control group path: %m");
800 a
= hashmap_new(&string_hash_ops
);
801 b
= hashmap_new(&string_hash_ops
);
807 signal(SIGWINCH
, columns_lines_cache_reset
);
809 if (arg_iterations
== (unsigned) -1)
810 arg_iterations
= on_tty() ? 0 : 1;
816 char h
[FORMAT_TIMESPAN_MAX
];
818 t
= now(CLOCK_MONOTONIC
);
820 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
822 r
= refresh(root
, a
, b
, iteration
++);
824 log_error_errno(r
, "Failed to refresh: %m");
828 group_hashmap_clear(b
);
835 immediate_refresh
= false;
840 if (arg_iterations
&& iteration
>= arg_iterations
)
843 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
848 (void) usleep(last_refresh
+ arg_delay
- t
);
850 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
854 log_error_errno(r
, "Couldn't read key: %m");
859 if (on_tty()) { /* TTY: Clear any user keystroke */
860 fputs("\r \r", stdout
);
870 immediate_refresh
= true;
878 arg_order
= ORDER_PATH
;
882 arg_order
= ORDER_TASKS
;
886 arg_order
= ORDER_CPU
;
890 arg_order
= ORDER_MEMORY
;
894 arg_order
= ORDER_IO
;
898 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
902 arg_kernel_threads
= !arg_kernel_threads
;
903 fprintf(stdout
, "\nCounting kernel threads: %s.", yes_no(arg_kernel_threads
));
909 arg_recursive
= !arg_recursive
;
910 fprintf(stdout
, "\nRecursive task counting: %s", yes_no(arg_recursive
));
916 if (arg_delay
< USEC_PER_SEC
)
917 arg_delay
+= USEC_PER_MSEC
*250;
919 arg_delay
+= USEC_PER_SEC
;
921 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
927 if (arg_delay
<= USEC_PER_MSEC
*500)
928 arg_delay
= USEC_PER_MSEC
*250;
929 else if (arg_delay
< USEC_PER_MSEC
*1250)
930 arg_delay
-= USEC_PER_MSEC
*250;
932 arg_delay
-= USEC_PER_SEC
;
934 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
942 "\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"
943 "\t<" ON
"+" OFF
"> Inc. delay; <" ON
"-" OFF
"> Dec. delay; <" ON
"%%" OFF
"> Toggle time; <" ON
"SPACE" OFF
"> Refresh\n"
944 "\t<" ON
"k" OFF
"> Count kernel threads; <" ON
"r" OFF
"> Count recursively; <" ON
"q" OFF
"> Quit");
951 fprintf(stdout
, "\nUnknown key '\\x%x'. Ignoring.", key
);
953 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
963 group_hashmap_free(a
);
964 group_hashmap_free(b
);
966 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;