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