@@ -5,25 +5,17 @@ local rlist = require 'algo.rlist'
55local log = require ' log'
66
77local math_floor = math.floor
8- local math_min = math.min
9- local math_max = math.max
108local setmetatable = setmetatable
119local table_new = table .new
1210local tonumber = tonumber
13- local type = type
1411
15- --- Appends all k-vs from t2 to t1, if not exists in t1
16- --- @param t1 table ?
17- --- @param t2 table ?
18- local function merge (t1 , t2 )
19- if type (t1 ) ~= ' table' or type (t2 ) ~= ' table' then return end
12+ local merge = require ' algo.utils' .merge
13+ local map_mt = require ' algo.utils' .map_mt
14+ local weak_mt = require ' algo.utils' .weak_mt
15+ local new_list = require ' algo.utils' .new_list
16+ local new_zero_list = require ' algo.utils' .new_zero_list
17+ local make_list_pretty = require ' algo.utils' .make_list_pretty
2018
21- for k in pairs (t2 ) do
22- if t1 [k ] == nil then
23- t1 [k ] = t2 [k ]
24- end
25- end
26- end
2719
2820--- Class rmean is plain-Lua implementation of Tarantool's rmean collector
2921---
4840--- @field _roller_f ? Fiber
4941
5042--- @class algo.rmean.collector.weak : algo.rlist.item
51- --- @field collector algo.rmean.collector weakref to collector
43+ --- @field collector ? algo.rmean.collector weakref to collector
5244
5345--- rmean.collector is named separate counter
5446--- @class algo.rmean.collector
5547--- @field name string ? name of collector
5648--- @field window number window size of collector (default =5s )
5749--- @field size number capacity collector (window /resolution )
5850--- @field label_pairs table<string,string> specified label pairs for metrics
59- --- @field sum_value number[] list of sum values per second
60- --- @field min_value number[] list of min values per second
61- --- @field max_value number[] list of max values per second
51+ --- @field sum_value number[] list of sum values per second (running sum )
52+ --- @field hit_value number[] list of hit values per second (running count )
53+ --- @field min_value number[] list of min values per second (running min )
54+ --- @field max_value number[] list of max values per second (running max )
6255--- @field total number sum of all values from last reset
6356--- @field count number monotonic counter of all collected values from last reset
6457--- @field _resolution number length in seconds of each slot
6760--- @field _invalid ? boolean is set to true when collector was freed from previous rmean
6861local collector = {}
6962
70- local map_mt = { __serialize = ' map' }
71- local list_mt = { __serialize = ' seq' }
72- local weak_mt = { __mode = ' v' }
73-
7463local collector_mt = {
7564 __index = collector ,
7665 __tostring = function (self )
@@ -92,37 +81,12 @@ local collector_mt = {
9281 window = self .window ,
9382 min = self :min (),
9483 max = self :max (),
84+ mean = self :mean (),
9585 per_second = self :per_second (),
9686 }, map_mt )
9787 end
9888}
9989
100- -- #region algo.rmean.utils
101-
102- --- Creates new list with null values
103- --- @param size number
104- --- @private
105- --- @return number[]
106- local function new_list (size )
107- size = math_floor (size )
108- local t = setmetatable (table_new (size , 0 ), list_mt )
109- return t
110- end
111-
112- --- Creates new list with zero values
113- --- @param size number
114- --- @private
115- --- @return number[]
116- local function new_zero_list (size )
117- local t = new_list (size )
118- -- we start iteration from 0
119- -- we abuse how lua stores arrays
120- for i = 0 , size do
121- t [i ] = 0
122- end
123- return t
124- end
125-
12690--- @param depth number
12791--- @param max_value number
12892--- @return integer
@@ -142,15 +106,6 @@ local function _get_depth(depth, max_value)
142106 return depth
143107end
144108
145- local function _list_serialize (list )
146- local r = {}
147- for i = 1 , # list do
148- r [i ] = tostring (list [i ])
149- end
150- return r
151- end
152-
153- -- #endregion
154109
155110--- fiber roller of registered collectors
156111--- @private
@@ -231,11 +186,13 @@ function rmean_methods:collector(name, window)
231186
232187 window = tonumber (window ) or self .window
233188 local size = math_floor (window / self ._resolution )
189+
234190 local remote = setmetatable ({
235191 name = name ,
236192 window = window ,
237193 size = size ,
238194 sum_value = new_zero_list (size ),
195+ hit_value = new_zero_list (size ),
239196 min_value = new_list (size ),
240197 max_value = new_list (size ),
241198 label_pairs = { name = name , window = window },
@@ -245,6 +202,9 @@ function rmean_methods:collector(name, window)
245202 _resolution = self ._resolution ,
246203 }, collector_mt )
247204
205+ -- cache hot function into object itself
206+ remote .observe = remote .observe
207+
248208 --- @type algo.rmean.collector.weak
249209 local _item = setmetatable ({ collector = remote }, weak_mt )
250210 remote ._rlist_item = _item
267227--- @return algo.rmean.collector
268228function rmean_methods :reload (counter )
269229 -- ? check __version
270- local new = self :collector (counter .name )
230+ local new = self :collector (counter .name , counter . window )
271231 new .sum_value = counter .sum_value
232+ new .hit_value = counter .hit_value
272233 new .count = counter .count
273234 new .total = counter .total
274235 new .max_value = counter .max_value
@@ -298,7 +259,7 @@ function rmean_methods:getall()
298259 local rv = table_new (self ._collectors .count , 0 )
299260 local n = 0
300261
301- setmetatable (rv , { __serialize = _list_serialize } )
262+ make_list_pretty (rv )
302263
303264 for _ , cursor in self ._collectors :pairs () do
304265 --- @cast cursor algo.rmean.collector.weak
313274
314275--- returns registered collector by name
315276--- @param name string
316- --- @return algo.rmean.collector ?
277+ --- @return algo.rmean.collector | algo.rmean.collector[] | nil
317278function rmean_methods :get (name )
318279 if not name then
319280 return self :getall ()
337298
338299--- metrics collect hook
339300function rmean_methods :collect ()
340- local result = table . new (self ._collectors .count * 6 , 0 )
301+ local result = table_new (self ._collectors .count * 6 , 0 )
341302 local label_pairs
342303 if self .metrics_registry then
343304 label_pairs = self .metrics_registry .label_pairs
363324
364325-- #region algo.rmean.collector
365326
366- --- Calculates and returns per_second value
367- --- @param depth integer ? depth in seconds (default =window size , [1,window size] )
368- --- @return number
369- function collector :per_second (depth )
370- depth = _get_depth (depth , self .window )
371- local sum = 0
372- for i = 1 , depth / self ._resolution do
373- sum = sum + self .sum_value [i ]
374- end
375- return sum / depth
376- end
377-
378327function collector :set_labels (label_pairs )
379328 self .label_pairs = table .copy (label_pairs )
380329 self .label_pairs .window = tostring (self .window )
@@ -387,13 +336,13 @@ end
387336function collector :min (depth )
388337 depth = _get_depth (depth , self .window )
389338
390- local min
339+ local _min
391340 for i = 1 , depth / self ._resolution do
392- if not min or (self .min_value [i ] and min > self .min_value [i ]) then
393- min = self .min_value [i ]
341+ if not _min or (self .min_value [i ] and _min > self .min_value [i ]) then
342+ _min = self .min_value [i ]
394343 end
395344 end
396- return min
345+ return _min
397346end
398347
399348--- Returns moving max value
@@ -402,18 +351,21 @@ end
402351function collector :max (depth )
403352 depth = _get_depth (depth , self .window )
404353
405- local max
354+ local _max
406355 for i = 1 , depth / self ._resolution do
407- if not max or (self .max_value [i ] and max < self .max_value [i ]) then
408- max = self .max_value [i ]
356+ if not _max or (self .max_value [i ] and _max < self .max_value [i ]) then
357+ _max = self .max_value [i ]
409358 end
410359 end
411- return max
360+ return _max
412361end
413362
414363--- Returns moving sum value
364+ ---
365+ --- Equivalent to SUM(VALUE[0:depth])
366+ ---
415367--- @param depth integer ? depth in seconds (default =window size , [1,window size] )
416- --- @return number ? sum # can return null if no values were observed
368+ --- @return number sum
417369function collector :sum (depth )
418370 depth = _get_depth (depth , self .window )
419371
@@ -424,6 +376,45 @@ function collector:sum(depth)
424376 return sum
425377end
426378
379+ --- Calculates and returns moving average value with given depth
380+ ---
381+ --- Equivalent to SUM(VALUE[0:depth]) / COUNT(VALUE[0:depth])
382+ ---
383+ --- @param depth integer ? depth in seconds (default =window size , [1,window size] )
384+ --- @return number average
385+ function collector :mean (depth )
386+ depth = _get_depth (depth , self .window )
387+
388+ local sum = 0
389+ local count = 0
390+ for i = 1 , depth / self ._resolution do
391+ sum = sum + self .sum_value [i ]
392+ count = count + self .hit_value [i ]
393+ end
394+ if count == 0 then
395+ return 0
396+ end
397+ return sum / count
398+ end
399+
400+ --- Calculates and returns moving sum value devided by depth
401+ ---
402+ --- Equivalent to SUM(values[0:depth]) / depth
403+ ---
404+ --- It has the same meaning as average 'per second' sum of values
405+ ---
406+ --- Usefull for calculating average hits per second (such as rps or sizes)
407+ --- @param depth integer ? depth in seconds (default =window size , [1,window size] )
408+ --- @return number
409+ function collector :per_second (depth )
410+ depth = _get_depth (depth , self .window )
411+ local sum = 0
412+ for i = 1 , depth / self ._resolution do
413+ sum = sum + self .sum_value [i ]
414+ end
415+ return sum / depth
416+ end
417+
427418--- Increments current time bucket with given value
428419--- @param value number | uint64_t | integer64
429420function collector :observe (value )
@@ -432,11 +423,20 @@ function collector:observe(value)
432423 if not value then return end
433424
434425 self .sum_value [0 ] = self .sum_value [0 ] + value
426+ self .hit_value [0 ] = self .hit_value [0 ] + 1
435427 self .total = self .total + value
436428 self .count = self .count + 1
437429
438- self .min_value [0 ] = math_min (self .min_value [0 ] or value , value )
439- self .max_value [0 ] = math_max (self .max_value [0 ] or value , value )
430+ if self .min_value [0 ] then
431+ if value < self .min_value [0 ] then
432+ self .min_value [0 ] = value
433+ elseif value > self .max_value [0 ] then
434+ self .max_value [0 ] = value
435+ end
436+ else
437+ self .min_value [0 ] = value
438+ self .max_value [0 ] = value
439+ end
440440end
441441
442442-- inc is alias for observe
@@ -451,6 +451,7 @@ function collector:collect()
451451 timestamp = fiber .time64 (),
452452 },
453453 { metric_name = ' rmean_sum' , value = self :sum (), label_pairs = self .label_pairs , timestamp = fiber .time64 () },
454+ { metric_name = ' rmean_mean' , value = self :mean (), label_pairs = self .label_pairs , timestamp = fiber .time64 () },
454455 { metric_name = ' rmean_min' , value = self :min (), label_pairs = self .label_pairs , timestamp = fiber .time64 () },
455456 { metric_name = ' rmean_max' , value = self :max (), label_pairs = self .label_pairs , timestamp = fiber .time64 () },
456457 { metric_name = ' rmean_total' , value = self .total , label_pairs = self .label_pairs , timestamp = fiber .time64 () },
@@ -459,27 +460,29 @@ function collector:collect()
459460end
460461
461462--- Rerolls statistics
462- --- @param dt number
463+ --- @param dt number delta time
463464function collector :roll (dt )
464465 if self ._invalid then return end
465466 if dt < 0 then return end
466467 local sum = self .sum_value
467468 local min = self .min_value
468469 local max = self .max_value
470+ local hit = self .hit_value
469471 local avg = sum [0 ] / dt
470472 local j = math_floor (self .size )
471473 while j > dt + 0.1 do
472474 if j > 0 then
473- sum [j ], min [j ], max [j ] = sum [j - 1 ], min [j - 1 ], max [j - 1 ]
475+ sum [j ], min [j ], max [j ], hit [ j ] = sum [j - 1 ], min [j - 1 ], max [ j - 1 ], hit [j - 1 ]
474476 else
475477 sum [j ] = avg
476478 end
477479 j = j - 1
478480 end
479481 for i = j , 1 , - 1 do
480- sum [i ], min [i ], max [i ] = avg , min [0 ], max [0 ]
482+ sum [i ], min [i ], max [i ], hit [ i ] = avg , min [0 ], max [0 ], hit [ i ]
481483 end
482484 sum [0 ] = 0
485+ hit [0 ] = 0
483486 min [0 ] = nil
484487 max [0 ] = nil
485488end
488491function collector :reset ()
489492 for i = 0 , self .size do
490493 self .sum_value [i ] = 0
494+ self .hit_value [i ] = 0
495+ self .min_value [i ] = math.huge
496+ self .max_value [i ] = - math.huge
491497 end
492- table .clear (self .min_value )
493- table .clear (self .max_value )
494498 self .total = 0
495499 self .count = 0
496500end
0 commit comments