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