]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/cgtop/cgtop.c
cgls: print the expressive error message we have
[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
22#include <errno.h>
23#include <string.h>
24#include <stdlib.h>
1e913bcb 25#include <stdint.h>
8f2d43a0
LP
26#include <unistd.h>
27#include <alloca.h>
28#include <getopt.h>
97b845b0 29#include <signal.h>
8f2d43a0 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;
45d7a8bb
LP
50 nsec_t cpu_usage;
51 nsec_t cpu_timestamp;
8f2d43a0
LP
52 double cpu_fraction;
53
54 uint64_t memory;
55
56 unsigned io_iteration;
57 uint64_t io_input, io_output;
45d7a8bb 58 nsec_t io_timestamp;
8f2d43a0
LP
59 uint64_t io_input_bps, io_output_bps;
60} Group;
61
30edf116 62static unsigned arg_depth = 3;
45d7a8bb 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;
8f2d43a0
LP
113
114 assert(controller);
115 assert(path);
116 assert(a);
117
118 g = hashmap_get(a, path);
119 if (!g) {
120 g = hashmap_get(b, path);
121 if (!g) {
122 g = new0(Group, 1);
123 if (!g)
124 return -ENOMEM;
125
126 g->path = strdup(path);
127 if (!g->path) {
128 group_free(g);
129 return -ENOMEM;
130 }
131
132 r = hashmap_put(a, g->path, g);
133 if (r < 0) {
134 group_free(g);
135 return r;
136 }
137 } else {
2d5c93c7
MS
138 r = hashmap_move_one(a, b, path);
139 if (r < 0)
140 return r;
45d7a8bb 141
8f2d43a0
LP
142 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
143 }
144 }
145
45d7a8bb
LP
146 if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
147 _cleanup_fclose_ FILE *f = NULL;
148 pid_t pid;
8f2d43a0 149
45d7a8bb
LP
150 r = cg_enumerate_processes(controller, path, &f);
151 if (r == -ENOENT)
152 return 0;
153 if (r < 0)
154 return r;
8f2d43a0 155
45d7a8bb
LP
156 g->n_tasks = 0;
157 while (cg_read_pid(f, &pid) > 0)
158 g->n_tasks++;
8f2d43a0 159
45d7a8bb
LP
160 if (g->n_tasks > 0)
161 g->n_tasks_valid = true;
8f2d43a0 162
45d7a8bb
LP
163 } else if (streq(controller, "cpuacct")) {
164 _cleanup_free_ char *p = NULL, *v = NULL;
8f2d43a0 165 uint64_t new_usage;
45d7a8bb 166 nsec_t timestamp;
8f2d43a0
LP
167
168 r = cg_get_path(controller, path, "cpuacct.usage", &p);
169 if (r < 0)
170 return r;
171
172 r = read_one_line_file(p, &v);
45d7a8bb
LP
173 if (r == -ENOENT)
174 return 0;
8f2d43a0
LP
175 if (r < 0)
176 return r;
177
178 r = safe_atou64(v, &new_usage);
8f2d43a0
LP
179 if (r < 0)
180 return r;
181
45d7a8bb 182 timestamp = now_nsec(CLOCK_MONOTONIC);
8f2d43a0 183
45d7a8bb
LP
184 if (g->cpu_iteration == iteration - 1 &&
185 (nsec_t) new_usage > g->cpu_usage) {
8f2d43a0 186
45d7a8bb 187 nsec_t x, y;
8f2d43a0 188
45d7a8bb
LP
189 x = timestamp - g->cpu_timestamp;
190 if (x < 1)
191 x = 1;
8f2d43a0 192
45d7a8bb
LP
193 y = (nsec_t) new_usage - g->cpu_usage;
194 g->cpu_fraction = (double) y / (double) x;
195 g->cpu_valid = true;
8f2d43a0
LP
196 }
197
45d7a8bb
LP
198 g->cpu_usage = (nsec_t) new_usage;
199 g->cpu_timestamp = timestamp;
8f2d43a0
LP
200 g->cpu_iteration = iteration;
201
202 } else if (streq(controller, "memory")) {
45d7a8bb 203 _cleanup_free_ char *p = NULL, *v = NULL;
8f2d43a0
LP
204
205 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
206 if (r < 0)
207 return r;
208
209 r = read_one_line_file(p, &v);
45d7a8bb
LP
210 if (r == -ENOENT)
211 return 0;
8f2d43a0
LP
212 if (r < 0)
213 return r;
214
215 r = safe_atou64(v, &g->memory);
8f2d43a0
LP
216 if (r < 0)
217 return r;
218
219 if (g->memory > 0)
220 g->memory_valid = true;
221
222 } else if (streq(controller, "blkio")) {
45d7a8bb
LP
223 _cleanup_fclose_ FILE *f = NULL;
224 _cleanup_free_ char *p = NULL;
8f2d43a0 225 uint64_t wr = 0, rd = 0;
45d7a8bb 226 nsec_t timestamp;
8f2d43a0
LP
227
228 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
229 if (r < 0)
230 return r;
231
232 f = fopen(p, "re");
45d7a8bb
LP
233 if (!f) {
234 if (errno == ENOENT)
235 return 0;
8f2d43a0 236 return -errno;
45d7a8bb 237 }
8f2d43a0
LP
238
239 for (;;) {
240 char line[LINE_MAX], *l;
241 uint64_t k, *q;
242
243 if (!fgets(line, sizeof(line), f))
244 break;
245
246 l = strstrip(line);
247 l += strcspn(l, WHITESPACE);
248 l += strspn(l, WHITESPACE);
249
250 if (first_word(l, "Read")) {
251 l += 4;
252 q = &rd;
253 } else if (first_word(l, "Write")) {
254 l += 5;
255 q = &wr;
256 } else
257 continue;
258
259 l += strspn(l, WHITESPACE);
260 r = safe_atou64(l, &k);
261 if (r < 0)
262 continue;
263
264 *q += k;
265 }
266
45d7a8bb 267 timestamp = now_nsec(CLOCK_MONOTONIC);
8f2d43a0
LP
268
269 if (g->io_iteration == iteration - 1) {
270 uint64_t x, yr, yw;
271
45d7a8bb
LP
272 x = (uint64_t) (timestamp - g->io_timestamp);
273 if (x < 1)
274 x = 1;
8f2d43a0 275
45d7a8bb
LP
276 if (rd > g->io_input)
277 yr = rd - g->io_input;
278 else
279 yr = 0;
280
281 if (wr > g->io_output)
282 yw = wr - g->io_output;
283 else
284 yw = 0;
8f2d43a0 285
45d7a8bb 286 if (yr > 0 || yw > 0) {
8f2d43a0
LP
287 g->io_input_bps = (yr * 1000000000ULL) / x;
288 g->io_output_bps = (yw * 1000000000ULL) / x;
289 g->io_valid = true;
8f2d43a0
LP
290 }
291 }
292
293 g->io_input = rd;
294 g->io_output = wr;
45d7a8bb 295 g->io_timestamp = timestamp;
8f2d43a0
LP
296 g->io_iteration = iteration;
297 }
298
299 return 0;
300}
301
302static int refresh_one(
303 const char *controller,
304 const char *path,
305 Hashmap *a,
306 Hashmap *b,
307 unsigned iteration,
308 unsigned depth) {
309
45d7a8bb 310 _cleanup_closedir_ DIR *d = NULL;
8f2d43a0
LP
311 int r;
312
313 assert(controller);
314 assert(path);
315 assert(a);
316
317 if (depth > arg_depth)
318 return 0;
319
320 r = process(controller, path, a, b, iteration);
321 if (r < 0)
322 return r;
323
324 r = cg_enumerate_subgroups(controller, path, &d);
45d7a8bb
LP
325 if (r == -ENOENT)
326 return 0;
327 if (r < 0)
8f2d43a0 328 return r;
8f2d43a0
LP
329
330 for (;;) {
45d7a8bb 331 _cleanup_free_ char *fn = NULL, *p = NULL;
8f2d43a0
LP
332
333 r = cg_read_subgroup(d, &fn);
334 if (r <= 0)
45d7a8bb 335 return r;
8f2d43a0 336
b7def684 337 p = strjoin(path, "/", fn, NULL);
45d7a8bb
LP
338 if (!p)
339 return -ENOMEM;
8f2d43a0
LP
340
341 path_kill_slashes(p);
342
343 r = refresh_one(controller, p, a, b, iteration, depth + 1);
8f2d43a0 344 if (r < 0)
45d7a8bb 345 return r;
8f2d43a0
LP
346 }
347
8f2d43a0
LP
348 return r;
349}
350
03af6492 351static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) {
8f2d43a0
LP
352 int r;
353
354 assert(a);
355
03af6492 356 r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0);
8f2d43a0 357 if (r < 0)
45d7a8bb 358 return r;
03af6492 359 r = refresh_one("cpuacct", root, a, b, iteration, 0);
8f2d43a0 360 if (r < 0)
45d7a8bb 361 return r;
03af6492 362 r = refresh_one("memory", root, a, b, iteration, 0);
8f2d43a0 363 if (r < 0)
45d7a8bb 364 return r;
03af6492 365 r = refresh_one("blkio", root, a, b, iteration, 0);
63210a15 366 if (r < 0)
45d7a8bb
LP
367 return r;
368
63210a15 369 return 0;
8f2d43a0
LP
370}
371
372static int group_compare(const void*a, const void *b) {
373 const Group *x = *(Group**)a, *y = *(Group**)b;
374
45d7a8bb
LP
375 if (arg_order != ORDER_TASKS) {
376 /* Let's make sure that the parent is always before
377 * the child. Except when ordering by tasks, since
378 * that is actually not accumulative for all
379 * children. */
380
381 if (path_startswith(y->path, x->path))
382 return -1;
383 if (path_startswith(x->path, y->path))
384 return 1;
385 }
386
387 switch (arg_order) {
388
389 case ORDER_PATH:
390 break;
8f2d43a0 391
45d7a8bb 392 case ORDER_CPU:
1e913bcb
UTL
393 if (arg_cpu_type == CPU_PERCENT) {
394 if (x->cpu_valid && y->cpu_valid) {
395 if (x->cpu_fraction > y->cpu_fraction)
396 return -1;
397 else if (x->cpu_fraction < y->cpu_fraction)
398 return 1;
399 } else if (x->cpu_valid)
8f2d43a0 400 return -1;
1e913bcb 401 else if (y->cpu_valid)
8f2d43a0 402 return 1;
1e913bcb
UTL
403 } else {
404 if (x->cpu_usage > y->cpu_usage)
405 return -1;
406 else if (x->cpu_usage < y->cpu_usage)
407 return 1;
408 }
8f2d43a0 409
45d7a8bb 410 break;
8f2d43a0 411
45d7a8bb 412 case ORDER_TASKS:
8f2d43a0
LP
413 if (x->n_tasks_valid && y->n_tasks_valid) {
414 if (x->n_tasks > y->n_tasks)
415 return -1;
416 else if (x->n_tasks < y->n_tasks)
417 return 1;
418 } else if (x->n_tasks_valid)
419 return -1;
420 else if (y->n_tasks_valid)
421 return 1;
8f2d43a0 422
45d7a8bb
LP
423 break;
424
425 case ORDER_MEMORY:
8f2d43a0
LP
426 if (x->memory_valid && y->memory_valid) {
427 if (x->memory > y->memory)
428 return -1;
429 else if (x->memory < y->memory)
430 return 1;
431 } else if (x->memory_valid)
432 return -1;
433 else if (y->memory_valid)
434 return 1;
8f2d43a0 435
45d7a8bb
LP
436 break;
437
438 case ORDER_IO:
8f2d43a0
LP
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
45d7a8bb 450 return path_compare(x->path, y->path);
8f2d43a0
LP
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 482
45d7a8bb 483 format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0);
62b95b8b 484 cputlen = strlen(buffer);
1e913bcb 485 maxtcpu = MAX(maxtcpu, cputlen);
45d7a8bb 486
1e913bcb
UTL
487 pathtlen = strlen(array[j]->path);
488 maxtpath = MAX(maxtpath, pathtlen);
489 }
490
491 if (arg_cpu_type == CPU_PERCENT)
62b95b8b 492 snprintf(buffer, sizeof(buffer), "%6s", "%CPU");
1e913bcb 493 else
62b95b8b 494 snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time");
1e913bcb 495
ed757c0c
LP
496 rows = lines();
497 if (rows <= 10)
498 rows = 10;
8f2d43a0 499
1e913bcb 500 if (on_tty()) {
62b95b8b 501 path_columns = columns() - 36 - strlen(buffer);
1e913bcb
UTL
502 if (path_columns < 10)
503 path_columns = 10;
504
505 printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
45d7a8bb 506 arg_order == ORDER_PATH ? ON : "", path_columns, "Control Group",
1e913bcb
UTL
507 arg_order == ORDER_PATH ? OFF : "",
508 arg_order == ORDER_TASKS ? ON : "", "Tasks",
509 arg_order == ORDER_TASKS ? OFF : "",
62b95b8b 510 arg_order == ORDER_CPU ? ON : "", buffer,
1e913bcb
UTL
511 arg_order == ORDER_CPU ? OFF : "",
512 arg_order == ORDER_MEMORY ? ON : "", "Memory",
513 arg_order == ORDER_MEMORY ? OFF : "",
514 arg_order == ORDER_IO ? ON : "", "Input/s",
515 arg_order == ORDER_IO ? OFF : "",
516 arg_order == ORDER_IO ? ON : "", "Output/s",
517 arg_order == ORDER_IO ? OFF : "");
518 } else
519 path_columns = maxtpath;
8f2d43a0
LP
520
521 for (j = 0; j < n; j++) {
45d7a8bb 522 _cleanup_free_ char *p = NULL;
8f2d43a0 523
1e913bcb 524 if (on_tty() && j + 5 > rows)
8f2d43a0
LP
525 break;
526
527 g = array[j];
528
11f96fac 529 p = ellipsize(g->path, path_columns, 33);
45d7a8bb 530 printf("%-*s", path_columns, p ?: g->path);
8f2d43a0
LP
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 542 } else
45d7a8bb 543 printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_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"
45d7a8bb
LP
559 " --version Show package version\n"
560 " -p --order=path Order by path\n"
561 " -t --order=tasks Order by number of tasks\n"
562 " -c --order=cpu Order by CPU load (default)\n"
563 " -m --order=memory Order by memory load\n"
564 " -i --order=io Order by IO load\n"
a2c9f631 565 " -r --raw Provide raw (not human-readable) numbers\n"
45d7a8bb
LP
566 " --cpu=percentage Show CPU usage as percentage (default)\n"
567 " --cpu=time Show CPU usage as time\n"
1e913bcb 568 " -d --delay=DELAY Delay between updates\n"
a152771a 569 " -n --iterations=N Run for N iterations before exiting\n"
e66bb58b 570 " -b --batch Run in batch mode, accepting no input\n"
601185b4
ZJS
571 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
572 , program_invocation_short_name, arg_depth);
0d7e32fa
ZJS
573}
574
8f2d43a0
LP
575static int parse_argv(int argc, char *argv[]) {
576
577 enum {
0d7e32fa
ZJS
578 ARG_VERSION = 0x100,
579 ARG_DEPTH,
45d7a8bb
LP
580 ARG_CPU_TYPE,
581 ARG_ORDER,
8f2d43a0
LP
582 };
583
584 static const struct option options[] = {
45d7a8bb
LP
585 { "help", no_argument, NULL, 'h' },
586 { "version", no_argument, NULL, ARG_VERSION },
587 { "delay", required_argument, NULL, 'd' },
588 { "iterations", required_argument, NULL, 'n' },
589 { "batch", no_argument, NULL, 'b' },
590 { "raw", no_argument, NULL, 'r' },
591 { "depth", required_argument, NULL, ARG_DEPTH },
592 { "cpu", optional_argument, NULL, ARG_CPU_TYPE },
593 { "order", required_argument, NULL, ARG_ORDER },
eb9da376 594 {}
8f2d43a0
LP
595 };
596
597 int c;
598 int r;
599
600 assert(argc >= 1);
601 assert(argv);
602
a2c9f631 603 while ((c = getopt_long(argc, argv, "hptcmin:brd:", options, NULL)) >= 0)
8f2d43a0
LP
604
605 switch (c) {
606
607 case 'h':
601185b4
ZJS
608 help();
609 return 0;
8f2d43a0 610
0d7e32fa 611 case ARG_VERSION:
eb9da376
LP
612 puts(PACKAGE_STRING);
613 puts(SYSTEMD_FEATURES);
0d7e32fa
ZJS
614 return 0;
615
1e913bcb
UTL
616 case ARG_CPU_TYPE:
617 if (optarg) {
45d7a8bb 618 if (streq(optarg, "time"))
1e913bcb 619 arg_cpu_type = CPU_TIME;
45d7a8bb 620 else if (streq(optarg, "percentage"))
1e913bcb 621 arg_cpu_type = CPU_PERCENT;
45d7a8bb
LP
622 else {
623 log_error("Unknown argument to --cpu=: %s", optarg);
1e913bcb 624 return -EINVAL;
45d7a8bb
LP
625 }
626 } else
627 arg_cpu_type = CPU_TIME;
628
1e913bcb
UTL
629 break;
630
8f2d43a0
LP
631 case ARG_DEPTH:
632 r = safe_atou(optarg, &arg_depth);
633 if (r < 0) {
634 log_error("Failed to parse depth parameter.");
635 return -EINVAL;
636 }
637
638 break;
639
640 case 'd':
7f602784 641 r = parse_sec(optarg, &arg_delay);
8f2d43a0
LP
642 if (r < 0 || arg_delay <= 0) {
643 log_error("Failed to parse delay parameter.");
644 return -EINVAL;
645 }
646
647 break;
648
a152771a
DS
649 case 'n':
650 r = safe_atou(optarg, &arg_iterations);
651 if (r < 0) {
652 log_error("Failed to parse iterations parameter.");
653 return -EINVAL;
654 }
655
656 break;
657
e66bb58b
DS
658 case 'b':
659 arg_batch = true;
660 break;
661
a2c9f631
CD
662 case 'r':
663 arg_raw = true;
664 break;
665
8f2d43a0
LP
666 case 'p':
667 arg_order = ORDER_PATH;
668 break;
669
670 case 't':
671 arg_order = ORDER_TASKS;
672 break;
673
674 case 'c':
675 arg_order = ORDER_CPU;
676 break;
677
678 case 'm':
679 arg_order = ORDER_MEMORY;
680 break;
681
682 case 'i':
683 arg_order = ORDER_IO;
684 break;
685
45d7a8bb
LP
686 case ARG_ORDER:
687 if (streq(optarg, "path"))
688 arg_order = ORDER_PATH;
689 else if (streq(optarg, "tasks"))
690 arg_order = ORDER_TASKS;
691 else if (streq(optarg, "cpu"))
692 arg_order = ORDER_CPU;
693 else if (streq(optarg, "memory"))
694 arg_order = ORDER_MEMORY;
695 else if (streq(optarg, "io"))
696 arg_order = ORDER_IO;
697 else {
698 log_error("Invalid argument to --order=: %s", optarg);
699 return -EINVAL;
700 }
701 break;
702
8f2d43a0
LP
703 case '?':
704 return -EINVAL;
705
706 default:
eb9da376 707 assert_not_reached("Unhandled option");
8f2d43a0 708 }
8f2d43a0
LP
709
710 if (optind < argc) {
711 log_error("Too many arguments.");
712 return -EINVAL;
713 }
714
715 return 1;
716}
717
718int main(int argc, char *argv[]) {
719 int r;
720 Hashmap *a = NULL, *b = NULL;
721 unsigned iteration = 0;
722 usec_t last_refresh = 0;
723 bool quit = false, immediate_refresh = false;
03af6492 724 _cleanup_free_ char *root = NULL;
8f2d43a0
LP
725
726 log_parse_environment();
727 log_open();
728
729 r = parse_argv(argc, argv);
730 if (r <= 0)
731 goto finish;
732
03af6492
LP
733 r = cg_get_root_path(&root);
734 if (r < 0) {
735 log_error_errno(r, "Failed to get root control group path: %m");
736 goto finish;
737 }
738
d5099efc
MS
739 a = hashmap_new(&string_hash_ops);
740 b = hashmap_new(&string_hash_ops);
8f2d43a0 741 if (!a || !b) {
0d0f0c50 742 r = log_oom();
8f2d43a0
LP
743 goto finish;
744 }
745
ed757c0c 746 signal(SIGWINCH, columns_lines_cache_reset);
28917d7d 747
45d7a8bb 748 if (arg_iterations == (unsigned) -1)
780fe62e 749 arg_iterations = on_tty() ? 0 : 1;
1e913bcb 750
8f2d43a0
LP
751 while (!quit) {
752 Hashmap *c;
753 usec_t t;
754 char key;
755 char h[FORMAT_TIMESPAN_MAX];
756
757 t = now(CLOCK_MONOTONIC);
758
759 if (t >= last_refresh + arg_delay || immediate_refresh) {
760
03af6492 761 r = refresh(root, a, b, iteration++);
8f2d43a0
LP
762 if (r < 0)
763 goto finish;
764
765 group_hashmap_clear(b);
766
767 c = a;
768 a = b;
769 b = c;
770
771 last_refresh = t;
772 immediate_refresh = false;
773 }
774
775 r = display(b);
776 if (r < 0)
777 goto finish;
778
a152771a
DS
779 if (arg_iterations && iteration >= arg_iterations)
780 break;
781
dcc7aacd
CD
782 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
783 fputs("\n", stdout);
784 fflush(stdout);
785
45d7a8bb 786 if (arg_batch)
e66bb58b 787 usleep(last_refresh + arg_delay - t);
45d7a8bb
LP
788 else {
789 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
e66bb58b
DS
790 if (r == -ETIMEDOUT)
791 continue;
792 if (r < 0) {
da927ba9 793 log_error_errno(r, "Couldn't read key: %m");
e66bb58b
DS
794 goto finish;
795 }
8f2d43a0
LP
796 }
797
dcc7aacd
CD
798 if (on_tty()) { /* TTY: Clear any user keystroke */
799 fputs("\r \r", stdout);
800 fflush(stdout);
801 }
8f2d43a0 802
e66bb58b
DS
803 if (arg_batch)
804 continue;
805
8f2d43a0
LP
806 switch (key) {
807
808 case ' ':
809 immediate_refresh = true;
810 break;
811
812 case 'q':
813 quit = true;
814 break;
815
816 case 'p':
817 arg_order = ORDER_PATH;
818 break;
819
820 case 't':
821 arg_order = ORDER_TASKS;
822 break;
823
824 case 'c':
825 arg_order = ORDER_CPU;
826 break;
827
828 case 'm':
829 arg_order = ORDER_MEMORY;
830 break;
831
832 case 'i':
833 arg_order = ORDER_IO;
834 break;
835
2bfc1eda
ZJS
836 case '%':
837 arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
838 break;
839
8f2d43a0
LP
840 case '+':
841 if (arg_delay < USEC_PER_SEC)
842 arg_delay += USEC_PER_MSEC*250;
843 else
844 arg_delay += USEC_PER_SEC;
845
2fa4092c 846 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
8f2d43a0
LP
847 fflush(stdout);
848 sleep(1);
849 break;
850
851 case '-':
852 if (arg_delay <= USEC_PER_MSEC*500)
853 arg_delay = USEC_PER_MSEC*250;
854 else if (arg_delay < USEC_PER_MSEC*1250)
855 arg_delay -= USEC_PER_MSEC*250;
856 else
857 arg_delay -= USEC_PER_SEC;
858
2fa4092c 859 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
8f2d43a0
LP
860 fflush(stdout);
861 sleep(1);
862 break;
863
864 case '?':
865 case 'h':
866 fprintf(stdout,
c851f34b 867 "\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 868 "\t<" ON "+" OFF "> Increase delay; <" ON "-" OFF "> Decrease delay; <" ON "%%" OFF "> Toggle time\n"
c851f34b 869 "\t<" ON "q" OFF "> Quit; <" ON "SPACE" OFF "> Refresh");
8f2d43a0
LP
870 fflush(stdout);
871 sleep(3);
872 break;
873
874 default:
45d7a8bb
LP
875 if (key < ' ')
876 fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key);
877 else
878 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
8f2d43a0
LP
879 fflush(stdout);
880 sleep(1);
881 break;
882 }
883 }
884
8f2d43a0
LP
885 r = 0;
886
887finish:
888 group_hashmap_free(a);
889 group_hashmap_free(b);
890
14212119 891 if (r < 0) {
da927ba9 892 log_error_errno(r, "Exiting with failure: %m");
14212119
SL
893 return EXIT_FAILURE;
894 }
895
896 return EXIT_SUCCESS;
8f2d43a0 897}