Software /
code /
prosody-modules
File
mod_statistics/top.lib.lua @ 5119:048e339706ba
mod_rest: Remove manual reference expansion in schema
This hack was originally added to reduce the number of definitions of
common attributes (type, to, from etc) and payloads (e.g. delay). This
predated pointers and references, and until now was needed because
parsing picked out the correct stanza kind from the schema, which broke
internal references.
Removing this hack paves the way for allowing the schema to be
configured or customized more easily.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Tue, 20 Dec 2022 21:48:28 +0100 |
parent | 3414:66bda434d476 |
line wrap: on
line source
local array = require "util.array"; local it = require "util.iterators"; local curses = require "curses"; local stats = module:require "stats".stats; local time = require "socket".gettime; local sessions_idle_after = 60; local stanza_names = {"message", "presence", "iq"}; local top = {}; top.__index = top; local status_lines = { "Prosody $version - $time up $up_since, $total_users users, $cpu busy"; "Connections: $total_c2s c2s, $total_s2sout s2sout, $total_s2sin s2sin, $total_component component"; "Memory: $memory_lua lua, $memory_allocated process ($memory_used in use)"; "Stanzas in: $message_in_per_second message/s, $presence_in_per_second presence/s, $iq_in_per_second iq/s"; "Stanzas out: $message_out_per_second message/s, $presence_out_per_second presence/s, $iq_out_per_second iq/s"; }; function top:draw() self:draw_status(); self:draw_column_titles(); self:draw_conn_list(); self.statuswin:refresh(); self.listwin:refresh(); --self.infowin:refresh() self.stdscr:move(#status_lines,0) end -- Width specified as cols or % of unused space, defaults to -- title width if not specified local conn_list_columns = { { title = "ID", key = "id", width = "8" }; { title = "JID", key = "jid", width = "100%" }; { title = "STANZAS IN>", key = "total_stanzas_in", align = "right" }; { title = "MSG", key = "message_in", align = "right", width = "4" }; { title = "PRES", key = "presence_in", align = "right", width = "4" }; { title = "IQ", key = "iq_in", align = "right", width = "4" }; { title = "STANZAS OUT>", key = "total_stanzas_out", align = "right" }; { title = "MSG", key = "message_out", align = "right", width = "4" }; { title = "PRES", key = "presence_out", align = "right", width = "4" }; { title = "IQ", key = "iq_out", align = "right", width = "4" }; { title = "BYTES IN", key = "bytes_in", align = "right" }; { title = "BYTES OUT", key = "bytes_out", align = "right" }; }; function top:draw_status() for row, line in ipairs(status_lines) do self.statuswin:mvaddstr(row-1, 0, (line:gsub("%$([%w_]+)", self.data))); self.statuswin:clrtoeol(); end -- Clear stanza counts for _, stanza_type in ipairs(stanza_names) do self.prosody[stanza_type.."_in_per_second"] = 0; self.prosody[stanza_type.."_out_per_second"] = 0; end end local function padright(s, width) return s..string.rep(" ", width-#s); end local function padleft(s, width) return string.rep(" ", width-#s)..s; end function top:resized() self:recalc_column_widths(); --self.stdscr:clear(); self:draw(); end function top:recalc_column_widths() local widths = {}; self.column_widths = widths; local total_width = curses.cols()-4; local free_width = total_width; for i = 1, #conn_list_columns do local width = conn_list_columns[i].width or "0"; if not(type(width) == "string" and width:sub(-1) == "%") then width = math.max(tonumber(width), #conn_list_columns[i].title+1); widths[i] = width; free_width = free_width - width; end end for i = 1, #conn_list_columns do if not widths[i] then local pc_width = tonumber((conn_list_columns[i].width:gsub("%%$", ""))); widths[i] = math.floor(free_width*(pc_width/100)); end end return widths; end function top:draw_column_titles() local widths = self.column_widths; self.listwin:attron(curses.A_REVERSE); self.listwin:mvaddstr(0, 0, " "); for i, column in ipairs(conn_list_columns) do self.listwin:addstr(padright(column.title, widths[i])); end self.listwin:addstr(" "); self.listwin:attroff(curses.A_REVERSE); end local function session_compare(session1, session2) local stats1, stats2 = session1.stats, session2.stats; return (stats1.total_stanzas_in + stats1.total_stanzas_out) > (stats2.total_stanzas_in + stats2.total_stanzas_out); end function top:draw_conn_list() local rows = curses.lines()-(#status_lines+2)-5; local cutoff_time = time() - sessions_idle_after; local widths = self.column_widths; local top_sessions = array.collect(it.values(self.active_sessions)):sort(session_compare); for index = 1, rows do local session = top_sessions[index]; if session then if session.last_update < cutoff_time then self.active_sessions[session.id] = nil; else local row = {}; for i, column in ipairs(conn_list_columns) do local width = widths[i]; local v = tostring(session[column.key] or ""):sub(1, width); if #v < width then if column.align == "right" then v = padleft(v, width-1).." "; else v = padright(v, width); end end table.insert(row, v); end if session.updated then self.listwin:attron(curses.A_BOLD); end self.listwin:mvaddstr(index, 0, " "..table.concat(row)); if session.updated then session.updated = false; self.listwin:attroff(curses.A_BOLD); end end else -- FIXME: How to clear a line? It's 5am and I don't feel like reading docs. self.listwin:move(index, 0); self.listwin:clrtoeol(); end end end function top:update_stat(name, value) self.prosody[name] = value; end function top:update_session(id, jid, stats) self.active_sessions[id] = stats; stats.id, stats.jid, stats.stats = id, jid, stats; stats.total_bytes = stats.bytes_in + stats.bytes_out; for _, stanza_type in ipairs(stanza_names) do self.prosody[stanza_type.."_in_per_second"] = (self.prosody[stanza_type.."_in_per_second"] or 0) + stats[stanza_type.."_in"]; self.prosody[stanza_type.."_out_per_second"] = (self.prosody[stanza_type.."_out_per_second"] or 0) + stats[stanza_type.."_out"]; end stats.total_stanzas_in = stats.message_in + stats.presence_in + stats.iq_in; stats.total_stanzas_out = stats.message_out + stats.presence_out + stats.iq_out; stats.last_update = time(); stats.updated = true; end local function new(base) setmetatable(base, top); base.data = setmetatable({}, { __index = function (t, k) local stat = stats[k]; if stat and stat.tostring then if type(stat.tostring) == "function" then return stat.tostring(base.prosody[k]); elseif type(stat.tostring) == "string" then local v = base.prosody[k]; if v == nil then return "?"; end return (stat.tostring):format(v); end end return base.prosody[k]; end; }); base.active_sessions = {}; base.statuswin = curses.newwin(#status_lines, 0, 0, 0); base.promptwin = curses.newwin(1, 0, #status_lines, 0); base.promptwin:addstr(""); base.promptwin:refresh(); base.listwin = curses.newwin(curses.lines()-(#status_lines+2)-5, 0, #status_lines+1, 0); base.listwin:syncok(); base.infowin = curses.newwin(5, 0, curses.lines()-5, 0); base.infowin:mvaddstr(1, 1, "Hello world"); base.infowin:border(0,0,0,0); base.infowin:syncok(); base.infowin:refresh(); base:resized(); return base; end return { new = new };