]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/cgtop/cgtop.c
util: unify line caching and column caching
[thirdparty/systemd.git] / src / cgtop / cgtop.c
CommitLineData
8f2d43a0
LP
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
5430f7f2
LP
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
8f2d43a0
LP
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
5430f7f2 16 Lesser General Public License for more details.
8f2d43a0 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
8f2d43a0
LP
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 <unistd.h>
26#include <alloca.h>
27#include <getopt.h>
28
9eb977db 29#include "path-util.h"
8f2d43a0
LP
30#include "util.h"
31#include "hashmap.h"
32#include "cgroup-util.h"
0d7e32fa 33#include "build.h"
8f2d43a0
LP
34
35typedef struct Group {
36 char *path;
37
38 bool n_tasks_valid:1;
39 bool cpu_valid:1;
40 bool memory_valid:1;
41 bool io_valid:1;
42
43 unsigned n_tasks;
44
45 unsigned cpu_iteration;
46 uint64_t cpu_usage;
47 struct timespec cpu_timestamp;
48 double cpu_fraction;
49
50 uint64_t memory;
51
52 unsigned io_iteration;
53 uint64_t io_input, io_output;
54 struct timespec io_timestamp;
55 uint64_t io_input_bps, io_output_bps;
56} Group;
57
30edf116 58static unsigned arg_depth = 3;
a152771a 59static unsigned arg_iterations = 0;
e66bb58b 60static bool arg_batch = false;
8f2d43a0
LP
61static usec_t arg_delay = 1*USEC_PER_SEC;
62
63static enum {
64 ORDER_PATH,
65 ORDER_TASKS,
66 ORDER_CPU,
67 ORDER_MEMORY,
68 ORDER_IO
69} arg_order = ORDER_CPU;
70
71static void group_free(Group *g) {
72 assert(g);
73
74 free(g->path);
75 free(g);
76}
77
78static void group_hashmap_clear(Hashmap *h) {
79 Group *g;
80
81 while ((g = hashmap_steal_first(h)))
82 group_free(g);
83}
84
85static void group_hashmap_free(Hashmap *h) {
86 group_hashmap_clear(h);
87 hashmap_free(h);
88}
89
90static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
91 Group *g;
92 int r;
93 FILE *f;
94 pid_t pid;
95 unsigned n;
96
97 assert(controller);
98 assert(path);
99 assert(a);
100
101 g = hashmap_get(a, path);
102 if (!g) {
103 g = hashmap_get(b, path);
104 if (!g) {
105 g = new0(Group, 1);
106 if (!g)
107 return -ENOMEM;
108
109 g->path = strdup(path);
110 if (!g->path) {
111 group_free(g);
112 return -ENOMEM;
113 }
114
115 r = hashmap_put(a, g->path, g);
116 if (r < 0) {
117 group_free(g);
118 return r;
119 }
120 } else {
121 assert_se(hashmap_move_one(a, b, path) == 0);
122 g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
123 }
124 }
125
126 /* Regardless which controller, let's find the maximum number
127 * of processes in any of it */
128
129 r = cg_enumerate_tasks(controller, path, &f);
130 if (r < 0)
131 return r;
132
133 n = 0;
134 while (cg_read_pid(f, &pid) > 0)
135 n++;
136 fclose(f);
137
138 if (n > 0) {
139 if (g->n_tasks_valid)
140 g->n_tasks = MAX(g->n_tasks, n);
141 else
142 g->n_tasks = n;
143
144 g->n_tasks_valid = true;
145 }
146
147 if (streq(controller, "cpuacct")) {
148 uint64_t new_usage;
149 char *p, *v;
150 struct timespec ts;
151
152 r = cg_get_path(controller, path, "cpuacct.usage", &p);
153 if (r < 0)
154 return r;
155
156 r = read_one_line_file(p, &v);
157 free(p);
158 if (r < 0)
159 return r;
160
161 r = safe_atou64(v, &new_usage);
162 free(v);
163 if (r < 0)
164 return r;
165
166 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
167
168 if (g->cpu_iteration == iteration - 1) {
169 uint64_t x, y;
170
171 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
172 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
173
174 y = new_usage - g->cpu_usage;
175
176 if (y > 0) {
177 g->cpu_fraction = (double) y / (double) x;
178 g->cpu_valid = true;
179 }
180 }
181
182 g->cpu_usage = new_usage;
183 g->cpu_timestamp = ts;
184 g->cpu_iteration = iteration;
185
186 } else if (streq(controller, "memory")) {
187 char *p, *v;
188
189 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
190 if (r < 0)
191 return r;
192
193 r = read_one_line_file(p, &v);
194 free(p);
195 if (r < 0)
196 return r;
197
198 r = safe_atou64(v, &g->memory);
199 free(v);
200 if (r < 0)
201 return r;
202
203 if (g->memory > 0)
204 g->memory_valid = true;
205
206 } else if (streq(controller, "blkio")) {
207 char *p;
208 uint64_t wr = 0, rd = 0;
209 struct timespec ts;
210
211 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
212 if (r < 0)
213 return r;
214
215 f = fopen(p, "re");
216 free(p);
217
218 if (!f)
219 return -errno;
220
221 for (;;) {
222 char line[LINE_MAX], *l;
223 uint64_t k, *q;
224
225 if (!fgets(line, sizeof(line), f))
226 break;
227
228 l = strstrip(line);
229 l += strcspn(l, WHITESPACE);
230 l += strspn(l, WHITESPACE);
231
232 if (first_word(l, "Read")) {
233 l += 4;
234 q = &rd;
235 } else if (first_word(l, "Write")) {
236 l += 5;
237 q = &wr;
238 } else
239 continue;
240
241 l += strspn(l, WHITESPACE);
242 r = safe_atou64(l, &k);
243 if (r < 0)
244 continue;
245
246 *q += k;
247 }
248
249 fclose(f);
250
251 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
252
253 if (g->io_iteration == iteration - 1) {
254 uint64_t x, yr, yw;
255
256 x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
257 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
258
259 yr = rd - g->io_input;
260 yw = wr - g->io_output;
261
262 if (yr > 0 || yw > 0) {
263 g->io_input_bps = (yr * 1000000000ULL) / x;
264 g->io_output_bps = (yw * 1000000000ULL) / x;
265 g->io_valid = true;
266
267 }
268 }
269
270 g->io_input = rd;
271 g->io_output = wr;
272 g->io_timestamp = ts;
273 g->io_iteration = iteration;
274 }
275
276 return 0;
277}
278
279static int refresh_one(
280 const char *controller,
281 const char *path,
282 Hashmap *a,
283 Hashmap *b,
284 unsigned iteration,
285 unsigned depth) {
286
287 DIR *d = NULL;
288 int r;
289
290 assert(controller);
291 assert(path);
292 assert(a);
293
294 if (depth > arg_depth)
295 return 0;
296
297 r = process(controller, path, a, b, iteration);
298 if (r < 0)
299 return r;
300
301 r = cg_enumerate_subgroups(controller, path, &d);
302 if (r < 0) {
2f29c419 303 if (r == -ENOENT)
8f2d43a0
LP
304 return 0;
305
306 return r;
307 }
308
309 for (;;) {
310 char *fn, *p;
311
312 r = cg_read_subgroup(d, &fn);
313 if (r <= 0)
314 goto finish;
315
b7def684 316 p = strjoin(path, "/", fn, NULL);
8f2d43a0
LP
317 free(fn);
318
319 if (!p) {
320 r = -ENOMEM;
321 goto finish;
322 }
323
324 path_kill_slashes(p);
325
326 r = refresh_one(controller, p, a, b, iteration, depth + 1);
327 free(p);
328
329 if (r < 0)
330 goto finish;
331 }
332
333finish:
334 if (d)
335 closedir(d);
336
337 return r;
338}
339
340static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
341 int r;
342
343 assert(a);
344
345 r = refresh_one("name=systemd", "/", a, b, iteration, 0);
346 if (r < 0)
63210a15
SL
347 if (r != -ENOENT)
348 return r;
8f2d43a0
LP
349 r = refresh_one("cpuacct", "/", a, b, iteration, 0);
350 if (r < 0)
63210a15
SL
351 if (r != -ENOENT)
352 return r;
8f2d43a0
LP
353 r = refresh_one("memory", "/", a, b, iteration, 0);
354 if (r < 0)
63210a15
SL
355 if (r != -ENOENT)
356 return r;
8f2d43a0 357
63210a15
SL
358 r = refresh_one("blkio", "/", a, b, iteration, 0);
359 if (r < 0)
360 if (r != -ENOENT)
361 return r;
362 return 0;
8f2d43a0
LP
363}
364
365static int group_compare(const void*a, const void *b) {
366 const Group *x = *(Group**)a, *y = *(Group**)b;
367
368 if (path_startswith(y->path, x->path))
369 return -1;
370 if (path_startswith(x->path, y->path))
371 return 1;
372
373 if (arg_order == ORDER_CPU) {
374 if (x->cpu_valid && y->cpu_valid) {
375
376 if (x->cpu_fraction > y->cpu_fraction)
377 return -1;
378 else if (x->cpu_fraction < y->cpu_fraction)
379 return 1;
380 } else if (x->cpu_valid)
381 return -1;
382 else if (y->cpu_valid)
383 return 1;
384 }
385
386 if (arg_order == ORDER_TASKS) {
387
388 if (x->n_tasks_valid && y->n_tasks_valid) {
389 if (x->n_tasks > y->n_tasks)
390 return -1;
391 else if (x->n_tasks < y->n_tasks)
392 return 1;
393 } else if (x->n_tasks_valid)
394 return -1;
395 else if (y->n_tasks_valid)
396 return 1;
397 }
398
399 if (arg_order == ORDER_MEMORY) {
400 if (x->memory_valid && y->memory_valid) {
401 if (x->memory > y->memory)
402 return -1;
403 else if (x->memory < y->memory)
404 return 1;
405 } else if (x->memory_valid)
406 return -1;
407 else if (y->memory_valid)
408 return 1;
409 }
410
411 if (arg_order == ORDER_IO) {
412 if (x->io_valid && y->io_valid) {
413 if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
414 return -1;
415 else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
416 return 1;
417 } else if (x->io_valid)
418 return -1;
419 else if (y->io_valid)
420 return 1;
421 }
422
423 return strcmp(x->path, y->path);
424}
425
426static int display(Hashmap *a) {
427 Iterator i;
428 Group *g;
429 Group **array;
11f96fac 430 unsigned rows, path_columns, n = 0, j;
8f2d43a0
LP
431
432 assert(a);
433
434 /* Set cursor to top left corner and clear screen */
435 fputs("\033[H"
436 "\033[2J", stdout);
437
438 array = alloca(sizeof(Group*) * hashmap_size(a));
439
440 HASHMAP_FOREACH(g, a, i)
441 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
442 array[n++] = g;
443
444 qsort(array, n, sizeof(Group*), group_compare);
445
ed757c0c
LP
446 rows = lines();
447 if (rows <= 10)
448 rows = 10;
8f2d43a0 449
28917d7d 450 path_columns = columns() - 42;
11f96fac
ZJS
451 if (path_columns < 10)
452 path_columns = 10;
453
454 printf("%s%-*s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
455 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_ON : "", path_columns, "Path",
456 arg_order == ORDER_PATH ? ANSI_HIGHLIGHT_OFF : "",
457 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_ON : "", "Tasks",
458 arg_order == ORDER_TASKS ? ANSI_HIGHLIGHT_OFF : "",
459 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_ON : "", "%CPU",
460 arg_order == ORDER_CPU ? ANSI_HIGHLIGHT_OFF : "",
461 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",
462 arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
463 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Input/s",
464 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "",
465 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_ON : "", "Output/s",
466 arg_order == ORDER_IO ? ANSI_HIGHLIGHT_OFF : "");
8f2d43a0
LP
467
468 for (j = 0; j < n; j++) {
469 char *p;
470 char m[FORMAT_BYTES_MAX];
471
472 if (j + 5 > rows)
473 break;
474
475 g = array[j];
476
11f96fac
ZJS
477 p = ellipsize(g->path, path_columns, 33);
478 printf("%-*s", path_columns, p ? p : g->path);
8f2d43a0
LP
479 free(p);
480
481 if (g->n_tasks_valid)
482 printf(" %7u", g->n_tasks);
483 else
484 fputs(" -", stdout);
485
486 if (g->cpu_valid)
487 printf(" %6.1f", g->cpu_fraction*100);
488 else
489 fputs(" -", stdout);
490
491 if (g->memory_valid)
492 printf(" %8s", format_bytes(m, sizeof(m), g->memory));
493 else
494 fputs(" -", stdout);
495
496 if (g->io_valid) {
497 printf(" %8s",
498 format_bytes(m, sizeof(m), g->io_input_bps));
499 printf(" %8s",
500 format_bytes(m, sizeof(m), g->io_output_bps));
501 } else
502 fputs(" - -", stdout);
503
504 putchar('\n');
505 }
506
507 return 0;
508}
509
510static void help(void) {
511
512 printf("%s [OPTIONS...]\n\n"
513 "Show top control groups by their resource usage.\n\n"
514 " -h --help Show this help\n"
0d7e32fa 515 " --version Print version and exit\n"
8f2d43a0
LP
516 " -p Order by path\n"
517 " -t Order by number of tasks\n"
518 " -c Order by CPU load\n"
519 " -m Order by memory load\n"
520 " -i Order by IO load\n"
521 " -d --delay=DELAY Specify delay\n"
a152771a 522 " -n --iterations=N Run for N iterations before exiting\n"
e66bb58b 523 " -b --batch Run in batch mode, accepting no input\n"
caa94887 524 " --depth=DEPTH Maximum traversal depth (default: 2)\n",
8f2d43a0
LP
525 program_invocation_short_name);
526}
527
0d7e32fa
ZJS
528static void version(void) {
529 puts(PACKAGE_STRING " cgtop");
530}
531
8f2d43a0
LP
532static int parse_argv(int argc, char *argv[]) {
533
534 enum {
0d7e32fa
ZJS
535 ARG_VERSION = 0x100,
536 ARG_DEPTH,
8f2d43a0
LP
537 };
538
539 static const struct option options[] = {
0d7e32fa
ZJS
540 { "help", no_argument, NULL, 'h' },
541 { "version", no_argument, NULL, ARG_VERSION },
542 { "delay", required_argument, NULL, 'd' },
543 { "iterations", required_argument, NULL, 'n' },
544 { "batch", no_argument, NULL, 'b' },
545 { "depth", required_argument, NULL, ARG_DEPTH },
546 { NULL, 0, NULL, 0 }
8f2d43a0
LP
547 };
548
549 int c;
550 int r;
551
552 assert(argc >= 1);
553 assert(argv);
554
e66bb58b 555 while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0) {
8f2d43a0
LP
556
557 switch (c) {
558
559 case 'h':
560 help();
561 return 0;
562
0d7e32fa
ZJS
563 case ARG_VERSION:
564 version();
565 return 0;
566
8f2d43a0
LP
567 case ARG_DEPTH:
568 r = safe_atou(optarg, &arg_depth);
569 if (r < 0) {
570 log_error("Failed to parse depth parameter.");
571 return -EINVAL;
572 }
573
574 break;
575
576 case 'd':
577 r = parse_usec(optarg, &arg_delay);
578 if (r < 0 || arg_delay <= 0) {
579 log_error("Failed to parse delay parameter.");
580 return -EINVAL;
581 }
582
583 break;
584
a152771a
DS
585 case 'n':
586 r = safe_atou(optarg, &arg_iterations);
587 if (r < 0) {
588 log_error("Failed to parse iterations parameter.");
589 return -EINVAL;
590 }
591
592 break;
593
e66bb58b
DS
594 case 'b':
595 arg_batch = true;
596 break;
597
8f2d43a0
LP
598 case 'p':
599 arg_order = ORDER_PATH;
600 break;
601
602 case 't':
603 arg_order = ORDER_TASKS;
604 break;
605
606 case 'c':
607 arg_order = ORDER_CPU;
608 break;
609
610 case 'm':
611 arg_order = ORDER_MEMORY;
612 break;
613
614 case 'i':
615 arg_order = ORDER_IO;
616 break;
617
618 case '?':
619 return -EINVAL;
620
621 default:
622 log_error("Unknown option code %c", c);
623 return -EINVAL;
624 }
625 }
626
627 if (optind < argc) {
628 log_error("Too many arguments.");
629 return -EINVAL;
630 }
631
632 return 1;
633}
634
635int main(int argc, char *argv[]) {
636 int r;
637 Hashmap *a = NULL, *b = NULL;
638 unsigned iteration = 0;
639 usec_t last_refresh = 0;
640 bool quit = false, immediate_refresh = false;
641
642 log_parse_environment();
643 log_open();
644
645 r = parse_argv(argc, argv);
646 if (r <= 0)
647 goto finish;
648
649 a = hashmap_new(string_hash_func, string_compare_func);
650 b = hashmap_new(string_hash_func, string_compare_func);
651 if (!a || !b) {
0d0f0c50 652 r = log_oom();
8f2d43a0
LP
653 goto finish;
654 }
655
ed757c0c 656 signal(SIGWINCH, columns_lines_cache_reset);
28917d7d 657
8f2d43a0
LP
658 while (!quit) {
659 Hashmap *c;
660 usec_t t;
661 char key;
662 char h[FORMAT_TIMESPAN_MAX];
663
664 t = now(CLOCK_MONOTONIC);
665
666 if (t >= last_refresh + arg_delay || immediate_refresh) {
667
668 r = refresh(a, b, iteration++);
669 if (r < 0)
670 goto finish;
671
672 group_hashmap_clear(b);
673
674 c = a;
675 a = b;
676 b = c;
677
678 last_refresh = t;
679 immediate_refresh = false;
680 }
681
682 r = display(b);
683 if (r < 0)
684 goto finish;
685
a152771a
DS
686 if (arg_iterations && iteration >= arg_iterations)
687 break;
688
e66bb58b
DS
689 if (arg_batch) {
690 usleep(last_refresh + arg_delay - t);
691 } else {
692 r = read_one_char(stdin, &key,
693 last_refresh + arg_delay - t, NULL);
694 if (r == -ETIMEDOUT)
695 continue;
696 if (r < 0) {
697 log_error("Couldn't read key: %s", strerror(-r));
698 goto finish;
699 }
8f2d43a0
LP
700 }
701
702 fputs("\r \r", stdout);
703 fflush(stdout);
704
e66bb58b
DS
705 if (arg_batch)
706 continue;
707
8f2d43a0
LP
708 switch (key) {
709
710 case ' ':
711 immediate_refresh = true;
712 break;
713
714 case 'q':
715 quit = true;
716 break;
717
718 case 'p':
719 arg_order = ORDER_PATH;
720 break;
721
722 case 't':
723 arg_order = ORDER_TASKS;
724 break;
725
726 case 'c':
727 arg_order = ORDER_CPU;
728 break;
729
730 case 'm':
731 arg_order = ORDER_MEMORY;
732 break;
733
734 case 'i':
735 arg_order = ORDER_IO;
736 break;
737
738 case '+':
739 if (arg_delay < USEC_PER_SEC)
740 arg_delay += USEC_PER_MSEC*250;
741 else
742 arg_delay += USEC_PER_SEC;
743
744 fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
745 fflush(stdout);
746 sleep(1);
747 break;
748
749 case '-':
750 if (arg_delay <= USEC_PER_MSEC*500)
751 arg_delay = USEC_PER_MSEC*250;
752 else if (arg_delay < USEC_PER_MSEC*1250)
753 arg_delay -= USEC_PER_MSEC*250;
754 else
755 arg_delay -= USEC_PER_SEC;
756
757 fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
758 fflush(stdout);
759 sleep(1);
760 break;
761
762 case '?':
763 case 'h':
764 fprintf(stdout,
765 "\t<" ANSI_HIGHLIGHT_ON "P" ANSI_HIGHLIGHT_OFF "> By path; <" ANSI_HIGHLIGHT_ON "T" ANSI_HIGHLIGHT_OFF "> By tasks; <" ANSI_HIGHLIGHT_ON "C" ANSI_HIGHLIGHT_OFF "> By CPU; <" ANSI_HIGHLIGHT_ON "M" ANSI_HIGHLIGHT_OFF "> By memory; <" ANSI_HIGHLIGHT_ON "I" ANSI_HIGHLIGHT_OFF "> By I/O\n"
766 "\t<" ANSI_HIGHLIGHT_ON "Q" ANSI_HIGHLIGHT_OFF "> Quit; <" ANSI_HIGHLIGHT_ON "+" ANSI_HIGHLIGHT_OFF "> Increase delay; <" ANSI_HIGHLIGHT_ON "-" ANSI_HIGHLIGHT_OFF "> Decrease delay; <" ANSI_HIGHLIGHT_ON "SPACE" ANSI_HIGHLIGHT_OFF "> Refresh");
767 fflush(stdout);
768 sleep(3);
769 break;
770
771 default:
772 fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
773 fflush(stdout);
774 sleep(1);
775 break;
776 }
777 }
778
779 log_info("Exiting.");
780
781 r = 0;
782
783finish:
784 group_hashmap_free(a);
785 group_hashmap_free(b);
786
14212119
SL
787 if (r < 0) {
788 log_error("Exiting with failure: %s", strerror(-r));
789 return EXIT_FAILURE;
790 }
791
792 return EXIT_SUCCESS;
8f2d43a0 793}