[lxc-devel] [PATCH v3] port lxc-top from lua to C for wider availability
Dwight Engen
dwight.engen at oracle.com
Tue Sep 23 17:37:50 UTC 2014
- 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>
---
v3:
- remove unneeded exit from for loop (which could've cause a mem leak)
- remove leftover debug printf
- check return from realloc()
v2:
- name of source file should be lxc_top.c not lxc-top.c
- distribute lxc-top.lua in tarball as reference example, but don't install
it into bin
- use mem.usage_in_bytes instead of memsw.usage_in_bytes, this was just an
error introduced in porting
doc/lxc-top.sgml.in | 26 +--
src/lxc/Makefile.am | 8 +-
src/lxc/lxc-top | 243 -------------------------
src/lxc/lxc-top.lua | 243 +++++++++++++++++++++++++
src/lxc/lxc_top.c | 511 ++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 763 insertions(+), 268 deletions(-)
delete mode 100755 src/lxc/lxc-top
create mode 100755 src/lxc/lxc-top.lua
create mode 100644 src/lxc/lxc_top.c
diff --git a/doc/lxc-top.sgml.in b/doc/lxc-top.sgml.in
index ba727c5..2e2f774 100644
--- a/doc/lxc-top.sgml.in
+++ b/doc/lxc-top.sgml.in
@@ -47,7 +47,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<cmdsynopsis>
<command>lxc-top</command>
<arg choice="opt">--help</arg>
- <arg choice="opt">--max <replaceable>count</replaceable></arg>
<arg choice="opt">--delay <replaceable>delay</replaceable></arg>
<arg choice="opt">--sort <replaceable>sortby</replaceable></arg>
<arg choice="opt">--reverse</arg>
@@ -60,9 +59,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<command>lxc-top</command> displays container statistics. The output
is updated every <replaceable>delay</replaceable> seconds, and is
ordered according to the <replaceable>sortby</replaceable> value
- given. Specifying <replaceable>count</replaceable> will limit the
- number of containers displayed, otherwise <command>lxc-top</command>
- will display as many containers as can fit in your terminal.
+ given. <command>lxc-top</command> will display as many containers as
+ can fit in your terminal. Press 'q' to quit. Press one of the sort
+ key letters to sort by that statistic. Pressing a sort key letter a
+ second time reverses the sort order.
</para>
</refsect1>
@@ -72,26 +72,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<varlistentry>
<term>
- <option><optional>-m, --max <replaceable>count</replaceable></optional></option>
- </term>
- <listitem>
- <para>
- Limit the number of containers displayed to
- <replaceable>count</replaceable>.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term>
<option><optional>-d, --delay <replaceable>delay</replaceable></optional></option>
</term>
<listitem>
<para>
Amount of time in seconds to delay between screen updates.
- This can be specified as less than a second by giving a
- rational number, for example 0.5 for a half second delay. The
- default is 3 seconds.
+ The default is 3 seconds.
</para>
</listitem>
</varlistentry>
@@ -103,7 +89,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<para>
Sort the containers by name, cpu use, or memory use. The
<replaceable>sortby</replaceable> argument should be one of
- the letters n,c,d,m,k to sort by name, cpu use, disk I/O, memory,
+ the letters n,c,b,m,k to sort by name, cpu use, block I/O, memory,
or kernel memory use respectively. The default is 'n'.
</para>
</listitem>
diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am
index 8322e62..c65a15d 100644
--- a/src/lxc/Makefile.am
+++ b/src/lxc/Makefile.am
@@ -165,7 +165,7 @@ bin_SCRIPTS = lxc-checkconfig
EXTRA_DIST = \
lxc-device \
lxc-ls \
- lxc-top \
+ lxc-top.lua \
lxc.net \
lxc-restore-net
@@ -177,10 +177,6 @@ else
bin_SCRIPTS += legacy/lxc-ls
endif
-if ENABLE_LUA
-bin_SCRIPTS += lxc-top
-endif
-
bin_PROGRAMS = \
lxc-attach \
lxc-autostart \
@@ -198,6 +194,7 @@ bin_PROGRAMS = \
lxc-snapshot \
lxc-start \
lxc-stop \
+ lxc-top \
lxc-unfreeze \
lxc-unshare \
lxc-usernsexec \
@@ -231,6 +228,7 @@ lxc_monitord_SOURCES = lxc_monitord.c
lxc_clone_SOURCES = lxc_clone.c
lxc_start_SOURCES = lxc_start.c
lxc_stop_SOURCES = lxc_stop.c
+lxc_top_SOURCES = lxc_top.c
lxc_unfreeze_SOURCES = lxc_unfreeze.c
lxc_unshare_SOURCES = lxc_unshare.c
lxc_wait_SOURCES = lxc_wait.c
diff --git a/src/lxc/lxc-top b/src/lxc/lxc-top
deleted file mode 100755
index b5b3a69..0000000
--- a/src/lxc/lxc-top
+++ /dev/null
@@ -1,243 +0,0 @@
-#!/usr/bin/env lua
---
--- top(1) like monitor for lxc containers
---
--- Copyright © 2012 Oracle.
---
--- Authors:
--- Dwight Engen <dwight.engen at oracle.com>
---
--- This library is free software; you can redistribute it and/or modify
--- it under the terms of the GNU General Public License version 2, as
--- published by the Free Software Foundation.
---
--- This program is distributed in the hope that it will be useful,
--- but WITHOUT ANY WARRANTY; without even the implied warranty of
--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--- GNU General Public License for more details.
---
--- You should have received a copy of the GNU General Public License along
--- with this program; if not, write to the Free Software Foundation, Inc.,
--- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
---
-
-local lxc = require("lxc")
-local core = require("lxc.core")
-local getopt = require("alt_getopt")
-
-local USER_HZ = 100
-local ESC = string.format("%c", 27)
-local TERMCLEAR = ESC.."[H"..ESC.."[J"
-local TERMNORM = ESC.."[0m"
-local TERMBOLD = ESC.."[1m"
-local TERMRVRS = ESC.."[7m"
-
-local containers = {}
-local stats = {}
-local stats_total = {}
-local max_containers
-
-function printf(...)
- local function wrapper(...) io.write(string.format(...)) end
- local status, result = pcall(wrapper, ...)
- if not status then
- error(result, 2)
- end
-end
-
-function string:split(delim, max_cols)
- local cols = {}
- local start = 1
- local nextc
- repeat
- nextc = string.find(self, delim, start)
- if (nextc and #cols ~= max_cols - 1) then
- table.insert(cols, string.sub(self, start, nextc-1))
- start = nextc + #delim
- else
- table.insert(cols, string.sub(self, start, string.len(self)))
- nextc = nil
- end
- until nextc == nil or start > #self
- return cols
-end
-
-function strsisize(size, width)
- local KiB = 1024
- local MiB = 1048576
- local GiB = 1073741824
- local TiB = 1099511627776
- local PiB = 1125899906842624
- local EiB = 1152921504606846976
- local ZiB = 1180591620717411303424
-
- if (size >= ZiB) then
- return string.format("%d.%2.2d ZB", size / ZiB, (math.floor(size % ZiB) * 100) / ZiB)
- end
- if (size >= EiB) then
- return string.format("%d.%2.2d EB", size / EiB, (math.floor(size % EiB) * 100) / EiB)
- end
- if (size >= PiB) then
- return string.format("%d.%2.2d PB", size / PiB, (math.floor(size % PiB) * 100) / PiB)
- end
- if (size >= TiB) then
- return string.format("%d.%2.2d TB", size / TiB, (math.floor(size % TiB) * 100) / TiB)
- end
- if (size >= GiB) then
- return string.format("%d.%2.2d GB", size / GiB, (math.floor(size % GiB) * 100) / GiB)
- end
- if (size >= MiB) then
- return string.format("%d.%2.2d MB", size / MiB, (math.floor(size % MiB) * 1000) / (MiB * 10))
- end
- if (size >= KiB) then
- return string.format("%d.%2.2d KB", size / KiB, (math.floor(size % KiB) * 1000) / (KiB * 10))
- end
- return string.format("%3d.00 ", size)
-end
-
-function tty_lines()
- local rows = 25
- local f = assert(io.popen("stty -a | head -n 1"))
- for line in f:lines() do
- local stty_rows
- _,_,stty_rows = string.find(line, "rows (%d+)")
- if (stty_rows ~= nil) then
- rows = stty_rows
- break
- end
- end
- f:close()
- return rows
-end
-
-function container_sort(a, b)
- if (optarg["r"]) then
- if (optarg["s"] == "n") then return (a > b)
- elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos < stats[b].cpu_use_nanos)
- elseif (optarg["s"] == "d") then return (stats[a].blkio < stats[b].blkio)
- elseif (optarg["s"] == "m") then return (stats[a].mem_used < stats[b].mem_used)
- elseif (optarg["s"] == "k") then return (stats[a].kmem_used < stats[b].kmem_used)
- end
- else
- if (optarg["s"] == "n") then return (a < b)
- elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos > stats[b].cpu_use_nanos)
- elseif (optarg["s"] == "d") then return (stats[a].blkio > stats[b].blkio)
- elseif (optarg["s"] == "m") then return (stats[a].mem_used > stats[b].mem_used)
- elseif (optarg["s"] == "k") then return (stats[a].kmem_used > stats[b].kmem_used)
- end
- end
-end
-
-function container_list_update()
- local now_running
-
- now_running = lxc.containers_running(true)
-
- -- check for newly started containers
- for _,v in ipairs(now_running) do
- if (containers[v] == nil) then
- local ct = lxc.container:new(v)
- -- note, this is a "mixed" table, ie both dictionary and list
- containers[v] = ct
- table.insert(containers, v)
- end
- end
-
- -- check for newly stopped containers
- local indx = 1
- while (indx <= #containers) do
- local ctname = containers[indx]
- if (now_running[ctname] == nil) then
- containers[ctname] = nil
- stats[ctname] = nil
- table.remove(containers, indx)
- else
- indx = indx + 1
- end
- end
-
- -- get stats for all current containers and resort the list
- lxc.stats_clear(stats_total)
- for _,ctname in ipairs(containers) do
- stats[ctname] = containers[ctname]:stats_get(stats_total)
- end
- table.sort(containers, container_sort)
-end
-
-function stats_print_header(stats_total)
- printf(TERMRVRS .. TERMBOLD)
- printf("%-15s %8s %8s %8s %10s %10s", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem")
- if (stats_total.kmem_used > 0) then printf(" %10s", "KMem") end
- printf("\n")
-
- printf("%-15s %8s %8s %8s %10s %10s", "Name", "Used", "Sys", "User", "Total", "Used")
- if (stats_total.kmem_used > 0) then printf(" %10s", "Used") end
- printf("\n")
- printf(TERMNORM)
-end
-
-function stats_print(name, stats, stats_total)
- printf("%-15s %8.2f %8.2f %8.2f %10s %10s",
- name,
- stats.cpu_use_nanos / 1000000000,
- stats.cpu_use_sys / USER_HZ,
- stats.cpu_use_user / USER_HZ,
- strsisize(stats.blkio),
- strsisize(stats.mem_used))
- if (stats_total.kmem_used > 0) then
- printf(" %10s", strsisize(stats.kmem_used))
- end
-end
-
-function usage()
- printf("Usage: lxc-top [options]\n" ..
- " -h|--help print this help message\n" ..
- " -m|--max display maximum number of containers\n" ..
- " -d|--delay delay in seconds between refreshes (default: 3.0)\n" ..
- " -s|--sort sort by [n,c,d,m] (default: n) where\n" ..
- " n = Name\n" ..
- " c = CPU use\n" ..
- " d = Disk I/O use\n" ..
- " m = Memory use\n" ..
- " k = Kernel memory use\n" ..
- " -r|--reverse sort in reverse (descending) order\n"
- )
- os.exit(1)
-end
-
-local long_opts = {
- help = "h",
- delay = "d",
- max = "m",
- reverse = "r",
- sort = "s",
-}
-
-optarg,optind = alt_getopt.get_opts (arg, "hd:m:rs:", long_opts)
-optarg["d"] = tonumber(optarg["d"]) or 3.0
-optarg["m"] = tonumber(optarg["m"]) or tonumber(tty_lines() - 3)
-optarg["r"] = optarg["r"] or false
-optarg["s"] = optarg["s"] or "n"
-if (optarg["h"] ~= nil) then
- usage()
-end
-
-while true
-do
- container_list_update()
- -- if some terminal we care about doesn't support the simple escapes, we
- -- may fall back to this, or ncurses. ug.
- --os.execute("tput clear")
- printf(TERMCLEAR)
- stats_print_header(stats_total)
- for index,ctname in ipairs(containers) do
- stats_print(ctname, stats[ctname], stats_total)
- printf("\n")
- if (index >= optarg["m"]) then
- break
- end
- end
- stats_print(string.format("TOTAL (%-2d)", #containers), stats_total, stats_total)
- io.flush()
- core.usleep(optarg["d"] * 1000000)
-end
diff --git a/src/lxc/lxc-top.lua b/src/lxc/lxc-top.lua
new file mode 100755
index 0000000..b5b3a69
--- /dev/null
+++ b/src/lxc/lxc-top.lua
@@ -0,0 +1,243 @@
+#!/usr/bin/env lua
+--
+-- top(1) like monitor for lxc containers
+--
+-- Copyright © 2012 Oracle.
+--
+-- Authors:
+-- Dwight Engen <dwight.engen at oracle.com>
+--
+-- This library is free software; you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License version 2, as
+-- published by the Free Software Foundation.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along
+-- with this program; if not, write to the Free Software Foundation, Inc.,
+-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+--
+
+local lxc = require("lxc")
+local core = require("lxc.core")
+local getopt = require("alt_getopt")
+
+local USER_HZ = 100
+local ESC = string.format("%c", 27)
+local TERMCLEAR = ESC.."[H"..ESC.."[J"
+local TERMNORM = ESC.."[0m"
+local TERMBOLD = ESC.."[1m"
+local TERMRVRS = ESC.."[7m"
+
+local containers = {}
+local stats = {}
+local stats_total = {}
+local max_containers
+
+function printf(...)
+ local function wrapper(...) io.write(string.format(...)) end
+ local status, result = pcall(wrapper, ...)
+ if not status then
+ error(result, 2)
+ end
+end
+
+function string:split(delim, max_cols)
+ local cols = {}
+ local start = 1
+ local nextc
+ repeat
+ nextc = string.find(self, delim, start)
+ if (nextc and #cols ~= max_cols - 1) then
+ table.insert(cols, string.sub(self, start, nextc-1))
+ start = nextc + #delim
+ else
+ table.insert(cols, string.sub(self, start, string.len(self)))
+ nextc = nil
+ end
+ until nextc == nil or start > #self
+ return cols
+end
+
+function strsisize(size, width)
+ local KiB = 1024
+ local MiB = 1048576
+ local GiB = 1073741824
+ local TiB = 1099511627776
+ local PiB = 1125899906842624
+ local EiB = 1152921504606846976
+ local ZiB = 1180591620717411303424
+
+ if (size >= ZiB) then
+ return string.format("%d.%2.2d ZB", size / ZiB, (math.floor(size % ZiB) * 100) / ZiB)
+ end
+ if (size >= EiB) then
+ return string.format("%d.%2.2d EB", size / EiB, (math.floor(size % EiB) * 100) / EiB)
+ end
+ if (size >= PiB) then
+ return string.format("%d.%2.2d PB", size / PiB, (math.floor(size % PiB) * 100) / PiB)
+ end
+ if (size >= TiB) then
+ return string.format("%d.%2.2d TB", size / TiB, (math.floor(size % TiB) * 100) / TiB)
+ end
+ if (size >= GiB) then
+ return string.format("%d.%2.2d GB", size / GiB, (math.floor(size % GiB) * 100) / GiB)
+ end
+ if (size >= MiB) then
+ return string.format("%d.%2.2d MB", size / MiB, (math.floor(size % MiB) * 1000) / (MiB * 10))
+ end
+ if (size >= KiB) then
+ return string.format("%d.%2.2d KB", size / KiB, (math.floor(size % KiB) * 1000) / (KiB * 10))
+ end
+ return string.format("%3d.00 ", size)
+end
+
+function tty_lines()
+ local rows = 25
+ local f = assert(io.popen("stty -a | head -n 1"))
+ for line in f:lines() do
+ local stty_rows
+ _,_,stty_rows = string.find(line, "rows (%d+)")
+ if (stty_rows ~= nil) then
+ rows = stty_rows
+ break
+ end
+ end
+ f:close()
+ return rows
+end
+
+function container_sort(a, b)
+ if (optarg["r"]) then
+ if (optarg["s"] == "n") then return (a > b)
+ elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos < stats[b].cpu_use_nanos)
+ elseif (optarg["s"] == "d") then return (stats[a].blkio < stats[b].blkio)
+ elseif (optarg["s"] == "m") then return (stats[a].mem_used < stats[b].mem_used)
+ elseif (optarg["s"] == "k") then return (stats[a].kmem_used < stats[b].kmem_used)
+ end
+ else
+ if (optarg["s"] == "n") then return (a < b)
+ elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos > stats[b].cpu_use_nanos)
+ elseif (optarg["s"] == "d") then return (stats[a].blkio > stats[b].blkio)
+ elseif (optarg["s"] == "m") then return (stats[a].mem_used > stats[b].mem_used)
+ elseif (optarg["s"] == "k") then return (stats[a].kmem_used > stats[b].kmem_used)
+ end
+ end
+end
+
+function container_list_update()
+ local now_running
+
+ now_running = lxc.containers_running(true)
+
+ -- check for newly started containers
+ for _,v in ipairs(now_running) do
+ if (containers[v] == nil) then
+ local ct = lxc.container:new(v)
+ -- note, this is a "mixed" table, ie both dictionary and list
+ containers[v] = ct
+ table.insert(containers, v)
+ end
+ end
+
+ -- check for newly stopped containers
+ local indx = 1
+ while (indx <= #containers) do
+ local ctname = containers[indx]
+ if (now_running[ctname] == nil) then
+ containers[ctname] = nil
+ stats[ctname] = nil
+ table.remove(containers, indx)
+ else
+ indx = indx + 1
+ end
+ end
+
+ -- get stats for all current containers and resort the list
+ lxc.stats_clear(stats_total)
+ for _,ctname in ipairs(containers) do
+ stats[ctname] = containers[ctname]:stats_get(stats_total)
+ end
+ table.sort(containers, container_sort)
+end
+
+function stats_print_header(stats_total)
+ printf(TERMRVRS .. TERMBOLD)
+ printf("%-15s %8s %8s %8s %10s %10s", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem")
+ if (stats_total.kmem_used > 0) then printf(" %10s", "KMem") end
+ printf("\n")
+
+ printf("%-15s %8s %8s %8s %10s %10s", "Name", "Used", "Sys", "User", "Total", "Used")
+ if (stats_total.kmem_used > 0) then printf(" %10s", "Used") end
+ printf("\n")
+ printf(TERMNORM)
+end
+
+function stats_print(name, stats, stats_total)
+ printf("%-15s %8.2f %8.2f %8.2f %10s %10s",
+ name,
+ stats.cpu_use_nanos / 1000000000,
+ stats.cpu_use_sys / USER_HZ,
+ stats.cpu_use_user / USER_HZ,
+ strsisize(stats.blkio),
+ strsisize(stats.mem_used))
+ if (stats_total.kmem_used > 0) then
+ printf(" %10s", strsisize(stats.kmem_used))
+ end
+end
+
+function usage()
+ printf("Usage: lxc-top [options]\n" ..
+ " -h|--help print this help message\n" ..
+ " -m|--max display maximum number of containers\n" ..
+ " -d|--delay delay in seconds between refreshes (default: 3.0)\n" ..
+ " -s|--sort sort by [n,c,d,m] (default: n) where\n" ..
+ " n = Name\n" ..
+ " c = CPU use\n" ..
+ " d = Disk I/O use\n" ..
+ " m = Memory use\n" ..
+ " k = Kernel memory use\n" ..
+ " -r|--reverse sort in reverse (descending) order\n"
+ )
+ os.exit(1)
+end
+
+local long_opts = {
+ help = "h",
+ delay = "d",
+ max = "m",
+ reverse = "r",
+ sort = "s",
+}
+
+optarg,optind = alt_getopt.get_opts (arg, "hd:m:rs:", long_opts)
+optarg["d"] = tonumber(optarg["d"]) or 3.0
+optarg["m"] = tonumber(optarg["m"]) or tonumber(tty_lines() - 3)
+optarg["r"] = optarg["r"] or false
+optarg["s"] = optarg["s"] or "n"
+if (optarg["h"] ~= nil) then
+ usage()
+end
+
+while true
+do
+ container_list_update()
+ -- if some terminal we care about doesn't support the simple escapes, we
+ -- may fall back to this, or ncurses. ug.
+ --os.execute("tput clear")
+ printf(TERMCLEAR)
+ stats_print_header(stats_total)
+ for index,ctname in ipairs(containers) do
+ stats_print(ctname, stats[ctname], stats_total)
+ printf("\n")
+ if (index >= optarg["m"]) then
+ break
+ end
+ end
+ stats_print(string.format("TOTAL (%-2d)", #containers), stats_total, stats_total)
+ io.flush()
+ core.usleep(optarg["d"] * 1000000)
+end
diff --git a/src/lxc/lxc_top.c b/src/lxc/lxc_top.c
new file mode 100644
index 0000000..aec4b52
--- /dev/null
+++ b/src/lxc/lxc_top.c
@@ -0,0 +1,511 @@
+/*
+ * lxc: linux Container library
+ *
+ * Copyright © 2014 Oracle.
+ *
+ * Authors:
+ * Dwight Engen <dwight.engen at oracle.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <lxc/lxccontainer.h>
+
+#include "arguments.h"
+#include "log.h"
+#include "lxc.h"
+#include "mainloop.h"
+#include "utils.h"
+
+lxc_log_define(lxc_top_ui, lxc);
+
+#define USER_HZ 100
+#define ESC "\033"
+#define TERMCLEAR ESC "[H" ESC "[J"
+#define TERMNORM ESC "[0m"
+#define TERMBOLD ESC "[1m"
+#define TERMRVRS ESC "[7m"
+
+struct stats {
+ uint64_t mem_used;
+ uint64_t mem_limit;
+ uint64_t kmem_used;
+ uint64_t kmem_limit;
+ uint64_t cpu_use_nanos;
+ uint64_t cpu_use_user;
+ uint64_t cpu_use_sys;
+ uint64_t blkio;
+};
+
+struct ct {
+ struct lxc_container *c;
+ struct stats *stats;
+};
+
+static int delay = 3;
+static char sort_by = 'n';
+static int sort_reverse = 0;
+
+static struct termios oldtios;
+static struct ct *ct = NULL;
+static int ct_alloc_cnt = 0;
+
+static int my_parser(struct lxc_arguments* args, int c, char* arg)
+{
+ switch (c) {
+ case 'd': delay = atoi(arg); break;
+ case 's': sort_by = arg[0]; break;
+ case 'r': sort_reverse = 1; break;
+ }
+ return 0;
+}
+
+static const struct option my_longopts[] = {
+ {"delay", required_argument, 0, 'd'},
+ {"sort", required_argument, 0, 's'},
+ {"reverse", no_argument, 0, 'r'},
+ LXC_COMMON_OPTIONS
+};
+
+static struct lxc_arguments my_args = {
+ .progname = "lxc-top",
+ .help = "\
+[--name=NAME]\n\
+\n\
+lxc-top monitors the state of the active containers\n\
+\n\
+Options :\n\
+ -d, --delay delay in seconds between refreshes (default: 3.0)\n\
+ -s, --sort sort by [n,c,b,m] (default: n) where\n\
+ n = Name\n\
+ c = CPU use\n\
+ b = Block I/O use\n\
+ m = Memory use\n\
+ k = Kernel memory use\n\
+ -r, --reverse sort in reverse (descending) order\n",
+ .name = ".*",
+ .options = my_longopts,
+ .parser = my_parser,
+ .checker = NULL,
+ .lxcpath_additional = -1,
+};
+
+static void stdin_tios_restore(void)
+{
+ tcsetattr(0, TCSAFLUSH, &oldtios);
+}
+
+static int stdin_tios_setup(void)
+{
+ struct termios newtios;
+
+ if (!isatty(0)) {
+ ERROR("stdin is not a tty");
+ return -1;
+ }
+
+ if (tcgetattr(0, &oldtios)) {
+ SYSERROR("failed to get current terminal settings");
+ return -1;
+ }
+
+ newtios = oldtios;
+
+ /* turn off echo and line buffering */
+ newtios.c_iflag &= ~IGNBRK;
+ newtios.c_iflag &= BRKINT;
+ newtios.c_lflag &= ~(ECHO|ICANON);
+ newtios.c_cc[VMIN] = 1;
+ newtios.c_cc[VTIME] = 0;
+
+ if (tcsetattr(0, TCSAFLUSH, &newtios)) {
+ ERROR("failed to set new terminal settings");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int stdin_tios_rows(void)
+{
+ struct winsize wsz;
+ if (isatty(0) && ioctl(0, TIOCGWINSZ, &wsz) == 0)
+ return wsz.ws_row;
+ return 25;
+}
+
+static int stdin_handler(int fd, uint32_t events, void *data,
+ struct lxc_epoll_descr *descr)
+{
+ char *in_char = data;
+
+ if (events & EPOLLIN) {
+ int rc;
+
+ rc = read(fd, in_char, sizeof(*in_char));
+ if (rc <= 0)
+ *in_char = '\0';
+ }
+
+ if (events & EPOLLHUP)
+ *in_char = 'q';
+ return 1;
+}
+
+static void sig_handler(int sig)
+{
+ exit(EXIT_SUCCESS);
+}
+
+static void size_humanize(unsigned long long val, char *buf, size_t bufsz)
+{
+ if (val > 1 << 30) {
+ snprintf(buf, bufsz, "%u.%2.2u GB",
+ (int)(val >> 30),
+ (int)(val & ((1 << 30) - 1)) / 10737419);
+ } else if (val > 1 << 20) {
+ int x = val + 5243; /* for rounding */
+ snprintf(buf, bufsz, "%u.%2.2u MB",
+ x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20);
+ } else if (val > 1 << 10) {
+ int x = val + 5; /* for rounding */
+ snprintf(buf, bufsz, "%u.%2.2u KB",
+ x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10);
+ } else {
+ snprintf(buf, bufsz, "%3u.00 ", (int)val);
+ }
+}
+
+static uint64_t stat_get_int(struct lxc_container *c, const char *item)
+{
+ char buf[80];
+ int len;
+ uint64_t val;
+
+ len = c->get_cgroup_item(c, item, buf, sizeof(buf));
+ if (len <= 0) {
+ ERROR("unable to read cgroup item %s", item);
+ return 0;
+ }
+
+ val = strtoull(buf, NULL, 0);
+ return val;
+}
+
+static uint64_t stat_match_get_int(struct lxc_container *c, const char *item,
+ const char *match, int column)
+{
+ char buf[4096];
+ int i,j,len;
+ uint64_t val = 0;
+ char **lines, **cols;
+ size_t matchlen;
+
+ len = c->get_cgroup_item(c, item, buf, sizeof(buf));
+ if (len <= 0) {
+ ERROR("unable to read cgroup item %s", item);
+ goto out;
+ }
+
+ lines = lxc_string_split_and_trim(buf, '\n');
+ if (!lines)
+ goto out;
+
+ matchlen = strlen(match);
+ for (i = 0; lines[i]; i++) {
+ if (strncmp(lines[i], match, matchlen) == 0) {
+ cols = lxc_string_split_and_trim(lines[i], ' ');
+ if (!cols)
+ goto err1;
+ for (j = 0; cols[j]; j++) {
+ if (j == column) {
+ val = strtoull(cols[j], NULL, 0);
+ break;
+ }
+ }
+ lxc_free_array((void **)cols, free);
+ break;
+ }
+ }
+err1:
+ lxc_free_array((void **)lines, free);
+out:
+ return val;
+}
+
+static void stats_get(struct lxc_container *c, struct ct *ct, struct stats *total)
+{
+ ct->c = c;
+ ct->stats->mem_used = stat_get_int(c, "memory.usage_in_bytes");
+ ct->stats->mem_limit = stat_get_int(c, "memory.limit_in_bytes");
+ ct->stats->kmem_used = stat_get_int(c, "memory.kmem.usage_in_bytes");
+ ct->stats->kmem_limit = stat_get_int(c, "memory.kmem.limit_in_bytes");
+ ct->stats->cpu_use_nanos = stat_get_int(c, "cpuacct.usage");
+ ct->stats->cpu_use_user = stat_match_get_int(c, "cpuacct.stat", "user", 1);
+ ct->stats->cpu_use_sys = stat_match_get_int(c, "cpuacct.stat", "system", 1);
+ ct->stats->blkio = stat_match_get_int(c, "blkio.throttle.io_service_bytes", "Total", 1);
+
+ if (total) {
+ total->mem_used = total->mem_used + ct->stats->mem_used;
+ total->mem_limit = total->mem_limit + ct->stats->mem_limit;
+ total->kmem_used = total->kmem_used + ct->stats->kmem_used;
+ total->kmem_limit = total->kmem_limit + ct->stats->kmem_limit;
+ total->cpu_use_nanos = total->cpu_use_nanos + ct->stats->cpu_use_nanos;
+ total->cpu_use_user = total->cpu_use_user + ct->stats->cpu_use_user;
+ total->cpu_use_sys = total->cpu_use_sys + ct->stats->cpu_use_sys;
+ total->blkio = total->blkio + ct->stats->blkio;
+ }
+}
+
+static void stats_print_header(struct stats *stats)
+{
+ printf(TERMRVRS TERMBOLD);
+ printf("%-18s %8s %8s %8s %10s %10s", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem");
+ if (stats->kmem_used > 0)
+ printf(" %10s", "KMem");
+ printf("\n");
+
+ printf("%-18s %8s %8s %8s %10s %10s", "Name", "Used", "Sys", "User", "Total", "Used");
+ if (stats->kmem_used > 0)
+ printf(" %10s", "Used");
+ printf("\n");
+ printf(TERMNORM);
+}
+
+static void stats_print(const char *name, const struct stats *stats,
+ const struct stats *total)
+{
+ char blkio_str[20];
+ char mem_used_str[20];
+ char kmem_used_str[20];
+
+ size_humanize(stats->blkio, blkio_str, sizeof(blkio_str));
+ size_humanize(stats->mem_used, mem_used_str, sizeof(mem_used_str));
+
+ printf("%-18s %8.2f %8.2f %8.2f %10s %10s",
+ name,
+ (float)stats->cpu_use_nanos / 1000000000,
+ (float)stats->cpu_use_sys / USER_HZ,
+ (float)stats->cpu_use_user / USER_HZ,
+ blkio_str,
+ mem_used_str);
+ if (total->kmem_used > 0) {
+ size_humanize(stats->kmem_used, kmem_used_str, sizeof(kmem_used_str));
+ printf(" %10s", kmem_used_str);
+ }
+}
+
+static int cmp_name(const void *sct1, const void *sct2)
+{
+ const struct ct *ct1 = sct1;
+ const struct ct *ct2 = sct2;
+
+ if (sort_reverse)
+ return strcmp(ct2->c->name, ct1->c->name);
+ return strcmp(ct1->c->name, ct2->c->name);
+}
+
+static int cmp_cpuuse(const void *sct1, const void *sct2)
+{
+ const struct ct *ct1 = sct1;
+ const struct ct *ct2 = sct2;
+
+ if (sort_reverse)
+ return ct2->stats->cpu_use_nanos < ct1->stats->cpu_use_nanos;
+ return ct1->stats->cpu_use_nanos < ct2->stats->cpu_use_nanos;
+}
+
+static int cmp_blkio(const void *sct1, const void *sct2)
+{
+ const struct ct *ct1 = sct1;
+ const struct ct *ct2 = sct2;
+
+ if (sort_reverse)
+ return ct2->stats->blkio < ct1->stats->blkio;
+ return ct1->stats->blkio < ct2->stats->blkio;
+}
+
+static int cmp_memory(const void *sct1, const void *sct2)
+{
+ const struct ct *ct1 = sct1;
+ const struct ct *ct2 = sct2;
+
+ if (sort_reverse)
+ return ct2->stats->mem_used < ct1->stats->mem_used;
+ return ct1->stats->mem_used < ct2->stats->mem_used;
+}
+
+static int cmp_kmemory(const void *sct1, const void *sct2)
+{
+ const struct ct *ct1 = sct1;
+ const struct ct *ct2 = sct2;
+
+ if (sort_reverse)
+ return ct2->stats->kmem_used < ct1->stats->kmem_used;
+ return ct1->stats->kmem_used < ct2->stats->kmem_used;
+}
+
+static void ct_sort(int active)
+{
+ int (*cmp_func)(const void *, const void *);
+
+ switch(sort_by) {
+ default:
+ case 'n': cmp_func = cmp_name; break;
+ case 'c': cmp_func = cmp_cpuuse; break;
+ case 'b': cmp_func = cmp_blkio; break;
+ case 'm': cmp_func = cmp_memory; break;
+ case 'k': cmp_func = cmp_kmemory; break;
+ }
+ qsort(ct, active, sizeof(*ct), (int (*)(const void *,const void *))cmp_func);
+}
+
+static void ct_free(void)
+{
+ int i;
+
+ for (i = 0; i < ct_alloc_cnt; i++) {
+ if (ct[i].c) {
+ lxc_container_put(ct[i].c);
+ ct[i].c = NULL;
+ }
+ if (ct[i].stats) {
+ 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);
+ if (!ct) {
+ ERROR("cannot alloc mem");
+ exit(EXIT_FAILURE);
+ }
+ for (i = 0; i < active_cnt; i++) {
+ ct[i].stats = malloc(sizeof(*ct[0].stats));
+ if (!ct[i].stats) {
+ ERROR("cannot alloc mem");
+ exit(EXIT_FAILURE);
+ }
+ }
+ ct_alloc_cnt = active_cnt;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct lxc_epoll_descr descr;
+ int ret, ct_print_cnt;
+ char in_char;
+
+ ret = EXIT_FAILURE;
+ if (lxc_arguments_parse(&my_args, argc, argv))
+ goto out;
+
+ ct_print_cnt = stdin_tios_rows() - 3; /* 3 -> header and total */
+ if (stdin_tios_setup() < 0) {
+ ERROR("failed to setup terminal");
+ goto out;
+ }
+
+ /* ensure the terminal gets restored */
+ atexit(stdin_tios_restore);
+ signal(SIGINT, sig_handler);
+ signal(SIGQUIT, sig_handler);
+
+ if (lxc_mainloop_open(&descr)) {
+ ERROR("failed to create mainloop");
+ goto out;
+ }
+
+ ret = lxc_mainloop_add_handler(&descr, 0, stdin_handler, &in_char);
+ if (ret) {
+ ERROR("failed to add stdin handler");
+ ret = EXIT_FAILURE;
+ goto err1;
+ }
+
+ for(;;) {
+ struct lxc_container **active;
+ int i, active_cnt;
+ struct stats total;
+ char total_name[30];
+
+ active_cnt = list_active_containers(my_args.lxcpath[0], NULL, &active);
+ ct_realloc(active_cnt);
+
+ memset(&total, 0, sizeof(total));
+ for (i = 0; i < active_cnt; i++)
+ stats_get(active[i], &ct[i], &total);
+
+ ct_sort(active_cnt);
+
+ printf(TERMCLEAR);
+ stats_print_header(&total);
+ for (i = 0; i < active_cnt && i < ct_print_cnt; i++) {
+ stats_print(ct[i].c->name, ct[i].stats, &total);
+ printf("\n");
+ }
+ sprintf(total_name, "TOTAL %d of %d", i, active_cnt);
+ stats_print(total_name, &total, &total);
+ fflush(stdout);
+
+ for (i = 0; i < active_cnt; i++) {
+ lxc_container_put(ct[i].c);
+ ct[i].c = NULL;
+ }
+
+ in_char = '\0';
+ ret = lxc_mainloop(&descr, 1000 * delay);
+ if (ret != 0 || in_char == 'q')
+ break;
+ switch(in_char) {
+ case 'r':
+ sort_reverse ^= 1;
+ break;
+ case 'n':
+ case 'c':
+ case 'b':
+ case 'm':
+ case 'k':
+ if (sort_by == in_char)
+ sort_reverse ^= 1;
+ else
+ sort_reverse = 0;
+ sort_by = in_char;
+ }
+ }
+ ret = EXIT_SUCCESS;
+
+err1:
+ lxc_mainloop_close(&descr);
+out:
+ return ret;
+}
--
1.9.3
More information about the lxc-devel
mailing list