chiark / gitweb /
hashmap: introduce hash_ops to make struct Hashmap smaller
[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                         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 void help(void) {
552         printf("%s [OPTIONS...]\n\n"
553                "Show top control groups by their resource usage.\n\n"
554                "  -h --help           Show this help\n"
555                "  --version           Print version and exit\n"
556                "  -p                  Order by path\n"
557                "  -t                  Order by number of tasks\n"
558                "  -c                  Order by CPU load\n"
559                "  -m                  Order by memory load\n"
560                "  -i                  Order by IO load\n"
561                "     --cpu[=TYPE]     Show CPU usage as time or percentage (default)\n"
562                "  -d --delay=DELAY    Delay between updates\n"
563                "  -n --iterations=N   Run for N iterations before exiting\n"
564                "  -b --batch          Run in batch mode, accepting no input\n"
565                "     --depth=DEPTH    Maximum traversal depth (default: %u)\n"
566                , program_invocation_short_name, arg_depth);
567 }
568
569 static int parse_argv(int argc, char *argv[]) {
570
571         enum {
572                 ARG_VERSION = 0x100,
573                 ARG_DEPTH,
574                 ARG_CPU_TYPE
575         };
576
577         static const struct option options[] = {
578                 { "help",       no_argument,       NULL, 'h'         },
579                 { "version",    no_argument,       NULL, ARG_VERSION },
580                 { "delay",      required_argument, NULL, 'd'         },
581                 { "iterations", required_argument, NULL, 'n'         },
582                 { "batch",      no_argument,       NULL, 'b'         },
583                 { "depth",      required_argument, NULL, ARG_DEPTH   },
584                 { "cpu",        optional_argument, NULL, ARG_CPU_TYPE},
585                 {}
586         };
587
588         int c;
589         int r;
590
591         assert(argc >= 1);
592         assert(argv);
593
594         while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0)
595
596                 switch (c) {
597
598                 case 'h':
599                         help();
600                         return 0;
601
602                 case ARG_VERSION:
603                         puts(PACKAGE_STRING);
604                         puts(SYSTEMD_FEATURES);
605                         return 0;
606
607                 case ARG_CPU_TYPE:
608                         if (optarg) {
609                                 if (strcmp(optarg, "time") == 0)
610                                         arg_cpu_type = CPU_TIME;
611                                 else if (strcmp(optarg, "percentage") == 0)
612                                         arg_cpu_type = CPU_PERCENT;
613                                 else
614                                         return -EINVAL;
615                         }
616                         break;
617
618                 case ARG_DEPTH:
619                         r = safe_atou(optarg, &arg_depth);
620                         if (r < 0) {
621                                 log_error("Failed to parse depth parameter.");
622                                 return -EINVAL;
623                         }
624
625                         break;
626
627                 case 'd':
628                         r = parse_sec(optarg, &arg_delay);
629                         if (r < 0 || arg_delay <= 0) {
630                                 log_error("Failed to parse delay parameter.");
631                                 return -EINVAL;
632                         }
633
634                         break;
635
636                 case 'n':
637                         r = safe_atou(optarg, &arg_iterations);
638                         if (r < 0) {
639                                 log_error("Failed to parse iterations parameter.");
640                                 return -EINVAL;
641                         }
642
643                         break;
644
645                 case 'b':
646                         arg_batch = true;
647                         break;
648
649                 case 'p':
650                         arg_order = ORDER_PATH;
651                         break;
652
653                 case 't':
654                         arg_order = ORDER_TASKS;
655                         break;
656
657                 case 'c':
658                         arg_order = ORDER_CPU;
659                         break;
660
661                 case 'm':
662                         arg_order = ORDER_MEMORY;
663                         break;
664
665                 case 'i':
666                         arg_order = ORDER_IO;
667                         break;
668
669                 case '?':
670                         return -EINVAL;
671
672                 default:
673                         assert_not_reached("Unhandled option");
674                 }
675
676         if (optind < argc) {
677                 log_error("Too many arguments.");
678                 return -EINVAL;
679         }
680
681         return 1;
682 }
683
684 int main(int argc, char *argv[]) {
685         int r;
686         Hashmap *a = NULL, *b = NULL;
687         unsigned iteration = 0;
688         usec_t last_refresh = 0;
689         bool quit = false, immediate_refresh = false;
690
691         log_parse_environment();
692         log_open();
693
694         r = parse_argv(argc, argv);
695         if (r <= 0)
696                 goto finish;
697
698         a = hashmap_new(&string_hash_ops);
699         b = hashmap_new(&string_hash_ops);
700         if (!a || !b) {
701                 r = log_oom();
702                 goto finish;
703         }
704
705         signal(SIGWINCH, columns_lines_cache_reset);
706
707         if (!on_tty())
708                 arg_iterations = 1;
709
710         while (!quit) {
711                 Hashmap *c;
712                 usec_t t;
713                 char key;
714                 char h[FORMAT_TIMESPAN_MAX];
715
716                 t = now(CLOCK_MONOTONIC);
717
718                 if (t >= last_refresh + arg_delay || immediate_refresh) {
719
720                         r = refresh(a, b, iteration++);
721                         if (r < 0)
722                                 goto finish;
723
724                         group_hashmap_clear(b);
725
726                         c = a;
727                         a = b;
728                         b = c;
729
730                         last_refresh = t;
731                         immediate_refresh = false;
732                 }
733
734                 r = display(b);
735                 if (r < 0)
736                         goto finish;
737
738                 if (arg_iterations && iteration >= arg_iterations)
739                         break;
740
741                 if (arg_batch) {
742                         usleep(last_refresh + arg_delay - t);
743                 } else {
744                         r = read_one_char(stdin, &key,
745                                           last_refresh + arg_delay - t, NULL);
746                         if (r == -ETIMEDOUT)
747                                 continue;
748                         if (r < 0) {
749                                 log_error("Couldn't read key: %s", strerror(-r));
750                                 goto finish;
751                         }
752                 }
753
754                 fputs("\r \r", stdout);
755                 fflush(stdout);
756
757                 if (arg_batch)
758                         continue;
759
760                 switch (key) {
761
762                 case ' ':
763                         immediate_refresh = true;
764                         break;
765
766                 case 'q':
767                         quit = true;
768                         break;
769
770                 case 'p':
771                         arg_order = ORDER_PATH;
772                         break;
773
774                 case 't':
775                         arg_order = ORDER_TASKS;
776                         break;
777
778                 case 'c':
779                         arg_order = ORDER_CPU;
780                         break;
781
782                 case 'm':
783                         arg_order = ORDER_MEMORY;
784                         break;
785
786                 case 'i':
787                         arg_order = ORDER_IO;
788                         break;
789
790                 case '%':
791                         arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
792                         break;
793
794                 case '+':
795                         if (arg_delay < USEC_PER_SEC)
796                                 arg_delay += USEC_PER_MSEC*250;
797                         else
798                                 arg_delay += USEC_PER_SEC;
799
800                         fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
801                         fflush(stdout);
802                         sleep(1);
803                         break;
804
805                 case '-':
806                         if (arg_delay <= USEC_PER_MSEC*500)
807                                 arg_delay = USEC_PER_MSEC*250;
808                         else if (arg_delay < USEC_PER_MSEC*1250)
809                                 arg_delay -= USEC_PER_MSEC*250;
810                         else
811                                 arg_delay -= USEC_PER_SEC;
812
813                         fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
814                         fflush(stdout);
815                         sleep(1);
816                         break;
817
818                 case '?':
819                 case 'h':
820                         fprintf(stdout,
821                                 "\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"
822                                 "\t<" ON "+" OFF "> Increase delay; <" ON "-" OFF "> Decrease delay; <" ON "%%" OFF "> Toggle time\n"
823                                 "\t<" ON "q" OFF "> Quit; <" ON "SPACE" OFF "> Refresh");
824                         fflush(stdout);
825                         sleep(3);
826                         break;
827
828                 default:
829                         fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
830                         fflush(stdout);
831                         sleep(1);
832                         break;
833                 }
834         }
835
836         r = 0;
837
838 finish:
839         group_hashmap_free(a);
840         group_hashmap_free(b);
841
842         if (r < 0) {
843                 log_error("Exiting with failure: %s", strerror(-r));
844                 return EXIT_FAILURE;
845         }
846
847         return EXIT_SUCCESS;
848 }