workerholic 0.0.15 → 0.0.16
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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/app_test/bm.rb +45 -0
- data/app_test/job_test.rb +46 -4
- data/app_test/run.rb +39 -8
- data/lib/workerholic/job.rb +7 -2
- data/lib/workerholic/job_processor.rb +2 -0
- data/lib/workerholic/job_scheduler.rb +6 -3
- data/lib/workerholic/job_wrapper.rb +3 -1
- data/lib/workerholic/starter.rb +4 -8
- data/lib/workerholic/statistics_api.rb +81 -28
- data/lib/workerholic/statistics_storage.rb +21 -3
- data/lib/workerholic/storage.rb +26 -19
- data/lib/workerholic/version.rb +1 -1
- data/lib/workerholic.rb +4 -1
- data/spec/helpers/helper_methods.rb +6 -4
- data/spec/integration/dequeuing_and_job_processing_spec.rb +1 -1
- data/spec/integration/enqueuing_jobs_spec.rb +22 -9
- data/spec/job_retry_spec.rb +1 -1
- data/spec/job_scheduler_spec.rb +41 -29
- data/spec/job_wrapper_spec.rb +2 -1
- data/spec/queue_spec.rb +1 -1
- data/spec/sorted_set_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -5
- data/spec/statistics_api_spec.rb +73 -0
- data/spec/statistics_storage_spec.rb +42 -0
- data/spec/storage_spec.rb +4 -4
- data/spec/worker_balancer_spec.rb +5 -2
- data/spec/worker_spec.rb +1 -1
- data/web/application.rb +23 -2
- data/web/public/javascripts/application.js +188 -54
- data/web/public/stylesheets/application.css +7 -1
- data/web/views/history.erb +22 -0
- data/web/views/layout.erb +2 -2
- data/web/views/overview.erb +15 -12
- metadata +8 -2
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
2
|
|
3
3
|
describe 'dequeuing and processesing of jobs' do
|
4
|
-
let(:redis) { Redis.new }
|
4
|
+
let(:redis) { Redis.new(url: Workerholic::REDIS_URL) }
|
5
5
|
|
6
6
|
xit 'successfully dequeues and process a simple job' do
|
7
7
|
serialized_job = Workerholic::JobSerializer.serialize(
|
@@ -1,15 +1,20 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
2
|
|
3
3
|
describe 'enqueuing jobs to Redis' do
|
4
|
-
let(:redis) { Redis.new }
|
4
|
+
let(:redis) { Redis.new(url: Workerholic::REDIS_URL) }
|
5
5
|
|
6
6
|
context 'successfully creates a job and enqueues it in Redis' do
|
7
7
|
it 'enqueues a simple job in redis' do
|
8
8
|
SimpleJobTest.new.perform_async('test job')
|
9
|
-
serialized_job = redis.lpop(TEST_QUEUE)
|
9
|
+
serialized_job = redis.lpop(WORKERHOLIC_QUEUE_NAMESPACE + TEST_QUEUE)
|
10
10
|
job_from_redis = Workerholic::JobSerializer.deserialize(serialized_job)
|
11
11
|
|
12
|
-
expected_job = Workerholic::JobWrapper.new(
|
12
|
+
expected_job = Workerholic::JobWrapper.new(
|
13
|
+
klass: SimpleJobTest,
|
14
|
+
arguments: ['test job'],
|
15
|
+
wrapper: SimpleJobTest,
|
16
|
+
queue: WORKERHOLIC_QUEUE_NAMESPACE + TEST_QUEUE
|
17
|
+
)
|
13
18
|
expected_job.statistics.enqueued_at = job_from_redis.statistics.enqueued_at
|
14
19
|
|
15
20
|
expect(job_from_redis.to_hash).to eq(expected_job.to_hash)
|
@@ -17,13 +22,14 @@ describe 'enqueuing jobs to Redis' do
|
|
17
22
|
|
18
23
|
it 'enqueues a complex job in redis' do
|
19
24
|
ComplexJobTest.new.perform_async('test job', { a: 1, b: 2 }, [1, 2, 3])
|
20
|
-
serialized_job = redis.lpop(TEST_QUEUE)
|
25
|
+
serialized_job = redis.lpop(WORKERHOLIC_QUEUE_NAMESPACE + TEST_QUEUE)
|
21
26
|
job_from_redis = Workerholic::JobSerializer.deserialize(serialized_job)
|
22
27
|
|
23
28
|
expected_job = Workerholic::JobWrapper.new(
|
24
29
|
klass: ComplexJobTest,
|
25
30
|
arguments: ['test job', { a: 1, b: 2 }, [1, 2, 3]],
|
26
|
-
wrapper: ComplexJobTest
|
31
|
+
wrapper: ComplexJobTest,
|
32
|
+
queue: WORKERHOLIC_QUEUE_NAMESPACE + TEST_QUEUE
|
27
33
|
)
|
28
34
|
|
29
35
|
expected_job.statistics.enqueued_at = job_from_redis.statistics.enqueued_at
|
@@ -31,20 +37,27 @@ describe 'enqueuing jobs to Redis' do
|
|
31
37
|
expect(job_from_redis.to_hash).to eq(expected_job.to_hash)
|
32
38
|
end
|
33
39
|
|
34
|
-
|
40
|
+
it 'enqueues a delayed job in redis' do
|
35
41
|
DelayedJobTest.new.perform_delayed(100, 'test job')
|
36
|
-
|
42
|
+
|
43
|
+
serialized_job, execution_time = redis.zrange('workerholic:scheduled_jobs', 0, 0, with_scores: true).first
|
37
44
|
job_from_redis = Workerholic::JobSerializer.deserialize(serialized_job)
|
38
45
|
|
39
|
-
expected_job = Workerholic::JobWrapper.new(
|
46
|
+
expected_job = Workerholic::JobWrapper.new(
|
47
|
+
klass: DelayedJobTest,
|
48
|
+
arguments: ['test job'],
|
49
|
+
wrapper: DelayedJobTest,
|
50
|
+
queue: 'workerholic:queue:main'
|
51
|
+
)
|
40
52
|
expected_job.statistics.enqueued_at = job_from_redis.statistics.enqueued_at
|
41
53
|
|
42
54
|
expect(job_from_redis.to_hash).to eq(expected_job.to_hash)
|
55
|
+
expect(Time.now.to_i + 100).to eq(execution_time.to_i)
|
43
56
|
end
|
44
57
|
|
45
58
|
it 'enqueues a job with the right statistics' do
|
46
59
|
SimpleJobTest.new.perform_async('test_job')
|
47
|
-
serialized_job = redis.lpop(TEST_QUEUE)
|
60
|
+
serialized_job = redis.lpop(WORKERHOLIC_QUEUE_NAMESPACE + TEST_QUEUE)
|
48
61
|
job_from_redis = Workerholic::JobSerializer.deserialize(serialized_job)
|
49
62
|
|
50
63
|
expect(job_from_redis.statistics.enqueued_at).to be < Time.now.to_f
|
data/spec/job_retry_spec.rb
CHANGED
data/spec/job_scheduler_spec.rb
CHANGED
@@ -1,47 +1,59 @@
|
|
1
1
|
require_relative 'spec_helper'
|
2
2
|
|
3
3
|
describe Workerholic::JobScheduler do
|
4
|
+
let(:redis) { Redis.new(url: Workerholic::REDIS_URL) }
|
4
5
|
let(:scheduler) do
|
5
6
|
Workerholic::JobScheduler.new(
|
6
|
-
sorted_set: Workerholic::SortedSet.new(TEST_SCHEDULED_SORTED_SET)
|
7
|
-
queue_name: TEST_QUEUE
|
7
|
+
sorted_set: Workerholic::SortedSet.new(TEST_SCHEDULED_SORTED_SET)
|
8
8
|
)
|
9
9
|
end
|
10
|
+
let(:serialized_job) do
|
11
|
+
job = Workerholic::JobWrapper.new(
|
12
|
+
class: ComplexJobTest,
|
13
|
+
arguments: ['test job', { a: 1, b: 2 }, [1, 2, 3]],
|
14
|
+
queue: Workerholic::Queue.new.name
|
15
|
+
)
|
10
16
|
|
11
|
-
|
17
|
+
Workerholic::JobSerializer.serialize(job)
|
18
|
+
end
|
12
19
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
class: ComplexJobTest,
|
17
|
-
arguments: ['test job', { a: 1, b: 2 }, [1, 2, 3]]
|
18
|
-
)
|
20
|
+
it 'checks the time for scheduled job inside sorted set' do
|
21
|
+
score = Time.now.to_f
|
22
|
+
scheduler.schedule(serialized_job, score)
|
19
23
|
|
20
|
-
|
21
|
-
|
24
|
+
expect(scheduler.job_due?).to eq(true)
|
25
|
+
end
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
it 'fetches a job from a sorted set' do
|
28
|
+
score = Time.now.to_f
|
29
|
+
scheduler.schedule(serialized_job, score)
|
30
|
+
scheduler.enqueue_due_jobs
|
26
31
|
|
27
|
-
|
28
|
-
|
32
|
+
expect(scheduler.sorted_set.empty?).to eq(true)
|
33
|
+
end
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
35
|
+
it 'enqueues due job to the main queue by default' do
|
36
|
+
score = Time.now.to_f
|
37
|
+
scheduler.schedule(serialized_job, score)
|
38
|
+
scheduler.enqueue_due_jobs
|
34
39
|
|
35
|
-
|
36
|
-
|
40
|
+
expect(Workerholic::Queue.new.empty?).to eq(false)
|
41
|
+
expect(Workerholic::Queue.new.dequeue).to eq(serialized_job)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'enqueues due job in specific queue' do
|
45
|
+
job = Workerholic::JobWrapper.new(
|
46
|
+
class: ComplexJobTest,
|
47
|
+
arguments: ['test job', { a: 1, b: 2 }, [1, 2, 3]],
|
48
|
+
queue: TEST_QUEUE
|
49
|
+
)
|
50
|
+
serialized_job = Workerholic::JobSerializer.serialize(job)
|
51
|
+
score = Time.now.to_f
|
37
52
|
|
38
|
-
|
39
|
-
|
40
|
-
scheduler.schedule(serialized_job, score)
|
41
|
-
scheduler.enqueue_due_jobs
|
53
|
+
scheduler.schedule(serialized_job, score)
|
54
|
+
scheduler.enqueue_due_jobs
|
42
55
|
|
43
|
-
|
44
|
-
|
45
|
-
end
|
56
|
+
expect(Workerholic::Queue.new(TEST_QUEUE).empty?).to eq(false)
|
57
|
+
expect(Workerholic::Queue.new(TEST_QUEUE).dequeue).to eq(serialized_job)
|
46
58
|
end
|
47
59
|
end
|
data/spec/job_wrapper_spec.rb
CHANGED
@@ -2,12 +2,13 @@ require_relative 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Workerholic::JobWrapper do
|
4
4
|
it 'returns a hash with job meta info and job stats info' do
|
5
|
-
job = Workerholic::JobWrapper.new(klass: SimpleJobTest, arguments: ['test job'])
|
5
|
+
job = Workerholic::JobWrapper.new(klass: SimpleJobTest, arguments: ['test job'], queue: TEST_QUEUE)
|
6
6
|
|
7
7
|
expected_result = {
|
8
8
|
klass: SimpleJobTest,
|
9
9
|
wrapper: nil,
|
10
10
|
arguments: ['test job'],
|
11
|
+
queue: TEST_QUEUE,
|
11
12
|
retry_count: 0,
|
12
13
|
execute_at: nil,
|
13
14
|
statistics: {
|
data/spec/queue_spec.rb
CHANGED
data/spec/sorted_set_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require_relative 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Workerholic::SortedSet do
|
4
4
|
let(:job) {{ class: SimpleJobTest, arguments: [] }}
|
5
|
-
let(:redis) { Redis.new }
|
5
|
+
let(:redis) { Redis.new(url: Workerholic::REDIS_URL) }
|
6
6
|
let(:sorted_set) { Workerholic::SortedSet.new(TEST_SCHEDULED_SORTED_SET) }
|
7
7
|
|
8
8
|
it 'adds a serialized job to the sorted set' do
|
data/spec/spec_helper.rb
CHANGED
@@ -20,10 +20,6 @@ RSpec.configure do |config|
|
|
20
20
|
config.shared_context_metadata_behavior = :apply_to_host_groups
|
21
21
|
|
22
22
|
config.before do
|
23
|
-
Redis.new
|
24
|
-
end
|
25
|
-
|
26
|
-
config.after do
|
27
|
-
Redis.new.del(TEST_QUEUE, ANOTHER_TEST_QUEUE, BALANCER_TEST_QUEUE, ANOTHER_BALANCER_TEST_QUEUE, TEST_SCHEDULED_SORTED_SET, HASH_TEST)
|
23
|
+
Redis.new(url: Workerholic::REDIS_URL).flushdb
|
28
24
|
end
|
29
25
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Workerholic::StatsAPI do
|
4
|
+
let(:storage) { Workerholic::Storage::RedisWrapper.new }
|
5
|
+
let(:job) { Workerholic::JobWrapper.new(klass: SimpleJobTest, arguments: ['test job']) }
|
6
|
+
|
7
|
+
it 'returns full statistics for category' do
|
8
|
+
namespace = 'workerholic:stats:completed_jobs:*'
|
9
|
+
|
10
|
+
Workerholic::StatsStorage.save_job('completed_jobs', job)
|
11
|
+
|
12
|
+
jobs_classes = storage.get_keys_for_namespace(namespace)
|
13
|
+
|
14
|
+
serialized_job = storage.sorted_set_all_members(jobs_classes.first)
|
15
|
+
deserialized_job = Workerholic::JobSerializer.deserialize_stats(serialized_job.first)
|
16
|
+
|
17
|
+
stats_api_result = Workerholic::StatsAPI.job_statistics(category: 'completed_jobs')
|
18
|
+
first_job_stat = stats_api_result.first[0]
|
19
|
+
|
20
|
+
expect(stats_api_result.size).to eq 1
|
21
|
+
expect(first_job_stat).to eq deserialized_job
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns jobs count statistics for category' do
|
25
|
+
Workerholic::StatsStorage.save_job('completed_jobs', job)
|
26
|
+
|
27
|
+
stats_api_result = Workerholic::StatsAPI.job_statistics(category: 'completed_jobs', count_only: true)
|
28
|
+
stat_counter = stats_api_result.first
|
29
|
+
|
30
|
+
expect(stat_counter).to eq [job.klass.to_s, 1]
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with scheduled job' do
|
34
|
+
let(:namespace) { 'workerholic:scheduled_jobs' }
|
35
|
+
|
36
|
+
before do
|
37
|
+
sorted_set = Workerholic::SortedSet.new(namespace)
|
38
|
+
|
39
|
+
job.execute_at = Time.now.to_f + 10
|
40
|
+
job_hash = job.to_hash
|
41
|
+
job_hash[:klass] = job.klass.to_s
|
42
|
+
job_hash[:wrapper] = nil
|
43
|
+
|
44
|
+
sorted_set.add(Workerholic::JobSerializer.serialize(job_hash), job_hash[:execute_at])
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns scheduled jobs statistics' do
|
48
|
+
serialized_job = storage.sorted_set_all_members(namespace).first
|
49
|
+
deserialized_job = Workerholic::JobSerializer.deserialize_stats(serialized_job)
|
50
|
+
|
51
|
+
stats_api_result = Workerholic::StatsAPI.scheduled_jobs
|
52
|
+
|
53
|
+
first_scheduled_job = stats_api_result.first
|
54
|
+
|
55
|
+
expect(first_scheduled_job).to eq deserialized_job
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'returns scheduled jobs count' do
|
59
|
+
stats_api_result = Workerholic::StatsAPI.scheduled_jobs(count_only: true)
|
60
|
+
|
61
|
+
expect(stats_api_result).to eq 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns historical statistics for namespace' do
|
66
|
+
category = 'completed_jobs'
|
67
|
+
Workerholic::StatsStorage.update_historical_stats(category, job.klass.to_s)
|
68
|
+
|
69
|
+
history_hash = Workerholic::StatsAPI.history_for_period(category: category, period: 1)
|
70
|
+
|
71
|
+
expect(history_hash[:job_counts]).to match [1, 0]
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Workerholic::StatsStorage do
|
4
|
+
let(:storage) { Workerholic::Storage::RedisWrapper.new }
|
5
|
+
let(:stats_namespace) { 'workerholic:stats' }
|
6
|
+
|
7
|
+
it 'saves job stats' do
|
8
|
+
job = Workerholic::JobWrapper.new(klass: SimpleJobTest, arguments: ['test job'])
|
9
|
+
|
10
|
+
job_hash = job.to_hash
|
11
|
+
job_hash[:klass] = job_hash[:klass].to_s
|
12
|
+
job_hash[:wrapper] = nil
|
13
|
+
|
14
|
+
local_namespace = ":completed_jobs:#{job.klass.to_s}"
|
15
|
+
namespace = stats_namespace + local_namespace
|
16
|
+
|
17
|
+
Workerholic::StatsStorage.save_job('completed_jobs', job)
|
18
|
+
|
19
|
+
serialized_stats = storage.sorted_set_all_members(namespace).first
|
20
|
+
deserialized_stats = Workerholic::JobSerializer.deserialize_stats(serialized_stats)
|
21
|
+
|
22
|
+
expect(storage.sorted_set_size(namespace)).to eq 1
|
23
|
+
expect(deserialized_stats).to eq job_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'saves process memory usage' do
|
27
|
+
Workerholic::StatsStorage.save_processes_memory_usage
|
28
|
+
|
29
|
+
namespace = stats_namespace + ':memory:processes'
|
30
|
+
process_data = storage.hash_get_all(namespace)
|
31
|
+
|
32
|
+
expect(storage.hash_get(namespace, process_data.keys.first)).to eq process_data.values.first
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'cleans previous metrics records' do
|
36
|
+
Workerholic::StatsStorage.delete_memory_stats
|
37
|
+
|
38
|
+
namespace = stats_namespace + ':memory:processes'
|
39
|
+
|
40
|
+
expect(storage.hash_get_all(namespace).empty?).to be true
|
41
|
+
end
|
42
|
+
end
|
data/spec/storage_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require_relative 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Workerholic::Storage do
|
4
4
|
let(:storage) { Workerholic::Storage::RedisWrapper.new }
|
5
|
-
let(:redis) { Redis.new }
|
5
|
+
let(:redis) { Redis.new(url: Workerholic::REDIS_URL) }
|
6
6
|
let(:queue_name) { TEST_QUEUE }
|
7
7
|
let(:job) { 'test job' }
|
8
8
|
|
@@ -63,10 +63,10 @@ describe Workerholic::Storage do
|
|
63
63
|
end
|
64
64
|
|
65
65
|
it 'returns the workerholic queue names that are in redis' do
|
66
|
-
storage.push(queue_name, job)
|
67
|
-
storage.push(ANOTHER_TEST_QUEUE, job)
|
66
|
+
storage.push(WORKERHOLIC_QUEUE_NAMESPACE + queue_name, job)
|
67
|
+
storage.push(WORKERHOLIC_QUEUE_NAMESPACE + ANOTHER_TEST_QUEUE, job)
|
68
68
|
|
69
|
-
expect(storage.fetch_queue_names).to match_array([queue_name, ANOTHER_TEST_QUEUE])
|
69
|
+
expect(storage.fetch_queue_names).to match_array([WORKERHOLIC_QUEUE_NAMESPACE + queue_name, WORKERHOLIC_QUEUE_NAMESPACE + ANOTHER_TEST_QUEUE])
|
70
70
|
end
|
71
71
|
|
72
72
|
it 'sets k and a value to a hash in redis' do
|
@@ -1,10 +1,13 @@
|
|
1
1
|
require_relative 'spec_helper'
|
2
2
|
|
3
|
-
TESTED_QUEUES = [
|
3
|
+
TESTED_QUEUES = [
|
4
|
+
WORKERHOLIC_QUEUE_NAMESPACE + BALANCER_TEST_QUEUE,
|
5
|
+
WORKERHOLIC_QUEUE_NAMESPACE + ANOTHER_BALANCER_TEST_QUEUE
|
6
|
+
]
|
4
7
|
|
5
8
|
describe Workerholic::WorkerBalancer do
|
6
9
|
let(:storage) { Workerholic::Storage::RedisWrapper.new }
|
7
|
-
let(:redis) { Redis.new }
|
10
|
+
let(:redis) { Redis.new(url: Workerholic::REDIS_URL) }
|
8
11
|
|
9
12
|
before do
|
10
13
|
100.times do |n|
|
data/spec/worker_spec.rb
CHANGED
data/web/application.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
$LOAD_PATH << __dir__ + '/../lib'
|
1
2
|
require 'sinatra/base'
|
2
3
|
|
3
|
-
|
4
|
+
require 'sinatra/reloader'
|
4
5
|
require 'json'
|
5
6
|
require 'workerholic'
|
6
7
|
|
@@ -11,6 +12,8 @@ class WorkerholicWeb < Sinatra::Base
|
|
11
12
|
end
|
12
13
|
|
13
14
|
get '/overview' do
|
15
|
+
@processes = Workerholic::StatsAPI.process_stats
|
16
|
+
|
14
17
|
erb :overview
|
15
18
|
end
|
16
19
|
|
@@ -49,13 +52,22 @@ class WorkerholicWeb < Sinatra::Base
|
|
49
52
|
erb :queues
|
50
53
|
end
|
51
54
|
|
55
|
+
get '/history' do
|
56
|
+
@days = params[:days]
|
57
|
+
@classes = Workerholic::StatsAPI.jobs_classes(true)
|
58
|
+
@class = params[:class] || 'completed'
|
59
|
+
|
60
|
+
erb :history
|
61
|
+
end
|
62
|
+
|
52
63
|
get '/overview-data' do
|
53
64
|
JSON.generate({
|
54
65
|
completed_jobs: Workerholic::StatsAPI.job_statistics( {category: 'completed_jobs', count_only: true} ),
|
55
66
|
failed_jobs: Workerholic::StatsAPI.job_statistics( {category: 'failed_jobs', count_only: true} ),
|
56
67
|
queued_jobs: Workerholic::StatsAPI.queued_jobs,
|
68
|
+
scheduled_jobs: Workerholic::StatsAPI.scheduled_jobs( { count_only: true }),
|
57
69
|
workers_count: Workerholic.workers_count,
|
58
|
-
|
70
|
+
memory_usage: Workerholic::StatsAPI.process_stats,
|
59
71
|
})
|
60
72
|
end
|
61
73
|
|
@@ -72,4 +84,13 @@ class WorkerholicWeb < Sinatra::Base
|
|
72
84
|
})
|
73
85
|
end
|
74
86
|
|
87
|
+
get '/historic-data' do
|
88
|
+
puts params[:className]
|
89
|
+
params[:className] = nil if params[:className] == 'completed'
|
90
|
+
|
91
|
+
JSON.generate({
|
92
|
+
completed_jobs: Workerholic::StatsAPI.history_for_period({ category: 'completed_jobs', klass: params[:className], period: params[:days].to_i }),
|
93
|
+
failed_jobs: Workerholic::StatsAPI.history_for_period({ category: 'failed_jobs', klass: params[:className], period: params[:days].to_i })
|
94
|
+
})
|
95
|
+
end
|
75
96
|
end
|