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