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