]> git.proxmox.com Git - mirror_lxc.git/blob - src/lxc/tools/lxc_top.c
caps: fix includes
[mirror_lxc.git] / src / lxc / tools / lxc_top.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE 1
5 #endif
6 #define __STDC_FORMAT_MACROS /* Required for PRIu64 to work. */
7 #include <errno.h>
8 #include <inttypes.h>
9 #include <signal.h>
10 #include <stdbool.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/epoll.h>
16 #include <sys/ioctl.h>
17 #include <sys/time.h>
18 #include <termios.h>
19 #include <unistd.h>
20
21 #include <lxc/lxccontainer.h>
22
23 #include "arguments.h"
24 #include "config.h"
25 #include "mainloop.h"
26 #include "utils.h"
27
28 #define USER_HZ 100
29 #define ESC "\033"
30 #define TERMCLEAR ESC "[H" ESC "[J"
31 #define TERMNORM ESC "[0m"
32 #define TERMBOLD ESC "[1m"
33 #define TERMRVRS ESC "[7m"
34
35 struct blkio_stats {
36 uint64_t read;
37 uint64_t write;
38 uint64_t total;
39 };
40
41 struct stats {
42 uint64_t mem_used;
43 uint64_t mem_limit;
44 uint64_t memsw_used;
45 uint64_t memsw_limit;
46 uint64_t kmem_used;
47 uint64_t kmem_limit;
48 uint64_t cpu_use_nanos;
49 uint64_t cpu_use_user;
50 uint64_t cpu_use_sys;
51 struct blkio_stats io_service_bytes;
52 struct blkio_stats io_serviced;
53 };
54
55 struct container_stats {
56 struct lxc_container *c;
57 struct stats *stats;
58 };
59
60 static int batch = 0;
61 static int delay_set = 0;
62 static int delay = 3;
63 static char sort_by = 'n';
64 static int sort_reverse = 0;
65 static struct termios oldtios;
66 static struct container_stats *container_stats = NULL;
67 static int ct_alloc_cnt = 0;
68
69 static int my_parser(struct lxc_arguments *args, int c, char *arg)
70 {
71 switch (c) {
72 case 'd':
73 delay_set = 1;
74 if (lxc_safe_int(arg, &delay) < 0)
75 return -1;
76 break;
77 case 'b':
78 batch = 1;
79 break;
80 case 's':
81 sort_by = arg[0];
82 break;
83 case 'r':
84 sort_reverse = 1;
85 break;
86 }
87
88 return 0;
89 }
90
91 static const struct option my_longopts[] = {
92 {"delay", required_argument, 0, 'd'},
93 {"batch", no_argument, 0, 'b'},
94 {"sort", required_argument, 0, 's'},
95 {"reverse", no_argument, 0, 'r'},
96 LXC_COMMON_OPTIONS
97 };
98
99 static struct lxc_arguments my_args = {
100 .progname = "lxc-top",
101 .help = "\
102 \n\
103 \n\
104 lxc-top monitors the state of the active containers\n\
105 \n\
106 Options :\n\
107 -d, --delay delay in seconds between refreshes (default: 3.0)\n\
108 -b, --batch output designed to capture to a file\n\
109 -s, --sort sort by [n,c,b,m] (default: n) where\n\
110 n = Name\n\
111 c = CPU use\n\
112 b = Block I/O use\n\
113 m = Memory use\n\
114 s = Memory + Swap use\n\
115 k = Kernel memory use\n\
116 -r, --reverse sort in reverse (descending) order\n",
117 .name = ".*",
118 .options = my_longopts,
119 .parser = my_parser,
120 .checker = NULL,
121 .lxcpath_additional = -1,
122 };
123
124 static void stdin_tios_restore(void)
125 {
126 (void)tcsetattr(0, TCSAFLUSH, &oldtios);
127 fprintf(stderr, "\n");
128 }
129
130 static int stdin_tios_setup(void)
131 {
132 struct termios newtios;
133
134 if (!isatty(0)) {
135 fprintf(stderr, "stdin is not a tty\n");
136 return -1;
137 }
138
139 if (tcgetattr(0, &oldtios)) {
140 fprintf(stderr, "Failed to get current terminal settings\n");
141 return -1;
142 }
143
144 newtios = oldtios;
145
146 /* turn off echo and line buffering */
147 newtios.c_iflag &= ~IGNBRK;
148 newtios.c_iflag &= BRKINT;
149 newtios.c_lflag &= ~(ECHO|ICANON);
150 newtios.c_cc[VMIN] = 1;
151 newtios.c_cc[VTIME] = 0;
152
153 if (tcsetattr(0, TCSAFLUSH, &newtios)) {
154 fprintf(stderr, "Failed to set new terminal settings\n");
155 return -1;
156 }
157
158 return 0;
159 }
160
161 static int stdin_tios_rows(void)
162 {
163 struct winsize wsz;
164
165 if (isatty(0) && ioctl(0, TIOCGWINSZ, &wsz) == 0)
166 return wsz.ws_row;
167
168 return 25;
169 }
170
171 static void sig_handler(int sig)
172 {
173 exit(EXIT_SUCCESS);
174 }
175
176 static void size_humanize(unsigned long long val, char *buf, size_t bufsz)
177 {
178 int ret;
179
180 if (val > 1 << 30) {
181 ret = snprintf(buf, bufsz, "%u.%2.2u GiB",
182 (unsigned int)(val >> 30),
183 (unsigned int)(val & ((1 << 30) - 1)) / 10737419);
184 } else if (val > 1 << 20) {
185 unsigned int x = val + 5243; /* for rounding */
186 ret = snprintf(buf, bufsz, "%u.%2.2u MiB",
187 x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20);
188 } else if (val > 1 << 10) {
189 unsigned int x = val + 5; /* for rounding */
190 ret = snprintf(buf, bufsz, "%u.%2.2u KiB",
191 x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10);
192 } else {
193 ret = snprintf(buf, bufsz, "%3u.00 ", (unsigned int)val);
194 }
195
196 if (ret < 0 || (size_t)ret >= bufsz)
197 fprintf(stderr, "Failed to create string\n");
198 }
199
200 static uint64_t stat_get_int(struct lxc_container *c, const char *item)
201 {
202 char buf[80];
203 int len;
204 uint64_t val;
205
206 len = c->get_cgroup_item(c, item, buf, sizeof(buf));
207 if (len <= 0) {
208 fprintf(stderr, "Unable to read cgroup item %s\n", item);
209 return 0;
210 }
211
212 val = strtoull(buf, NULL, 0);
213 return val;
214 }
215
216 static uint64_t stat_match_get_int(struct lxc_container *c, const char *item,
217 const char *match, int column)
218 {
219 char buf[4096];
220 int i,j,len;
221 uint64_t val = 0;
222 char **lines, **cols;
223 size_t matchlen;
224
225 len = c->get_cgroup_item(c, item, buf, sizeof(buf));
226 if (len <= 0) {
227 fprintf(stderr, "Unable to read cgroup item %s\n", item);
228 goto out;
229 }
230
231 lines = lxc_string_split_and_trim(buf, '\n');
232 if (!lines)
233 goto out;
234
235 matchlen = strlen(match);
236 for (i = 0; lines[i]; i++) {
237 if (strncmp(lines[i], match, matchlen) == 0) {
238 cols = lxc_string_split_and_trim(lines[i], ' ');
239 if (!cols)
240 goto err1;
241
242 for (j = 0; cols[j]; j++) {
243 if (j == column) {
244 val = strtoull(cols[j], NULL, 0);
245 break;
246 }
247 }
248
249 lxc_free_array((void **)cols, free);
250 break;
251 }
252 }
253
254 err1:
255 lxc_free_array((void **)lines, free);
256
257 out:
258 return val;
259 }
260
261 /*
262 examples:
263 blkio.throttle.io_serviced
264 8:0 Read 4259
265 8:0 Write 835
266 8:0 Sync 292
267 8:0 Async 4802
268 8:0 Total 5094
269 Total 5094
270
271 blkio.throttle.io_service_bytes
272 8:0 Read 110309376
273 8:0 Write 39018496
274 8:0 Sync 2818048
275 8:0 Async 146509824
276 8:0 Total 149327872
277 Total 149327872
278 */
279 static void stat_get_blk_stats(struct lxc_container *c, const char *item,
280 struct blkio_stats *stats) {
281 char buf[4096];
282 int i, len;
283 char **lines, **cols;
284
285 len = c->get_cgroup_item(c, item, buf, sizeof(buf));
286 if (len <= 0 || (size_t)len >= sizeof(buf)) {
287 fprintf(stderr, "Unable to read cgroup item %s\n", item);
288 return;
289 }
290
291 lines = lxc_string_split_and_trim(buf, '\n');
292 if (!lines)
293 return;
294
295 memset(stats, 0, sizeof(struct blkio_stats));
296
297 for (i = 0; lines[i]; i++) {
298 cols = lxc_string_split_and_trim(lines[i], ' ');
299 if (!cols)
300 goto out;
301
302 if (strncmp(cols[1], "Read", strlen(cols[1])) == 0)
303 stats->read += strtoull(cols[2], NULL, 0);
304 else if (strncmp(cols[1], "Write", strlen(cols[1])) == 0)
305 stats->write += strtoull(cols[2], NULL, 0);
306
307 if (strncmp(cols[0], "Total", strlen(cols[0])) == 0)
308 stats->total = strtoull(cols[1], NULL, 0);
309
310 lxc_free_array((void **)cols, free);
311 }
312
313 out:
314 lxc_free_array((void **)lines, free);
315 return;
316 }
317
318 static void stats_get(struct lxc_container *c, struct container_stats *ct, struct stats *total)
319 {
320 ct->c = c;
321 ct->stats->mem_used = stat_get_int(c, "memory.usage_in_bytes");
322 ct->stats->mem_limit = stat_get_int(c, "memory.limit_in_bytes");
323 ct->stats->memsw_used = stat_get_int(c, "memory.memsw.usage_in_bytes");
324 ct->stats->memsw_limit = stat_get_int(c, "memory.memsw.limit_in_bytes");
325 ct->stats->kmem_used = stat_get_int(c, "memory.kmem.usage_in_bytes");
326 ct->stats->kmem_limit = stat_get_int(c, "memory.kmem.limit_in_bytes");
327 ct->stats->cpu_use_nanos = stat_get_int(c, "cpuacct.usage");
328 ct->stats->cpu_use_user = stat_match_get_int(c, "cpuacct.stat", "user", 1);
329 ct->stats->cpu_use_sys = stat_match_get_int(c, "cpuacct.stat", "system", 1);
330
331 stat_get_blk_stats(c, "blkio.throttle.io_service_bytes", &ct->stats->io_service_bytes);
332 stat_get_blk_stats(c, "blkio.throttle.io_serviced", &ct->stats->io_serviced);
333
334 if (total) {
335 total->mem_used = total->mem_used + ct->stats->mem_used;
336 total->mem_limit = total->mem_limit + ct->stats->mem_limit;
337 total->memsw_used = total->memsw_used + ct->stats->memsw_used;
338 total->memsw_limit = total->memsw_limit + ct->stats->memsw_limit;
339 total->kmem_used = total->kmem_used + ct->stats->kmem_used;
340 total->kmem_limit = total->kmem_limit + ct->stats->kmem_limit;
341 total->cpu_use_nanos = total->cpu_use_nanos + ct->stats->cpu_use_nanos;
342 total->cpu_use_user = total->cpu_use_user + ct->stats->cpu_use_user;
343 total->cpu_use_sys = total->cpu_use_sys + ct->stats->cpu_use_sys;
344 total->io_service_bytes.total += ct->stats->io_service_bytes.total;
345 total->io_service_bytes.read += ct->stats->io_service_bytes.read;
346 total->io_service_bytes.write += ct->stats->io_service_bytes.write;
347 }
348 }
349
350 static void stats_print_header(struct stats *stats)
351 {
352 printf(TERMRVRS TERMBOLD);
353 printf("%-18s %12s %12s %12s %36s %10s", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem");
354
355 if (stats->memsw_used > 0)
356 printf(" %10s", "MemSw");
357
358 if (stats->kmem_used > 0)
359 printf(" %10s", "KMem");
360 printf("\n");
361
362 printf("%-18s %12s %12s %12s %36s %10s", "Name", "Used", "Sys", "User", "Total(Read/Write)", "Used");
363
364 if (stats->memsw_used > 0)
365 printf(" %10s", "Used");
366
367 if (stats->kmem_used > 0)
368 printf(" %10s", "Used");
369
370 printf("\n");
371 printf(TERMNORM);
372 }
373
374 static void stats_print(const char *name, const struct stats *stats,
375 const struct stats *total)
376 {
377 char iosb_str[63];
378 char iosb_total_str[20];
379 char iosb_read_str[20];
380 char iosb_write_str[20];
381 char mem_used_str[20];
382 char memsw_used_str[20];
383 char kmem_used_str[20];
384 struct timeval time_val;
385 unsigned long long time_ms;
386 int ret;
387
388 if (!batch) {
389 size_humanize(stats->io_service_bytes.total, iosb_total_str, sizeof(iosb_total_str));
390 size_humanize(stats->io_service_bytes.read, iosb_read_str, sizeof(iosb_read_str));
391 size_humanize(stats->io_service_bytes.write, iosb_write_str, sizeof(iosb_write_str));
392 size_humanize(stats->mem_used, mem_used_str, sizeof(mem_used_str));
393
394 ret = snprintf(iosb_str, sizeof(iosb_str), "%s(%s/%s)", iosb_total_str, iosb_read_str, iosb_write_str);
395 if (ret < 0 || (size_t)ret >= sizeof(iosb_str))
396 printf("snprintf'd too many characters: %d\n", ret);
397
398 printf("%-18.18s %12.2f %12.2f %12.2f %36s %10s",
399 name,
400 (float)stats->cpu_use_nanos / 1000000000,
401 (float)stats->cpu_use_sys / USER_HZ,
402 (float)stats->cpu_use_user / USER_HZ,
403 iosb_str,
404 mem_used_str);
405
406 if (total->memsw_used > 0) {
407 size_humanize(stats->memsw_used, memsw_used_str, sizeof(memsw_used_str));
408 printf(" %10s", memsw_used_str);
409 }
410 if (total->kmem_used > 0) {
411 size_humanize(stats->kmem_used, kmem_used_str, sizeof(kmem_used_str));
412 printf(" %10s", kmem_used_str);
413 }
414 } else {
415 (void)gettimeofday(&time_val, NULL);
416 time_ms = (unsigned long long) (time_val.tv_sec) * 1000 + (unsigned long long) (time_val.tv_usec) / 1000;
417 printf("%" PRIu64 ",%s,%" PRIu64 ",%" PRIu64 ",%" PRIu64
418 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64,
419 (uint64_t)time_ms, name, (uint64_t)stats->cpu_use_nanos,
420 (uint64_t)stats->cpu_use_sys,
421 (uint64_t)stats->cpu_use_user, (uint64_t)stats->io_service_bytes.total,
422 (uint64_t)stats->io_serviced.total, (uint64_t)stats->mem_used,
423 (uint64_t)stats->memsw_used, (uint64_t)stats->kmem_used);
424 }
425
426 }
427
428 static int cmp_name(const void *sct1, const void *sct2)
429 {
430 const struct container_stats *ct1 = sct1;
431 const struct container_stats *ct2 = sct2;
432
433 if (sort_reverse)
434 return strncmp(ct2->c->name, ct1->c->name, strlen(ct2->c->name));
435
436 return strncmp(ct1->c->name, ct2->c->name, strlen(ct1->c->name));
437 }
438
439 static int cmp_cpuuse(const void *sct1, const void *sct2)
440 {
441 const struct container_stats *ct1 = sct1;
442 const struct container_stats *ct2 = sct2;
443
444 if (sort_reverse)
445 return ct2->stats->cpu_use_nanos < ct1->stats->cpu_use_nanos;
446
447 return ct1->stats->cpu_use_nanos < ct2->stats->cpu_use_nanos;
448 }
449
450 static int cmp_blkio(const void *sct1, const void *sct2)
451 {
452 const struct container_stats *ct1 = sct1;
453 const struct container_stats *ct2 = sct2;
454
455 if (sort_reverse)
456 return ct2->stats->io_service_bytes.total < ct1->stats->io_service_bytes.total;
457
458 return ct1->stats->io_service_bytes.total < ct2->stats->io_service_bytes.total;
459 }
460
461 static int cmp_memory(const void *sct1, const void *sct2)
462 {
463 const struct container_stats *ct1 = sct1;
464 const struct container_stats *ct2 = sct2;
465
466 if (sort_reverse)
467 return ct2->stats->mem_used < ct1->stats->mem_used;
468
469 return ct1->stats->mem_used < ct2->stats->mem_used;
470 }
471
472 static int cmp_memorysw(const void *sct1, const void *sct2)
473 {
474 const struct container_stats *ct1 = sct1;
475 const struct container_stats *ct2 = sct2;
476
477 if (sort_reverse)
478 return ct2->stats->memsw_used < ct1->stats->memsw_used;
479
480 return ct1->stats->memsw_used < ct2->stats->memsw_used;
481 }
482
483 static int cmp_kmemory(const void *sct1, const void *sct2)
484 {
485 const struct container_stats *ct1 = sct1;
486 const struct container_stats *ct2 = sct2;
487
488 if (sort_reverse)
489 return ct2->stats->kmem_used < ct1->stats->kmem_used;
490
491 return ct1->stats->kmem_used < ct2->stats->kmem_used;
492 }
493
494 static void ct_sort(int active)
495 {
496 int (*cmp_func)(const void *, const void *);
497
498 switch(sort_by) {
499 default:
500 case 'n': cmp_func = cmp_name; break;
501 case 'c': cmp_func = cmp_cpuuse; break;
502 case 'b': cmp_func = cmp_blkio; break;
503 case 'm': cmp_func = cmp_memory; break;
504 case 's': cmp_func = cmp_memorysw; break;
505 case 'k': cmp_func = cmp_kmemory; break;
506 }
507
508 qsort(container_stats, active, sizeof(*container_stats), (int (*)(const void *,const void *))cmp_func);
509 }
510
511 static void ct_free(void)
512 {
513 int i;
514
515 for (i = 0; i < ct_alloc_cnt; i++) {
516 if (container_stats[i].c) {
517 lxc_container_put(container_stats[i].c);
518 container_stats[i].c = NULL;
519 }
520
521 free(container_stats[i].stats);
522 container_stats[i].stats = NULL;
523 }
524 }
525
526 static void ct_realloc(int active_cnt)
527 {
528 if (active_cnt > ct_alloc_cnt) {
529 int i;
530
531 ct_free();
532
533 container_stats = realloc(container_stats, sizeof(*container_stats) * active_cnt);
534 if (!container_stats) {
535 fprintf(stderr, "Cannot alloc mem\n");
536 exit(EXIT_FAILURE);
537 }
538
539 for (i = 0; i < active_cnt; i++) {
540 container_stats[i].stats = malloc(sizeof(*container_stats[0].stats));
541 if (!container_stats[i].stats) {
542 fprintf(stderr, "Cannot alloc mem\n");
543 exit(EXIT_FAILURE);
544 }
545 }
546
547 ct_alloc_cnt = active_cnt;
548 }
549 }
550
551 static int stdin_handler(int fd, uint32_t events, void *data,
552 struct lxc_async_descr *descr)
553 {
554 char *in_char = data;
555
556 if (events & EPOLLIN) {
557 int rc;
558
559 rc = lxc_read_nointr(fd, in_char, sizeof(*in_char));
560 if (rc <= 0)
561 *in_char = '\0';
562 }
563
564 if (events & EPOLLHUP)
565 *in_char = 'q';
566
567 return LXC_MAINLOOP_CLOSE;
568 }
569
570 int main(int argc, char *argv[])
571 {
572 struct lxc_async_descr descr;
573 int ret, ct_print_cnt;
574 char in_char;
575
576 ret = EXIT_FAILURE;
577
578 if (lxc_arguments_parse(&my_args, argc, argv))
579 goto out;
580
581 ct_print_cnt = stdin_tios_rows() - 3; /* 3 -> header and total */
582 if (stdin_tios_setup() < 0) {
583 fprintf(stderr, "Failed to setup terminal\n");
584 goto out;
585 }
586
587 /* ensure the terminal gets restored */
588 atexit(stdin_tios_restore);
589 signal(SIGINT, sig_handler);
590 signal(SIGQUIT, sig_handler);
591
592 if (lxc_mainloop_open(&descr)) {
593 fprintf(stderr, "Failed to create mainloop\n");
594 goto out;
595 }
596
597 ret = lxc_mainloop_add_handler(&descr, 0,
598 stdin_handler,
599 default_cleanup_handler,
600 &in_char, "stdin_handler");
601 if (ret) {
602 fprintf(stderr, "Failed to add stdin handler\n");
603 ret = EXIT_FAILURE;
604 goto err1;
605 }
606
607 if (batch && !delay_set)
608 delay = 300;
609
610 if (batch)
611 printf("time_ms,container,cpu_nanos,cpu_sys_userhz,cpu_user_userhz,blkio_bytes,blkio_iops,mem_used_bytes,memsw_used_bytes,kernel_mem_used_bytes\n");
612
613 for(;;) {
614 struct lxc_container **active;
615 int i, active_cnt;
616 struct stats total;
617 char total_name[30];
618
619 active_cnt = list_active_containers(my_args.lxcpath[0], NULL, &active);
620 ct_realloc(active_cnt);
621
622 memset(&total, 0, sizeof(total));
623
624 for (i = 0; i < active_cnt; i++)
625 stats_get(active[i], &container_stats[i], &total);
626
627 ct_sort(active_cnt);
628
629 if (!batch) {
630 printf(TERMCLEAR);
631 stats_print_header(&total);
632 }
633
634 for (i = 0; i < active_cnt && i < ct_print_cnt; i++) {
635 stats_print(container_stats[i].c->name, container_stats[i].stats, &total);
636 printf("\n");
637 }
638
639 if (!batch) {
640 sprintf(total_name, "TOTAL %d of %d", i, active_cnt);
641 stats_print(total_name, &total, &total);
642 }
643 fflush(stdout);
644
645 for (i = 0; i < active_cnt; i++) {
646 lxc_container_put(container_stats[i].c);
647 container_stats[i].c = NULL;
648 }
649
650 in_char = '\0';
651
652 if (!batch) {
653 ret = lxc_mainloop(&descr, 1000 * delay);
654 if (ret != 0 || in_char == 'q')
655 break;
656
657 switch(in_char) {
658 case 'r':
659 sort_reverse ^= 1;
660 break;
661 case 'n':
662 case 'c':
663 case 'b':
664 case 'm':
665 case 's':
666 case 'k':
667 if (sort_by == in_char)
668 sort_reverse ^= 1;
669 else
670 sort_reverse = 0;
671 sort_by = in_char;
672 }
673 } else {
674 sleep(delay);
675 }
676 }
677
678 ret = EXIT_SUCCESS;
679
680 err1:
681 lxc_mainloop_close(&descr);
682
683 out:
684 exit(ret);
685 }