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