diff --git a/doc/lxc-top.sgml.in b/doc/lxc-top.sgml.in index ba727c571..2e2f77473 100644 --- a/doc/lxc-top.sgml.in +++ b/doc/lxc-top.sgml.in @@ -47,7 +47,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA lxc-top --help - --max count --delay delay --sort sortby --reverse @@ -60,9 +59,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA lxc-top displays container statistics. The output is updated every delay seconds, and is ordered according to the sortby value - given. Specifying count will limit the - number of containers displayed, otherwise lxc-top - will display as many containers as can fit in your terminal. + given. lxc-top will display as many containers as + can fit in your terminal. Press 'q' to quit. Press one of the sort + key letters to sort by that statistic. Pressing a sort key letter a + second time reverses the sort order. @@ -70,18 +70,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Options - - - - - - - Limit the number of containers displayed to - count. - - - - @@ -89,9 +77,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Amount of time in seconds to delay between screen updates. - This can be specified as less than a second by giving a - rational number, for example 0.5 for a half second delay. The - default is 3 seconds. + The default is 3 seconds. @@ -103,7 +89,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Sort the containers by name, cpu use, or memory use. The sortby argument should be one of - the letters n,c,d,m,k to sort by name, cpu use, disk I/O, memory, + the letters n,c,b,m,k to sort by name, cpu use, block I/O, memory, or kernel memory use respectively. The default is 'n'. diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am index 8322e628d..c65a15db9 100644 --- a/src/lxc/Makefile.am +++ b/src/lxc/Makefile.am @@ -165,7 +165,7 @@ bin_SCRIPTS = lxc-checkconfig EXTRA_DIST = \ lxc-device \ lxc-ls \ - lxc-top \ + lxc-top.lua \ lxc.net \ lxc-restore-net @@ -177,10 +177,6 @@ else bin_SCRIPTS += legacy/lxc-ls endif -if ENABLE_LUA -bin_SCRIPTS += lxc-top -endif - bin_PROGRAMS = \ lxc-attach \ lxc-autostart \ @@ -198,6 +194,7 @@ bin_PROGRAMS = \ lxc-snapshot \ lxc-start \ lxc-stop \ + lxc-top \ lxc-unfreeze \ lxc-unshare \ lxc-usernsexec \ @@ -231,6 +228,7 @@ lxc_monitord_SOURCES = lxc_monitord.c lxc_clone_SOURCES = lxc_clone.c lxc_start_SOURCES = lxc_start.c lxc_stop_SOURCES = lxc_stop.c +lxc_top_SOURCES = lxc_top.c lxc_unfreeze_SOURCES = lxc_unfreeze.c lxc_unshare_SOURCES = lxc_unshare.c lxc_wait_SOURCES = lxc_wait.c diff --git a/src/lxc/lxc-top b/src/lxc/lxc-top.lua similarity index 100% rename from src/lxc/lxc-top rename to src/lxc/lxc-top.lua diff --git a/src/lxc/lxc_top.c b/src/lxc/lxc_top.c new file mode 100644 index 000000000..aec4b52f6 --- /dev/null +++ b/src/lxc/lxc_top.c @@ -0,0 +1,511 @@ +/* + * lxc: linux Container library + * + * Copyright © 2014 Oracle. + * + * Authors: + * Dwight Engen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arguments.h" +#include "log.h" +#include "lxc.h" +#include "mainloop.h" +#include "utils.h" + +lxc_log_define(lxc_top_ui, lxc); + +#define USER_HZ 100 +#define ESC "\033" +#define TERMCLEAR ESC "[H" ESC "[J" +#define TERMNORM ESC "[0m" +#define TERMBOLD ESC "[1m" +#define TERMRVRS ESC "[7m" + +struct stats { + uint64_t mem_used; + uint64_t mem_limit; + uint64_t kmem_used; + uint64_t kmem_limit; + uint64_t cpu_use_nanos; + uint64_t cpu_use_user; + uint64_t cpu_use_sys; + uint64_t blkio; +}; + +struct ct { + struct lxc_container *c; + struct stats *stats; +}; + +static int delay = 3; +static char sort_by = 'n'; +static int sort_reverse = 0; + +static struct termios oldtios; +static struct ct *ct = NULL; +static int ct_alloc_cnt = 0; + +static int my_parser(struct lxc_arguments* args, int c, char* arg) +{ + switch (c) { + case 'd': delay = atoi(arg); break; + case 's': sort_by = arg[0]; break; + case 'r': sort_reverse = 1; break; + } + return 0; +} + +static const struct option my_longopts[] = { + {"delay", required_argument, 0, 'd'}, + {"sort", required_argument, 0, 's'}, + {"reverse", no_argument, 0, 'r'}, + LXC_COMMON_OPTIONS +}; + +static struct lxc_arguments my_args = { + .progname = "lxc-top", + .help = "\ +[--name=NAME]\n\ +\n\ +lxc-top monitors the state of the active containers\n\ +\n\ +Options :\n\ + -d, --delay delay in seconds between refreshes (default: 3.0)\n\ + -s, --sort sort by [n,c,b,m] (default: n) where\n\ + n = Name\n\ + c = CPU use\n\ + b = Block I/O use\n\ + m = Memory use\n\ + k = Kernel memory use\n\ + -r, --reverse sort in reverse (descending) order\n", + .name = ".*", + .options = my_longopts, + .parser = my_parser, + .checker = NULL, + .lxcpath_additional = -1, +}; + +static void stdin_tios_restore(void) +{ + tcsetattr(0, TCSAFLUSH, &oldtios); +} + +static int stdin_tios_setup(void) +{ + struct termios newtios; + + if (!isatty(0)) { + ERROR("stdin is not a tty"); + return -1; + } + + if (tcgetattr(0, &oldtios)) { + SYSERROR("failed to get current terminal settings"); + return -1; + } + + newtios = oldtios; + + /* turn off echo and line buffering */ + newtios.c_iflag &= ~IGNBRK; + newtios.c_iflag &= BRKINT; + newtios.c_lflag &= ~(ECHO|ICANON); + newtios.c_cc[VMIN] = 1; + newtios.c_cc[VTIME] = 0; + + if (tcsetattr(0, TCSAFLUSH, &newtios)) { + ERROR("failed to set new terminal settings"); + return -1; + } + + return 0; +} + +static int stdin_tios_rows(void) +{ + struct winsize wsz; + if (isatty(0) && ioctl(0, TIOCGWINSZ, &wsz) == 0) + return wsz.ws_row; + return 25; +} + +static int stdin_handler(int fd, uint32_t events, void *data, + struct lxc_epoll_descr *descr) +{ + char *in_char = data; + + if (events & EPOLLIN) { + int rc; + + rc = read(fd, in_char, sizeof(*in_char)); + if (rc <= 0) + *in_char = '\0'; + } + + if (events & EPOLLHUP) + *in_char = 'q'; + return 1; +} + +static void sig_handler(int sig) +{ + exit(EXIT_SUCCESS); +} + +static void size_humanize(unsigned long long val, char *buf, size_t bufsz) +{ + if (val > 1 << 30) { + snprintf(buf, bufsz, "%u.%2.2u GB", + (int)(val >> 30), + (int)(val & ((1 << 30) - 1)) / 10737419); + } else if (val > 1 << 20) { + int x = val + 5243; /* for rounding */ + snprintf(buf, bufsz, "%u.%2.2u MB", + x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20); + } else if (val > 1 << 10) { + int x = val + 5; /* for rounding */ + snprintf(buf, bufsz, "%u.%2.2u KB", + x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10); + } else { + snprintf(buf, bufsz, "%3u.00 ", (int)val); + } +} + +static uint64_t stat_get_int(struct lxc_container *c, const char *item) +{ + char buf[80]; + int len; + uint64_t val; + + len = c->get_cgroup_item(c, item, buf, sizeof(buf)); + if (len <= 0) { + ERROR("unable to read cgroup item %s", item); + return 0; + } + + val = strtoull(buf, NULL, 0); + return val; +} + +static uint64_t stat_match_get_int(struct lxc_container *c, const char *item, + const char *match, int column) +{ + char buf[4096]; + int i,j,len; + uint64_t val = 0; + char **lines, **cols; + size_t matchlen; + + len = c->get_cgroup_item(c, item, buf, sizeof(buf)); + if (len <= 0) { + ERROR("unable to read cgroup item %s", item); + goto out; + } + + lines = lxc_string_split_and_trim(buf, '\n'); + if (!lines) + goto out; + + matchlen = strlen(match); + for (i = 0; lines[i]; i++) { + if (strncmp(lines[i], match, matchlen) == 0) { + cols = lxc_string_split_and_trim(lines[i], ' '); + if (!cols) + goto err1; + for (j = 0; cols[j]; j++) { + if (j == column) { + val = strtoull(cols[j], NULL, 0); + break; + } + } + lxc_free_array((void **)cols, free); + break; + } + } +err1: + lxc_free_array((void **)lines, free); +out: + return val; +} + +static void stats_get(struct lxc_container *c, struct ct *ct, struct stats *total) +{ + ct->c = c; + ct->stats->mem_used = stat_get_int(c, "memory.usage_in_bytes"); + ct->stats->mem_limit = stat_get_int(c, "memory.limit_in_bytes"); + ct->stats->kmem_used = stat_get_int(c, "memory.kmem.usage_in_bytes"); + ct->stats->kmem_limit = stat_get_int(c, "memory.kmem.limit_in_bytes"); + ct->stats->cpu_use_nanos = stat_get_int(c, "cpuacct.usage"); + ct->stats->cpu_use_user = stat_match_get_int(c, "cpuacct.stat", "user", 1); + ct->stats->cpu_use_sys = stat_match_get_int(c, "cpuacct.stat", "system", 1); + ct->stats->blkio = stat_match_get_int(c, "blkio.throttle.io_service_bytes", "Total", 1); + + if (total) { + total->mem_used = total->mem_used + ct->stats->mem_used; + total->mem_limit = total->mem_limit + ct->stats->mem_limit; + total->kmem_used = total->kmem_used + ct->stats->kmem_used; + total->kmem_limit = total->kmem_limit + ct->stats->kmem_limit; + total->cpu_use_nanos = total->cpu_use_nanos + ct->stats->cpu_use_nanos; + total->cpu_use_user = total->cpu_use_user + ct->stats->cpu_use_user; + total->cpu_use_sys = total->cpu_use_sys + ct->stats->cpu_use_sys; + total->blkio = total->blkio + ct->stats->blkio; + } +} + +static void stats_print_header(struct stats *stats) +{ + printf(TERMRVRS TERMBOLD); + printf("%-18s %8s %8s %8s %10s %10s", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem"); + if (stats->kmem_used > 0) + printf(" %10s", "KMem"); + printf("\n"); + + printf("%-18s %8s %8s %8s %10s %10s", "Name", "Used", "Sys", "User", "Total", "Used"); + if (stats->kmem_used > 0) + printf(" %10s", "Used"); + printf("\n"); + printf(TERMNORM); +} + +static void stats_print(const char *name, const struct stats *stats, + const struct stats *total) +{ + char blkio_str[20]; + char mem_used_str[20]; + char kmem_used_str[20]; + + size_humanize(stats->blkio, blkio_str, sizeof(blkio_str)); + size_humanize(stats->mem_used, mem_used_str, sizeof(mem_used_str)); + + printf("%-18s %8.2f %8.2f %8.2f %10s %10s", + name, + (float)stats->cpu_use_nanos / 1000000000, + (float)stats->cpu_use_sys / USER_HZ, + (float)stats->cpu_use_user / USER_HZ, + blkio_str, + mem_used_str); + if (total->kmem_used > 0) { + size_humanize(stats->kmem_used, kmem_used_str, sizeof(kmem_used_str)); + printf(" %10s", kmem_used_str); + } +} + +static int cmp_name(const void *sct1, const void *sct2) +{ + const struct ct *ct1 = sct1; + const struct ct *ct2 = sct2; + + if (sort_reverse) + return strcmp(ct2->c->name, ct1->c->name); + return strcmp(ct1->c->name, ct2->c->name); +} + +static int cmp_cpuuse(const void *sct1, const void *sct2) +{ + const struct ct *ct1 = sct1; + const struct ct *ct2 = sct2; + + if (sort_reverse) + return ct2->stats->cpu_use_nanos < ct1->stats->cpu_use_nanos; + return ct1->stats->cpu_use_nanos < ct2->stats->cpu_use_nanos; +} + +static int cmp_blkio(const void *sct1, const void *sct2) +{ + const struct ct *ct1 = sct1; + const struct ct *ct2 = sct2; + + if (sort_reverse) + return ct2->stats->blkio < ct1->stats->blkio; + return ct1->stats->blkio < ct2->stats->blkio; +} + +static int cmp_memory(const void *sct1, const void *sct2) +{ + const struct ct *ct1 = sct1; + const struct ct *ct2 = sct2; + + if (sort_reverse) + return ct2->stats->mem_used < ct1->stats->mem_used; + return ct1->stats->mem_used < ct2->stats->mem_used; +} + +static int cmp_kmemory(const void *sct1, const void *sct2) +{ + const struct ct *ct1 = sct1; + const struct ct *ct2 = sct2; + + if (sort_reverse) + return ct2->stats->kmem_used < ct1->stats->kmem_used; + return ct1->stats->kmem_used < ct2->stats->kmem_used; +} + +static void ct_sort(int active) +{ + int (*cmp_func)(const void *, const void *); + + switch(sort_by) { + default: + case 'n': cmp_func = cmp_name; break; + case 'c': cmp_func = cmp_cpuuse; break; + case 'b': cmp_func = cmp_blkio; break; + case 'm': cmp_func = cmp_memory; break; + case 'k': cmp_func = cmp_kmemory; break; + } + qsort(ct, active, sizeof(*ct), (int (*)(const void *,const void *))cmp_func); +} + +static void ct_free(void) +{ + int i; + + for (i = 0; i < ct_alloc_cnt; i++) { + if (ct[i].c) { + lxc_container_put(ct[i].c); + ct[i].c = NULL; + } + if (ct[i].stats) { + free(ct[i].stats); + ct[i].stats = NULL; + } + } +} + +static void ct_realloc(int active_cnt) +{ + int i; + + if (active_cnt > ct_alloc_cnt) { + ct_free(); + ct = realloc(ct, sizeof(*ct) * active_cnt); + if (!ct) { + ERROR("cannot alloc mem"); + exit(EXIT_FAILURE); + } + for (i = 0; i < active_cnt; i++) { + ct[i].stats = malloc(sizeof(*ct[0].stats)); + if (!ct[i].stats) { + ERROR("cannot alloc mem"); + exit(EXIT_FAILURE); + } + } + ct_alloc_cnt = active_cnt; + } +} + +int main(int argc, char *argv[]) +{ + struct lxc_epoll_descr descr; + int ret, ct_print_cnt; + char in_char; + + ret = EXIT_FAILURE; + if (lxc_arguments_parse(&my_args, argc, argv)) + goto out; + + ct_print_cnt = stdin_tios_rows() - 3; /* 3 -> header and total */ + if (stdin_tios_setup() < 0) { + ERROR("failed to setup terminal"); + goto out; + } + + /* ensure the terminal gets restored */ + atexit(stdin_tios_restore); + signal(SIGINT, sig_handler); + signal(SIGQUIT, sig_handler); + + if (lxc_mainloop_open(&descr)) { + ERROR("failed to create mainloop"); + goto out; + } + + ret = lxc_mainloop_add_handler(&descr, 0, stdin_handler, &in_char); + if (ret) { + ERROR("failed to add stdin handler"); + ret = EXIT_FAILURE; + goto err1; + } + + for(;;) { + struct lxc_container **active; + int i, active_cnt; + struct stats total; + char total_name[30]; + + active_cnt = list_active_containers(my_args.lxcpath[0], NULL, &active); + ct_realloc(active_cnt); + + memset(&total, 0, sizeof(total)); + for (i = 0; i < active_cnt; i++) + stats_get(active[i], &ct[i], &total); + + ct_sort(active_cnt); + + printf(TERMCLEAR); + stats_print_header(&total); + for (i = 0; i < active_cnt && i < ct_print_cnt; i++) { + stats_print(ct[i].c->name, ct[i].stats, &total); + printf("\n"); + } + sprintf(total_name, "TOTAL %d of %d", i, active_cnt); + stats_print(total_name, &total, &total); + fflush(stdout); + + for (i = 0; i < active_cnt; i++) { + lxc_container_put(ct[i].c); + ct[i].c = NULL; + } + + in_char = '\0'; + ret = lxc_mainloop(&descr, 1000 * delay); + if (ret != 0 || in_char == 'q') + break; + switch(in_char) { + case 'r': + sort_reverse ^= 1; + break; + case 'n': + case 'c': + case 'b': + case 'm': + case 'k': + if (sort_by == in_char) + sort_reverse ^= 1; + else + sort_reverse = 0; + sort_by = in_char; + } + } + ret = EXIT_SUCCESS; + +err1: + lxc_mainloop_close(&descr); +out: + return ret; +}