Comparison

plugins/mod_admin_telnet.lua @ 8964:76780f37028d

mod_admin_telnet: Add some experimental commands for inspecting stats
author Matthew Wild <mwild1@gmail.com>
date Wed, 04 Jul 2018 06:57:32 +0100
parent 8922:47d318eeff8d
child 9009:4c745d42c974
comparison
equal deleted inserted replaced
8963:ff522d5db95d 8964:76780f37028d
1193 for conn, session in pairs(sessions) do 1193 for conn, session in pairs(sessions) do
1194 session.print("Shutting down: "..(event.reason or "unknown reason")); 1194 session.print("Shutting down: "..(event.reason or "unknown reason"));
1195 end 1195 end
1196 end); 1196 end);
1197 1197
1198 def_env.stats = {};
1199
1200 local function format_stat(type, value, ref_value)
1201 ref_value = ref_value or value;
1202 --do return tostring(value) end
1203 if type == "duration" then
1204 if ref_value < 0.001 then
1205 return ("%d µs"):format(value*1000000);
1206 elseif ref_value < 0.9 then
1207 return ("%0.2f ms"):format(value*1000);
1208 end
1209 return ("%0.2f"):format(value);
1210 elseif type == "size" then
1211 if ref_value > 1048576 then
1212 return ("%d MB"):format(value/1048576);
1213 elseif ref_value > 1024 then
1214 return ("%d KB"):format(value/1024);
1215 end
1216 return ("%d bytes"):format(value);
1217 elseif type == "rate" then
1218 if ref_value < 0.9 then
1219 return ("%0.2f/min"):format(value*60);
1220 end
1221 return ("%0.2f/sec"):format(value);
1222 end
1223 return tostring(value);
1224 end
1225
1226 local stats_methods = {};
1227 function stats_methods:bounds(_lower, _upper)
1228 local statistics = require "util.statistics";
1229 for _, stat_info in ipairs(self) do
1230 local data = stat_info[4];
1231 if data then
1232 local lower = _lower or data.min;
1233 local upper = _upper or data.max;
1234 local new_data = {
1235 min = lower;
1236 max = upper;
1237 samples = {};
1238 sample_count = 0;
1239 count = data.count;
1240 units = data.units;
1241 };
1242 local sum = 0;
1243 for i, v in ipairs(data.samples) do
1244 if v > upper then
1245 break;
1246 elseif v>=lower then
1247 table.insert(new_data.samples, v);
1248 sum = sum + v;
1249 end
1250 end
1251 new_data.sample_count = #new_data.samples;
1252 stat_info[4] = new_data;
1253 stat_info[3] = sum/new_data.sample_count;
1254 end
1255 end
1256 return self;
1257 end
1258
1259 function stats_methods:trim(lower, upper)
1260 upper = upper or (100-lower);
1261 local statistics = require "util.statistics";
1262 for _, stat_info in ipairs(self) do
1263 -- Strip outliers
1264 local data = stat_info[4];
1265 if data then
1266 local new_data = {
1267 min = statistics.get_percentile(data, lower);
1268 max = statistics.get_percentile(data, upper);
1269 samples = {};
1270 sample_count = 0;
1271 count = data.count;
1272 units = data.units;
1273 };
1274 local sum = 0;
1275 for i, v in ipairs(data.samples) do
1276 if v > new_data.max then
1277 break;
1278 elseif v>=new_data.min then
1279 table.insert(new_data.samples, v);
1280 sum = sum + v;
1281 end
1282 end
1283 new_data.sample_count = #new_data.samples;
1284 stat_info[4] = new_data;
1285 stat_info[3] = sum/new_data.sample_count;
1286 end
1287 end
1288 return self;
1289 end
1290
1291 function stats_methods:max(upper)
1292 return self:bounds(nil, upper);
1293 end
1294
1295 function stats_methods:min(lower)
1296 return self:bounds(lower, nil);
1297 end
1298
1299 function stats_methods:summary()
1300 local statistics = require "util.statistics";
1301 for _, stat_info in ipairs(self) do
1302 local name, type, value, data = stat_info[1], stat_info[2], stat_info[3], stat_info[4];
1303 if data and data.samples then
1304 table.insert(stat_info.output, string.format("Count: %d (%d captured)",
1305 data.count,
1306 data.sample_count
1307 ));
1308 table.insert(stat_info.output, string.format("Min: %s Mean: %s Max: %s",
1309 format_stat(type, data.min),
1310 format_stat(type, value),
1311 format_stat(type, data.max)
1312 ));
1313 table.insert(stat_info.output, string.format("Q1: %s Median: %s Q3: %s",
1314 format_stat(type, statistics.get_percentile(data, 25)),
1315 format_stat(type, statistics.get_percentile(data, 50)),
1316 format_stat(type, statistics.get_percentile(data, 75))
1317 ));
1318 end
1319 end
1320 return self;
1321 end
1322
1323 function stats_methods:cfgraph()
1324 for _, stat_info in ipairs(self) do
1325 local name, type, value, data = unpack(stat_info, 1, 4);
1326 local function print(s)
1327 table.insert(stat_info.output, s);
1328 end
1329
1330 if data and data.sample_count > 0 then
1331 local raw_histogram = require "util.statistics".get_histogram(data);
1332
1333 local graph_width, graph_height = 50, 10;
1334 local eighth_chars = " ▁▂▃▄▅▆▇█";
1335
1336 local range = data.max - data.min;
1337
1338 if range > 0 then
1339 local x_scaling = #raw_histogram/graph_width;
1340 local histogram = {};
1341 for i = 1, graph_width do
1342 histogram[i] = math.max(raw_histogram[i*x_scaling-1] or 0, raw_histogram[i*x_scaling] or 0);
1343 end
1344
1345 print("");
1346 print(("_"):rep(52)..format_stat(type, data.max));
1347 for row = graph_height, 1, -1 do
1348 local row_chars = {};
1349 local min_eighths, max_eighths = 8, 0;
1350 for i = 1, #histogram do
1351 local char_eighths = math.ceil(math.max(math.min((graph_height/(data.max/histogram[i]))-(row-1), 1), 0)*8);
1352 if char_eighths < min_eighths then
1353 min_eighths = char_eighths;
1354 end
1355 if char_eighths > max_eighths then
1356 max_eighths = char_eighths;
1357 end
1358 if char_eighths == 0 then
1359 row_chars[i] = "-";
1360 else
1361 local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3);
1362 row_chars[i] = char;
1363 end
1364 end
1365 print(table.concat(row_chars).."|-"..format_stat(type, data.max/(graph_height/(row-0.5))));
1366 end
1367 print(("\\ "):rep(11));
1368 local x_labels = {};
1369 for i = 1, 11 do
1370 local s = ("%-4s"):format((i-1)*10);
1371 if #s > 4 then
1372 s = s:sub(1, 3).."…";
1373 end
1374 x_labels[i] = s;
1375 end
1376 print(" "..table.concat(x_labels, " "));
1377 local units = "%";
1378 local margin = math.floor((graph_width-#units)/2);
1379 print((" "):rep(margin)..units);
1380 else
1381 print("[range too small to graph]");
1382 end
1383 print("");
1384 end
1385 end
1386 return self;
1387 end
1388
1389 function stats_methods:histogram()
1390 for _, stat_info in ipairs(self) do
1391 local name, type, value, data = unpack(stat_info, 1, 4);
1392 local function print(s)
1393 table.insert(stat_info.output, s);
1394 end
1395
1396 if not data then
1397 print("[no data]");
1398 return self;
1399 elseif not data.sample_count then
1400 print("[not a sampled metric type]");
1401 return self;
1402 end
1403
1404 local raw_histogram = require "util.statistics".get_histogram(data);
1405
1406 local graph_width, graph_height = 50, 10;
1407 local eighth_chars = " ▁▂▃▄▅▆▇█";
1408
1409 local range = data.max - data.min;
1410
1411 if range > 0 then
1412 local n_buckets = graph_width;
1413
1414 local histogram = {};
1415 for i = 1, n_buckets do
1416 histogram[i] = 0;
1417 end
1418 local max_bin_samples = 0;
1419 for i, d in ipairs(data.samples) do
1420 local bucket = math.floor(1+(n_buckets-1)/(range/(d-data.min)));
1421 histogram[bucket] = histogram[bucket] + 1;
1422 if histogram[bucket] > max_bin_samples then
1423 max_bin_samples = histogram[bucket];
1424 end
1425 end
1426
1427 print("");
1428 print(("_"):rep(52)..max_bin_samples);
1429 for row = graph_height, 1, -1 do
1430 local row_chars = {};
1431 local min_eighths, max_eighths = 8, 0;
1432 for i = 1, #histogram do
1433 local char_eighths = math.ceil(math.max(math.min((graph_height/(max_bin_samples/histogram[i]))-(row-1), 1), 0)*8);
1434 if char_eighths < min_eighths then
1435 min_eighths = char_eighths;
1436 end
1437 if char_eighths > max_eighths then
1438 max_eighths = char_eighths;
1439 end
1440 if char_eighths == 0 then
1441 row_chars[i] = "-";
1442 else
1443 local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3);
1444 row_chars[i] = char;
1445 end
1446 end
1447 print(table.concat(row_chars).."|-"..math.ceil((max_bin_samples/graph_height)*(row-0.5)));
1448 end
1449 print(("\\ "):rep(11));
1450 local x_labels = {};
1451 for i = 1, 11 do
1452 local s = ("%-4s"):format(format_stat(type, data.min+range*i/11, data.min):match("^%S+"));
1453 if #s > 4 then
1454 s = s:sub(1, 3).."…";
1455 end
1456 x_labels[i] = s;
1457 end
1458 print(" "..table.concat(x_labels, " "));
1459 local units = format_stat(type, data.min):match("%s+(.+)$") or data.units or "";
1460 local margin = math.floor((graph_width-#units)/2);
1461 print((" "):rep(margin)..units);
1462 else
1463 print("[range too small to graph]");
1464 end
1465 print("");
1466 end
1467 return self;
1468 end
1469
1470 local function stats_tostring(stats)
1471 local print = stats.session.print;
1472 for _, stat_info in ipairs(stats) do
1473 if #stat_info.output > 0 then
1474 print("\n#"..stat_info[1]);
1475 print("");
1476 for i, v in ipairs(stat_info.output) do
1477 print(v);
1478 end
1479 print("");
1480 else
1481 print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], stat_info[3])));
1482 end
1483 end
1484 return #stats.." statistics displayed";
1485 end
1486
1487 local function new_stats_context(self)
1488 return setmetatable({ session = self.session, stats = true }, {__index = stats_methods, __tostring = stats_tostring });
1489 end
1490
1491 function def_env.stats:show(filter)
1492 local print = self.session.print;
1493 local stats, changed, extra = require "core.statsmanager".get_stats();
1494 local available, displayed = 0, 0;
1495 local displayed_stats = new_stats_context(self);
1496 for name, value in pairs(stats) do
1497 available = available + 1;
1498 if not filter or name:match(filter) then
1499 displayed = displayed + 1;
1500 local type = name:match(":(%a+)$");
1501 table.insert(displayed_stats, {
1502 name, type, value, extra[name];
1503 output = {};
1504 });
1505 end
1506 end
1507 return displayed_stats;
1508 end
1509
1510
1511
1198 ------------- 1512 -------------
1199 1513
1200 function printbanner(session) 1514 function printbanner(session)
1201 local option = module:get_option_string("console_banner", "full"); 1515 local option = module:get_option_string("console_banner", "full");
1202 if option == "full" or option == "graphic" then 1516 if option == "full" or option == "graphic" then