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"
51 typedef struct Group
{
61 unsigned cpu_iteration
;
68 unsigned io_iteration
;
69 uint64_t io_input
, io_output
;
71 uint64_t io_input_bps
, io_output_bps
;
74 static unsigned arg_depth
= 3;
75 static unsigned arg_iterations
= (unsigned) -1;
76 static bool arg_batch
= false;
77 static bool arg_raw
= false;
78 static usec_t arg_delay
= 1*USEC_PER_SEC
;
79 static char* arg_machine
= NULL
;
80 static char* arg_root
= NULL
;
81 static bool arg_recursive
= true;
82 static bool arg_recursive_unset
= false;
86 COUNT_USERSPACE_PROCESSES
,
88 } arg_count
= COUNT_PIDS
;
96 } arg_order
= ORDER_CPU
;
101 } arg_cpu_type
= CPU_PERCENT
;
103 static void group_free(Group
*g
) {
110 static void group_hashmap_clear(Hashmap
*h
) {
111 hashmap_clear_with_destructor(h
, group_free
);
114 static void group_hashmap_free(Hashmap
*h
) {
115 group_hashmap_clear(h
);
119 static const char *maybe_format_bytes(char *buf
, size_t l
, bool is_valid
, uint64_t t
) {
123 snprintf(buf
, l
, "%" PRIu64
, t
);
126 return format_bytes(buf
, l
, t
);
129 static bool is_root_cgroup(const char *path
) {
131 /* Returns true if the specified path belongs to the root cgroup. The root cgroup is special on cgroupsv2 as it
132 * carries only very few attributes in order not to export multiple truth about system state as most
133 * information is available elsewhere in /proc anyway. We need to be able to deal with that, and need to get
134 * our data from different sources in that case.
136 * There's one extra complication in all of this, though 😣: if the path to the cgroup indicates we are in the
137 * root cgroup this might actually not be the case, because cgroup namespacing might be in effect
138 * (CLONE_NEWCGROUP). Since there's no nice way to distuingish a real cgroup root from a fake namespaced one we
139 * do an explicit container check here, under the assumption that CLONE_NEWCGROUP is generally used when
140 * container managers are used too.
142 * Note that checking for a container environment is kinda ugly, since in theory people could use cgtop from
143 * inside a container where cgroup namespacing is turned off to watch the host system. However, that's mostly a
144 * theoretic usecase, and if people actually try all they'll lose is accounting for the top-level cgroup. Which
147 if (detect_container() > 0)
150 return isempty(path
) || path_equal(path
, "/");
154 const char *controller
,
168 all_unified
= cg_all_unified();
172 g
= hashmap_get(a
, path
);
174 g
= hashmap_get(b
, path
);
180 g
->path
= strdup(path
);
186 r
= hashmap_put(a
, g
->path
, g
);
192 r
= hashmap_move_one(a
, b
, path
);
196 g
->cpu_valid
= g
->memory_valid
= g
->io_valid
= g
->n_tasks_valid
= false;
200 if (streq(controller
, SYSTEMD_CGROUP_CONTROLLER
) &&
201 IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
)) {
202 _cleanup_fclose_
FILE *f
= NULL
;
205 r
= cg_enumerate_processes(controller
, path
, &f
);
212 while (cg_read_pid(f
, &pid
) > 0) {
214 if (arg_count
== COUNT_USERSPACE_PROCESSES
&& is_kernel_thread(pid
) > 0)
221 g
->n_tasks_valid
= true;
223 } else if (streq(controller
, "pids") && arg_count
== COUNT_PIDS
) {
225 if (is_root_cgroup(path
)) {
226 r
= procfs_tasks_get_current(&g
->n_tasks
);
230 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
232 r
= cg_get_path(controller
, path
, "pids.current", &p
);
236 r
= read_one_line_file(p
, &v
);
242 r
= safe_atou64(v
, &g
->n_tasks
);
248 g
->n_tasks_valid
= true;
250 } else if (STR_IN_SET(controller
, "cpu", "cpuacct")) {
251 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
255 if (is_root_cgroup(path
)) {
256 r
= procfs_cpu_get_usage(&new_usage
);
259 } else if (all_unified
) {
260 _cleanup_free_
char *val
= NULL
;
262 if (!streq(controller
, "cpu"))
265 r
= cg_get_keyed_attribute("cpu", path
, "cpu.stat", STRV_MAKE("usage_usec"), &val
);
266 if (IN_SET(r
, -ENOENT
, -ENXIO
))
271 r
= safe_atou64(val
, &new_usage
);
275 new_usage
*= NSEC_PER_USEC
;
277 if (!streq(controller
, "cpuacct"))
280 r
= cg_get_path(controller
, path
, "cpuacct.usage", &p
);
284 r
= read_one_line_file(p
, &v
);
290 r
= safe_atou64(v
, &new_usage
);
295 timestamp
= now_nsec(CLOCK_MONOTONIC
);
297 if (g
->cpu_iteration
== iteration
- 1 &&
298 (nsec_t
) new_usage
> g
->cpu_usage
) {
302 x
= timestamp
- g
->cpu_timestamp
;
306 y
= (nsec_t
) new_usage
- g
->cpu_usage
;
307 g
->cpu_fraction
= (double) y
/ (double) x
;
311 g
->cpu_usage
= (nsec_t
) new_usage
;
312 g
->cpu_timestamp
= timestamp
;
313 g
->cpu_iteration
= iteration
;
315 } else if (streq(controller
, "memory")) {
317 if (is_root_cgroup(path
)) {
318 r
= procfs_memory_get_current(&g
->memory
);
322 _cleanup_free_
char *p
= NULL
, *v
= NULL
;
325 r
= cg_get_path(controller
, path
, "memory.current", &p
);
327 r
= cg_get_path(controller
, path
, "memory.usage_in_bytes", &p
);
331 r
= read_one_line_file(p
, &v
);
337 r
= safe_atou64(v
, &g
->memory
);
343 g
->memory_valid
= true;
345 } else if ((streq(controller
, "io") && all_unified
) ||
346 (streq(controller
, "blkio") && !all_unified
)) {
347 _cleanup_fclose_
FILE *f
= NULL
;
348 _cleanup_free_
char *p
= NULL
;
349 uint64_t wr
= 0, rd
= 0;
352 r
= cg_get_path(controller
, path
, all_unified
? "io.stat" : "blkio.io_service_bytes", &p
);
364 char line
[LINE_MAX
], *l
;
367 if (!fgets(line
, sizeof(line
), f
))
370 /* Trim and skip the device */
372 l
+= strcspn(l
, WHITESPACE
);
373 l
+= strspn(l
, WHITESPACE
);
376 while (!isempty(l
)) {
377 if (sscanf(l
, "rbytes=%" SCNu64
, &k
))
379 else if (sscanf(l
, "wbytes=%" SCNu64
, &k
))
382 l
+= strcspn(l
, WHITESPACE
);
383 l
+= strspn(l
, WHITESPACE
);
386 if (first_word(l
, "Read")) {
389 } else if (first_word(l
, "Write")) {
395 l
+= strspn(l
, WHITESPACE
);
396 r
= safe_atou64(l
, &k
);
404 timestamp
= now_nsec(CLOCK_MONOTONIC
);
406 if (g
->io_iteration
== iteration
- 1) {
409 x
= (uint64_t) (timestamp
- g
->io_timestamp
);
413 if (rd
> g
->io_input
)
414 yr
= rd
- g
->io_input
;
418 if (wr
> g
->io_output
)
419 yw
= wr
- g
->io_output
;
423 if (yr
> 0 || yw
> 0) {
424 g
->io_input_bps
= (yr
* 1000000000ULL) / x
;
425 g
->io_output_bps
= (yw
* 1000000000ULL) / x
;
432 g
->io_timestamp
= timestamp
;
433 g
->io_iteration
= iteration
;
442 static int refresh_one(
443 const char *controller
,
451 _cleanup_closedir_
DIR *d
= NULL
;
459 if (depth
> arg_depth
)
462 r
= process(controller
, path
, a
, b
, iteration
, &ours
);
466 r
= cg_enumerate_subgroups(controller
, path
, &d
);
473 _cleanup_free_
char *fn
= NULL
, *p
= NULL
;
476 r
= cg_read_subgroup(d
, &fn
);
482 p
= strjoin(path
, "/", fn
);
486 path_kill_slashes(p
);
488 r
= refresh_one(controller
, p
, a
, b
, iteration
, depth
+ 1, &child
);
493 IN_SET(arg_count
, COUNT_ALL_PROCESSES
, COUNT_USERSPACE_PROCESSES
) &&
495 child
->n_tasks_valid
&&
496 streq(controller
, SYSTEMD_CGROUP_CONTROLLER
)) {
498 /* Recursively sum up processes */
500 if (ours
->n_tasks_valid
)
501 ours
->n_tasks
+= child
->n_tasks
;
503 ours
->n_tasks
= child
->n_tasks
;
504 ours
->n_tasks_valid
= true;
515 static int refresh(const char *root
, Hashmap
*a
, Hashmap
*b
, unsigned iteration
) {
520 r
= refresh_one(SYSTEMD_CGROUP_CONTROLLER
, root
, a
, b
, iteration
, 0, NULL
);
523 r
= refresh_one("cpu", root
, a
, b
, iteration
, 0, NULL
);
526 r
= refresh_one("cpuacct", root
, a
, b
, iteration
, 0, NULL
);
529 r
= refresh_one("memory", root
, a
, b
, iteration
, 0, NULL
);
532 r
= refresh_one("io", root
, a
, b
, iteration
, 0, NULL
);
535 r
= refresh_one("blkio", root
, a
, b
, iteration
, 0, NULL
);
538 r
= refresh_one("pids", root
, a
, b
, iteration
, 0, NULL
);
545 static const char *empty_to_slash(const char *p
) {
546 return isempty(p
) ? "/" : p
;
549 static int group_compare(const void*a
, const void *b
) {
550 const Group
*x
= *(Group
**)a
, *y
= *(Group
**)b
;
552 if (arg_order
!= ORDER_TASKS
|| arg_recursive
) {
553 /* Let's make sure that the parent is always before
554 * the child. Except when ordering by tasks and
555 * recursive summing is off, since that is actually
556 * not accumulative for all children. */
558 if (path_startswith(empty_to_slash(y
->path
), empty_to_slash(x
->path
)))
560 if (path_startswith(empty_to_slash(x
->path
), empty_to_slash(y
->path
)))
570 if (arg_cpu_type
== CPU_PERCENT
) {
571 if (x
->cpu_valid
&& y
->cpu_valid
) {
572 if (x
->cpu_fraction
> y
->cpu_fraction
)
574 else if (x
->cpu_fraction
< y
->cpu_fraction
)
576 } else if (x
->cpu_valid
)
578 else if (y
->cpu_valid
)
581 if (x
->cpu_usage
> y
->cpu_usage
)
583 else if (x
->cpu_usage
< y
->cpu_usage
)
590 if (x
->n_tasks_valid
&& y
->n_tasks_valid
) {
591 if (x
->n_tasks
> y
->n_tasks
)
593 else if (x
->n_tasks
< y
->n_tasks
)
595 } else if (x
->n_tasks_valid
)
597 else if (y
->n_tasks_valid
)
603 if (x
->memory_valid
&& y
->memory_valid
) {
604 if (x
->memory
> y
->memory
)
606 else if (x
->memory
< y
->memory
)
608 } else if (x
->memory_valid
)
610 else if (y
->memory_valid
)
616 if (x
->io_valid
&& y
->io_valid
) {
617 if (x
->io_input_bps
+ x
->io_output_bps
> y
->io_input_bps
+ y
->io_output_bps
)
619 else if (x
->io_input_bps
+ x
->io_output_bps
< y
->io_input_bps
+ y
->io_output_bps
)
621 } else if (x
->io_valid
)
623 else if (y
->io_valid
)
627 return path_compare(x
->path
, y
->path
);
630 static void display(Hashmap
*a
) {
635 unsigned rows
, n
= 0, j
, maxtcpu
= 0, maxtpath
= 3; /* 3 for ellipsize() to work properly */
636 char buffer
[MAX3(21, FORMAT_BYTES_MAX
, FORMAT_TIMESPAN_MAX
)];
640 if (!terminal_is_dumb())
641 fputs(ANSI_HOME_CLEAR
, stdout
);
643 array
= alloca(sizeof(Group
*) * hashmap_size(a
));
645 HASHMAP_FOREACH(g
, a
, i
)
646 if (g
->n_tasks_valid
|| g
->cpu_valid
|| g
->memory_valid
|| g
->io_valid
)
649 qsort_safe(array
, n
, sizeof(Group
*), group_compare
);
651 /* Find the longest names in one run */
652 for (j
= 0; j
< n
; j
++) {
653 unsigned cputlen
, pathtlen
;
655 format_timespan(buffer
, sizeof(buffer
), (usec_t
) (array
[j
]->cpu_usage
/ NSEC_PER_USEC
), 0);
656 cputlen
= strlen(buffer
);
657 maxtcpu
= MAX(maxtcpu
, cputlen
);
659 pathtlen
= strlen(array
[j
]->path
);
660 maxtpath
= MAX(maxtpath
, pathtlen
);
663 if (arg_cpu_type
== CPU_PERCENT
)
664 xsprintf(buffer
, "%6s", "%CPU");
666 xsprintf(buffer
, "%*s", maxtcpu
, "CPU Time");
673 const char *on
, *off
;
675 path_columns
= columns() - 36 - strlen(buffer
);
676 if (path_columns
< 10)
679 on
= ansi_highlight_underline();
680 off
= ansi_underline();
682 printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n",
684 arg_order
== ORDER_PATH
? on
: "", path_columns
, "Control Group",
685 arg_order
== ORDER_PATH
? off
: "",
686 arg_order
== ORDER_TASKS
? on
: "", arg_count
== COUNT_PIDS
? "Tasks" : arg_count
== COUNT_USERSPACE_PROCESSES
? "Procs" : "Proc+",
687 arg_order
== ORDER_TASKS
? off
: "",
688 arg_order
== ORDER_CPU
? on
: "", buffer
,
689 arg_order
== ORDER_CPU
? off
: "",
690 arg_order
== ORDER_MEMORY
? on
: "", "Memory",
691 arg_order
== ORDER_MEMORY
? off
: "",
692 arg_order
== ORDER_IO
? on
: "", "Input/s",
693 arg_order
== ORDER_IO
? off
: "",
694 arg_order
== ORDER_IO
? on
: "", "Output/s",
695 arg_order
== ORDER_IO
? off
: "",
698 path_columns
= maxtpath
;
700 for (j
= 0; j
< n
; j
++) {
701 _cleanup_free_
char *ellipsized
= NULL
;
704 if (on_tty() && j
+ 6 > rows
)
709 path
= empty_to_slash(g
->path
);
710 ellipsized
= ellipsize(path
, path_columns
, 33);
711 printf("%-*s", path_columns
, ellipsized
?: path
);
713 if (g
->n_tasks_valid
)
714 printf(" %7" PRIu64
, g
->n_tasks
);
718 if (arg_cpu_type
== CPU_PERCENT
) {
720 printf(" %6.1f", g
->cpu_fraction
*100);
724 printf(" %*s", maxtcpu
, format_timespan(buffer
, sizeof(buffer
), (usec_t
) (g
->cpu_usage
/ NSEC_PER_USEC
), 0));
726 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->memory_valid
, g
->memory
));
727 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_input_bps
));
728 printf(" %8s", maybe_format_bytes(buffer
, sizeof(buffer
), g
->io_valid
, g
->io_output_bps
));
734 static void help(void) {
735 printf("%s [OPTIONS...] [CGROUP]\n\n"
736 "Show top control groups by their resource usage.\n\n"
737 " -h --help Show this help\n"
738 " --version Show package version\n"
739 " -p --order=path Order by path\n"
740 " -t --order=tasks Order by number of tasks/processes\n"
741 " -c --order=cpu Order by CPU load (default)\n"
742 " -m --order=memory Order by memory load\n"
743 " -i --order=io Order by IO load\n"
744 " -r --raw Provide raw (not human-readable) numbers\n"
745 " --cpu=percentage Show CPU usage as percentage (default)\n"
746 " --cpu=time Show CPU usage as time\n"
747 " -P Count userspace processes instead of tasks (excl. kernel)\n"
748 " -k Count all processes instead of tasks (incl. kernel)\n"
749 " --recursive=BOOL Sum up process count recursively\n"
750 " -d --delay=DELAY Delay between updates\n"
751 " -n --iterations=N Run for N iterations before exiting\n"
752 " -1 Shortcut for --iterations=1\n"
753 " -b --batch Run in batch mode, accepting no input\n"
754 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
755 " -M --machine= Show container\n"
756 , program_invocation_short_name
, arg_depth
);
759 static int parse_argv(int argc
, char *argv
[]) {
769 static const struct option options
[] = {
770 { "help", no_argument
, NULL
, 'h' },
771 { "version", no_argument
, NULL
, ARG_VERSION
},
772 { "delay", required_argument
, NULL
, 'd' },
773 { "iterations", required_argument
, NULL
, 'n' },
774 { "batch", no_argument
, NULL
, 'b' },
775 { "raw", no_argument
, NULL
, 'r' },
776 { "depth", required_argument
, NULL
, ARG_DEPTH
},
777 { "cpu", optional_argument
, NULL
, ARG_CPU_TYPE
},
778 { "order", required_argument
, NULL
, ARG_ORDER
},
779 { "recursive", required_argument
, NULL
, ARG_RECURSIVE
},
780 { "machine", required_argument
, NULL
, 'M' },
789 while ((c
= getopt_long(argc
, argv
, "hptcmin:brd:kPM:1", options
, NULL
)) >= 0)
802 if (streq(optarg
, "time"))
803 arg_cpu_type
= CPU_TIME
;
804 else if (streq(optarg
, "percentage"))
805 arg_cpu_type
= CPU_PERCENT
;
807 log_error("Unknown argument to --cpu=: %s", optarg
);
811 arg_cpu_type
= CPU_TIME
;
816 r
= safe_atou(optarg
, &arg_depth
);
818 return log_error_errno(r
, "Failed to parse depth parameter: %s", optarg
);
823 r
= parse_sec(optarg
, &arg_delay
);
824 if (r
< 0 || arg_delay
<= 0) {
825 log_error("Failed to parse delay parameter: %s", optarg
);
832 r
= safe_atou(optarg
, &arg_iterations
);
834 return log_error_errno(r
, "Failed to parse iterations parameter: %s", optarg
);
851 arg_order
= ORDER_PATH
;
855 arg_order
= ORDER_TASKS
;
859 arg_order
= ORDER_CPU
;
863 arg_order
= ORDER_MEMORY
;
867 arg_order
= ORDER_IO
;
871 if (streq(optarg
, "path"))
872 arg_order
= ORDER_PATH
;
873 else if (streq(optarg
, "tasks"))
874 arg_order
= ORDER_TASKS
;
875 else if (streq(optarg
, "cpu"))
876 arg_order
= ORDER_CPU
;
877 else if (streq(optarg
, "memory"))
878 arg_order
= ORDER_MEMORY
;
879 else if (streq(optarg
, "io"))
880 arg_order
= ORDER_IO
;
882 log_error("Invalid argument to --order=: %s", optarg
);
888 arg_count
= COUNT_ALL_PROCESSES
;
892 arg_count
= COUNT_USERSPACE_PROCESSES
;
896 r
= parse_boolean(optarg
);
898 return log_error_errno(r
, "Failed to parse --recursive= argument: %s", optarg
);
901 arg_recursive_unset
= r
== 0;
905 arg_machine
= optarg
;
912 assert_not_reached("Unhandled option");
915 if (optind
== argc
- 1)
916 arg_root
= argv
[optind
];
917 else if (optind
< argc
) {
918 log_error("Too many arguments.");
925 static const char* counting_what(void) {
926 if (arg_count
== COUNT_PIDS
)
928 else if (arg_count
== COUNT_ALL_PROCESSES
)
929 return "all processes (incl. kernel)";
931 return "userspace processes (excl. kernel)";
934 int main(int argc
, char *argv
[]) {
936 Hashmap
*a
= NULL
, *b
= NULL
;
937 unsigned iteration
= 0;
938 usec_t last_refresh
= 0;
939 bool quit
= false, immediate_refresh
= false;
940 _cleanup_free_
char *root
= NULL
;
943 log_parse_environment();
946 r
= parse_argv(argc
, argv
);
950 r
= cg_mask_supported(&mask
);
952 log_error_errno(r
, "Failed to determine supported controllers: %m");
956 arg_count
= (mask
& CGROUP_MASK_PIDS
) ? COUNT_PIDS
: COUNT_USERSPACE_PROCESSES
;
958 if (arg_recursive_unset
&& arg_count
== COUNT_PIDS
) {
959 log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
963 r
= show_cgroup_get_path_and_warn(arg_machine
, arg_root
, &root
);
965 log_error_errno(r
, "Failed to get root control group path: %m");
968 log_debug("Cgroup path: %s", root
);
970 a
= hashmap_new(&path_hash_ops
);
971 b
= hashmap_new(&path_hash_ops
);
977 signal(SIGWINCH
, columns_lines_cache_reset
);
979 if (arg_iterations
== (unsigned) -1)
980 arg_iterations
= on_tty() ? 0 : 1;
986 char h
[FORMAT_TIMESPAN_MAX
];
988 t
= now(CLOCK_MONOTONIC
);
990 if (t
>= last_refresh
+ arg_delay
|| immediate_refresh
) {
992 r
= refresh(root
, a
, b
, iteration
++);
994 log_error_errno(r
, "Failed to refresh: %m");
998 group_hashmap_clear(b
);
1005 immediate_refresh
= false;
1010 if (arg_iterations
&& iteration
>= arg_iterations
)
1013 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
1014 fputs("\n", stdout
);
1018 (void) usleep(last_refresh
+ arg_delay
- t
);
1020 r
= read_one_char(stdin
, &key
, last_refresh
+ arg_delay
- t
, NULL
);
1021 if (r
== -ETIMEDOUT
)
1024 log_error_errno(r
, "Couldn't read key: %m");
1029 if (on_tty()) { /* TTY: Clear any user keystroke */
1030 fputs("\r \r", stdout
);
1040 immediate_refresh
= true;
1048 arg_order
= ORDER_PATH
;
1052 arg_order
= ORDER_TASKS
;
1056 arg_order
= ORDER_CPU
;
1060 arg_order
= ORDER_MEMORY
;
1064 arg_order
= ORDER_IO
;
1068 arg_cpu_type
= arg_cpu_type
== CPU_TIME
? CPU_PERCENT
: CPU_TIME
;
1072 arg_count
= arg_count
!= COUNT_ALL_PROCESSES
? COUNT_ALL_PROCESSES
: COUNT_PIDS
;
1073 fprintf(stdout
, "\nCounting: %s.", counting_what());
1079 arg_count
= arg_count
!= COUNT_USERSPACE_PROCESSES
? COUNT_USERSPACE_PROCESSES
: COUNT_PIDS
;
1080 fprintf(stdout
, "\nCounting: %s.", counting_what());
1086 if (arg_count
== COUNT_PIDS
)
1087 fprintf(stdout
, "\n\aCannot toggle recursive counting, not available in task counting mode.");
1089 arg_recursive
= !arg_recursive
;
1090 fprintf(stdout
, "\nRecursive process counting: %s", yes_no(arg_recursive
));
1097 if (arg_delay
< USEC_PER_SEC
)
1098 arg_delay
+= USEC_PER_MSEC
*250;
1100 arg_delay
+= USEC_PER_SEC
;
1102 fprintf(stdout
, "\nIncreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
1108 if (arg_delay
<= USEC_PER_MSEC
*500)
1109 arg_delay
= USEC_PER_MSEC
*250;
1110 else if (arg_delay
< USEC_PER_MSEC
*1250)
1111 arg_delay
-= USEC_PER_MSEC
*250;
1113 arg_delay
-= USEC_PER_SEC
;
1115 fprintf(stdout
, "\nDecreased delay to %s.", format_timespan(h
, sizeof(h
), arg_delay
, 0));
1123 #define ON ANSI_HIGHLIGHT
1124 #define OFF ANSI_NORMAL
1127 "\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"
1128 "\t<" ON
"+" OFF
"> Inc. delay; <" ON
"-" OFF
"> Dec. delay; <" ON
"%%" OFF
"> Toggle time; <" ON
"SPACE" OFF
"> Refresh\n"
1129 "\t<" ON
"P" OFF
"> Toggle count userspace processes; <" ON
"k" OFF
"> Toggle count all processes\n"
1130 "\t<" ON
"r" OFF
"> Count processes recursively; <" ON
"q" OFF
"> Quit");
1137 fprintf(stdout
, "\nUnknown key '\\x%x'. Ignoring.", key
);
1139 fprintf(stdout
, "\nUnknown key '%c'. Ignoring.", key
);
1149 group_hashmap_free(a
);
1150 group_hashmap_free(b
);
1152 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;