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