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

Dwight Engen dwight.engen at oracle.com
Mon Sep 22 21:59:22 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>
---
v2:
 - name of source file should be lxc_top.c not lxc-top.c
 - distribute lxc-top.lua in tarball as reference example, but don't install
   it into bin
 - use mem.usage_in_bytes instead of memsw.usage_in_bytes, this was just an
   error introduced in porting

 doc/lxc-top.sgml.in |  26 +--
 src/lxc/Makefile.am |   8 +-
 src/lxc/lxc-top     | 243 -------------------------
 src/lxc/lxc-top.lua | 243 +++++++++++++++++++++++++
 src/lxc/lxc_top.c   | 510 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 762 insertions(+), 268 deletions(-)
 delete mode 100755 src/lxc/lxc-top
 create mode 100755 src/lxc/lxc-top.lua
 create mode 100644 src/lxc/lxc_top.c

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



More information about the lxc-devel mailing list