Skip to content

Commit 8f2b898

Browse files
committed
performance and annotations fixes
1 parent 98a85d7 commit 8f2b898

File tree

9 files changed

+408
-99
lines changed

9 files changed

+408
-99
lines changed

algo-scm-1.rockspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,7 @@ build = {
3535
["algo.rlist"] = "algo/rlist.lua",
3636
["algo.rmean"] = "algo/rmean.lua",
3737
["algo.heap"] = "algo/heap.lua"
38+
["algo.odict"] = "algo/odict.lua"
39+
["algo.skiplist"] = "algo/skiplist.lua"
3840
}
3941
}

algo.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ return {
33
heap = require 'algo.heap',
44
rmean = require 'algo.rmean',
55
odict = require 'algo.odict',
6-
_VERSION = '0.1.0',
6+
skiplist = require 'algo.skiplist',
7+
_VERSION = '0.2.0',
78
}

algo/rlist.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ end
2929
---@class algo.rlist.item:table
3030
---@field prev? algo.rlist.item
3131
---@field next? algo.rlist.item
32-
local rlist_item_mt = {__serialize = __item_serialize}
32+
33+
local rlist_item_mt = {}
34+
rlist_item_mt.__serialize = __item_serialize
3335

3436
---@param node table
3537
local function to_rlist_item(node)

algo/rmean.lua

Lines changed: 93 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,17 @@ local rlist = require 'algo.rlist'
55
local log = require 'log'
66

77
local math_floor = math.floor
8-
local math_min = math.min
9-
local math_max = math.max
108
local setmetatable = setmetatable
119
local table_new = table.new
1210
local 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
---
@@ -48,17 +40,18 @@ end
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
@@ -67,10 +60,6 @@ end
6760
---@field _invalid? boolean is set to true when collector was freed from previous rmean
6861
local collector = {}
6962

70-
local map_mt = { __serialize = 'map' }
71-
local list_mt = { __serialize = 'seq' }
72-
local weak_mt = { __mode = 'v' }
73-
7463
local 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
143107
end
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
@@ -267,8 +227,9 @@ end
267227
---@return algo.rmean.collector
268228
function 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
@@ -313,7 +274,7 @@ end
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
317278
function rmean_methods:get(name)
318279
if not name then
319280
return self:getall()
@@ -337,7 +298,7 @@ end
337298

338299
---metrics collect hook
339300
function 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
@@ -363,18 +324,6 @@ end
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-
378327
function 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
387336
function 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
397346
end
398347

399348
---Returns moving max value
@@ -402,18 +351,21 @@ end
402351
function 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
412361
end
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
417369
function collector:sum(depth)
418370
depth = _get_depth(depth, self.window)
419371

@@ -424,6 +376,45 @@ function collector:sum(depth)
424376
return sum
425377
end
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
429420
function 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
440440
end
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()
459460
end
460461

461462
---Rerolls statistics
462-
---@param dt number
463+
---@param dt number delta time
463464
function 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
485488
end
@@ -488,9 +491,10 @@ end
488491
function 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
496500
end

0 commit comments

Comments
 (0)