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