Mercurial > prosody-modules
comparison mod_statistics/top.lib.lua @ 3414:66bda434d476
mod_statistics: Update 'top' to (hopefully) work with Prosody 0.11/Lua 5.2
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Sun, 23 Dec 2018 11:00:38 +0000 |
| parents | mod_statistics/top.lua@7dbde05b48a9 |
| children | f16c0b5e4fa6 |
comparison
equal
deleted
inserted
replaced
| 3413:6421c9f05e02 | 3414:66bda434d476 |
|---|---|
| 1 local array = require "util.array"; | |
| 2 local it = require "util.iterators"; | |
| 3 local curses = require "curses"; | |
| 4 local stats = module:require "stats".stats; | |
| 5 local time = require "socket".gettime; | |
| 6 | |
| 7 local sessions_idle_after = 60; | |
| 8 local stanza_names = {"message", "presence", "iq"}; | |
| 9 | |
| 10 local top = {}; | |
| 11 top.__index = top; | |
| 12 | |
| 13 local status_lines = { | |
| 14 "Prosody $version - $time up $up_since, $total_users users, $cpu busy"; | |
| 15 "Connections: $total_c2s c2s, $total_s2sout s2sout, $total_s2sin s2sin, $total_component component"; | |
| 16 "Memory: $memory_lua lua, $memory_allocated process ($memory_used in use)"; | |
| 17 "Stanzas in: $message_in_per_second message/s, $presence_in_per_second presence/s, $iq_in_per_second iq/s"; | |
| 18 "Stanzas out: $message_out_per_second message/s, $presence_out_per_second presence/s, $iq_out_per_second iq/s"; | |
| 19 }; | |
| 20 | |
| 21 function top:draw() | |
| 22 self:draw_status(); | |
| 23 self:draw_column_titles(); | |
| 24 self:draw_conn_list(); | |
| 25 self.statuswin:refresh(); | |
| 26 self.listwin:refresh(); | |
| 27 --self.infowin:refresh() | |
| 28 self.stdscr:move(#status_lines,0) | |
| 29 end | |
| 30 | |
| 31 -- Width specified as cols or % of unused space, defaults to | |
| 32 -- title width if not specified | |
| 33 local conn_list_columns = { | |
| 34 { title = "ID", key = "id", width = "8" }; | |
| 35 { title = "JID", key = "jid", width = "100%" }; | |
| 36 { title = "STANZAS IN>", key = "total_stanzas_in", align = "right" }; | |
| 37 { title = "MSG", key = "message_in", align = "right", width = "4" }; | |
| 38 { title = "PRES", key = "presence_in", align = "right", width = "4" }; | |
| 39 { title = "IQ", key = "iq_in", align = "right", width = "4" }; | |
| 40 { title = "STANZAS OUT>", key = "total_stanzas_out", align = "right" }; | |
| 41 { title = "MSG", key = "message_out", align = "right", width = "4" }; | |
| 42 { title = "PRES", key = "presence_out", align = "right", width = "4" }; | |
| 43 { title = "IQ", key = "iq_out", align = "right", width = "4" }; | |
| 44 { title = "BYTES IN", key = "bytes_in", align = "right" }; | |
| 45 { title = "BYTES OUT", key = "bytes_out", align = "right" }; | |
| 46 | |
| 47 }; | |
| 48 | |
| 49 function top:draw_status() | |
| 50 for row, line in ipairs(status_lines) do | |
| 51 self.statuswin:mvaddstr(row-1, 0, (line:gsub("%$([%w_]+)", self.data))); | |
| 52 self.statuswin:clrtoeol(); | |
| 53 end | |
| 54 -- Clear stanza counts | |
| 55 for _, stanza_type in ipairs(stanza_names) do | |
| 56 self.prosody[stanza_type.."_in_per_second"] = 0; | |
| 57 self.prosody[stanza_type.."_out_per_second"] = 0; | |
| 58 end | |
| 59 end | |
| 60 | |
| 61 local function padright(s, width) | |
| 62 return s..string.rep(" ", width-#s); | |
| 63 end | |
| 64 | |
| 65 local function padleft(s, width) | |
| 66 return string.rep(" ", width-#s)..s; | |
| 67 end | |
| 68 | |
| 69 function top:resized() | |
| 70 self:recalc_column_widths(); | |
| 71 --self.stdscr:clear(); | |
| 72 self:draw(); | |
| 73 end | |
| 74 | |
| 75 function top:recalc_column_widths() | |
| 76 local widths = {}; | |
| 77 self.column_widths = widths; | |
| 78 local total_width = curses.cols()-4; | |
| 79 local free_width = total_width; | |
| 80 for i = 1, #conn_list_columns do | |
| 81 local width = conn_list_columns[i].width or "0"; | |
| 82 if not(type(width) == "string" and width:sub(-1) == "%") then | |
| 83 width = math.max(tonumber(width), #conn_list_columns[i].title+1); | |
| 84 widths[i] = width; | |
| 85 free_width = free_width - width; | |
| 86 end | |
| 87 end | |
| 88 for i = 1, #conn_list_columns do | |
| 89 if not widths[i] then | |
| 90 local pc_width = tonumber((conn_list_columns[i].width:gsub("%%$", ""))); | |
| 91 widths[i] = math.floor(free_width*(pc_width/100)); | |
| 92 end | |
| 93 end | |
| 94 return widths; | |
| 95 end | |
| 96 | |
| 97 function top:draw_column_titles() | |
| 98 local widths = self.column_widths; | |
| 99 self.listwin:attron(curses.A_REVERSE); | |
| 100 self.listwin:mvaddstr(0, 0, " "); | |
| 101 for i, column in ipairs(conn_list_columns) do | |
| 102 self.listwin:addstr(padright(column.title, widths[i])); | |
| 103 end | |
| 104 self.listwin:addstr(" "); | |
| 105 self.listwin:attroff(curses.A_REVERSE); | |
| 106 end | |
| 107 | |
| 108 local function session_compare(session1, session2) | |
| 109 local stats1, stats2 = session1.stats, session2.stats; | |
| 110 return (stats1.total_stanzas_in + stats1.total_stanzas_out) > | |
| 111 (stats2.total_stanzas_in + stats2.total_stanzas_out); | |
| 112 end | |
| 113 | |
| 114 function top:draw_conn_list() | |
| 115 local rows = curses.lines()-(#status_lines+2)-5; | |
| 116 local cutoff_time = time() - sessions_idle_after; | |
| 117 local widths = self.column_widths; | |
| 118 local top_sessions = array.collect(it.values(self.active_sessions)):sort(session_compare); | |
| 119 for index = 1, rows do | |
| 120 local session = top_sessions[index]; | |
| 121 if session then | |
| 122 if session.last_update < cutoff_time then | |
| 123 self.active_sessions[session.id] = nil; | |
| 124 else | |
| 125 local row = {}; | |
| 126 for i, column in ipairs(conn_list_columns) do | |
| 127 local width = widths[i]; | |
| 128 local v = tostring(session[column.key] or ""):sub(1, width); | |
| 129 if #v < width then | |
| 130 if column.align == "right" then | |
| 131 v = padleft(v, width-1).." "; | |
| 132 else | |
| 133 v = padright(v, width); | |
| 134 end | |
| 135 end | |
| 136 table.insert(row, v); | |
| 137 end | |
| 138 if session.updated then | |
| 139 self.listwin:attron(curses.A_BOLD); | |
| 140 end | |
| 141 self.listwin:mvaddstr(index, 0, " "..table.concat(row)); | |
| 142 if session.updated then | |
| 143 session.updated = false; | |
| 144 self.listwin:attroff(curses.A_BOLD); | |
| 145 end | |
| 146 end | |
| 147 else | |
| 148 -- FIXME: How to clear a line? It's 5am and I don't feel like reading docs. | |
| 149 self.listwin:move(index, 0); | |
| 150 self.listwin:clrtoeol(); | |
| 151 end | |
| 152 end | |
| 153 end | |
| 154 | |
| 155 function top:update_stat(name, value) | |
| 156 self.prosody[name] = value; | |
| 157 end | |
| 158 | |
| 159 function top:update_session(id, jid, stats) | |
| 160 self.active_sessions[id] = stats; | |
| 161 stats.id, stats.jid, stats.stats = id, jid, stats; | |
| 162 stats.total_bytes = stats.bytes_in + stats.bytes_out; | |
| 163 for _, stanza_type in ipairs(stanza_names) do | |
| 164 self.prosody[stanza_type.."_in_per_second"] = (self.prosody[stanza_type.."_in_per_second"] or 0) + stats[stanza_type.."_in"]; | |
| 165 self.prosody[stanza_type.."_out_per_second"] = (self.prosody[stanza_type.."_out_per_second"] or 0) + stats[stanza_type.."_out"]; | |
| 166 end | |
| 167 stats.total_stanzas_in = stats.message_in + stats.presence_in + stats.iq_in; | |
| 168 stats.total_stanzas_out = stats.message_out + stats.presence_out + stats.iq_out; | |
| 169 stats.last_update = time(); | |
| 170 stats.updated = true; | |
| 171 end | |
| 172 | |
| 173 local function new(base) | |
| 174 setmetatable(base, top); | |
| 175 base.data = setmetatable({}, { | |
| 176 __index = function (t, k) | |
| 177 local stat = stats[k]; | |
| 178 if stat and stat.tostring then | |
| 179 if type(stat.tostring) == "function" then | |
| 180 return stat.tostring(base.prosody[k]); | |
| 181 elseif type(stat.tostring) == "string" then | |
| 182 local v = base.prosody[k]; | |
| 183 if v == nil then | |
| 184 return "?"; | |
| 185 end | |
| 186 return (stat.tostring):format(v); | |
| 187 end | |
| 188 end | |
| 189 return base.prosody[k]; | |
| 190 end; | |
| 191 }); | |
| 192 | |
| 193 base.active_sessions = {}; | |
| 194 | |
| 195 base.statuswin = curses.newwin(#status_lines, 0, 0, 0); | |
| 196 | |
| 197 base.promptwin = curses.newwin(1, 0, #status_lines, 0); | |
| 198 base.promptwin:addstr(""); | |
| 199 base.promptwin:refresh(); | |
| 200 | |
| 201 base.listwin = curses.newwin(curses.lines()-(#status_lines+2)-5, 0, #status_lines+1, 0); | |
| 202 base.listwin:syncok(); | |
| 203 | |
| 204 base.infowin = curses.newwin(5, 0, curses.lines()-5, 0); | |
| 205 base.infowin:mvaddstr(1, 1, "Hello world"); | |
| 206 base.infowin:border(0,0,0,0); | |
| 207 base.infowin:syncok(); | |
| 208 base.infowin:refresh(); | |
| 209 | |
| 210 base:resized(); | |
| 211 | |
| 212 return base; | |
| 213 end | |
| 214 | |
| 215 return { new = new }; |
