Merge 0.10->trunk
[prosody.git] / util / statistics.lua
1 local t_sort = table.sort
2 local m_floor = math.floor;
3 local time = require "socket".gettime;
4
5 local function nop_function() end
6
7 local function percentile(arr, length, pc)
8         local n = pc/100 * (length + 1);
9         local k, d = m_floor(n), n%1;
10         if k == 0 then
11                 return arr[1] or 0;
12         elseif k >= length then
13                 return arr[length];
14         end
15         return arr[k] + d*(arr[k+1] - arr[k]);
16 end
17
18 local function new_registry(config)
19         config = config or {};
20         local duration_sample_interval = config.duration_sample_interval or 5;
21         local duration_max_samples = config.duration_max_stored_samples or 5000;
22
23         local function get_distribution_stats(events, n_actual_events, since, new_time, units)
24                 local n_stored_events = #events;
25                 t_sort(events);
26                 local sum = 0;
27                 for i = 1, n_stored_events do
28                         sum = sum + events[i];
29                 end
30
31                 return {
32                         samples = events;
33                         sample_count = n_stored_events;
34                         count = n_actual_events,
35                         rate = n_actual_events/(new_time-since);
36                         average = n_stored_events > 0 and sum/n_stored_events or 0,
37                         min = events[1] or 0,
38                         max = events[n_stored_events] or 0,
39                         units = units,
40                 };
41         end
42
43
44         local registry = {};
45         local methods;
46         methods = {
47                 amount = function (name, initial)
48                         local v = initial or 0;
49                         registry[name..":amount"] = function () return "amount", v; end
50                         return function (new_v) v = new_v; end
51                 end;
52                 counter = function (name, initial)
53                         local v = initial or 0;
54                         registry[name..":amount"] = function () return "amount", v; end
55                         return function (delta)
56                                 v = v + delta;
57                         end;
58                 end;
59                 rate = function (name)
60                         local since, n = time(), 0;
61                         registry[name..":rate"] = function ()
62                                 local t = time();
63                                 local stats = {
64                                         rate = n/(t-since);
65                                         count = n;
66                                 };
67                                 since, n = t, 0;
68                                 return "rate", stats.rate, stats;
69                         end;
70                         return function ()
71                                 n = n + 1;
72                         end;
73                 end;
74                 distribution = function (name, unit, type)
75                         type = type or "distribution";
76                         local events, last_event = {}, 0;
77                         local n_actual_events = 0;
78                         local since = time();
79
80                         registry[name..":"..type] = function ()
81                                 local new_time = time();
82                                 local stats = get_distribution_stats(events, n_actual_events, since, new_time, unit);
83                                 events, last_event = {}, 0;
84                                 n_actual_events = 0;
85                                 since = new_time;
86                                 return type, stats.average, stats;
87                         end;
88
89                         return function (value)
90                                 n_actual_events = n_actual_events + 1;
91                                 if n_actual_events%duration_sample_interval == 1 then
92                                         last_event = (last_event%duration_max_samples) + 1;
93                                         events[last_event] = value;
94                                 end
95                         end;
96                 end;
97                 sizes = function (name)
98                         return methods.distribution(name, "bytes", "size");
99                 end;
100                 times = function (name)
101                         local events, last_event = {}, 0;
102                         local n_actual_events = 0;
103                         local since = time();
104
105                         registry[name..":duration"] = function ()
106                                 local new_time = time();
107                                 local stats = get_distribution_stats(events, n_actual_events, since, new_time, "seconds");
108                                 events, last_event = {}, 0;
109                                 n_actual_events = 0;
110                                 since = new_time;
111                                 return "duration", stats.average, stats;
112                         end;
113
114                         return function ()
115                                 n_actual_events = n_actual_events + 1;
116                                 if n_actual_events%duration_sample_interval ~= 1 then
117                                         return nop_function;
118                                 end
119
120                                 local start_time = time();
121                                 return function ()
122                                         local end_time = time();
123                                         local duration = end_time - start_time;
124                                         last_event = (last_event%duration_max_samples) + 1;
125                                         events[last_event] = duration;
126                                 end
127                         end;
128                 end;
129
130                 get_stats = function ()
131                         return registry;
132                 end;
133         };
134         return methods;
135 end
136
137 return {
138         new = new_registry;
139         get_histogram = function (duration, n_buckets)
140                 n_buckets = n_buckets or 100;
141                 local events, n_events = duration.samples, duration.sample_count;
142                 if not (events and n_events) then
143                         return nil, "not a valid distribution stat";
144                 end
145                 local histogram = {};
146
147                 for i = 1, 100, 100/n_buckets do
148                         histogram[i] = percentile(events, n_events, i);
149                 end
150                 return histogram;
151         end;
152
153         get_percentile = function (duration, pc)
154                 local events, n_events = duration.samples, duration.sample_count;
155                 if not (events and n_events) then
156                         return nil, "not a valid distribution stat";
157                 end
158                 return percentile(events, n_events, pc);
159         end;
160 }