]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/cgtop/cgtop.c
cgtop: command line parsing improvements
[thirdparty/systemd.git] / src / cgtop / cgtop.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
8f2d43a0
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
8f2d43a0
LP
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 15 Lesser General Public License for more details.
8f2d43a0 16
5430f7f2 17 You should have received a copy of the GNU Lesser General Public License
8f2d43a0
LP
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
8f2d43a0 21#include <alloca.h>
3f6fd1ba 22#include <errno.h>
8f2d43a0 23#include <getopt.h>
97b845b0 24#include <signal.h>
3f6fd1ba
LP
25#include <stdint.h>
26#include <stdlib.h>
27#include <string.h>
28#include <unistd.h>
8f2d43a0 29
96a6426f 30#include "sd-bus.h"
3f6fd1ba 31
b5efdb8a 32#include "alloc-util.h"
96a6426f 33#include "bus-error.h"
3f6fd1ba 34#include "bus-util.h"
d3e8277d 35#include "cgroup-show.h"
3f6fd1ba 36#include "cgroup-util.h"
3ffd4af2 37#include "fd-util.h"
3f6fd1ba
LP
38#include "fileio.h"
39#include "hashmap.h"
6bedfcbb 40#include "parse-util.h"
3f6fd1ba
LP
41#include "path-util.h"
42#include "process-util.h"
fe37a784 43#include "procfs-util.h"
d054f0a4 44#include "stdio-util.h"
e104fb80 45#include "strv.h"
3f6fd1ba 46#include "terminal-util.h"
96a6426f 47#include "unit-name.h"
3f6fd1ba 48#include "util.h"
8f2d43a0
LP
49
50typedef struct Group {
51 char *path;
52
53 bool n_tasks_valid:1;
54 bool cpu_valid:1;
55 bool memory_valid:1;
56 bool io_valid:1;
57
03a7b521 58 uint64_t n_tasks;
8f2d43a0
LP
59
60 unsigned cpu_iteration;
45d7a8bb
LP
61 nsec_t cpu_usage;
62 nsec_t cpu_timestamp;
8f2d43a0
LP
63 double cpu_fraction;
64
65 uint64_t memory;
66
67 unsigned io_iteration;
68 uint64_t io_input, io_output;
45d7a8bb 69 nsec_t io_timestamp;
8f2d43a0
LP
70 uint64_t io_input_bps, io_output_bps;
71} Group;
72
30edf116 73static unsigned arg_depth = 3;
45d7a8bb 74static unsigned arg_iterations = (unsigned) -1;
e66bb58b 75static bool arg_batch = false;
a2c9f631 76static bool arg_raw = false;
8f2d43a0 77static usec_t arg_delay = 1*USEC_PER_SEC;
96a6426f 78static char* arg_machine = NULL;
308253c5 79static char* arg_root = NULL;
dd422d1e 80static bool arg_recursive = true;
6d9f40d5 81static bool arg_recursive_unset = false;
03a7b521 82
dd422d1e 83static enum {
03a7b521
LP
84 COUNT_PIDS,
85 COUNT_USERSPACE_PROCESSES,
86 COUNT_ALL_PROCESSES,
87} arg_count = COUNT_PIDS;
8f2d43a0
LP
88
89static enum {
90 ORDER_PATH,
91 ORDER_TASKS,
92 ORDER_CPU,
93 ORDER_MEMORY,
03a7b521 94 ORDER_IO,
8f2d43a0
LP
95} arg_order = ORDER_CPU;
96
1e913bcb
UTL
97static enum {
98 CPU_PERCENT,
99 CPU_TIME,
100} arg_cpu_type = CPU_PERCENT;
101
8f2d43a0
LP
102static void group_free(Group *g) {
103 assert(g);
104
105 free(g->path);
106 free(g);
107}
108
109static void group_hashmap_clear(Hashmap *h) {
224b0e7a 110 hashmap_clear_with_destructor(h, group_free);
8f2d43a0
LP
111}
112
113static void group_hashmap_free(Hashmap *h) {
114 group_hashmap_clear(h);
115 hashmap_free(h);
116}
117
59f448cf 118static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, uint64_t t) {
a2c9f631
CD
119 if (!is_valid)
120 return "-";
121 if (arg_raw) {
557e3693 122 snprintf(buf, l, "%" PRIu64, t);
a2c9f631
CD
123 return buf;
124 }
125 return format_bytes(buf, l, t);
126}
127
a7e6de21
LP
128static bool is_root_cgroup(const char *path) {
129 return isempty(path) || path_equal(path, "/");
130}
131
3cb5beea
LP
132static int process(
133 const char *controller,
134 const char *path,
135 Hashmap *a,
136 Hashmap *b,
137 unsigned iteration,
138 Group **ret) {
139
8f2d43a0 140 Group *g;
b4cccbc1 141 int r, all_unified;
8f2d43a0
LP
142
143 assert(controller);
144 assert(path);
145 assert(a);
146
b4cccbc1
LP
147 all_unified = cg_all_unified();
148 if (all_unified < 0)
149 return all_unified;
150
8f2d43a0
LP
151 g = hashmap_get(a, path);
152 if (!g) {
153 g = hashmap_get(b, path);
154 if (!g) {
155 g = new0(Group, 1);
156 if (!g)
157 return -ENOMEM;
158
159 g->path = strdup(path);
160 if (!g->path) {
161 group_free(g);
162 return -ENOMEM;
163 }
164
165 r = hashmap_put(a, g->path, g);
166 if (r < 0) {
167 group_free(g);
168 return r;
169 }
170 } else {
2d5c93c7
MS
171 r = hashmap_move_one(a, b, path);
172 if (r < 0)
173 return r;
45d7a8bb 174
8f2d43a0
LP
175 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
176 }
177 }
178
03a7b521 179 if (streq(controller, SYSTEMD_CGROUP_CONTROLLER) && IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES)) {
45d7a8bb
LP
180 _cleanup_fclose_ FILE *f = NULL;
181 pid_t pid;
8f2d43a0 182
45d7a8bb
LP
183 r = cg_enumerate_processes(controller, path, &f);
184 if (r == -ENOENT)
185 return 0;
186 if (r < 0)
187 return r;
8f2d43a0 188
45d7a8bb 189 g->n_tasks = 0;
41ba8b6e
LP
190 while (cg_read_pid(f, &pid) > 0) {
191
03a7b521 192 if (arg_count == COUNT_USERSPACE_PROCESSES && is_kernel_thread(pid) > 0)
41ba8b6e
LP
193 continue;
194
45d7a8bb 195 g->n_tasks++;
41ba8b6e 196 }
8f2d43a0 197
45d7a8bb
LP
198 if (g->n_tasks > 0)
199 g->n_tasks_valid = true;
8f2d43a0 200
03a7b521 201 } else if (streq(controller, "pids") && arg_count == COUNT_PIDS) {
03a7b521 202
a7e6de21 203 if (is_root_cgroup(path)) {
fe37a784
LP
204 r = procfs_tasks_get_current(&g->n_tasks);
205 if (r < 0)
206 return r;
207 } else {
208 _cleanup_free_ char *p = NULL, *v = NULL;
03a7b521 209
fe37a784
LP
210 r = cg_get_path(controller, path, "pids.current", &p);
211 if (r < 0)
212 return r;
03a7b521 213
fe37a784
LP
214 r = read_one_line_file(p, &v);
215 if (r == -ENOENT)
216 return 0;
217 if (r < 0)
218 return r;
219
220 r = safe_atou64(v, &g->n_tasks);
221 if (r < 0)
222 return r;
223 }
03a7b521
LP
224
225 if (g->n_tasks > 0)
226 g->n_tasks_valid = true;
227
e104fb80 228 } else if (STR_IN_SET(controller, "cpu", "cpuacct")) {
45d7a8bb 229 _cleanup_free_ char *p = NULL, *v = NULL;
8f2d43a0 230 uint64_t new_usage;
45d7a8bb 231 nsec_t timestamp;
8f2d43a0 232
b4cccbc1 233 if (all_unified) {
66ebf6c0
TH
234 const char *keys[] = { "usage_usec", NULL };
235 _cleanup_free_ char *val = NULL;
8f2d43a0 236
66ebf6c0
TH
237 if (!streq(controller, "cpu"))
238 return 0;
8f2d43a0 239
66ebf6c0
TH
240 r = cg_get_keyed_attribute("cpu", path, "cpu.stat", keys, &val);
241 if (r == -ENOENT)
242 return 0;
243 if (r < 0)
244 return r;
245
246 r = safe_atou64(val, &new_usage);
247 if (r < 0)
248 return r;
249
250 new_usage *= NSEC_PER_USEC;
251 } else {
252 if (!streq(controller, "cpuacct"))
253 return 0;
254
255 r = cg_get_path(controller, path, "cpuacct.usage", &p);
256 if (r < 0)
257 return r;
258
259 r = read_one_line_file(p, &v);
260 if (r == -ENOENT)
261 return 0;
262 if (r < 0)
263 return r;
264
265 r = safe_atou64(v, &new_usage);
266 if (r < 0)
267 return r;
268 }
8f2d43a0 269
45d7a8bb 270 timestamp = now_nsec(CLOCK_MONOTONIC);
8f2d43a0 271
45d7a8bb
LP
272 if (g->cpu_iteration == iteration - 1 &&
273 (nsec_t) new_usage > g->cpu_usage) {
8f2d43a0 274
45d7a8bb 275 nsec_t x, y;
8f2d43a0 276
45d7a8bb
LP
277 x = timestamp - g->cpu_timestamp;
278 if (x < 1)
279 x = 1;
8f2d43a0 280
45d7a8bb
LP
281 y = (nsec_t) new_usage - g->cpu_usage;
282 g->cpu_fraction = (double) y / (double) x;
283 g->cpu_valid = true;
8f2d43a0
LP
284 }
285
45d7a8bb
LP
286 g->cpu_usage = (nsec_t) new_usage;
287 g->cpu_timestamp = timestamp;
8f2d43a0
LP
288 g->cpu_iteration = iteration;
289
290 } else if (streq(controller, "memory")) {
45d7a8bb 291 _cleanup_free_ char *p = NULL, *v = NULL;
8f2d43a0 292
b4cccbc1 293 if (all_unified)
efdb0237 294 r = cg_get_path(controller, path, "memory.current", &p);
b4cccbc1
LP
295 else
296 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
8f2d43a0
LP
297 if (r < 0)
298 return r;
299
300 r = read_one_line_file(p, &v);
45d7a8bb
LP
301 if (r == -ENOENT)
302 return 0;
8f2d43a0
LP
303 if (r < 0)
304 return r;
305
306 r = safe_atou64(v, &g->memory);
8f2d43a0
LP
307 if (r < 0)
308 return r;
309
310 if (g->memory > 0)
311 g->memory_valid = true;
312
b4cccbc1
LP
313 } else if ((streq(controller, "io") && all_unified) ||
314 (streq(controller, "blkio") && !all_unified)) {
45d7a8bb
LP
315 _cleanup_fclose_ FILE *f = NULL;
316 _cleanup_free_ char *p = NULL;
8f2d43a0 317 uint64_t wr = 0, rd = 0;
45d7a8bb 318 nsec_t timestamp;
8f2d43a0 319
b4cccbc1 320 r = cg_get_path(controller, path, all_unified ? "io.stat" : "blkio.io_service_bytes", &p);
8f2d43a0
LP
321 if (r < 0)
322 return r;
323
324 f = fopen(p, "re");
45d7a8bb
LP
325 if (!f) {
326 if (errno == ENOENT)
327 return 0;
8f2d43a0 328 return -errno;
45d7a8bb 329 }
8f2d43a0
LP
330
331 for (;;) {
332 char line[LINE_MAX], *l;
333 uint64_t k, *q;
334
335 if (!fgets(line, sizeof(line), f))
336 break;
337
13c31542 338 /* Trim and skip the device */
8f2d43a0
LP
339 l = strstrip(line);
340 l += strcspn(l, WHITESPACE);
341 l += strspn(l, WHITESPACE);
342
b4cccbc1 343 if (all_unified) {
13c31542
TH
344 while (!isempty(l)) {
345 if (sscanf(l, "rbytes=%" SCNu64, &k))
346 rd += k;
347 else if (sscanf(l, "wbytes=%" SCNu64, &k))
348 wr += k;
349
350 l += strcspn(l, WHITESPACE);
351 l += strspn(l, WHITESPACE);
352 }
353 } else {
354 if (first_word(l, "Read")) {
355 l += 4;
356 q = &rd;
357 } else if (first_word(l, "Write")) {
358 l += 5;
359 q = &wr;
360 } else
361 continue;
362
363 l += strspn(l, WHITESPACE);
364 r = safe_atou64(l, &k);
365 if (r < 0)
366 continue;
367
368 *q += k;
369 }
8f2d43a0
LP
370 }
371
45d7a8bb 372 timestamp = now_nsec(CLOCK_MONOTONIC);
8f2d43a0
LP
373
374 if (g->io_iteration == iteration - 1) {
375 uint64_t x, yr, yw;
376
45d7a8bb
LP
377 x = (uint64_t) (timestamp - g->io_timestamp);
378 if (x < 1)
379 x = 1;
8f2d43a0 380
45d7a8bb
LP
381 if (rd > g->io_input)
382 yr = rd - g->io_input;
383 else
384 yr = 0;
385
386 if (wr > g->io_output)
387 yw = wr - g->io_output;
388 else
389 yw = 0;
8f2d43a0 390
45d7a8bb 391 if (yr > 0 || yw > 0) {
8f2d43a0
LP
392 g->io_input_bps = (yr * 1000000000ULL) / x;
393 g->io_output_bps = (yw * 1000000000ULL) / x;
394 g->io_valid = true;
8f2d43a0
LP
395 }
396 }
397
398 g->io_input = rd;
399 g->io_output = wr;
45d7a8bb 400 g->io_timestamp = timestamp;
8f2d43a0
LP
401 g->io_iteration = iteration;
402 }
403
3cb5beea
LP
404 if (ret)
405 *ret = g;
406
8f2d43a0
LP
407 return 0;
408}
409
410static int refresh_one(
411 const char *controller,
412 const char *path,
413 Hashmap *a,
414 Hashmap *b,
415 unsigned iteration,
3cb5beea
LP
416 unsigned depth,
417 Group **ret) {
8f2d43a0 418
45d7a8bb 419 _cleanup_closedir_ DIR *d = NULL;
80b2ab4b 420 Group *ours = NULL;
8f2d43a0
LP
421 int r;
422
423 assert(controller);
424 assert(path);
425 assert(a);
426
427 if (depth > arg_depth)
428 return 0;
429
3cb5beea 430 r = process(controller, path, a, b, iteration, &ours);
8f2d43a0
LP
431 if (r < 0)
432 return r;
433
434 r = cg_enumerate_subgroups(controller, path, &d);
45d7a8bb
LP
435 if (r == -ENOENT)
436 return 0;
437 if (r < 0)
8f2d43a0 438 return r;
8f2d43a0
LP
439
440 for (;;) {
45d7a8bb 441 _cleanup_free_ char *fn = NULL, *p = NULL;
3cb5beea 442 Group *child = NULL;
8f2d43a0
LP
443
444 r = cg_read_subgroup(d, &fn);
3cb5beea 445 if (r < 0)
45d7a8bb 446 return r;
3cb5beea
LP
447 if (r == 0)
448 break;
8f2d43a0 449
605405c6 450 p = strjoin(path, "/", fn);
45d7a8bb
LP
451 if (!p)
452 return -ENOMEM;
8f2d43a0
LP
453
454 path_kill_slashes(p);
455
3cb5beea 456 r = refresh_one(controller, p, a, b, iteration, depth + 1, &child);
8f2d43a0 457 if (r < 0)
45d7a8bb 458 return r;
3cb5beea
LP
459
460 if (arg_recursive &&
03a7b521 461 IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES) &&
3cb5beea
LP
462 child &&
463 child->n_tasks_valid &&
464 streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
465
466 /* Recursively sum up processes */
467
468 if (ours->n_tasks_valid)
469 ours->n_tasks += child->n_tasks;
470 else {
471 ours->n_tasks = child->n_tasks;
472 ours->n_tasks_valid = true;
473 }
474 }
8f2d43a0
LP
475 }
476
3cb5beea
LP
477 if (ret)
478 *ret = ours;
479
480 return 1;
8f2d43a0
LP
481}
482
03af6492 483static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) {
8f2d43a0
LP
484 int r;
485
486 assert(a);
487
3cb5beea 488 r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL);
66ebf6c0
TH
489 if (r < 0)
490 return r;
491 r = refresh_one("cpu", root, a, b, iteration, 0, NULL);
8f2d43a0 492 if (r < 0)
45d7a8bb 493 return r;
3cb5beea 494 r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL);
8f2d43a0 495 if (r < 0)
45d7a8bb 496 return r;
3cb5beea 497 r = refresh_one("memory", root, a, b, iteration, 0, NULL);
13c31542
TH
498 if (r < 0)
499 return r;
500 r = refresh_one("io", root, a, b, iteration, 0, NULL);
8f2d43a0 501 if (r < 0)
45d7a8bb 502 return r;
3cb5beea 503 r = refresh_one("blkio", root, a, b, iteration, 0, NULL);
03a7b521
LP
504 if (r < 0)
505 return r;
506 r = refresh_one("pids", root, a, b, iteration, 0, NULL);
63210a15 507 if (r < 0)
45d7a8bb
LP
508 return r;
509
63210a15 510 return 0;
8f2d43a0
LP
511}
512
9c6e3e1d
LP
513static const char *empty_to_slash(const char *p) {
514 return isempty(p) ? "/" : p;
515}
516
8f2d43a0
LP
517static int group_compare(const void*a, const void *b) {
518 const Group *x = *(Group**)a, *y = *(Group**)b;
519
3cb5beea 520 if (arg_order != ORDER_TASKS || arg_recursive) {
45d7a8bb 521 /* Let's make sure that the parent is always before
3cb5beea
LP
522 * the child. Except when ordering by tasks and
523 * recursive summing is off, since that is actually
524 * not accumulative for all children. */
45d7a8bb 525
9c6e3e1d 526 if (path_startswith(empty_to_slash(y->path), empty_to_slash(x->path)))
45d7a8bb 527 return -1;
9c6e3e1d 528 if (path_startswith(empty_to_slash(x->path), empty_to_slash(y->path)))
45d7a8bb
LP
529 return 1;
530 }
531
532 switch (arg_order) {
533
534 case ORDER_PATH:
535 break;
8f2d43a0 536
45d7a8bb 537 case ORDER_CPU:
1e913bcb
UTL
538 if (arg_cpu_type == CPU_PERCENT) {
539 if (x->cpu_valid && y->cpu_valid) {
540 if (x->cpu_fraction > y->cpu_fraction)
541 return -1;
542 else if (x->cpu_fraction < y->cpu_fraction)
543 return 1;
544 } else if (x->cpu_valid)
8f2d43a0 545 return -1;
1e913bcb 546 else if (y->cpu_valid)
8f2d43a0 547 return 1;
1e913bcb
UTL
548 } else {
549 if (x->cpu_usage > y->cpu_usage)
550 return -1;
551 else if (x->cpu_usage < y->cpu_usage)
552 return 1;
553 }
8f2d43a0 554
45d7a8bb 555 break;
8f2d43a0 556
45d7a8bb 557 case ORDER_TASKS:
8f2d43a0
LP
558 if (x->n_tasks_valid && y->n_tasks_valid) {
559 if (x->n_tasks > y->n_tasks)
560 return -1;
561 else if (x->n_tasks < y->n_tasks)
562 return 1;
563 } else if (x->n_tasks_valid)
564 return -1;
565 else if (y->n_tasks_valid)
566 return 1;
8f2d43a0 567
45d7a8bb
LP
568 break;
569
570 case ORDER_MEMORY:
8f2d43a0
LP
571 if (x->memory_valid && y->memory_valid) {
572 if (x->memory > y->memory)
573 return -1;
574 else if (x->memory < y->memory)
575 return 1;
576 } else if (x->memory_valid)
577 return -1;
578 else if (y->memory_valid)
579 return 1;
8f2d43a0 580
45d7a8bb
LP
581 break;
582
583 case ORDER_IO:
8f2d43a0
LP
584 if (x->io_valid && y->io_valid) {
585 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
586 return -1;
587 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
588 return 1;
589 } else if (x->io_valid)
590 return -1;
591 else if (y->io_valid)
592 return 1;
593 }
594
45d7a8bb 595 return path_compare(x->path, y->path);
8f2d43a0
LP
596}
597
dcd71990 598static void display(Hashmap *a) {
8f2d43a0
LP
599 Iterator i;
600 Group *g;
601 Group **array;
1e913bcb 602 signed path_columns;
510c4a0f 603 unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */
62b95b8b 604 char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)];
8f2d43a0
LP
605
606 assert(a);
607
ac96418b 608 if (!terminal_is_dumb())
1fc464f6 609 fputs(ANSI_HOME_CLEAR, stdout);
8f2d43a0
LP
610
611 array = alloca(sizeof(Group*) * hashmap_size(a));
612
613 HASHMAP_FOREACH(g, a, i)
614 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
615 array[n++] = g;
616
7ff7394d 617 qsort_safe(array, n, sizeof(Group*), group_compare);
8f2d43a0 618
1e913bcb
UTL
619 /* Find the longest names in one run */
620 for (j = 0; j < n; j++) {
621 unsigned cputlen, pathtlen;
62b95b8b 622
45d7a8bb 623 format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0);
62b95b8b 624 cputlen = strlen(buffer);
1e913bcb 625 maxtcpu = MAX(maxtcpu, cputlen);
45d7a8bb 626
1e913bcb
UTL
627 pathtlen = strlen(array[j]->path);
628 maxtpath = MAX(maxtpath, pathtlen);
629 }
630
631 if (arg_cpu_type == CPU_PERCENT)
d054f0a4 632 xsprintf(buffer, "%6s", "%CPU");
1e913bcb 633 else
d054f0a4 634 xsprintf(buffer, "%*s", maxtcpu, "CPU Time");
1e913bcb 635
ed757c0c
LP
636 rows = lines();
637 if (rows <= 10)
638 rows = 10;
8f2d43a0 639
1e913bcb 640 if (on_tty()) {
1fc464f6
LP
641 const char *on, *off;
642
62b95b8b 643 path_columns = columns() - 36 - strlen(buffer);
1e913bcb
UTL
644 if (path_columns < 10)
645 path_columns = 10;
646
1fc464f6
LP
647 on = ansi_highlight_underline();
648 off = ansi_underline();
649
650 printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n",
651 ansi_underline(),
652 arg_order == ORDER_PATH ? on : "", path_columns, "Control Group",
653 arg_order == ORDER_PATH ? off : "",
654 arg_order == ORDER_TASKS ? on : "", arg_count == COUNT_PIDS ? "Tasks" : arg_count == COUNT_USERSPACE_PROCESSES ? "Procs" : "Proc+",
655 arg_order == ORDER_TASKS ? off : "",
656 arg_order == ORDER_CPU ? on : "", buffer,
657 arg_order == ORDER_CPU ? off : "",
658 arg_order == ORDER_MEMORY ? on : "", "Memory",
659 arg_order == ORDER_MEMORY ? off : "",
660 arg_order == ORDER_IO ? on : "", "Input/s",
661 arg_order == ORDER_IO ? off : "",
662 arg_order == ORDER_IO ? on : "", "Output/s",
663 arg_order == ORDER_IO ? off : "",
664 ansi_normal());
1e913bcb
UTL
665 } else
666 path_columns = maxtpath;
8f2d43a0
LP
667
668 for (j = 0; j < n; j++) {
9660efb8
LP
669 _cleanup_free_ char *ellipsized = NULL;
670 const char *path;
8f2d43a0 671
08edf879 672 if (on_tty() && j + 6 > rows)
8f2d43a0
LP
673 break;
674
675 g = array[j];
676
9c6e3e1d 677 path = empty_to_slash(g->path);
9660efb8
LP
678 ellipsized = ellipsize(path, path_columns, 33);
679 printf("%-*s", path_columns, ellipsized ?: path);
8f2d43a0
LP
680
681 if (g->n_tasks_valid)
03a7b521 682 printf(" %7" PRIu64, g->n_tasks);
8f2d43a0
LP
683 else
684 fputs(" -", stdout);
685
62b95b8b 686 if (arg_cpu_type == CPU_PERCENT) {
1e913bcb
UTL
687 if (g->cpu_valid)
688 printf(" %6.1f", g->cpu_fraction*100);
689 else
690 fputs(" -", stdout);
62b95b8b 691 } else
45d7a8bb 692 printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
8f2d43a0 693
a2c9f631
CD
694 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory));
695 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps));
696 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps));
8f2d43a0
LP
697
698 putchar('\n');
699 }
8f2d43a0
LP
700}
701
601185b4 702static void help(void) {
308253c5 703 printf("%s [OPTIONS...] [CGROUP]\n\n"
8f2d43a0
LP
704 "Show top control groups by their resource usage.\n\n"
705 " -h --help Show this help\n"
45d7a8bb
LP
706 " --version Show package version\n"
707 " -p --order=path Order by path\n"
03a7b521 708 " -t --order=tasks Order by number of tasks/processes\n"
45d7a8bb
LP
709 " -c --order=cpu Order by CPU load (default)\n"
710 " -m --order=memory Order by memory load\n"
711 " -i --order=io Order by IO load\n"
a2c9f631 712 " -r --raw Provide raw (not human-readable) numbers\n"
45d7a8bb
LP
713 " --cpu=percentage Show CPU usage as percentage (default)\n"
714 " --cpu=time Show CPU usage as time\n"
03a7b521
LP
715 " -P Count userspace processes instead of tasks (excl. kernel)\n"
716 " -k Count all processes instead of tasks (incl. kernel)\n"
717 " --recursive=BOOL Sum up process count recursively\n"
1e913bcb 718 " -d --delay=DELAY Delay between updates\n"
a152771a 719 " -n --iterations=N Run for N iterations before exiting\n"
4fc9ffab 720 " -1 Shortcut for --iterations=1\n"
e66bb58b 721 " -b --batch Run in batch mode, accepting no input\n"
601185b4 722 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
96a6426f 723 " -M --machine= Show container\n"
601185b4 724 , program_invocation_short_name, arg_depth);
0d7e32fa
ZJS
725}
726
8f2d43a0
LP
727static int parse_argv(int argc, char *argv[]) {
728
729 enum {
0d7e32fa
ZJS
730 ARG_VERSION = 0x100,
731 ARG_DEPTH,
45d7a8bb
LP
732 ARG_CPU_TYPE,
733 ARG_ORDER,
3cb5beea 734 ARG_RECURSIVE,
8f2d43a0
LP
735 };
736
737 static const struct option options[] = {
3cb5beea
LP
738 { "help", no_argument, NULL, 'h' },
739 { "version", no_argument, NULL, ARG_VERSION },
740 { "delay", required_argument, NULL, 'd' },
741 { "iterations", required_argument, NULL, 'n' },
742 { "batch", no_argument, NULL, 'b' },
743 { "raw", no_argument, NULL, 'r' },
744 { "depth", required_argument, NULL, ARG_DEPTH },
745 { "cpu", optional_argument, NULL, ARG_CPU_TYPE },
746 { "order", required_argument, NULL, ARG_ORDER },
747 { "recursive", required_argument, NULL, ARG_RECURSIVE },
96a6426f 748 { "machine", required_argument, NULL, 'M' },
eb9da376 749 {}
8f2d43a0
LP
750 };
751
3cb5beea 752 int c, r;
8f2d43a0
LP
753
754 assert(argc >= 1);
755 assert(argv);
756
4fc9ffab 757 while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:1", options, NULL)) >= 0)
8f2d43a0
LP
758
759 switch (c) {
760
761 case 'h':
601185b4
ZJS
762 help();
763 return 0;
8f2d43a0 764
0d7e32fa 765 case ARG_VERSION:
3f6fd1ba 766 return version();
0d7e32fa 767
1e913bcb
UTL
768 case ARG_CPU_TYPE:
769 if (optarg) {
45d7a8bb 770 if (streq(optarg, "time"))
1e913bcb 771 arg_cpu_type = CPU_TIME;
45d7a8bb 772 else if (streq(optarg, "percentage"))
1e913bcb 773 arg_cpu_type = CPU_PERCENT;
45d7a8bb
LP
774 else {
775 log_error("Unknown argument to --cpu=: %s", optarg);
1e913bcb 776 return -EINVAL;
45d7a8bb
LP
777 }
778 } else
779 arg_cpu_type = CPU_TIME;
780
1e913bcb
UTL
781 break;
782
8f2d43a0
LP
783 case ARG_DEPTH:
784 r = safe_atou(optarg, &arg_depth);
ad078b41
LP
785 if (r < 0)
786 return log_error_errno(r, "Failed to parse depth parameter: %s", optarg);
8f2d43a0
LP
787
788 break;
789
790 case 'd':
7f602784 791 r = parse_sec(optarg, &arg_delay);
8f2d43a0 792 if (r < 0 || arg_delay <= 0) {
ad078b41 793 log_error("Failed to parse delay parameter: %s", optarg);
8f2d43a0
LP
794 return -EINVAL;
795 }
796
797 break;
798
a152771a
DS
799 case 'n':
800 r = safe_atou(optarg, &arg_iterations);
ad078b41
LP
801 if (r < 0)
802 return log_error_errno(r, "Failed to parse iterations parameter: %s", optarg);
a152771a
DS
803
804 break;
805
4fc9ffab
LP
806 case '1':
807 arg_iterations = 1;
808 break;
809
e66bb58b
DS
810 case 'b':
811 arg_batch = true;
812 break;
813
a2c9f631
CD
814 case 'r':
815 arg_raw = true;
816 break;
817
8f2d43a0
LP
818 case 'p':
819 arg_order = ORDER_PATH;
820 break;
821
822 case 't':
823 arg_order = ORDER_TASKS;
824 break;
825
826 case 'c':
827 arg_order = ORDER_CPU;
828 break;
829
830 case 'm':
831 arg_order = ORDER_MEMORY;
832 break;
833
834 case 'i':
835 arg_order = ORDER_IO;
836 break;
837
45d7a8bb
LP
838 case ARG_ORDER:
839 if (streq(optarg, "path"))
840 arg_order = ORDER_PATH;
841 else if (streq(optarg, "tasks"))
842 arg_order = ORDER_TASKS;
843 else if (streq(optarg, "cpu"))
844 arg_order = ORDER_CPU;
845 else if (streq(optarg, "memory"))
846 arg_order = ORDER_MEMORY;
847 else if (streq(optarg, "io"))
848 arg_order = ORDER_IO;
849 else {
850 log_error("Invalid argument to --order=: %s", optarg);
851 return -EINVAL;
852 }
853 break;
854
41ba8b6e 855 case 'k':
03a7b521
LP
856 arg_count = COUNT_ALL_PROCESSES;
857 break;
858
859 case 'P':
860 arg_count = COUNT_USERSPACE_PROCESSES;
41ba8b6e
LP
861 break;
862
3cb5beea
LP
863 case ARG_RECURSIVE:
864 r = parse_boolean(optarg);
ad078b41
LP
865 if (r < 0)
866 return log_error_errno(r, "Failed to parse --recursive= argument: %s", optarg);
3cb5beea
LP
867
868 arg_recursive = r;
6d9f40d5 869 arg_recursive_unset = r == 0;
3cb5beea
LP
870 break;
871
96a6426f
EV
872 case 'M':
873 arg_machine = optarg;
874 break;
875
8f2d43a0
LP
876 case '?':
877 return -EINVAL;
878
879 default:
eb9da376 880 assert_not_reached("Unhandled option");
8f2d43a0 881 }
8f2d43a0 882
d3e8277d 883 if (optind == argc - 1)
308253c5 884 arg_root = argv[optind];
d3e8277d 885 else if (optind < argc) {
8f2d43a0
LP
886 log_error("Too many arguments.");
887 return -EINVAL;
888 }
889
890 return 1;
891}
892
03a7b521
LP
893static const char* counting_what(void) {
894 if (arg_count == COUNT_PIDS)
895 return "tasks";
896 else if (arg_count == COUNT_ALL_PROCESSES)
897 return "all processes (incl. kernel)";
898 else
899 return "userspace processes (excl. kernel)";
900}
901
8f2d43a0
LP
902int main(int argc, char *argv[]) {
903 int r;
904 Hashmap *a = NULL, *b = NULL;
905 unsigned iteration = 0;
906 usec_t last_refresh = 0;
907 bool quit = false, immediate_refresh = false;
03af6492 908 _cleanup_free_ char *root = NULL;
03a7b521 909 CGroupMask mask;
8f2d43a0
LP
910
911 log_parse_environment();
912 log_open();
913
6d9f40d5
ZJS
914 r = parse_argv(argc, argv);
915 if (r <= 0)
916 goto finish;
917
03a7b521
LP
918 r = cg_mask_supported(&mask);
919 if (r < 0) {
920 log_error_errno(r, "Failed to determine supported controllers: %m");
921 goto finish;
922 }
923
924 arg_count = (mask & CGROUP_MASK_PIDS) ? COUNT_PIDS : COUNT_USERSPACE_PROCESSES;
925
6d9f40d5
ZJS
926 if (arg_recursive_unset && arg_count == COUNT_PIDS) {
927 log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
928 return -EINVAL;
929 }
8f2d43a0 930
d3e8277d 931 r = show_cgroup_get_path_and_warn(arg_machine, arg_root, &root);
03af6492
LP
932 if (r < 0) {
933 log_error_errno(r, "Failed to get root control group path: %m");
934 goto finish;
d3e8277d
ZJS
935 } else
936 log_debug("Cgroup path: %s", root);
03af6492 937
d5099efc
MS
938 a = hashmap_new(&string_hash_ops);
939 b = hashmap_new(&string_hash_ops);
8f2d43a0 940 if (!a || !b) {
0d0f0c50 941 r = log_oom();
8f2d43a0
LP
942 goto finish;
943 }
944
ed757c0c 945 signal(SIGWINCH, columns_lines_cache_reset);
28917d7d 946
45d7a8bb 947 if (arg_iterations == (unsigned) -1)
780fe62e 948 arg_iterations = on_tty() ? 0 : 1;
1e913bcb 949
8f2d43a0
LP
950 while (!quit) {
951 Hashmap *c;
952 usec_t t;
953 char key;
954 char h[FORMAT_TIMESPAN_MAX];
955
956 t = now(CLOCK_MONOTONIC);
957
958 if (t >= last_refresh + arg_delay || immediate_refresh) {
959
03af6492 960 r = refresh(root, a, b, iteration++);
dcd71990
LP
961 if (r < 0) {
962 log_error_errno(r, "Failed to refresh: %m");
8f2d43a0 963 goto finish;
dcd71990 964 }
8f2d43a0
LP
965
966 group_hashmap_clear(b);
967
968 c = a;
969 a = b;
970 b = c;
971
972 last_refresh = t;
973 immediate_refresh = false;
974 }
975
dcd71990 976 display(b);
8f2d43a0 977
a152771a
DS
978 if (arg_iterations && iteration >= arg_iterations)
979 break;
980
dcc7aacd
CD
981 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
982 fputs("\n", stdout);
983 fflush(stdout);
984
45d7a8bb 985 if (arg_batch)
dcd71990 986 (void) usleep(last_refresh + arg_delay - t);
45d7a8bb
LP
987 else {
988 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
e66bb58b
DS
989 if (r == -ETIMEDOUT)
990 continue;
991 if (r < 0) {
da927ba9 992 log_error_errno(r, "Couldn't read key: %m");
e66bb58b
DS
993 goto finish;
994 }
8f2d43a0
LP
995 }
996
dcc7aacd
CD
997 if (on_tty()) { /* TTY: Clear any user keystroke */
998 fputs("\r \r", stdout);
999 fflush(stdout);
1000 }
8f2d43a0 1001
e66bb58b
DS
1002 if (arg_batch)
1003 continue;
1004
8f2d43a0
LP
1005 switch (key) {
1006
1007 case ' ':
1008 immediate_refresh = true;
1009 break;
1010
1011 case 'q':
1012 quit = true;
1013 break;
1014
1015 case 'p':
1016 arg_order = ORDER_PATH;
1017 break;
1018
1019 case 't':
1020 arg_order = ORDER_TASKS;
1021 break;
1022
1023 case 'c':
1024 arg_order = ORDER_CPU;
1025 break;
1026
1027 case 'm':
1028 arg_order = ORDER_MEMORY;
1029 break;
1030
1031 case 'i':
1032 arg_order = ORDER_IO;
1033 break;
1034
2bfc1eda
ZJS
1035 case '%':
1036 arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
1037 break;
1038
7fcfb7ee 1039 case 'k':
03a7b521
LP
1040 arg_count = arg_count != COUNT_ALL_PROCESSES ? COUNT_ALL_PROCESSES : COUNT_PIDS;
1041 fprintf(stdout, "\nCounting: %s.", counting_what());
1042 fflush(stdout);
1043 sleep(1);
1044 break;
1045
1046 case 'P':
1047 arg_count = arg_count != COUNT_USERSPACE_PROCESSES ? COUNT_USERSPACE_PROCESSES : COUNT_PIDS;
1048 fprintf(stdout, "\nCounting: %s.", counting_what());
7fcfb7ee
LP
1049 fflush(stdout);
1050 sleep(1);
1051 break;
1052
1053 case 'r':
03a7b521
LP
1054 if (arg_count == COUNT_PIDS)
1055 fprintf(stdout, "\n\aCannot toggle recursive counting, not available in task counting mode.");
1056 else {
1057 arg_recursive = !arg_recursive;
1058 fprintf(stdout, "\nRecursive process counting: %s", yes_no(arg_recursive));
1059 }
7fcfb7ee
LP
1060 fflush(stdout);
1061 sleep(1);
1062 break;
1063
8f2d43a0
LP
1064 case '+':
1065 if (arg_delay < USEC_PER_SEC)
1066 arg_delay += USEC_PER_MSEC*250;
1067 else
1068 arg_delay += USEC_PER_SEC;
1069
2fa4092c 1070 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
8f2d43a0
LP
1071 fflush(stdout);
1072 sleep(1);
1073 break;
1074
1075 case '-':
1076 if (arg_delay <= USEC_PER_MSEC*500)
1077 arg_delay = USEC_PER_MSEC*250;
1078 else if (arg_delay < USEC_PER_MSEC*1250)
1079 arg_delay -= USEC_PER_MSEC*250;
1080 else
1081 arg_delay -= USEC_PER_SEC;
1082
2fa4092c 1083 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
8f2d43a0
LP
1084 fflush(stdout);
1085 sleep(1);
1086 break;
1087
1088 case '?':
1089 case 'h':
1fc464f6
LP
1090
1091#define ON ANSI_HIGHLIGHT
1092#define OFF ANSI_NORMAL
1093
8f2d43a0 1094 fprintf(stdout,
03a7b521 1095 "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks/procs; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n"
7fcfb7ee 1096 "\t<" ON "+" OFF "> Inc. delay; <" ON "-" OFF "> Dec. delay; <" ON "%%" OFF "> Toggle time; <" ON "SPACE" OFF "> Refresh\n"
03a7b521
LP
1097 "\t<" ON "P" OFF "> Toggle count userspace processes; <" ON "k" OFF "> Toggle count all processes\n"
1098 "\t<" ON "r" OFF "> Count processes recursively; <" ON "q" OFF "> Quit");
8f2d43a0
LP
1099 fflush(stdout);
1100 sleep(3);
1101 break;
1102
1103 default:
45d7a8bb
LP
1104 if (key < ' ')
1105 fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key);
1106 else
1107 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
8f2d43a0
LP
1108 fflush(stdout);
1109 sleep(1);
1110 break;
1111 }
1112 }
1113
8f2d43a0
LP
1114 r = 0;
1115
1116finish:
1117 group_hashmap_free(a);
1118 group_hashmap_free(b);
1119
dcd71990 1120 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
8f2d43a0 1121}