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