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
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 };