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