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