lxc-top.lua revision 5a21336025eec5b4228994d0efece129257411bd
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#!/usr/bin/env lua
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano--
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- top(1) like monitor for lxc containers
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano--
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- Copyright © 2012 Oracle.
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano--
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- Authors:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- Dwight Engen <dwight.engen@oracle.com>
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano--
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- This library is free software; you can redistribute it and/or modify
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- it under the terms of the GNU General Public License version 2, as
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- published by the Free Software Foundation.
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano--
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- This program is distributed in the hope that it will be useful,
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- but WITHOUT ANY WARRANTY; without even the implied warranty of
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- GNU General Public License for more details.
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano--
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- You should have received a copy of the GNU General Public License along
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- with this program; if not, write to the Free Software Foundation, Inc.,
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano--
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal lxc = require("lxc")
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal core = require("lxc.core")
99e4008cad9e959b683c6f48411fcf15a92be3b5Michel Normandlocal getopt = require("alt_getopt")
99e4008cad9e959b683c6f48411fcf15a92be3b5Michel Normand
99e4008cad9e959b683c6f48411fcf15a92be3b5Michel Normandlocal USER_HZ = 100
99e4008cad9e959b683c6f48411fcf15a92be3b5Michel Normandlocal ESC = string.format("%c", 27)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal TERMCLEAR = ESC.."[H"..ESC.."[J"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal TERMNORM = ESC.."[0m"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal TERMBOLD = ESC.."[1m"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal TERMRVRS = ESC.."[7m"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal containers = {}
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal stats = {}
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal stats_total = {}
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanolocal max_containers
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanofunction printf(...)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local function wrapper(...) io.write(string.format(...)) end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local status, result = pcall(wrapper, ...)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if not status then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano error(result, 2)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanoend
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanofunction string:split(delim, max_cols)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local cols = {}
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local start = 1
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local nextc
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano repeat
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano nextc = string.find(self, delim, start)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (nextc and #cols ~= max_cols - 1) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano table.insert(cols, string.sub(self, start, nextc-1))
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano start = nextc + #delim
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano else
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano table.insert(cols, string.sub(self, start, string.len(self)))
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano nextc = nil
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano until nextc == nil or start > #self
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return cols
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanoend
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanofunction strsisize(size, width)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local KiB = 1024
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local MiB = 1048576
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local GiB = 1073741824
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local TiB = 1099511627776
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local PiB = 1125899906842624
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local EiB = 1152921504606846976
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local ZiB = 1180591620717411303424
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (size >= ZiB) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return string.format("%d.%2.2d ZB", size / ZiB, (math.floor(size % ZiB) * 100) / ZiB)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (size >= EiB) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return string.format("%d.%2.2d EB", size / EiB, (math.floor(size % EiB) * 100) / EiB)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (size >= PiB) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return string.format("%d.%2.2d PB", size / PiB, (math.floor(size % PiB) * 100) / PiB)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (size >= TiB) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return string.format("%d.%2.2d TB", size / TiB, (math.floor(size % TiB) * 100) / TiB)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (size >= GiB) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return string.format("%d.%2.2d GB", size / GiB, (math.floor(size % GiB) * 100) / GiB)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (size >= MiB) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return string.format("%d.%2.2d MB", size / MiB, (math.floor(size % MiB) * 1000) / (MiB * 10))
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (size >= KiB) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return string.format("%d.%2.2d KB", size / KiB, (math.floor(size % KiB) * 1000) / (KiB * 10))
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return string.format("%3d.00 ", size)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanoend
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanofunction tty_lines()
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local rows = 25
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local f = assert(io.popen("stty -a | head -n 1"))
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano for line in f:lines() do
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local stty_rows
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano _,_,stty_rows = string.find(line, "rows (%d+)")
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (stty_rows ~= nil) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano rows = stty_rows
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano break
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano f:close()
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano return rows
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanoend
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanofunction container_sort(a, b)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (optarg["r"]) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (optarg["s"] == "n") then return (a > b)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos < stats[b].cpu_use_nanos)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano elseif (optarg["s"] == "d") then return (stats[a].blkio < stats[b].blkio)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano elseif (optarg["s"] == "m") then return (stats[a].mem_used < stats[b].mem_used)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano elseif (optarg["s"] == "k") then return (stats[a].kmem_used < stats[b].kmem_used)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano else
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (optarg["s"] == "n") then return (a < b)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos > stats[b].cpu_use_nanos)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano elseif (optarg["s"] == "d") then return (stats[a].blkio > stats[b].blkio)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano elseif (optarg["s"] == "m") then return (stats[a].mem_used > stats[b].mem_used)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano elseif (optarg["s"] == "k") then return (stats[a].kmem_used > stats[b].kmem_used)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanoend
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanofunction container_list_update()
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local now_running
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano now_running = lxc.containers_running(true)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano -- check for newly started containers
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano for _,v in ipairs(now_running) do
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (containers[v] == nil) then
99e4008cad9e959b683c6f48411fcf15a92be3b5Michel Normand local ct = lxc.container:new(v)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano -- note, this is a "mixed" table, ie both dictionary and list
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano containers[v] = ct
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano table.insert(containers, v)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano -- check for newly stopped containers
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local indx = 1
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano while (indx <= #containers) do
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano local ctname = containers[indx]
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (now_running[ctname] == nil) then
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano containers[ctname] = nil
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano stats[ctname] = nil
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano table.remove(containers, indx)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano else
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano indx = indx + 1
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano end
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano -- get stats for all current containers and resort the list
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano lxc.stats_clear(stats_total)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano for _,ctname in ipairs(containers) do
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano stats[ctname] = containers[ctname]:stats_get(stats_total)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano 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