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")) {
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 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
224 r
= read_one_line_file(p
, &v
);
230 r
= safe_atou64(v
, &g
->memory
);
235 g
->memory_valid
= true;
237 } else if (streq(controller
, "blkio")) {
238 _cleanup_fclose_
FILE *f
= NULL
;
239 _cleanup_free_
char *p
= NULL
;
240 uint64_t wr
= 0, rd
= 0;
243 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
255 char line
[LINE_MAX
], *l
;
258 if (!fgets(line
, sizeof(line
), f
))
262 l
+= strcspn(l
, WHITESPACE
);
263 l
+= strspn(l
, WHITESPACE
);
265 if (first_word(l
, "Read")) {
268 } else if (first_word(l
, "Write")) {
274 l
+= strspn(l
, WHITESPACE
);
275 r
= safe_atou64(l
, &k
);
282 timestamp
= now_nsec(CLOCK_MONOTONIC
);
284 if (g
->io_iteration
== iteration
- 1) {
287 x
= (uint64_t) (timestamp
- g
->io_timestamp
);
291 if (rd
> g
->io_input
)
292 yr
= rd
- g
->io_input
;
296 if (wr
> g
->io_output
)
297 yw
= wr
- g
->io_output
;
301 if (yr
> 0 || yw
> 0) {
302 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
303 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
310 g
->io_timestamp
= timestamp
;
311 g
->io_iteration
= iteration
;
320 static int refresh_one(
321 const char *controller
,
329 _cleanup_closedir_
DIR *d
= NULL
;
337 if (depth
> arg_depth
)
340 r
= process(controller
, path
, a
, b
, iteration
, &ours
);
344 r
= cg_enumerate_subgroups(controller
, path
, &d
);
351 _cleanup_free_
char *fn
= NULL
, *p
= NULL
;
354 r
= cg_read_subgroup(d
, &fn
);
360 p
= strjoin(path
, "/", fn
, NULL
);
364 path_kill_slashes(p
);
366 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1, &child
);
372 child
->n_tasks_valid
&&
373 streq(controller
, SYSTEMD_CGROUP_CONTROLLER
)) {
375 /* Recursively sum up processes */
377 if (ours
->n_tasks_valid
)
378 ours
->n_tasks
+= child
->n_tasks
;
380 ours
->n_tasks
= child
->n_tasks
;
381 ours
->n_tasks_valid
= true;
392 static int refresh(const char *root
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
397 r
= refresh_one(SYSTEMD_CGROUP_CONTROLLER
, root
, a
, b
, iteration
, 0, NULL
);
400 r
= refresh_one("cpuacct", root
, a
, b
, iteration
, 0, NULL
);
403 r
= refresh_one("memory", root
, a
, b
, iteration
, 0, NULL
);
406 r
= refresh_one("blkio", root
, a
, b
, iteration
, 0, NULL
);
413 static int group_compare(const void*a
, const void *b
) {
414 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
416 if (arg_order
!= ORDER_TASKS
|| arg_recursive
) {
417 /* Let's make sure that the parent is always before
418 * the child. Except when ordering by tasks and
419 * recursive summing is off, since that is actually
420 * not accumulative for all children. */
422 if (path_startswith(y
->path
, x
->path
))
424 if (path_startswith(x
->path
, y
->path
))
434 if (arg_cpu_type
== CPU_PERCENT
) {
435 if (x
->cpu_valid
&& y
->cpu_valid
) {
436 if (x
->cpu_fraction
> y
->cpu_fraction
)
438 else if (x
->cpu_fraction
< y
->cpu_fraction
)
440 } else if (x
->cpu_valid
)
442 else if (y
->cpu_valid
)
445 if (x
->cpu_usage
> y
->cpu_usage
)
447 else if (x
->cpu_usage
< y
->cpu_usage
)
454 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
455 if (x
->n_tasks
> y
->n_tasks
)
457 else if (x
->n_tasks
< y
->n_tasks
)
459 } else if (x
->n_tasks_valid
)
461 else if (y
->n_tasks_valid
)
467 if (x
->memory_valid
&& y
->memory_valid
) {
468 if (x
->memory
> y
->memory
)
470 else if (x
->memory
< y
->memory
)
472 } else if (x
->memory_valid
)
474 else if (y
->memory_valid
)
480 if (x
->io_valid
&& y
->io_valid
) {
481 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
483 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
485 } else if (x
->io_valid
)
487 else if (y
->io_valid
)
491 return path_compare(x
->path
, y
->path
);
494 #define ON ANSI_HIGHLIGHT_ON
495 #define OFF ANSI_HIGHLIGHT_OFF
497 static void display(Hashmap
*a
) {
502 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
503 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
507 /* Set cursor to top left corner and clear screen */
512 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
514 HASHMAP_FOREACH(g
, a
, i
)
515 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
518 qsort_safe(array
, n
, sizeof(Group
*), group_compare
);
520 /* Find the longest names in one run */
521 for (j
= 0; j
< n
; j
++) {
522 unsigned cputlen
, pathtlen
;
524 format_timespan(buffer
, sizeof(buffer
), (usec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
525 cputlen
= strlen(buffer
);
526 maxtcpu
= MAX(maxtcpu
, cputlen
);
528 pathtlen
= strlen(array
[j
]->path
);
529 maxtpath
= MAX(maxtpath
, pathtlen
);
532 if (arg_cpu_type
== CPU_PERCENT
)
533 snprintf(buffer
, sizeof(buffer
), "%6s", "%CPU");
535 snprintf(buffer
, sizeof(buffer
), "%*s", maxtcpu
, "CPU Time");
542 path_columns
= columns() - 36 - strlen(buffer
);
543 if (path_columns
< 10)
546 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
547 arg_order
== ORDER_PATH
? ON
: "", path_columns
, "Control Group",
548 arg_order
== ORDER_PATH
? OFF
: "",
549 arg_order
== ORDER_TASKS
? ON
: "", "Tasks",
550 arg_order
== ORDER_TASKS
? OFF
: "",
551 arg_order
== ORDER_CPU
? ON
: "", buffer
,
552 arg_order
== ORDER_CPU
? OFF
: "",
553 arg_order
== ORDER_MEMORY
? ON
: "", "Memory",
554 arg_order
== ORDER_MEMORY
? OFF
: "",
555 arg_order
== ORDER_IO
? ON
: "", "Input/s",
556 arg_order
== ORDER_IO
? OFF
: "",
557 arg_order
== ORDER_IO
? ON
: "", "Output/s",
558 arg_order
== ORDER_IO
? OFF
: "");
560 path_columns
= maxtpath
;
562 for (j
= 0; j
< n
; j
++) {
563 _cleanup_free_
char *p
= NULL
;
565 if (on_tty() && j
+ 5 > rows
)
570 p
= ellipsize(g
->path
, path_columns
, 33);
571 printf("%-*s", path_columns
, p
?: g
->path
);
573 if (g
->n_tasks_valid
)
574 printf(" %7u", g
->n_tasks
);
578 if (arg_cpu_type
== CPU_PERCENT
) {
580 printf(" %6.1f", g
->cpu_fraction
*100);
584 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (usec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
586 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
587 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
588 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
594 static void help(void) {
595 printf("%s [OPTIONS...]\n\n"
596 "Show top control groups by their resource usage.\n\n"
597 " -h --help Show this help\n"
598 " --version Show package version\n"
599 " -p --order=path Order by path\n"
600 " -t --order=tasks Order by number of tasks\n"
601 " -c --order=cpu Order by CPU load (default)\n"
602 " -m --order=memory Order by memory load\n"
603 " -i --order=io Order by IO load\n"
604 " -r --raw Provide raw (not human-readable) numbers\n"
605 " --cpu=percentage Show CPU usage as percentage (default)\n"
606 " --cpu=time Show CPU usage as time\n"
607 " -k Include kernel threads in task count\n"
608 " --recursive=BOOL Sum up task count recursively\n"
609 " -d --delay=DELAY Delay between updates\n"
610 " -n --iterations=N Run for N iterations before exiting\n"
611 " -b --batch Run in batch mode, accepting no input\n"
612 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
613 , program_invocation_short_name
, arg_depth
);
616 static int parse_argv(int argc
, char *argv
[]) {
626 static const struct option options
[] = {
627 { "help", no_argument
, NULL
, 'h' },
628 { "version", no_argument
, NULL
, ARG_VERSION
},
629 { "delay", required_argument
, NULL
, 'd' },
630 { "iterations", required_argument
, NULL
, 'n' },
631 { "batch", no_argument
, NULL
, 'b' },
632 { "raw", no_argument
, NULL
, 'r' },
633 { "depth", required_argument
, NULL
, ARG_DEPTH
},
634 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
635 { "order", required_argument
, NULL
, ARG_ORDER
},
636 { "recursive", required_argument
, NULL
, ARG_RECURSIVE
},
645 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:k", options
, NULL
)) >= 0)
654 puts(PACKAGE_STRING
);
655 puts(SYSTEMD_FEATURES
);
660 if (streq(optarg
, "time"))
661 arg_cpu_type
= CPU_TIME
;
662 else if (streq(optarg
, "percentage"))
663 arg_cpu_type
= CPU_PERCENT
;
665 log_error("Unknown argument to --cpu=: %s", optarg
);
669 arg_cpu_type
= CPU_TIME
;
674 r
= safe_atou(optarg
, &arg_depth
);
676 log_error("Failed to parse depth parameter.");
683 r
= parse_sec(optarg
, &arg_delay
);
684 if (r
< 0 || arg_delay
<= 0) {
685 log_error("Failed to parse delay parameter.");
692 r
= safe_atou(optarg
, &arg_iterations
);
694 log_error("Failed to parse iterations parameter.");
709 arg_order
= ORDER_PATH
;
713 arg_order
= ORDER_TASKS
;
717 arg_order
= ORDER_CPU
;
721 arg_order
= ORDER_MEMORY
;
725 arg_order
= ORDER_IO
;
729 if (streq(optarg
, "path"))
730 arg_order
= ORDER_PATH
;
731 else if (streq(optarg
, "tasks"))
732 arg_order
= ORDER_TASKS
;
733 else if (streq(optarg
, "cpu"))
734 arg_order
= ORDER_CPU
;
735 else if (streq(optarg
, "memory"))
736 arg_order
= ORDER_MEMORY
;
737 else if (streq(optarg
, "io"))
738 arg_order
= ORDER_IO
;
740 log_error("Invalid argument to --order=: %s", optarg
);
746 arg_kernel_threads
= true;
750 r
= parse_boolean(optarg
);
752 log_error("Failed to parse --recursive= argument: %s", optarg
);
763 assert_not_reached("Unhandled option");
767 log_error("Too many arguments.");
774 int main(int argc
, char *argv
[]) {
776 Hashmap
*a
= NULL
, *b
= NULL
;
777 unsigned iteration
= 0;
778 usec_t last_refresh
= 0;
779 bool quit
= false, immediate_refresh
= false;
780 _cleanup_free_
char *root
= NULL
;
782 log_parse_environment();
785 r
= parse_argv(argc
, argv
);
789 r
= cg_get_root_path(&root
);
791 log_error_errno(r
, "Failed to get root control group path: %m");
795 a
= hashmap_new(&string_hash_ops
);
796 b
= hashmap_new(&string_hash_ops
);
802 signal(SIGWINCH
, columns_lines_cache_reset
);
804 if (arg_iterations
== (unsigned) -1)
805 arg_iterations
= on_tty() ? 0 : 1;
811 char h
[FORMAT_TIMESPAN_MAX
];
813 t
= now(CLOCK_MONOTONIC
);
815 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
817 r
= refresh(root
, a
, b
, iteration
++);
819 log_error_errno(r
, "Failed to refresh: %m");
823 group_hashmap_clear(b
);
830 immediate_refresh
= false;
835 if (arg_iterations
&& iteration
>= arg_iterations
)
838 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
843 (void) usleep(last_refresh
+ arg_delay
- t
);
845 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
849 log_error_errno(r
, "Couldn't read key: %m");
854 if (on_tty()) { /* TTY: Clear any user keystroke */
855 fputs("\r \r", stdout
);
865 immediate_refresh
= true;
873 arg_order
= ORDER_PATH
;
877 arg_order
= ORDER_TASKS
;
881 arg_order
= ORDER_CPU
;
885 arg_order
= ORDER_MEMORY
;
889 arg_order
= ORDER_IO
;
893 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
897 arg_kernel_threads
= !arg_kernel_threads
;
898 fprintf(stdout
, "\nCounting kernel threads: %s.", yes_no(arg_kernel_threads
));
904 arg_recursive
= !arg_recursive
;
905 fprintf(stdout
, "\nRecursive task counting: %s", yes_no(arg_recursive
));
911 if (arg_delay
< USEC_PER_SEC
)
912 arg_delay
+= USEC_PER_MSEC
*250;
914 arg_delay
+= USEC_PER_SEC
;
916 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
922 if (arg_delay
<= USEC_PER_MSEC
*500)
923 arg_delay
= USEC_PER_MSEC
*250;
924 else if (arg_delay
< USEC_PER_MSEC
*1250)
925 arg_delay
-= USEC_PER_MSEC
*250;
927 arg_delay
-= USEC_PER_SEC
;
929 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
937 "\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"
938 "\t<" ON
"+" OFF
"> Inc. delay; <" ON
"-" OFF
"> Dec. delay; <" ON
"%%" OFF
"> Toggle time; <" ON
"SPACE" OFF
"> Refresh\n"
939 "\t<" ON
"k" OFF
"> Count kernel threads; <" ON
"r" OFF
"> Count recursively; <" ON
"q" OFF
"> Quit");
946 fprintf(stdout
, "\nUnknown key '\\x%x'. Ignoring.", key
);
948 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
958 group_hashmap_free(a
);
959 group_hashmap_free(b
);
961 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;