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