]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cgtop/cgtop.c
cgtop: properly show "/" instead of empty string in cgroup list
[thirdparty/systemd.git] / src / cgtop / cgtop.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7
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.
12
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.
17
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/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <stdint.h>
26 #include <unistd.h>
27 #include <alloca.h>
28 #include <getopt.h>
29 #include <signal.h>
30
31 #include "path-util.h"
32 #include "terminal-util.h"
33 #include "process-util.h"
34 #include "util.h"
35 #include "hashmap.h"
36 #include "cgroup-util.h"
37 #include "build.h"
38 #include "fileio.h"
39
40 typedef struct Group {
41 char *path;
42
43 bool n_tasks_valid:1;
44 bool cpu_valid:1;
45 bool memory_valid:1;
46 bool io_valid:1;
47
48 unsigned n_tasks;
49
50 unsigned cpu_iteration;
51 nsec_t cpu_usage;
52 nsec_t cpu_timestamp;
53 double cpu_fraction;
54
55 uint64_t memory;
56
57 unsigned io_iteration;
58 uint64_t io_input, io_output;
59 nsec_t io_timestamp;
60 uint64_t io_input_bps, io_output_bps;
61 } Group;
62
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;
68 static bool arg_kernel_threads = false;
69 static bool arg_recursive = true;
70
71 static enum {
72 ORDER_PATH,
73 ORDER_TASKS,
74 ORDER_CPU,
75 ORDER_MEMORY,
76 ORDER_IO
77 } arg_order = ORDER_CPU;
78
79 static enum {
80 CPU_PERCENT,
81 CPU_TIME,
82 } arg_cpu_type = CPU_PERCENT;
83
84 static void group_free(Group *g) {
85 assert(g);
86
87 free(g->path);
88 free(g);
89 }
90
91 static void group_hashmap_clear(Hashmap *h) {
92 Group *g;
93
94 while ((g = hashmap_steal_first(h)))
95 group_free(g);
96 }
97
98 static void group_hashmap_free(Hashmap *h) {
99 group_hashmap_clear(h);
100 hashmap_free(h);
101 }
102
103 static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, off_t t) {
104 if (!is_valid)
105 return "-";
106 if (arg_raw) {
107 snprintf(buf, l, "%jd", t);
108 return buf;
109 }
110 return format_bytes(buf, l, t);
111 }
112
113 static int process(
114 const char *controller,
115 const char *path,
116 Hashmap *a,
117 Hashmap *b,
118 unsigned iteration,
119 Group **ret) {
120
121 Group *g;
122 int r;
123
124 assert(controller);
125 assert(path);
126 assert(a);
127
128 g = hashmap_get(a, path);
129 if (!g) {
130 g = hashmap_get(b, path);
131 if (!g) {
132 g = new0(Group, 1);
133 if (!g)
134 return -ENOMEM;
135
136 g->path = strdup(path);
137 if (!g->path) {
138 group_free(g);
139 return -ENOMEM;
140 }
141
142 r = hashmap_put(a, g->path, g);
143 if (r < 0) {
144 group_free(g);
145 return r;
146 }
147 } else {
148 r = hashmap_move_one(a, b, path);
149 if (r < 0)
150 return r;
151
152 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
153 }
154 }
155
156 if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
157 _cleanup_fclose_ FILE *f = NULL;
158 pid_t pid;
159
160 r = cg_enumerate_processes(controller, path, &f);
161 if (r == -ENOENT)
162 return 0;
163 if (r < 0)
164 return r;
165
166 g->n_tasks = 0;
167 while (cg_read_pid(f, &pid) > 0) {
168
169 if (!arg_kernel_threads && is_kernel_thread(pid) > 0)
170 continue;
171
172 g->n_tasks++;
173 }
174
175 if (g->n_tasks > 0)
176 g->n_tasks_valid = true;
177
178 } else if (streq(controller, "cpuacct")) {
179 _cleanup_free_ char *p = NULL, *v = NULL;
180 uint64_t new_usage;
181 nsec_t timestamp;
182
183 r = cg_get_path(controller, path, "cpuacct.usage", &p);
184 if (r < 0)
185 return r;
186
187 r = read_one_line_file(p, &v);
188 if (r == -ENOENT)
189 return 0;
190 if (r < 0)
191 return r;
192
193 r = safe_atou64(v, &new_usage);
194 if (r < 0)
195 return r;
196
197 timestamp = now_nsec(CLOCK_MONOTONIC);
198
199 if (g->cpu_iteration == iteration - 1 &&
200 (nsec_t) new_usage > g->cpu_usage) {
201
202 nsec_t x, y;
203
204 x = timestamp - g->cpu_timestamp;
205 if (x < 1)
206 x = 1;
207
208 y = (nsec_t) new_usage - g->cpu_usage;
209 g->cpu_fraction = (double) y / (double) x;
210 g->cpu_valid = true;
211 }
212
213 g->cpu_usage = (nsec_t) new_usage;
214 g->cpu_timestamp = timestamp;
215 g->cpu_iteration = iteration;
216
217 } else if (streq(controller, "memory")) {
218 _cleanup_free_ char *p = NULL, *v = NULL;
219
220 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
221 if (r < 0)
222 return r;
223
224 r = read_one_line_file(p, &v);
225 if (r == -ENOENT)
226 return 0;
227 if (r < 0)
228 return r;
229
230 r = safe_atou64(v, &g->memory);
231 if (r < 0)
232 return r;
233
234 if (g->memory > 0)
235 g->memory_valid = true;
236
237 } else if (streq(controller, "blkio")) {
238 _cleanup_fclose_ FILE *f = NULL;
239 _cleanup_free_ char *p = NULL;
240 uint64_t wr = 0, rd = 0;
241 nsec_t timestamp;
242
243 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
244 if (r < 0)
245 return r;
246
247 f = fopen(p, "re");
248 if (!f) {
249 if (errno == ENOENT)
250 return 0;
251 return -errno;
252 }
253
254 for (;;) {
255 char line[LINE_MAX], *l;
256 uint64_t k, *q;
257
258 if (!fgets(line, sizeof(line), f))
259 break;
260
261 l = strstrip(line);
262 l += strcspn(l, WHITESPACE);
263 l += strspn(l, WHITESPACE);
264
265 if (first_word(l, "Read")) {
266 l += 4;
267 q = &rd;
268 } else if (first_word(l, "Write")) {
269 l += 5;
270 q = &wr;
271 } else
272 continue;
273
274 l += strspn(l, WHITESPACE);
275 r = safe_atou64(l, &k);
276 if (r < 0)
277 continue;
278
279 *q += k;
280 }
281
282 timestamp = now_nsec(CLOCK_MONOTONIC);
283
284 if (g->io_iteration == iteration - 1) {
285 uint64_t x, yr, yw;
286
287 x = (uint64_t) (timestamp - g->io_timestamp);
288 if (x < 1)
289 x = 1;
290
291 if (rd > g->io_input)
292 yr = rd - g->io_input;
293 else
294 yr = 0;
295
296 if (wr > g->io_output)
297 yw = wr - g->io_output;
298 else
299 yw = 0;
300
301 if (yr > 0 || yw > 0) {
302 g->io_input_bps = (yr * 1000000000ULL) / x;
303 g->io_output_bps = (yw * 1000000000ULL) / x;
304 g->io_valid = true;
305 }
306 }
307
308 g->io_input = rd;
309 g->io_output = wr;
310 g->io_timestamp = timestamp;
311 g->io_iteration = iteration;
312 }
313
314 if (ret)
315 *ret = g;
316
317 return 0;
318 }
319
320 static int refresh_one(
321 const char *controller,
322 const char *path,
323 Hashmap *a,
324 Hashmap *b,
325 unsigned iteration,
326 unsigned depth,
327 Group **ret) {
328
329 _cleanup_closedir_ DIR *d = NULL;
330 Group *ours;
331 int r;
332
333 assert(controller);
334 assert(path);
335 assert(a);
336
337 if (depth > arg_depth)
338 return 0;
339
340 r = process(controller, path, a, b, iteration, &ours);
341 if (r < 0)
342 return r;
343
344 r = cg_enumerate_subgroups(controller, path, &d);
345 if (r == -ENOENT)
346 return 0;
347 if (r < 0)
348 return r;
349
350 for (;;) {
351 _cleanup_free_ char *fn = NULL, *p = NULL;
352 Group *child = NULL;
353
354 r = cg_read_subgroup(d, &fn);
355 if (r < 0)
356 return r;
357 if (r == 0)
358 break;
359
360 p = strjoin(path, "/", fn, NULL);
361 if (!p)
362 return -ENOMEM;
363
364 path_kill_slashes(p);
365
366 r = refresh_one(controller, p, a, b, iteration, depth + 1, &child);
367 if (r < 0)
368 return r;
369
370 if (arg_recursive &&
371 child &&
372 child->n_tasks_valid &&
373 streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
374
375 /* Recursively sum up processes */
376
377 if (ours->n_tasks_valid)
378 ours->n_tasks += child->n_tasks;
379 else {
380 ours->n_tasks = child->n_tasks;
381 ours->n_tasks_valid = true;
382 }
383 }
384 }
385
386 if (ret)
387 *ret = ours;
388
389 return 1;
390 }
391
392 static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) {
393 int r;
394
395 assert(a);
396
397 r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL);
398 if (r < 0)
399 return r;
400 r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL);
401 if (r < 0)
402 return r;
403 r = refresh_one("memory", root, a, b, iteration, 0, NULL);
404 if (r < 0)
405 return r;
406 r = refresh_one("blkio", root, a, b, iteration, 0, NULL);
407 if (r < 0)
408 return r;
409
410 return 0;
411 }
412
413 static int group_compare(const void*a, const void *b) {
414 const Group *x = *(Group**)a, *y = *(Group**)b;
415
416 if (arg_order != ORDER_TASKS || arg_recursive) {
417 /* Let's make sure that the parent is always before
418 * the child. Except when ordering by tasks and
419 * recursive summing is off, since that is actually
420 * not accumulative for all children. */
421
422 if (path_startswith(y->path, x->path))
423 return -1;
424 if (path_startswith(x->path, y->path))
425 return 1;
426 }
427
428 switch (arg_order) {
429
430 case ORDER_PATH:
431 break;
432
433 case ORDER_CPU:
434 if (arg_cpu_type == CPU_PERCENT) {
435 if (x->cpu_valid && y->cpu_valid) {
436 if (x->cpu_fraction > y->cpu_fraction)
437 return -1;
438 else if (x->cpu_fraction < y->cpu_fraction)
439 return 1;
440 } else if (x->cpu_valid)
441 return -1;
442 else if (y->cpu_valid)
443 return 1;
444 } else {
445 if (x->cpu_usage > y->cpu_usage)
446 return -1;
447 else if (x->cpu_usage < y->cpu_usage)
448 return 1;
449 }
450
451 break;
452
453 case ORDER_TASKS:
454 if (x->n_tasks_valid && y->n_tasks_valid) {
455 if (x->n_tasks > y->n_tasks)
456 return -1;
457 else if (x->n_tasks < y->n_tasks)
458 return 1;
459 } else if (x->n_tasks_valid)
460 return -1;
461 else if (y->n_tasks_valid)
462 return 1;
463
464 break;
465
466 case ORDER_MEMORY:
467 if (x->memory_valid && y->memory_valid) {
468 if (x->memory > y->memory)
469 return -1;
470 else if (x->memory < y->memory)
471 return 1;
472 } else if (x->memory_valid)
473 return -1;
474 else if (y->memory_valid)
475 return 1;
476
477 break;
478
479 case ORDER_IO:
480 if (x->io_valid && y->io_valid) {
481 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
482 return -1;
483 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
484 return 1;
485 } else if (x->io_valid)
486 return -1;
487 else if (y->io_valid)
488 return 1;
489 }
490
491 return path_compare(x->path, y->path);
492 }
493
494 #define ON ANSI_HIGHLIGHT_ON
495 #define OFF ANSI_HIGHLIGHT_OFF
496
497 static void display(Hashmap *a) {
498 Iterator i;
499 Group *g;
500 Group **array;
501 signed path_columns;
502 unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */
503 char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)];
504
505 assert(a);
506
507 /* Set cursor to top left corner and clear screen */
508 if (on_tty())
509 fputs("\033[H"
510 "\033[2J", stdout);
511
512 array = alloca(sizeof(Group*) * hashmap_size(a));
513
514 HASHMAP_FOREACH(g, a, i)
515 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
516 array[n++] = g;
517
518 qsort_safe(array, n, sizeof(Group*), group_compare);
519
520 /* Find the longest names in one run */
521 for (j = 0; j < n; j++) {
522 unsigned cputlen, pathtlen;
523
524 format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0);
525 cputlen = strlen(buffer);
526 maxtcpu = MAX(maxtcpu, cputlen);
527
528 pathtlen = strlen(array[j]->path);
529 maxtpath = MAX(maxtpath, pathtlen);
530 }
531
532 if (arg_cpu_type == CPU_PERCENT)
533 snprintf(buffer, sizeof(buffer), "%6s", "%CPU");
534 else
535 snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time");
536
537 rows = lines();
538 if (rows <= 10)
539 rows = 10;
540
541 if (on_tty()) {
542 path_columns = columns() - 36 - strlen(buffer);
543 if (path_columns < 10)
544 path_columns = 10;
545
546 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
547 arg_order == ORDER_PATH ? ON : "", path_columns, "Control Group",
548 arg_order == ORDER_PATH ? OFF : "",
549 arg_order == ORDER_TASKS ? ON : "", "Tasks",
550 arg_order == ORDER_TASKS ? OFF : "",
551 arg_order == ORDER_CPU ? ON : "", buffer,
552 arg_order == ORDER_CPU ? OFF : "",
553 arg_order == ORDER_MEMORY ? ON : "", "Memory",
554 arg_order == ORDER_MEMORY ? OFF : "",
555 arg_order == ORDER_IO ? ON : "", "Input/s",
556 arg_order == ORDER_IO ? OFF : "",
557 arg_order == ORDER_IO ? ON : "", "Output/s",
558 arg_order == ORDER_IO ? OFF : "");
559 } else
560 path_columns = maxtpath;
561
562 for (j = 0; j < n; j++) {
563 _cleanup_free_ char *ellipsized = NULL;
564 const char *path;
565
566 if (on_tty() && j + 5 > rows)
567 break;
568
569 g = array[j];
570
571 path = isempty(g->path) ? "/" : g->path;
572 ellipsized = ellipsize(path, path_columns, 33);
573 printf("%-*s", path_columns, ellipsized ?: path);
574
575 if (g->n_tasks_valid)
576 printf(" %7u", g->n_tasks);
577 else
578 fputs(" -", stdout);
579
580 if (arg_cpu_type == CPU_PERCENT) {
581 if (g->cpu_valid)
582 printf(" %6.1f", g->cpu_fraction*100);
583 else
584 fputs(" -", stdout);
585 } else
586 printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
587
588 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory));
589 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps));
590 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps));
591
592 putchar('\n');
593 }
594 }
595
596 static void help(void) {
597 printf("%s [OPTIONS...]\n\n"
598 "Show top control groups by their resource usage.\n\n"
599 " -h --help Show this help\n"
600 " --version Show package version\n"
601 " -p --order=path Order by path\n"
602 " -t --order=tasks Order by number of tasks\n"
603 " -c --order=cpu Order by CPU load (default)\n"
604 " -m --order=memory Order by memory load\n"
605 " -i --order=io Order by IO load\n"
606 " -r --raw Provide raw (not human-readable) numbers\n"
607 " --cpu=percentage Show CPU usage as percentage (default)\n"
608 " --cpu=time Show CPU usage as time\n"
609 " -k Include kernel threads in task count\n"
610 " --recursive=BOOL Sum up task count recursively\n"
611 " -d --delay=DELAY Delay between updates\n"
612 " -n --iterations=N Run for N iterations before exiting\n"
613 " -b --batch Run in batch mode, accepting no input\n"
614 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
615 , program_invocation_short_name, arg_depth);
616 }
617
618 static int parse_argv(int argc, char *argv[]) {
619
620 enum {
621 ARG_VERSION = 0x100,
622 ARG_DEPTH,
623 ARG_CPU_TYPE,
624 ARG_ORDER,
625 ARG_RECURSIVE,
626 };
627
628 static const struct option options[] = {
629 { "help", no_argument, NULL, 'h' },
630 { "version", no_argument, NULL, ARG_VERSION },
631 { "delay", required_argument, NULL, 'd' },
632 { "iterations", required_argument, NULL, 'n' },
633 { "batch", no_argument, NULL, 'b' },
634 { "raw", no_argument, NULL, 'r' },
635 { "depth", required_argument, NULL, ARG_DEPTH },
636 { "cpu", optional_argument, NULL, ARG_CPU_TYPE },
637 { "order", required_argument, NULL, ARG_ORDER },
638 { "recursive", required_argument, NULL, ARG_RECURSIVE },
639 {}
640 };
641
642 int c, r;
643
644 assert(argc >= 1);
645 assert(argv);
646
647 while ((c = getopt_long(argc, argv, "hptcmin:brd:k", options, NULL)) >= 0)
648
649 switch (c) {
650
651 case 'h':
652 help();
653 return 0;
654
655 case ARG_VERSION:
656 puts(PACKAGE_STRING);
657 puts(SYSTEMD_FEATURES);
658 return 0;
659
660 case ARG_CPU_TYPE:
661 if (optarg) {
662 if (streq(optarg, "time"))
663 arg_cpu_type = CPU_TIME;
664 else if (streq(optarg, "percentage"))
665 arg_cpu_type = CPU_PERCENT;
666 else {
667 log_error("Unknown argument to --cpu=: %s", optarg);
668 return -EINVAL;
669 }
670 } else
671 arg_cpu_type = CPU_TIME;
672
673 break;
674
675 case ARG_DEPTH:
676 r = safe_atou(optarg, &arg_depth);
677 if (r < 0) {
678 log_error("Failed to parse depth parameter.");
679 return -EINVAL;
680 }
681
682 break;
683
684 case 'd':
685 r = parse_sec(optarg, &arg_delay);
686 if (r < 0 || arg_delay <= 0) {
687 log_error("Failed to parse delay parameter.");
688 return -EINVAL;
689 }
690
691 break;
692
693 case 'n':
694 r = safe_atou(optarg, &arg_iterations);
695 if (r < 0) {
696 log_error("Failed to parse iterations parameter.");
697 return -EINVAL;
698 }
699
700 break;
701
702 case 'b':
703 arg_batch = true;
704 break;
705
706 case 'r':
707 arg_raw = true;
708 break;
709
710 case 'p':
711 arg_order = ORDER_PATH;
712 break;
713
714 case 't':
715 arg_order = ORDER_TASKS;
716 break;
717
718 case 'c':
719 arg_order = ORDER_CPU;
720 break;
721
722 case 'm':
723 arg_order = ORDER_MEMORY;
724 break;
725
726 case 'i':
727 arg_order = ORDER_IO;
728 break;
729
730 case ARG_ORDER:
731 if (streq(optarg, "path"))
732 arg_order = ORDER_PATH;
733 else if (streq(optarg, "tasks"))
734 arg_order = ORDER_TASKS;
735 else if (streq(optarg, "cpu"))
736 arg_order = ORDER_CPU;
737 else if (streq(optarg, "memory"))
738 arg_order = ORDER_MEMORY;
739 else if (streq(optarg, "io"))
740 arg_order = ORDER_IO;
741 else {
742 log_error("Invalid argument to --order=: %s", optarg);
743 return -EINVAL;
744 }
745 break;
746
747 case 'k':
748 arg_kernel_threads = true;
749 break;
750
751 case ARG_RECURSIVE:
752 r = parse_boolean(optarg);
753 if (r < 0) {
754 log_error("Failed to parse --recursive= argument: %s", optarg);
755 return r;
756 }
757
758 arg_recursive = r;
759 break;
760
761 case '?':
762 return -EINVAL;
763
764 default:
765 assert_not_reached("Unhandled option");
766 }
767
768 if (optind < argc) {
769 log_error("Too many arguments.");
770 return -EINVAL;
771 }
772
773 return 1;
774 }
775
776 int main(int argc, char *argv[]) {
777 int r;
778 Hashmap *a = NULL, *b = NULL;
779 unsigned iteration = 0;
780 usec_t last_refresh = 0;
781 bool quit = false, immediate_refresh = false;
782 _cleanup_free_ char *root = NULL;
783
784 log_parse_environment();
785 log_open();
786
787 r = parse_argv(argc, argv);
788 if (r <= 0)
789 goto finish;
790
791 r = cg_get_root_path(&root);
792 if (r < 0) {
793 log_error_errno(r, "Failed to get root control group path: %m");
794 goto finish;
795 }
796
797 a = hashmap_new(&string_hash_ops);
798 b = hashmap_new(&string_hash_ops);
799 if (!a || !b) {
800 r = log_oom();
801 goto finish;
802 }
803
804 signal(SIGWINCH, columns_lines_cache_reset);
805
806 if (arg_iterations == (unsigned) -1)
807 arg_iterations = on_tty() ? 0 : 1;
808
809 while (!quit) {
810 Hashmap *c;
811 usec_t t;
812 char key;
813 char h[FORMAT_TIMESPAN_MAX];
814
815 t = now(CLOCK_MONOTONIC);
816
817 if (t >= last_refresh + arg_delay || immediate_refresh) {
818
819 r = refresh(root, a, b, iteration++);
820 if (r < 0) {
821 log_error_errno(r, "Failed to refresh: %m");
822 goto finish;
823 }
824
825 group_hashmap_clear(b);
826
827 c = a;
828 a = b;
829 b = c;
830
831 last_refresh = t;
832 immediate_refresh = false;
833 }
834
835 display(b);
836
837 if (arg_iterations && iteration >= arg_iterations)
838 break;
839
840 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
841 fputs("\n", stdout);
842 fflush(stdout);
843
844 if (arg_batch)
845 (void) usleep(last_refresh + arg_delay - t);
846 else {
847 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
848 if (r == -ETIMEDOUT)
849 continue;
850 if (r < 0) {
851 log_error_errno(r, "Couldn't read key: %m");
852 goto finish;
853 }
854 }
855
856 if (on_tty()) { /* TTY: Clear any user keystroke */
857 fputs("\r \r", stdout);
858 fflush(stdout);
859 }
860
861 if (arg_batch)
862 continue;
863
864 switch (key) {
865
866 case ' ':
867 immediate_refresh = true;
868 break;
869
870 case 'q':
871 quit = true;
872 break;
873
874 case 'p':
875 arg_order = ORDER_PATH;
876 break;
877
878 case 't':
879 arg_order = ORDER_TASKS;
880 break;
881
882 case 'c':
883 arg_order = ORDER_CPU;
884 break;
885
886 case 'm':
887 arg_order = ORDER_MEMORY;
888 break;
889
890 case 'i':
891 arg_order = ORDER_IO;
892 break;
893
894 case '%':
895 arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
896 break;
897
898 case 'k':
899 arg_kernel_threads = !arg_kernel_threads;
900 fprintf(stdout, "\nCounting kernel threads: %s.", yes_no(arg_kernel_threads));
901 fflush(stdout);
902 sleep(1);
903 break;
904
905 case 'r':
906 arg_recursive = !arg_recursive;
907 fprintf(stdout, "\nRecursive task counting: %s", yes_no(arg_recursive));
908 fflush(stdout);
909 sleep(1);
910 break;
911
912 case '+':
913 if (arg_delay < USEC_PER_SEC)
914 arg_delay += USEC_PER_MSEC*250;
915 else
916 arg_delay += USEC_PER_SEC;
917
918 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
919 fflush(stdout);
920 sleep(1);
921 break;
922
923 case '-':
924 if (arg_delay <= USEC_PER_MSEC*500)
925 arg_delay = USEC_PER_MSEC*250;
926 else if (arg_delay < USEC_PER_MSEC*1250)
927 arg_delay -= USEC_PER_MSEC*250;
928 else
929 arg_delay -= USEC_PER_SEC;
930
931 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
932 fflush(stdout);
933 sleep(1);
934 break;
935
936 case '?':
937 case 'h':
938 fprintf(stdout,
939 "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n"
940 "\t<" ON "+" OFF "> Inc. delay; <" ON "-" OFF "> Dec. delay; <" ON "%%" OFF "> Toggle time; <" ON "SPACE" OFF "> Refresh\n"
941 "\t<" ON "k" OFF "> Count kernel threads; <" ON "r" OFF "> Count recursively; <" ON "q" OFF "> Quit");
942 fflush(stdout);
943 sleep(3);
944 break;
945
946 default:
947 if (key < ' ')
948 fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key);
949 else
950 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
951 fflush(stdout);
952 sleep(1);
953 break;
954 }
955 }
956
957 r = 0;
958
959 finish:
960 group_hashmap_free(a);
961 group_hashmap_free(b);
962
963 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
964 }