chiark / gitweb /
util: rename parse_usec() to parse_sec() sinds the default unit is seconds
[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_tasks(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 cpu_title[21];
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(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                 snprintf(cpu_title, sizeof(cpu_title), "%"PRIu64, array[j]->cpu_usage);
470                 cputlen = strlen(cpu_title);
471                 maxtcpu = MAX(maxtcpu, cputlen);
472                 pathtlen = strlen(array[j]->path);
473                 maxtpath = MAX(maxtpath, pathtlen);
474         }
475
476         if (arg_cpu_type == CPU_PERCENT)
477                 snprintf(cpu_title, sizeof(cpu_title), "%6s", "%CPU");
478         else
479                 snprintf(cpu_title, sizeof(cpu_title), "%*s", maxtcpu, "CPU Time");
480
481         rows = lines();
482         if (rows <= 10)
483                 rows = 10;
484
485         if (on_tty()) {
486                 path_columns = columns() - 36 - strlen(cpu_title);
487                 if (path_columns < 10)
488                         path_columns = 10;
489
490                 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
491                        arg_order == ORDER_PATH ? ON : "", path_columns, "Path",
492                        arg_order == ORDER_PATH ? OFF : "",
493                        arg_order == ORDER_TASKS ? ON : "", "Tasks",
494                        arg_order == ORDER_TASKS ? OFF : "",
495                        arg_order == ORDER_CPU ? ON : "", cpu_title,
496                        arg_order == ORDER_CPU ? OFF : "",
497                        arg_order == ORDER_MEMORY ? ON : "", "Memory",
498                        arg_order == ORDER_MEMORY ? OFF : "",
499                        arg_order == ORDER_IO ? ON : "", "Input/s",
500                        arg_order == ORDER_IO ? OFF : "",
501                        arg_order == ORDER_IO ? ON : "", "Output/s",
502                        arg_order == ORDER_IO ? OFF : "");
503         } else
504                 path_columns = maxtpath;
505
506         for (j = 0; j < n; j++) {
507                 char *p;
508                 char m[FORMAT_BYTES_MAX];
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(" %*"PRIu64, maxtcpu, g->cpu_usage);
531
532                 if (g->memory_valid)
533                         printf(" %8s", format_bytes(m, sizeof(m), g->memory));
534                 else
535                         fputs("        -", stdout);
536
537                 if (g->io_valid) {
538                         printf(" %8s",
539                                format_bytes(m, sizeof(m), g->io_input_bps));
540                         printf(" %8s",
541                                format_bytes(m, sizeof(m), 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
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
570 static void version(void) {
571         puts(PACKAGE_STRING " cgtop");
572 }
573
574 static int parse_argv(int argc, char *argv[]) {
575
576         enum {
577                 ARG_VERSION = 0x100,
578                 ARG_DEPTH,
579                 ARG_CPU_TYPE
580         };
581
582         static const struct option options[] = {
583                 { "help",       no_argument,       NULL, 'h'         },
584                 { "version",    no_argument,       NULL, ARG_VERSION },
585                 { "delay",      required_argument, NULL, 'd'         },
586                 { "iterations", required_argument, NULL, 'n'         },
587                 { "batch",      no_argument,       NULL, 'b'         },
588                 { "depth",      required_argument, NULL, ARG_DEPTH   },
589                 { "cpu",        optional_argument, NULL, ARG_CPU_TYPE},
590                 { NULL,         0,                 NULL, 0           }
591         };
592
593         int c;
594         int r;
595
596         assert(argc >= 1);
597         assert(argv);
598
599         while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0) {
600
601                 switch (c) {
602
603                 case 'h':
604                         help();
605                         return 0;
606
607                 case ARG_VERSION:
608                         version();
609                         return 0;
610
611                 case ARG_CPU_TYPE:
612                         if (optarg) {
613                                 if (strcmp(optarg, "time") == 0)
614                                         arg_cpu_type = CPU_TIME;
615                                 else if (strcmp(optarg, "percentage") == 0)
616                                         arg_cpu_type = CPU_PERCENT;
617                                 else
618                                         return -EINVAL;
619                         }
620                         break;
621
622                 case ARG_DEPTH:
623                         r = safe_atou(optarg, &arg_depth);
624                         if (r < 0) {
625                                 log_error("Failed to parse depth parameter.");
626                                 return -EINVAL;
627                         }
628
629                         break;
630
631                 case 'd':
632                         r = parse_sec(optarg, &arg_delay);
633                         if (r < 0 || arg_delay <= 0) {
634                                 log_error("Failed to parse delay parameter.");
635                                 return -EINVAL;
636                         }
637
638                         break;
639
640                 case 'n':
641                         r = safe_atou(optarg, &arg_iterations);
642                         if (r < 0) {
643                                 log_error("Failed to parse iterations parameter.");
644                                 return -EINVAL;
645                         }
646
647                         break;
648
649                 case 'b':
650                         arg_batch = true;
651                         break;
652
653                 case 'p':
654                         arg_order = ORDER_PATH;
655                         break;
656
657                 case 't':
658                         arg_order = ORDER_TASKS;
659                         break;
660
661                 case 'c':
662                         arg_order = ORDER_CPU;
663                         break;
664
665                 case 'm':
666                         arg_order = ORDER_MEMORY;
667                         break;
668
669                 case 'i':
670                         arg_order = ORDER_IO;
671                         break;
672
673                 case '?':
674                         return -EINVAL;
675
676                 default:
677                         log_error("Unknown option code %c", c);
678                         return -EINVAL;
679                 }
680         }
681
682         if (optind < argc) {
683                 log_error("Too many arguments.");
684                 return -EINVAL;
685         }
686
687         return 1;
688 }
689
690 int main(int argc, char *argv[]) {
691         int r;
692         Hashmap *a = NULL, *b = NULL;
693         unsigned iteration = 0;
694         usec_t last_refresh = 0;
695         bool quit = false, immediate_refresh = false;
696
697         log_parse_environment();
698         log_open();
699
700         r = parse_argv(argc, argv);
701         if (r <= 0)
702                 goto finish;
703
704         a = hashmap_new(string_hash_func, string_compare_func);
705         b = hashmap_new(string_hash_func, string_compare_func);
706         if (!a || !b) {
707                 r = log_oom();
708                 goto finish;
709         }
710
711         signal(SIGWINCH, columns_lines_cache_reset);
712
713         if (!on_tty())
714                 arg_iterations = 1;
715
716         while (!quit) {
717                 Hashmap *c;
718                 usec_t t;
719                 char key;
720                 char h[FORMAT_TIMESPAN_MAX];
721
722                 t = now(CLOCK_MONOTONIC);
723
724                 if (t >= last_refresh + arg_delay || immediate_refresh) {
725
726                         r = refresh(a, b, iteration++);
727                         if (r < 0)
728                                 goto finish;
729
730                         group_hashmap_clear(b);
731
732                         c = a;
733                         a = b;
734                         b = c;
735
736                         last_refresh = t;
737                         immediate_refresh = false;
738                 }
739
740                 r = display(b);
741                 if (r < 0)
742                         goto finish;
743
744                 if (arg_iterations && iteration >= arg_iterations)
745                         break;
746
747                 if (arg_batch) {
748                         usleep(last_refresh + arg_delay - t);
749                 } else {
750                         r = read_one_char(stdin, &key,
751                                           last_refresh + arg_delay - t, NULL);
752                         if (r == -ETIMEDOUT)
753                                 continue;
754                         if (r < 0) {
755                                 log_error("Couldn't read key: %s", strerror(-r));
756                                 goto finish;
757                         }
758                 }
759
760                 fputs("\r \r", stdout);
761                 fflush(stdout);
762
763                 if (arg_batch)
764                         continue;
765
766                 switch (key) {
767
768                 case ' ':
769                         immediate_refresh = true;
770                         break;
771
772                 case 'q':
773                         quit = true;
774                         break;
775
776                 case 'p':
777                         arg_order = ORDER_PATH;
778                         break;
779
780                 case 't':
781                         arg_order = ORDER_TASKS;
782                         break;
783
784                 case 'c':
785                         arg_order = ORDER_CPU;
786                         break;
787
788                 case 'm':
789                         arg_order = ORDER_MEMORY;
790                         break;
791
792                 case 'i':
793                         arg_order = ORDER_IO;
794                         break;
795
796                 case '%':
797                         arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
798                         break;
799
800                 case '+':
801                         if (arg_delay < USEC_PER_SEC)
802                                 arg_delay += USEC_PER_MSEC*250;
803                         else
804                                 arg_delay += USEC_PER_SEC;
805
806                         fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
807                         fflush(stdout);
808                         sleep(1);
809                         break;
810
811                 case '-':
812                         if (arg_delay <= USEC_PER_MSEC*500)
813                                 arg_delay = USEC_PER_MSEC*250;
814                         else if (arg_delay < USEC_PER_MSEC*1250)
815                                 arg_delay -= USEC_PER_MSEC*250;
816                         else
817                                 arg_delay -= USEC_PER_SEC;
818
819                         fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
820                         fflush(stdout);
821                         sleep(1);
822                         break;
823
824                 case '?':
825                 case 'h':
826                         fprintf(stdout,
827                                 "\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"
828                                 "\t<" ON "+" OFF "> Increase delay; <" ON "-" OFF "> Decrease delay; <" ON "%%" OFF "> Toggle time\n"
829                                 "\t<" ON "Q" OFF "> Quit; <" ON "SPACE" OFF "> Refresh");
830                         fflush(stdout);
831                         sleep(3);
832                         break;
833
834                 default:
835                         fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
836                         fflush(stdout);
837                         sleep(1);
838                         break;
839                 }
840         }
841
842         r = 0;
843
844 finish:
845         group_hashmap_free(a);
846         group_hashmap_free(b);
847
848         if (r < 0) {
849                 log_error("Exiting with failure: %s", strerror(-r));
850                 return EXIT_FAILURE;
851         }
852
853         return EXIT_SUCCESS;
854 }