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