Software /
code /
prosody-modules
File
mod_statistics/top.lib.lua @ 4876:0f5f2d4475b9
mod_http_xep227: Add support for import via APIs rather than direct store manipulation
In particular this transitions PEP nodes and data to be imported via mod_pep's
APIs, fixing issues with importing at runtime while PEP data may already be
live in RAM.
Next obvious candidate for this approach is rosters, so clients get immediate
roster pushes and other special handling (such as emitting subscribes to reach
the desired subscription state).
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Tue, 18 Jan 2022 17:01:18 +0000 |
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 };