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
;
71 COUNT_USERSPACE_PROCESSES
,
73 } arg_count
= COUNT_PIDS
;
74 static bool arg_recursive
= true;
82 } arg_order
= ORDER_CPU
;
87 } arg_cpu_type
= CPU_PERCENT
;
89 static void group_free(Group
*g
) {
96 static void group_hashmap_clear(Hashmap
*h
) {
99 while ((g
= hashmap_steal_first(h
)))
103 static void group_hashmap_free(Hashmap
*h
) {
104 group_hashmap_clear(h
);
108 static const char *maybe_format_bytes(char *buf
, size_t l
, bool is_valid
, uint64_t t
) {
112 snprintf(buf
, l
, "%jd", t
);
115 return format_bytes(buf
, l
, t
);
119 const char *controller
,
133 g
= hashmap_get(a
, path
);
135 g
= hashmap_get(b
, path
);
141 g
->path
= strdup(path
);
147 r
= hashmap_put(a
, g
->path
, g
);
153 r
= hashmap_move_one(a
, b
, path
);
157 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
161 if (streq(controller
, SYSTEMD_CGROUP_CONTROLLER
) && IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
)) {
162 _cleanup_fclose_
FILE *f
= NULL
;
165 r
= cg_enumerate_processes(controller
, path
, &f
);
172 while (cg_read_pid(f
, &pid
) > 0) {
174 if (arg_count
== COUNT_USERSPACE_PROCESSES
&& is_kernel_thread(pid
) > 0)
181 g
->n_tasks_valid
= true;
183 } else if (streq(controller
, "pids") && arg_count
== COUNT_PIDS
) {
184 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
186 r
= cg_get_path(controller
, path
, "pids.current", &p
);
190 r
= read_one_line_file(p
, &v
);
196 r
= safe_atou64(v
, &g
->n_tasks
);
201 g
->n_tasks_valid
= true;
203 } else if (streq(controller
, "cpuacct") && cg_unified() <= 0) {
204 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
208 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
212 r
= read_one_line_file(p
, &v
);
218 r
= safe_atou64(v
, &new_usage
);
222 timestamp
= now_nsec(CLOCK_MONOTONIC
);
224 if (g
->cpu_iteration
== iteration
- 1 &&
225 (nsec_t
) new_usage
> g
->cpu_usage
) {
229 x
= timestamp
- g
->cpu_timestamp
;
233 y
= (nsec_t
) new_usage
- g
->cpu_usage
;
234 g
->cpu_fraction
= (double) y
/ (double) x
;
238 g
->cpu_usage
= (nsec_t
) new_usage
;
239 g
->cpu_timestamp
= timestamp
;
240 g
->cpu_iteration
= iteration
;
242 } else if (streq(controller
, "memory")) {
243 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
245 if (cg_unified() <= 0)
246 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
248 r
= cg_get_path(controller
, path
, "memory.current", &p
);
252 r
= read_one_line_file(p
, &v
);
258 r
= safe_atou64(v
, &g
->memory
);
263 g
->memory_valid
= true;
265 } else if (streq(controller
, "blkio") && cg_unified() <= 0) {
266 _cleanup_fclose_
FILE *f
= NULL
;
267 _cleanup_free_
char *p
= NULL
;
268 uint64_t wr
= 0, rd
= 0;
271 r
= cg_get_path(controller
, path
, "blkio.io_service_bytes", &p
);
283 char line
[LINE_MAX
], *l
;
286 if (!fgets(line
, sizeof(line
), f
))
290 l
+= strcspn(l
, WHITESPACE
);
291 l
+= strspn(l
, WHITESPACE
);
293 if (first_word(l
, "Read")) {
296 } else if (first_word(l
, "Write")) {
302 l
+= strspn(l
, WHITESPACE
);
303 r
= safe_atou64(l
, &k
);
310 timestamp
= now_nsec(CLOCK_MONOTONIC
);
312 if (g
->io_iteration
== iteration
- 1) {
315 x
= (uint64_t) (timestamp
- g
->io_timestamp
);
319 if (rd
> g
->io_input
)
320 yr
= rd
- g
->io_input
;
324 if (wr
> g
->io_output
)
325 yw
= wr
- g
->io_output
;
329 if (yr
> 0 || yw
> 0) {
330 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
331 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
338 g
->io_timestamp
= timestamp
;
339 g
->io_iteration
= iteration
;
348 static int refresh_one(
349 const char *controller
,
357 _cleanup_closedir_
DIR *d
= NULL
;
365 if (depth
> arg_depth
)
368 r
= process(controller
, path
, a
, b
, iteration
, &ours
);
372 r
= cg_enumerate_subgroups(controller
, path
, &d
);
379 _cleanup_free_
char *fn
= NULL
, *p
= NULL
;
382 r
= cg_read_subgroup(d
, &fn
);
388 p
= strjoin(path
, "/", fn
, NULL
);
392 path_kill_slashes(p
);
394 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1, &child
);
399 IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
) &&
401 child
->n_tasks_valid
&&
402 streq(controller
, SYSTEMD_CGROUP_CONTROLLER
)) {
404 /* Recursively sum up processes */
406 if (ours
->n_tasks_valid
)
407 ours
->n_tasks
+= child
->n_tasks
;
409 ours
->n_tasks
= child
->n_tasks
;
410 ours
->n_tasks_valid
= true;
421 static int refresh(const char *root
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
426 r
= refresh_one(SYSTEMD_CGROUP_CONTROLLER
, root
, a
, b
, iteration
, 0, NULL
);
429 r
= refresh_one("cpuacct", root
, a
, b
, iteration
, 0, NULL
);
432 r
= refresh_one("memory", root
, a
, b
, iteration
, 0, NULL
);
435 r
= refresh_one("blkio", root
, a
, b
, iteration
, 0, NULL
);
438 r
= refresh_one("pids", root
, a
, b
, iteration
, 0, NULL
);
445 static int group_compare(const void*a
, const void *b
) {
446 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
448 if (arg_order
!= ORDER_TASKS
|| arg_recursive
) {
449 /* Let's make sure that the parent is always before
450 * the child. Except when ordering by tasks and
451 * recursive summing is off, since that is actually
452 * not accumulative for all children. */
454 if (path_startswith(y
->path
, x
->path
))
456 if (path_startswith(x
->path
, y
->path
))
466 if (arg_cpu_type
== CPU_PERCENT
) {
467 if (x
->cpu_valid
&& y
->cpu_valid
) {
468 if (x
->cpu_fraction
> y
->cpu_fraction
)
470 else if (x
->cpu_fraction
< y
->cpu_fraction
)
472 } else if (x
->cpu_valid
)
474 else if (y
->cpu_valid
)
477 if (x
->cpu_usage
> y
->cpu_usage
)
479 else if (x
->cpu_usage
< y
->cpu_usage
)
486 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
487 if (x
->n_tasks
> y
->n_tasks
)
489 else if (x
->n_tasks
< y
->n_tasks
)
491 } else if (x
->n_tasks_valid
)
493 else if (y
->n_tasks_valid
)
499 if (x
->memory_valid
&& y
->memory_valid
) {
500 if (x
->memory
> y
->memory
)
502 else if (x
->memory
< y
->memory
)
504 } else if (x
->memory_valid
)
506 else if (y
->memory_valid
)
512 if (x
->io_valid
&& y
->io_valid
) {
513 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
515 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
517 } else if (x
->io_valid
)
519 else if (y
->io_valid
)
523 return path_compare(x
->path
, y
->path
);
526 #define ON ANSI_HIGHLIGHT_ON
527 #define OFF ANSI_HIGHLIGHT_OFF
529 static void display(Hashmap
*a
) {
534 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
535 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
539 /* Set cursor to top left corner and clear screen */
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 path_columns
= columns() - 36 - strlen(buffer
);
575 if (path_columns
< 10)
578 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
579 arg_order
== ORDER_PATH
? ON
: "", path_columns
, "Control Group",
580 arg_order
== ORDER_PATH
? OFF
: "",
581 arg_order
== ORDER_TASKS
? ON
: "", arg_count
== COUNT_PIDS
? "Tasks" : arg_count
== COUNT_USERSPACE_PROCESSES
? "Procs" : "Proc+",
582 arg_order
== ORDER_TASKS
? OFF
: "",
583 arg_order
== ORDER_CPU
? ON
: "", buffer
,
584 arg_order
== ORDER_CPU
? OFF
: "",
585 arg_order
== ORDER_MEMORY
? ON
: "", "Memory",
586 arg_order
== ORDER_MEMORY
? OFF
: "",
587 arg_order
== ORDER_IO
? ON
: "", "Input/s",
588 arg_order
== ORDER_IO
? OFF
: "",
589 arg_order
== ORDER_IO
? ON
: "", "Output/s",
590 arg_order
== ORDER_IO
? OFF
: "");
592 path_columns
= maxtpath
;
594 for (j
= 0; j
< n
; j
++) {
595 _cleanup_free_
char *ellipsized
= NULL
;
598 if (on_tty() && j
+ 5 > rows
)
603 path
= isempty(g
->path
) ? "/" : g
->path
;
604 ellipsized
= ellipsize(path
, path_columns
, 33);
605 printf("%-*s", path_columns
, ellipsized
?: path
);
607 if (g
->n_tasks_valid
)
608 printf(" %7" PRIu64
, g
->n_tasks
);
612 if (arg_cpu_type
== CPU_PERCENT
) {
614 printf(" %6.1f", g
->cpu_fraction
*100);
618 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (usec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
620 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
621 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
622 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
628 static void help(void) {
629 printf("%s [OPTIONS...]\n\n"
630 "Show top control groups by their resource usage.\n\n"
631 " -h --help Show this help\n"
632 " --version Show package version\n"
633 " -p --order=path Order by path\n"
634 " -t --order=tasks Order by number of tasks/processes\n"
635 " -c --order=cpu Order by CPU load (default)\n"
636 " -m --order=memory Order by memory load\n"
637 " -i --order=io Order by IO load\n"
638 " -r --raw Provide raw (not human-readable) numbers\n"
639 " --cpu=percentage Show CPU usage as percentage (default)\n"
640 " --cpu=time Show CPU usage as time\n"
641 " -P Count userspace processes instead of tasks (excl. kernel)\n"
642 " -k Count all processes instead of tasks (incl. kernel)\n"
643 " --recursive=BOOL Sum up process count recursively\n"
644 " -d --delay=DELAY Delay between updates\n"
645 " -n --iterations=N Run for N iterations before exiting\n"
646 " -b --batch Run in batch mode, accepting no input\n"
647 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
648 , program_invocation_short_name
, arg_depth
);
651 static int parse_argv(int argc
, char *argv
[]) {
661 static const struct option options
[] = {
662 { "help", no_argument
, NULL
, 'h' },
663 { "version", no_argument
, NULL
, ARG_VERSION
},
664 { "delay", required_argument
, NULL
, 'd' },
665 { "iterations", required_argument
, NULL
, 'n' },
666 { "batch", no_argument
, NULL
, 'b' },
667 { "raw", no_argument
, NULL
, 'r' },
668 { "depth", required_argument
, NULL
, ARG_DEPTH
},
669 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
670 { "order", required_argument
, NULL
, ARG_ORDER
},
671 { "recursive", required_argument
, NULL
, ARG_RECURSIVE
},
675 bool recursive_unset
= false;
681 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:kP", options
, NULL
)) >= 0)
690 puts(PACKAGE_STRING
);
691 puts(SYSTEMD_FEATURES
);
696 if (streq(optarg
, "time"))
697 arg_cpu_type
= CPU_TIME
;
698 else if (streq(optarg
, "percentage"))
699 arg_cpu_type
= CPU_PERCENT
;
701 log_error("Unknown argument to --cpu=: %s", optarg
);
705 arg_cpu_type
= CPU_TIME
;
710 r
= safe_atou(optarg
, &arg_depth
);
712 log_error("Failed to parse depth parameter.");
719 r
= parse_sec(optarg
, &arg_delay
);
720 if (r
< 0 || arg_delay
<= 0) {
721 log_error("Failed to parse delay parameter.");
728 r
= safe_atou(optarg
, &arg_iterations
);
730 log_error("Failed to parse iterations parameter.");
745 arg_order
= ORDER_PATH
;
749 arg_order
= ORDER_TASKS
;
753 arg_order
= ORDER_CPU
;
757 arg_order
= ORDER_MEMORY
;
761 arg_order
= ORDER_IO
;
765 if (streq(optarg
, "path"))
766 arg_order
= ORDER_PATH
;
767 else if (streq(optarg
, "tasks"))
768 arg_order
= ORDER_TASKS
;
769 else if (streq(optarg
, "cpu"))
770 arg_order
= ORDER_CPU
;
771 else if (streq(optarg
, "memory"))
772 arg_order
= ORDER_MEMORY
;
773 else if (streq(optarg
, "io"))
774 arg_order
= ORDER_IO
;
776 log_error("Invalid argument to --order=: %s", optarg
);
782 arg_count
= COUNT_ALL_PROCESSES
;
786 arg_count
= COUNT_USERSPACE_PROCESSES
;
790 r
= parse_boolean(optarg
);
792 log_error("Failed to parse --recursive= argument: %s", optarg
);
797 recursive_unset
= r
== 0;
804 assert_not_reached("Unhandled option");
808 log_error("Too many arguments.");
812 if (recursive_unset
&& arg_count
== COUNT_PIDS
) {
813 log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
820 static const char* counting_what(void) {
821 if (arg_count
== COUNT_PIDS
)
823 else if (arg_count
== COUNT_ALL_PROCESSES
)
824 return "all processes (incl. kernel)";
826 return "userspace processes (excl. kernel)";
829 int main(int argc
, char *argv
[]) {
831 Hashmap
*a
= NULL
, *b
= NULL
;
832 unsigned iteration
= 0;
833 usec_t last_refresh
= 0;
834 bool quit
= false, immediate_refresh
= false;
835 _cleanup_free_
char *root
= NULL
;
838 log_parse_environment();
841 r
= cg_mask_supported(&mask
);
843 log_error_errno(r
, "Failed to determine supported controllers: %m");
847 arg_count
= (mask
& CGROUP_MASK_PIDS
) ? COUNT_PIDS
: COUNT_USERSPACE_PROCESSES
;
849 r
= parse_argv(argc
, argv
);
853 r
= cg_get_root_path(&root
);
855 log_error_errno(r
, "Failed to get root control group path: %m");
859 a
= hashmap_new(&string_hash_ops
);
860 b
= hashmap_new(&string_hash_ops
);
866 signal(SIGWINCH
, columns_lines_cache_reset
);
868 if (arg_iterations
== (unsigned) -1)
869 arg_iterations
= on_tty() ? 0 : 1;
875 char h
[FORMAT_TIMESPAN_MAX
];
877 t
= now(CLOCK_MONOTONIC
);
879 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
881 r
= refresh(root
, a
, b
, iteration
++);
883 log_error_errno(r
, "Failed to refresh: %m");
887 group_hashmap_clear(b
);
894 immediate_refresh
= false;
899 if (arg_iterations
&& iteration
>= arg_iterations
)
902 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
907 (void) usleep(last_refresh
+ arg_delay
- t
);
909 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
913 log_error_errno(r
, "Couldn't read key: %m");
918 if (on_tty()) { /* TTY: Clear any user keystroke */
919 fputs("\r \r", stdout
);
929 immediate_refresh
= true;
937 arg_order
= ORDER_PATH
;
941 arg_order
= ORDER_TASKS
;
945 arg_order
= ORDER_CPU
;
949 arg_order
= ORDER_MEMORY
;
953 arg_order
= ORDER_IO
;
957 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
961 arg_count
= arg_count
!= COUNT_ALL_PROCESSES
? COUNT_ALL_PROCESSES
: COUNT_PIDS
;
962 fprintf(stdout
, "\nCounting: %s.", counting_what());
968 arg_count
= arg_count
!= COUNT_USERSPACE_PROCESSES
? COUNT_USERSPACE_PROCESSES
: COUNT_PIDS
;
969 fprintf(stdout
, "\nCounting: %s.", counting_what());
975 if (arg_count
== COUNT_PIDS
)
976 fprintf(stdout
, "\n\aCannot toggle recursive counting, not available in task counting mode.");
978 arg_recursive
= !arg_recursive
;
979 fprintf(stdout
, "\nRecursive process counting: %s", yes_no(arg_recursive
));
986 if (arg_delay
< USEC_PER_SEC
)
987 arg_delay
+= USEC_PER_MSEC
*250;
989 arg_delay
+= USEC_PER_SEC
;
991 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
997 if (arg_delay
<= USEC_PER_MSEC
*500)
998 arg_delay
= USEC_PER_MSEC
*250;
999 else if (arg_delay
< USEC_PER_MSEC
*1250)
1000 arg_delay
-= USEC_PER_MSEC
*250;
1002 arg_delay
-= USEC_PER_SEC
;
1004 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
1012 "\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"
1013 "\t<" ON
"+" OFF
"> Inc. delay; <" ON
"-" OFF
"> Dec. delay; <" ON
"%%" OFF
"> Toggle time; <" ON
"SPACE" OFF
"> Refresh\n"
1014 "\t<" ON
"P" OFF
"> Toggle count userspace processes; <" ON
"k" OFF
"> Toggle count all processes\n"
1015 "\t<" ON
"r" OFF
"> Count processes recursively; <" ON
"q" OFF
"> Quit");
1022 fprintf(stdout
, "\nUnknown key '\\x%x'. Ignoring.", key
);
1024 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
1034 group_hashmap_free(a
);
1035 group_hashmap_free(b
);
1037 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;