]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cgtop/cgtop.c
util-lib: split string parsing related calls from util.[ch] into parse-util.[ch]
[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 <alloca.h>
23 #include <errno.h>
24 #include <getopt.h>
25 #include <signal.h>
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30
31 #include "sd-bus.h"
32
33 #include "bus-error.h"
34 #include "bus-util.h"
35 #include "cgroup-util.h"
36 #include "fd-util.h"
37 #include "fileio.h"
38 #include "hashmap.h"
39 #include "parse-util.h"
40 #include "path-util.h"
41 #include "process-util.h"
42 #include "terminal-util.h"
43 #include "unit-name.h"
44 #include "util.h"
45
46 typedef struct Group {
47 char *path;
48
49 bool n_tasks_valid:1;
50 bool cpu_valid:1;
51 bool memory_valid:1;
52 bool io_valid:1;
53
54 uint64_t n_tasks;
55
56 unsigned cpu_iteration;
57 nsec_t cpu_usage;
58 nsec_t cpu_timestamp;
59 double cpu_fraction;
60
61 uint64_t memory;
62
63 unsigned io_iteration;
64 uint64_t io_input, io_output;
65 nsec_t io_timestamp;
66 uint64_t io_input_bps, io_output_bps;
67 } Group;
68
69 static unsigned arg_depth = 3;
70 static unsigned arg_iterations = (unsigned) -1;
71 static bool arg_batch = false;
72 static bool arg_raw = false;
73 static usec_t arg_delay = 1*USEC_PER_SEC;
74 static char* arg_machine = NULL;
75
76 enum {
77 COUNT_PIDS,
78 COUNT_USERSPACE_PROCESSES,
79 COUNT_ALL_PROCESSES,
80 } arg_count = COUNT_PIDS;
81 static bool arg_recursive = true;
82
83 static enum {
84 ORDER_PATH,
85 ORDER_TASKS,
86 ORDER_CPU,
87 ORDER_MEMORY,
88 ORDER_IO,
89 } arg_order = ORDER_CPU;
90
91 static enum {
92 CPU_PERCENT,
93 CPU_TIME,
94 } arg_cpu_type = CPU_PERCENT;
95
96 static void group_free(Group *g) {
97 assert(g);
98
99 free(g->path);
100 free(g);
101 }
102
103 static void group_hashmap_clear(Hashmap *h) {
104 Group *g;
105
106 while ((g = hashmap_steal_first(h)))
107 group_free(g);
108 }
109
110 static void group_hashmap_free(Hashmap *h) {
111 group_hashmap_clear(h);
112 hashmap_free(h);
113 }
114
115 static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, uint64_t t) {
116 if (!is_valid)
117 return "-";
118 if (arg_raw) {
119 snprintf(buf, l, "%jd", t);
120 return buf;
121 }
122 return format_bytes(buf, l, t);
123 }
124
125 static int process(
126 const char *controller,
127 const char *path,
128 Hashmap *a,
129 Hashmap *b,
130 unsigned iteration,
131 Group **ret) {
132
133 Group *g;
134 int r;
135
136 assert(controller);
137 assert(path);
138 assert(a);
139
140 g = hashmap_get(a, path);
141 if (!g) {
142 g = hashmap_get(b, path);
143 if (!g) {
144 g = new0(Group, 1);
145 if (!g)
146 return -ENOMEM;
147
148 g->path = strdup(path);
149 if (!g->path) {
150 group_free(g);
151 return -ENOMEM;
152 }
153
154 r = hashmap_put(a, g->path, g);
155 if (r < 0) {
156 group_free(g);
157 return r;
158 }
159 } else {
160 r = hashmap_move_one(a, b, path);
161 if (r < 0)
162 return r;
163
164 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
165 }
166 }
167
168 if (streq(controller, SYSTEMD_CGROUP_CONTROLLER) && IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES)) {
169 _cleanup_fclose_ FILE *f = NULL;
170 pid_t pid;
171
172 r = cg_enumerate_processes(controller, path, &f);
173 if (r == -ENOENT)
174 return 0;
175 if (r < 0)
176 return r;
177
178 g->n_tasks = 0;
179 while (cg_read_pid(f, &pid) > 0) {
180
181 if (arg_count == COUNT_USERSPACE_PROCESSES && is_kernel_thread(pid) > 0)
182 continue;
183
184 g->n_tasks++;
185 }
186
187 if (g->n_tasks > 0)
188 g->n_tasks_valid = true;
189
190 } else if (streq(controller, "pids") && arg_count == COUNT_PIDS) {
191 _cleanup_free_ char *p = NULL, *v = NULL;
192
193 r = cg_get_path(controller, path, "pids.current", &p);
194 if (r < 0)
195 return r;
196
197 r = read_one_line_file(p, &v);
198 if (r == -ENOENT)
199 return 0;
200 if (r < 0)
201 return r;
202
203 r = safe_atou64(v, &g->n_tasks);
204 if (r < 0)
205 return r;
206
207 if (g->n_tasks > 0)
208 g->n_tasks_valid = true;
209
210 } else if (streq(controller, "cpuacct") && cg_unified() <= 0) {
211 _cleanup_free_ char *p = NULL, *v = NULL;
212 uint64_t new_usage;
213 nsec_t timestamp;
214
215 r = cg_get_path(controller, path, "cpuacct.usage", &p);
216 if (r < 0)
217 return r;
218
219 r = read_one_line_file(p, &v);
220 if (r == -ENOENT)
221 return 0;
222 if (r < 0)
223 return r;
224
225 r = safe_atou64(v, &new_usage);
226 if (r < 0)
227 return r;
228
229 timestamp = now_nsec(CLOCK_MONOTONIC);
230
231 if (g->cpu_iteration == iteration - 1 &&
232 (nsec_t) new_usage > g->cpu_usage) {
233
234 nsec_t x, y;
235
236 x = timestamp - g->cpu_timestamp;
237 if (x < 1)
238 x = 1;
239
240 y = (nsec_t) new_usage - g->cpu_usage;
241 g->cpu_fraction = (double) y / (double) x;
242 g->cpu_valid = true;
243 }
244
245 g->cpu_usage = (nsec_t) new_usage;
246 g->cpu_timestamp = timestamp;
247 g->cpu_iteration = iteration;
248
249 } else if (streq(controller, "memory")) {
250 _cleanup_free_ char *p = NULL, *v = NULL;
251
252 if (cg_unified() <= 0)
253 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
254 else
255 r = cg_get_path(controller, path, "memory.current", &p);
256 if (r < 0)
257 return r;
258
259 r = read_one_line_file(p, &v);
260 if (r == -ENOENT)
261 return 0;
262 if (r < 0)
263 return r;
264
265 r = safe_atou64(v, &g->memory);
266 if (r < 0)
267 return r;
268
269 if (g->memory > 0)
270 g->memory_valid = true;
271
272 } else if (streq(controller, "blkio") && cg_unified() <= 0) {
273 _cleanup_fclose_ FILE *f = NULL;
274 _cleanup_free_ char *p = NULL;
275 uint64_t wr = 0, rd = 0;
276 nsec_t timestamp;
277
278 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
279 if (r < 0)
280 return r;
281
282 f = fopen(p, "re");
283 if (!f) {
284 if (errno == ENOENT)
285 return 0;
286 return -errno;
287 }
288
289 for (;;) {
290 char line[LINE_MAX], *l;
291 uint64_t k, *q;
292
293 if (!fgets(line, sizeof(line), f))
294 break;
295
296 l = strstrip(line);
297 l += strcspn(l, WHITESPACE);
298 l += strspn(l, WHITESPACE);
299
300 if (first_word(l, "Read")) {
301 l += 4;
302 q = &rd;
303 } else if (first_word(l, "Write")) {
304 l += 5;
305 q = &wr;
306 } else
307 continue;
308
309 l += strspn(l, WHITESPACE);
310 r = safe_atou64(l, &k);
311 if (r < 0)
312 continue;
313
314 *q += k;
315 }
316
317 timestamp = now_nsec(CLOCK_MONOTONIC);
318
319 if (g->io_iteration == iteration - 1) {
320 uint64_t x, yr, yw;
321
322 x = (uint64_t) (timestamp - g->io_timestamp);
323 if (x < 1)
324 x = 1;
325
326 if (rd > g->io_input)
327 yr = rd - g->io_input;
328 else
329 yr = 0;
330
331 if (wr > g->io_output)
332 yw = wr - g->io_output;
333 else
334 yw = 0;
335
336 if (yr > 0 || yw > 0) {
337 g->io_input_bps = (yr * 1000000000ULL) / x;
338 g->io_output_bps = (yw * 1000000000ULL) / x;
339 g->io_valid = true;
340 }
341 }
342
343 g->io_input = rd;
344 g->io_output = wr;
345 g->io_timestamp = timestamp;
346 g->io_iteration = iteration;
347 }
348
349 if (ret)
350 *ret = g;
351
352 return 0;
353 }
354
355 static int refresh_one(
356 const char *controller,
357 const char *path,
358 Hashmap *a,
359 Hashmap *b,
360 unsigned iteration,
361 unsigned depth,
362 Group **ret) {
363
364 _cleanup_closedir_ DIR *d = NULL;
365 Group *ours;
366 int r;
367
368 assert(controller);
369 assert(path);
370 assert(a);
371
372 if (depth > arg_depth)
373 return 0;
374
375 r = process(controller, path, a, b, iteration, &ours);
376 if (r < 0)
377 return r;
378
379 r = cg_enumerate_subgroups(controller, path, &d);
380 if (r == -ENOENT)
381 return 0;
382 if (r < 0)
383 return r;
384
385 for (;;) {
386 _cleanup_free_ char *fn = NULL, *p = NULL;
387 Group *child = NULL;
388
389 r = cg_read_subgroup(d, &fn);
390 if (r < 0)
391 return r;
392 if (r == 0)
393 break;
394
395 p = strjoin(path, "/", fn, NULL);
396 if (!p)
397 return -ENOMEM;
398
399 path_kill_slashes(p);
400
401 r = refresh_one(controller, p, a, b, iteration, depth + 1, &child);
402 if (r < 0)
403 return r;
404
405 if (arg_recursive &&
406 IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES) &&
407 child &&
408 child->n_tasks_valid &&
409 streq(controller, SYSTEMD_CGROUP_CONTROLLER)) {
410
411 /* Recursively sum up processes */
412
413 if (ours->n_tasks_valid)
414 ours->n_tasks += child->n_tasks;
415 else {
416 ours->n_tasks = child->n_tasks;
417 ours->n_tasks_valid = true;
418 }
419 }
420 }
421
422 if (ret)
423 *ret = ours;
424
425 return 1;
426 }
427
428 static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) {
429 int r;
430
431 assert(a);
432
433 r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL);
434 if (r < 0)
435 return r;
436 r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL);
437 if (r < 0)
438 return r;
439 r = refresh_one("memory", root, a, b, iteration, 0, NULL);
440 if (r < 0)
441 return r;
442 r = refresh_one("blkio", root, a, b, iteration, 0, NULL);
443 if (r < 0)
444 return r;
445 r = refresh_one("pids", root, a, b, iteration, 0, NULL);
446 if (r < 0)
447 return r;
448
449 return 0;
450 }
451
452 static int group_compare(const void*a, const void *b) {
453 const Group *x = *(Group**)a, *y = *(Group**)b;
454
455 if (arg_order != ORDER_TASKS || arg_recursive) {
456 /* Let's make sure that the parent is always before
457 * the child. Except when ordering by tasks and
458 * recursive summing is off, since that is actually
459 * not accumulative for all children. */
460
461 if (path_startswith(y->path, x->path))
462 return -1;
463 if (path_startswith(x->path, y->path))
464 return 1;
465 }
466
467 switch (arg_order) {
468
469 case ORDER_PATH:
470 break;
471
472 case ORDER_CPU:
473 if (arg_cpu_type == CPU_PERCENT) {
474 if (x->cpu_valid && y->cpu_valid) {
475 if (x->cpu_fraction > y->cpu_fraction)
476 return -1;
477 else if (x->cpu_fraction < y->cpu_fraction)
478 return 1;
479 } else if (x->cpu_valid)
480 return -1;
481 else if (y->cpu_valid)
482 return 1;
483 } else {
484 if (x->cpu_usage > y->cpu_usage)
485 return -1;
486 else if (x->cpu_usage < y->cpu_usage)
487 return 1;
488 }
489
490 break;
491
492 case ORDER_TASKS:
493 if (x->n_tasks_valid && y->n_tasks_valid) {
494 if (x->n_tasks > y->n_tasks)
495 return -1;
496 else if (x->n_tasks < y->n_tasks)
497 return 1;
498 } else if (x->n_tasks_valid)
499 return -1;
500 else if (y->n_tasks_valid)
501 return 1;
502
503 break;
504
505 case ORDER_MEMORY:
506 if (x->memory_valid && y->memory_valid) {
507 if (x->memory > y->memory)
508 return -1;
509 else if (x->memory < y->memory)
510 return 1;
511 } else if (x->memory_valid)
512 return -1;
513 else if (y->memory_valid)
514 return 1;
515
516 break;
517
518 case ORDER_IO:
519 if (x->io_valid && y->io_valid) {
520 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_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
523 return 1;
524 } else if (x->io_valid)
525 return -1;
526 else if (y->io_valid)
527 return 1;
528 }
529
530 return path_compare(x->path, y->path);
531 }
532
533 static void display(Hashmap *a) {
534 Iterator i;
535 Group *g;
536 Group **array;
537 signed path_columns;
538 unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */
539 char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)];
540
541 assert(a);
542
543 if (on_tty())
544 fputs(ANSI_HOME_CLEAR, stdout);
545
546 array = alloca(sizeof(Group*) * hashmap_size(a));
547
548 HASHMAP_FOREACH(g, a, i)
549 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
550 array[n++] = g;
551
552 qsort_safe(array, n, sizeof(Group*), group_compare);
553
554 /* Find the longest names in one run */
555 for (j = 0; j < n; j++) {
556 unsigned cputlen, pathtlen;
557
558 format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0);
559 cputlen = strlen(buffer);
560 maxtcpu = MAX(maxtcpu, cputlen);
561
562 pathtlen = strlen(array[j]->path);
563 maxtpath = MAX(maxtpath, pathtlen);
564 }
565
566 if (arg_cpu_type == CPU_PERCENT)
567 snprintf(buffer, sizeof(buffer), "%6s", "%CPU");
568 else
569 snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time");
570
571 rows = lines();
572 if (rows <= 10)
573 rows = 10;
574
575 if (on_tty()) {
576 const char *on, *off;
577
578 path_columns = columns() - 36 - strlen(buffer);
579 if (path_columns < 10)
580 path_columns = 10;
581
582 on = ansi_highlight_underline();
583 off = ansi_underline();
584
585 printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n",
586 ansi_underline(),
587 arg_order == ORDER_PATH ? on : "", path_columns, "Control Group",
588 arg_order == ORDER_PATH ? off : "",
589 arg_order == ORDER_TASKS ? on : "", arg_count == COUNT_PIDS ? "Tasks" : arg_count == COUNT_USERSPACE_PROCESSES ? "Procs" : "Proc+",
590 arg_order == ORDER_TASKS ? off : "",
591 arg_order == ORDER_CPU ? on : "", buffer,
592 arg_order == ORDER_CPU ? off : "",
593 arg_order == ORDER_MEMORY ? on : "", "Memory",
594 arg_order == ORDER_MEMORY ? off : "",
595 arg_order == ORDER_IO ? on : "", "Input/s",
596 arg_order == ORDER_IO ? off : "",
597 arg_order == ORDER_IO ? on : "", "Output/s",
598 arg_order == ORDER_IO ? off : "",
599 ansi_normal());
600 } else
601 path_columns = maxtpath;
602
603 for (j = 0; j < n; j++) {
604 _cleanup_free_ char *ellipsized = NULL;
605 const char *path;
606
607 if (on_tty() && j + 6 > rows)
608 break;
609
610 g = array[j];
611
612 path = isempty(g->path) ? "/" : g->path;
613 ellipsized = ellipsize(path, path_columns, 33);
614 printf("%-*s", path_columns, ellipsized ?: path);
615
616 if (g->n_tasks_valid)
617 printf(" %7" PRIu64, g->n_tasks);
618 else
619 fputs(" -", stdout);
620
621 if (arg_cpu_type == CPU_PERCENT) {
622 if (g->cpu_valid)
623 printf(" %6.1f", g->cpu_fraction*100);
624 else
625 fputs(" -", stdout);
626 } else
627 printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
628
629 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory));
630 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps));
631 printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps));
632
633 putchar('\n');
634 }
635 }
636
637 static void help(void) {
638 printf("%s [OPTIONS...]\n\n"
639 "Show top control groups by their resource usage.\n\n"
640 " -h --help Show this help\n"
641 " --version Show package version\n"
642 " -p --order=path Order by path\n"
643 " -t --order=tasks Order by number of tasks/processes\n"
644 " -c --order=cpu Order by CPU load (default)\n"
645 " -m --order=memory Order by memory load\n"
646 " -i --order=io Order by IO load\n"
647 " -r --raw Provide raw (not human-readable) numbers\n"
648 " --cpu=percentage Show CPU usage as percentage (default)\n"
649 " --cpu=time Show CPU usage as time\n"
650 " -P Count userspace processes instead of tasks (excl. kernel)\n"
651 " -k Count all processes instead of tasks (incl. kernel)\n"
652 " --recursive=BOOL Sum up process count recursively\n"
653 " -d --delay=DELAY Delay between updates\n"
654 " -n --iterations=N Run for N iterations before exiting\n"
655 " -b --batch Run in batch mode, accepting no input\n"
656 " --depth=DEPTH Maximum traversal depth (default: %u)\n"
657 " -M --machine= Show container\n"
658 , program_invocation_short_name, arg_depth);
659 }
660
661 static int parse_argv(int argc, char *argv[]) {
662
663 enum {
664 ARG_VERSION = 0x100,
665 ARG_DEPTH,
666 ARG_CPU_TYPE,
667 ARG_ORDER,
668 ARG_RECURSIVE,
669 };
670
671 static const struct option options[] = {
672 { "help", no_argument, NULL, 'h' },
673 { "version", no_argument, NULL, ARG_VERSION },
674 { "delay", required_argument, NULL, 'd' },
675 { "iterations", required_argument, NULL, 'n' },
676 { "batch", no_argument, NULL, 'b' },
677 { "raw", no_argument, NULL, 'r' },
678 { "depth", required_argument, NULL, ARG_DEPTH },
679 { "cpu", optional_argument, NULL, ARG_CPU_TYPE },
680 { "order", required_argument, NULL, ARG_ORDER },
681 { "recursive", required_argument, NULL, ARG_RECURSIVE },
682 { "machine", required_argument, NULL, 'M' },
683 {}
684 };
685
686 bool recursive_unset = false;
687 int c, r;
688
689 assert(argc >= 1);
690 assert(argv);
691
692 while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:", options, NULL)) >= 0)
693
694 switch (c) {
695
696 case 'h':
697 help();
698 return 0;
699
700 case ARG_VERSION:
701 return version();
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_connect_transport_systemd(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 }