[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