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