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