yabeda-delayed_job 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ [paths]
2
+ provisioning = /etc/grafana/provisioning
3
+
4
+ [server]
5
+ http_port = 9091
6
+
7
+ [users]
8
+ default_theme = dark
9
+
@@ -0,0 +1,8 @@
1
+ apiVersion: 1
2
+ datasources:
3
+ - name: Prometheus
4
+ type: prometheus
5
+ access: proxy
6
+ url: http://prometheus:9090
7
+ isDefault: true
8
+
data/example/jobs.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RandomJob
4
+ def call
5
+ sleep(rand(5000) / 1000.0)
6
+ end
7
+ end
8
+
9
+ class RandomFailJob
10
+ def call
11
+ raise StandardError if (rand(100) % 3).zero?
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # Logfile created on 2021-06-21 18:52:52 +0000 by logger.rb/v1.4.3
2
+ I, [2021-06-21T18:52:52.998760 #85] INFO -- : 2021-06-21T18:52:52+0000: [Worker(delayed_job host:d7008330188a pid:85)] Starting job worker
3
+ I, [2021-06-21T18:52:53.031345 #85] INFO -- : 2021-06-21T18:52:53+0000: [Worker(delayed_job host:d7008330188a pid:85)] Job RandomJob#call (id=1) RUNNING
4
+ I, [2021-06-21T18:52:55.246040 #85] INFO -- : 2021-06-21T18:52:55+0000: [Worker(delayed_job host:d7008330188a pid:85)] Job RandomJob#call (id=1) COMPLETED after 2.2140
5
+ I, [2021-06-21T18:52:55.249806 #85] INFO -- : 2021-06-21T18:52:55+0000: [Worker(delayed_job host:d7008330188a pid:85)] Job RandomJob#call (id=2) RUNNING
6
+ I, [2021-06-21T18:52:59.055058 #85] INFO -- : 2021-06-21T18:52:59+0000: [Worker(delayed_job host:d7008330188a pid:85)] Job RandomJob#call (id=2) COMPLETED after 3.8045
7
+ I, [2021-06-21T18:52:59.058246 #85] INFO -- : 2021-06-21T18:52:59+0000: [Worker(delayed_job host:d7008330188a pid:85)] Job RandomJob#call (id=3) RUNNING
8
+ I, [2021-06-21T18:52:59.909979 #85] INFO -- : 2021-06-21T18:52:59+0000: [Worker(delayed_job host:d7008330188a pid:85)] Job RandomJob#call (id=3) COMPLETED after 0.8510
9
+ I, [2021-06-21T18:52:59.914425 #85] INFO -- : 2021-06-21T18:52:59+0000: [Worker(delayed_job host:d7008330188a pid:85)] Job RandomJob#call (id=4) RUNNING
10
+ I, [2021-06-21T18:53:00.916535 #85] INFO -- : 2021-06-21T18:53:00+0000: [Worker(delayed_job host:d7008330188a pid:85)] Exiting...
11
+ I, [2021-06-21T18:53:04.921236 #85] INFO -- : 2021-06-21T18:53:04+0000: [Worker(delayed_job host:d7008330188a pid:85)] Job RandomJob#call (id=4) COMPLETED after 5.0059
12
+ I, [2021-06-21T18:53:04.922786 #85] INFO -- : 2021-06-21T18:53:04+0000: [Worker(delayed_job host:d7008330188a pid:85)] 4 jobs processed at 0.3355 j/s, 0 failed
@@ -0,0 +1,19 @@
1
+ global:
2
+ scrape_interval: 5s
3
+ scrape_timeout: 5s
4
+ evaluation_interval: 5s
5
+
6
+ scrape_configs:
7
+ - job_name: 'enqueuer'
8
+ scheme: http
9
+ metrics_path: '/metrics'
10
+ static_configs:
11
+ - targets:
12
+ - 'enqueuer:9394'
13
+ - job_name: 'delayed_job'
14
+ scheme: http
15
+ metrics_path: '/metrics'
16
+ static_configs:
17
+ - targets:
18
+ - 'delayed_job:9394'
19
+
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ FileUtils.rm_rf('/tmp/prometheus')
5
+
6
+ require 'yabeda/prometheus'
7
+ data_store = Prometheus::Client::DataStores::DirectFileStore.new(dir: '/tmp/prometheus')
8
+ Prometheus::Client.config.data_store = data_store
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yabeda/delayed_job/version'
4
+ require 'delayed_job'
5
+ require 'yabeda'
6
+ require 'yabeda/delayed_job/plugin'
7
+
8
+ module Yabeda
9
+ module DelayedJob
10
+ class Error < StandardError; end
11
+
12
+ LONG_RUNNING_JOB_RUNTIME_BUCKETS = [
13
+ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, # standard (from Prometheus)
14
+ 30, 60, 120, 300, 1800, 3600, 21_600 # DelayedJob tasks may be very long-running
15
+ ].freeze
16
+
17
+ ::Yabeda.configure do
18
+ group :delayed_job
19
+
20
+ counter :jobs_enqueued_total, tags: %i[queue worker], comment: 'A counter of the total number of jobs enqueued.'
21
+ counter :jobs_failed_total, tags: %i[queue worker error], comment: 'asdf'
22
+ counter :jobs_errored_total, tags: %i[queue worker error], comment: 'asdf'
23
+
24
+ histogram :job_runtime, comment: 'A histogram of the job execution time.',
25
+ unit: :seconds, per: :job,
26
+ tags: %i[queue worker],
27
+ buckets: LONG_RUNNING_JOB_RUNTIME_BUCKETS
28
+ gauge :running_job_runtime, tags: %i[queue worker], aggregation: :max, unit: :seconds,
29
+ comment: 'How long currently running jobs are running ' \
30
+ '(useful for detection of hung jobs)'
31
+ gauge :jobs_waiting_count, tags: %i[queue], comment: 'The number of jobs waiting to process in sidekiq.'
32
+ gauge :queue_latency, tags: %i[queue],
33
+ comment: 'The queue latency, the difference in seconds since the oldest ' \
34
+ 'job in the queue was enqueued'
35
+
36
+ collect do
37
+ if ::Yabeda::DelayedJob.server?
38
+ Yabeda::DelayedJob.track_max_job_runtime
39
+ ::Yabeda::DelayedJob.track_database_metrics if ::Yabeda::DelayedJob.active_record_adapter?
40
+ end
41
+ end
42
+ end
43
+
44
+ class << self
45
+ def server?
46
+ require 'delayed/command'
47
+ @server ||= ObjectSpace.each_object(Delayed::Command).any?
48
+ end
49
+
50
+ def labelize(job)
51
+ result = { queue: job.queue, worker: job.name }
52
+ result.merge!(error: job.error.class.name) if job.error
53
+ result
54
+ end
55
+
56
+ def track_database_metrics
57
+ job_scope.select(:queue).count.each do |queue, count|
58
+ Yabeda.delayed_job.jobs_waiting_count.set({ queue: queue }, count)
59
+ end
60
+ job_scope.select('queue, max(NOW() - run_at) latency').each do |job|
61
+ Yabeda.delayed_job.queue_latency.set({ queue: job.queue }, job.latency)
62
+ end
63
+ end
64
+
65
+ def job_scope
66
+ db_time_now = ::Delayed::Worker.backend.db_time_now
67
+ ::Delayed::Worker.backend.where(
68
+ '(run_at <= ? AND (locked_at IS NULL OR locked_at < ?)) AND failed_at IS NULL',
69
+ db_time_now,
70
+ db_time_now - ::Delayed::Worker.max_run_time
71
+ ).group(:queue)
72
+ end
73
+
74
+ def active_record_adapter?
75
+ defined?(Delayed::Backend::ActiveRecord::Job) &&
76
+ Delayed::Worker.backend.name == Delayed::Backend::ActiveRecord::Job.name
77
+ end
78
+
79
+ # Hash of hashes containing all currently running jobs' start timestamps
80
+ # to calculate maximum durations of currently running not yet completed jobs
81
+ # { { queue: "default", worker: "SomeJob" } => { "jid1" => 100500, "jid2" => 424242 } }
82
+ attr_accessor :jobs_started_at
83
+
84
+ def track_max_job_runtime
85
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
86
+ ::Yabeda::DelayedJob.jobs_started_at.each do |labels, jobs|
87
+ oldest_job_started_at = jobs.values.min
88
+ oldest_job_duration = oldest_job_started_at ? (now - oldest_job_started_at).round(3) : 0
89
+ Yabeda.delayed_job.running_job_runtime.set(labels, oldest_job_duration)
90
+ end
91
+ end
92
+
93
+ def elapsed(start)
94
+ (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start).round(3)
95
+ end
96
+ end
97
+
98
+ self.jobs_started_at = Concurrent::Hash.new { |hash, key| hash[key] = Concurrent::Hash.new }
99
+
100
+ Delayed::Worker.plugins << ::Yabeda::DelayedJob::Plugin
101
+ end
102
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark'
4
+
5
+ module Yabeda
6
+ module DelayedJob
7
+ class Plugin < ::Delayed::Plugin
8
+ callbacks do |lifecycle|
9
+ lifecycle.after(:enqueue) do |job|
10
+ labels = ::Yabeda::DelayedJob.labelize(job)
11
+ ::Yabeda.delayed_job.jobs_enqueued_total.increment(labels)
12
+ end
13
+
14
+ lifecycle.after(:failure) do |_worker, job|
15
+ labels = ::Yabeda::DelayedJob.labelize(job)
16
+ ::Yabeda.delayed_job.jobs_failed_total.increment(labels)
17
+ end
18
+
19
+ lifecycle.after(:error) do |_worker, job|
20
+ labels = ::Yabeda::DelayedJob.labelize(job)
21
+ ::Yabeda.delayed_job.jobs_errored_total.increment(labels)
22
+ end
23
+
24
+ lifecycle.around(:perform) do |worker, job, &block|
25
+ labels = ::Yabeda::DelayedJob.labelize(job)
26
+
27
+ Process.clock_gettime(Process::CLOCK_MONOTONIC).tap do |start|
28
+ Yabeda::DelayedJob.jobs_started_at[labels][job.id] = start
29
+
30
+ block.call(worker)
31
+
32
+ Yabeda.delayed_job.job_runtime.measure(
33
+ labels,
34
+ ::Yabeda::DelayedJob.elapsed(start)
35
+ )
36
+ end
37
+ ensure
38
+ Yabeda::DelayedJob.jobs_started_at[labels].delete(job.id)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yabeda
4
+ module DelayedJob
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'yabeda/delayed_job/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'yabeda-delayed_job'
9
+ spec.version = Yabeda::DelayedJob::VERSION
10
+ spec.authors = ['Dmitry']
11
+ spec.email = ['dsalahutdinov@gmail.com']
12
+
13
+ spec.summary = 'Monitoring for delayed_job background worker'
14
+ spec.description = 'Extends Yabeda to colleck delayed_job metrics'
15
+ spec.homepage = 'https://github.com/yabeda-rb/yabeda-delayed_job'
16
+ spec.license = 'MIT'
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/yabeda-rb/yabeda-delayed_job'
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_dependency 'delayed_job'
31
+ spec.add_dependency 'yabeda'
32
+
33
+ spec.add_development_dependency 'bundler'
34
+ spec.add_development_dependency 'rake', '~> 10.0'
35
+ spec.add_development_dependency 'rspec', '~> 3.0'
36
+ spec.add_development_dependency 'rubocop', '~> 1.15'
37
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yabeda-delayed_job
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dmitry
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-07-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: delayed_job
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: yabeda
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.15'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.15'
97
+ description: Extends Yabeda to colleck delayed_job metrics
98
+ email:
99
+ - dsalahutdinov@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".rubocop.yml"
107
+ - ".travis.yml"
108
+ - CODE_OF_CONDUCT.md
109
+ - Gemfile
110
+ - Gemfile.lock
111
+ - LICENSE
112
+ - LICENSE.txt
113
+ - README.md
114
+ - Rakefile
115
+ - bin/console
116
+ - bin/setup
117
+ - docker-compose.yml
118
+ - docs/yabeda-delayed_job-grafana.png
119
+ - example/Gemfile
120
+ - example/Gemfile.lock
121
+ - example/delayed_job.rb
122
+ - example/docker-compose.yml
123
+ - example/enqueuer.rb
124
+ - example/grafana/dashboards/delayed_job.json
125
+ - example/grafana/grafana.ini
126
+ - example/grafana/provisioning/dashboards/delayed_job.yml
127
+ - example/grafana/provisioning/datasources/prometheus.yml
128
+ - example/jobs.rb
129
+ - example/log/delayed_job.log
130
+ - example/prometheus/config.yml
131
+ - example/prometheus_store.rb
132
+ - lib/yabeda/delayed_job.rb
133
+ - lib/yabeda/delayed_job/plugin.rb
134
+ - lib/yabeda/delayed_job/version.rb
135
+ - yabeda-delayed_job.gemspec
136
+ homepage: https://github.com/yabeda-rb/yabeda-delayed_job
137
+ licenses:
138
+ - MIT
139
+ metadata:
140
+ homepage_uri: https://github.com/yabeda-rb/yabeda-delayed_job
141
+ source_code_uri: https://github.com/yabeda-rb/yabeda-delayed_job
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubygems_version: 3.2.15
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Monitoring for delayed_job background worker
161
+ test_files: []