]> git.proxmox.com Git - mirror_lxc.git/blob - src/lxc/tools/lxc-top.lua
Merge pull request #1539 from brauner/2017-05-06/fix_abstract_unix_sockets
[mirror_lxc.git] / src / lxc / tools / lxc-top.lua
1 #!/usr/bin/env lua
2 --
3 -- top(1) like monitor for lxc containers
4 --
5 -- Copyright © 2012 Oracle.
6 --
7 -- Authors:
8 -- Dwight Engen <dwight.engen@oracle.com>
9 --
10 -- This library is free software; you can redistribute it and/or modify
11 -- it under the terms of the GNU General Public License version 2, as
12 -- published by the Free Software Foundation.
13 --
14 -- This program is distributed in the hope that it will be useful,
15 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
16 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 -- GNU General Public License for more details.
18 --
19 -- You should have received a copy of the GNU General Public License along
20 -- with this program; if not, write to the Free Software Foundation, Inc.,
21 -- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 --
23
24 local lxc = require("lxc")
25 local core = require("lxc.core")
26 local getopt = require("alt_getopt")
27
28 local USER_HZ = 100
29 local ESC = string.format("%c", 27)
30 local TERMCLEAR = ESC.."[H"..ESC.."[J"
31 local TERMNORM = ESC.."[0m"
32 local TERMBOLD = ESC.."[1m"
33 local TERMRVRS = ESC.."[7m"
34
35 local containers = {}
36 local stats = {}
37 local stats_total = {}
38 local max_containers
39
40 function printf(...)
41 local function wrapper(...) io.write(string.format(...)) end
42 local status, result = pcall(wrapper, ...)
43 if not status then
44 error(result, 2)
45 end
46 end
47
48 function string:split(delim, max_cols)
49 local cols = {}
50 local start = 1
51 local nextc
52 repeat
53 nextc = string.find(self, delim, start)
54 if (nextc and #cols ~= max_cols - 1) then
55 table.insert(cols, string.sub(self, start, nextc-1))
56 start = nextc + #delim
57 else
58 table.insert(cols, string.sub(self, start, string.len(self)))
59 nextc = nil
60 end
61 until nextc == nil or start > #self
62 return cols
63 end
64
65 function strsisize(size, width)
66 local KiB = 1024
67 local MiB = 1048576
68 local GiB = 1073741824
69 local TiB = 1099511627776
70 local PiB = 1125899906842624
71 local EiB = 1152921504606846976
72 local ZiB = 1180591620717411303424
73
74 if (size >= ZiB) then
75 return string.format("%d.%2.2d ZB", size / ZiB, (math.floor(size % ZiB) * 100) / ZiB)
76 end
77 if (size >= EiB) then
78 return string.format("%d.%2.2d EB", size / EiB, (math.floor(size % EiB) * 100) / EiB)
79 end
80 if (size >= PiB) then
81 return string.format("%d.%2.2d PB", size / PiB, (math.floor(size % PiB) * 100) / PiB)
82 end
83 if (size >= TiB) then
84 return string.format("%d.%2.2d TB", size / TiB, (math.floor(size % TiB) * 100) / TiB)
85 end
86 if (size >= GiB) then
87 return string.format("%d.%2.2d GB", size / GiB, (math.floor(size % GiB) * 100) / GiB)
88 end
89 if (size >= MiB) then
90 return string.format("%d.%2.2d MB", size / MiB, (math.floor(size % MiB) * 1000) / (MiB * 10))
91 end
92 if (size >= KiB) then
93 return string.format("%d.%2.2d KB", size / KiB, (math.floor(size % KiB) * 1000) / (KiB * 10))
94 end
95 return string.format("%3d.00 ", size)
96 end
97
98 function tty_lines()
99 local rows = 25
100 local f = assert(io.popen("stty -a | head -n 1"))
101 for line in f:lines() do
102 local stty_rows
103 _,_,stty_rows = string.find(line, "rows (%d+)")
104 if (stty_rows ~= nil) then
105 rows = stty_rows
106 break
107 end
108 end
109 f:close()
110 return rows
111 end
112
113 function container_sort(a, b)
114 if (optarg["r"]) then
115 if (optarg["s"] == "n") then return (a > b)
116 elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos < stats[b].cpu_use_nanos)
117 elseif (optarg["s"] == "d") then return (stats[a].blkio < stats[b].blkio)
118 elseif (optarg["s"] == "m") then return (stats[a].mem_used < stats[b].mem_used)
119 elseif (optarg["s"] == "k") then return (stats[a].kmem_used < stats[b].kmem_used)
120 end
121 else
122 if (optarg["s"] == "n") then return (a < b)
123 elseif (optarg["s"] == "c") then return (stats[a].cpu_use_nanos > stats[b].cpu_use_nanos)
124 elseif (optarg["s"] == "d") then return (stats[a].blkio > stats[b].blkio)
125 elseif (optarg["s"] == "m") then return (stats[a].mem_used > stats[b].mem_used)
126 elseif (optarg["s"] == "k") then return (stats[a].kmem_used > stats[b].kmem_used)
127 end
128 end
129 end
130
131 function container_list_update()
132 local now_running
133
134 now_running = lxc.containers_running(true)
135
136 -- check for newly started containers
137 for _,v in ipairs(now_running) do
138 if (containers[v] == nil) then
139 local ct = lxc.container:new(v)
140 -- note, this is a "mixed" table, ie both dictionary and list
141 containers[v] = ct
142 table.insert(containers, v)
143 end
144 end
145
146 -- check for newly stopped containers
147 local indx = 1
148 while (indx <= #containers) do
149 local ctname = containers[indx]
150 if (now_running[ctname] == nil) then
151 containers[ctname] = nil
152 stats[ctname] = nil
153 table.remove(containers, indx)
154 else
155 indx = indx + 1
156 end
157 end
158
159 -- get stats for all current containers and resort the list
160 lxc.stats_clear(stats_total)
161 for _,ctname in ipairs(containers) do
162 stats[ctname] = containers[ctname]:stats_get(stats_total)
163 end
164 table.sort(containers, container_sort)
165 end
166
167 function stats_print_header(stats_total)
168 printf(TERMRVRS .. TERMBOLD)
169 printf("%-15s %8s %8s %8s %10s %10s", "Container", "CPU", "CPU", "CPU", "BlkIO", "Mem")
170 if (stats_total.kmem_used > 0) then printf(" %10s", "KMem") end
171 printf("\n")
172
173 printf("%-15s %8s %8s %8s %10s %10s", "Name", "Used", "Sys", "User", "Total", "Used")
174 if (stats_total.kmem_used > 0) then printf(" %10s", "Used") end
175 printf("\n")
176 printf(TERMNORM)
177 end
178
179 function stats_print(name, stats, stats_total)
180 printf("%-15s %8.2f %8.2f %8.2f %10s %10s",
181 name,
182 stats.cpu_use_nanos / 1000000000,
183 stats.cpu_use_sys / USER_HZ,
184 stats.cpu_use_user / USER_HZ,
185 strsisize(stats.blkio),
186 strsisize(stats.mem_used))
187 if (stats_total.kmem_used > 0) then
188 printf(" %10s", strsisize(stats.kmem_used))
189 end
190 end
191
192 function usage()
193 printf("Usage: lxc-top [options]\n" ..
194 " -h|--help print this help message\n" ..
195 " -m|--max display maximum number of containers\n" ..
196 " -d|--delay delay in seconds between refreshes (default: 3.0)\n" ..
197 " -s|--sort sort by [n,c,d,m] (default: n) where\n" ..
198 " n = Name\n" ..
199 " c = CPU use\n" ..
200 " d = Disk I/O use\n" ..
201 " m = Memory use\n" ..
202 " k = Kernel memory use\n" ..
203 " -r|--reverse sort in reverse (descending) order\n"
204 )
205 os.exit(1)
206 end
207
208 local long_opts = {
209 help = "h",
210 delay = "d",
211 max = "m",
212 reverse = "r",
213 sort = "s",
214 }
215
216 optarg,optind = alt_getopt.get_opts (arg, "hd:m:rs:", long_opts)
217 optarg["d"] = tonumber(optarg["d"]) or 3.0
218 optarg["m"] = tonumber(optarg["m"]) or tonumber(tty_lines() - 3)
219 optarg["r"] = optarg["r"] or false
220 optarg["s"] = optarg["s"] or "n"
221 if (optarg["h"] ~= nil) then
222 usage()
223 end
224
225 while true
226 do
227 container_list_update()
228 -- if some terminal we care about doesn't support the simple escapes, we
229 -- may fall back to this, or ncurses. ug.
230 --os.execute("tput clear")
231 printf(TERMCLEAR)
232 stats_print_header(stats_total)
233 for index,ctname in ipairs(containers) do
234 stats_print(ctname, stats[ctname], stats_total)
235 printf("\n")
236 if (index >= optarg["m"]) then
237 break
238 end
239 end
240 stats_print(string.format("TOTAL (%-2d)", #containers), stats_total, stats_total)
241 io.flush()
242 core.usleep(optarg["d"] * 1000000)
243 end