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