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 |