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/>.
33 #include "bus-error.h"
35 #include "cgroup-util.h"
38 #include "path-util.h"
39 #include "process-util.h"
40 #include "terminal-util.h"
41 #include "unit-name.h"
44 typedef struct Group
{
54 unsigned cpu_iteration
;
61 unsigned io_iteration
;
62 uint64_t io_input
, io_output
;
64 uint64_t io_input_bps
, io_output_bps
;
67 static unsigned arg_depth
= 3;
68 static unsigned arg_iterations
= (unsigned) -1;
69 static bool arg_batch
= false;
70 static bool arg_raw
= false;
71 static usec_t arg_delay
= 1*USEC_PER_SEC
;
72 static char* arg_machine
= NULL
;
76 COUNT_USERSPACE_PROCESSES
,
78 } arg_count
= COUNT_PIDS
;
79 static bool arg_recursive
= true;
87 } arg_order
= ORDER_CPU
;
92 } arg_cpu_type
= CPU_PERCENT
;
94 static void group_free(Group
*g
) {
101 static void group_hashmap_clear(Hashmap
*h
) {
104 while ((g
= hashmap_steal_first(h
)))
108 static void group_hashmap_free(Hashmap
*h
) {
109 group_hashmap_clear(h
);
113 static const char *maybe_format_bytes(char *buf
, size_t l
, bool is_valid
, uint64_t t
) {
117 snprintf(buf
, l
, "%jd", t
);
120 return format_bytes(buf
, l
, t
);
124 const char *controller
,
138 g
= hashmap_get(a
, path
);
140 g
= hashmap_get(b
, path
);
146 g
->path
= strdup(path
);
152 r
= hashmap_put(a
, g
->path
, g
);
158 r
= hashmap_move_one(a
, b
, path
);
162 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
166 if (streq(controller
, SYSTEMD_CGROUP_CONTROLLER
) && IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
)) {
167 _cleanup_fclose_
FILE *f
= NULL
;
170 r
= cg_enumerate_processes(controller
, path
, &f
);
177 while (cg_read_pid(f
, &pid
) > 0) {
179 if (arg_count
== COUNT_USERSPACE_PROCESSES
&& is_kernel_thread(pid
) > 0)
186 g
->n_tasks_valid
= true;
188 } else if (streq(controller
, "pids") && arg_count
== COUNT_PIDS
) {
189 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
191 r
= cg_get_path(controller
, path
, "pids.current", &p
);
195 r
= read_one_line_file(p
, &v
);
201 r
= safe_atou64(v
, &g
->n_tasks
);
206 g
->n_tasks_valid
= true;
208 } else if (streq(controller
, "cpuacct") && cg_unified() <= 0) {
209 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
213 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
217 r
= read_one_line_file(p
, &v
);
223 r
= safe_atou64(v
, &new_usage
);
227 timestamp
= now_nsec(CLOCK_MONOTONIC
);
229 if (g
->cpu_iteration
== iteration
- 1 &&
230 (nsec_t
) new_usage
> g
->cpu_usage
) {
234 x
= timestamp
- g
->cpu_timestamp
;
238 y
= (nsec_t
) new_usage
- g
->cpu_usage
;
239 g
->cpu_fraction
= (double) y
/ (double) x
;
243 g
->cpu_usage
= (nsec_t
) new_usage
;
244 g
->cpu_timestamp
= timestamp
;
245 g
->cpu_iteration
= iteration
;
247 } else if (streq(controller
, "memory")) {
248 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
250 if (cg_unified() <= 0)
251 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
253 r
= cg_get_path(controller
, path
, "memory.current", &p
);
257 r
= read_one_line_file(p
, &v
);
263 r
= safe_atou64(v
, &g
->memory
);
268 g
->memory_valid
= true;
270 } else if (streq(controller
, "blkio") && cg_unified() <= 0) {
271 _cleanup_fclose_
FILE *f
= NULL
;
272 _cleanup_free_
char *p
= NULL
;
273 uint64_t wr
= 0, rd
= 0;
276 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
288 char line
[LINE_MAX
], *l
;
291 if (!fgets(line
, sizeof(line
), f
))
295 l
+= strcspn(l
, WHITESPACE
);
296 l
+= strspn(l
, WHITESPACE
);
298 if (first_word(l
, "Read")) {
301 } else if (first_word(l
, "Write")) {
307 l
+= strspn(l
, WHITESPACE
);
308 r
= safe_atou64(l
, &k
);
315 timestamp
= now_nsec(CLOCK_MONOTONIC
);
317 if (g
->io_iteration
== iteration
- 1) {
320 x
= (uint64_t) (timestamp
- g
->io_timestamp
);
324 if (rd
> g
->io_input
)
325 yr
= rd
- g
->io_input
;
329 if (wr
> g
->io_output
)
330 yw
= wr
- g
->io_output
;
334 if (yr
> 0 || yw
> 0) {
335 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
336 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
343 g
->io_timestamp
= timestamp
;
344 g
->io_iteration
= iteration
;
353 static int refresh_one(
354 const char *controller
,
362 _cleanup_closedir_
DIR *d
= NULL
;
370 if (depth
> arg_depth
)
373 r
= process(controller
, path
, a
, b
, iteration
, &ours
);
377 r
= cg_enumerate_subgroups(controller
, path
, &d
);
384 _cleanup_free_
char *fn
= NULL
, *p
= NULL
;
387 r
= cg_read_subgroup(d
, &fn
);
393 p
= strjoin(path
, "/", fn
, NULL
);
397 path_kill_slashes(p
);
399 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1, &child
);
404 IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
) &&
406 child
->n_tasks_valid
&&
407 streq(controller
, SYSTEMD_CGROUP_CONTROLLER
)) {
409 /* Recursively sum up processes */
411 if (ours
->n_tasks_valid
)
412 ours
->n_tasks
+= child
->n_tasks
;
414 ours
->n_tasks
= child
->n_tasks
;
415 ours
->n_tasks_valid
= true;
426 static int refresh(const char *root
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
431 r
= refresh_one(SYSTEMD_CGROUP_CONTROLLER
, root
, a
, b
, iteration
, 0, NULL
);
434 r
= refresh_one("cpuacct", root
, a
, b
, iteration
, 0, NULL
);
437 r
= refresh_one("memory", root
, a
, b
, iteration
, 0, NULL
);
440 r
= refresh_one("blkio", root
, a
, b
, iteration
, 0, NULL
);
443 r
= refresh_one("pids", root
, a
, b
, iteration
, 0, NULL
);
450 static int group_compare(const void*a
, const void *b
) {
451 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
453 if (arg_order
!= ORDER_TASKS
|| arg_recursive
) {
454 /* Let's make sure that the parent is always before
455 * the child. Except when ordering by tasks and
456 * recursive summing is off, since that is actually
457 * not accumulative for all children. */
459 if (path_startswith(y
->path
, x
->path
))
461 if (path_startswith(x
->path
, y
->path
))
471 if (arg_cpu_type
== CPU_PERCENT
) {
472 if (x
->cpu_valid
&& y
->cpu_valid
) {
473 if (x
->cpu_fraction
> y
->cpu_fraction
)
475 else if (x
->cpu_fraction
< y
->cpu_fraction
)
477 } else if (x
->cpu_valid
)
479 else if (y
->cpu_valid
)
482 if (x
->cpu_usage
> y
->cpu_usage
)
484 else if (x
->cpu_usage
< y
->cpu_usage
)
491 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
492 if (x
->n_tasks
> y
->n_tasks
)
494 else if (x
->n_tasks
< y
->n_tasks
)
496 } else if (x
->n_tasks_valid
)
498 else if (y
->n_tasks_valid
)
504 if (x
->memory_valid
&& y
->memory_valid
) {
505 if (x
->memory
> y
->memory
)
507 else if (x
->memory
< y
->memory
)
509 } else if (x
->memory_valid
)
511 else if (y
->memory_valid
)
517 if (x
->io_valid
&& y
->io_valid
) {
518 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
520 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
522 } else if (x
->io_valid
)
524 else if (y
->io_valid
)
528 return path_compare(x
->path
, y
->path
);
531 static void display(Hashmap
*a
) {
536 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
537 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
542 fputs(ANSI_HOME_CLEAR
, stdout
);
544 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
546 HASHMAP_FOREACH(g
, a
, i
)
547 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
550 qsort_safe(array
, n
, sizeof(Group
*), group_compare
);
552 /* Find the longest names in one run */
553 for (j
= 0; j
< n
; j
++) {
554 unsigned cputlen
, pathtlen
;
556 format_timespan(buffer
, sizeof(buffer
), (usec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
557 cputlen
= strlen(buffer
);
558 maxtcpu
= MAX(maxtcpu
, cputlen
);
560 pathtlen
= strlen(array
[j
]->path
);
561 maxtpath
= MAX(maxtpath
, pathtlen
);
564 if (arg_cpu_type
== CPU_PERCENT
)
565 snprintf(buffer
, sizeof(buffer
), "%6s", "%CPU");
567 snprintf(buffer
, sizeof(buffer
), "%*s", maxtcpu
, "CPU Time");
574 const char *on
, *off
;
576 path_columns
= columns() - 36 - strlen(buffer
);
577 if (path_columns
< 10)
580 on
= ansi_highlight_underline();
581 off
= ansi_underline();
583 printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n",
585 arg_order
== ORDER_PATH
? on
: "", path_columns
, "Control Group",
586 arg_order
== ORDER_PATH
? off
: "",
587 arg_order
== ORDER_TASKS
? on
: "", arg_count
== COUNT_PIDS
? "Tasks" : arg_count
== COUNT_USERSPACE_PROCESSES
? "Procs" : "Proc+",
588 arg_order
== ORDER_TASKS
? off
: "",
589 arg_order
== ORDER_CPU
? on
: "", buffer
,
590 arg_order
== ORDER_CPU
? off
: "",
591 arg_order
== ORDER_MEMORY
? on
: "", "Memory",
592 arg_order
== ORDER_MEMORY
? off
: "",
593 arg_order
== ORDER_IO
? on
: "", "Input/s",
594 arg_order
== ORDER_IO
? off
: "",
595 arg_order
== ORDER_IO
? on
: "", "Output/s",
596 arg_order
== ORDER_IO
? off
: "",
599 path_columns
= maxtpath
;
601 for (j
= 0; j
< n
; j
++) {
602 _cleanup_free_
char *ellipsized
= NULL
;
605 if (on_tty() && j
+ 6 > rows
)
610 path
= isempty(g
->path
) ? "/" : g
->path
;
611 ellipsized
= ellipsize(path
, path_columns
, 33);
612 printf("%-*s", path_columns
, ellipsized
?: path
);
614 if (g
->n_tasks_valid
)
615 printf(" %7" PRIu64
, g
->n_tasks
);
619 if (arg_cpu_type
== CPU_PERCENT
) {
621 printf(" %6.1f", g
->cpu_fraction
*100);
625 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (usec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
627 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
628 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
629 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
635 static void help(void) {
636 printf("%s [OPTIONS...]\n\n"
637 "Show top control groups by their resource usage.\n\n"
638 " -h --help Show this help\n"
639 " --version Show package version\n"
640 " -p --order=path Order by path\n"
641 " -t --order=tasks Order by number of tasks/processes\n"
642 " -c --order=cpu Order by CPU load (default)\n"
643 " -m --order=memory Order by memory load\n"
644 " -i --order=io Order by IO load\n"
645 " -r --raw Provide raw (not human-readable) numbers\n"
646 " --cpu=percentage Show CPU usage as percentage (default)\n"
647 " --cpu=time Show CPU usage as time\n"
648 " -P Count userspace processes instead of tasks (excl. kernel)\n"
649 " -k Count all processes instead of tasks (incl. kernel)\n"
650 " --recursive=BOOL Sum up process count recursively\n"
651 " -d --delay=DELAY Delay between updates\n"
652 " -n --iterations=N Run for N iterations before exiting\n"
653 " -b --batch Run in batch mode, accepting no input\n"
654 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
655 " -M --machine= Show container\n"
656 , program_invocation_short_name
, arg_depth
);
659 static int parse_argv(int argc
, char *argv
[]) {
669 static const struct option options
[] = {
670 { "help", no_argument
, NULL
, 'h' },
671 { "version", no_argument
, NULL
, ARG_VERSION
},
672 { "delay", required_argument
, NULL
, 'd' },
673 { "iterations", required_argument
, NULL
, 'n' },
674 { "batch", no_argument
, NULL
, 'b' },
675 { "raw", no_argument
, NULL
, 'r' },
676 { "depth", required_argument
, NULL
, ARG_DEPTH
},
677 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
678 { "order", required_argument
, NULL
, ARG_ORDER
},
679 { "recursive", required_argument
, NULL
, ARG_RECURSIVE
},
680 { "machine", required_argument
, NULL
, 'M' },
684 bool recursive_unset
= false;
690 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:kPM:", options
, NULL
)) >= 0)
703 if (streq(optarg
, "time"))
704 arg_cpu_type
= CPU_TIME
;
705 else if (streq(optarg
, "percentage"))
706 arg_cpu_type
= CPU_PERCENT
;
708 log_error("Unknown argument to --cpu=: %s", optarg
);
712 arg_cpu_type
= CPU_TIME
;
717 r
= safe_atou(optarg
, &arg_depth
);
719 log_error("Failed to parse depth parameter.");
726 r
= parse_sec(optarg
, &arg_delay
);
727 if (r
< 0 || arg_delay
<= 0) {
728 log_error("Failed to parse delay parameter.");
735 r
= safe_atou(optarg
, &arg_iterations
);
737 log_error("Failed to parse iterations parameter.");
752 arg_order
= ORDER_PATH
;
756 arg_order
= ORDER_TASKS
;
760 arg_order
= ORDER_CPU
;
764 arg_order
= ORDER_MEMORY
;
768 arg_order
= ORDER_IO
;
772 if (streq(optarg
, "path"))
773 arg_order
= ORDER_PATH
;
774 else if (streq(optarg
, "tasks"))
775 arg_order
= ORDER_TASKS
;
776 else if (streq(optarg
, "cpu"))
777 arg_order
= ORDER_CPU
;
778 else if (streq(optarg
, "memory"))
779 arg_order
= ORDER_MEMORY
;
780 else if (streq(optarg
, "io"))
781 arg_order
= ORDER_IO
;
783 log_error("Invalid argument to --order=: %s", optarg
);
789 arg_count
= COUNT_ALL_PROCESSES
;
793 arg_count
= COUNT_USERSPACE_PROCESSES
;
797 r
= parse_boolean(optarg
);
799 log_error("Failed to parse --recursive= argument: %s", optarg
);
804 recursive_unset
= r
== 0;
808 arg_machine
= optarg
;
815 assert_not_reached("Unhandled option");
819 log_error("Too many arguments.");
823 if (recursive_unset
&& arg_count
== COUNT_PIDS
) {
824 log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
831 static const char* counting_what(void) {
832 if (arg_count
== COUNT_PIDS
)
834 else if (arg_count
== COUNT_ALL_PROCESSES
)
835 return "all processes (incl. kernel)";
837 return "userspace processes (excl. kernel)";
840 static int get_cgroup_root(char **ret
) {
841 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
842 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
843 _cleanup_free_
char *unit
= NULL
, *path
= NULL
;
848 r
= cg_get_root_path(ret
);
850 return log_error_errno(r
, "Failed to get root control group path: %m");
855 m
= strjoina("/run/systemd/machines/", arg_machine
);
856 r
= parse_env_file(m
, NEWLINE
, "SCOPE", &unit
, NULL
);
858 return log_error_errno(r
, "Failed to load machine data: %m");
860 path
= unit_dbus_path_from_name(unit
);
864 r
= bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL
, NULL
, false, &bus
);
866 return log_error_errno(r
, "Failed to create bus connection: %m");
868 r
= sd_bus_get_property_string(
870 "org.freedesktop.systemd1",
872 unit_dbus_interface_from_name(unit
),
877 return log_error_errno(r
, "Failed to query unit control group path: %s", bus_error_message(&error
, r
));
882 int main(int argc
, char *argv
[]) {
884 Hashmap
*a
= NULL
, *b
= NULL
;
885 unsigned iteration
= 0;
886 usec_t last_refresh
= 0;
887 bool quit
= false, immediate_refresh
= false;
888 _cleanup_free_
char *root
= NULL
;
891 log_parse_environment();
894 r
= cg_mask_supported(&mask
);
896 log_error_errno(r
, "Failed to determine supported controllers: %m");
900 arg_count
= (mask
& CGROUP_MASK_PIDS
) ? COUNT_PIDS
: COUNT_USERSPACE_PROCESSES
;
902 r
= parse_argv(argc
, argv
);
906 r
= get_cgroup_root(&root
);
908 log_error_errno(r
, "Failed to get root control group path: %m");
912 a
= hashmap_new(&string_hash_ops
);
913 b
= hashmap_new(&string_hash_ops
);
919 signal(SIGWINCH
, columns_lines_cache_reset
);
921 if (arg_iterations
== (unsigned) -1)
922 arg_iterations
= on_tty() ? 0 : 1;
928 char h
[FORMAT_TIMESPAN_MAX
];
930 t
= now(CLOCK_MONOTONIC
);
932 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
934 r
= refresh(root
, a
, b
, iteration
++);
936 log_error_errno(r
, "Failed to refresh: %m");
940 group_hashmap_clear(b
);
947 immediate_refresh
= false;
952 if (arg_iterations
&& iteration
>= arg_iterations
)
955 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
960 (void) usleep(last_refresh
+ arg_delay
- t
);
962 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
966 log_error_errno(r
, "Couldn't read key: %m");
971 if (on_tty()) { /* TTY: Clear any user keystroke */
972 fputs("\r \r", stdout
);
982 immediate_refresh
= true;
990 arg_order
= ORDER_PATH
;
994 arg_order
= ORDER_TASKS
;
998 arg_order
= ORDER_CPU
;
1002 arg_order
= ORDER_MEMORY
;
1006 arg_order
= ORDER_IO
;
1010 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
1014 arg_count
= arg_count
!= COUNT_ALL_PROCESSES
? COUNT_ALL_PROCESSES
: COUNT_PIDS
;
1015 fprintf(stdout
, "\nCounting: %s.", counting_what());
1021 arg_count
= arg_count
!= COUNT_USERSPACE_PROCESSES
? COUNT_USERSPACE_PROCESSES
: COUNT_PIDS
;
1022 fprintf(stdout
, "\nCounting: %s.", counting_what());
1028 if (arg_count
== COUNT_PIDS
)
1029 fprintf(stdout
, "\n\aCannot toggle recursive counting, not available in task counting mode.");
1031 arg_recursive
= !arg_recursive
;
1032 fprintf(stdout
, "\nRecursive process counting: %s", yes_no(arg_recursive
));
1039 if (arg_delay
< USEC_PER_SEC
)
1040 arg_delay
+= USEC_PER_MSEC
*250;
1042 arg_delay
+= USEC_PER_SEC
;
1044 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
1050 if (arg_delay
<= USEC_PER_MSEC
*500)
1051 arg_delay
= USEC_PER_MSEC
*250;
1052 else if (arg_delay
< USEC_PER_MSEC
*1250)
1053 arg_delay
-= USEC_PER_MSEC
*250;
1055 arg_delay
-= USEC_PER_SEC
;
1057 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
1065 #define ON ANSI_HIGHLIGHT
1066 #define OFF ANSI_NORMAL
1069 "\t<" ON
"p" OFF
"> By path; <" ON
"t" OFF
"> By tasks/procs; <" ON
"c" OFF
"> By CPU; <" ON
"m" OFF
"> By memory; <" ON
"i" OFF
"> By I/O\n"
1070 "\t<" ON
"+" OFF
"> Inc. delay; <" ON
"-" OFF
"> Dec. delay; <" ON
"%%" OFF
"> Toggle time; <" ON
"SPACE" OFF
"> Refresh\n"
1071 "\t<" ON
"P" OFF
"> Toggle count userspace processes; <" ON
"k" OFF
"> Toggle count all processes\n"
1072 "\t<" ON
"r" OFF
"> Count processes recursively; <" ON
"q" OFF
"> Quit");
1079 fprintf(stdout
, "\nUnknown key '\\x%x'. Ignoring.", key
);
1081 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
1091 group_hashmap_free(a
);
1092 group_hashmap_free(b
);
1094 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;