[lxc-devel] [PATCH v2] add lua binding for the lxc API
Stéphane Graber
stgraber at ubuntu.com
Mon Jan 28 23:06:56 UTC 2013
On 01/24/2013 11:42 AM, Dwight Engen wrote:
> The lua binding is based closely on the python binding. Also included are
> a test program for excercising the binding, and an lxc-top utility for
> showing statistics on running containers.
>
> Signed-off-by: Dwight Engen <dwight.engen at oracle.com>
Test build passed and the configure.ac/automake integration looks good.
I haven't looked at the code in much more depth than that, but it looks
clean, self-contained and builds, so good enough for me :)
Acked-by: Stéphane Graber <stgraber at ubuntu.com>
Pushed to staging. Thanks!
> ---
> Makefile.am | 7 +-
> configure.ac | 20 +++
> doc/Makefile.am | 4 +
> doc/lxc-top.sgml.in | 164 +++++++++++++++++
> lxc.spec.in | 13 ++
> src/Makefile.am | 2 +-
> src/lua-lxc/Makefile.am | 26 +++
> src/lua-lxc/core.c | 382 +++++++++++++++++++++++++++++++++++++++
> src/lua-lxc/lxc.lua | 412 +++++++++++++++++++++++++++++++++++++++++++
> src/lua-lxc/test/apitest.lua | 302 +++++++++++++++++++++++++++++++
> src/lxc/Makefile.am | 8 +-
> src/lxc/lxc-top | 242 +++++++++++++++++++++++++
> 12 files changed, 1579 insertions(+), 3 deletions(-)
> create mode 100644 doc/lxc-top.sgml.in
> create mode 100644 src/lua-lxc/Makefile.am
> create mode 100644 src/lua-lxc/core.c
> create mode 100755 src/lua-lxc/lxc.lua
> create mode 100755 src/lua-lxc/test/apitest.lua
> create mode 100755 src/lxc/lxc-top
>
> diff --git a/Makefile.am b/Makefile.am
> index 3fb453e..53473ee 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -5,9 +5,14 @@ ACLOCAL_AMFLAGS = -I config
> SUBDIRS = config src templates doc
> DIST_SUBDIRS = config src templates doc
> EXTRA_DIST = autogen.sh lxc.spec CONTRIBUTING MAINTAINERS ChangeLog
> +RPMARGS =
> +
> +if ENABLE_LUA
> +RPMARGS += --with lua
> +endif
>
> if ENABLE_PYTHON
> -RPMARGS = --with python
> +RPMARGS += --with python
> endif
>
> pcdatadir = $(libdir)/pkgconfig
> diff --git a/configure.ac b/configure.ac
> index d1f5ad9..02c75a1 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -137,6 +137,23 @@ AM_COND_IF([ENABLE_PYTHON],
> PKG_CHECK_MODULES([PYTHONDEV], [python3 >= 3.2],[],[AC_MSG_ERROR([You must install python3-dev])])
> AC_DEFINE_UNQUOTED([ENABLE_PYTHON], 1, [Python3 is available])])
>
> +# Lua module and scripts
> +if test x"$with_distro" = "xdebian" -o x"$with_distro" = "xubuntu" ; then
> + LUAPKGCONFIG=lua5.1
> +else
> + LUAPKGCONFIG=lua
> +fi
> +
> +AC_ARG_ENABLE([lua],
> + [AC_HELP_STRING([--enable-lua], [enable lua binding])],
> + [enable_lua=yes], [enable_lua=no])
> +
> +AM_CONDITIONAL([ENABLE_LUA], [test "x$enable_lua" = "xyes"])
> +
> +AM_COND_IF([ENABLE_LUA],
> + [PKG_CHECK_MODULES([LUA], [$LUAPKGCONFIG >= 5.1],[],[AC_MSG_ERROR([You must install lua-devel for lua 5.1])])
> + AC_DEFINE_UNQUOTED([ENABLE_LUA], 1, [Lua is available])])
> +
> # Optional test binaries
> AC_ARG_ENABLE([tests],
> [AC_HELP_STRING([--enable-tests], [build test/example binaries])],
> @@ -268,6 +285,7 @@ AC_CONFIG_FILES([
> doc/lxc-wait.sgml
> doc/lxc-ls.sgml
> doc/lxc-ps.sgml
> + doc/lxc-top.sgml
> doc/lxc-cgroup.sgml
> doc/lxc-kill.sgml
> doc/lxc-attach.sgml
> @@ -321,6 +339,8 @@ AC_CONFIG_FILES([
> src/python-lxc/lxc/__init__.py
> src/python-lxc/examples/api_test.py
>
> + src/lua-lxc/Makefile
> +
> src/tests/Makefile
> ])
> AC_CONFIG_COMMANDS([default],[[]],[[]])
> diff --git a/doc/Makefile.am b/doc/Makefile.am
> index 86de2fe..e539254 100644
> --- a/doc/Makefile.am
> +++ b/doc/Makefile.am
> @@ -34,6 +34,10 @@ else
> man_MANS += legacy/lxc-ls.1
> endif
>
> +if ENABLE_LUA
> + man_MANS += lxc-top.1
> +endif
> +
> %.1 : %.sgml
> $(db2xman) $<
> test "$(shell basename $@)" != "$@" && mv $(shell basename $@) $@ || true
> diff --git a/doc/lxc-top.sgml.in b/doc/lxc-top.sgml.in
> new file mode 100644
> index 0000000..2a4f835
> --- /dev/null
> +++ b/doc/lxc-top.sgml.in
> @@ -0,0 +1,164 @@
> +<!--
> +
> +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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
> +
> +-->
> +
> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
> +
> +<!ENTITY seealso SYSTEM "@builddir@/see_also.sgml">
> +]>
> +
> +<refentry>
> +
> + <docinfo><date>@LXC_GENERATE_DATE@</date></docinfo>
> +
> + <refmeta>
> + <refentrytitle>lxc-top</refentrytitle>
> + <manvolnum>1</manvolnum>
> + </refmeta>
> +
> + <refnamediv>
> + <refname>lxc-top</refname>
> +
> + <refpurpose>
> + monitor container statistics
> + </refpurpose>
> + </refnamediv>
> +
> + <refsynopsisdiv>
> + <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>
> + </cmdsynopsis>
> + </refsynopsisdiv>
> +
> + <refsect1>
> + <title>Description</title>
> + <para>
> + <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.
> + </para>
> + </refsect1>
> +
> + <refsect1>
> + <title>Options</title>
> + <variablelist>
> +
> + <varlistentry>
> + <term>
> + <option><optional>-m, --max <replaceable>count</replaceable></optional></option>
> + </term>
> + <listitem>
> + <para>
> + Limit the number of containers displayed to
> + <replaceable>count</replaceable>.
> + </para>
> + </listitem>
> + </varlistentry>
> +
> + <varlistentry>
> + <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.
> + </para>
> + </listitem>
> + </varlistentry>
> + <varlistentry>
> + <term>
> + <option><optional>-s, --sort <replaceable>sortby</replaceable></optional></option>
> + </term>
> + <listitem>
> + <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 to sort by name, cpu use, disk I/O, or
> + memory use respectively. The default is 'n'.
> + </para>
> + </listitem>
> + </varlistentry>
> + <varlistentry>
> + <term>
> + <option><optional>-r, --reverse</optional></option>
> + </term>
> + <listitem>
> + <para>
> + Reverse the default sort order. By default, names sort in
> + ascending alphabetical order and values sort in descending
> + amounts (ie. largest value first).
> + </para>
> + </listitem>
> + </varlistentry>
> + </variablelist>
> + </refsect1>
> +
> + <refsect1>
> + <title>Example</title>
> + <variablelist>
> + <varlistentry>
> + <term>lxc-top --delay 1 --sort m</term>
> + <listitem>
> + <para>
> + Display containers, updating every second, sorted by memory use.
> + </para>
> + </listitem>
> + </varlistentry>
> + </variablelist>
> + </refsect1>
> +
> + &seealso;
> +
> + <refsect1>
> + <title>Author</title>
> + <para>Dwight Engen <email>dwight.engen at oracle.com</email></para>
> + </refsect1>
> +
> +</refentry>
> +
> +<!-- Keep this comment at the end of the file
> +Local variables:
> +mode: sgml
> +sgml-omittag:t
> +sgml-shorttag:t
> +sgml-minimize-attributes:nil
> +sgml-always-quote-attributes:t
> +sgml-indent-step:2
> +sgml-indent-data:t
> +sgml-parent-document:nil
> +sgml-default-dtd-file:nil
> +sgml-exposed-tags:nil
> +sgml-local-catalogs:nil
> +sgml-local-ecat-files:nil
> +End:
> +-->
> diff --git a/lxc.spec.in b/lxc.spec.in
> index 65997d9..9fbd6b0 100644
> --- a/lxc.spec.in
> +++ b/lxc.spec.in
> @@ -38,6 +38,12 @@ Requires: python3
> BuildRequires: python3-devel
> %endif
>
> +%define with_lua %{?_with_lua: 1} %{?!_with_lua: 0}
> +%if %{with_lua}
> +Requires: lua-filesystem
> +BuildRequires: lua-devel
> +%endif
> +
> %description
>
> The package "%{name}" provides the command lines to create and manage
> @@ -69,6 +75,9 @@ development of the linux containers.
> %setup
> %build
> PATH=$PATH:/usr/sbin:/sbin %configure $args \
> +%if %{with_lua}
> + --enable-lua \
> +%endif
> %if %{with_python}
> --enable-python \
> %endif
> @@ -107,6 +116,10 @@ rm -rf %{buildroot}
> %defattr(-,root,root)
> %{_libdir}/*.so.*
> %{_libdir}/%{name}
> +%if %{with_lua}
> +%{_datadir}/lua
> +%{_libdir}/lua
> +%endif
> %if %{with_python}
> %{_libdir}/python*
> %endif
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 4e4d66b..c96cbe7 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -1 +1 @@
> -SUBDIRS = lxc tests python-lxc
> +SUBDIRS = lxc tests python-lxc lua-lxc
> diff --git a/src/lua-lxc/Makefile.am b/src/lua-lxc/Makefile.am
> new file mode 100644
> index 0000000..f05eb72
> --- /dev/null
> +++ b/src/lua-lxc/Makefile.am
> @@ -0,0 +1,26 @@
> +if ENABLE_LUA
> +
> +luadir=$(datadir)/lua/5.1
> +sodir=$(libdir)/lua/5.1/lxc
> +
> +lua_SCRIPTS=lxc.lua
> +EXTRA_DIST=lxc.lua
> +
> +so_PROGRAMS = core.so
> +
> +core_so_SOURCES = core.c
> +
> +AM_CFLAGS=-I$(top_srcdir)/src $(LUA_CFLAGS) -DVERSION=\"$(VERSION)\" -DLXCPATH=\"$(LXCPATH)\"
> +
> +core_so_CFLAGS = -fPIC -DPIC $(AM_CFLAGS)
> +
> +core_so_LDFLAGS = \
> + -shared \
> + -L$(top_srcdir)/src/lxc \
> + -Wl,-soname,core.so.$(firstword $(subst ., ,$(VERSION)))
> +
> +core_so_LDADD = -llxc $(LUA_LIBS)
> +
> +lxc.lua:
> +
> +endif
> diff --git a/src/lua-lxc/core.c b/src/lua-lxc/core.c
> new file mode 100644
> index 0000000..5c47aed
> --- /dev/null
> +++ b/src/lua-lxc/core.c
> @@ -0,0 +1,382 @@
> +/*
> + * lua-lxc: lua bindings for lxc
> + *
> + * 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.
> + */
> +
> +#define LUA_LIB
> +#define _GNU_SOURCE
> +#include <lua.h>
> +#include <lauxlib.h>
> +#include <string.h>
> +#include <lxc/lxccontainer.h>
> +
> +#ifdef NO_CHECK_UDATA
> +#define checkudata(L,i,tname) lua_touserdata(L, i)
> +#else
> +#define checkudata(L,i,tname) luaL_checkudata(L, i, tname)
> +#endif
> +
> +#define lua_boxpointer(L,u) \
> + (*(void **) (lua_newuserdata(L, sizeof(void *))) = (u))
> +
> +#define lua_unboxpointer(L,i,tname) \
> + (*(void **) (checkudata(L, i, tname)))
> +
> +#define CONTAINER_TYPENAME "lxc.container"
> +
> +static int container_new(lua_State *L)
> +{
> + const char *name = luaL_checkstring(L, 1);
> + struct lxc_container *c = lxc_container_new(name);
> +
> + if (c) {
> + lua_boxpointer(L, c);
> + luaL_getmetatable(L, CONTAINER_TYPENAME);
> + lua_setmetatable(L, -2);
> + } else {
> + lua_pushnil(L);
> + }
> + return 1;
> +}
> +
> +static int container_gc(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + /* XXX what to do if this fails? */
> + lxc_container_put(c);
> + return 0;
> +}
> +
> +static int container_config_file_name(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + char *config_file_name;
> +
> + config_file_name = c->config_file_name(c);
> + lua_pushstring(L, config_file_name);
> + free(config_file_name);
> + return 1;
> +}
> +
> +static int container_defined(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + lua_pushboolean(L, !!c->is_defined(c));
> + return 1;
> +}
> +
> +static int container_name(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + lua_pushstring(L, c->name);
> + return 1;
> +}
> +
> +static int container_create(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + char *template_name = strdupa(luaL_checkstring(L, 2));
> + int argc = lua_gettop(L);
> + char **argv;
> + int i;
> +
> + argv = alloca((argc+1) * sizeof(char *));
> + for (i = 0; i < argc-2; i++)
> + argv[i] = strdupa(luaL_checkstring(L, i+3));
> + argv[i] = NULL;
> +
> + lua_pushboolean(L, !!c->create(c, template_name, argv));
> + return 1;
> +}
> +
> +static int container_destroy(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + lua_pushboolean(L, !!c->destroy(c));
> + return 1;
> +}
> +
> +/* container state */
> +static int container_start(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + int argc = lua_gettop(L);
> + char **argv = NULL;
> + int i,j;
> + int useinit = 0;
> +
> + if (argc > 1) {
> + argv = alloca((argc+1) * sizeof(char *));
> + for (i = 0, j = 0; i < argc-1; i++) {
> + const char *arg = luaL_checkstring(L, i+2);
> +
> + if (!strcmp(arg, "useinit"))
> + useinit = 1;
> + else
> + argv[j++] = strdupa(arg);
> + }
> + argv[j] = NULL;
> + }
> +
> + c->want_daemonize(c);
> + lua_pushboolean(L, !!c->start(c, useinit, argv));
> + return 1;
> +}
> +
> +static int container_stop(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + lua_pushboolean(L, !!c->stop(c));
> + return 1;
> +}
> +
> +static int container_shutdown(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + int timeout = luaL_checkinteger(L, 2);
> +
> + lua_pushboolean(L, !!c->shutdown(c, timeout));
> + return 1;
> +}
> +
> +static int container_wait(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + const char *state = luaL_checkstring(L, 2);
> + int timeout = luaL_checkinteger(L, 3);
> +
> + lua_pushboolean(L, !!c->wait(c, state, timeout));
> + return 1;
> +}
> +
> +static int container_freeze(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + lua_pushboolean(L, !!c->freeze(c));
> + return 1;
> +}
> +
> +static int container_unfreeze(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + lua_pushboolean(L, !!c->unfreeze(c));
> + return 1;
> +}
> +
> +static int container_running(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + lua_pushboolean(L, !!c->is_running(c));
> + return 1;
> +}
> +
> +static int container_state(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + lua_pushstring(L, c->state(c));
> + return 1;
> +}
> +
> +static int container_init_pid(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> +
> + lua_pushinteger(L, c->init_pid(c));
> + return 1;
> +}
> +
> +/* configuration file methods */
> +static int container_load_config(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + int arg_cnt = lua_gettop(L);
> + const char *alt_path = NULL;
> +
> + if (arg_cnt > 1)
> + alt_path = luaL_checkstring(L, 2);
> +
> + lua_pushboolean(L, !!c->load_config(c, alt_path));
> + return 1;
> +}
> +
> +static int container_save_config(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + int arg_cnt = lua_gettop(L);
> + const char *alt_path = NULL;
> +
> + if (arg_cnt > 1)
> + alt_path = luaL_checkstring(L, 2);
> +
> + lua_pushboolean(L, !!c->save_config(c, alt_path));
> + return 1;
> +}
> +
> +static int container_clear_config_item(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + const char *key = luaL_checkstring(L, 2);
> +
> + lua_pushboolean(L, !!c->clear_config_item(c, key));
> + return 1;
> +}
> +
> +static int container_get_config_item(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + const char *key = luaL_checkstring(L, 2);
> + int len;
> + char *value;
> +
> + len = c->get_config_item(c, key, NULL, 0);
> + if (len <= 0)
> + goto not_found;
> +
> + value = alloca(sizeof(char)*len + 1);
> + if (c->get_config_item(c, key, value, len + 1) != len)
> + goto not_found;
> +
> + lua_pushstring(L, value);
> + return 1;
> +
> +not_found:
> + lua_pushnil(L);
> + return 1;
> +}
> +
> +static int container_set_config_item(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + const char *key = luaL_checkstring(L, 2);
> + const char *value = luaL_checkstring(L, 3);
> +
> + lua_pushboolean(L, !!c->set_config_item(c, key, value));
> + return 1;
> +}
> +
> +static int container_get_keys(lua_State *L)
> +{
> + struct lxc_container *c = lua_unboxpointer(L, 1, CONTAINER_TYPENAME);
> + const char *key = NULL;
> + int len;
> + char *value;
> + int arg_cnt = lua_gettop(L);
> +
> + if (arg_cnt > 1)
> + key = luaL_checkstring(L, 2);
> +
> + len = c->get_keys(c, key, NULL, 0);
> + if (len <= 0)
> + goto not_found;
> +
> + value = alloca(sizeof(char)*len + 1);
> + if (c->get_keys(c, key, value, len + 1) != len)
> + goto not_found;
> +
> + lua_pushstring(L, value);
> + return 1;
> +
> +not_found:
> + lua_pushnil(L);
> + return 1;
> +}
> +
> +static luaL_Reg lxc_container_methods[] =
> +{
> + {"create", container_create},
> + {"defined", container_defined},
> + {"destroy", container_destroy},
> + {"init_pid", container_init_pid},
> + {"name", container_name},
> + {"running", container_running},
> + {"state", container_state},
> + {"freeze", container_freeze},
> + {"unfreeze", container_unfreeze},
> + {"start", container_start},
> + {"stop", container_stop},
> + {"shutdown", container_shutdown},
> + {"wait", container_wait},
> +
> + {"config_file_name", container_config_file_name},
> + {"load_config", container_load_config},
> + {"save_config", container_save_config},
> + {"get_config_item", container_get_config_item},
> + {"set_config_item", container_set_config_item},
> + {"clear_config_item", container_clear_config_item},
> + {"get_keys", container_get_keys},
> + {NULL, NULL}
> +};
> +
> +static int lxc_version_get(lua_State *L) {
> + lua_pushstring(L, VERSION);
> + return 1;
> +}
> +
> +static int lxc_path_get(lua_State *L) {
> + lua_pushstring(L, LXCPATH);
> + return 1;
> +}
> +
> +static luaL_Reg lxc_lib_methods[] = {
> + {"version_get", lxc_version_get},
> + {"path_get", lxc_path_get},
> + {"container_new", container_new},
> + {NULL, NULL}
> +};
> +
> +static int lxc_lib_uninit(lua_State *L) {
> + (void) L;
> + /* this is where we would fini liblxc.so if we needed to */
> + return 0;
> +}
> +
> +LUALIB_API int luaopen_lxc_core(lua_State *L) {
> + /* this is where we would initialize liblxc.so if we needed to */
> +
> + luaL_register(L, "lxc", lxc_lib_methods);
> +
> + lua_newuserdata(L, 0);
> + lua_newtable(L); /* metatable */
> + lua_pushvalue(L, -1);
> + lua_pushliteral(L, "__gc");
> + lua_pushcfunction(L, lxc_lib_uninit);
> + lua_rawset(L, -3);
> + lua_setmetatable(L, -3);
> + lua_rawset(L, -3);
> +
> + luaL_newmetatable(L, CONTAINER_TYPENAME);
> + lua_pushvalue(L, -1); /* push metatable */
> + lua_pushstring(L, "__gc");
> + lua_pushcfunction(L, container_gc);
> + lua_settable(L, -3);
> + lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */
> + luaL_register(L, NULL, lxc_container_methods);
> + lua_pop(L, 1);
> + return 1;
> +}
> diff --git a/src/lua-lxc/lxc.lua b/src/lua-lxc/lxc.lua
> new file mode 100755
> index 0000000..c71de48
> --- /dev/null
> +++ b/src/lua-lxc/lxc.lua
> @@ -0,0 +1,412 @@
> +--
> +-- lua lxc module
> +--
> +-- 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 core = require("lxc.core")
> +local lfs = require("lfs")
> +local table = require("table")
> +local string = require("string")
> +local io = require("io")
> +module("lxc", package.seeall)
> +
> +local lxc_path
> +local cgroup_path
> +local log_level = 3
> +
> +-- the following two functions can be useful for debugging
> +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 log(level, ...)
> + if (log_level >= level) then
> + printf(os.date("%Y-%m-%d %T "))
> + printf(...)
> + 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 dirname(path)
> + local f,output
> + f = io.popen("dirname " .. path)
> + output = f:read('*all')
> + f:close()
> + return output:sub(1,-2)
> +end
> +
> +function basename(path, suffix)
> + local f,output
> + f = io.popen("basename " .. path .. " " .. (suffix or ""))
> + output = f:read('*all')
> + f:close()
> + return output:sub(1,-2)
> +end
> +
> +function cgroup_path_get()
> + local f,line,cgroup_path
> +
> + f = io.open("/proc/mounts", "r")
> + if (f) then
> + while true do
> + local c
> + line = f:read()
> + c = line:split(" ", 6)
> + if (c[1] == "cgroup") then
> + cgroup_path = dirname(c[2])
> + break
> + end
> + end
> + f:close()
> + end
> + if (not cgroup_path) then
> + cgroup_path = "/sys/fs/cgroup"
> + end
> + return cgroup_path
> +end
> +
> +-- container class
> +container = {}
> +container_mt = {}
> +container_mt.__index = container
> +
> +function container:new(lname)
> + local lcore
> + local lnetcfg = {}
> + local lstats = {}
> +
> + if lname then
> + lcore = core.container_new(lname)
> + end
> +
> + return setmetatable({ctname = lname, core = lcore, netcfg = lnetcfg, stats = lstats}, container_mt)
> +end
> +
> +-- methods interfacing to core functionality
> +function container:config_file_name()
> + return self.core:config_file_name()
> +end
> +
> +function container:defined()
> + return self.core:defined()
> +end
> +
> +function container:init_pid()
> + return self.core:init_pid()
> +end
> +
> +function container:name()
> + return self.core:name()
> +end
> +
> +function container:start()
> + return self.core:start()
> +end
> +
> +function container:stop()
> + return self.core:stop()
> +end
> +
> +function container:shutdown(timeout)
> + return self.core:shutdown(timeout)
> +end
> +
> +function container:wait(state, timeout)
> + return self.core:wait(state, timeout)
> +end
> +
> +function container:freeze()
> + return self.core:freeze()
> +end
> +
> +function container:unfreeze()
> + return self.core:unfreeze()
> +end
> +
> +function container:running()
> + return self.core:running()
> +end
> +
> +function container:state()
> + return self.core:state()
> +end
> +
> +function container:create(template, ...)
> + return self.core:create(template, ...)
> +end
> +
> +function container:destroy()
> + return self.core:destroy()
> +end
> +
> +function container:append_config_item(key, value)
> + return self.core:set_config_item(key, value)
> +end
> +
> +function container:clear_config_item(key)
> + return self.core:clear_config_item(key)
> +end
> +
> +function container:get_config_item(key)
> + local value
> + local vals = {}
> +
> + value = self.core:get_config_item(key)
> +
> + -- check if it is a single item
> + if (not value or not string.find(value, "\n")) then
> + return value
> + end
> +
> + -- it must be a list type item, make a table of it
> + vals = value:split("\n", 1000)
> + -- make it a "mixed" table, ie both dictionary and list for ease of use
> + for _,v in ipairs(vals) do
> + vals[v] = true
> + end
> + return vals
> +end
> +
> +function container:set_config_item(key, value)
> + return self.core:set_config_item(key, value)
> +end
> +
> +function container:get_keys(base)
> + local ktab = {}
> + local keys
> +
> + if (base) then
> + keys = self.core:get_keys(base)
> + base = base .. "."
> + else
> + keys = self.core:get_keys()
> + base = ""
> + end
> + if (keys == nil) then
> + return nil
> + end
> + keys = keys:split("\n", 1000)
> + for _,v in ipairs(keys) do
> + local config_item = base .. v
> + ktab[v] = self.core:get_config_item(config_item)
> + end
> + return ktab
> +end
> +
> +function container:load_config(alt_path)
> + if (alt_path) then
> + return self.core:load_config(alt_path)
> + else
> + return self.core:load_config()
> + end
> +end
> +
> +function container:save_config(alt_path)
> + if (alt_path) then
> + return self.core:save_config(alt_path)
> + else
> + return self.core:save_config()
> + end
> +end
> +
> +-- methods for stats collection from various cgroup files
> +-- read integers at given coordinates from a cgroup file
> +function container:stat_get_ints(controller, item, coords)
> + local f = io.open(cgroup_path.."/"..controller.."/lxc/"..self.ctname.."/"..item, "r")
> + local lines = {}
> + local result = {}
> +
> + if (not f) then
> + for k,c in ipairs(coords) do
> + table.insert(result, 0)
> + end
> + else
> + for line in f:lines() do
> + table.insert(lines, line)
> + end
> + f:close()
> + for k,c in ipairs(coords) do
> + local col
> +
> + col = lines[c[1]]:split(" ", 80)
> + local val = tonumber(col[c[2]])
> + table.insert(result, val)
> + end
> + end
> + return unpack(result)
> +end
> +
> +-- read an integer from a cgroup file
> +function container:stat_get_int(controller, item)
> + local f = io.open(cgroup_path.."/"..controller.."/lxc/"..self.ctname.."/"..item, "r")
> + if (not f) then
> + return 0
> + end
> +
> + local line = f:read()
> + f:close()
> + -- if line is nil (on an error like Operation not supported because
> + -- CONFIG_MEMCG_SWAP_ENABLED isn't enabled) return 0
> + return tonumber(line) or 0
> +end
> +
> +function container:stat_match_get_int(controller, item, match, column)
> + local val
> + local f = io.open(cgroup_path.."/"..controller.."/lxc/"..self.ctname.."/"..item, "r")
> + if (not f) then
> + return 0
> + end
> +
> + for line in f:lines() do
> + printf("matching line:%s with match:%s\n", line, match)
> + if (string.find(line, match)) then
> + local col
> +
> + col = line:split(" ", 80)
> + val = tonumber(col[column]) or 0
> + printf("found line!! val:%d\n", val)
> + end
> + end
> + f:close()
> + return val
> +end
> +
> +function stats_clear(stat)
> + stat.mem_used = 0
> + stat.mem_limit = 0
> + stat.memsw_used = 0
> + stat.memsw_limit = 0
> + stat.cpu_use_nanos = 0
> + stat.cpu_use_user = 0
> + stat.cpu_use_sys = 0
> + stat.blkio = 0
> +end
> +
> +function container:stats_get(total)
> + local stat = {}
> + stat.mem_used = self:stat_get_int("memory", "memory.usage_in_bytes")
> + stat.mem_limit = self:stat_get_int("memory", "memory.limit_in_bytes")
> + stat.memsw_used = self:stat_get_int("memory", "memory.memsw.usage_in_bytes")
> + stat.memsw_limit = self:stat_get_int("memory", "memory.memsw.limit_in_bytes")
> + stat.cpu_use_nanos = self:stat_get_int("cpuacct", "cpuacct.usage")
> + stat.cpu_use_user,
> + stat.cpu_use_sys = self:stat_get_ints("cpuacct", "cpuacct.stat", {{1, 2}, {2, 2}})
> + stat.blkio = self:stat_match_get_int("blkio", "blkio.throttle.io_service_bytes", "Total", 2)
> +
> + if (total) then
> + total.mem_used = total.mem_used + stat.mem_used
> + total.mem_limit = total.mem_limit + stat.mem_limit
> + total.memsw_used = total.memsw_used + stat.memsw_used
> + total.memsw_limit = total.memsw_limit + stat.memsw_limit
> + total.cpu_use_nanos = total.cpu_use_nanos + stat.cpu_use_nanos
> + total.cpu_use_user = total.cpu_use_user + stat.cpu_use_user
> + total.cpu_use_sys = total.cpu_use_sys + stat.cpu_use_sys
> + total.blkio = total.blkio + stat.blkio
> + end
> + return stat
> +end
> +
> +
> +
> +-- return configured containers found in LXC_PATH directory
> +function containers_configured(names_only)
> + local containers = {}
> +
> + for dir in lfs.dir(lxc_path) do
> + if (dir ~= "." and dir ~= "..")
> + then
> + local cfgfile = lxc_path .. "/" .. dir .. "/config"
> + local cfgattr = lfs.attributes(cfgfile)
> +
> + if (cfgattr and cfgattr.mode == "file") then
> + if (names_only) then
> + -- note, this is a "mixed" table, ie both dictionary and list
> + containers[dir] = true
> + table.insert(containers, dir)
> + else
> + local ct = container:new(dir)
> + -- note, this is a "mixed" table, ie both dictionary and list
> + containers[dir] = ct
> + table.insert(containers, dir)
> + end
> + end
> + end
> + end
> + table.sort(containers, function (a,b) return (a < b) end)
> + return containers
> +end
> +
> +-- return running containers found in cgroup fs
> +function containers_running(names_only)
> + local containers = {}
> + local attr
> +
> + -- the lxc directory won't exist if no containers has ever been started
> + attr = lfs.attributes(cgroup_path .. "/cpu/lxc")
> + if (not attr) then
> + return containers
> + end
> +
> + for file in lfs.dir(cgroup_path .. "/cpu/lxc") do
> + if (file ~= "." and file ~= "..")
> + then
> + local pathfile = cgroup_path .. "/cpu/lxc/" .. file
> + local attr = lfs.attributes(pathfile)
> +
> + if (attr.mode == "directory") then
> + if (names_only) then
> + -- note, this is a "mixed" table, ie both dictionary and list
> + containers[file] = true
> + table.insert(containers, file)
> + else
> + local ct = container:new(file)
> + -- note, this is a "mixed" table, ie both dictionary and list
> + containers[file] = ct
> + table.insert(containers, file)
> + end
> + end
> + end
> + end
> + table.sort(containers, function (a,b) return (a < b) end)
> + return containers
> +end
> +
> +lxc_path = core.path_get()
> +cgroup_path = cgroup_path_get()
> diff --git a/src/lua-lxc/test/apitest.lua b/src/lua-lxc/test/apitest.lua
> new file mode 100755
> index 0000000..14d2a9d
> --- /dev/null
> +++ b/src/lua-lxc/test/apitest.lua
> @@ -0,0 +1,302 @@
> +#!/usr/bin/env lua
> +--
> +-- test the lxc lua api
> +--
> +-- 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 getopt = require("alt_getopt")
> +
> +local LXC_PATH = lxc.path_get()
> +
> +local container
> +local cfg_containers = {}
> +local optarg = {}
> +local optind = {}
> +
> +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 log(level, ...)
> + if (optarg["v"] >= level) then
> + printf(os.date("%Y-%m-%d %T "))
> + printf(...)
> + printf("\n")
> + end
> +end
> +
> +function die(...)
> + printf(...)
> + os.exit(1)
> +end
> +
> +function test_global_info()
> + local cfg_containers
> + local run_containers
> +
> + log(0, "%-20s %s", "LXC version:", lxc.version_get())
> + log(0, "%-20s %s", "Container name:", optarg["n"])
> + if (optarg["c"]) then
> + log(0, "%-20s %s", "Creating container:", "yes")
> + log(0, "%-20s %s", "With template:", optarg["t"])
> + end
> + log(0, "%-20s %s", "Containers path:", LXC_PATH)
> +
> + cfg_containers = lxc.containers_configured()
> + log(0, "%-20s", "Containers configured:")
> + for _,v in ipairs(cfg_containers) do
> + log(0, " %s", v)
> + end
> +
> + run_containers = lxc.containers_running(true)
> + log(0, "%-20s", "Containers running:")
> + for _,v in ipairs(run_containers) do
> + log(0, " %s", v)
> + end
> +end
> +
> +function test_container_new()
> + container = lxc.container:new(optarg["n"])
> + assert(container ~= nil)
> + assert(container:config_file_name() == string.format("%s/%s/config", LXC_PATH, optarg["n"]))
> +end
> +
> +function test_container_create()
> + if (optarg["c"]) then
> + log(0, "%-20s %s", "Destroy existing container:", optarg["n"])
> + container:destroy()
> + assert(container:defined() == false)
> + else
> + local cfg_containers = lxc.containers_configured()
> + if (cfg_containers[optarg["n"]]) then
> + log(0, "%-20s %s", "Use existing container:", optarg["n"])
> + return
> + end
> + end
> + log(0, "%-20s %s", "Creating rootfs using:", optarg["t"])
> + container:create(optarg["t"])
> + assert(container:defined() == true)
> + assert(container:name() == optarg["n"])
> +end
> +
> +function test_container_started()
> + local now_running
> + log(2, "state:%s pid:%d\n", container:state(), container:init_pid())
> + assert(container:init_pid() > 1)
> + assert(container:running() == true)
> + assert(container:state() == "RUNNING")
> + now_running = lxc.containers_running(true)
> + assert(now_running[optarg["n"]] ~= nil)
> + log(1, "%-20s %s", "Running, init pid:", container:init_pid())
> +end
> +
> +function test_container_stopped()
> + local now_running
> + assert(container:init_pid() == -1)
> + assert(container:running() == false)
> + assert(container:state() == "STOPPED")
> + now_running = lxc.containers_running(true)
> + assert(now_running[optarg["n"]] == nil)
> +end
> +
> +function test_container_frozen()
> + local now_running
> + assert(container:init_pid() > 1)
> + assert(container:running() == true)
> + assert(container:state() == "FROZEN")
> + now_running = lxc.containers_running(true)
> + assert(now_running[optarg["n"]] ~= nil)
> +end
> +
> +function test_container_start()
> + log(0, "Starting...")
> + if (not container:start()) then
> + log(1, "Start returned failure, waiting another 10 seconds...")
> + container:wait("RUNNING", 10)
> + end
> + container:wait("RUNNING", 1)
> +end
> +
> +function test_container_stop()
> + log(0, "Stopping...")
> + if (not container:stop()) then
> + log(1, "Stop returned failure, waiting another 10 seconds...")
> + container:wait("STOPPED", 10)
> + end
> + container:wait("STOPPED", 1)
> +end
> +
> +function test_container_freeze()
> + log(0, "Freezing...")
> + if (not container:freeze()) then
> + log(1, "Freeze returned failure, waiting another 10 seconds...")
> + container:wait("FROZEN", 10)
> + end
> +end
> +
> +function test_container_unfreeze()
> + log(0, "Unfreezing...")
> + if (not container:unfreeze()) then
> + log(1, "Unfreeze returned failure, waiting another 10 seconds...")
> + container:wait("RUNNING", 10)
> + end
> +end
> +
> +function test_container_shutdown()
> + log(0, "Shutting down...")
> + container:shutdown(5)
> +
> + if (container:running()) then
> + test_container_stop()
> + end
> +end
> +
> +function test_container_in_cfglist(should_find)
> + local cfg_containers = lxc.containers_configured()
> +
> + if (should_find) then
> + assert(cfg_containers[container:name()] ~= nil)
> + else
> + assert(cfg_containers[container:name()] == nil)
> + end
> +end
> +
> +function test_config_items()
> + log(0, "Test set/clear configuration items...")
> +
> + -- test setting a 'single type' item
> + assert(container:get_config_item("lxc.utsname") == optarg["n"])
> + container:set_config_item("lxc.utsname", "foobar")
> + assert(container:get_config_item("lxc.utsname") == "foobar")
> + container:set_config_item("lxc.utsname", optarg["n"])
> + assert(container:get_config_item("lxc.utsname") == optarg["n"])
> +
> + -- test clearing/setting a 'list type' item
> + container:clear_config_item("lxc.cap.drop")
> + container:set_config_item("lxc.cap.drop", "new_cap1")
> + container:set_config_item("lxc.cap.drop", "new_cap2")
> + local cap_drop = container:get_config_item("lxc.cap.drop")
> + assert(cap_drop["new_cap1"] ~= nil)
> + assert(cap_drop["new_cap2"] ~= nil)
> + -- note: clear_config_item only works on list type items
> + container:clear_config_item("lxc.cap.drop")
> + assert(container:get_config_item("lxc.cap.drop") == nil)
> +
> + local altname = "/tmp/" .. optarg["n"] .. ".altconfig"
> + log(0, "Test saving to an alternate (%s) config file...", altname)
> + assert(container:save_config(altname))
> + assert(os.remove(altname))
> +end
> +
> +function test_config_mount_entries()
> + local mntents
> +
> + -- mount entries are a list type item
> + mntents = container:get_config_item("lxc.mount.entry")
> + log(0, "Mount entries:")
> + for _,v in ipairs(mntents) do
> + log(0, " %s", v)
> + end
> +end
> +
> +function test_config_keys()
> + local keys
> +
> + keys = container:get_keys()
> + log(0, "Top level keys:")
> + for k,v in pairs(keys) do
> + log(0, " %s = %s", k, v or "")
> + end
> +end
> +
> +function test_config_network(net_nr)
> + log(0, "Test network %d config...", net_nr)
> + local netcfg
> +
> + netcfg = container:get_keys("lxc.network." .. net_nr)
> + if (netcfg == nil) then
> + return
> + end
> + for k,v in pairs(netcfg) do
> + log(0, " %s = %s", k, v or "")
> + end
> + assert(netcfg["flags"] == "up")
> + assert(container:get_config_item("lxc.network."..net_nr..".type") == "veth")
> +end
> +
> +
> +function usage()
> + die("Usage: apitest <options>\n" ..
> + " -v|--verbose increase verbosity with each -v\n" ..
> + " -h|--help print help message\n" ..
> + " -n|--name name of container to use for testing\n" ..
> + " -c|--create create the test container anew\n" ..
> + " -l|--login do interactive login test\n" ..
> + " -t|--template template to use when creating test container\n"
> + )
> +end
> +
> +local long_opts = {
> + verbose = "v",
> + help = "h",
> + name = "n",
> + create = "c",
> + template = "t",
> +}
> +
> +optarg,optind = alt_getopt.get_opts (arg, "hvn:ct:", long_opts)
> +optarg["v"] = tonumber(optarg["v"]) or 0
> +optarg["n"] = optarg["n"] or "lua-apitest"
> +optarg["c"] = optarg["c"] or nil
> +optarg["t"] = optarg["t"] or "busybox"
> +if (optarg["h"] ~= nil) then
> + usage()
> +end
> +
> +test_global_info()
> +test_container_new()
> +test_container_create()
> +test_container_stopped()
> +test_container_in_cfglist(true)
> +
> +test_config_items()
> +test_config_keys()
> +test_config_mount_entries()
> +test_config_network(0)
> +
> +test_container_start()
> +test_container_started()
> +
> +test_container_freeze()
> +test_container_frozen()
> +test_container_unfreeze()
> +test_container_started()
> +
> +test_container_shutdown()
> +test_container_stopped()
> +container:destroy()
> +test_container_in_cfglist(false)
> +
> +log(0, "All tests passed")
> diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am
> index b55a20c..48234d8 100644
> --- a/src/lxc/Makefile.am
> +++ b/src/lxc/Makefile.am
> @@ -124,8 +124,9 @@ bin_SCRIPTS = \
> lxc-shutdown \
> lxc-destroy
>
> +EXTRA_DIST=
> if ENABLE_PYTHON
> - EXTRA_DIST = lxc-device lxc-ls
> + EXTRA_DIST += lxc-device lxc-ls
> bin_SCRIPTS += lxc-device
> bin_SCRIPTS += lxc-ls
> bin_SCRIPTS += lxc-start-ephemeral
> @@ -133,6 +134,11 @@ else
> bin_SCRIPTS += legacy/lxc-ls
> endif
>
> +if ENABLE_LUA
> + EXTRA_DIST += lxc-top
> + bin_SCRIPTS += lxc-top
> +endif
> +
> bin_PROGRAMS = \
> lxc-attach \
> lxc-unshare \
> diff --git a/src/lxc/lxc-top b/src/lxc/lxc-top
> new file mode 100755
> index 0000000..31aaecf
> --- /dev/null
> +++ b/src/lxc/lxc-top
> @@ -0,0 +1,242 @@
> +#!/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 getopt = require("alt_getopt")
> +local lfs = require("lfs")
> +
> +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 usleep(n)
> + if (n ~= 0) then
> + ret = os.execute("usleep " .. tonumber(n))
> + if (ret ~= 0) then
> + os.exit(0)
> + end
> + end
> +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)
> + 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)
> + end
> + end
> +end
> +
> +function container_list_update()
> + local now_running
> +
> + lxc.stats_clear(stats_total)
> + 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()
> + printf(TERMRVRS .. TERMBOLD)
> + printf("%-15s %8s %8s %8s %10s %10s\n", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem")
> + printf("%-15s %8s %8s %8s %10s %10s\n", "Name", "Used", "Sys", "User", "Total", "Used")
> + printf(TERMNORM)
> +end
> +
> +function stats_print(name, stats)
> + 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))
> +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" ..
> + " -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()
> + for index,ctname in ipairs(containers) do
> + stats_print(ctname, stats[ctname])
> + printf("\n")
> + if (index >= optarg["m"]) then
> + break
> + end
> + end
> + stats_print(string.format("TOTAL (%-2d)", #containers), stats_total)
> + io.flush()
> + usleep(optarg["d"] * 1000000)
> +end
>
--
Stéphane Graber
Ubuntu developer
http://www.ubuntu.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 901 bytes
Desc: OpenPGP digital signature
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20130128/e3836d8c/attachment.pgp>
More information about the lxc-devel
mailing list