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 *ellipsized
= NULL
;
566 if (on_tty() && j
+ 5 > rows
)
571 path
= isempty(g
->path
) ? "/" : g
->path
;
572 ellipsized
= ellipsize(path
, path_columns
, 33);
573 printf("%-*s", path_columns
, ellipsized
?: path
);
575 if (g
->n_tasks_valid
)
576 printf(" %7u", g
->n_tasks
);
580 if (arg_cpu_type
== CPU_PERCENT
) {
582 printf(" %6.1f", g
->cpu_fraction
*100);
586 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (usec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
588 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
589 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
590 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
596 static void help(void) {
597 printf("%s [OPTIONS...]\n\n"
598 "Show top control groups by their resource usage.\n\n"
599 " -h --help Show this help\n"
600 " --version Show package version\n"
601 " -p --order=path Order by path\n"
602 " -t --order=tasks Order by number of tasks\n"
603 " -c --order=cpu Order by CPU load (default)\n"
604 " -m --order=memory Order by memory load\n"
605 " -i --order=io Order by IO load\n"
606 " -r --raw Provide raw (not human-readable) numbers\n"
607 " --cpu=percentage Show CPU usage as percentage (default)\n"
608 " --cpu=time Show CPU usage as time\n"
609 " -k Include kernel threads in task count\n"
610 " --recursive=BOOL Sum up task count recursively\n"
611 " -d --delay=DELAY Delay between updates\n"
612 " -n --iterations=N Run for N iterations before exiting\n"
613 " -b --batch Run in batch mode, accepting no input\n"
614 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
615 , program_invocation_short_name
, arg_depth
);
618 static int parse_argv(int argc
, char *argv
[]) {
628 static const struct option options
[] = {
629 { "help", no_argument
, NULL
, 'h' },
630 { "version", no_argument
, NULL
, ARG_VERSION
},
631 { "delay", required_argument
, NULL
, 'd' },
632 { "iterations", required_argument
, NULL
, 'n' },
633 { "batch", no_argument
, NULL
, 'b' },
634 { "raw", no_argument
, NULL
, 'r' },
635 { "depth", required_argument
, NULL
, ARG_DEPTH
},
636 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
637 { "order", required_argument
, NULL
, ARG_ORDER
},
638 { "recursive", required_argument
, NULL
, ARG_RECURSIVE
},
647 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:k", options
, NULL
)) >= 0)
656 puts(PACKAGE_STRING
);
657 puts(SYSTEMD_FEATURES
);
662 if (streq(optarg
, "time"))
663 arg_cpu_type
= CPU_TIME
;
664 else if (streq(optarg
, "percentage"))
665 arg_cpu_type
= CPU_PERCENT
;
667 log_error("Unknown argument to --cpu=: %s", optarg
);
671 arg_cpu_type
= CPU_TIME
;
676 r
= safe_atou(optarg
, &arg_depth
);
678 log_error("Failed to parse depth parameter.");
685 r
= parse_sec(optarg
, &arg_delay
);
686 if (r
< 0 || arg_delay
<= 0) {
687 log_error("Failed to parse delay parameter.");
694 r
= safe_atou(optarg
, &arg_iterations
);
696 log_error("Failed to parse iterations parameter.");
711 arg_order
= ORDER_PATH
;
715 arg_order
= ORDER_TASKS
;
719 arg_order
= ORDER_CPU
;
723 arg_order
= ORDER_MEMORY
;
727 arg_order
= ORDER_IO
;
731 if (streq(optarg
, "path"))
732 arg_order
= ORDER_PATH
;
733 else if (streq(optarg
, "tasks"))
734 arg_order
= ORDER_TASKS
;
735 else if (streq(optarg
, "cpu"))
736 arg_order
= ORDER_CPU
;
737 else if (streq(optarg
, "memory"))
738 arg_order
= ORDER_MEMORY
;
739 else if (streq(optarg
, "io"))
740 arg_order
= ORDER_IO
;
742 log_error("Invalid argument to --order=: %s", optarg
);
748 arg_kernel_threads
= true;
752 r
= parse_boolean(optarg
);
754 log_error("Failed to parse --recursive= argument: %s", optarg
);
765 assert_not_reached("Unhandled option");
769 log_error("Too many arguments.");
776 int main(int argc
, char *argv
[]) {
778 Hashmap
*a
= NULL
, *b
= NULL
;
779 unsigned iteration
= 0;
780 usec_t last_refresh
= 0;
781 bool quit
= false, immediate_refresh
= false;
782 _cleanup_free_
char *root
= NULL
;
784 log_parse_environment();
787 r
= parse_argv(argc
, argv
);
791 r
= cg_get_root_path(&root
);
793 log_error_errno(r
, "Failed to get root control group path: %m");
797 a
= hashmap_new(&string_hash_ops
);
798 b
= hashmap_new(&string_hash_ops
);
804 signal(SIGWINCH
, columns_lines_cache_reset
);
806 if (arg_iterations
== (unsigned) -1)
807 arg_iterations
= on_tty() ? 0 : 1;
813 char h
[FORMAT_TIMESPAN_MAX
];
815 t
= now(CLOCK_MONOTONIC
);
817 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
819 r
= refresh(root
, a
, b
, iteration
++);
821 log_error_errno(r
, "Failed to refresh: %m");
825 group_hashmap_clear(b
);
832 immediate_refresh
= false;
837 if (arg_iterations
&& iteration
>= arg_iterations
)
840 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
845 (void) usleep(last_refresh
+ arg_delay
- t
);
847 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
851 log_error_errno(r
, "Couldn't read key: %m");
856 if (on_tty()) { /* TTY: Clear any user keystroke */
857 fputs("\r \r", stdout
);
867 immediate_refresh
= true;
875 arg_order
= ORDER_PATH
;
879 arg_order
= ORDER_TASKS
;
883 arg_order
= ORDER_CPU
;
887 arg_order
= ORDER_MEMORY
;
891 arg_order
= ORDER_IO
;
895 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
899 arg_kernel_threads
= !arg_kernel_threads
;
900 fprintf(stdout
, "\nCounting kernel threads: %s.", yes_no(arg_kernel_threads
));
906 arg_recursive
= !arg_recursive
;
907 fprintf(stdout
, "\nRecursive task counting: %s", yes_no(arg_recursive
));
913 if (arg_delay
< USEC_PER_SEC
)
914 arg_delay
+= USEC_PER_MSEC
*250;
916 arg_delay
+= USEC_PER_SEC
;
918 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
924 if (arg_delay
<= USEC_PER_MSEC
*500)
925 arg_delay
= USEC_PER_MSEC
*250;
926 else if (arg_delay
< USEC_PER_MSEC
*1250)
927 arg_delay
-= USEC_PER_MSEC
*250;
929 arg_delay
-= USEC_PER_SEC
;
931 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
939 "\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"
940 "\t<" ON
"+" OFF
"> Inc. delay; <" ON
"-" OFF
"> Dec. delay; <" ON
"%%" OFF
"> Toggle time; <" ON
"SPACE" OFF
"> Refresh\n"
941 "\t<" ON
"k" OFF
"> Count kernel threads; <" ON
"r" OFF
"> Count recursively; <" ON
"q" OFF
"> Quit");
948 fprintf(stdout
, "\nUnknown key '\\x%x'. Ignoring.", key
);
950 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
960 group_hashmap_free(a
);
961 group_hashmap_free(b
);
963 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;