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