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