From: Dwight Engen Date: Thu, 24 Jan 2013 16:42:22 +0000 (-0500) Subject: add lua binding for the lxc API X-Git-Tag: lxc-0.9.0.alpha3~1^2~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f080ffd7d656fbd9505a8e8eb52a05d61355c677;p=thirdparty%2Flxc.git add lua binding for the lxc API 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 Acked-by: Stéphane Graber --- diff --git a/Makefile.am b/Makefile.am index 3fb453e3d..53473eecb 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 e56724571..359f28de4 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])], @@ -289,6 +306,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 @@ -342,6 +360,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 86de2fedb..e5392546e 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 000000000..2a4f83541 --- /dev/null +++ b/doc/lxc-top.sgml.in @@ -0,0 +1,164 @@ + + + +]> + + + + @LXC_GENERATE_DATE@ + + + lxc-top + 1 + + + + lxc-top + + + monitor container statistics + + + + + + lxc-top + --help + --max count + --delay delay + --sort sortby + --reverse + + + + + Description + + lxc-top displays container statistics. The output + is updated every delay seconds, and is + ordered according to the sortby value + given. Specifying count will limit the + number of containers displayed, otherwise lxc-top + will display as many containers as can fit in your terminal. + + + + + Options + + + + + + + + + Limit the number of containers displayed to + count. + + + + + + + + + + + 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. + + + + + + + + + + Sort the containers by name, cpu use, or memory use. The + sortby 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'. + + + + + + + + + + Reverse the default sort order. By default, names sort in + ascending alphabetical order and values sort in descending + amounts (ie. largest value first). + + + + + + + + Example + + + lxc-top --delay 1 --sort m + + + Display containers, updating every second, sorted by memory use. + + + + + + + &seealso; + + + Author + Dwight Engen dwight.engen@oracle.com + + + + + diff --git a/lxc.spec.in b/lxc.spec.in index 65997d9fd..9fbd6b0f6 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 4e4d66b5e..c96cbe79f 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 000000000..f05eb7259 --- /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 000000000..5c47aedfa --- /dev/null +++ b/src/lua-lxc/core.c @@ -0,0 +1,382 @@ +/* + * lua-lxc: lua bindings for lxc + * + * Copyright © 2012 Oracle. + * + * Authors: + * Dwight Engen + * + * 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 +#include +#include +#include + +#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 000000000..c71de48c9 --- /dev/null +++ b/src/lua-lxc/lxc.lua @@ -0,0 +1,412 @@ +-- +-- lua lxc module +-- +-- Copyright © 2012 Oracle. +-- +-- Authors: +-- Dwight Engen +-- +-- 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 000000000..14d2a9da3 --- /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 +-- +-- 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 \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 2fb829137..d8506d349 100644 --- a/src/lxc/Makefile.am +++ b/src/lxc/Makefile.am @@ -129,8 +129,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 @@ -138,6 +139,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 000000000..31aaecf9d --- /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 +-- +-- 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