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