[lxc-devel] [PATCH] port lxc-top from lua to C for wider availability

Serge Hallyn serge.hallyn at ubuntu.com
Mon Sep 22 17:15:40 UTC 2014


Quoting Dwight Engen (dwight.engen at oracle.com):
> It seems lxc-top would be useful on some platforms which don't yet bundle
> the lxc lua binding. This is pretty much a direct port of the lua based
> lxc-top to C. It still just uses printf for output so there are no
> additional dependencies for lxc.
> 
> - 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 at oracle.com>

Thanks, Dwight, that'll be nice to have.  And I'm glad to see you're
keeping the lua code around.

In src/lxc/Makefile.am you left lxc-top in bin_SCRIPTS and EXTRA_DIST.
Should those be removed?

Also while 'lxc-top -r' worked, 'lxc-top -s m -r' didn't seem to.  Looking
at the code, you're using 'memsw.usage_in_bytes.'  But that doesn't
always exist.  So in cmp_memory, I'd suggest checking whether both
containers have memsw_used 0, and if so then just use mem_used.

But overall,

Acked-by: Serge E. Hallyn <serge.hallyn at ubuntu.com>


> ---
>  doc/lxc-top.sgml.in |  26 +--
>  src/lxc/Makefile.am |   1 +
>  src/lxc/lxc-top     | 243 -------------------------
>  src/lxc/lxc-top.c   | 516 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  src/lxc/lxc-top.lua | 243 +++++++++++++++++++++++++
>  5 files changed, 766 insertions(+), 263 deletions(-)
>  delete mode 100755 src/lxc/lxc-top
>  create mode 100644 src/lxc/lxc-top.c
>  create mode 100755 src/lxc/lxc-top.lua
> 
> diff --git a/doc/lxc-top.sgml.in b/doc/lxc-top.sgml.in
> index ba727c5..2e2f774 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
>      <cmdsynopsis>
>        <command>lxc-top</command>
>        <arg choice="opt">--help</arg>
> -      <arg choice="opt">--max <replaceable>count</replaceable></arg>
>        <arg choice="opt">--delay <replaceable>delay</replaceable></arg>
>        <arg choice="opt">--sort <replaceable>sortby</replaceable></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
>        is updated every <replaceable>delay</replaceable> seconds, and is
>        ordered according to the <replaceable>sortby</replaceable> value
> -      given. Specifying <replaceable>count</replaceable> will limit the
> -      number of containers displayed, otherwise <command>lxc-top</command>
> -      will display as many containers as can fit in your terminal.
> +      given. <command>lxc-top</command> 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.
>      </para>
>    </refsect1>
>  
> @@ -72,26 +72,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>  
>        <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>
> -        <term>
>            <option><optional>-d, --delay <replaceable>delay</replaceable></optional></option>
>          </term>
>          <listitem>
>            <para>
>              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.
>            </para>
>          </listitem>
>        </varlistentry>
> @@ -103,7 +89,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>            <para>
>              Sort the containers by name, cpu use, or memory use. The
>              <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'.
>            </para>
>          </listitem>
> diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am
> index 8322e62..65c1f91 100644
> --- a/src/lxc/Makefile.am
> +++ b/src/lxc/Makefile.am
> @@ -198,6 +198,7 @@ bin_PROGRAMS = \
>  	lxc-snapshot \
>  	lxc-start \
>  	lxc-stop \
> +	lxc-top \
>  	lxc-unfreeze \
>  	lxc-unshare \
>  	lxc-usernsexec \
> diff --git a/src/lxc/lxc-top b/src/lxc/lxc-top
> deleted file mode 100755
> index b5b3a69..0000000
> --- a/src/lxc/lxc-top
> +++ /dev/null
> @@ -1,243 +0,0 @@
> -#!/usr/bin/env lua
> ---
> --- top(1) like monitor for lxc containers
> ---
> --- Copyright © 2012 Oracle.
> ---
> --- Authors:
> --- Dwight Engen <dwight.engen at oracle.com>
> ---
> --- This library is free software; you can redistribute it and/or modify
> --- it under the terms of the GNU General Public License version 2, as
> --- published by the Free Software Foundation.
> ---
> --- This program 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 General Public License for more details.
> ---
> --- You should have received a copy of the GNU General Public License along
> --- with this program; if not, write to the Free Software Foundation, Inc.,
> --- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> ---
> -
> -local lxc    = require("lxc")
> -local core   = require("lxc.core")
> -local getopt = require("alt_getopt")
> -
> -local USER_HZ   = 100
> -local ESC       = string.format("%c", 27)
> -local TERMCLEAR = ESC.."[H"..ESC.."[J"
> -local TERMNORM  = ESC.."[0m"
> -local TERMBOLD  = ESC.."[1m"
> -local TERMRVRS  = ESC.."[7m"
> -
> -local containers = {}
> -local stats = {}
> -local stats_total = {}
> -local max_containers
> -
> -function printf(...)
> -    local function wrapper(...) io.write(string.format(...)) end
> -    local status, result = pcall(wrapper, ...)
> -    if not status then
> -	error(result, 2)
> -    end
> -end
> -
> -function string:split(delim, max_cols)
> -    local cols = {}
> -    local start = 1
> -    local nextc
> -    repeat
> -	nextc = string.find(self, delim, start)
> -	if (nextc and #cols ~= max_cols - 1) then
> -	    table.insert(cols, string.sub(self, start, nextc-1))
> -	    start = nextc + #delim
> -	else
> -	    table.insert(cols, string.sub(self, start, string.len(self)))
> -	    nextc = nil
> -	end
> -    until nextc == nil or start > #self
> -    return cols
> -end
> -
> -function strsisize(size, width)
> -    local KiB = 1024
> -    local MiB = 1048576
> -    local GiB = 1073741824
> -    local TiB = 1099511627776
> -    local PiB = 1125899906842624
> -    local EiB = 1152921504606846976
> -    local ZiB = 1180591620717411303424
> -
> -    if (size >= ZiB) then
> -	return string.format("%d.%2.2d ZB", size / ZiB, (math.floor(size % ZiB) * 100) / ZiB)
> -    end
> -    if (size >= EiB) then
> -	return string.format("%d.%2.2d EB", size / EiB, (math.floor(size % EiB) * 100) / EiB)
> -    end
> -    if (size >= PiB) then
> -	return string.format("%d.%2.2d PB", size / PiB, (math.floor(size % PiB) * 100) / PiB)
> -    end
> -    if (size >= TiB) then
> -	return string.format("%d.%2.2d TB", size / TiB, (math.floor(size % TiB) * 100) / TiB)
> -    end
> -    if (size >= GiB) then
> -	return string.format("%d.%2.2d GB", size / GiB, (math.floor(size % GiB) * 100) / GiB)
> -    end
> -    if (size >= MiB) then
> -	return string.format("%d.%2.2d MB", size / MiB, (math.floor(size % MiB) * 1000) / (MiB * 10))
> -    end
> -    if (size >= KiB) then
> -	return string.format("%d.%2.2d KB", size / KiB, (math.floor(size % KiB) * 1000) / (KiB * 10))
> -    end
> -    return string.format("%3d.00   ", size)
> -end
> -
> -function tty_lines()
> -    local rows = 25
> -    local f = assert(io.popen("stty -a | head -n 1"))
> -    for line in f:lines() do
> -	local stty_rows
> -	_,_,stty_rows = string.find(line, "rows (%d+)")
> -	if (stty_rows ~= nil) then
> -	    rows = stty_rows
> -	    break
> -	end
> -    end
> -    f:close()
> -    return rows
> -end
> -
> -function container_sort(a, b)
> -    if (optarg["r"]) then
> -	if     (optarg["s"] == "n") then return (a > b)
> -	elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos < stats[b].cpu_use_nanos)
> -	elseif (optarg["s"] == "d") then return (stats[a].blkio < stats[b].blkio)
> -	elseif (optarg["s"] == "m") then return (stats[a].mem_used < stats[b].mem_used)
> -	elseif (optarg["s"] == "k") then return (stats[a].kmem_used < stats[b].kmem_used)
> -	end
> -    else
> -	if     (optarg["s"] == "n") then return (a < b)
> -	elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos > stats[b].cpu_use_nanos)
> -	elseif (optarg["s"] == "d") then return (stats[a].blkio > stats[b].blkio)
> -	elseif (optarg["s"] == "m") then return (stats[a].mem_used > stats[b].mem_used)
> -	elseif (optarg["s"] == "k") then return (stats[a].kmem_used > stats[b].kmem_used)
> -	end
> -    end
> -end
> -
> -function container_list_update()
> -    local now_running
> -
> -    now_running = lxc.containers_running(true)
> -
> -    -- check for newly started containers
> -    for _,v in ipairs(now_running) do
> -	if (containers[v] == nil) then
> -	    local ct = lxc.container:new(v)
> -	    -- note, this is a "mixed" table, ie both dictionary and list
> -	    containers[v] = ct
> -	    table.insert(containers, v)
> -	end
> -    end
> -
> -    -- check for newly stopped containers
> -    local indx = 1
> -    while (indx <= #containers) do
> -	local ctname = containers[indx]
> -	if (now_running[ctname] == nil) then
> -	    containers[ctname] = nil
> -	    stats[ctname] = nil
> -	    table.remove(containers, indx)
> -	else
> -	    indx = indx + 1
> -	end
> -    end
> -
> -    -- get stats for all current containers and resort the list
> -    lxc.stats_clear(stats_total)
> -    for _,ctname in ipairs(containers) do
> -	stats[ctname] = containers[ctname]:stats_get(stats_total)
> -    end
> -    table.sort(containers, container_sort)
> -end
> -
> -function stats_print_header(stats_total)
> -    printf(TERMRVRS .. TERMBOLD)
> -    printf("%-15s %8s %8s %8s %10s %10s", "Container", "CPU",  "CPU",  "CPU",  "BlkIO", "Mem")
> -    if (stats_total.kmem_used > 0) then printf(" %10s", "KMem") end
> -    printf("\n")
> -
> -    printf("%-15s %8s %8s %8s %10s %10s", "Name",      "Used", "Sys",  "User", "Total", "Used")
> -    if (stats_total.kmem_used > 0) then printf(" %10s", "Used") end
> -    printf("\n")
> -    printf(TERMNORM)
> -end
> -
> -function stats_print(name, stats, stats_total)
> -    printf("%-15s %8.2f %8.2f %8.2f %10s %10s",
> -	   name,
> -	   stats.cpu_use_nanos / 1000000000,
> -	   stats.cpu_use_sys  / USER_HZ,
> -	   stats.cpu_use_user / USER_HZ,
> -	   strsisize(stats.blkio),
> -	   strsisize(stats.mem_used))
> -    if (stats_total.kmem_used > 0) then
> -	printf(" %10s", strsisize(stats.kmem_used))
> -    end
> -end
> -
> -function usage()
> -    printf("Usage: lxc-top [options]\n" ..
> -	"  -h|--help      print this help message\n" ..
> -	"  -m|--max       display maximum number of containers\n" ..
> -	"  -d|--delay     delay in seconds between refreshes (default: 3.0)\n" ..
> -	"  -s|--sort      sort by [n,c,d,m] (default: n) where\n" ..
> -	"                 n = Name\n" ..
> -	"                 c = CPU use\n" ..
> -	"                 d = Disk I/O use\n" ..
> -	"                 m = Memory use\n" ..
> -	"                 k = Kernel memory use\n" ..
> -	"  -r|--reverse   sort in reverse (descending) order\n"
> -    )
> -    os.exit(1)
> -end
> -
> -local long_opts = {
> -    help      = "h",
> -    delay     = "d",
> -    max       = "m",
> -    reverse   = "r",
> -    sort      = "s",
> -}
> -
> -optarg,optind = alt_getopt.get_opts (arg, "hd:m:rs:", long_opts)
> -optarg["d"] = tonumber(optarg["d"]) or 3.0
> -optarg["m"] = tonumber(optarg["m"]) or tonumber(tty_lines() - 3)
> -optarg["r"] = optarg["r"] or false
> -optarg["s"] = optarg["s"] or "n"
> -if (optarg["h"] ~= nil) then
> -    usage()
> -end
> -
> -while true
> -do
> -    container_list_update()
> -    -- if some terminal we care about doesn't support the simple escapes, we
> -    -- may fall back to this, or ncurses. ug.
> -    --os.execute("tput clear")
> -    printf(TERMCLEAR)
> -    stats_print_header(stats_total)
> -    for index,ctname in ipairs(containers) do
> -	stats_print(ctname, stats[ctname], stats_total)
> -	printf("\n")
> -	if (index >= optarg["m"]) then
> -	    break
> -	end
> -    end
> -    stats_print(string.format("TOTAL (%-2d)", #containers), stats_total, stats_total)
> -    io.flush()
> -    core.usleep(optarg["d"] * 1000000)
> -end
> diff --git a/src/lxc/lxc-top.c b/src/lxc/lxc-top.c
> new file mode 100644
> index 0000000..fe89cc5
> --- /dev/null
> +++ b/src/lxc/lxc-top.c
> @@ -0,0 +1,516 @@
> +/*
> + * lxc: linux Container library
> + *
> + * Copyright © 2014 Oracle.
> + *
> + * Authors:
> + * Dwight Engen <dwight.engen at 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 memsw_used;
> +	uint64_t memsw_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 (!cols[j])
> +					goto err1;
> +				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->memsw_used    = stat_get_int(c, "memory.memsw.usage_in_bytes");
> +	ct->stats->memsw_limit   = stat_get_int(c, "memory.memsw.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->memsw_used    = total->memsw_used    + ct->stats->memsw_used;
> +		total->memsw_limit   = total->memsw_limit   + ct->stats->memsw_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->memsw_used < ct1->stats->memsw_used;
> +	return ct1->stats->memsw_used < ct2->stats->memsw_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) {
> +			printf("stats free\n");
> +			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);
> +		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;
> +}
> diff --git a/src/lxc/lxc-top.lua b/src/lxc/lxc-top.lua
> new file mode 100755
> index 0000000..b5b3a69
> --- /dev/null
> +++ b/src/lxc/lxc-top.lua
> @@ -0,0 +1,243 @@
> +#!/usr/bin/env lua
> +--
> +-- top(1) like monitor for lxc containers
> +--
> +-- Copyright © 2012 Oracle.
> +--
> +-- Authors:
> +-- Dwight Engen <dwight.engen at oracle.com>
> +--
> +-- This library is free software; you can redistribute it and/or modify
> +-- it under the terms of the GNU General Public License version 2, as
> +-- published by the Free Software Foundation.
> +--
> +-- This program 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 General Public License for more details.
> +--
> +-- You should have received a copy of the GNU General Public License along
> +-- with this program; if not, write to the Free Software Foundation, Inc.,
> +-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> +--
> +
> +local lxc    = require("lxc")
> +local core   = require("lxc.core")
> +local getopt = require("alt_getopt")
> +
> +local USER_HZ   = 100
> +local ESC       = string.format("%c", 27)
> +local TERMCLEAR = ESC.."[H"..ESC.."[J"
> +local TERMNORM  = ESC.."[0m"
> +local TERMBOLD  = ESC.."[1m"
> +local TERMRVRS  = ESC.."[7m"
> +
> +local containers = {}
> +local stats = {}
> +local stats_total = {}
> +local max_containers
> +
> +function printf(...)
> +    local function wrapper(...) io.write(string.format(...)) end
> +    local status, result = pcall(wrapper, ...)
> +    if not status then
> +	error(result, 2)
> +    end
> +end
> +
> +function string:split(delim, max_cols)
> +    local cols = {}
> +    local start = 1
> +    local nextc
> +    repeat
> +	nextc = string.find(self, delim, start)
> +	if (nextc and #cols ~= max_cols - 1) then
> +	    table.insert(cols, string.sub(self, start, nextc-1))
> +	    start = nextc + #delim
> +	else
> +	    table.insert(cols, string.sub(self, start, string.len(self)))
> +	    nextc = nil
> +	end
> +    until nextc == nil or start > #self
> +    return cols
> +end
> +
> +function strsisize(size, width)
> +    local KiB = 1024
> +    local MiB = 1048576
> +    local GiB = 1073741824
> +    local TiB = 1099511627776
> +    local PiB = 1125899906842624
> +    local EiB = 1152921504606846976
> +    local ZiB = 1180591620717411303424
> +
> +    if (size >= ZiB) then
> +	return string.format("%d.%2.2d ZB", size / ZiB, (math.floor(size % ZiB) * 100) / ZiB)
> +    end
> +    if (size >= EiB) then
> +	return string.format("%d.%2.2d EB", size / EiB, (math.floor(size % EiB) * 100) / EiB)
> +    end
> +    if (size >= PiB) then
> +	return string.format("%d.%2.2d PB", size / PiB, (math.floor(size % PiB) * 100) / PiB)
> +    end
> +    if (size >= TiB) then
> +	return string.format("%d.%2.2d TB", size / TiB, (math.floor(size % TiB) * 100) / TiB)
> +    end
> +    if (size >= GiB) then
> +	return string.format("%d.%2.2d GB", size / GiB, (math.floor(size % GiB) * 100) / GiB)
> +    end
> +    if (size >= MiB) then
> +	return string.format("%d.%2.2d MB", size / MiB, (math.floor(size % MiB) * 1000) / (MiB * 10))
> +    end
> +    if (size >= KiB) then
> +	return string.format("%d.%2.2d KB", size / KiB, (math.floor(size % KiB) * 1000) / (KiB * 10))
> +    end
> +    return string.format("%3d.00   ", size)
> +end
> +
> +function tty_lines()
> +    local rows = 25
> +    local f = assert(io.popen("stty -a | head -n 1"))
> +    for line in f:lines() do
> +	local stty_rows
> +	_,_,stty_rows = string.find(line, "rows (%d+)")
> +	if (stty_rows ~= nil) then
> +	    rows = stty_rows
> +	    break
> +	end
> +    end
> +    f:close()
> +    return rows
> +end
> +
> +function container_sort(a, b)
> +    if (optarg["r"]) then
> +	if     (optarg["s"] == "n") then return (a > b)
> +	elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos < stats[b].cpu_use_nanos)
> +	elseif (optarg["s"] == "d") then return (stats[a].blkio < stats[b].blkio)
> +	elseif (optarg["s"] == "m") then return (stats[a].mem_used < stats[b].mem_used)
> +	elseif (optarg["s"] == "k") then return (stats[a].kmem_used < stats[b].kmem_used)
> +	end
> +    else
> +	if     (optarg["s"] == "n") then return (a < b)
> +	elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos > stats[b].cpu_use_nanos)
> +	elseif (optarg["s"] == "d") then return (stats[a].blkio > stats[b].blkio)
> +	elseif (optarg["s"] == "m") then return (stats[a].mem_used > stats[b].mem_used)
> +	elseif (optarg["s"] == "k") then return (stats[a].kmem_used > stats[b].kmem_used)
> +	end
> +    end
> +end
> +
> +function container_list_update()
> +    local now_running
> +
> +    now_running = lxc.containers_running(true)
> +
> +    -- check for newly started containers
> +    for _,v in ipairs(now_running) do
> +	if (containers[v] == nil) then
> +	    local ct = lxc.container:new(v)
> +	    -- note, this is a "mixed" table, ie both dictionary and list
> +	    containers[v] = ct
> +	    table.insert(containers, v)
> +	end
> +    end
> +
> +    -- check for newly stopped containers
> +    local indx = 1
> +    while (indx <= #containers) do
> +	local ctname = containers[indx]
> +	if (now_running[ctname] == nil) then
> +	    containers[ctname] = nil
> +	    stats[ctname] = nil
> +	    table.remove(containers, indx)
> +	else
> +	    indx = indx + 1
> +	end
> +    end
> +
> +    -- get stats for all current containers and resort the list
> +    lxc.stats_clear(stats_total)
> +    for _,ctname in ipairs(containers) do
> +	stats[ctname] = containers[ctname]:stats_get(stats_total)
> +    end
> +    table.sort(containers, container_sort)
> +end
> +
> +function stats_print_header(stats_total)
> +    printf(TERMRVRS .. TERMBOLD)
> +    printf("%-15s %8s %8s %8s %10s %10s", "Container", "CPU",  "CPU",  "CPU",  "BlkIO", "Mem")
> +    if (stats_total.kmem_used > 0) then printf(" %10s", "KMem") end
> +    printf("\n")
> +
> +    printf("%-15s %8s %8s %8s %10s %10s", "Name",      "Used", "Sys",  "User", "Total", "Used")
> +    if (stats_total.kmem_used > 0) then printf(" %10s", "Used") end
> +    printf("\n")
> +    printf(TERMNORM)
> +end
> +
> +function stats_print(name, stats, stats_total)
> +    printf("%-15s %8.2f %8.2f %8.2f %10s %10s",
> +	   name,
> +	   stats.cpu_use_nanos / 1000000000,
> +	   stats.cpu_use_sys  / USER_HZ,
> +	   stats.cpu_use_user / USER_HZ,
> +	   strsisize(stats.blkio),
> +	   strsisize(stats.mem_used))
> +    if (stats_total.kmem_used > 0) then
> +	printf(" %10s", strsisize(stats.kmem_used))
> +    end
> +end
> +
> +function usage()
> +    printf("Usage: lxc-top [options]\n" ..
> +	"  -h|--help      print this help message\n" ..
> +	"  -m|--max       display maximum number of containers\n" ..
> +	"  -d|--delay     delay in seconds between refreshes (default: 3.0)\n" ..
> +	"  -s|--sort      sort by [n,c,d,m] (default: n) where\n" ..
> +	"                 n = Name\n" ..
> +	"                 c = CPU use\n" ..
> +	"                 d = Disk I/O use\n" ..
> +	"                 m = Memory use\n" ..
> +	"                 k = Kernel memory use\n" ..
> +	"  -r|--reverse   sort in reverse (descending) order\n"
> +    )
> +    os.exit(1)
> +end
> +
> +local long_opts = {
> +    help      = "h",
> +    delay     = "d",
> +    max       = "m",
> +    reverse   = "r",
> +    sort      = "s",
> +}
> +
> +optarg,optind = alt_getopt.get_opts (arg, "hd:m:rs:", long_opts)
> +optarg["d"] = tonumber(optarg["d"]) or 3.0
> +optarg["m"] = tonumber(optarg["m"]) or tonumber(tty_lines() - 3)
> +optarg["r"] = optarg["r"] or false
> +optarg["s"] = optarg["s"] or "n"
> +if (optarg["h"] ~= nil) then
> +    usage()
> +end
> +
> +while true
> +do
> +    container_list_update()
> +    -- if some terminal we care about doesn't support the simple escapes, we
> +    -- may fall back to this, or ncurses. ug.
> +    --os.execute("tput clear")
> +    printf(TERMCLEAR)
> +    stats_print_header(stats_total)
> +    for index,ctname in ipairs(containers) do
> +	stats_print(ctname, stats[ctname], stats_total)
> +	printf("\n")
> +	if (index >= optarg["m"]) then
> +	    break
> +	end
> +    end
> +    stats_print(string.format("TOTAL (%-2d)", #containers), stats_total, stats_total)
> +    io.flush()
> +    core.usleep(optarg["d"] * 1000000)
> +end
> -- 
> 1.9.3
> 
> _______________________________________________
> lxc-devel mailing list
> lxc-devel at lists.linuxcontainers.org
> http://lists.linuxcontainers.org/listinfo/lxc-devel


More information about the lxc-devel mailing list