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