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