File: //usr/share/sysdig/chisels/common.lua
--[[
Copyright (C) 2013-2018 Draios Inc dba Sysdig.
This file is part of sysdig.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
--[[
This file contains a bunch of functions that are helpful in multiple scripts
]]--
--[[
Serialize the content of a table into a string
]]--
function st(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false
depth = depth or 0
local tmp = string.rep(" ", depth)
if name then tmp = tmp .. name .. " = " end
if type(val) == "table" then
tmp = tmp .. "{" .. (not skipnewlines and "\n" or "")
for k, v in pairs(val) do
tmp = tmp .. st(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "")
end
tmp = tmp .. string.rep(" ", depth) .. "}"
elseif type(val) == "number" then
tmp = tmp .. tostring(val)
elseif type(val) == "string" then
tmp = tmp .. string.format("%q", val)
elseif type(val) == "boolean" then
tmp = tmp .. (val and "true" or "false")
else
tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\""
end
return tmp
end
--[[
Extends a string to newlen with spaces
]]--
function extend_string(s, newlen)
if #s < newlen then
local ccs = " "
s = s .. string.sub(ccs, 0, newlen - #s)
return s
else
if newlen > 0 then
return (string.sub(s, 0, newlen - 1) .. " ")
else
return ""
end
end
end
--[[
Basic string split.
]]--
function split(s, delimiter)
local result = {}
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match)
end
return result
end
--[[
Substring matching.
]]--
function starts_with(str, prefix)
return prefix == "" or str:sub(1, #prefix) == prefix
end
function ends_with(str, suffix)
return suffix == "" or str:sub(-#suffix) == suffix
end
--[[
convert a number into a byte representation.
E.g. 1230 becomes 1.23K
]]--
function format_bytes(val)
if val > (1024 * 1024 * 1024 * 1024 * 1024) then
return string.format("%.2fP", val / (1024 * 1024 * 1024 * 1024 * 1024))
elseif val > (1024 * 1024 * 1024 * 1024) then
return string.format("%.2fT", val / (1024 * 1024 * 1024 * 1024))
elseif val > (1024 * 1024 * 1024) then
return string.format("%.2fG", val / (1024 * 1024 * 1024))
elseif val > (1024 * 1024) then
return string.format("%.2fM", val / (1024 * 1024))
elseif val > 1024 then
return string.format("%.2fKB", val / (1024))
else
return string.format("%dB", val)
end
end
--[[
convert a nanosecond time interval into a s.ns representation.
E.g. 1100000000 becomes 1.1s
]]--
ONE_S_IN_NS=1000000000
ONE_MS_IN_NS=1000000
ONE_US_IN_NS=1000
function format_time_interval(val)
if val >= (ONE_S_IN_NS) then
return string.format("%u.%02us", math.floor(val / ONE_S_IN_NS), (val % ONE_S_IN_NS) / 10000000)
elseif val >= (ONE_S_IN_NS / 100) then
return string.format("%ums", math.floor(val / (ONE_S_IN_NS / 1000)))
elseif val >= (ONE_S_IN_NS / 1000) then
return string.format("%u.%02ums", math.floor(val / (ONE_S_IN_NS / 1000)), (val % ONE_MS_IN_NS) / 10000)
elseif val >= (ONE_S_IN_NS / 100000) then
return string.format("%uus", math.floor(val / (ONE_S_IN_NS / 1000000)))
elseif val >= (ONE_S_IN_NS / 1000000) then
return string.format("%u.%02uus", math.floor(val / (ONE_S_IN_NS / 1000000)), (val % ONE_US_IN_NS) / 10)
else
return string.format("%uns", val)
end
end
--[[
extract the top num entries from the table t, after sorting them based on the entry value using the function order()
]]--
function pairs_top_by_val(t, num, order)
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
table.sort(keys, function(a,b) return order(t, a, b) end)
local i = 0
return function()
i = i + 1
if (num == 0 or i <= num) and keys[i] then
return keys[i], t[keys[i]]
end
end
end
--[[
Timestamp <-> string conversion
]]--
function ts_to_str(tshi, tslo)
return string.format("%u%.9u", tshi, tslo)
end
--[[
Pick a key-value table and render it to the console in sorted top format
]]--
json = require ("dkjson")
function print_sorted_table(stable, ts_s, ts_ns, timedelta, viz_info)
local sorted_grtable = pairs_top_by_val(stable, viz_info.top_number, function(t,a,b) return t[b] < t[a] end)
if viz_info.output_format == "json" then
local jdata = {}
local j = 1
for k,v in sorted_grtable do
local vals = split(k, "\001\001")
vals[#vals + 1] = v
jdata[j] = vals
j = j + 1
end
local jinfo = {}
for i, keyname in ipairs(viz_info.key_fld) do
jinfo[i] = {name = keyname, desc = viz_info.key_desc[i], is_key = true}
end
jinfo[3] = {name = viz_info.value_fld, desc = viz_info.value_desc, is_key = false}
local res = {ts = sysdig.make_ts(ts_s, ts_ns), data = jdata, info = jinfo}
local str = json.encode(res, { indent = true })
print(str)
else
-- Same size to extend each string
local EXTEND_STRING_SIZE = 20
local header = extend_string(viz_info.value_desc, EXTEND_STRING_SIZE)
for i, fldname in ipairs(viz_info.key_desc) do
header = header .. extend_string(fldname, EXTEND_STRING_SIZE)
end
print(header)
print("--------------------------------------------------------------------------------")
for k,v in sorted_grtable do
local keystr = ""
local singlekeys = split(k, "\001\001")
for i, singlekey in ipairs(singlekeys) do
if i < #singlekeys then
keystr = keystr .. extend_string(string.sub(singlekey, 0, EXTEND_STRING_SIZE), EXTEND_STRING_SIZE)
else
keystr = keystr .. singlekey
end
end
if viz_info.value_units == "none" then
print(extend_string(tostring(v), EXTEND_STRING_SIZE) .. keystr)
elseif viz_info.value_units == "bytes" then
print(extend_string(format_bytes(v), EXTEND_STRING_SIZE) .. keystr)
elseif viz_info.value_units == "time" then
print(extend_string(format_time_interval(v), EXTEND_STRING_SIZE) .. keystr)
elseif viz_info.value_units == "timepct" then
if timedelta > 0 then
pctstr = string.format("%.2f%%", v / timedelta * 100)
else
pctstr = "0.00%"
end
print(extend_string(pctstr, EXTEND_STRING_SIZE) .. keystr)
end
end
end
end
--[[
Try to convert user input to a number using tonumber(). If tonumber() returns
'nil', print an error message to the user and exit, otherwise return
tonumber(value).
]]--
function parse_numeric_input(value, name)
val = tonumber(value)
if val == nil then
print(string.format("Input %s must be a number.", name))
require ("os")
os.exit()
end
return val
end
--[[
Perform a deep copy of a table.
]]--
function copytable(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[copytable(orig_key)] = copytable(orig_value)
end
setmetatable(copy, copytable(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
--[[
Add the content of a table at the end of another one.
]]--
function concattable(dst, src)
for i=1,#src do
dst[#dst + 1] = src[i]
end
return dst
end
--[[
return the type of a variable.
]]--
function typeof(var)
local _type = type(var);
if(_type ~= "table" and _type ~= "userdata") then
return _type;
end
local _meta = getmetatable(var);
if(_meta ~= nil and _meta._NAME ~= nil) then
return _meta._NAME;
else
return _type;
end
end