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