1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2012 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
32 #include "alloc-util.h"
33 #include "bus-error.h"
35 #include "cgroup-show.h"
36 #include "cgroup-util.h"
40 #include "parse-util.h"
41 #include "path-util.h"
42 #include "process-util.h"
43 #include "procfs-util.h"
44 #include "stdio-util.h"
46 #include "terminal-util.h"
47 #include "unit-name.h"
50 typedef struct Group
{
60 unsigned cpu_iteration
;
67 unsigned io_iteration
;
68 uint64_t io_input
, io_output
;
70 uint64_t io_input_bps
, io_output_bps
;
73 static unsigned arg_depth
= 3;
74 static unsigned arg_iterations
= (unsigned) -1;
75 static bool arg_batch
= false;
76 static bool arg_raw
= false;
77 static usec_t arg_delay
= 1*USEC_PER_SEC
;
78 static char* arg_machine
= NULL
;
79 static char* arg_root
= NULL
;
80 static bool arg_recursive
= true;
81 static bool arg_recursive_unset
= false;
85 COUNT_USERSPACE_PROCESSES
,
87 } arg_count
= COUNT_PIDS
;
95 } arg_order
= ORDER_CPU
;
100 } arg_cpu_type
= CPU_PERCENT
;
102 static void group_free(Group
*g
) {
109 static void group_hashmap_clear(Hashmap
*h
) {
110 hashmap_clear_with_destructor(h
, group_free
);
113 static void group_hashmap_free(Hashmap
*h
) {
114 group_hashmap_clear(h
);
118 static const char *maybe_format_bytes(char *buf
, size_t l
, bool is_valid
, uint64_t t
) {
122 snprintf(buf
, l
, "%" PRIu64
, t
);
125 return format_bytes(buf
, l
, t
);
129 const char *controller
,
143 all_unified
= cg_all_unified();
147 g
= hashmap_get(a
, path
);
149 g
= hashmap_get(b
, path
);
155 g
->path
= strdup(path
);
161 r
= hashmap_put(a
, g
->path
, g
);
167 r
= hashmap_move_one(a
, b
, path
);
171 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
175 if (streq(controller
, SYSTEMD_CGROUP_CONTROLLER
) && IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
)) {
176 _cleanup_fclose_
FILE *f
= NULL
;
179 r
= cg_enumerate_processes(controller
, path
, &f
);
186 while (cg_read_pid(f
, &pid
) > 0) {
188 if (arg_count
== COUNT_USERSPACE_PROCESSES
&& is_kernel_thread(pid
) > 0)
195 g
->n_tasks_valid
= true;
197 } else if (streq(controller
, "pids") && arg_count
== COUNT_PIDS
) {
199 if (isempty(path
) || path_equal(path
, "/")) {
200 r
= procfs_tasks_get_current(&g
->n_tasks
);
204 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
206 r
= cg_get_path(controller
, path
, "pids.current", &p
);
210 r
= read_one_line_file(p
, &v
);
216 r
= safe_atou64(v
, &g
->n_tasks
);
222 g
->n_tasks_valid
= true;
224 } else if (STR_IN_SET(controller
, "cpu", "cpuacct")) {
225 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
230 const char *keys
[] = { "usage_usec", NULL
};
231 _cleanup_free_
char *val
= NULL
;
233 if (!streq(controller
, "cpu"))
236 r
= cg_get_keyed_attribute("cpu", path
, "cpu.stat", keys
, &val
);
242 r
= safe_atou64(val
, &new_usage
);
246 new_usage
*= NSEC_PER_USEC
;
248 if (!streq(controller
, "cpuacct"))
251 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
255 r
= read_one_line_file(p
, &v
);
261 r
= safe_atou64(v
, &new_usage
);
266 timestamp
= now_nsec(CLOCK_MONOTONIC
);
268 if (g
->cpu_iteration
== iteration
- 1 &&
269 (nsec_t
) new_usage
> g
->cpu_usage
) {
273 x
= timestamp
- g
->cpu_timestamp
;
277 y
= (nsec_t
) new_usage
- g
->cpu_usage
;
278 g
->cpu_fraction
= (double) y
/ (double) x
;
282 g
->cpu_usage
= (nsec_t
) new_usage
;
283 g
->cpu_timestamp
= timestamp
;
284 g
->cpu_iteration
= iteration
;
286 } else if (streq(controller
, "memory")) {
287 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
290 r
= cg_get_path(controller
, path
, "memory.current", &p
);
292 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
296 r
= read_one_line_file(p
, &v
);
302 r
= safe_atou64(v
, &g
->memory
);
307 g
->memory_valid
= true;
309 } else if ((streq(controller
, "io") && all_unified
) ||
310 (streq(controller
, "blkio") && !all_unified
)) {
311 _cleanup_fclose_
FILE *f
= NULL
;
312 _cleanup_free_
char *p
= NULL
;
313 uint64_t wr
= 0, rd
= 0;
316 r
= cg_get_path(controller
, path
, all_unified
? "io.stat" : "blkio.io_service_bytes", &p
);
328 char line
[LINE_MAX
], *l
;
331 if (!fgets(line
, sizeof(line
), f
))
334 /* Trim and skip the device */
336 l
+= strcspn(l
, WHITESPACE
);
337 l
+= strspn(l
, WHITESPACE
);
340 while (!isempty(l
)) {
341 if (sscanf(l
, "rbytes=%" SCNu64
, &k
))
343 else if (sscanf(l
, "wbytes=%" SCNu64
, &k
))
346 l
+= strcspn(l
, WHITESPACE
);
347 l
+= strspn(l
, WHITESPACE
);
350 if (first_word(l
, "Read")) {
353 } else if (first_word(l
, "Write")) {
359 l
+= strspn(l
, WHITESPACE
);
360 r
= safe_atou64(l
, &k
);
368 timestamp
= now_nsec(CLOCK_MONOTONIC
);
370 if (g
->io_iteration
== iteration
- 1) {
373 x
= (uint64_t) (timestamp
- g
->io_timestamp
);
377 if (rd
> g
->io_input
)
378 yr
= rd
- g
->io_input
;
382 if (wr
> g
->io_output
)
383 yw
= wr
- g
->io_output
;
387 if (yr
> 0 || yw
> 0) {
388 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
389 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
396 g
->io_timestamp
= timestamp
;
397 g
->io_iteration
= iteration
;
406 static int refresh_one(
407 const char *controller
,
415 _cleanup_closedir_
DIR *d
= NULL
;
423 if (depth
> arg_depth
)
426 r
= process(controller
, path
, a
, b
, iteration
, &ours
);
430 r
= cg_enumerate_subgroups(controller
, path
, &d
);
437 _cleanup_free_
char *fn
= NULL
, *p
= NULL
;
440 r
= cg_read_subgroup(d
, &fn
);
446 p
= strjoin(path
, "/", fn
);
450 path_kill_slashes(p
);
452 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1, &child
);
457 IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
) &&
459 child
->n_tasks_valid
&&
460 streq(controller
, SYSTEMD_CGROUP_CONTROLLER
)) {
462 /* Recursively sum up processes */
464 if (ours
->n_tasks_valid
)
465 ours
->n_tasks
+= child
->n_tasks
;
467 ours
->n_tasks
= child
->n_tasks
;
468 ours
->n_tasks_valid
= true;
479 static int refresh(const char *root
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
484 r
= refresh_one(SYSTEMD_CGROUP_CONTROLLER
, root
, a
, b
, iteration
, 0, NULL
);
487 r
= refresh_one("cpu", root
, a
, b
, iteration
, 0, NULL
);
490 r
= refresh_one("cpuacct", root
, a
, b
, iteration
, 0, NULL
);
493 r
= refresh_one("memory", root
, a
, b
, iteration
, 0, NULL
);
496 r
= refresh_one("io", root
, a
, b
, iteration
, 0, NULL
);
499 r
= refresh_one("blkio", root
, a
, b
, iteration
, 0, NULL
);
502 r
= refresh_one("pids", root
, a
, b
, iteration
, 0, NULL
);
509 static const char *empty_to_slash(const char *p
) {
510 return isempty(p
) ? "/" : p
;
513 static int group_compare(const void*a
, const void *b
) {
514 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
516 if (arg_order
!= ORDER_TASKS
|| arg_recursive
) {
517 /* Let's make sure that the parent is always before
518 * the child. Except when ordering by tasks and
519 * recursive summing is off, since that is actually
520 * not accumulative for all children. */
522 if (path_startswith(empty_to_slash(y
->path
), empty_to_slash(x
->path
)))
524 if (path_startswith(empty_to_slash(x
->path
), empty_to_slash(y
->path
)))
534 if (arg_cpu_type
== CPU_PERCENT
) {
535 if (x
->cpu_valid
&& y
->cpu_valid
) {
536 if (x
->cpu_fraction
> y
->cpu_fraction
)
538 else if (x
->cpu_fraction
< y
->cpu_fraction
)
540 } else if (x
->cpu_valid
)
542 else if (y
->cpu_valid
)
545 if (x
->cpu_usage
> y
->cpu_usage
)
547 else if (x
->cpu_usage
< y
->cpu_usage
)
554 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
555 if (x
->n_tasks
> y
->n_tasks
)
557 else if (x
->n_tasks
< y
->n_tasks
)
559 } else if (x
->n_tasks_valid
)
561 else if (y
->n_tasks_valid
)
567 if (x
->memory_valid
&& y
->memory_valid
) {
568 if (x
->memory
> y
->memory
)
570 else if (x
->memory
< y
->memory
)
572 } else if (x
->memory_valid
)
574 else if (y
->memory_valid
)
580 if (x
->io_valid
&& y
->io_valid
) {
581 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
583 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
585 } else if (x
->io_valid
)
587 else if (y
->io_valid
)
591 return path_compare(x
->path
, y
->path
);
594 static void display(Hashmap
*a
) {
599 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
600 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
604 if (!terminal_is_dumb())
605 fputs(ANSI_HOME_CLEAR
, stdout
);
607 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
609 HASHMAP_FOREACH(g
, a
, i
)
610 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
613 qsort_safe(array
, n
, sizeof(Group
*), group_compare
);
615 /* Find the longest names in one run */
616 for (j
= 0; j
< n
; j
++) {
617 unsigned cputlen
, pathtlen
;
619 format_timespan(buffer
, sizeof(buffer
), (usec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
620 cputlen
= strlen(buffer
);
621 maxtcpu
= MAX(maxtcpu
, cputlen
);
623 pathtlen
= strlen(array
[j
]->path
);
624 maxtpath
= MAX(maxtpath
, pathtlen
);
627 if (arg_cpu_type
== CPU_PERCENT
)
628 xsprintf(buffer
, "%6s", "%CPU");
630 xsprintf(buffer
, "%*s", maxtcpu
, "CPU Time");
637 const char *on
, *off
;
639 path_columns
= columns() - 36 - strlen(buffer
);
640 if (path_columns
< 10)
643 on
= ansi_highlight_underline();
644 off
= ansi_underline();
646 printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n",
648 arg_order
== ORDER_PATH
? on
: "", path_columns
, "Control Group",
649 arg_order
== ORDER_PATH
? off
: "",
650 arg_order
== ORDER_TASKS
? on
: "", arg_count
== COUNT_PIDS
? "Tasks" : arg_count
== COUNT_USERSPACE_PROCESSES
? "Procs" : "Proc+",
651 arg_order
== ORDER_TASKS
? off
: "",
652 arg_order
== ORDER_CPU
? on
: "", buffer
,
653 arg_order
== ORDER_CPU
? off
: "",
654 arg_order
== ORDER_MEMORY
? on
: "", "Memory",
655 arg_order
== ORDER_MEMORY
? off
: "",
656 arg_order
== ORDER_IO
? on
: "", "Input/s",
657 arg_order
== ORDER_IO
? off
: "",
658 arg_order
== ORDER_IO
? on
: "", "Output/s",
659 arg_order
== ORDER_IO
? off
: "",
662 path_columns
= maxtpath
;
664 for (j
= 0; j
< n
; j
++) {
665 _cleanup_free_
char *ellipsized
= NULL
;
668 if (on_tty() && j
+ 6 > rows
)
673 path
= empty_to_slash(g
->path
);
674 ellipsized
= ellipsize(path
, path_columns
, 33);
675 printf("%-*s", path_columns
, ellipsized
?: path
);
677 if (g
->n_tasks_valid
)
678 printf(" %7" PRIu64
, g
->n_tasks
);
682 if (arg_cpu_type
== CPU_PERCENT
) {
684 printf(" %6.1f", g
->cpu_fraction
*100);
688 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (usec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
690 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
691 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
692 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
698 static void help(void) {
699 printf("%s [OPTIONS...] [CGROUP]\n\n"
700 "Show top control groups by their resource usage.\n\n"
701 " -h --help Show this help\n"
702 " --version Show package version\n"
703 " -p --order=path Order by path\n"
704 " -t --order=tasks Order by number of tasks/processes\n"
705 " -c --order=cpu Order by CPU load (default)\n"
706 " -m --order=memory Order by memory load\n"
707 " -i --order=io Order by IO load\n"
708 " -r --raw Provide raw (not human-readable) numbers\n"
709 " --cpu=percentage Show CPU usage as percentage (default)\n"
710 " --cpu=time Show CPU usage as time\n"
711 " -P Count userspace processes instead of tasks (excl. kernel)\n"
712 " -k Count all processes instead of tasks (incl. kernel)\n"
713 " --recursive=BOOL Sum up process count recursively\n"
714 " -d --delay=DELAY Delay between updates\n"
715 " -n --iterations=N Run for N iterations before exiting\n"
716 " -b --batch Run in batch mode, accepting no input\n"
717 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
718 " -M --machine= Show container\n"
719 , program_invocation_short_name
, arg_depth
);
722 static int parse_argv(int argc
, char *argv
[]) {
732 static const struct option options
[] = {
733 { "help", no_argument
, NULL
, 'h' },
734 { "version", no_argument
, NULL
, ARG_VERSION
},
735 { "delay", required_argument
, NULL
, 'd' },
736 { "iterations", required_argument
, NULL
, 'n' },
737 { "batch", no_argument
, NULL
, 'b' },
738 { "raw", no_argument
, NULL
, 'r' },
739 { "depth", required_argument
, NULL
, ARG_DEPTH
},
740 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
741 { "order", required_argument
, NULL
, ARG_ORDER
},
742 { "recursive", required_argument
, NULL
, ARG_RECURSIVE
},
743 { "machine", required_argument
, NULL
, 'M' },
752 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:kPM:", options
, NULL
)) >= 0)
765 if (streq(optarg
, "time"))
766 arg_cpu_type
= CPU_TIME
;
767 else if (streq(optarg
, "percentage"))
768 arg_cpu_type
= CPU_PERCENT
;
770 log_error("Unknown argument to --cpu=: %s", optarg
);
774 arg_cpu_type
= CPU_TIME
;
779 r
= safe_atou(optarg
, &arg_depth
);
781 log_error("Failed to parse depth parameter.");
788 r
= parse_sec(optarg
, &arg_delay
);
789 if (r
< 0 || arg_delay
<= 0) {
790 log_error("Failed to parse delay parameter.");
797 r
= safe_atou(optarg
, &arg_iterations
);
799 log_error("Failed to parse iterations parameter.");
814 arg_order
= ORDER_PATH
;
818 arg_order
= ORDER_TASKS
;
822 arg_order
= ORDER_CPU
;
826 arg_order
= ORDER_MEMORY
;
830 arg_order
= ORDER_IO
;
834 if (streq(optarg
, "path"))
835 arg_order
= ORDER_PATH
;
836 else if (streq(optarg
, "tasks"))
837 arg_order
= ORDER_TASKS
;
838 else if (streq(optarg
, "cpu"))
839 arg_order
= ORDER_CPU
;
840 else if (streq(optarg
, "memory"))
841 arg_order
= ORDER_MEMORY
;
842 else if (streq(optarg
, "io"))
843 arg_order
= ORDER_IO
;
845 log_error("Invalid argument to --order=: %s", optarg
);
851 arg_count
= COUNT_ALL_PROCESSES
;
855 arg_count
= COUNT_USERSPACE_PROCESSES
;
859 r
= parse_boolean(optarg
);
861 log_error("Failed to parse --recursive= argument: %s", optarg
);
866 arg_recursive_unset
= r
== 0;
870 arg_machine
= optarg
;
877 assert_not_reached("Unhandled option");
880 if (optind
== argc
- 1)
881 arg_root
= argv
[optind
];
882 else if (optind
< argc
) {
883 log_error("Too many arguments.");
890 static const char* counting_what(void) {
891 if (arg_count
== COUNT_PIDS
)
893 else if (arg_count
== COUNT_ALL_PROCESSES
)
894 return "all processes (incl. kernel)";
896 return "userspace processes (excl. kernel)";
899 int main(int argc
, char *argv
[]) {
901 Hashmap
*a
= NULL
, *b
= NULL
;
902 unsigned iteration
= 0;
903 usec_t last_refresh
= 0;
904 bool quit
= false, immediate_refresh
= false;
905 _cleanup_free_
char *root
= NULL
;
908 log_parse_environment();
911 r
= parse_argv(argc
, argv
);
915 r
= cg_mask_supported(&mask
);
917 log_error_errno(r
, "Failed to determine supported controllers: %m");
921 arg_count
= (mask
& CGROUP_MASK_PIDS
) ? COUNT_PIDS
: COUNT_USERSPACE_PROCESSES
;
923 if (arg_recursive_unset
&& arg_count
== COUNT_PIDS
) {
924 log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
928 r
= show_cgroup_get_path_and_warn(arg_machine
, arg_root
, &root
);
930 log_error_errno(r
, "Failed to get root control group path: %m");
933 log_debug("Cgroup path: %s", root
);
935 a
= hashmap_new(&string_hash_ops
);
936 b
= hashmap_new(&string_hash_ops
);
942 signal(SIGWINCH
, columns_lines_cache_reset
);
944 if (arg_iterations
== (unsigned) -1)
945 arg_iterations
= on_tty() ? 0 : 1;
951 char h
[FORMAT_TIMESPAN_MAX
];
953 t
= now(CLOCK_MONOTONIC
);
955 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
957 r
= refresh(root
, a
, b
, iteration
++);
959 log_error_errno(r
, "Failed to refresh: %m");
963 group_hashmap_clear(b
);
970 immediate_refresh
= false;
975 if (arg_iterations
&& iteration
>= arg_iterations
)
978 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
983 (void) usleep(last_refresh
+ arg_delay
- t
);
985 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
989 log_error_errno(r
, "Couldn't read key: %m");
994 if (on_tty()) { /* TTY: Clear any user keystroke */
995 fputs("\r \r", stdout
);
1005 immediate_refresh
= true;
1013 arg_order
= ORDER_PATH
;
1017 arg_order
= ORDER_TASKS
;
1021 arg_order
= ORDER_CPU
;
1025 arg_order
= ORDER_MEMORY
;
1029 arg_order
= ORDER_IO
;
1033 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
1037 arg_count
= arg_count
!= COUNT_ALL_PROCESSES
? COUNT_ALL_PROCESSES
: COUNT_PIDS
;
1038 fprintf(stdout
, "\nCounting: %s.", counting_what());
1044 arg_count
= arg_count
!= COUNT_USERSPACE_PROCESSES
? COUNT_USERSPACE_PROCESSES
: COUNT_PIDS
;
1045 fprintf(stdout
, "\nCounting: %s.", counting_what());
1051 if (arg_count
== COUNT_PIDS
)
1052 fprintf(stdout
, "\n\aCannot toggle recursive counting, not available in task counting mode.");
1054 arg_recursive
= !arg_recursive
;
1055 fprintf(stdout
, "\nRecursive process counting: %s", yes_no(arg_recursive
));
1062 if (arg_delay
< USEC_PER_SEC
)
1063 arg_delay
+= USEC_PER_MSEC
*250;
1065 arg_delay
+= USEC_PER_SEC
;
1067 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
1073 if (arg_delay
<= USEC_PER_MSEC
*500)
1074 arg_delay
= USEC_PER_MSEC
*250;
1075 else if (arg_delay
< USEC_PER_MSEC
*1250)
1076 arg_delay
-= USEC_PER_MSEC
*250;
1078 arg_delay
-= USEC_PER_SEC
;
1080 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
1088 #define ON ANSI_HIGHLIGHT
1089 #define OFF ANSI_NORMAL
1092 "\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"
1093 "\t<" ON
"+" OFF
"> Inc. delay; <" ON
"-" OFF
"> Dec. delay; <" ON
"%%" OFF
"> Toggle time; <" ON
"SPACE" OFF
"> Refresh\n"
1094 "\t<" ON
"P" OFF
"> Toggle count userspace processes; <" ON
"k" OFF
"> Toggle count all processes\n"
1095 "\t<" ON
"r" OFF
"> Count processes recursively; <" ON
"q" OFF
"> Quit");
1102 fprintf(stdout
, "\nUnknown key '\\x%x'. Ignoring.", key
);
1104 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
1114 group_hashmap_free(a
);
1115 group_hashmap_free(b
);
1117 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;