Software /
code /
prosody
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 |