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
);
128 static bool is_root_cgroup(const char *path
) {
129 return isempty(path
) || path_equal(path
, "/");
133 const char *controller
,
147 all_unified
= cg_all_unified();
151 g
= hashmap_get(a
, path
);
153 g
= hashmap_get(b
, path
);
159 g
->path
= strdup(path
);
165 r
= hashmap_put(a
, g
->path
, g
);
171 r
= hashmap_move_one(a
, b
, path
);
175 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
179 if (streq(controller
, SYSTEMD_CGROUP_CONTROLLER
) && IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
)) {
180 _cleanup_fclose_
FILE *f
= NULL
;
183 r
= cg_enumerate_processes(controller
, path
, &f
);
190 while (cg_read_pid(f
, &pid
) > 0) {
192 if (arg_count
== COUNT_USERSPACE_PROCESSES
&& is_kernel_thread(pid
) > 0)
199 g
->n_tasks_valid
= true;
201 } else if (streq(controller
, "pids") && arg_count
== COUNT_PIDS
) {
203 if (is_root_cgroup(path
)) {
204 r
= procfs_tasks_get_current(&g
->n_tasks
);
208 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
210 r
= cg_get_path(controller
, path
, "pids.current", &p
);
214 r
= read_one_line_file(p
, &v
);
220 r
= safe_atou64(v
, &g
->n_tasks
);
226 g
->n_tasks_valid
= true;
228 } else if (STR_IN_SET(controller
, "cpu", "cpuacct")) {
229 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
234 const char *keys
[] = { "usage_usec", NULL
};
235 _cleanup_free_
char *val
= NULL
;
237 if (!streq(controller
, "cpu"))
240 r
= cg_get_keyed_attribute("cpu", path
, "cpu.stat", keys
, &val
);
246 r
= safe_atou64(val
, &new_usage
);
250 new_usage
*= NSEC_PER_USEC
;
252 if (!streq(controller
, "cpuacct"))
255 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
259 r
= read_one_line_file(p
, &v
);
265 r
= safe_atou64(v
, &new_usage
);
270 timestamp
= now_nsec(CLOCK_MONOTONIC
);
272 if (g
->cpu_iteration
== iteration
- 1 &&
273 (nsec_t
) new_usage
> g
->cpu_usage
) {
277 x
= timestamp
- g
->cpu_timestamp
;
281 y
= (nsec_t
) new_usage
- g
->cpu_usage
;
282 g
->cpu_fraction
= (double) y
/ (double) x
;
286 g
->cpu_usage
= (nsec_t
) new_usage
;
287 g
->cpu_timestamp
= timestamp
;
288 g
->cpu_iteration
= iteration
;
290 } else if (streq(controller
, "memory")) {
291 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
294 r
= cg_get_path(controller
, path
, "memory.current", &p
);
296 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
300 r
= read_one_line_file(p
, &v
);
306 r
= safe_atou64(v
, &g
->memory
);
311 g
->memory_valid
= true;
313 } else if ((streq(controller
, "io") && all_unified
) ||
314 (streq(controller
, "blkio") && !all_unified
)) {
315 _cleanup_fclose_
FILE *f
= NULL
;
316 _cleanup_free_
char *p
= NULL
;
317 uint64_t wr
= 0, rd
= 0;
320 r
= cg_get_path(controller
, path
, all_unified
? "io.stat" : "blkio.io_service_bytes", &p
);
332 char line
[LINE_MAX
], *l
;
335 if (!fgets(line
, sizeof(line
), f
))
338 /* Trim and skip the device */
340 l
+= strcspn(l
, WHITESPACE
);
341 l
+= strspn(l
, WHITESPACE
);
344 while (!isempty(l
)) {
345 if (sscanf(l
, "rbytes=%" SCNu64
, &k
))
347 else if (sscanf(l
, "wbytes=%" SCNu64
, &k
))
350 l
+= strcspn(l
, WHITESPACE
);
351 l
+= strspn(l
, WHITESPACE
);
354 if (first_word(l
, "Read")) {
357 } else if (first_word(l
, "Write")) {
363 l
+= strspn(l
, WHITESPACE
);
364 r
= safe_atou64(l
, &k
);
372 timestamp
= now_nsec(CLOCK_MONOTONIC
);
374 if (g
->io_iteration
== iteration
- 1) {
377 x
= (uint64_t) (timestamp
- g
->io_timestamp
);
381 if (rd
> g
->io_input
)
382 yr
= rd
- g
->io_input
;
386 if (wr
> g
->io_output
)
387 yw
= wr
- g
->io_output
;
391 if (yr
> 0 || yw
> 0) {
392 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
393 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
400 g
->io_timestamp
= timestamp
;
401 g
->io_iteration
= iteration
;
410 static int refresh_one(
411 const char *controller
,
419 _cleanup_closedir_
DIR *d
= NULL
;
427 if (depth
> arg_depth
)
430 r
= process(controller
, path
, a
, b
, iteration
, &ours
);
434 r
= cg_enumerate_subgroups(controller
, path
, &d
);
441 _cleanup_free_
char *fn
= NULL
, *p
= NULL
;
444 r
= cg_read_subgroup(d
, &fn
);
450 p
= strjoin(path
, "/", fn
);
454 path_kill_slashes(p
);
456 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1, &child
);
461 IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
) &&
463 child
->n_tasks_valid
&&
464 streq(controller
, SYSTEMD_CGROUP_CONTROLLER
)) {
466 /* Recursively sum up processes */
468 if (ours
->n_tasks_valid
)
469 ours
->n_tasks
+= child
->n_tasks
;
471 ours
->n_tasks
= child
->n_tasks
;
472 ours
->n_tasks_valid
= true;
483 static int refresh(const char *root
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
488 r
= refresh_one(SYSTEMD_CGROUP_CONTROLLER
, root
, a
, b
, iteration
, 0, NULL
);
491 r
= refresh_one("cpu", root
, a
, b
, iteration
, 0, NULL
);
494 r
= refresh_one("cpuacct", root
, a
, b
, iteration
, 0, NULL
);
497 r
= refresh_one("memory", root
, a
, b
, iteration
, 0, NULL
);
500 r
= refresh_one("io", root
, a
, b
, iteration
, 0, NULL
);
503 r
= refresh_one("blkio", root
, a
, b
, iteration
, 0, NULL
);
506 r
= refresh_one("pids", root
, a
, b
, iteration
, 0, NULL
);
513 static const char *empty_to_slash(const char *p
) {
514 return isempty(p
) ? "/" : p
;
517 static int group_compare(const void*a
, const void *b
) {
518 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
520 if (arg_order
!= ORDER_TASKS
|| arg_recursive
) {
521 /* Let's make sure that the parent is always before
522 * the child. Except when ordering by tasks and
523 * recursive summing is off, since that is actually
524 * not accumulative for all children. */
526 if (path_startswith(empty_to_slash(y
->path
), empty_to_slash(x
->path
)))
528 if (path_startswith(empty_to_slash(x
->path
), empty_to_slash(y
->path
)))
538 if (arg_cpu_type
== CPU_PERCENT
) {
539 if (x
->cpu_valid
&& y
->cpu_valid
) {
540 if (x
->cpu_fraction
> y
->cpu_fraction
)
542 else if (x
->cpu_fraction
< y
->cpu_fraction
)
544 } else if (x
->cpu_valid
)
546 else if (y
->cpu_valid
)
549 if (x
->cpu_usage
> y
->cpu_usage
)
551 else if (x
->cpu_usage
< y
->cpu_usage
)
558 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
559 if (x
->n_tasks
> y
->n_tasks
)
561 else if (x
->n_tasks
< y
->n_tasks
)
563 } else if (x
->n_tasks_valid
)
565 else if (y
->n_tasks_valid
)
571 if (x
->memory_valid
&& y
->memory_valid
) {
572 if (x
->memory
> y
->memory
)
574 else if (x
->memory
< y
->memory
)
576 } else if (x
->memory_valid
)
578 else if (y
->memory_valid
)
584 if (x
->io_valid
&& y
->io_valid
) {
585 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
587 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
589 } else if (x
->io_valid
)
591 else if (y
->io_valid
)
595 return path_compare(x
->path
, y
->path
);
598 static void display(Hashmap
*a
) {
603 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
604 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
608 if (!terminal_is_dumb())
609 fputs(ANSI_HOME_CLEAR
, stdout
);
611 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
613 HASHMAP_FOREACH(g
, a
, i
)
614 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
617 qsort_safe(array
, n
, sizeof(Group
*), group_compare
);
619 /* Find the longest names in one run */
620 for (j
= 0; j
< n
; j
++) {
621 unsigned cputlen
, pathtlen
;
623 format_timespan(buffer
, sizeof(buffer
), (usec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
624 cputlen
= strlen(buffer
);
625 maxtcpu
= MAX(maxtcpu
, cputlen
);
627 pathtlen
= strlen(array
[j
]->path
);
628 maxtpath
= MAX(maxtpath
, pathtlen
);
631 if (arg_cpu_type
== CPU_PERCENT
)
632 xsprintf(buffer
, "%6s", "%CPU");
634 xsprintf(buffer
, "%*s", maxtcpu
, "CPU Time");
641 const char *on
, *off
;
643 path_columns
= columns() - 36 - strlen(buffer
);
644 if (path_columns
< 10)
647 on
= ansi_highlight_underline();
648 off
= ansi_underline();
650 printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n",
652 arg_order
== ORDER_PATH
? on
: "", path_columns
, "Control Group",
653 arg_order
== ORDER_PATH
? off
: "",
654 arg_order
== ORDER_TASKS
? on
: "", arg_count
== COUNT_PIDS
? "Tasks" : arg_count
== COUNT_USERSPACE_PROCESSES
? "Procs" : "Proc+",
655 arg_order
== ORDER_TASKS
? off
: "",
656 arg_order
== ORDER_CPU
? on
: "", buffer
,
657 arg_order
== ORDER_CPU
? off
: "",
658 arg_order
== ORDER_MEMORY
? on
: "", "Memory",
659 arg_order
== ORDER_MEMORY
? off
: "",
660 arg_order
== ORDER_IO
? on
: "", "Input/s",
661 arg_order
== ORDER_IO
? off
: "",
662 arg_order
== ORDER_IO
? on
: "", "Output/s",
663 arg_order
== ORDER_IO
? off
: "",
666 path_columns
= maxtpath
;
668 for (j
= 0; j
< n
; j
++) {
669 _cleanup_free_
char *ellipsized
= NULL
;
672 if (on_tty() && j
+ 6 > rows
)
677 path
= empty_to_slash(g
->path
);
678 ellipsized
= ellipsize(path
, path_columns
, 33);
679 printf("%-*s", path_columns
, ellipsized
?: path
);
681 if (g
->n_tasks_valid
)
682 printf(" %7" PRIu64
, g
->n_tasks
);
686 if (arg_cpu_type
== CPU_PERCENT
) {
688 printf(" %6.1f", g
->cpu_fraction
*100);
692 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (usec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
694 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
695 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
696 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
702 static void help(void) {
703 printf("%s [OPTIONS...] [CGROUP]\n\n"
704 "Show top control groups by their resource usage.\n\n"
705 " -h --help Show this help\n"
706 " --version Show package version\n"
707 " -p --order=path Order by path\n"
708 " -t --order=tasks Order by number of tasks/processes\n"
709 " -c --order=cpu Order by CPU load (default)\n"
710 " -m --order=memory Order by memory load\n"
711 " -i --order=io Order by IO load\n"
712 " -r --raw Provide raw (not human-readable) numbers\n"
713 " --cpu=percentage Show CPU usage as percentage (default)\n"
714 " --cpu=time Show CPU usage as time\n"
715 " -P Count userspace processes instead of tasks (excl. kernel)\n"
716 " -k Count all processes instead of tasks (incl. kernel)\n"
717 " --recursive=BOOL Sum up process count recursively\n"
718 " -d --delay=DELAY Delay between updates\n"
719 " -n --iterations=N Run for N iterations before exiting\n"
720 " -1 Shortcut for --iterations=1\n"
721 " -b --batch Run in batch mode, accepting no input\n"
722 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
723 " -M --machine= Show container\n"
724 , program_invocation_short_name
, arg_depth
);
727 static int parse_argv(int argc
, char *argv
[]) {
737 static const struct option options
[] = {
738 { "help", no_argument
, NULL
, 'h' },
739 { "version", no_argument
, NULL
, ARG_VERSION
},
740 { "delay", required_argument
, NULL
, 'd' },
741 { "iterations", required_argument
, NULL
, 'n' },
742 { "batch", no_argument
, NULL
, 'b' },
743 { "raw", no_argument
, NULL
, 'r' },
744 { "depth", required_argument
, NULL
, ARG_DEPTH
},
745 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
746 { "order", required_argument
, NULL
, ARG_ORDER
},
747 { "recursive", required_argument
, NULL
, ARG_RECURSIVE
},
748 { "machine", required_argument
, NULL
, 'M' },
757 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:kPM:1", options
, NULL
)) >= 0)
770 if (streq(optarg
, "time"))
771 arg_cpu_type
= CPU_TIME
;
772 else if (streq(optarg
, "percentage"))
773 arg_cpu_type
= CPU_PERCENT
;
775 log_error("Unknown argument to --cpu=: %s", optarg
);
779 arg_cpu_type
= CPU_TIME
;
784 r
= safe_atou(optarg
, &arg_depth
);
786 log_error("Failed to parse depth parameter.");
793 r
= parse_sec(optarg
, &arg_delay
);
794 if (r
< 0 || arg_delay
<= 0) {
795 log_error("Failed to parse delay parameter.");
802 r
= safe_atou(optarg
, &arg_iterations
);
804 log_error("Failed to parse iterations parameter.");
823 arg_order
= ORDER_PATH
;
827 arg_order
= ORDER_TASKS
;
831 arg_order
= ORDER_CPU
;
835 arg_order
= ORDER_MEMORY
;
839 arg_order
= ORDER_IO
;
843 if (streq(optarg
, "path"))
844 arg_order
= ORDER_PATH
;
845 else if (streq(optarg
, "tasks"))
846 arg_order
= ORDER_TASKS
;
847 else if (streq(optarg
, "cpu"))
848 arg_order
= ORDER_CPU
;
849 else if (streq(optarg
, "memory"))
850 arg_order
= ORDER_MEMORY
;
851 else if (streq(optarg
, "io"))
852 arg_order
= ORDER_IO
;
854 log_error("Invalid argument to --order=: %s", optarg
);
860 arg_count
= COUNT_ALL_PROCESSES
;
864 arg_count
= COUNT_USERSPACE_PROCESSES
;
868 r
= parse_boolean(optarg
);
870 log_error("Failed to parse --recursive= argument: %s", optarg
);
875 arg_recursive_unset
= r
== 0;
879 arg_machine
= optarg
;
886 assert_not_reached("Unhandled option");
889 if (optind
== argc
- 1)
890 arg_root
= argv
[optind
];
891 else if (optind
< argc
) {
892 log_error("Too many arguments.");
899 static const char* counting_what(void) {
900 if (arg_count
== COUNT_PIDS
)
902 else if (arg_count
== COUNT_ALL_PROCESSES
)
903 return "all processes (incl. kernel)";
905 return "userspace processes (excl. kernel)";
908 int main(int argc
, char *argv
[]) {
910 Hashmap
*a
= NULL
, *b
= NULL
;
911 unsigned iteration
= 0;
912 usec_t last_refresh
= 0;
913 bool quit
= false, immediate_refresh
= false;
914 _cleanup_free_
char *root
= NULL
;
917 log_parse_environment();
920 r
= parse_argv(argc
, argv
);
924 r
= cg_mask_supported(&mask
);
926 log_error_errno(r
, "Failed to determine supported controllers: %m");
930 arg_count
= (mask
& CGROUP_MASK_PIDS
) ? COUNT_PIDS
: COUNT_USERSPACE_PROCESSES
;
932 if (arg_recursive_unset
&& arg_count
== COUNT_PIDS
) {
933 log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
937 r
= show_cgroup_get_path_and_warn(arg_machine
, arg_root
, &root
);
939 log_error_errno(r
, "Failed to get root control group path: %m");
942 log_debug("Cgroup path: %s", root
);
944 a
= hashmap_new(&string_hash_ops
);
945 b
= hashmap_new(&string_hash_ops
);
951 signal(SIGWINCH
, columns_lines_cache_reset
);
953 if (arg_iterations
== (unsigned) -1)
954 arg_iterations
= on_tty() ? 0 : 1;
960 char h
[FORMAT_TIMESPAN_MAX
];
962 t
= now(CLOCK_MONOTONIC
);
964 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
966 r
= refresh(root
, a
, b
, iteration
++);
968 log_error_errno(r
, "Failed to refresh: %m");
972 group_hashmap_clear(b
);
979 immediate_refresh
= false;
984 if (arg_iterations
&& iteration
>= arg_iterations
)
987 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
992 (void) usleep(last_refresh
+ arg_delay
- t
);
994 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
998 log_error_errno(r
, "Couldn't read key: %m");
1003 if (on_tty()) { /* TTY: Clear any user keystroke */
1004 fputs("\r \r", stdout
);
1014 immediate_refresh
= true;
1022 arg_order
= ORDER_PATH
;
1026 arg_order
= ORDER_TASKS
;
1030 arg_order
= ORDER_CPU
;
1034 arg_order
= ORDER_MEMORY
;
1038 arg_order
= ORDER_IO
;
1042 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
1046 arg_count
= arg_count
!= COUNT_ALL_PROCESSES
? COUNT_ALL_PROCESSES
: COUNT_PIDS
;
1047 fprintf(stdout
, "\nCounting: %s.", counting_what());
1053 arg_count
= arg_count
!= COUNT_USERSPACE_PROCESSES
? COUNT_USERSPACE_PROCESSES
: COUNT_PIDS
;
1054 fprintf(stdout
, "\nCounting: %s.", counting_what());
1060 if (arg_count
== COUNT_PIDS
)
1061 fprintf(stdout
, "\n\aCannot toggle recursive counting, not available in task counting mode.");
1063 arg_recursive
= !arg_recursive
;
1064 fprintf(stdout
, "\nRecursive process counting: %s", yes_no(arg_recursive
));
1071 if (arg_delay
< USEC_PER_SEC
)
1072 arg_delay
+= USEC_PER_MSEC
*250;
1074 arg_delay
+= USEC_PER_SEC
;
1076 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
1082 if (arg_delay
<= USEC_PER_MSEC
*500)
1083 arg_delay
= USEC_PER_MSEC
*250;
1084 else if (arg_delay
< USEC_PER_MSEC
*1250)
1085 arg_delay
-= USEC_PER_MSEC
*250;
1087 arg_delay
-= USEC_PER_SEC
;
1089 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
1097 #define ON ANSI_HIGHLIGHT
1098 #define OFF ANSI_NORMAL
1101 "\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"
1102 "\t<" ON
"+" OFF
"> Inc. delay; <" ON
"-" OFF
"> Dec. delay; <" ON
"%%" OFF
"> Toggle time; <" ON
"SPACE" OFF
"> Refresh\n"
1103 "\t<" ON
"P" OFF
"> Toggle count userspace processes; <" ON
"k" OFF
"> Toggle count all processes\n"
1104 "\t<" ON
"r" OFF
"> Count processes recursively; <" ON
"q" OFF
"> Quit");
1111 fprintf(stdout
, "\nUnknown key '\\x%x'. Ignoring.", key
);
1113 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
1123 group_hashmap_free(a
);
1124 group_hashmap_free(b
);
1126 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;