port lxc-top from lua to C for wider availability

- keep but rename the lua version as an example of how to use the lua API

- got rid of the fairly useless --max argument

Signed-off-by: Dwight Engen <dwight.engen@oracle.com>
Acked-by: Serge E. Hallyn <serge.hallyn@ubuntu.com>
This commit is contained in:
Dwight Engen 2014-09-23 13:37:50 -04:00 committed by Stéphane Graber
parent 3cd988ccdb
commit 7dc6f6e279
4 changed files with 520 additions and 25 deletions

View File

@ -47,7 +47,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<cmdsynopsis> <cmdsynopsis>
<command>lxc-top</command> <command>lxc-top</command>
<arg choice="opt">--help</arg> <arg choice="opt">--help</arg>
<arg choice="opt">--max <replaceable>count</replaceable></arg>
<arg choice="opt">--delay <replaceable>delay</replaceable></arg> <arg choice="opt">--delay <replaceable>delay</replaceable></arg>
<arg choice="opt">--sort <replaceable>sortby</replaceable></arg> <arg choice="opt">--sort <replaceable>sortby</replaceable></arg>
<arg choice="opt">--reverse</arg> <arg choice="opt">--reverse</arg>
@ -60,9 +59,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<command>lxc-top</command> displays container statistics. The output <command>lxc-top</command> displays container statistics. The output
is updated every <replaceable>delay</replaceable> seconds, and is is updated every <replaceable>delay</replaceable> seconds, and is
ordered according to the <replaceable>sortby</replaceable> value ordered according to the <replaceable>sortby</replaceable> value
given. Specifying <replaceable>count</replaceable> will limit the given. <command>lxc-top</command> will display as many containers as
number of containers displayed, otherwise <command>lxc-top</command> can fit in your terminal. Press 'q' to quit. Press one of the sort
will display as many containers as can fit in your terminal. key letters to sort by that statistic. Pressing a sort key letter a
second time reverses the sort order.
</para> </para>
</refsect1> </refsect1>
@ -70,18 +70,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<title>Options</title> <title>Options</title>
<variablelist> <variablelist>
<varlistentry>
<term>
<option><optional>-m, --max <replaceable>count</replaceable></optional></option>
</term>
<listitem>
<para>
Limit the number of containers displayed to
<replaceable>count</replaceable>.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term> <term>
<option><optional>-d, --delay <replaceable>delay</replaceable></optional></option> <option><optional>-d, --delay <replaceable>delay</replaceable></optional></option>
@ -89,9 +77,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<listitem> <listitem>
<para> <para>
Amount of time in seconds to delay between screen updates. Amount of time in seconds to delay between screen updates.
This can be specified as less than a second by giving a The default is 3 seconds.
rational number, for example 0.5 for a half second delay. The
default is 3 seconds.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -103,7 +89,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<para> <para>
Sort the containers by name, cpu use, or memory use. The Sort the containers by name, cpu use, or memory use. The
<replaceable>sortby</replaceable> argument should be one of <replaceable>sortby</replaceable> 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'. or kernel memory use respectively. The default is 'n'.
</para> </para>
</listitem> </listitem>

View File

@ -165,7 +165,7 @@ bin_SCRIPTS = lxc-checkconfig
EXTRA_DIST = \ EXTRA_DIST = \
lxc-device \ lxc-device \
lxc-ls \ lxc-ls \
lxc-top \ lxc-top.lua \
lxc.net \ lxc.net \
lxc-restore-net lxc-restore-net
@ -177,10 +177,6 @@ else
bin_SCRIPTS += legacy/lxc-ls bin_SCRIPTS += legacy/lxc-ls
endif endif
if ENABLE_LUA
bin_SCRIPTS += lxc-top
endif
bin_PROGRAMS = \ bin_PROGRAMS = \
lxc-attach \ lxc-attach \
lxc-autostart \ lxc-autostart \
@ -198,6 +194,7 @@ bin_PROGRAMS = \
lxc-snapshot \ lxc-snapshot \
lxc-start \ lxc-start \
lxc-stop \ lxc-stop \
lxc-top \
lxc-unfreeze \ lxc-unfreeze \
lxc-unshare \ lxc-unshare \
lxc-usernsexec \ lxc-usernsexec \
@ -231,6 +228,7 @@ lxc_monitord_SOURCES = lxc_monitord.c
lxc_clone_SOURCES = lxc_clone.c lxc_clone_SOURCES = lxc_clone.c
lxc_start_SOURCES = lxc_start.c lxc_start_SOURCES = lxc_start.c
lxc_stop_SOURCES = lxc_stop.c lxc_stop_SOURCES = lxc_stop.c
lxc_top_SOURCES = lxc_top.c
lxc_unfreeze_SOURCES = lxc_unfreeze.c lxc_unfreeze_SOURCES = lxc_unfreeze.c
lxc_unshare_SOURCES = lxc_unshare.c lxc_unshare_SOURCES = lxc_unshare.c
lxc_wait_SOURCES = lxc_wait.c lxc_wait_SOURCES = lxc_wait.c

511
src/lxc/lxc_top.c Normal file
View File

@ -0,0 +1,511 @@
/*
* lxc: linux Container library
*
* Copyright © 2014 Oracle.
*
* Authors:
* Dwight Engen <dwight.engen@oracle.com>
*
* 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 <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <lxc/lxccontainer.h>
#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;
}