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

Dwight Engen dwight.engen at oracle.com
Tue Sep 23 13:59:11 UTC 2014


On Mon, 22 Sep 2014 23:52:18 +0000
Serge Hallyn <serge.hallyn at ubuntu.com> wrote:

> Quoting Dwight Engen (dwight.engen at oracle.com):
> > - 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>
> 
> Sorry, a few more comments below.  Also, I noticed that when I run
> this as root then I can control it, i.e. 'q' to quit;  but when i run
> it as non-root i cannot.  Do you know offhand why that would be?

No problem, I'll fix those up in a v3. As far as using 'q', I'm not
sure why that wouldn't be working, it does here for non-root. Maybe
your terminal isn't getting into non-cannonical mode? You could try the
following patch for debugging and also try hitting 'q' followed by
'enter'.

diff --git a/src/lxc/lxc_top.c b/src/lxc/lxc_top.c
index f74c846..991d829 100644
--- a/src/lxc/lxc_top.c
+++ b/src/lxc/lxc_top.c
@@ -166,6 +166,8 @@ static int stdin_handler(int fd, uint32_t events, void *data,
                rc = read(fd, in_char, sizeof(*in_char));
                if (rc <= 0)
                        *in_char = '\0';
+               printf("stdin_handler got char '%c'\n", *in_char);
+               sleep(2);
        }

        if (events & EPOLLHUP)


> > ---
> > v2:
> >  - name of source file should be lxc_top.c not lxc-top.c
> >  - distribute lxc-top.lua in tarball as reference example, but
> > don't install it into bin
> >  - use mem.usage_in_bytes instead of memsw.usage_in_bytes, this was
> > just an error introduced in porting
> > 
> >  doc/lxc-top.sgml.in |  26 +--
> >  src/lxc/Makefile.am |   8 +-
> >  src/lxc/lxc-top     | 243 -------------------------
> >  src/lxc/lxc-top.lua | 243 +++++++++++++++++++++++++
> >  src/lxc/lxc_top.c   | 510
> > ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files
> > changed, 762 insertions(+), 268 deletions(-) delete mode 100755
> > src/lxc/lxc-top create mode 100755 src/lxc/lxc-top.lua
> >  create mode 100644 src/lxc/lxc_top.c
> > 
> > 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..c65a15d 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
> > 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.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
> > diff --git a/src/lxc/lxc_top.c b/src/lxc/lxc_top.c
> > new file mode 100644
> > index 0000000..f74c846
> > --- /dev/null
> > +++ b/src/lxc/lxc_top.c
> > @@ -0,0 +1,510 @@
> > +/*
> > + * 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 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])
> 
> why this check given the loop control?
> 
> > +					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->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) {
> > +			printf("stats free\n");
> 
> leftover debugging?
> 
> > +			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);
> 
> check for success?
> 
> > +		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;
> > +}
> > -- 
> > 1.9.3
> > 
> > _______________________________________________
> > lxc-devel mailing list
> > lxc-devel at lists.linuxcontainers.org
> > http://lists.linuxcontainers.org/listinfo/lxc-devel
> _______________________________________________
> 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