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