solid-jobs 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,72 +3,68 @@
3
3
  require "json"
4
4
 
5
5
  module SolidJobs
6
- class Stats
6
+ class Metrics
7
7
  attr_reader :config
8
8
 
9
9
  def initialize(config: SolidJobs.config)
10
10
  @config = config
11
11
  end
12
12
 
13
- def processed
14
- integer("stat:processed")
13
+ def completed
14
+ counter(Keyspace::PROCESSED)
15
15
  end
16
16
 
17
17
  def failed
18
- integer("stat:failed")
18
+ counter(Keyspace::FAILED)
19
19
  end
20
20
 
21
- def enqueued
22
- queues.sum(&:size)
21
+ def ready
22
+ Channel.catalog(config: config).sum(&:size)
23
23
  end
24
24
 
25
- def scheduled_size
26
- integer_command("ZCARD", "schedule")
25
+ def planned
26
+ cardinality(Keyspace::PLANNED)
27
27
  end
28
28
 
29
- def retry_size
30
- integer_command("ZCARD", "retry")
29
+ def retrying
30
+ cardinality(Keyspace::RETRIES)
31
31
  end
32
32
 
33
- def dead_size
34
- integer_command("ZCARD", "dead")
33
+ def discarded
34
+ cardinality(Keyspace::DISCARDED)
35
35
  end
36
36
 
37
- def processes_size
38
- integer_command("SCARD", "processes")
37
+ def nodes
38
+ Integer(config.redis_pool.call("SCARD", Keyspace::NODES))
39
39
  end
40
40
 
41
- def workers_size
42
- ProcessSet.new(config: config).sum(&:busy)
41
+ def executions
42
+ Nodes.new(config: config).sum(&:active_count)
43
43
  end
44
44
 
45
45
  def reset
46
- config.redis_pool.call("DEL", "stat:processed", "stat:failed")
46
+ config.redis_pool.call("DEL", Keyspace::PROCESSED, Keyspace::FAILED)
47
47
  self
48
48
  end
49
49
 
50
50
  private
51
51
 
52
- def queues
53
- Queue.all(config: config)
54
- end
55
-
56
- def integer(key)
52
+ def counter(key)
57
53
  Integer(config.redis_pool.call("GET", key) || 0)
58
54
  end
59
55
 
60
- def integer_command(*command)
61
- Integer(config.redis_pool.call(*command))
56
+ def cardinality(key)
57
+ Integer(config.redis_pool.call("ZCARD", key))
62
58
  end
63
59
  end
64
60
 
65
- class Queue
61
+ class Channel
66
62
  include Enumerable
67
63
 
68
64
  attr_reader :name
69
65
 
70
- def self.all(config: SolidJobs.config)
71
- Array(config.redis_pool.call("SMEMBERS", "queues")).sort.map do |name|
66
+ def self.catalog(config: SolidJobs.config)
67
+ Array(config.redis_pool.call("SMEMBERS", Keyspace::CHANNELS)).sort.map do |name|
72
68
  new(name, config: config)
73
69
  end
74
70
  end
@@ -81,191 +77,180 @@ module SolidJobs
81
77
  def each
82
78
  return enum_for(:each) unless block_given?
83
79
 
84
- Array(@config.redis_pool.call("LRANGE", key, 0, -1)).reverse_each do |raw|
85
- yield JobRecord.new(raw, config: @config, container: self)
80
+ Array(@config.redis_pool.call("LRANGE", storage_key, 0, -1)).reverse_each do |raw|
81
+ yield StoredTask.new(raw, config: @config, owner: self)
86
82
  end
87
83
  end
88
84
 
89
85
  def size
90
- Integer(@config.redis_pool.call("LLEN", key))
86
+ Integer(@config.redis_pool.call("LLEN", storage_key))
91
87
  end
92
- alias_method :length, :size
93
88
 
94
- def latency
95
- raw = @config.redis_pool.call("LINDEX", key, -1)
89
+ def waiting_time
90
+ raw = @config.redis_pool.call("LINDEX", storage_key, -1)
96
91
  return 0.0 unless raw
97
92
 
98
- payload = JSON.parse(raw)
99
- enqueued_at = payload["enqueued_at"] || payload["created_at"]
100
- return 0.0 unless enqueued_at
101
-
102
- [Time.now.to_f - Float(enqueued_at) / 1_000, 0.0].max
93
+ envelope = JSON.parse(raw)
94
+ timestamp = envelope["queued_ms"] || envelope["created_ms"]
95
+ timestamp ? [Time.now.to_f - Float(timestamp) / 1_000, 0.0].max : 0.0
103
96
  end
104
97
 
105
- def clear
106
- @config.redis_pool.call("DEL", key)
107
- self
108
- end
109
-
110
- def delete
98
+ def purge
111
99
  @config.redis_pool.pipelined do |pipeline|
112
- pipeline.call("DEL", key)
113
- pipeline.call("SREM", "queues", name)
100
+ pipeline.call("DEL", storage_key)
101
+ pipeline.call("SREM", Keyspace::CHANNELS, name)
114
102
  end
115
103
  self
116
104
  end
117
105
 
118
106
  def paused?
119
- Array(@config.redis_pool.call("SISMEMBER", "paused", name)).first.to_i == 1
107
+ Integer(@config.redis_pool.call("SISMEMBER", Keyspace::PAUSED_CHANNELS, name)) == 1
120
108
  end
121
109
 
122
110
  def pause
123
- @config.redis_pool.call("SADD", "paused", name)
111
+ @config.redis_pool.call("SADD", Keyspace::PAUSED_CHANNELS, name)
124
112
  self
125
113
  end
126
114
 
127
- def unpause
128
- @config.redis_pool.call("SREM", "paused", name)
115
+ def resume
116
+ @config.redis_pool.call("SREM", Keyspace::PAUSED_CHANNELS, name)
129
117
  self
130
118
  end
131
119
 
132
- private
133
-
134
- def key
135
- "queue:#{name}"
120
+ def storage_key
121
+ Keyspace.channel(name)
136
122
  end
137
123
  end
138
124
 
139
- class JobRecord
140
- attr_reader :item, :score
125
+ class StoredTask
126
+ attr_reader :envelope, :score
141
127
 
142
- def initialize(raw, score: nil, config: SolidJobs.config, container: nil)
128
+ def initialize(raw, score: nil, config: SolidJobs.config, owner: nil)
143
129
  @raw = raw
144
- @item = JSON.parse(raw)
130
+ @envelope = JSON.parse(raw)
145
131
  @score = Float(score) if score
146
132
  @config = config
147
- @container = container
133
+ @owner = owner
148
134
  end
149
135
 
150
- def jid
151
- item["jid"]
136
+ def id
137
+ envelope["id"]
152
138
  end
153
139
 
154
- def queue
155
- item["queue"]
140
+ def channel
141
+ envelope["channel"]
156
142
  end
157
143
 
158
- def klass
159
- item["class"]
144
+ def task
145
+ envelope["task"]
160
146
  end
161
147
 
162
- def args
163
- item["args"]
148
+ def arguments
149
+ envelope["arguments"]
164
150
  end
165
151
 
166
152
  def created_at
167
- milliseconds_time(item["created_at"])
153
+ millisecond_time(envelope["created_ms"])
168
154
  end
169
155
 
170
- def enqueued_at
171
- milliseconds_time(item["enqueued_at"])
156
+ def queued_at
157
+ millisecond_time(envelope["queued_ms"])
172
158
  end
173
159
 
174
- def at
160
+ def due_at
175
161
  Time.at(score) if score
176
162
  end
177
163
 
178
- def delete
179
- case @container
180
- when SortedSet
181
- @config.redis_pool.call("ZREM", @container.name, @raw)
182
- when Queue
183
- @config.redis_pool.call("LREM", "queue:#{@container.name}", 1, @raw)
164
+ def remove
165
+ case @owner
166
+ when TimedTasks
167
+ @config.redis_pool.call("ZREM", @owner.key, @raw)
168
+ when Channel
169
+ @config.redis_pool.call("LREM", @owner.storage_key, 1, @raw)
184
170
  else
185
171
  false
186
172
  end
187
173
  end
188
174
 
189
- def retry
190
- delete
191
- Client.new(config: @config).push(item)
175
+ def requeue
176
+ remove
177
+ Publisher.new(config: @config).publish(envelope)
192
178
  end
193
179
 
194
180
  private
195
181
 
196
- def milliseconds_time(value)
182
+ def millisecond_time(value)
197
183
  Time.at(Float(value) / 1_000) if value
198
184
  end
199
185
  end
200
186
 
201
- class SortedSet
187
+ class TimedTasks
202
188
  include Enumerable
203
189
 
204
- attr_reader :name
190
+ attr_reader :key
205
191
 
206
- def initialize(name, config: SolidJobs.config)
207
- @name = name
192
+ def initialize(key, config: SolidJobs.config)
193
+ @key = key
208
194
  @config = config
209
195
  end
210
196
 
211
197
  def each
212
198
  return enum_for(:each) unless block_given?
213
199
 
214
- values = Array(@config.redis_pool.call("ZRANGE", name, 0, -1, "WITHSCORES"))
200
+ values = Array(@config.redis_pool.call("ZRANGE", key, 0, -1, "WITHSCORES"))
215
201
  values.each_slice(2) do |raw, score|
216
- yield JobRecord.new(raw, score: score, config: @config, container: self)
202
+ yield StoredTask.new(raw, score: score, config: @config, owner: self)
217
203
  end
218
204
  end
219
205
 
220
206
  def size
221
- Integer(@config.redis_pool.call("ZCARD", name))
207
+ Integer(@config.redis_pool.call("ZCARD", key))
222
208
  end
223
- alias_method :length, :size
224
209
 
225
- def clear
226
- @config.redis_pool.call("DEL", name)
210
+ def purge
211
+ @config.redis_pool.call("DEL", key)
227
212
  self
228
213
  end
229
214
 
230
- def fetch(*jids)
231
- wanted = jids.flatten
232
- select { |job| wanted.include?(job.jid) }
215
+ def locate(*ids)
216
+ wanted = ids.flatten
217
+ select { |task| wanted.include?(task.id) }
233
218
  end
234
219
  end
235
220
 
236
- class ScheduledSet < SortedSet
221
+ class PlannedTasks < TimedTasks
237
222
  def initialize(config: SolidJobs.config)
238
- super("schedule", config: config)
223
+ super(Keyspace::PLANNED, config: config)
239
224
  end
240
225
  end
241
226
 
242
- class RetrySet < SortedSet
227
+ class RetryingTasks < TimedTasks
243
228
  def initialize(config: SolidJobs.config)
244
- super("retry", config: config)
229
+ super(Keyspace::RETRIES, config: config)
245
230
  end
246
231
 
247
- def retry_all
248
- each.to_a.each(&:retry)
232
+ def requeue_all
233
+ each.to_a.each(&:requeue)
249
234
  end
250
235
  end
251
236
 
252
- class DeadSet < SortedSet
237
+ class DiscardedTasks < TimedTasks
253
238
  def initialize(config: SolidJobs.config)
254
- super("dead", config: config)
239
+ super(Keyspace::DISCARDED, config: config)
255
240
  end
256
241
 
257
- def retry_all
258
- each.to_a.each(&:retry)
242
+ def requeue_all
243
+ each.to_a.each(&:requeue)
259
244
  end
260
245
  end
261
246
 
262
- class Process
247
+ class Node
263
248
  attr_reader :identity
264
249
 
265
250
  def initialize(identity, config: SolidJobs.config)
266
251
  @identity = identity
267
252
  @config = config
268
- fields = Array(@config.redis_pool.call("HGETALL", identity))
253
+ fields = Array(@config.redis_pool.call("HGETALL", Keyspace.node(identity)))
269
254
  @attributes = fields.each_slice(2).to_h
270
255
  @info = JSON.parse(@attributes.fetch("info", "{}"))
271
256
  end
@@ -278,11 +263,11 @@ module SolidJobs
278
263
  Integer(@info["pid"] || 0)
279
264
  end
280
265
 
281
- def queues
282
- @info["queues"] || []
266
+ def channels
267
+ @info["channels"] || []
283
268
  end
284
269
 
285
- def busy
270
+ def active_count
286
271
  Integer(@attributes["busy"] || 0)
287
272
  end
288
273
 
@@ -294,15 +279,15 @@ module SolidJobs
294
279
  @attributes["quiet"] == "true"
295
280
  end
296
281
 
297
- def beat
282
+ def last_seen_at
298
283
  Time.at(Float(@attributes["beat"])) if @attributes["beat"]
299
284
  end
300
285
 
301
- def quiet!
286
+ def quiet
302
287
  signal("TSTP")
303
288
  end
304
289
 
305
- def stop!
290
+ def stop
306
291
  signal("TERM")
307
292
  end
308
293
 
@@ -310,22 +295,22 @@ module SolidJobs
310
295
  signal("TTIN")
311
296
  end
312
297
 
313
- def workers
314
- fields = Array(@config.redis_pool.call("HGETALL", "#{identity}:work"))
315
- fields.each_slice(2).map do |processor_id, raw|
316
- Work.new(identity, processor_id, raw)
298
+ def executions
299
+ fields = Array(@config.redis_pool.call("HGETALL", Keyspace.node_work(identity)))
300
+ fields.each_slice(2).map do |executor_id, raw|
301
+ Execution.new(identity, executor_id, raw)
317
302
  end
318
303
  end
319
304
 
320
305
  private
321
306
 
322
307
  def signal(name)
323
- @config.redis_pool.call("LPUSH", "#{identity}-signals", name)
308
+ @config.redis_pool.call("LPUSH", Keyspace.node_signals(identity), name)
324
309
  true
325
310
  end
326
311
  end
327
312
 
328
- class ProcessSet
313
+ class Nodes
329
314
  include Enumerable
330
315
 
331
316
  def initialize(config: SolidJobs.config)
@@ -335,44 +320,40 @@ module SolidJobs
335
320
  def each
336
321
  return enum_for(:each) unless block_given?
337
322
 
338
- Array(@config.redis_pool.call("SMEMBERS", "processes")).each do |identity|
339
- process = Process.new(identity, config: @config)
340
- if process.beat
341
- yield process
323
+ Array(@config.redis_pool.call("SMEMBERS", Keyspace::NODES)).each do |identity|
324
+ node = Node.new(identity, config: @config)
325
+ if node.last_seen_at
326
+ yield node
342
327
  else
343
- @config.redis_pool.call("SREM", "processes", identity)
328
+ @config.redis_pool.call("SREM", Keyspace::NODES, identity)
344
329
  end
345
330
  end
346
331
  end
347
-
348
- def size
349
- count
350
- end
351
332
  end
352
333
 
353
- class Work
354
- attr_reader :process_id, :processor_id
334
+ class Execution
335
+ attr_reader :node_id, :executor_id
355
336
 
356
- def initialize(process_id, processor_id, raw)
357
- @process_id = process_id
358
- @processor_id = processor_id
337
+ def initialize(node_id, executor_id, raw)
338
+ @node_id = node_id
339
+ @executor_id = executor_id
359
340
  @attributes = JSON.parse(raw)
360
341
  end
361
342
 
362
- def queue
363
- @attributes["queue"]
343
+ def channel
344
+ @attributes["channel"]
364
345
  end
365
346
 
366
- def payload
367
- @attributes["payload"]
347
+ def envelope
348
+ @attributes["envelope"]
368
349
  end
369
350
 
370
- def run_at
371
- Time.at(Float(@attributes["run_at"]))
351
+ def started_at
352
+ Time.at(Float(@attributes["started_at"]))
372
353
  end
373
354
 
374
- def reservation_id
375
- @attributes["reservation_id"]
355
+ def claim_token
356
+ @attributes["claim_token"]
376
357
  end
377
358
 
378
359
  def attempt
@@ -380,14 +361,14 @@ module SolidJobs
380
361
  end
381
362
  end
382
363
 
383
- class Reservation
364
+ class ClaimInfo
384
365
  attr_reader :attributes
385
366
 
386
367
  def initialize(raw)
387
368
  @attributes = JSON.parse(raw)
388
369
  end
389
370
 
390
- %w[job_id reservation_id process_id worker_id queue].each do |name|
371
+ %w[task_id claim_token node_id executor_id channel].each do |name|
391
372
  define_method(name) { attributes[name] }
392
373
  end
393
374
 
@@ -395,12 +376,12 @@ module SolidJobs
395
376
  Integer(attributes["attempt"])
396
377
  end
397
378
 
398
- def reserved_at
399
- Time.at(Float(attributes["reserved_at"]))
379
+ def claimed_at
380
+ Time.at(Float(attributes["claimed_at"]))
400
381
  end
401
382
  end
402
383
 
403
- class ReservationSet
384
+ class Claims
404
385
  include Enumerable
405
386
 
406
387
  def initialize(config: SolidJobs.config)
@@ -412,13 +393,13 @@ module SolidJobs
412
393
 
413
394
  keys.each do |key|
414
395
  Array(@config.redis_pool.call("HVALS", key)).each do |raw|
415
- yield Reservation.new(raw)
396
+ yield ClaimInfo.new(raw)
416
397
  end
417
398
  end
418
399
  end
419
400
 
420
- def find_job(job_id)
421
- find { |reservation| reservation.job_id == job_id }
401
+ def find_task(task_id)
402
+ find { |claim| claim.task_id == task_id }
422
403
  end
423
404
 
424
405
  private
@@ -428,7 +409,7 @@ module SolidJobs
428
409
  found = []
429
410
  loop do
430
411
  cursor, batch = @config.redis_pool.call(
431
- "SCAN", cursor, "MATCH", "*:reservations", "COUNT", 100,
412
+ "SCAN", cursor, "MATCH", "#{Keyspace::PREFIX}:node:*:claims", "COUNT", 100,
432
413
  )
433
414
  found.concat(batch)
434
415
  break if cursor == "0"
@@ -437,21 +418,17 @@ module SolidJobs
437
418
  end
438
419
  end
439
420
 
440
- class Workers
421
+ class Executions
441
422
  include Enumerable
442
423
 
443
424
  def initialize(config: SolidJobs.config)
444
- @processes = ProcessSet.new(config: config)
425
+ @nodes = Nodes.new(config: config)
445
426
  end
446
427
 
447
428
  def each
448
429
  return enum_for(:each) unless block_given?
449
430
 
450
- @processes.each { |process| process.workers.each { |work| yield work } }
451
- end
452
-
453
- def size
454
- count
431
+ @nodes.each { |node| node.executions.each { |execution| yield execution } }
455
432
  end
456
433
  end
457
434
  end