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