qless 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/HISTORY.md +168 -0
- data/README.md +571 -0
- data/Rakefile +28 -0
- data/bin/qless-campfire +106 -0
- data/bin/qless-growl +99 -0
- data/bin/qless-web +23 -0
- data/lib/qless.rb +185 -0
- data/lib/qless/config.rb +31 -0
- data/lib/qless/job.rb +259 -0
- data/lib/qless/job_reservers/ordered.rb +23 -0
- data/lib/qless/job_reservers/round_robin.rb +34 -0
- data/lib/qless/lua.rb +25 -0
- data/lib/qless/qless-core/cancel.lua +71 -0
- data/lib/qless/qless-core/complete.lua +218 -0
- data/lib/qless/qless-core/config.lua +44 -0
- data/lib/qless/qless-core/depends.lua +65 -0
- data/lib/qless/qless-core/fail.lua +107 -0
- data/lib/qless/qless-core/failed.lua +83 -0
- data/lib/qless/qless-core/get.lua +37 -0
- data/lib/qless/qless-core/heartbeat.lua +50 -0
- data/lib/qless/qless-core/jobs.lua +41 -0
- data/lib/qless/qless-core/peek.lua +155 -0
- data/lib/qless/qless-core/pop.lua +278 -0
- data/lib/qless/qless-core/priority.lua +32 -0
- data/lib/qless/qless-core/put.lua +156 -0
- data/lib/qless/qless-core/queues.lua +58 -0
- data/lib/qless/qless-core/recur.lua +181 -0
- data/lib/qless/qless-core/retry.lua +73 -0
- data/lib/qless/qless-core/ruby/lib/qless-core.rb +1 -0
- data/lib/qless/qless-core/ruby/lib/qless/core.rb +13 -0
- data/lib/qless/qless-core/ruby/lib/qless/core/version.rb +5 -0
- data/lib/qless/qless-core/ruby/spec/qless_core_spec.rb +13 -0
- data/lib/qless/qless-core/stats.lua +92 -0
- data/lib/qless/qless-core/tag.lua +100 -0
- data/lib/qless/qless-core/track.lua +79 -0
- data/lib/qless/qless-core/workers.lua +69 -0
- data/lib/qless/queue.rb +141 -0
- data/lib/qless/server.rb +411 -0
- data/lib/qless/tasks.rb +10 -0
- data/lib/qless/version.rb +3 -0
- data/lib/qless/worker.rb +195 -0
- metadata +239 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
-- Queues(0, now, [queue])
|
2
|
+
-- -----------------------
|
3
|
+
--
|
4
|
+
-- Return all the queues we know about, with how many jobs are scheduled, waiting,
|
5
|
+
-- and running in that queue. If a queue name is provided, then only the appropriate
|
6
|
+
-- response hash should be returned. The response is JSON:
|
7
|
+
--
|
8
|
+
-- [
|
9
|
+
-- {
|
10
|
+
-- 'name': 'testing',
|
11
|
+
-- 'stalled': 2,
|
12
|
+
-- 'waiting': 5,
|
13
|
+
-- 'running': 5,
|
14
|
+
-- 'scheduled': 10,
|
15
|
+
-- 'depends': 5,
|
16
|
+
-- 'recurring': 0
|
17
|
+
-- }, {
|
18
|
+
-- ...
|
19
|
+
-- }
|
20
|
+
-- ]
|
21
|
+
|
22
|
+
if #KEYS > 0 then
|
23
|
+
error('Queues(): Got '.. #KEYS .. ' expected 0 KEYS arguments')
|
24
|
+
end
|
25
|
+
|
26
|
+
local now = assert(tonumber(ARGV[1]), 'Queues(): Arg "now" missing or not a number: ' .. (ARGV[1] or 'nil'))
|
27
|
+
local queue = ARGV[2]
|
28
|
+
|
29
|
+
local response = {}
|
30
|
+
local queuenames = redis.call('zrange', 'ql:queues', 0, -1)
|
31
|
+
|
32
|
+
if queue then
|
33
|
+
local stalled = redis.call('zcount', 'ql:q:' .. queue .. '-locks', 0, now)
|
34
|
+
response = {
|
35
|
+
name = queue,
|
36
|
+
waiting = redis.call('zcard', 'ql:q:' .. queue .. '-work'),
|
37
|
+
stalled = stalled,
|
38
|
+
running = redis.call('zcard', 'ql:q:' .. queue .. '-locks') - stalled,
|
39
|
+
scheduled = redis.call('zcard', 'ql:q:' .. queue .. '-scheduled'),
|
40
|
+
depends = redis.call('zcard', 'ql:q:' .. queue .. '-depends'),
|
41
|
+
recurring = redis.call('zcard', 'ql:q:' .. queue .. '-recur')
|
42
|
+
}
|
43
|
+
else
|
44
|
+
for index, qname in ipairs(queuenames) do
|
45
|
+
local stalled = redis.call('zcount', 'ql:q:' .. qname .. '-locks', 0, now)
|
46
|
+
table.insert(response, {
|
47
|
+
name = qname,
|
48
|
+
waiting = redis.call('zcard', 'ql:q:' .. qname .. '-work'),
|
49
|
+
stalled = stalled,
|
50
|
+
running = redis.call('zcard', 'ql:q:' .. qname .. '-locks') - stalled,
|
51
|
+
scheduled = redis.call('zcard', 'ql:q:' .. qname .. '-scheduled'),
|
52
|
+
depends = redis.call('zcard', 'ql:q:' .. qname .. '-depends'),
|
53
|
+
recurring = redis.call('zcard', 'ql:q:' .. qname .. '-recur')
|
54
|
+
})
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
return cjson.encode(response)
|
@@ -0,0 +1,181 @@
|
|
1
|
+
-- Recur(0, 'on', queue, jid, klass, data, now, 'interval', second, offset, [priority p], [tags t], [retries r])
|
2
|
+
-- Recur(0, 'off', jid)
|
3
|
+
-- Recur(0, 'get', jid)
|
4
|
+
-- Recur(0, 'update', jid, ['priority', priority], ['interval', interval], ['retries', retries], ['data', data], ['klass', klass], ['queue', queue])
|
5
|
+
-- Recur(0, 'tag', jid, tag, [tag, [...]])
|
6
|
+
-- Recur(0, 'untag', jid, tag, [tag, [...]])
|
7
|
+
-- -------------------------------------------------------------------------------------------------------
|
8
|
+
-- This script takes the name of a queue, and then the info
|
9
|
+
-- info about the work item, and makes sure that jobs matching
|
10
|
+
-- its criteria are regularly made available.
|
11
|
+
|
12
|
+
if #KEYS ~= 0 then
|
13
|
+
error('Recur(): Got ' .. #KEYS .. ', expected 0 KEYS arguments')
|
14
|
+
end
|
15
|
+
|
16
|
+
local command = assert(ARGV[1] , 'Recur(): Missing first argument')
|
17
|
+
|
18
|
+
if command == 'on' then
|
19
|
+
local queue = assert(ARGV[2] , 'Recur(): Arg "queue" missing')
|
20
|
+
local jid = assert(ARGV[3] , 'Recur(): Arg "jid" missing')
|
21
|
+
local klass = assert(ARGV[4] , 'Recur(): Arg "klass" missing')
|
22
|
+
local data = assert(cjson.decode(ARGV[5]) , 'Recur(): Arg "data" missing or not JSON: ' .. tostring(ARGV[5]))
|
23
|
+
local now = assert(tonumber(ARGV[6]) , 'Recur(): Arg "now" missing or not a number: ' .. tostring(ARGV[6]))
|
24
|
+
local spec = assert(ARGV[7] , 'Recur(): Arg "schedule type" missing')
|
25
|
+
if spec == 'interval' then
|
26
|
+
local interval = assert(tonumber(ARGV[8]) , 'Recur(): Arg "interval" must be a number: ' .. tostring(ARGV[8]))
|
27
|
+
local offset = assert(tonumber(ARGV[9]) , 'Recur(): Arg "offset" must be a number: ' .. tostring(ARGV[9]))
|
28
|
+
if interval <= 0 then
|
29
|
+
error('Recur(): Arg "interval" must be greater than or equal to 0')
|
30
|
+
end
|
31
|
+
-- Read in all the optional parameters
|
32
|
+
local options = {}
|
33
|
+
for i = 10, #ARGV, 2 do options[ARGV[i]] = ARGV[i + 1] end
|
34
|
+
options.tags = assert(cjson.decode(options.tags or {}), 'Recur(): Arg "tags" must be JSON-encoded array of string. Got: ' .. tostring(options.tags))
|
35
|
+
options.priority = assert(tonumber(options.priority or 0) , 'Recur(): Arg "priority" must be a number. Got: ' .. tostring(options.priority))
|
36
|
+
options.retries = assert(tonumber(options.retries or 0) , 'Recur(): Arg "retries" must be a number. Got: ' .. tostring(options.retries))
|
37
|
+
|
38
|
+
-- Do some insertions
|
39
|
+
redis.call('hmset', 'ql:r:' .. jid,
|
40
|
+
'jid' , jid,
|
41
|
+
'klass' , klass,
|
42
|
+
'data' , cjson.encode(data),
|
43
|
+
'priority', options.priority,
|
44
|
+
'tags' , cjson.encode(options.tags or {}),
|
45
|
+
'state' , 'recur',
|
46
|
+
'queue' , queue,
|
47
|
+
'type' , 'interval',
|
48
|
+
-- How many jobs we've spawned from this
|
49
|
+
'count' , 0,
|
50
|
+
'interval', interval,
|
51
|
+
'retries' , options.retries)
|
52
|
+
-- Now, we should schedule the next run of the job
|
53
|
+
redis.call('zadd', 'ql:q:' .. queue .. '-recur', now + offset, jid)
|
54
|
+
|
55
|
+
-- Lastly, we're going to make sure that this item is in the
|
56
|
+
-- set of known queues. We should keep this sorted by the
|
57
|
+
-- order in which we saw each of these queues
|
58
|
+
if redis.call('zscore', 'ql:queues', queue) == false then
|
59
|
+
redis.call('zadd', 'ql:queues', now, queue)
|
60
|
+
end
|
61
|
+
|
62
|
+
return jid
|
63
|
+
else
|
64
|
+
error('Recur(): schedule type "' .. tostring(spec) .. '" unknown')
|
65
|
+
end
|
66
|
+
elseif command == 'off' then
|
67
|
+
local jid = assert(ARGV[2], 'Recur(): Arg "jid" missing')
|
68
|
+
-- First, find out what queue it was attached to
|
69
|
+
local queue = redis.call('hget', 'ql:r:' .. jid, 'queue')
|
70
|
+
if queue then
|
71
|
+
-- Now, delete it from the queue it was attached to, and delete the thing itself
|
72
|
+
redis.call('zrem', 'ql:q:' .. queue .. '-recur', jid)
|
73
|
+
redis.call('del', 'ql:r:' .. jid)
|
74
|
+
return true
|
75
|
+
else
|
76
|
+
return true
|
77
|
+
end
|
78
|
+
elseif command == 'get' then
|
79
|
+
local jid = assert(ARGV[2], 'Recur(): Arg "jid" missing')
|
80
|
+
local job = redis.call(
|
81
|
+
'hmget', 'ql:r:' .. jid, 'jid', 'klass', 'state', 'queue',
|
82
|
+
'priority', 'interval', 'retries', 'count', 'data', 'tags')
|
83
|
+
|
84
|
+
if not job[1] then
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
return cjson.encode({
|
89
|
+
jid = job[1],
|
90
|
+
klass = job[2],
|
91
|
+
state = job[3],
|
92
|
+
queue = job[4],
|
93
|
+
priority = tonumber(job[5]),
|
94
|
+
interval = tonumber(job[6]),
|
95
|
+
retries = tonumber(job[7]),
|
96
|
+
count = tonumber(job[8]),
|
97
|
+
data = cjson.decode(job[9]),
|
98
|
+
tags = cjson.decode(job[10])
|
99
|
+
})
|
100
|
+
elseif command == 'update' then
|
101
|
+
local jid = assert(ARGV[2], 'Recur(): Arg "jid" missing')
|
102
|
+
local options = {}
|
103
|
+
|
104
|
+
-- Make sure that the job exists
|
105
|
+
if redis.call('exists', 'ql:r:' .. jid) then
|
106
|
+
for i = 3, #ARGV, 2 do
|
107
|
+
local key = ARGV[i]
|
108
|
+
local value = ARGV[i+1]
|
109
|
+
if key == 'priority' or key == 'interval' or key == 'retries' then
|
110
|
+
value = assert(tonumber(value), 'Recur(): Arg "' .. key .. '" must be a number: ' .. tostring(value))
|
111
|
+
-- If the command is 'interval', then we need to update the time
|
112
|
+
-- when it should next be scheduled
|
113
|
+
if key == 'interval' then
|
114
|
+
local queue, interval = unpack(redis.call('hmget', 'ql:r:' .. jid, 'queue', 'interval'))
|
115
|
+
redis.call('zincrby', 'ql:q:' .. queue .. '-recur', value - tonumber(interval), jid)
|
116
|
+
end
|
117
|
+
redis.call('hset', 'ql:r:' .. jid, key, value)
|
118
|
+
elseif key == 'data' then
|
119
|
+
value = assert(cjson.decode(value), 'Recur(): Arg "data" is not JSON-encoded: ' .. tostring(value))
|
120
|
+
redis.call('hset', 'ql:r:' .. jid, 'data', cjson.encode(value))
|
121
|
+
elseif key == 'klass' then
|
122
|
+
redis.call('hset', 'ql:r:' .. jid, 'klass', value)
|
123
|
+
elseif key == 'queue' then
|
124
|
+
local queue = redis.call('hget', 'ql:r:' .. jid, 'queue')
|
125
|
+
local score = redis.call('zscore', 'ql:q:' .. queue .. '-recur', jid)
|
126
|
+
redis.call('zrem', 'ql:q:' .. queue .. '-recur', jid)
|
127
|
+
redis.call('zadd', 'ql:q:' .. value .. '-recur', score, jid)
|
128
|
+
redis.call('hset', 'ql:r:' .. jid, 'queue', value)
|
129
|
+
else
|
130
|
+
error('Recur(): Unrecognized option "' .. key .. '"')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
return true
|
134
|
+
else
|
135
|
+
return false
|
136
|
+
end
|
137
|
+
elseif command == 'tag' then
|
138
|
+
local jid = assert(ARGV[2], 'Recur(): Arg "jid" missing')
|
139
|
+
local tags = redis.call('hget', 'ql:r:' .. jid, 'tags')
|
140
|
+
-- If the job has been canceled / deleted, then return false
|
141
|
+
if tags then
|
142
|
+
-- Decode the json blob, convert to dictionary
|
143
|
+
tags = cjson.decode(tags)
|
144
|
+
local _tags = {}
|
145
|
+
for i,v in ipairs(tags) do _tags[v] = true end
|
146
|
+
|
147
|
+
-- Otherwise, add the job to the sorted set with that tags
|
148
|
+
for i=3,#ARGV do if _tags[ARGV[i]] == nil then table.insert(tags, ARGV[i]) end end
|
149
|
+
|
150
|
+
tags = cjson.encode(tags)
|
151
|
+
redis.call('hset', 'ql:r:' .. jid, 'tags', tags)
|
152
|
+
return tags
|
153
|
+
else
|
154
|
+
return false
|
155
|
+
end
|
156
|
+
elseif command == 'untag' then
|
157
|
+
local jid = assert(ARGV[2], 'Recur(): Arg "jid" missing')
|
158
|
+
-- Get the existing tags
|
159
|
+
local tags = redis.call('hget', 'ql:r:' .. jid, 'tags')
|
160
|
+
-- If the job has been canceled / deleted, then return false
|
161
|
+
if tags then
|
162
|
+
-- Decode the json blob, convert to dictionary
|
163
|
+
tags = cjson.decode(tags)
|
164
|
+
local _tags = {}
|
165
|
+
-- Make a hash
|
166
|
+
for i,v in ipairs(tags) do _tags[v] = true end
|
167
|
+
-- Delete these from the hash
|
168
|
+
for i = 3,#ARGV do _tags[ARGV[i]] = nil end
|
169
|
+
-- Back into a list
|
170
|
+
local results = {}
|
171
|
+
for i, tag in ipairs(tags) do if _tags[tag] then table.insert(results, tag) end end
|
172
|
+
-- json encode them, set, and return
|
173
|
+
tags = cjson.encode(results)
|
174
|
+
redis.call('hset', 'ql:r:' .. jid, 'tags', tags)
|
175
|
+
return tags
|
176
|
+
else
|
177
|
+
return false
|
178
|
+
end
|
179
|
+
else
|
180
|
+
error('Recur(): First argument must be one of [on, off, get, update, tag, untag]. Got ' .. tostring(ARGV[1]))
|
181
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
-- retry(0, jid, queue, worker, now, [delay])
|
2
|
+
-- ------------------------------------------
|
3
|
+
-- This script accepts jid, queue, worker and delay for
|
4
|
+
-- retrying a job. This is similar in functionality to
|
5
|
+
-- `put`, except that this counts against the retries
|
6
|
+
-- a job has for a stage.
|
7
|
+
--
|
8
|
+
-- If the worker is not the worker with a lock on the job,
|
9
|
+
-- then it returns false. If the job is not actually running,
|
10
|
+
-- then it returns false. Otherwise, it returns the number
|
11
|
+
-- of retries remaining. If the allowed retries have been
|
12
|
+
-- exhausted, then it is automatically failed, and a negative
|
13
|
+
-- number is returned.
|
14
|
+
|
15
|
+
if #KEYS ~= 0 then
|
16
|
+
error('Retry(): Got ' .. #KEYS .. ', expected 0')
|
17
|
+
end
|
18
|
+
|
19
|
+
local jid = assert(ARGV[1] , 'Retry(): Arg "jid" missing')
|
20
|
+
local queue = assert(ARGV[2] , 'Retry(): Arg "queue" missing')
|
21
|
+
local worker = assert(ARGV[3] , 'Retry(): Arg "worker" missing')
|
22
|
+
local now = assert(tonumber(ARGV[4]) , 'Retry(): Arg "now" missing')
|
23
|
+
local delay = assert(tonumber(ARGV[5] or 0), 'Retry(): Arg "delay" not a number: ' .. tostring(ARGV[5]))
|
24
|
+
|
25
|
+
-- Let's see what the old priority, history and tags were
|
26
|
+
local oldqueue, state, retries, oldworker, priority = unpack(redis.call('hmget', 'ql:j:' .. jid, 'queue', 'state', 'retries', 'worker', 'priority'))
|
27
|
+
|
28
|
+
-- If this isn't the worker that owns
|
29
|
+
if oldworker ~= worker or (state ~= 'running') then
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
-- Remove it from the locks key of the old queue
|
34
|
+
redis.call('zrem', 'ql:q:' .. oldqueue .. '-locks', jid)
|
35
|
+
|
36
|
+
local remaining = redis.call('hincrby', 'ql:j:' .. jid, 'remaining', -1)
|
37
|
+
|
38
|
+
-- Remove this job from the worker that was previously working it
|
39
|
+
redis.call('zrem', 'ql:w:' .. worker .. ':jobs', jid)
|
40
|
+
|
41
|
+
if remaining < 0 then
|
42
|
+
-- Now remove the instance from the schedule, and work queues for the queue it's in
|
43
|
+
local group = 'failed-retries-' .. queue
|
44
|
+
-- First things first, we should get the history
|
45
|
+
local history = redis.call('hget', 'ql:j:' .. jid, 'history')
|
46
|
+
-- Now, take the element of the history for which our provided worker is the worker, and update 'failed'
|
47
|
+
history = cjson.decode(history or '[]')
|
48
|
+
history[#history]['failed'] = now
|
49
|
+
|
50
|
+
redis.call('hmset', 'ql:j:' .. jid, 'state', 'failed', 'worker', '',
|
51
|
+
'expires', '', 'history', cjson.encode(history), 'failure', cjson.encode({
|
52
|
+
['group'] = group,
|
53
|
+
['message'] = 'Job exhuasted retries in queue "' .. queue .. '"',
|
54
|
+
['when'] = now,
|
55
|
+
['worker'] = worker
|
56
|
+
}))
|
57
|
+
|
58
|
+
-- Add this type of failure to the list of failures
|
59
|
+
redis.call('sadd', 'ql:failures', group)
|
60
|
+
-- And add this particular instance to the failed types
|
61
|
+
redis.call('lpush', 'ql:f:' .. group, jid)
|
62
|
+
else
|
63
|
+
-- Put it in the queue again with a delay. Like put()
|
64
|
+
if delay > 0 then
|
65
|
+
redis.call('zadd', 'ql:q:' .. queue .. '-scheduled', now + delay, jid)
|
66
|
+
redis.call('hset', 'ql:j:' .. jid, 'state', 'scheduled')
|
67
|
+
else
|
68
|
+
redis.call('zadd', 'ql:q:' .. queue .. '-work', priority - (now / 10000000000), jid)
|
69
|
+
redis.call('hset', 'ql:j:' .. jid, 'state', 'waiting')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
return remaining
|
@@ -0,0 +1 @@
|
|
1
|
+
require "qless/core"
|
@@ -0,0 +1,92 @@
|
|
1
|
+
-- Stats(0, queue, date)
|
2
|
+
-- ---------------------
|
3
|
+
-- Return the current statistics for a given queue on a given date. The results
|
4
|
+
-- are returned are a JSON blob:
|
5
|
+
--
|
6
|
+
--
|
7
|
+
-- {
|
8
|
+
-- # These are unimplemented as of yet
|
9
|
+
-- 'failed': 3,
|
10
|
+
-- 'retries': 5,
|
11
|
+
-- 'wait' : {
|
12
|
+
-- 'total' : ...,
|
13
|
+
-- 'mean' : ...,
|
14
|
+
-- 'variance' : ...,
|
15
|
+
-- 'histogram': [
|
16
|
+
-- ...
|
17
|
+
-- ]
|
18
|
+
-- }, 'run': {
|
19
|
+
-- 'total' : ...,
|
20
|
+
-- 'mean' : ...,
|
21
|
+
-- 'variance' : ...,
|
22
|
+
-- 'histogram': [
|
23
|
+
-- ...
|
24
|
+
-- ]
|
25
|
+
-- }
|
26
|
+
-- }
|
27
|
+
--
|
28
|
+
-- The histogram's data points are at the second resolution for the first minute,
|
29
|
+
-- the minute resolution for the first hour, the 15-minute resolution for the first
|
30
|
+
-- day, the hour resolution for the first 3 days, and then at the day resolution
|
31
|
+
-- from there on out. The `histogram` key is a list of those values.
|
32
|
+
--
|
33
|
+
-- Args:
|
34
|
+
-- 1) queue
|
35
|
+
-- 2) time
|
36
|
+
|
37
|
+
if #KEYS > 0 then error('Stats(): No Keys should be provided') end
|
38
|
+
|
39
|
+
local queue = assert(ARGV[1] , 'Stats(): Arg "queue" missing')
|
40
|
+
local time = assert(tonumber(ARGV[2]), 'Stats(): Arg "time" missing or not a number: ' .. (ARGV[2] or 'nil'))
|
41
|
+
|
42
|
+
-- The bin is midnight of the provided day
|
43
|
+
-- 24 * 60 * 60 = 86400
|
44
|
+
local bin = time - (time % 86400)
|
45
|
+
|
46
|
+
-- This a table of all the keys we want to use in order to produce a histogram
|
47
|
+
local histokeys = {
|
48
|
+
's0','s1','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','s12','s13','s14','s15','s16','s17','s18','s19','s20','s21','s22','s23','s24','s25','s26','s27','s28','s29','s30','s31','s32','s33','s34','s35','s36','s37','s38','s39','s40','s41','s42','s43','s44','s45','s46','s47','s48','s49','s50','s51','s52','s53','s54','s55','s56','s57','s58','s59',
|
49
|
+
'm1','m2','m3','m4','m5','m6','m7','m8','m9','m10','m11','m12','m13','m14','m15','m16','m17','m18','m19','m20','m21','m22','m23','m24','m25','m26','m27','m28','m29','m30','m31','m32','m33','m34','m35','m36','m37','m38','m39','m40','m41','m42','m43','m44','m45','m46','m47','m48','m49','m50','m51','m52','m53','m54','m55','m56','m57','m58','m59',
|
50
|
+
'h1','h2','h3','h4','h5','h6','h7','h8','h9','h10','h11','h12','h13','h14','h15','h16','h17','h18','h19','h20','h21','h22','h23',
|
51
|
+
'd1','d2','d3','d4','d5','d6'
|
52
|
+
}
|
53
|
+
|
54
|
+
local mkstats = function(name, bin, queue)
|
55
|
+
-- The results we'll be sending back
|
56
|
+
local results = {}
|
57
|
+
|
58
|
+
local count, mean, vk = unpack(redis.call('hmget', 'ql:s:' .. name .. ':' .. bin .. ':' .. queue, 'total', 'mean', 'vk'))
|
59
|
+
|
60
|
+
count = tonumber(count) or 0
|
61
|
+
mean = tonumber(mean) or 0
|
62
|
+
vk = tonumber(vk)
|
63
|
+
|
64
|
+
results.count = count or 0
|
65
|
+
results.mean = mean or 0
|
66
|
+
results.histogram = {}
|
67
|
+
|
68
|
+
if not count then
|
69
|
+
results.std = 0
|
70
|
+
else
|
71
|
+
if count > 1 then
|
72
|
+
results.std = math.sqrt(vk / (count - 1))
|
73
|
+
else
|
74
|
+
results.std = 0
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
local histogram = redis.call('hmget', 'ql:s:' .. name .. ':' .. bin .. ':' .. queue, unpack(histokeys))
|
79
|
+
for i=1,#histokeys do
|
80
|
+
table.insert(results.histogram, tonumber(histogram[i]) or 0)
|
81
|
+
end
|
82
|
+
return results
|
83
|
+
end
|
84
|
+
|
85
|
+
local retries, failed, failures = unpack(redis.call('hmget', 'ql:s:stats:' .. bin .. ':' .. queue, 'retries', 'failed', 'failures'))
|
86
|
+
return cjson.encode({
|
87
|
+
retries = tonumber(retries or 0),
|
88
|
+
failed = tonumber(failed or 0),
|
89
|
+
failures = tonumber(failures or 0),
|
90
|
+
wait = mkstats('wait', bin, queue),
|
91
|
+
run = mkstats('run' , bin, queue)
|
92
|
+
})
|