1 /* SPDX-License-Identifier: LGPL-2.1+ */
13 #include <sys/epoll.h>
14 #include <sys/ioctl.h>
21 #include "arguments.h"
27 #define TERMCLEAR ESC "[H" ESC "[J"
28 #define TERMNORM ESC "[0m"
29 #define TERMBOLD ESC "[1m"
30 #define TERMRVRS ESC "[7m"
45 uint64_t cpu_use_nanos
;
46 uint64_t cpu_use_user
;
48 struct blkio_stats io_service_bytes
;
49 struct blkio_stats io_serviced
;
52 struct container_stats
{
53 struct lxc_container
*c
;
58 static int delay_set
= 0;
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;
66 static int my_parser(struct lxc_arguments
*args
, int c
, char *arg
)
71 if (lxc_safe_int(arg
, &delay
) < 0)
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'},
96 static struct lxc_arguments my_args
= {
97 .progname
= "lxc-top",
101 lxc-top monitors the state of the active containers\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\
111 s = Memory + Swap use\n\
112 k = Kernel memory use\n\
113 -r, --reverse sort in reverse (descending) order\n",
115 .options
= my_longopts
,
118 .lxcpath_additional
= -1,
121 static void stdin_tios_restore(void)
123 (void)tcsetattr(0, TCSAFLUSH
, &oldtios
);
124 fprintf(stderr
, "\n");
127 static int stdin_tios_setup(void)
129 struct termios newtios
;
132 fprintf(stderr
, "stdin is not a tty\n");
136 if (tcgetattr(0, &oldtios
)) {
137 fprintf(stderr
, "Failed to get current terminal settings\n");
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;
150 if (tcsetattr(0, TCSAFLUSH
, &newtios
)) {
151 fprintf(stderr
, "Failed to set new terminal settings\n");
158 static int stdin_tios_rows(void)
162 if (isatty(0) && ioctl(0, TIOCGWINSZ
, &wsz
) == 0)
168 static void sig_handler(int sig
)
173 static void size_humanize(unsigned long long val
, char *buf
, size_t bufsz
)
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);
190 ret
= snprintf(buf
, bufsz
, "%3u.00 ", (unsigned int)val
);
193 if (ret
< 0 || (size_t)ret
>= bufsz
)
194 fprintf(stderr
, "Failed to create string\n");
197 static uint64_t stat_get_int(struct lxc_container
*c
, const char *item
)
203 len
= c
->get_cgroup_item(c
, item
, buf
, sizeof(buf
));
205 fprintf(stderr
, "Unable to read cgroup item %s\n", item
);
209 val
= strtoull(buf
, NULL
, 0);
213 static uint64_t stat_match_get_int(struct lxc_container
*c
, const char *item
,
214 const char *match
, int column
)
219 char **lines
, **cols
;
222 len
= c
->get_cgroup_item(c
, item
, buf
, sizeof(buf
));
224 fprintf(stderr
, "Unable to read cgroup item %s\n", item
);
228 lines
= lxc_string_split_and_trim(buf
, '\n');
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
], ' ');
239 for (j
= 0; cols
[j
]; j
++) {
241 val
= strtoull(cols
[j
], NULL
, 0);
246 lxc_free_array((void **)cols
, free
);
252 lxc_free_array((void **)lines
, free
);
260 blkio.throttle.io_serviced
268 blkio.throttle.io_service_bytes
276 static void stat_get_blk_stats(struct lxc_container
*c
, const char *item
,
277 struct blkio_stats
*stats
) {
280 char **lines
, **cols
;
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
);
288 lines
= lxc_string_split_and_trim(buf
, '\n');
292 memset(stats
, 0, sizeof(struct blkio_stats
));
294 for (i
= 0; lines
[i
]; i
++) {
295 cols
= lxc_string_split_and_trim(lines
[i
], ' ');
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);
304 if (strncmp(cols
[0], "Total", strlen(cols
[0])) == 0)
305 stats
->total
= strtoull(cols
[1], NULL
, 0);
307 lxc_free_array((void **)cols
, free
);
311 lxc_free_array((void **)lines
, free
);
315 static void stats_get(struct lxc_container
*c
, struct container_stats
*ct
, struct stats
*total
)
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);
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
);
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
;
347 static void stats_print_header(struct stats
*stats
)
349 printf(TERMRVRS TERMBOLD
);
350 printf("%-18s %12s %12s %12s %36s %10s", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem");
352 if (stats
->memsw_used
> 0)
353 printf(" %10s", "MemSw");
355 if (stats
->kmem_used
> 0)
356 printf(" %10s", "KMem");
359 printf("%-18s %12s %12s %12s %36s %10s", "Name", "Used", "Sys", "User", "Total(Read/Write)", "Used");
361 if (stats
->memsw_used
> 0)
362 printf(" %10s", "Used");
364 if (stats
->kmem_used
> 0)
365 printf(" %10s", "Used");
371 static void stats_print(const char *name
, const struct stats
*stats
,
372 const struct stats
*total
)
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
;
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
));
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
);
395 printf("%-18.18s %12.2f %12.2f %12.2f %36s %10s",
397 (float)stats
->cpu_use_nanos
/ 1000000000,
398 (float)stats
->cpu_use_sys
/ USER_HZ
,
399 (float)stats
->cpu_use_user
/ USER_HZ
,
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
);
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
);
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
);
425 static int cmp_name(const void *sct1
, const void *sct2
)
427 const struct container_stats
*ct1
= sct1
;
428 const struct container_stats
*ct2
= sct2
;
431 return strncmp(ct2
->c
->name
, ct1
->c
->name
, strlen(ct2
->c
->name
));
433 return strncmp(ct1
->c
->name
, ct2
->c
->name
, strlen(ct1
->c
->name
));
436 static int cmp_cpuuse(const void *sct1
, const void *sct2
)
438 const struct container_stats
*ct1
= sct1
;
439 const struct container_stats
*ct2
= sct2
;
442 return ct2
->stats
->cpu_use_nanos
< ct1
->stats
->cpu_use_nanos
;
444 return ct1
->stats
->cpu_use_nanos
< ct2
->stats
->cpu_use_nanos
;
447 static int cmp_blkio(const void *sct1
, const void *sct2
)
449 const struct container_stats
*ct1
= sct1
;
450 const struct container_stats
*ct2
= sct2
;
453 return ct2
->stats
->io_service_bytes
.total
< ct1
->stats
->io_service_bytes
.total
;
455 return ct1
->stats
->io_service_bytes
.total
< ct2
->stats
->io_service_bytes
.total
;
458 static int cmp_memory(const void *sct1
, const void *sct2
)
460 const struct container_stats
*ct1
= sct1
;
461 const struct container_stats
*ct2
= sct2
;
464 return ct2
->stats
->mem_used
< ct1
->stats
->mem_used
;
466 return ct1
->stats
->mem_used
< ct2
->stats
->mem_used
;
469 static int cmp_memorysw(const void *sct1
, const void *sct2
)
471 const struct container_stats
*ct1
= sct1
;
472 const struct container_stats
*ct2
= sct2
;
475 return ct2
->stats
->memsw_used
< ct1
->stats
->memsw_used
;
477 return ct1
->stats
->memsw_used
< ct2
->stats
->memsw_used
;
480 static int cmp_kmemory(const void *sct1
, const void *sct2
)
482 const struct container_stats
*ct1
= sct1
;
483 const struct container_stats
*ct2
= sct2
;
486 return ct2
->stats
->kmem_used
< ct1
->stats
->kmem_used
;
488 return ct1
->stats
->kmem_used
< ct2
->stats
->kmem_used
;
491 static void ct_sort(int active
)
493 int (*cmp_func
)(const void *, const void *);
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;
505 qsort(container_stats
, active
, sizeof(*container_stats
), (int (*)(const void *,const void *))cmp_func
);
508 static void ct_free(void)
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
;
518 free(container_stats
[i
].stats
);
519 container_stats
[i
].stats
= NULL
;
523 static void ct_realloc(int active_cnt
)
525 if (active_cnt
> ct_alloc_cnt
) {
530 container_stats
= realloc(container_stats
, sizeof(*container_stats
) * active_cnt
);
531 if (!container_stats
) {
532 fprintf(stderr
, "Cannot alloc mem\n");
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");
544 ct_alloc_cnt
= active_cnt
;
548 static int stdin_handler(int fd
, uint32_t events
, void *data
,
549 struct lxc_async_descr
*descr
)
551 char *in_char
= data
;
553 if (events
& EPOLLIN
) {
556 rc
= lxc_read_nointr(fd
, in_char
, sizeof(*in_char
));
561 if (events
& EPOLLHUP
)
564 return LXC_MAINLOOP_CLOSE
;
567 int main(int argc
, char *argv
[])
569 struct lxc_async_descr descr
;
570 int ret
, ct_print_cnt
;
575 if (lxc_arguments_parse(&my_args
, argc
, argv
))
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");
584 /* ensure the terminal gets restored */
585 atexit(stdin_tios_restore
);
586 signal(SIGINT
, sig_handler
);
587 signal(SIGQUIT
, sig_handler
);
589 if (lxc_mainloop_open(&descr
)) {
590 fprintf(stderr
, "Failed to create mainloop\n");
594 ret
= lxc_mainloop_add_handler(&descr
, 0,
596 default_cleanup_handler
,
597 &in_char
, "stdin_handler");
599 fprintf(stderr
, "Failed to add stdin handler\n");
604 if (batch
&& !delay_set
)
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");
611 struct lxc_container
**active
;
616 active_cnt
= list_active_containers(my_args
.lxcpath
[0], NULL
, &active
);
617 ct_realloc(active_cnt
);
619 memset(&total
, 0, sizeof(total
));
621 for (i
= 0; i
< active_cnt
; i
++)
622 stats_get(active
[i
], &container_stats
[i
], &total
);
628 stats_print_header(&total
);
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
);
637 sprintf(total_name
, "TOTAL %d of %d", i
, active_cnt
);
638 stats_print(total_name
, &total
, &total
);
642 for (i
= 0; i
< active_cnt
; i
++) {
643 lxc_container_put(container_stats
[i
].c
);
644 container_stats
[i
].c
= NULL
;
650 ret
= lxc_mainloop(&descr
, 1000 * delay
);
651 if (ret
!= 0 || in_char
== 'q')
664 if (sort_by
== in_char
)
678 lxc_mainloop_close(&descr
);