]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cgtop/cgtop.c
Merge pull request #8150 from poettering/memory-accounting-by-default
[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 int group_compare(const void*a, const void *b) {
510 const Group *x = *(Group**)a, *y = *(Group**)b;
511
512 if (arg_order != ORDER_TASKS || arg_recursive) {
513 /* Let's make sure that the parent is always before
514 * the child. Except when ordering by tasks and
515 * recursive summing is off, since that is actually
516 * not accumulative for all children. */
517
518 if (path_startswith(y->path, x->path))
519 return -1;
520 if (path_startswith(x->path, y->path))
521 return 1;
522 }
523
524 switch (arg_order) {
525
526 case ORDER_PATH:
527 break;
528
529 case ORDER_CPU:
530 if (arg_cpu_type == CPU_PERCENT) {
531 if (x->cpu_valid && y->cpu_valid) {
532 if (x->cpu_fraction > y->cpu_fraction)
533 return -1;
534 else if (x->cpu_fraction < y->cpu_fraction)
535 return 1;
536 } else if (x->cpu_valid)
537 return -1;
538 else if (y->cpu_valid)
539 return 1;
540 } else {
541 if (x->cpu_usage > y->cpu_usage)
542 return -1;
543 else if (x->cpu_usage < y->cpu_usage)
544 return 1;
545 }
546
547 break;
548
549 case ORDER_TASKS:
550 if (x->n_tasks_valid && y->n_tasks_valid) {
551 if (x->n_tasks > y->n_tasks)
552 return -1;
553 else if (x->n_tasks < y->n_tasks)
554 return 1;
555 } else if (x->n_tasks_valid)
556 return -1;
557 else if (y->n_tasks_valid)
558 return 1;
559
560 break;
561
562 case ORDER_MEMORY:
563 if (x->memory_valid && y->memory_valid) {
564 if (x->memory > y->memory)
565 return -1;
566 else if (x->memory < y->memory)
567 return 1;
568 } else if (x->memory_valid)
569 return -1;
570 else if (y->memory_valid)
571 return 1;
572
573 break;
574
575 case ORDER_IO:
576 if (x->io_valid && y->io_valid) {
577 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
578 return -1;
579 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
580 return 1;
581 } else if (x->io_valid)
582 return -1;
583 else if (y->io_valid)
584 return 1;
585 }
586
587 return path_compare(x->path, y->path);
588 }
589
590 static void display(Hashmap *a) {
591 Iterator i;
592 Group *g;
593 Group **array;
594 signed path_columns;
595 unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */
596 char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)];
597
598 assert(a);
599
600 if (!terminal_is_dumb())
601 fputs(ANSI_HOME_CLEAR, stdout);
602
603 array = alloca(sizeof(Group*) * hashmap_size(a));
604
605 HASHMAP_FOREACH(g, a, i)
606 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
607 array[n++] = g;
608
609 qsort_safe(array, n, sizeof(Group*), group_compare);
610
611 /* Find the longest names in one run */
612 for (j = 0; j < n; j++) {
613 unsigned cputlen, pathtlen;
614
615 format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0);
616 cputlen = strlen(buffer);
617 maxtcpu = MAX(maxtcpu, cputlen);
618
619 pathtlen = strlen(array[j]->path);
620 maxtpath = MAX(maxtpath, pathtlen);
621 }
622
623 if (arg_cpu_type == CPU_PERCENT)
624 xsprintf(buffer, "%6s", "%CPU");
625 else
626 xsprintf(buffer, "%*s", maxtcpu, "CPU Time");
627
628 rows = lines();
629 if (rows <= 10)
630 rows = 10;
631
632 if (on_tty()) {
633 const char *on, *off;
634
635 path_columns = columns() - 36 - strlen(buffer);
636 if (path_columns < 10)
637 path_columns = 10;
638
639 on = ansi_highlight_underline();
640 off = ansi_underline();
641
642 printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n",
643 ansi_underline(),
644 arg_order == ORDER_PATH ? on : "", path_columns, "Control Group",
645 arg_order == ORDER_PATH ? off : "",
646 arg_order == ORDER_TASKS ? on : "", arg_count == COUNT_PIDS ? "Tasks" : arg_count == COUNT_USERSPACE_PROCESSES ? "Procs" : "Proc+",
647 arg_order == ORDER_TASKS ? off : "",
648 arg_order == ORDER_CPU ? on : "", buffer,
649 arg_order == ORDER_CPU ? off : "",
650 arg_order == ORDER_MEMORY ? on : "", "Memory",
651 arg_order == ORDER_MEMORY ? off : "",
652 arg_order == ORDER_IO ? on : "", "Input/s",
653 arg_order == ORDER_IO ? off : "",
654 arg_order == ORDER_IO ? on : "", "Output/s",
655 arg_order == ORDER_IO ? off : "",
656 ansi_normal());
657 } else
658 path_columns = maxtpath;
659
660 for (j = 0; j < n; j++) {
661 _cleanup_free_ char *ellipsized = NULL;
662 const char *path;
663
664 if (on_tty() && j + 6 > rows)
665 break;
666
667 g = array[j];
668
669 path = isempty(g->path) ? "/" : g->path;
670 ellipsized = ellipsize(path, path_columns, 33);
671 printf("%-*s", path_columns, ellipsized ?: path);
672
673 if (g->n_tasks_valid)
674 printf(" %7" PRIu64, g->n_tasks);
675 else
676 fputs(" -", stdout);
677
678 if (arg_cpu_type == CPU_PERCENT) {
679 if (g->cpu_valid)
680 printf(" %6.1f", g->cpu_fraction*100);
681 else
682 fputs(" -", stdout);
683 } else
684 printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
685
686 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory));
687 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps));
688 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps));
689
690 putchar('\n');
691 }
692 }
693
694 static void help(void) {
695 printf("%s [OPTIONS...] [CGROUP]\n\n"
696 "Show top control groups by their resource usage.\n\n"
697 " -h --help Show this help\n"
698 " --version Show package version\n"
699 " -p --order=path Order by path\n"
700 " -t --order=tasks Order by number of tasks/processes\n"
701 " -c --order=cpu Order by CPU load (default)\n"
702 " -m --order=memory Order by memory load\n"
703 " -i --order=io Order by IO load\n"
704 " -r --raw Provide raw (not human-readable) numbers\n"
705 " --cpu=percentage Show CPU usage as percentage (default)\n"
706 " --cpu=time Show CPU usage as time\n"
707 " -P Count userspace processes instead of tasks (excl. kernel)\n"
708 " -k Count all processes instead of tasks (incl. kernel)\n"
709 " --recursive=BOOL Sum up process count recursively\n"
710 " -d --delay=DELAY Delay between updates\n"
711 " -n --iterations=N Run for N iterations before exiting\n"
712 " -b --batch Run in batch mode, accepting no input\n"
713 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
714 " -M --machine= Show container\n"
715 , program_invocation_short_name, arg_depth);
716 }
717
718 static int parse_argv(int argc, char *argv[]) {
719
720 enum {
721 ARG_VERSION = 0x100,
722 ARG_DEPTH,
723 ARG_CPU_TYPE,
724 ARG_ORDER,
725 ARG_RECURSIVE,
726 };
727
728 static const struct option options[] = {
729 { "help", no_argument, NULL, 'h' },
730 { "version", no_argument, NULL, ARG_VERSION },
731 { "delay", required_argument, NULL, 'd' },
732 { "iterations", required_argument, NULL, 'n' },
733 { "batch", no_argument, NULL, 'b' },
734 { "raw", no_argument, NULL, 'r' },
735 { "depth", required_argument, NULL, ARG_DEPTH },
736 { "cpu", optional_argument, NULL, ARG_CPU_TYPE },
737 { "order", required_argument, NULL, ARG_ORDER },
738 { "recursive", required_argument, NULL, ARG_RECURSIVE },
739 { "machine", required_argument, NULL, 'M' },
740 {}
741 };
742
743 int c, r;
744
745 assert(argc >= 1);
746 assert(argv);
747
748 while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:", options, NULL)) >= 0)
749
750 switch (c) {
751
752 case 'h':
753 help();
754 return 0;
755
756 case ARG_VERSION:
757 return version();
758
759 case ARG_CPU_TYPE:
760 if (optarg) {
761 if (streq(optarg, "time"))
762 arg_cpu_type = CPU_TIME;
763 else if (streq(optarg, "percentage"))
764 arg_cpu_type = CPU_PERCENT;
765 else {
766 log_error("Unknown argument to --cpu=: %s", optarg);
767 return -EINVAL;
768 }
769 } else
770 arg_cpu_type = CPU_TIME;
771
772 break;
773
774 case ARG_DEPTH:
775 r = safe_atou(optarg, &arg_depth);
776 if (r < 0) {
777 log_error("Failed to parse depth parameter.");
778 return -EINVAL;
779 }
780
781 break;
782
783 case 'd':
784 r = parse_sec(optarg, &arg_delay);
785 if (r < 0 || arg_delay <= 0) {
786 log_error("Failed to parse delay parameter.");
787 return -EINVAL;
788 }
789
790 break;
791
792 case 'n':
793 r = safe_atou(optarg, &arg_iterations);
794 if (r < 0) {
795 log_error("Failed to parse iterations parameter.");
796 return -EINVAL;
797 }
798
799 break;
800
801 case 'b':
802 arg_batch = true;
803 break;
804
805 case 'r':
806 arg_raw = true;
807 break;
808
809 case 'p':
810 arg_order = ORDER_PATH;
811 break;
812
813 case 't':
814 arg_order = ORDER_TASKS;
815 break;
816
817 case 'c':
818 arg_order = ORDER_CPU;
819 break;
820
821 case 'm':
822 arg_order = ORDER_MEMORY;
823 break;
824
825 case 'i':
826 arg_order = ORDER_IO;
827 break;
828
829 case ARG_ORDER:
830 if (streq(optarg, "path"))
831 arg_order = ORDER_PATH;
832 else if (streq(optarg, "tasks"))
833 arg_order = ORDER_TASKS;
834 else if (streq(optarg, "cpu"))
835 arg_order = ORDER_CPU;
836 else if (streq(optarg, "memory"))
837 arg_order = ORDER_MEMORY;
838 else if (streq(optarg, "io"))
839 arg_order = ORDER_IO;
840 else {
841 log_error("Invalid argument to --order=: %s", optarg);
842 return -EINVAL;
843 }
844 break;
845
846 case 'k':
847 arg_count = COUNT_ALL_PROCESSES;
848 break;
849
850 case 'P':
851 arg_count = COUNT_USERSPACE_PROCESSES;
852 break;
853
854 case ARG_RECURSIVE:
855 r = parse_boolean(optarg);
856 if (r < 0) {
857 log_error("Failed to parse --recursive= argument: %s", optarg);
858 return r;
859 }
860
861 arg_recursive = r;
862 arg_recursive_unset = r == 0;
863 break;
864
865 case 'M':
866 arg_machine = optarg;
867 break;
868
869 case '?':
870 return -EINVAL;
871
872 default:
873 assert_not_reached("Unhandled option");
874 }
875
876 if (optind == argc - 1)
877 arg_root = argv[optind];
878 else if (optind < argc) {
879 log_error("Too many arguments.");
880 return -EINVAL;
881 }
882
883 return 1;
884 }
885
886 static const char* counting_what(void) {
887 if (arg_count == COUNT_PIDS)
888 return "tasks";
889 else if (arg_count == COUNT_ALL_PROCESSES)
890 return "all processes (incl. kernel)";
891 else
892 return "userspace processes (excl. kernel)";
893 }
894
895 int main(int argc, char *argv[]) {
896 int r;
897 Hashmap *a = NULL, *b = NULL;
898 unsigned iteration = 0;
899 usec_t last_refresh = 0;
900 bool quit = false, immediate_refresh = false;
901 _cleanup_free_ char *root = NULL;
902 CGroupMask mask;
903
904 log_parse_environment();
905 log_open();
906
907 r = parse_argv(argc, argv);
908 if (r <= 0)
909 goto finish;
910
911 r = cg_mask_supported(&mask);
912 if (r < 0) {
913 log_error_errno(r, "Failed to determine supported controllers: %m");
914 goto finish;
915 }
916
917 arg_count = (mask & CGROUP_MASK_PIDS) ? COUNT_PIDS : COUNT_USERSPACE_PROCESSES;
918
919 if (arg_recursive_unset && arg_count == COUNT_PIDS) {
920 log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
921 return -EINVAL;
922 }
923
924 r = show_cgroup_get_path_and_warn(arg_machine, arg_root, &root);
925 if (r < 0) {
926 log_error_errno(r, "Failed to get root control group path: %m");
927 goto finish;
928 } else
929 log_debug("Cgroup path: %s", root);
930
931 a = hashmap_new(&path_hash_ops);
932 b = hashmap_new(&path_hash_ops);
933 if (!a || !b) {
934 r = log_oom();
935 goto finish;
936 }
937
938 signal(SIGWINCH, columns_lines_cache_reset);
939
940 if (arg_iterations == (unsigned) -1)
941 arg_iterations = on_tty() ? 0 : 1;
942
943 while (!quit) {
944 Hashmap *c;
945 usec_t t;
946 char key;
947 char h[FORMAT_TIMESPAN_MAX];
948
949 t = now(CLOCK_MONOTONIC);
950
951 if (t >= last_refresh + arg_delay || immediate_refresh) {
952
953 r = refresh(root, a, b, iteration++);
954 if (r < 0) {
955 log_error_errno(r, "Failed to refresh: %m");
956 goto finish;
957 }
958
959 group_hashmap_clear(b);
960
961 c = a;
962 a = b;
963 b = c;
964
965 last_refresh = t;
966 immediate_refresh = false;
967 }
968
969 display(b);
970
971 if (arg_iterations && iteration >= arg_iterations)
972 break;
973
974 if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
975 fputs("\n", stdout);
976 fflush(stdout);
977
978 if (arg_batch)
979 (void) usleep(last_refresh + arg_delay - t);
980 else {
981 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
982 if (r == -ETIMEDOUT)
983 continue;
984 if (r < 0) {
985 log_error_errno(r, "Couldn't read key: %m");
986 goto finish;
987 }
988 }
989
990 if (on_tty()) { /* TTY: Clear any user keystroke */
991 fputs("\r \r", stdout);
992 fflush(stdout);
993 }
994
995 if (arg_batch)
996 continue;
997
998 switch (key) {
999
1000 case ' ':
1001 immediate_refresh = true;
1002 break;
1003
1004 case 'q':
1005 quit = true;
1006 break;
1007
1008 case 'p':
1009 arg_order = ORDER_PATH;
1010 break;
1011
1012 case 't':
1013 arg_order = ORDER_TASKS;
1014 break;
1015
1016 case 'c':
1017 arg_order = ORDER_CPU;
1018 break;
1019
1020 case 'm':
1021 arg_order = ORDER_MEMORY;
1022 break;
1023
1024 case 'i':
1025 arg_order = ORDER_IO;
1026 break;
1027
1028 case '%':
1029 arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
1030 break;
1031
1032 case 'k':
1033 arg_count = arg_count != COUNT_ALL_PROCESSES ? COUNT_ALL_PROCESSES : COUNT_PIDS;
1034 fprintf(stdout, "\nCounting: %s.", counting_what());
1035 fflush(stdout);
1036 sleep(1);
1037 break;
1038
1039 case 'P':
1040 arg_count = arg_count != COUNT_USERSPACE_PROCESSES ? COUNT_USERSPACE_PROCESSES : COUNT_PIDS;
1041 fprintf(stdout, "\nCounting: %s.", counting_what());
1042 fflush(stdout);
1043 sleep(1);
1044 break;
1045
1046 case 'r':
1047 if (arg_count == COUNT_PIDS)
1048 fprintf(stdout, "\n\aCannot toggle recursive counting, not available in task counting mode.");
1049 else {
1050 arg_recursive = !arg_recursive;
1051 fprintf(stdout, "\nRecursive process counting: %s", yes_no(arg_recursive));
1052 }
1053 fflush(stdout);
1054 sleep(1);
1055 break;
1056
1057 case '+':
1058 if (arg_delay < USEC_PER_SEC)
1059 arg_delay += USEC_PER_MSEC*250;
1060 else
1061 arg_delay += USEC_PER_SEC;
1062
1063 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
1064 fflush(stdout);
1065 sleep(1);
1066 break;
1067
1068 case '-':
1069 if (arg_delay <= USEC_PER_MSEC*500)
1070 arg_delay = USEC_PER_MSEC*250;
1071 else if (arg_delay < USEC_PER_MSEC*1250)
1072 arg_delay -= USEC_PER_MSEC*250;
1073 else
1074 arg_delay -= USEC_PER_SEC;
1075
1076 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
1077 fflush(stdout);
1078 sleep(1);
1079 break;
1080
1081 case '?':
1082 case 'h':
1083
1084 #define ON ANSI_HIGHLIGHT
1085 #define OFF ANSI_NORMAL
1086
1087 fprintf(stdout,
1088 "\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"
1089 "\t<" ON "+" OFF "> Inc. delay; <" ON "-" OFF "> Dec. delay; <" ON "%%" OFF "> Toggle time; <" ON "SPACE" OFF "> Refresh\n"
1090 "\t<" ON "P" OFF "> Toggle count userspace processes; <" ON "k" OFF "> Toggle count all processes\n"
1091 "\t<" ON "r" OFF "> Count processes recursively; <" ON "q" OFF "> Quit");
1092 fflush(stdout);
1093 sleep(3);
1094 break;
1095
1096 default:
1097 if (key < ' ')
1098 fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key);
1099 else
1100 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
1101 fflush(stdout);
1102 sleep(1);
1103 break;
1104 }
1105 }
1106
1107 r = 0;
1108
1109 finish:
1110 group_hashmap_free(a);
1111 group_hashmap_free(b);
1112
1113 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1114 }