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