]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cgtop/cgtop.c
Merge pull request #1109 from phomes/man-typos
[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 *p = NULL;
564
565 if (on_tty() && j + 5 > rows)
566 break;
567
568 g = array[j];
569
570 p = ellipsize(g->path, path_columns, 33);
571 printf("%-*s", path_columns, p ?: g->path);
572
573 if (g->n_tasks_valid)
574 printf(" %7u", g->n_tasks);
575 else
576 fputs(" -", stdout);
577
578 if (arg_cpu_type == CPU_PERCENT) {
579 if (g->cpu_valid)
580 printf(" %6.1f", g->cpu_fraction*100);
581 else
582 fputs(" -", stdout);
583 } else
584 printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
585
586 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory));
587 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps));
588 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps));
589
590 putchar('\n');
591 }
592 }
593
594 static void help(void) {
595 printf("%s [OPTIONS...]\n\n"
596 "Show top control groups by their resource usage.\n\n"
597 " -h --help Show this help\n"
598 " --version Show package version\n"
599 " -p --order=path Order by path\n"
600 " -t --order=tasks Order by number of tasks\n"
601 " -c --order=cpu Order by CPU load (default)\n"
602 " -m --order=memory Order by memory load\n"
603 " -i --order=io Order by IO load\n"
604 " -r --raw Provide raw (not human-readable) numbers\n"
605 " --cpu=percentage Show CPU usage as percentage (default)\n"
606 " --cpu=time Show CPU usage as time\n"
607 " -k Include kernel threads in task count\n"
608 " --recursive=BOOL Sum up task count recursively\n"
609 " -d --delay=DELAY Delay between updates\n"
610 " -n --iterations=N Run for N iterations before exiting\n"
611 " -b --batch Run in batch mode, accepting no input\n"
612 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
613 , program_invocation_short_name, arg_depth);
614 }
615
616 static int parse_argv(int argc, char *argv[]) {
617
618 enum {
619 ARG_VERSION = 0x100,
620 ARG_DEPTH,
621 ARG_CPU_TYPE,
622 ARG_ORDER,
623 ARG_RECURSIVE,
624 };
625
626 static const struct option options[] = {
627 { "help", no_argument, NULL, 'h' },
628 { "version", no_argument, NULL, ARG_VERSION },
629 { "delay", required_argument, NULL, 'd' },
630 { "iterations", required_argument, NULL, 'n' },
631 { "batch", no_argument, NULL, 'b' },
632 { "raw", no_argument, NULL, 'r' },
633 { "depth", required_argument, NULL, ARG_DEPTH },
634 { "cpu", optional_argument, NULL, ARG_CPU_TYPE },
635 { "order", required_argument, NULL, ARG_ORDER },
636 { "recursive", required_argument, NULL, ARG_RECURSIVE },
637 {}
638 };
639
640 int c, r;
641
642 assert(argc >= 1);
643 assert(argv);
644
645 while ((c = getopt_long(argc, argv, "hptcmin:brd:k", options, NULL)) >= 0)
646
647 switch (c) {
648
649 case 'h':
650 help();
651 return 0;
652
653 case ARG_VERSION:
654 puts(PACKAGE_STRING);
655 puts(SYSTEMD_FEATURES);
656 return 0;
657
658 case ARG_CPU_TYPE:
659 if (optarg) {
660 if (streq(optarg, "time"))
661 arg_cpu_type = CPU_TIME;
662 else if (streq(optarg, "percentage"))
663 arg_cpu_type = CPU_PERCENT;
664 else {
665 log_error("Unknown argument to --cpu=: %s", optarg);
666 return -EINVAL;
667 }
668 } else
669 arg_cpu_type = CPU_TIME;
670
671 break;
672
673 case ARG_DEPTH:
674 r = safe_atou(optarg, &arg_depth);
675 if (r < 0) {
676 log_error("Failed to parse depth parameter.");
677 return -EINVAL;
678 }
679
680 break;
681
682 case 'd':
683 r = parse_sec(optarg, &arg_delay);
684 if (r < 0 || arg_delay <= 0) {
685 log_error("Failed to parse delay parameter.");
686 return -EINVAL;
687 }
688
689 break;
690
691 case 'n':
692 r = safe_atou(optarg, &arg_iterations);
693 if (r < 0) {
694 log_error("Failed to parse iterations parameter.");
695 return -EINVAL;
696 }
697
698 break;
699
700 case 'b':
701 arg_batch = true;
702 break;
703
704 case 'r':
705 arg_raw = true;
706 break;
707
708 case 'p':
709 arg_order = ORDER_PATH;
710 break;
711
712 case 't':
713 arg_order = ORDER_TASKS;
714 break;
715
716 case 'c':
717 arg_order = ORDER_CPU;
718 break;
719
720 case 'm':
721 arg_order = ORDER_MEMORY;
722 break;
723
724 case 'i':
725 arg_order = ORDER_IO;
726 break;
727
728 case ARG_ORDER:
729 if (streq(optarg, "path"))
730 arg_order = ORDER_PATH;
731 else if (streq(optarg, "tasks"))
732 arg_order = ORDER_TASKS;
733 else if (streq(optarg, "cpu"))
734 arg_order = ORDER_CPU;
735 else if (streq(optarg, "memory"))
736 arg_order = ORDER_MEMORY;
737 else if (streq(optarg, "io"))
738 arg_order = ORDER_IO;
739 else {
740 log_error("Invalid argument to --order=: %s", optarg);
741 return -EINVAL;
742 }
743 break;
744
745 case 'k':
746 arg_kernel_threads = true;
747 break;
748
749 case ARG_RECURSIVE:
750 r = parse_boolean(optarg);
751 if (r < 0) {
752 log_error("Failed to parse --recursive= argument: %s", optarg);
753 return r;
754 }
755
756 arg_recursive = r;
757 break;
758
759 case '?':
760 return -EINVAL;
761
762 default:
763 assert_not_reached("Unhandled option");
764 }
765
766 if (optind < argc) {
767 log_error("Too many arguments.");
768 return -EINVAL;
769 }
770
771 return 1;
772 }
773
774 int main(int argc, char *argv[]) {
775 int r;
776 Hashmap *a = NULL, *b = NULL;
777 unsigned iteration = 0;
778 usec_t last_refresh = 0;
779 bool quit = false, immediate_refresh = false;
780 _cleanup_free_ char *root = NULL;
781
782 log_parse_environment();
783 log_open();
784
785 r = parse_argv(argc, argv);
786 if (r <= 0)
787 goto finish;
788
789 r = cg_get_root_path(&root);
790 if (r < 0) {
791 log_error_errno(r, "Failed to get root control group path: %m");
792 goto finish;
793 }
794
795 a = hashmap_new(&string_hash_ops);
796 b = hashmap_new(&string_hash_ops);
797 if (!a || !b) {
798 r = log_oom();
799 goto finish;
800 }
801
802 signal(SIGWINCH, columns_lines_cache_reset);
803
804 if (arg_iterations == (unsigned) -1)
805 arg_iterations = on_tty() ? 0 : 1;
806
807 while (!quit) {
808 Hashmap *c;
809 usec_t t;
810 char key;
811 char h[FORMAT_TIMESPAN_MAX];
812
813 t = now(CLOCK_MONOTONIC);
814
815 if (t >= last_refresh + arg_delay || immediate_refresh) {
816
817 r = refresh(root, a, b, iteration++);
818 if (r < 0) {
819 log_error_errno(r, "Failed to refresh: %m");
820 goto finish;
821 }
822
823 group_hashmap_clear(b);
824
825 c = a;
826 a = b;
827 b = c;
828
829 last_refresh = t;
830 immediate_refresh = false;
831 }
832
833 display(b);
834
835 if (arg_iterations && iteration >= arg_iterations)
836 break;
837
838 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
839 fputs("\n", stdout);
840 fflush(stdout);
841
842 if (arg_batch)
843 (void) usleep(last_refresh + arg_delay - t);
844 else {
845 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
846 if (r == -ETIMEDOUT)
847 continue;
848 if (r < 0) {
849 log_error_errno(r, "Couldn't read key: %m");
850 goto finish;
851 }
852 }
853
854 if (on_tty()) { /* TTY: Clear any user keystroke */
855 fputs("\r \r", stdout);
856 fflush(stdout);
857 }
858
859 if (arg_batch)
860 continue;
861
862 switch (key) {
863
864 case ' ':
865 immediate_refresh = true;
866 break;
867
868 case 'q':
869 quit = true;
870 break;
871
872 case 'p':
873 arg_order = ORDER_PATH;
874 break;
875
876 case 't':
877 arg_order = ORDER_TASKS;
878 break;
879
880 case 'c':
881 arg_order = ORDER_CPU;
882 break;
883
884 case 'm':
885 arg_order = ORDER_MEMORY;
886 break;
887
888 case 'i':
889 arg_order = ORDER_IO;
890 break;
891
892 case '%':
893 arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
894 break;
895
896 case 'k':
897 arg_kernel_threads = !arg_kernel_threads;
898 fprintf(stdout, "\nCounting kernel threads: %s.", yes_no(arg_kernel_threads));
899 fflush(stdout);
900 sleep(1);
901 break;
902
903 case 'r':
904 arg_recursive = !arg_recursive;
905 fprintf(stdout, "\nRecursive task counting: %s", yes_no(arg_recursive));
906 fflush(stdout);
907 sleep(1);
908 break;
909
910 case '+':
911 if (arg_delay < USEC_PER_SEC)
912 arg_delay += USEC_PER_MSEC*250;
913 else
914 arg_delay += USEC_PER_SEC;
915
916 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
917 fflush(stdout);
918 sleep(1);
919 break;
920
921 case '-':
922 if (arg_delay <= USEC_PER_MSEC*500)
923 arg_delay = USEC_PER_MSEC*250;
924 else if (arg_delay < USEC_PER_MSEC*1250)
925 arg_delay -= USEC_PER_MSEC*250;
926 else
927 arg_delay -= USEC_PER_SEC;
928
929 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
930 fflush(stdout);
931 sleep(1);
932 break;
933
934 case '?':
935 case 'h':
936 fprintf(stdout,
937 "\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"
938 "\t<" ON "+" OFF "> Inc. delay; <" ON "-" OFF "> Dec. delay; <" ON "%%" OFF "> Toggle time; <" ON "SPACE" OFF "> Refresh\n"
939 "\t<" ON "k" OFF "> Count kernel threads; <" ON "r" OFF "> Count recursively; <" ON "q" OFF "> Quit");
940 fflush(stdout);
941 sleep(3);
942 break;
943
944 default:
945 if (key < ' ')
946 fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key);
947 else
948 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
949 fflush(stdout);
950 sleep(1);
951 break;
952 }
953 }
954
955 r = 0;
956
957 finish:
958 group_hashmap_free(a);
959 group_hashmap_free(b);
960
961 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
962 }