workerholic 0.0.14 → 0.0.15
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/.gitignore +1 -0
- data/app_test/job_test.rb +17 -0
- data/app_test/run.rb +14 -1
- data/lib/workerholic.rb +3 -0
- data/lib/workerholic/cli.rb +21 -33
- data/lib/workerholic/job.rb +7 -6
- data/lib/workerholic/job_processor.rb +7 -4
- data/lib/workerholic/job_retry.rb +1 -8
- data/lib/workerholic/job_scheduler.rb +1 -1
- data/lib/workerholic/manager.rb +7 -13
- data/lib/workerholic/starter.rb +106 -0
- data/lib/workerholic/statistics_api.rb +39 -7
- data/lib/workerholic/statistics_storage.rb +11 -0
- data/lib/workerholic/storage.rb +32 -13
- data/lib/workerholic/version.rb +1 -1
- data/pkg/workerholic-0.0.14.gem +0 -0
- data/spec/helpers/helper_methods.rb +1 -0
- data/spec/helpers/job_tests.rb +9 -2
- data/spec/integration/enqueuing_jobs_spec.rb +17 -0
- data/spec/job_processor_spec.rb +9 -9
- data/spec/job_retry_spec.rb +4 -4
- data/spec/job_scheduler_spec.rb +6 -23
- data/spec/spec_helper.rb +5 -1
- data/spec/storage_spec.rb +19 -0
- data/spec/worker_balancer_spec.rb +2 -2
- data/web/application.rb +13 -19
- data/web/public/javascripts/application.js +287 -241
- data/web/public/stylesheets/application.css +10 -8
- data/web/views/layout.erb +0 -4
- data/web/views/overview.erb +57 -0
- metadata +5 -4
- data/Gemfile.lock +0 -62
- data/web/views/index.erb +0 -46
@@ -8,6 +8,17 @@ module Workerholic
|
|
8
8
|
storage.push(namespace, serialized_job_stats)
|
9
9
|
end
|
10
10
|
|
11
|
+
def self.save_processes_memory_usage
|
12
|
+
PIDS.each do |pid|
|
13
|
+
size = `ps -p #{Process.pid} -o pid=,rss=`.scan(/\d+/).last
|
14
|
+
storage.hash_set('workerholic:stats:memory:processes', pid, size)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.delete_memory_stats
|
19
|
+
storage.delete('workerholic:stats:memory:processes')
|
20
|
+
end
|
21
|
+
|
11
22
|
class << self
|
12
23
|
private
|
13
24
|
|
data/lib/workerholic/storage.rb
CHANGED
@@ -13,6 +13,18 @@ module Workerholic
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
def hash_set(key, field, value, retry_delay = 5)
|
17
|
+
execute(retry_delay) { |conn| conn.hset(key, field, value) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def hash_get(key, field, retry_delay = 5)
|
21
|
+
execute(retry_delay) { |conn| conn.hget(key, field) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key, retry_delay = 5)
|
25
|
+
execute(retry_delay) { |conn| conn.del(key) }
|
26
|
+
end
|
27
|
+
|
16
28
|
def list_length(key, retry_delay = 5)
|
17
29
|
execute(retry_delay) { |conn| conn.llen(key) }
|
18
30
|
end
|
@@ -42,6 +54,14 @@ module Workerholic
|
|
42
54
|
execute(retry_delay) { |conn| conn.zcount(key, 0, '+inf') }
|
43
55
|
end
|
44
56
|
|
57
|
+
def sorted_set_members(key, retry_delay = 5)
|
58
|
+
execute(retry_delay) { |conn| conn.zrange(key, 0, -1) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def sorted_set_members_count(key, retry_delay = 5)
|
62
|
+
execute(retry_delay) { |conn| conn.zcard(key) }
|
63
|
+
end
|
64
|
+
|
45
65
|
def keys_count(namespace, retry_delay = 5)
|
46
66
|
execute(retry_delay) { |conn| conn.keys(namespace + ':*').size }
|
47
67
|
end
|
@@ -52,25 +72,24 @@ module Workerholic
|
|
52
72
|
execute(retry_delay) { |conn| conn.scan(0, match: queue_name_pattern).last }
|
53
73
|
end
|
54
74
|
|
55
|
-
def
|
56
|
-
execute(retry_delay) { |conn| conn.keys(
|
75
|
+
def get_keys_for_namespace(namespace, retry_delay = 5)
|
76
|
+
execute(retry_delay) { |conn| conn.keys(namespace) }
|
57
77
|
end
|
58
78
|
|
59
|
-
def
|
60
|
-
execute(retry_delay) { |conn| conn.
|
79
|
+
def get_all_elements_from_list(key, retry_delay = 5)
|
80
|
+
execute(retry_delay) { |conn| conn.lrange(key, 0, -1) }
|
61
81
|
end
|
62
82
|
|
63
|
-
def
|
64
|
-
execute(retry_delay) { |conn| conn.
|
83
|
+
def hash_get(key, field, retry_delay = 5)
|
84
|
+
execute(retry_delay) { |conn| conn.hget(key, field) }
|
65
85
|
end
|
66
86
|
|
67
|
-
def
|
68
|
-
execute(retry_delay)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
87
|
+
def hash_get_all(key, retry_delay = 5)
|
88
|
+
execute(retry_delay) { |conn| conn.hgetall(key) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def hash_keys(namespace, retry_delay = 5)
|
92
|
+
execute(retry_delay) { |conn| conn.hkeys(namespace) }
|
74
93
|
end
|
75
94
|
|
76
95
|
class RedisCannotRecover < Redis::CannotConnectError; end
|
data/lib/workerholic/version.rb
CHANGED
Binary file
|
@@ -3,6 +3,7 @@ ANOTHER_TEST_QUEUE = 'workerholic:testing:queue:another_test_queue'
|
|
3
3
|
BALANCER_TEST_QUEUE = 'workerholic:testing:queue:balancer_test_queue'
|
4
4
|
ANOTHER_BALANCER_TEST_QUEUE = 'workerholic:testing:queue:another_balancer_test_queue'
|
5
5
|
TEST_SCHEDULED_SORTED_SET = 'workerholic:testing:scheduled_jobs'
|
6
|
+
HASH_TEST = 'workerholic:testing:hash_test'
|
6
7
|
|
7
8
|
def expect_during(duration_in_secs, target)
|
8
9
|
timeout = Time.now.to_f + duration_in_secs
|
data/spec/helpers/job_tests.rb
CHANGED
@@ -20,7 +20,7 @@ class FirstJobBalancerTest
|
|
20
20
|
include Workerholic::Job
|
21
21
|
job_options queue_name: BALANCER_TEST_QUEUE
|
22
22
|
|
23
|
-
def perform(str,
|
23
|
+
def perform(str, n)
|
24
24
|
str
|
25
25
|
end
|
26
26
|
end
|
@@ -29,8 +29,15 @@ class SecondJobBalancerTest
|
|
29
29
|
include Workerholic::Job
|
30
30
|
job_options queue_name: ANOTHER_BALANCER_TEST_QUEUE
|
31
31
|
|
32
|
-
def perform(str,
|
32
|
+
def perform(str, n)
|
33
33
|
str
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
class DelayedJobTest
|
38
|
+
include Workerholic::Job
|
39
|
+
|
40
|
+
def perform(str)
|
41
|
+
str
|
42
|
+
end
|
43
|
+
end
|
@@ -31,6 +31,17 @@ describe 'enqueuing jobs to Redis' do
|
|
31
31
|
expect(job_from_redis.to_hash).to eq(expected_job.to_hash)
|
32
32
|
end
|
33
33
|
|
34
|
+
xit 'enqueues a delayed job in redis' do
|
35
|
+
DelayedJobTest.new.perform_delayed(100, 'test job')
|
36
|
+
serialized_job = redis.lpop(TEST_QUEUE)
|
37
|
+
job_from_redis = Workerholic::JobSerializer.deserialize(serialized_job)
|
38
|
+
|
39
|
+
expected_job = Workerholic::JobWrapper.new(klass: SimpleJobTest, arguments: ['test job'], wrapper: SimpleJobTest)
|
40
|
+
expected_job.statistics.enqueued_at = job_from_redis.statistics.enqueued_at
|
41
|
+
|
42
|
+
expect(job_from_redis.to_hash).to eq(expected_job.to_hash)
|
43
|
+
end
|
44
|
+
|
34
45
|
it 'enqueues a job with the right statistics' do
|
35
46
|
SimpleJobTest.new.perform_async('test_job')
|
36
47
|
serialized_job = redis.lpop(TEST_QUEUE)
|
@@ -50,5 +61,11 @@ describe 'enqueuing jobs to Redis' do
|
|
50
61
|
it 'raises an error when wrong number of arguments is specified to perform_async' do
|
51
62
|
expect { SimpleJobTest.new.perform_async(1, 2, 3) }.to raise_error(ArgumentError)
|
52
63
|
end
|
64
|
+
|
65
|
+
it 'raises an ArgumentError if perform_delayed first argument is not of Numeric type' do
|
66
|
+
job = DelayedJobTest.new
|
67
|
+
|
68
|
+
expect { job.perform_delayed('wrong type', 'test arg') }.to raise_error(ArgumentError)
|
69
|
+
end
|
53
70
|
end
|
54
71
|
end
|
data/spec/job_processor_spec.rb
CHANGED
@@ -2,7 +2,6 @@ require_relative 'spec_helper'
|
|
2
2
|
|
3
3
|
class SimpleJobTestWithError
|
4
4
|
include Workerholic::Job
|
5
|
-
job_options queue_name: TEST_SCHEDULED_SORTED_SET
|
6
5
|
|
7
6
|
def perform
|
8
7
|
raise Exception
|
@@ -34,13 +33,14 @@ describe Workerholic::JobProcessor do
|
|
34
33
|
end
|
35
34
|
|
36
35
|
it 'does not raise an error when processing a job with error' do
|
37
|
-
serialized_job = Workerholic::JobSerializer.serialize(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
serialized_job = Workerholic::JobSerializer.serialize(
|
37
|
+
klass: SimpleJobTestWithError,
|
38
|
+
arguments: [],
|
39
|
+
statistics: Workerholic::JobStatistics.new.to_hash
|
40
|
+
)
|
42
41
|
|
43
42
|
job_processor = Workerholic::JobProcessor.new(serialized_job)
|
43
|
+
allow(job_processor).to receive(:retry_job)
|
44
44
|
|
45
45
|
expect { job_processor.process }.not_to raise_error
|
46
46
|
end
|
@@ -52,10 +52,10 @@ describe Workerholic::JobProcessor do
|
|
52
52
|
statistics: Workerholic::JobStatistics.new.to_hash
|
53
53
|
}
|
54
54
|
serialized_job = Workerholic::JobSerializer.serialize(job)
|
55
|
+
job_processor = Workerholic::JobProcessor.new(serialized_job)
|
55
56
|
|
56
|
-
|
57
|
-
expect(Workerholic::JobRetry).to receive(:new)
|
57
|
+
expect(job_processor).to receive(:retry_job)
|
58
58
|
|
59
|
-
|
59
|
+
job_processor.process
|
60
60
|
end
|
61
61
|
end
|
data/spec/job_retry_spec.rb
CHANGED
@@ -15,7 +15,7 @@ describe Workerholic::JobRetry do
|
|
15
15
|
Workerholic::JobRetry.new(
|
16
16
|
job: job,
|
17
17
|
sorted_set: Workerholic::SortedSet.new(TEST_SCHEDULED_SORTED_SET)
|
18
|
-
)
|
18
|
+
).retry
|
19
19
|
|
20
20
|
expect(job.retry_count).to eq(1)
|
21
21
|
end
|
@@ -26,7 +26,7 @@ describe Workerholic::JobRetry do
|
|
26
26
|
Workerholic::JobRetry.new(
|
27
27
|
job: job,
|
28
28
|
sorted_set: Workerholic::SortedSet.new(TEST_SCHEDULED_SORTED_SET)
|
29
|
-
)
|
29
|
+
).retry
|
30
30
|
|
31
31
|
expect((job.execute_at - Time.now.to_f).ceil).to eq(30)
|
32
32
|
end
|
@@ -37,7 +37,7 @@ describe Workerholic::JobRetry do
|
|
37
37
|
Workerholic::JobRetry.new(
|
38
38
|
job: job,
|
39
39
|
sorted_set: Workerholic::SortedSet.new(TEST_SCHEDULED_SORTED_SET)
|
40
|
-
)
|
40
|
+
).retry
|
41
41
|
|
42
42
|
serialized_job = Workerholic::JobSerializer.serialize(job)
|
43
43
|
|
@@ -50,7 +50,7 @@ describe Workerholic::JobRetry do
|
|
50
50
|
Workerholic::JobRetry.new(
|
51
51
|
job: job,
|
52
52
|
sorted_set: Workerholic::SortedSet.new(TEST_SCHEDULED_SORTED_SET)
|
53
|
-
)
|
53
|
+
).retry
|
54
54
|
|
55
55
|
expect(redis.exists(TEST_SCHEDULED_SORTED_SET)).to eq(false)
|
56
56
|
end
|
data/spec/job_scheduler_spec.rb
CHANGED
@@ -1,16 +1,13 @@
|
|
1
1
|
require_relative 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
describe Workerholic::JobScheduler do
|
4
|
+
let(:scheduler) do
|
5
|
+
Workerholic::JobScheduler.new(
|
6
|
+
sorted_set: Workerholic::SortedSet.new(TEST_SCHEDULED_SORTED_SET),
|
7
|
+
queue_name: TEST_QUEUE
|
8
|
+
)
|
9
9
|
end
|
10
|
-
end
|
11
10
|
|
12
|
-
describe Workerholic::JobScheduler do
|
13
|
-
let(:scheduler) { Workerholic::JobScheduler.new(set_name: TEST_SCHEDULED_SORTED_SET, queue_name: TEST_QUEUE) }
|
14
11
|
let(:redis) { Redis.new }
|
15
12
|
|
16
13
|
context 'with non-empty set' do
|
@@ -47,18 +44,4 @@ describe Workerholic::JobScheduler do
|
|
47
44
|
expect(scheduler.queue.dequeue).to eq(serialized_job)
|
48
45
|
end
|
49
46
|
end
|
50
|
-
|
51
|
-
context 'with delayed job option specified' do
|
52
|
-
it 'adds delayed job to the scheduled sorted set' do
|
53
|
-
SimpleDelayedJobTest.new.perform_delayed(2, 'test arg')
|
54
|
-
|
55
|
-
expect(scheduler.sorted_set.empty?).to eq(false)
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'raises an ArgumentError if perform_delayed first argument is not of Numeric type' do
|
59
|
-
job = SimpleDelayedJobTest.new
|
60
|
-
|
61
|
-
expect { job.perform_delayed("wrong type", 'test arg') }.to raise_error(ArgumentError)
|
62
|
-
end
|
63
|
-
end
|
64
47
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -20,6 +20,10 @@ 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.del(TEST_QUEUE, ANOTHER_TEST_QUEUE, BALANCER_TEST_QUEUE, ANOTHER_BALANCER_TEST_QUEUE, TEST_SCHEDULED_SORTED_SET)
|
23
|
+
Redis.new.del(TEST_QUEUE, ANOTHER_TEST_QUEUE, BALANCER_TEST_QUEUE, ANOTHER_BALANCER_TEST_QUEUE, TEST_SCHEDULED_SORTED_SET, HASH_TEST)
|
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)
|
24
28
|
end
|
25
29
|
end
|
data/spec/storage_spec.rb
CHANGED
@@ -68,6 +68,25 @@ describe Workerholic::Storage do
|
|
68
68
|
|
69
69
|
expect(storage.fetch_queue_names).to match_array([queue_name, ANOTHER_TEST_QUEUE])
|
70
70
|
end
|
71
|
+
|
72
|
+
it 'sets k and a value to a hash in redis' do
|
73
|
+
storage.hash_set(HASH_TEST, 'key_test', 1234)
|
74
|
+
|
75
|
+
expect(redis.hget(HASH_TEST, 'key_test')).to eq('1234')
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'gets the value for a given key of a hash in redis' do
|
79
|
+
redis.hset(HASH_TEST, 'key_test', 1234)
|
80
|
+
|
81
|
+
expect(storage.hash_get(HASH_TEST, 'key_test')).to eq('1234')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'deletes a key from redis' do
|
85
|
+
redis.set(TEST_QUEUE, 'something')
|
86
|
+
storage.delete(TEST_QUEUE)
|
87
|
+
|
88
|
+
expect(redis.exists(TEST_QUEUE)).to eq(false)
|
89
|
+
end
|
71
90
|
end
|
72
91
|
|
73
92
|
context 'with Redis not running' do
|
@@ -16,8 +16,8 @@ describe Workerholic::WorkerBalancer do
|
|
16
16
|
it 'fetches queues' do
|
17
17
|
allow(Workerholic::WorkerBalancer.new).to receive(:fetch_queues).and_return(TESTED_QUEUES)
|
18
18
|
|
19
|
-
|
19
|
+
balancer = Workerholic::WorkerBalancer.new(workers: [])
|
20
20
|
|
21
|
-
expect(
|
21
|
+
expect(balancer.queues.map(&:name)).to match_array(TESTED_QUEUES)
|
22
22
|
end
|
23
23
|
end
|
data/web/application.rb
CHANGED
@@ -11,12 +11,12 @@ class WorkerholicWeb < Sinatra::Base
|
|
11
11
|
end
|
12
12
|
|
13
13
|
get '/overview' do
|
14
|
-
erb :
|
14
|
+
erb :overview
|
15
15
|
end
|
16
16
|
|
17
17
|
get '/details' do
|
18
|
-
completed_jobs = Workerholic::StatsAPI.job_statistics(
|
19
|
-
failed_jobs = Workerholic::StatsAPI.job_statistics(
|
18
|
+
completed_jobs = Workerholic::StatsAPI.job_statistics(category: 'completed_jobs', count_only: true)
|
19
|
+
failed_jobs = Workerholic::StatsAPI.job_statistics(category: 'failed_jobs', count_only: true)
|
20
20
|
|
21
21
|
@job_stats = {}
|
22
22
|
@completed_total = 0
|
@@ -28,7 +28,11 @@ class WorkerholicWeb < Sinatra::Base
|
|
28
28
|
end
|
29
29
|
|
30
30
|
failed_jobs.each do |job|
|
31
|
-
@job_stats[job[0]]
|
31
|
+
if @job_stats[job[0]]
|
32
|
+
@job_stats[job[0]].merge({ failed: job[1] })
|
33
|
+
else
|
34
|
+
@job_stats[job[0]] = { failed: job[1] }
|
35
|
+
end
|
32
36
|
@failed_total += job[1]
|
33
37
|
end
|
34
38
|
|
@@ -45,37 +49,27 @@ class WorkerholicWeb < Sinatra::Base
|
|
45
49
|
erb :queues
|
46
50
|
end
|
47
51
|
|
48
|
-
# get '/workers' do
|
49
|
-
# erb :workers
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# get '/failed' do
|
53
|
-
# erb :failed
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
# get '/scheduled' do
|
57
|
-
# erb :scheduled
|
58
|
-
# end
|
59
|
-
|
60
52
|
get '/overview-data' do
|
61
53
|
JSON.generate({
|
62
54
|
completed_jobs: Workerholic::StatsAPI.job_statistics( {category: 'completed_jobs', count_only: true} ),
|
63
55
|
failed_jobs: Workerholic::StatsAPI.job_statistics( {category: 'failed_jobs', count_only: true} ),
|
64
56
|
queued_jobs: Workerholic::StatsAPI.queued_jobs,
|
65
|
-
workers_count: Workerholic.workers_count
|
57
|
+
workers_count: Workerholic.workers_count,
|
58
|
+
# memory_usage: Workerholic::StatsAPI.memory_usage
|
66
59
|
})
|
67
60
|
end
|
68
61
|
|
69
|
-
get '/
|
62
|
+
get '/details-data' do
|
70
63
|
JSON.generate({
|
71
64
|
completed_jobs: Workerholic::StatsAPI.job_statistics( {category: 'completed_jobs', count_only: true} ),
|
72
65
|
failed_jobs: Workerholic::StatsAPI.job_statistics( {category: 'failed_jobs', count_only: true} )
|
73
66
|
})
|
74
67
|
end
|
75
68
|
|
76
|
-
get '/
|
69
|
+
get '/queues-data' do
|
77
70
|
JSON.generate({
|
78
71
|
queued_jobs: Workerholic::StatsAPI.queued_jobs
|
79
72
|
})
|
80
73
|
end
|
74
|
+
|
81
75
|
end
|
@@ -1,270 +1,316 @@
|
|
1
|
-
var
|
2
|
-
|
3
|
-
|
1
|
+
var App = {
|
2
|
+
queuedJobsCountHistory: [],
|
3
|
+
failedJobsCountHistory: [],
|
4
|
+
jobsCompletedHistory: [],
|
5
|
+
totalMemoryHistory: [],
|
6
|
+
tab: null,
|
7
|
+
getOverviewData: function() {
|
8
|
+
$.ajax({
|
9
|
+
url: '/overview-data',
|
10
|
+
context: this,
|
11
|
+
success: function(data) {
|
12
|
+
var deserializedData = JSON.parse(data);
|
4
13
|
|
5
|
-
|
6
|
-
|
7
|
-
|
14
|
+
var completedJobs = deserializedData.completed_jobs.reduce(function(sum, subArray) {
|
15
|
+
return sum + subArray[1];
|
16
|
+
}, 0);
|
8
17
|
|
9
|
-
|
10
|
-
|
18
|
+
var failedJobsCount= deserializedData.failed_jobs.reduce(function(sum, subArray) {
|
19
|
+
return sum + subArray[1];
|
20
|
+
}, 0);
|
11
21
|
|
12
|
-
|
13
|
-
getOverviewData();
|
22
|
+
var queuedJobs = deserializedData.queued_jobs;
|
14
23
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
}
|
19
|
-
|
20
|
-
if (tab === 'queues') {
|
21
|
-
setInterval(function() {
|
22
|
-
getQueueData();
|
23
|
-
}, 5000);
|
24
|
-
}
|
24
|
+
var queuedJobsCount = queuedJobs.reduce(function(sum, queue) {
|
25
|
+
return sum + queue[1];
|
26
|
+
}, 0);
|
25
27
|
|
26
|
-
|
27
|
-
setInterval(function() {
|
28
|
-
getDetailData();
|
29
|
-
}, 1000);
|
30
|
-
}
|
31
|
-
});
|
28
|
+
// var totalMemory = deserializedData.memory_usage ...
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
var deserializedData = JSON.parse(data);
|
30
|
+
this.queuedJobsCountHistory.unshift(queuedJobsCount);
|
31
|
+
this.failedJobsCountHistory.unshift(failedJobsCount);
|
32
|
+
this.jobsCompletedHistory.unshift(completedJobs);
|
33
|
+
// totalMemoryHistory.unshift(totalMemory); Waiting for data
|
38
34
|
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
if (this.queuedJobsCountHistory.length > 13) {
|
36
|
+
this.queuedJobsCountHistory.pop();
|
37
|
+
this.failedJobsCountHistory.pop();
|
38
|
+
this.jobsCompletedHistory.pop();
|
39
|
+
this.totalMemoryHistory.pop();
|
40
|
+
}
|
42
41
|
|
43
|
-
|
44
|
-
return sum + subArray[1];
|
45
|
-
}, 0);
|
42
|
+
this.drawChart();
|
46
43
|
|
47
|
-
|
44
|
+
var workersCount = deserializedData.workers_count;
|
48
45
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
failedJobsCountHistory.unshift(failedJobsCount);
|
55
|
-
jobsCompletedHistory.unshift(completedJobs);
|
56
|
-
|
57
|
-
if (queuedJobsCountHistory.length > 13) {
|
58
|
-
queuedJobsCountHistory.pop();
|
59
|
-
failedJobsCountHistory.pop();
|
60
|
-
jobsCompletedHistory.pop();
|
46
|
+
$('.completed_jobs').text(completedJobs);
|
47
|
+
$('.failed_jobs').text(failedJobsCount);
|
48
|
+
$('.queue_count').text(queuedJobs.length);
|
49
|
+
$('.queued_jobs_count').text(queuedJobsCount);
|
50
|
+
$('.workers_count').text(workersCount);
|
61
51
|
}
|
52
|
+
});
|
53
|
+
},
|
54
|
+
getQueueData: function() {
|
55
|
+
$.ajax({
|
56
|
+
url: '/queues-data',
|
57
|
+
success: function(data) {
|
58
|
+
var deserializedData = JSON.parse(data);
|
59
|
+
var queuedJobs = deserializedData.queued_jobs;
|
60
|
+
var total = 0;
|
62
61
|
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
for (var i = 0; i < queuedJobs.length; i++) {
|
63
|
+
$('#queue_name_' + queuedJobs[i][0].split(':').pop()).text(queuedJobs[i][0]);
|
64
|
+
$('#queue_count_' + queuedJobs[i][0].split(':').pop()).text(queuedJobs[i][1]);
|
65
|
+
total = total + queuedJobs[i][1];
|
66
|
+
}
|
66
67
|
|
67
|
-
|
68
|
-
$('.failed_jobs').text(failedJobsCount);
|
69
|
-
$('.queue_count').text(queuedJobs.length);
|
70
|
-
$('.queued_jobs_count').text(queuedJobsCount);
|
71
|
-
$('.workers_count').text(workersCount);
|
72
|
-
}
|
73
|
-
});
|
74
|
-
}
|
75
|
-
|
76
|
-
function getQueueData() {
|
77
|
-
$.ajax({
|
78
|
-
url: '/queue-data',
|
79
|
-
success: function(data) {
|
80
|
-
var deserializedData = JSON.parse(data);
|
81
|
-
var queuedJobs = deserializedData.queued_jobs;
|
82
|
-
var total = 0;
|
83
|
-
|
84
|
-
for (var i = 0; i < queuedJobs.length; i++) {
|
85
|
-
$('#queue_name_' + queuedJobs[i][0].split(':').pop()).text(queuedJobs[i][0]);
|
86
|
-
$('#queue_count_' + queuedJobs[i][0].split(':').pop()).text(queuedJobs[i][1]);
|
87
|
-
total = total + queuedJobs[i][1];
|
68
|
+
$('#queue_total').text(total);
|
88
69
|
}
|
70
|
+
});
|
71
|
+
},
|
72
|
+
getDetailData: function() {
|
73
|
+
$.ajax({
|
74
|
+
url: '/details-data',
|
75
|
+
success: function(data) {
|
76
|
+
var deserializedData = JSON.parse(data);
|
77
|
+
var completedJobs = deserializedData.completed_jobs;
|
78
|
+
var failedJobs = deserializedData.failed_jobs;
|
79
|
+
var completedTotal = 0;
|
80
|
+
var failedTotal = 0;
|
89
81
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
}
|
94
|
-
|
95
|
-
function getDetailData() {
|
96
|
-
$.ajax({
|
97
|
-
url: '/detail-data',
|
98
|
-
success: function(data) {
|
99
|
-
var deserializedData = JSON.parse(data);
|
100
|
-
var completedJobs = deserializedData.completed_jobs;
|
101
|
-
var failedJobs = deserializedData.failed_jobs;
|
102
|
-
var completedTotal = 0;
|
103
|
-
var failedTotal = 0;
|
82
|
+
completedJobs.forEach(function(job) {
|
83
|
+
$('#completed_' + job[0]).text(job[1]);
|
84
|
+
completedTotal = completedTotal + job[1];
|
85
|
+
});
|
104
86
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
87
|
+
failedJobs.forEach(function(job) {
|
88
|
+
$('#failed_' + job[0]).text(job[1]);
|
89
|
+
failedTotal = failedTotal + job[1];
|
90
|
+
});
|
109
91
|
|
110
|
-
|
111
|
-
$('#
|
112
|
-
|
113
|
-
|
92
|
+
$('#failed_total').text(failedTotal);
|
93
|
+
$('#completed_total').text(completedTotal);
|
94
|
+
}
|
95
|
+
})
|
96
|
+
},
|
97
|
+
drawChart: function() {
|
98
|
+
var processedJobsChart = new CanvasJS.Chart('jobs_processed_container', {
|
99
|
+
title: {
|
100
|
+
text: 'Jobs Processed per second',
|
101
|
+
fontFamily: 'Arial',
|
102
|
+
fontSize: 24,
|
103
|
+
},
|
104
|
+
axisX: {
|
105
|
+
reversed: true,
|
106
|
+
gridColor: 'Silver',
|
107
|
+
tickColor: 'silver',
|
108
|
+
animationEnabled: true,
|
109
|
+
title: 'Time ago (s)',
|
110
|
+
maximum: 60
|
111
|
+
},
|
112
|
+
toolTip: {
|
113
|
+
shared: true
|
114
|
+
},
|
115
|
+
theme: "theme2",
|
116
|
+
axisY: {
|
117
|
+
gridColor: "Silver",
|
118
|
+
tickColor: "silver",
|
119
|
+
title: 'Jobs per second',
|
120
|
+
},
|
121
|
+
data: [{
|
122
|
+
type: "line",
|
123
|
+
showInLegend: true,
|
124
|
+
name: "Jobs completed",
|
125
|
+
color: "blue",
|
126
|
+
markerType: 'circle',
|
127
|
+
lineThickness: 2,
|
128
|
+
dataPoints: [
|
129
|
+
{ x: '0', y: (this.jobsCompletedHistory[0] - this.jobsCompletedHistory[1]) / 5 || 0 },
|
130
|
+
{ x: '5', y: (this.jobsCompletedHistory[1] - this.jobsCompletedHistory[2]) / 5 || 0 },
|
131
|
+
{ x: '10', y: (this.jobsCompletedHistory[2] - this.jobsCompletedHistory[3]) / 5 || 0 },
|
132
|
+
{ x: '15', y: (this.jobsCompletedHistory[3] - this.jobsCompletedHistory[4]) / 5 || 0 },
|
133
|
+
{ x: '20', y: (this.jobsCompletedHistory[4] - this.jobsCompletedHistory[5]) / 5 || 0 },
|
134
|
+
{ x: '25', y: (this.jobsCompletedHistory[5] - this.jobsCompletedHistory[6]) / 5 || 0 },
|
135
|
+
{ x: '30', y: (this.jobsCompletedHistory[6] - this.jobsCompletedHistory[7]) / 5 || 0 },
|
136
|
+
{ x: '35', y: (this.jobsCompletedHistory[7] - this.jobsCompletedHistory[8]) / 5 || 0 },
|
137
|
+
{ x: '40', y: (this.jobsCompletedHistory[8] - this.jobsCompletedHistory[9]) / 5 || 0 },
|
138
|
+
{ x: '45', y: (this.jobsCompletedHistory[9] - this.jobsCompletedHistory[10]) / 5 || 0 },
|
139
|
+
{ x: '50', y: (this.jobsCompletedHistory[10] - this.jobsCompletedHistory[11]) / 5 || 0 },
|
140
|
+
{ x: '55', y: (this.jobsCompletedHistory[11] - this.jobsCompletedHistory[12]) / 5 || 0 },
|
141
|
+
{ x: '60', y: (this.jobsCompletedHistory[12] - this.jobsCompletedHistory[13]) / 5 || 0 },
|
142
|
+
]
|
143
|
+
}]
|
144
|
+
});
|
114
145
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
146
|
+
var queuedJobsChart = new CanvasJS.Chart('queued_jobs_container', {
|
147
|
+
title: {
|
148
|
+
text: 'Queued Jobs',
|
149
|
+
fontFamily: 'Arial',
|
150
|
+
fontSize: 24,
|
151
|
+
},
|
152
|
+
axisX: {
|
153
|
+
reversed: true,
|
154
|
+
gridColor: 'Silver',
|
155
|
+
tickColor: 'silver',
|
156
|
+
animationEnabled: true,
|
157
|
+
title: 'Time ago (s)',
|
158
|
+
// minimum: 0,
|
159
|
+
maximum: 60
|
160
|
+
},
|
161
|
+
toolTip: {
|
162
|
+
shared: true
|
163
|
+
},
|
164
|
+
theme: "theme2",
|
165
|
+
axisY: {
|
166
|
+
gridColor: "Silver",
|
167
|
+
tickColor: "silver",
|
168
|
+
title: 'Jobs'
|
169
|
+
},
|
170
|
+
data: [{
|
171
|
+
type: "line",
|
172
|
+
showInLegend: true,
|
173
|
+
lineThickness: 2,
|
174
|
+
name: "Queued Jobs",
|
175
|
+
markerType: "circle",
|
176
|
+
color: "#F08080",
|
177
|
+
dataPoints: this.setDataPoints(this.queuedJobsCountHistory),
|
178
|
+
}],
|
179
|
+
});
|
120
180
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
{ x: '10', y: jobsCompletedHistory[2] },
|
155
|
-
{ x: '15', y: jobsCompletedHistory[3] },
|
156
|
-
{ x: '20', y: jobsCompletedHistory[4] },
|
157
|
-
{ x: '25', y: jobsCompletedHistory[5] },
|
158
|
-
{ x: '30', y: jobsCompletedHistory[6] },
|
159
|
-
{ x: '35', y: jobsCompletedHistory[7] },
|
160
|
-
{ x: '40', y: jobsCompletedHistory[8] },
|
161
|
-
{ x: '45', y: jobsCompletedHistory[9] },
|
162
|
-
{ x: '50', y: jobsCompletedHistory[10] },
|
163
|
-
{ x: '55', y: jobsCompletedHistory[11] },
|
164
|
-
{ x: '60', y: jobsCompletedHistory[12] },
|
181
|
+
var failedJobsChart = new CanvasJS.Chart('failed_jobs_container', {
|
182
|
+
title: {
|
183
|
+
text: 'Failed Jobs',
|
184
|
+
fontFamily: 'Arial',
|
185
|
+
fontSize: 24,
|
186
|
+
},
|
187
|
+
axisX: {
|
188
|
+
reversed: true,
|
189
|
+
gridColor: 'Silver',
|
190
|
+
tickColor: 'silver',
|
191
|
+
animationEnabled: true,
|
192
|
+
title: 'Time ago (s)',
|
193
|
+
// minimum: 0,
|
194
|
+
maximum: 60
|
195
|
+
},
|
196
|
+
toolTip: {
|
197
|
+
shared: true
|
198
|
+
},
|
199
|
+
theme: "theme2",
|
200
|
+
axisY: {
|
201
|
+
gridColor: "Silver",
|
202
|
+
tickColor: "silver",
|
203
|
+
title: 'Jobs'
|
204
|
+
},
|
205
|
+
data: [{
|
206
|
+
type: "line",
|
207
|
+
showInLegend: true,
|
208
|
+
name: "Failed Jobs",
|
209
|
+
color: "#20B2AA",
|
210
|
+
markerType: 'circle',
|
211
|
+
lineThickness: 2,
|
212
|
+
dataPoints: this.setDataPoints(this.failedJobsCountHistory),
|
213
|
+
},
|
165
214
|
]
|
166
|
-
}
|
167
|
-
});
|
215
|
+
});
|
168
216
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
axisX: {
|
175
|
-
reversed: true,
|
176
|
-
gridColor: 'Silver',
|
177
|
-
tickColor: 'silver',
|
178
|
-
animationEnabled: true,
|
179
|
-
title: 'Time ago (s)',
|
180
|
-
// minimum: 0,
|
181
|
-
maximum: 60
|
182
|
-
},
|
183
|
-
toolTip: {
|
184
|
-
shared: true
|
185
|
-
},
|
186
|
-
theme: "theme2",
|
187
|
-
axisY: {
|
188
|
-
gridColor: "Silver",
|
189
|
-
tickColor: "silver",
|
190
|
-
title: 'Jobs'
|
191
|
-
},
|
192
|
-
data: [{
|
193
|
-
type: "line",
|
194
|
-
showInLegend: true,
|
195
|
-
lineThickness: 2,
|
196
|
-
name: "Queued Jobs",
|
197
|
-
markerType: "circle",
|
198
|
-
color: "#F08080",
|
199
|
-
dataPoints: [
|
200
|
-
{ x: '0', y: queuedJobsCountHistory[0] },
|
201
|
-
{ x: '5', y: queuedJobsCountHistory[1] },
|
202
|
-
{ x: '10', y: queuedJobsCountHistory[2] },
|
203
|
-
{ x: '15', y: queuedJobsCountHistory[3] },
|
204
|
-
{ x: '20', y: queuedJobsCountHistory[4] },
|
205
|
-
{ x: '25', y: queuedJobsCountHistory[5] },
|
206
|
-
{ x: '30', y: queuedJobsCountHistory[6] },
|
207
|
-
{ x: '35', y: queuedJobsCountHistory[7] },
|
208
|
-
{ x: '40', y: queuedJobsCountHistory[8] },
|
209
|
-
{ x: '45', y: queuedJobsCountHistory[9] },
|
210
|
-
{ x: '50', y: queuedJobsCountHistory[10] },
|
211
|
-
{ x: '55', y: queuedJobsCountHistory[11] },
|
212
|
-
{ x: '60', y: queuedJobsCountHistory[12] },
|
213
|
-
]
|
217
|
+
var totalMemoryChart = new CanvasJS.Chart('total_memory_container', {
|
218
|
+
title: {
|
219
|
+
text: 'Memory Usage',
|
220
|
+
fontFamily: 'Arial',
|
221
|
+
fontSize: 24,
|
214
222
|
},
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
},
|
235
|
-
theme: "theme2",
|
236
|
-
axisY: {
|
237
|
-
gridColor: "Silver",
|
238
|
-
tickColor: "silver",
|
239
|
-
title: 'Jobs'
|
240
|
-
},
|
241
|
-
data: [{
|
223
|
+
axisX: {
|
224
|
+
reversed: true,
|
225
|
+
gridColor: 'Silver',
|
226
|
+
tickColor: 'silver',
|
227
|
+
animationEnabled: true,
|
228
|
+
title: 'Time ago (s)',
|
229
|
+
// minimum: 0,
|
230
|
+
maximum: 60
|
231
|
+
},
|
232
|
+
toolTip: {
|
233
|
+
shared: true
|
234
|
+
},
|
235
|
+
theme: "theme2",
|
236
|
+
axisY: {
|
237
|
+
gridColor: "Silver",
|
238
|
+
tickColor: "silver",
|
239
|
+
title: 'Memory (mb)'
|
240
|
+
},
|
241
|
+
data: [{
|
242
242
|
type: "line",
|
243
243
|
showInLegend: true,
|
244
|
-
name: "
|
244
|
+
name: "Memory usage",
|
245
245
|
color: "#20B2AA",
|
246
246
|
markerType: 'circle',
|
247
247
|
lineThickness: 2,
|
248
|
-
dataPoints:
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
},
|
264
|
-
|
265
|
-
|
248
|
+
dataPoints: this.setDataPoints(this.totalMemoryHistory),
|
249
|
+
}],
|
250
|
+
});
|
251
|
+
|
252
|
+
queuedJobsChart.render();
|
253
|
+
failedJobsChart.render();
|
254
|
+
processedJobsChart.render();
|
255
|
+
totalMemoryChart.render();
|
256
|
+
},
|
257
|
+
setDataPoints: function(array) {
|
258
|
+
data = [
|
259
|
+
{ x: '0', y: array[0] },
|
260
|
+
{ x: '5', y: array[1] },
|
261
|
+
{ x: '10', y: array[2] },
|
262
|
+
{ x: '15', y: array[3] },
|
263
|
+
{ x: '20', y: array[4] },
|
264
|
+
{ x: '25', y: array[5] },
|
265
|
+
{ x: '30', y: array[6] },
|
266
|
+
{ x: '35', y: array[7] },
|
267
|
+
{ x: '40', y: array[8] },
|
268
|
+
{ x: '45', y: array[9] },
|
269
|
+
{ x: '50', y: array[10] },
|
270
|
+
{ x: '55', y: array[11] },
|
271
|
+
{ x: '60', y: array[12] },
|
272
|
+
];
|
273
|
+
|
274
|
+
return data;
|
275
|
+
},
|
276
|
+
setActiveTab: function() {
|
277
|
+
this.tab = $(location).attr('href').split('/').pop();
|
278
|
+
var $active = $('a[href=' + this.tab + ']');
|
279
|
+
|
280
|
+
$active.css('background', '#a2a2a2');
|
281
|
+
$active.css('color', '#fff');
|
282
|
+
},
|
283
|
+
pollData: function(tab) {
|
284
|
+
if (tab === 'overview') {
|
285
|
+
this.getOverviewData();
|
266
286
|
|
267
|
-
|
268
|
-
|
269
|
-
|
287
|
+
setInterval(function() {
|
288
|
+
this.getOverviewData();
|
289
|
+
}.bind(this), 5000);
|
290
|
+
}
|
291
|
+
|
292
|
+
if (tab === 'queues') {
|
293
|
+
setInterval(function() {
|
294
|
+
this.getQueueData();
|
295
|
+
}.bind(this), 5000);
|
296
|
+
}
|
297
|
+
|
298
|
+
if (tab === 'details') {
|
299
|
+
setInterval(function() {
|
300
|
+
this.getDetailData();
|
301
|
+
}.bind(this), 5000);
|
302
|
+
}
|
303
|
+
},
|
304
|
+
bindEvents: function() {
|
305
|
+
$('#memory_usage').on('click', function(e) {
|
306
|
+
$('.nested th').toggle();
|
307
|
+
});
|
308
|
+
},
|
309
|
+
init: function() {
|
310
|
+
this.setActiveTab();
|
311
|
+
this.bindEvents();
|
312
|
+
this.pollData(this.tab);
|
313
|
+
}
|
270
314
|
}
|
315
|
+
|
316
|
+
$(document).ready(App.init.bind(App));
|