1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #define __STDC_FORMAT_MACROS /* Required for PRIu64 to work. */
15 #include <sys/epoll.h>
16 #include <sys/ioctl.h>
21 #include <lxc/lxccontainer.h>
23 #include "arguments.h"
30 #define TERMCLEAR ESC "[H" ESC "[J"
31 #define TERMNORM ESC "[0m"
32 #define TERMBOLD ESC "[1m"
33 #define TERMRVRS ESC "[7m"
48 uint64_t cpu_use_nanos
;
49 uint64_t cpu_use_user
;
51 struct blkio_stats io_service_bytes
;
52 struct blkio_stats io_serviced
;
55 struct container_stats
{
56 struct lxc_container
*c
;
61 static int delay_set
= 0;
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;
69 static int my_parser(struct lxc_arguments
*args
, int c
, char *arg
)
74 if (lxc_safe_int(arg
, &delay
) < 0)
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'},
99 static struct lxc_arguments my_args
= {
100 .progname
= "lxc-top",
104 lxc-top monitors the state of the active containers\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\
114 s = Memory + Swap use\n\
115 k = Kernel memory use\n\
116 -r, --reverse sort in reverse (descending) order\n",
118 .options
= my_longopts
,
121 .lxcpath_additional
= -1,
124 static void stdin_tios_restore(void)
126 (void)tcsetattr(0, TCSAFLUSH
, &oldtios
);
127 fprintf(stderr
, "\n");
130 static int stdin_tios_setup(void)
132 struct termios newtios
;
135 fprintf(stderr
, "stdin is not a tty\n");
139 if (tcgetattr(0, &oldtios
)) {
140 fprintf(stderr
, "Failed to get current terminal settings\n");
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;
153 if (tcsetattr(0, TCSAFLUSH
, &newtios
)) {
154 fprintf(stderr
, "Failed to set new terminal settings\n");
161 static int stdin_tios_rows(void)
165 if (isatty(0) && ioctl(0, TIOCGWINSZ
, &wsz
) == 0)
171 static void sig_handler(int sig
)
176 static void size_humanize(unsigned long long val
, char *buf
, size_t bufsz
)
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);
193 ret
= snprintf(buf
, bufsz
, "%3u.00 ", (unsigned int)val
);
196 if (ret
< 0 || (size_t)ret
>= bufsz
)
197 fprintf(stderr
, "Failed to create string\n");
200 static uint64_t stat_get_int(struct lxc_container
*c
, const char *item
)
206 len
= c
->get_cgroup_item(c
, item
, buf
, sizeof(buf
));
208 fprintf(stderr
, "Unable to read cgroup item %s\n", item
);
212 val
= strtoull(buf
, NULL
, 0);
216 static uint64_t stat_match_get_int(struct lxc_container
*c
, const char *item
,
217 const char *match
, int column
)
222 char **lines
, **cols
;
225 len
= c
->get_cgroup_item(c
, item
, buf
, sizeof(buf
));
227 fprintf(stderr
, "Unable to read cgroup item %s\n", item
);
231 lines
= lxc_string_split_and_trim(buf
, '\n');
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
], ' ');
242 for (j
= 0; cols
[j
]; j
++) {
244 val
= strtoull(cols
[j
], NULL
, 0);
249 lxc_free_array((void **)cols
, free
);
255 lxc_free_array((void **)lines
, free
);
263 blkio.throttle.io_serviced
271 blkio.throttle.io_service_bytes
279 static void stat_get_blk_stats(struct lxc_container
*c
, const char *item
,
280 struct blkio_stats
*stats
) {
283 char **lines
, **cols
;
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
);
291 lines
= lxc_string_split_and_trim(buf
, '\n');
295 memset(stats
, 0, sizeof(struct blkio_stats
));
297 for (i
= 0; lines
[i
]; i
++) {
298 cols
= lxc_string_split_and_trim(lines
[i
], ' ');
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);
307 if (strncmp(cols
[0], "Total", strlen(cols
[0])) == 0)
308 stats
->total
= strtoull(cols
[1], NULL
, 0);
310 lxc_free_array((void **)cols
, free
);
314 lxc_free_array((void **)lines
, free
);
318 static void stats_get(struct lxc_container
*c
, struct container_stats
*ct
, struct stats
*total
)
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);
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
);
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
;
350 static void stats_print_header(struct stats
*stats
)
352 printf(TERMRVRS TERMBOLD
);
353 printf("%-18s %12s %12s %12s %36s %10s", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem");
355 if (stats
->memsw_used
> 0)
356 printf(" %10s", "MemSw");
358 if (stats
->kmem_used
> 0)
359 printf(" %10s", "KMem");
362 printf("%-18s %12s %12s %12s %36s %10s", "Name", "Used", "Sys", "User", "Total(Read/Write)", "Used");
364 if (stats
->memsw_used
> 0)
365 printf(" %10s", "Used");
367 if (stats
->kmem_used
> 0)
368 printf(" %10s", "Used");
374 static void stats_print(const char *name
, const struct stats
*stats
,
375 const struct stats
*total
)
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
;
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
));
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
);
398 printf("%-18.18s %12.2f %12.2f %12.2f %36s %10s",
400 (float)stats
->cpu_use_nanos
/ 1000000000,
401 (float)stats
->cpu_use_sys
/ USER_HZ
,
402 (float)stats
->cpu_use_user
/ USER_HZ
,
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
);
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
);
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
);
428 static int cmp_name(const void *sct1
, const void *sct2
)
430 const struct container_stats
*ct1
= sct1
;
431 const struct container_stats
*ct2
= sct2
;
434 return strncmp(ct2
->c
->name
, ct1
->c
->name
, strlen(ct2
->c
->name
));
436 return strncmp(ct1
->c
->name
, ct2
->c
->name
, strlen(ct1
->c
->name
));
439 static int cmp_cpuuse(const void *sct1
, const void *sct2
)
441 const struct container_stats
*ct1
= sct1
;
442 const struct container_stats
*ct2
= sct2
;
445 return ct2
->stats
->cpu_use_nanos
< ct1
->stats
->cpu_use_nanos
;
447 return ct1
->stats
->cpu_use_nanos
< ct2
->stats
->cpu_use_nanos
;
450 static int cmp_blkio(const void *sct1
, const void *sct2
)
452 const struct container_stats
*ct1
= sct1
;
453 const struct container_stats
*ct2
= sct2
;
456 return ct2
->stats
->io_service_bytes
.total
< ct1
->stats
->io_service_bytes
.total
;
458 return ct1
->stats
->io_service_bytes
.total
< ct2
->stats
->io_service_bytes
.total
;
461 static int cmp_memory(const void *sct1
, const void *sct2
)
463 const struct container_stats
*ct1
= sct1
;
464 const struct container_stats
*ct2
= sct2
;
467 return ct2
->stats
->mem_used
< ct1
->stats
->mem_used
;
469 return ct1
->stats
->mem_used
< ct2
->stats
->mem_used
;
472 static int cmp_memorysw(const void *sct1
, const void *sct2
)
474 const struct container_stats
*ct1
= sct1
;
475 const struct container_stats
*ct2
= sct2
;
478 return ct2
->stats
->memsw_used
< ct1
->stats
->memsw_used
;
480 return ct1
->stats
->memsw_used
< ct2
->stats
->memsw_used
;
483 static int cmp_kmemory(const void *sct1
, const void *sct2
)
485 const struct container_stats
*ct1
= sct1
;
486 const struct container_stats
*ct2
= sct2
;
489 return ct2
->stats
->kmem_used
< ct1
->stats
->kmem_used
;
491 return ct1
->stats
->kmem_used
< ct2
->stats
->kmem_used
;
494 static void ct_sort(int active
)
496 int (*cmp_func
)(const void *, const void *);
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;
508 qsort(container_stats
, active
, sizeof(*container_stats
), (int (*)(const void *,const void *))cmp_func
);
511 static void ct_free(void)
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
;
521 free(container_stats
[i
].stats
);
522 container_stats
[i
].stats
= NULL
;
526 static void ct_realloc(int active_cnt
)
528 if (active_cnt
> ct_alloc_cnt
) {
533 container_stats
= realloc(container_stats
, sizeof(*container_stats
) * active_cnt
);
534 if (!container_stats
) {
535 fprintf(stderr
, "Cannot alloc mem\n");
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");
547 ct_alloc_cnt
= active_cnt
;
551 static int stdin_handler(int fd
, uint32_t events
, void *data
,
552 struct lxc_async_descr
*descr
)
554 char *in_char
= data
;
556 if (events
& EPOLLIN
) {
559 rc
= lxc_read_nointr(fd
, in_char
, sizeof(*in_char
));
564 if (events
& EPOLLHUP
)
567 return LXC_MAINLOOP_CLOSE
;
570 int main(int argc
, char *argv
[])
572 struct lxc_async_descr descr
;
573 int ret
, ct_print_cnt
;
578 if (lxc_arguments_parse(&my_args
, argc
, argv
))
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");
587 /* ensure the terminal gets restored */
588 atexit(stdin_tios_restore
);
589 signal(SIGINT
, sig_handler
);
590 signal(SIGQUIT
, sig_handler
);
592 if (lxc_mainloop_open(&descr
)) {
593 fprintf(stderr
, "Failed to create mainloop\n");
597 ret
= lxc_mainloop_add_handler(&descr
, 0,
599 default_cleanup_handler
,
600 &in_char
, "stdin_handler");
602 fprintf(stderr
, "Failed to add stdin handler\n");
607 if (batch
&& !delay_set
)
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");
614 struct lxc_container
**active
;
619 active_cnt
= list_active_containers(my_args
.lxcpath
[0], NULL
, &active
);
620 ct_realloc(active_cnt
);
622 memset(&total
, 0, sizeof(total
));
624 for (i
= 0; i
< active_cnt
; i
++)
625 stats_get(active
[i
], &container_stats
[i
], &total
);
631 stats_print_header(&total
);
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
);
640 sprintf(total_name
, "TOTAL %d of %d", i
, active_cnt
);
641 stats_print(total_name
, &total
, &total
);
645 for (i
= 0; i
< active_cnt
; i
++) {
646 lxc_container_put(container_stats
[i
].c
);
647 container_stats
[i
].c
= NULL
;
653 ret
= lxc_mainloop(&descr
, 1000 * delay
);
654 if (ret
!= 0 || in_char
== 'q')
667 if (sort_by
== in_char
)
681 lxc_mainloop_close(&descr
);