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