aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/mod_admin_shell.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/mod_admin_shell.lua')
-rw-r--r--plugins/mod_admin_shell.lua420
1 files changed, 185 insertions, 235 deletions
diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua
index 7c2003e0..2e24bdf2 100644
--- a/plugins/mod_admin_shell.lua
+++ b/plugins/mod_admin_shell.lua
@@ -36,6 +36,9 @@ local serialization = require "util.serialization";
local serialize_config = serialization.new ({ fatal = false, unquoted = true});
local time = require "util.time";
+local t_insert = table.insert;
+local t_concat = table.concat;
+
local format_number = require "util.human.units".format;
local format_table = require "util.human.io".table;
@@ -1342,187 +1345,112 @@ local short_units = {
bytes = "B",
};
-local function format_stat(type, unit, value, ref_value)
- ref_value = ref_value or value;
- --do return tostring(value) end
- if not unit then
- if type == "duration" then
- unit = "seconds"
- elseif type == "size" then
- unit = "bytes";
- elseif type == "rate" then
- unit = " events/sec"
- if ref_value < 0.9 then
- unit = "events/min"
- value = value*60;
- if ref_value < 0.6/60 then
- unit = "events/h"
- value = value*60;
- end
+local stats_methods = {};
+
+function stats_methods:render_single_fancy_histogram_ex(print, prefix, metric_family, metric, cumulative)
+ local creation_timestamp, sum, count
+ local buckets = {}
+ local prev_bucket_count = 0
+ for suffix, extra_labels, value in metric:iter_samples() do
+ if suffix == "_created" then
+ creation_timestamp = value
+ elseif suffix == "_sum" then
+ sum = value
+ elseif suffix == "_count" then
+ count = value
+ else
+ local bucket_threshold = extra_labels["le"]
+ local bucket_count
+ if cumulative then
+ bucket_count = value
+ else
+ bucket_count = value - prev_bucket_count
+ prev_bucket_count = value
+ end
+ if bucket_threshold == "+Inf" then
+ t_insert(buckets, {threshold = 1/0, count = bucket_count})
+ elseif bucket_threshold ~= nil then
+ t_insert(buckets, {threshold = tonumber(bucket_threshold), count = bucket_count})
end
- return ("%.3g %s"):format(value, unit);
end
end
- return format_number(value, short_units[unit] or unit or "", unit == "bytes" and 'b' or nil);
-end
-local stats_methods = {};
-function stats_methods:bounds(_lower, _upper)
- for _, stat_info in ipairs(self) do
- local data = stat_info[4];
- if data then
- local lower = _lower or data.min;
- local upper = _upper or data.max;
- local new_data = {
- min = lower;
- max = upper;
- samples = {};
- sample_count = 0;
- count = data.count;
- units = data.units;
- };
- local sum = 0;
- for _, v in ipairs(data.samples) do
- if v > upper then
- break;
- elseif v>=lower then
- table.insert(new_data.samples, v);
- sum = sum + v;
- end
- end
- new_data.sample_count = #new_data.samples;
- stat_info[4] = new_data;
- stat_info[3] = sum/new_data.sample_count;
+ if #buckets == 0 or not creation_timestamp or not sum or not count then
+ print("[no data or not a histogram]")
+ return false
+ end
+
+ local graph_width, graph_height, wscale = #buckets, 10, 1;
+ if graph_width < 8 then
+ wscale = 8
+ elseif graph_width < 16 then
+ wscale = 4
+ elseif graph_width < 32 then
+ wscale = 2
+ end
+ local eighth_chars = " ▁▂▃▄▅▆▇█";
+
+ local max_bin_samples = 0
+ for _, bucket in ipairs(buckets) do
+ if bucket.count > max_bin_samples then
+ max_bin_samples = bucket.count
end
end
- return self;
-end
-function stats_methods:trim(lower, upper)
- upper = upper or (100-lower);
- local statistics = require "util.statistics";
- for _, stat_info in ipairs(self) do
- -- Strip outliers
- local data = stat_info[4];
- if data then
- local new_data = {
- min = statistics.get_percentile(data, lower);
- max = statistics.get_percentile(data, upper);
- samples = {};
- sample_count = 0;
- count = data.count;
- units = data.units;
- };
- local sum = 0;
- for _, v in ipairs(data.samples) do
- if v > new_data.max then
- break;
- elseif v>=new_data.min then
- table.insert(new_data.samples, v);
- sum = sum + v;
- end
+ print("");
+ print(prefix)
+ print(("_"):rep(graph_width*wscale).." "..max_bin_samples);
+ for row = graph_height, 1, -1 do
+ local row_chars = {};
+ local min_eighths, max_eighths = 8, 0;
+ for i = 1, #buckets do
+ local char_eighths = math.ceil(math.max(math.min((graph_height/(max_bin_samples/buckets[i].count))-(row-1), 1), 0)*8);
+ if char_eighths < min_eighths then
+ min_eighths = char_eighths;
+ end
+ if char_eighths > max_eighths then
+ max_eighths = char_eighths;
+ end
+ if char_eighths == 0 then
+ row_chars[i] = ("-"):rep(wscale);
+ else
+ local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3);
+ row_chars[i] = char:rep(wscale);
end
- new_data.sample_count = #new_data.samples;
- stat_info[4] = new_data;
- stat_info[3] = sum/new_data.sample_count;
end
+ print(table.concat(row_chars).."|- "..string.format("%.8g", math.ceil((max_bin_samples/graph_height)*(row-0.5))));
end
- return self;
-end
-function stats_methods:max(upper)
- return self:bounds(nil, upper);
+ local legend_pat = string.format("%%%d.%dg", wscale-1, wscale-1)
+ local row = {}
+ for i = 1, #buckets do
+ local threshold = buckets[i].threshold
+ t_insert(row, legend_pat:format(threshold))
+ end
+ t_insert(row, " " .. metric_family.unit)
+ print(t_concat(row, "/"))
+
+ return true
end
-function stats_methods:min(lower)
- return self:bounds(lower, nil);
+function stats_methods:render_single_fancy_histogram(print, prefix, metric_family, metric)
+ return self:render_single_fancy_histogram_ex(print, prefix, metric_family, metric, false)
end
-function stats_methods:summary()
- local statistics = require "util.statistics";
- for _, stat_info in ipairs(self) do
- local type, value, data = stat_info[2], stat_info[3], stat_info[4];
- if data and data.samples then
- table.insert(stat_info.output, string.format("Count: %d (%d captured)",
- data.count,
- data.sample_count
- ));
- table.insert(stat_info.output, string.format("Min: %s Mean: %s Max: %s",
- format_stat(type, data.units, data.min),
- format_stat(type, data.units, value),
- format_stat(type, data.units, data.max)
- ));
- table.insert(stat_info.output, string.format("Q1: %s Median: %s Q3: %s",
- format_stat(type, data.units, statistics.get_percentile(data, 25)),
- format_stat(type, data.units, statistics.get_percentile(data, 50)),
- format_stat(type, data.units, statistics.get_percentile(data, 75))
- ));
- end
- end
- return self;
+function stats_methods:render_single_fancy_histogram_cf(print, prefix, metric_family, metric)
+ -- cf = cumulative frequency
+ return self:render_single_fancy_histogram_ex(print, prefix, metric_family, metric, true)
end
function stats_methods:cfgraph()
for _, stat_info in ipairs(self) do
- local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211
+ local family_name, metric_family = unpack(stat_info, 1, 2)
local function print(s)
table.insert(stat_info.output, s);
end
- if data and data.sample_count and data.sample_count > 0 then
- local raw_histogram = require "util.statistics".get_histogram(data);
-
- local graph_width, graph_height = 50, 10;
- local eighth_chars = " ▁▂▃▄▅▆▇█";
-
- local range = data.max - data.min;
-
- if range > 0 then
- local x_scaling = #raw_histogram/graph_width;
- local histogram = {};
- for i = 1, graph_width do
- histogram[i] = math.max(raw_histogram[i*x_scaling-1] or 0, raw_histogram[i*x_scaling] or 0);
- end
-
- print("");
- print(("_"):rep(52)..format_stat(type, data.units, data.max));
- for row = graph_height, 1, -1 do
- local row_chars = {};
- local min_eighths, max_eighths = 8, 0;
- for i = 1, #histogram do
- local char_eighths = math.ceil(math.max(math.min((graph_height/(data.max/histogram[i]))-(row-1), 1), 0)*8);
- if char_eighths < min_eighths then
- min_eighths = char_eighths;
- end
- if char_eighths > max_eighths then
- max_eighths = char_eighths;
- end
- if char_eighths == 0 then
- row_chars[i] = "-";
- else
- local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3);
- row_chars[i] = char;
- end
- end
- print(table.concat(row_chars).."|-"..format_stat(type, data.units, data.max/(graph_height/(row-0.5))));
- end
- print(("\\ "):rep(11));
- local x_labels = {};
- for i = 1, 11 do
- local s = ("%-4s"):format((i-1)*10);
- if #s > 4 then
- s = s:sub(1, 3).."…";
- end
- x_labels[i] = s;
- end
- print(" "..table.concat(x_labels, " "));
- local units = "%";
- local margin = math.floor((graph_width-#units)/2);
- print((" "):rep(margin)..units);
- else
- print("[range too small to graph]");
- end
- print("");
+ if not self:render_family(print, family_name, metric_family, self.render_single_fancy_histogram_cf) then
+ return self
end
end
return self;
@@ -1530,81 +1458,90 @@ end
function stats_methods:histogram()
for _, stat_info in ipairs(self) do
- local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211
+ local family_name, metric_family = unpack(stat_info, 1, 2)
local function print(s)
table.insert(stat_info.output, s);
end
- if not data then
- print("[no data]");
- return self;
- elseif not data.sample_count then
- print("[not a sampled metric type]");
- return self;
+ if not self:render_family(print, family_name, metric_family, self.render_single_fancy_histogram) then
+ return self
end
+ end
+ return self;
+end
- local graph_width, graph_height = 50, 10;
- local eighth_chars = " ▁▂▃▄▅▆▇█";
-
- local range = data.max - data.min;
+function stats_methods:render_single_counter(print, prefix, metric_family, metric)
+ local created_timestamp, current_value
+ for suffix, _, value in metric:iter_samples() do
+ if suffix == "_created" then
+ created_timestamp = value
+ elseif suffix == "_total" then
+ current_value = value
+ end
+ end
+ if current_value and created_timestamp then
+ local base_unit = short_units[metric_family.unit] or metric_family.unit
+ local unit = base_unit .. "/s"
+ local factor = 1
+ if base_unit == "s" then
+ -- be smart!
+ unit = "%"
+ factor = 100
+ elseif base_unit == "" then
+ unit = "events/s"
+ end
+ print(("%-50s %s"):format(prefix, format_number(factor * current_value / (self.now - created_timestamp), unit.." [avg]")));
+ end
+end
- if range > 0 then
- local n_buckets = graph_width;
+function stats_methods:render_single_gauge(print, prefix, metric_family, metric)
+ local current_value
+ for _, _, value in metric:iter_samples() do
+ current_value = value
+ end
+ if current_value then
+ local unit = short_units[metric_family.unit] or metric_family.unit
+ print(("%-50s %s"):format(prefix, format_number(current_value, unit)));
+ end
+end
- local histogram = {};
- for i = 1, n_buckets do
- histogram[i] = 0;
- end
- local max_bin_samples = 0;
- for _, d in ipairs(data.samples) do
- local bucket = math.floor(1+(n_buckets-1)/(range/(d-data.min)));
- histogram[bucket] = histogram[bucket] + 1;
- if histogram[bucket] > max_bin_samples then
- max_bin_samples = histogram[bucket];
- end
- end
+function stats_methods:render_single_summary(print, prefix, metric_family, metric)
+ local sum, count
+ for suffix, _, value in metric:iter_samples() do
+ if suffix == "_sum" then
+ sum = value
+ elseif suffix == "_count" then
+ count = value
+ end
+ end
+ if sum and count then
+ local unit = short_units[metric_family.unit] or metric_family.unit
+ if count == 0 then
+ print(("%-50s %s"):format(prefix, "no obs."));
+ else
+ print(("%-50s %s"):format(prefix, format_number(sum / count, unit.."/event [avg]")));
+ end
+ end
+end
- print("");
- print(("_"):rep(52)..max_bin_samples);
- for row = graph_height, 1, -1 do
- local row_chars = {};
- local min_eighths, max_eighths = 8, 0;
- for i = 1, #histogram do
- local char_eighths = math.ceil(math.max(math.min((graph_height/(max_bin_samples/histogram[i]))-(row-1), 1), 0)*8);
- if char_eighths < min_eighths then
- min_eighths = char_eighths;
- end
- if char_eighths > max_eighths then
- max_eighths = char_eighths;
- end
- if char_eighths == 0 then
- row_chars[i] = "-";
- else
- local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3);
- row_chars[i] = char;
- end
- end
- print(table.concat(row_chars).."|-"..math.ceil((max_bin_samples/graph_height)*(row-0.5)));
- end
- print(("\\ "):rep(11));
- local x_labels = {};
- for i = 1, 11 do
- local s = ("%-4s"):format(format_stat(type, data.units, data.min+range*i/11, data.min):match("^%S+"));
- if #s > 4 then
- s = s:sub(1, 3).."…";
- end
- x_labels[i] = s;
+function stats_methods:render_family(print, family_name, metric_family, render_func)
+ local labelkeys = metric_family.label_keys
+ if #labelkeys > 0 then
+ print(family_name)
+ for labelset, metric in metric_family:iter_metrics() do
+ local labels = {}
+ for i, k in ipairs(labelkeys) do
+ local v = labelset[i]
+ t_insert(labels, ("%s=%s"):format(k, v))
end
- print(" "..table.concat(x_labels, " "));
- local units = format_stat(type, data.units, data.min):match("%s+(.+)$") or data.units or "";
- local margin = math.floor((graph_width-#units)/2);
- print((" "):rep(margin)..units);
- else
- print("[range too small to graph]");
+ local prefix = " "..t_concat(labels, " ")
+ render_func(self, print, prefix, metric_family, metric)
+ end
+ else
+ for _, metric in metric_family:iter_metrics() do
+ render_func(self, print, family_name, metric_family, metric)
end
- print("");
end
- return self;
end
local function stats_tostring(stats)
@@ -1618,7 +1555,14 @@ local function stats_tostring(stats)
end
print("");
else
- print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], (stat_info[4] or {}).units, stat_info[3])));
+ local metric_family = stat_info[2]
+ if metric_family.type_ == "counter" then
+ stats:render_family(print, stat_info[1], metric_family, stats.render_single_counter)
+ elseif metric_family.type_ == "gauge" or metric_family.type_ == "unknown" then
+ stats:render_family(print, stat_info[1], metric_family, stats.render_single_gauge)
+ elseif metric_family.type_ == "summary" or metric_family.type_ == "histogram" then
+ stats:render_family(print, stat_info[1], metric_family, stats.render_single_summary)
+ end
end
end
return #stats.." statistics displayed";
@@ -1626,23 +1570,29 @@ end
local stats_mt = {__index = stats_methods, __tostring = stats_tostring }
local function new_stats_context(self)
- return setmetatable({ session = self.session, stats = true }, stats_mt);
+ -- TODO: instead of now(), it might be better to take the time of the last
+ -- interval, if the statistics backend is set to use periodic collection
+ -- Otherwise we get strange stuff like average cpu usage decreasing until
+ -- the next sample and so on.
+ return setmetatable({ session = self.session, stats = true, now = time.now() }, stats_mt);
end
-function def_env.stats:show(filter)
- -- luacheck: ignore 211/changed
- local stats, changed, extra = require "core.statsmanager".get_stats();
- local available, displayed = 0, 0;
+function def_env.stats:show(name_filter)
+ local statsman = require "core.statsmanager"
+ local collect = statsman.collect
+ if collect then
+ -- force collection if in manual mode
+ collect()
+ end
+ local metric_registry = statsman.get_metric_registry();
local displayed_stats = new_stats_context(self);
- for name, value in iterators.sorted_pairs(stats) do
- available = available + 1;
- if not filter or name:match(filter) then
- displayed = displayed + 1;
- local type = name:match(":(%a+)$");
+ for family_name, metric_family in iterators.sorted_pairs(metric_registry:get_metric_families()) do
+ if not name_filter or family_name:match(name_filter) then
table.insert(displayed_stats, {
- name, type, value, extra[name];
- output = {};
- });
+ family_name,
+ metric_family,
+ output = {}
+ })
end
end
return displayed_stats;