good_job 3.15.14 → 3.16.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -2
  3. data/README.md +21 -4
  4. data/app/controllers/good_job/application_controller.rb +16 -9
  5. data/app/controllers/good_job/batches_controller.rb +1 -0
  6. data/app/controllers/good_job/cron_entries_controller.rb +2 -1
  7. data/app/controllers/good_job/frontends_controller.rb +1 -0
  8. data/app/controllers/good_job/jobs_controller.rb +1 -0
  9. data/app/controllers/good_job/processes_controller.rb +1 -0
  10. data/app/filters/good_job/base_filter.rb +1 -0
  11. data/app/filters/good_job/batches_filter.rb +1 -0
  12. data/app/filters/good_job/jobs_filter.rb +1 -0
  13. data/app/helpers/good_job/application_helper.rb +1 -1
  14. data/app/models/concerns/good_job/error_events.rb +28 -0
  15. data/app/models/concerns/good_job/filterable.rb +1 -0
  16. data/app/models/{good_job → concerns/good_job}/lockable.rb +1 -0
  17. data/app/models/concerns/good_job/reportable.rb +1 -0
  18. data/app/models/good_job/active_record_parent_class.rb +9 -0
  19. data/app/models/good_job/base_execution.rb +9 -0
  20. data/app/models/good_job/base_record.rb +4 -1
  21. data/app/models/good_job/cron_entry.rb +1 -0
  22. data/app/models/good_job/discrete_execution.rb +9 -0
  23. data/app/models/good_job/execution.rb +41 -8
  24. data/app/models/good_job/execution_result.rb +17 -2
  25. data/app/models/good_job/i18n_config.rb +25 -0
  26. data/app/models/good_job/job.rb +4 -1
  27. data/app/models/good_job/process.rb +13 -2
  28. data/app/views/good_job/jobs/_executions.erb +1 -1
  29. data/app/views/good_job/jobs/_table.erb +17 -4
  30. data/app/views/good_job/processes/index.html.erb +2 -2
  31. data/app/views/good_job/shared/_navbar.erb +1 -1
  32. data/config/locales/de.yml +8 -1
  33. data/config/locales/en.yml +8 -1
  34. data/config/locales/es.yml +8 -1
  35. data/config/locales/fr.yml +8 -1
  36. data/config/locales/ja.yml +8 -1
  37. data/config/locales/nl.yml +8 -1
  38. data/config/locales/ru.yml +8 -1
  39. data/config/locales/tr.yml +8 -1
  40. data/config/locales/{ua.yml → uk.yml} +33 -2
  41. data/config/routes.rb +1 -0
  42. data/exe/good_job +1 -0
  43. data/lib/active_job/queue_adapters/good_job_adapter.rb +1 -0
  44. data/lib/generators/good_job/install_generator.rb +1 -0
  45. data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +5 -1
  46. data/lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb +3 -1
  47. data/lib/generators/good_job/templates/update/migrations/02_create_good_job_settings.rb.erb +1 -0
  48. data/lib/generators/good_job/templates/update/migrations/03_create_index_good_jobs_jobs_on_priority_created_at_when_unfinished.rb.erb +1 -0
  49. data/lib/generators/good_job/templates/update/migrations/04_create_good_job_batches.rb.erb +1 -0
  50. data/lib/generators/good_job/templates/update/migrations/05_create_good_job_executions.rb.erb +1 -0
  51. data/lib/generators/good_job/templates/update/migrations/06_create_good_jobs_error_event.rb.erb +16 -0
  52. data/lib/generators/good_job/update_generator.rb +1 -0
  53. data/lib/good_job/active_job_extensions/batches.rb +1 -0
  54. data/lib/good_job/active_job_extensions/concurrency.rb +1 -0
  55. data/lib/good_job/active_job_extensions/interrupt_errors.rb +1 -0
  56. data/lib/good_job/active_job_extensions/notify_options.rb +1 -0
  57. data/lib/good_job/adapter.rb +3 -2
  58. data/lib/good_job/assignable_connection.rb +1 -0
  59. data/lib/good_job/bulk.rb +1 -0
  60. data/lib/good_job/capsule.rb +4 -3
  61. data/lib/good_job/cleanup_tracker.rb +2 -1
  62. data/lib/good_job/cli.rb +1 -0
  63. data/lib/good_job/configuration.rb +7 -0
  64. data/lib/good_job/cron_manager.rb +3 -3
  65. data/lib/good_job/current_thread.rb +8 -0
  66. data/lib/good_job/daemon.rb +1 -0
  67. data/lib/good_job/engine.rb +13 -0
  68. data/lib/good_job/interrupt_error.rb +1 -0
  69. data/lib/good_job/job_performer.rb +1 -0
  70. data/lib/good_job/log_subscriber.rb +1 -0
  71. data/lib/good_job/metrics.rb +57 -0
  72. data/lib/good_job/multi_scheduler.rb +1 -0
  73. data/lib/good_job/notifier/process_heartbeat.rb +5 -3
  74. data/lib/good_job/notifier.rb +3 -3
  75. data/lib/good_job/poller.rb +3 -3
  76. data/lib/good_job/scheduler.rb +30 -11
  77. data/lib/good_job/version.rb +2 -1
  78. data/lib/good_job.rb +15 -1
  79. metadata +9 -4
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "concurrent/hash"
3
4
  require "concurrent/scheduled_task"
4
5
  require "fugit"
@@ -12,7 +13,7 @@ module GoodJob # :nodoc:
12
13
  # @!scope class
13
14
  # List of all instantiated CronManagers in the current process.
14
15
  # @return [Array<GoodJob::CronManager>, nil]
15
- cattr_reader :instances, default: [], instance_reader: false
16
+ cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
16
17
 
17
18
  # Task observer for cron task
18
19
  # @param time [Time]
@@ -35,9 +36,8 @@ module GoodJob # :nodoc:
35
36
  @cron_entries = cron_entries
36
37
  @tasks = Concurrent::Hash.new
37
38
 
38
- self.class.instances << self
39
-
40
39
  start if start_on_initialize
40
+ self.class.instances << self
41
41
  end
42
42
 
43
43
  # Schedule tasks that will enqueue jobs based on their schedule
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_support/core_ext/module/attribute_accessors_per_thread'
3
4
 
4
5
  module GoodJob
@@ -11,6 +12,7 @@ module GoodJob
11
12
  cron_key
12
13
  error_on_discard
13
14
  error_on_retry
15
+ error_on_retry_stopped
14
16
  execution
15
17
  execution_interrupted
16
18
  execution_retried
@@ -40,6 +42,12 @@ module GoodJob
40
42
  # @return [Exception, nil]
41
43
  thread_mattr_accessor :error_on_retry
42
44
 
45
+ # @!attribute [rw] error_on_retry_stopped
46
+ # @!scope class
47
+ # Error captured by retry_stopped
48
+ # @return [Exception, nil]
49
+ thread_mattr_accessor :error_on_retry_stopped
50
+
43
51
  # @!attribute [rw] executions
44
52
  # @!scope class
45
53
  # Execution
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  #
4
5
  # Manages daemonization of the current process.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # Ruby on Rails integration.
4
5
  class Engine < ::Rails::Engine
@@ -19,6 +20,10 @@ module GoodJob
19
20
  GoodJob::CurrentThread.error_on_retry = event.payload[:error]
20
21
  end
21
22
 
23
+ ActiveSupport::Notifications.subscribe "retry_stopped.active_job" do |event|
24
+ GoodJob::CurrentThread.error_on_retry_stopped = event.payload[:error]
25
+ end
26
+
22
27
  ActiveSupport::Notifications.subscribe "discard.active_job" do |event|
23
28
  GoodJob::CurrentThread.error_on_discard = event.payload[:error]
24
29
  end
@@ -35,6 +40,14 @@ module GoodJob
35
40
  end
36
41
  end
37
42
 
43
+ initializer 'good_job.active_record' do
44
+ config.to_prepare do
45
+ ActiveSupport.on_load :good_job_base_record, run_once: true do
46
+ GoodJob::BaseRecord.class_eval(&GoodJob._active_record_configuration) if GoodJob._active_record_configuration
47
+ end
48
+ end
49
+ end
50
+
38
51
  initializer "good_job.start_async" do
39
52
  # This hooks into the hookable places during Rails boot, which is unfortunately not Rails.application.initialized?
40
53
  # If an Adapter is initialized during boot, we want to want to start async executors once the framework dependencies have loaded.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # Exception raised when a job is interrupted by a SIGKILL or power failure.
4
5
  class InterruptError < StandardError
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'concurrent/delay'
3
4
 
4
5
  module GoodJob
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  #
4
5
  # Listens to GoodJob notifications and logs them.
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob # :nodoc:
4
+ # Metrics for the scheduler.
5
+ class Metrics
6
+ def initialize
7
+ @empty_executions = Concurrent::AtomicFixnum.new
8
+ @errored_executions = Concurrent::AtomicFixnum.new
9
+ @succeeded_executions = Concurrent::AtomicFixnum.new
10
+ @unexecutable_executions = Concurrent::AtomicFixnum.new
11
+ end
12
+
13
+ # Increments number of empty queried executions.
14
+ # @return [Integer]
15
+ def increment_empty_executions
16
+ @empty_executions.increment
17
+ end
18
+
19
+ # Increments number of failed executions.
20
+ # @return [Integer]
21
+ def increment_errored_executions
22
+ @errored_executions.increment
23
+ end
24
+
25
+ # Increments number of succeeded executions.
26
+ # @return [Integer]
27
+ def increment_succeeded_executions
28
+ @succeeded_executions.increment
29
+ end
30
+
31
+ # Increments number of unlocked executions.
32
+ # @return [Integer]
33
+ def increment_unexecutable_executions
34
+ @unexecutable_executions.increment
35
+ end
36
+
37
+ def to_h
38
+ {
39
+ empty_executions_count: @empty_executions.value,
40
+ errored_executions_count: @errored_executions.value,
41
+ succeeded_executions_count: @succeeded_executions.value,
42
+ unexecutable_executions_count: @unexecutable_executions.value,
43
+ }.tap do |values|
44
+ values[:total_executions_count] = values.values.sum
45
+ end
46
+ end
47
+
48
+ # Reset counters.
49
+ # @return [void]
50
+ def reset
51
+ @empty_executions.value = 0
52
+ @errored_executions.value = 0
53
+ @succeeded_executions.value = 0
54
+ @unexecutable_executions.value = 0
55
+ end
56
+ end
57
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # Delegates the interface of a single {Scheduler} to multiple Schedulers.
4
5
  class MultiScheduler
@@ -21,9 +21,11 @@ module GoodJob # :nodoc:
21
21
  end
22
22
 
23
23
  def refresh_process
24
- GoodJob::Process.with_connection(connection) do
25
- Process.logger.silence do
26
- @process&.refresh_if_stale(cleanup: true)
24
+ Rails.application.executor.wrap do
25
+ GoodJob::Process.with_connection(connection) do
26
+ GoodJob::Process.logger.silence do
27
+ @process&.refresh_if_stale(cleanup: true)
28
+ end
27
29
  end
28
30
  end
29
31
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_support/core_ext/module/attribute_accessors_per_thread'
3
4
  require 'concurrent/atomic/atomic_boolean'
4
5
  require "good_job/notifier/process_heartbeat"
@@ -46,7 +47,7 @@ module GoodJob # :nodoc:
46
47
  # @!scope class
47
48
  # List of all instantiated Notifiers in the current process.
48
49
  # @return [Array<GoodJob::Notifier>, nil]
49
- cattr_reader :instances, default: [], instance_reader: false
50
+ cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
50
51
 
51
52
  # @!attribute [rw] connection
52
53
  # @!scope class
@@ -77,10 +78,9 @@ module GoodJob # :nodoc:
77
78
  @connection_errors_reported = Concurrent::AtomicBoolean.new(false)
78
79
  @enable_listening = enable_listening
79
80
 
80
- self.class.instances << self
81
-
82
81
  create_executor
83
82
  listen
83
+ self.class.instances << self
84
84
  end
85
85
 
86
86
  # Tests whether the notifier is active and has acquired a dedicated database connection.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'concurrent/atomic/atomic_boolean'
3
4
 
4
5
  module GoodJob # :nodoc:
@@ -17,7 +18,7 @@ module GoodJob # :nodoc:
17
18
  # @!scope class
18
19
  # List of all instantiated Pollers in the current process.
19
20
  # @return [Array<GoodJob::Poller>, nil]
20
- cattr_reader :instances, default: [], instance_reader: false
21
+ cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
21
22
 
22
23
  # Creates GoodJob::Poller from a GoodJob::Configuration instance.
23
24
  # @param configuration [GoodJob::Configuration]
@@ -38,9 +39,8 @@ module GoodJob # :nodoc:
38
39
  @timer_options = DEFAULT_TIMER_OPTIONS.dup
39
40
  @timer_options[:execution_interval] = poll_interval if poll_interval.present?
40
41
 
41
- self.class.instances << self
42
-
43
42
  create_timer
43
+ self.class.instances << self
44
44
  end
45
45
 
46
46
  # Tests whether the timer is running.
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "concurrent/executor/thread_pool_executor"
3
4
  require "concurrent/executor/timer_set"
4
5
  require "concurrent/scheduled_task"
5
6
  require "concurrent/utility/processor_counter"
7
+ require 'good_job/metrics'
6
8
 
7
9
  module GoodJob # :nodoc:
8
10
  #
@@ -32,7 +34,7 @@ module GoodJob # :nodoc:
32
34
  # @!scope class
33
35
  # List of all instantiated Schedulers in the current process.
34
36
  # @return [Array<GoodJob::Scheduler>, nil]
35
- cattr_reader :instances, default: [], instance_reader: false
37
+ cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
36
38
 
37
39
  # Creates GoodJob::Scheduler(s) and Performers from a GoodJob::Configuration instance.
38
40
  # @param configuration [GoodJob::Configuration]
@@ -74,8 +76,6 @@ module GoodJob # :nodoc:
74
76
  def initialize(performer, max_threads: nil, max_cache: nil, warm_cache_on_initialize: false, cleanup_interval_seconds: nil, cleanup_interval_jobs: nil)
75
77
  raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
76
78
 
77
- self.class.instances << self
78
-
79
79
  @performer = performer
80
80
 
81
81
  @max_cache = max_cache || 0
@@ -88,8 +88,12 @@ module GoodJob # :nodoc:
88
88
  @executor_options[:name] = name
89
89
 
90
90
  @cleanup_tracker = CleanupTracker.new(cleanup_interval_seconds: cleanup_interval_seconds, cleanup_interval_jobs: cleanup_interval_jobs)
91
+ @metrics = ::GoodJob::Metrics.new
92
+ @executor_options[:name] = name
93
+
91
94
  create_executor
92
95
  warm_cache if warm_cache_on_initialize
96
+ self.class.instances << self
93
97
  end
94
98
 
95
99
  # Tests whether the scheduler is running.
@@ -139,6 +143,7 @@ module GoodJob # :nodoc:
139
143
 
140
144
  instrument("scheduler_restart_pools") do
141
145
  shutdown(timeout: timeout)
146
+ @metrics.reset
142
147
  create_executor
143
148
  warm_cache
144
149
  end
@@ -197,8 +202,20 @@ module GoodJob # :nodoc:
197
202
  # @!visibility private
198
203
  # @return [void]
199
204
  def task_observer(time, output, thread_error)
200
- error = thread_error || (output.is_a?(GoodJob::ExecutionResult) ? output.unhandled_error : nil)
201
- GoodJob._on_thread_error(error) if error
205
+ result = output.is_a?(GoodJob::ExecutionResult) ? output : nil
206
+
207
+ unhandled_error = thread_error || result&.unhandled_error
208
+ GoodJob._on_thread_error(unhandled_error) if unhandled_error
209
+
210
+ if unhandled_error || result&.handled_error
211
+ @metrics.increment_errored_executions
212
+ elsif result&.unexecutable
213
+ @metrics.increment_unexecutable_executions
214
+ elsif result
215
+ @metrics.increment_succeeded_executions
216
+ else
217
+ @metrics.increment_empty_executions
218
+ end
202
219
 
203
220
  instrument("finished_job_task", { result: output, error: thread_error, time: time })
204
221
  return unless output
@@ -214,15 +231,17 @@ module GoodJob # :nodoc:
214
231
  # Information about the Scheduler
215
232
  # @return [Hash]
216
233
  def stats
234
+ available_threads = executor.ready_worker_count
217
235
  {
218
- name: performer.name,
236
+ name: name,
237
+ queues: performer.name,
219
238
  max_threads: @executor_options[:max_threads],
220
- active_threads: @executor_options[:max_threads] - executor.ready_worker_count,
221
- available_threads: executor.ready_worker_count,
239
+ active_threads: @executor_options[:max_threads] - available_threads,
240
+ available_threads: available_threads,
222
241
  max_cache: @max_cache,
223
242
  active_cache: cache_count,
224
243
  available_cache: remaining_cache_count,
225
- }
244
+ }.merge!(@metrics.to_h)
226
245
  end
227
246
 
228
247
  # Preload existing runnable and future-scheduled jobs
@@ -316,7 +335,7 @@ module GoodJob # :nodoc:
316
335
 
317
336
  # Custom sub-class of +Concurrent::ThreadPoolExecutor+ to add additional worker status.
318
337
  # @private
319
- class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
338
+ class ThreadPoolExecutor < ::Concurrent::ThreadPoolExecutor
320
339
  # Number of inactive threads available to execute tasks.
321
340
  # https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
322
341
  # @return [Integer]
@@ -335,7 +354,7 @@ module GoodJob # :nodoc:
335
354
 
336
355
  # Custom sub-class of +Concurrent::TimerSet+ for additional behavior.
337
356
  # @private
338
- class TimerSet < Concurrent::TimerSet
357
+ class TimerSet < ::Concurrent::TimerSet
339
358
  # Number of scheduled jobs in the queue
340
359
  # @return [Integer]
341
360
  def length
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # GoodJob gem version.
4
- VERSION = '3.15.14'
5
+ VERSION = '3.16.1'
5
6
 
6
7
  # GoodJob version as Gem::Version object
7
8
  GEM_VERSION = Gem::Version.new(VERSION)
data/lib/good_job.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "active_job"
3
4
  require "active_job/queue_adapters"
4
5
 
@@ -46,7 +47,7 @@ module GoodJob
46
47
  # @return [ActiveRecord::Base]
47
48
  # @example Change the base class:
48
49
  # GoodJob.active_record_parent_class = "CustomApplicationRecord"
49
- mattr_accessor :active_record_parent_class, default: "ActiveRecord::Base"
50
+ mattr_accessor :active_record_parent_class, default: nil
50
51
 
51
52
  # @!attribute [rw] logger
52
53
  # @!scope class
@@ -105,6 +106,19 @@ module GoodJob
105
106
  on_thread_error.call(exception) if on_thread_error.respond_to?(:call)
106
107
  end
107
108
 
109
+ # Custom Active Record configuration that is class_eval'ed into +GoodJob::BaseRecord+
110
+ # @param block Custom Active Record configuration
111
+ # @retyrn [void]
112
+ #
113
+ # @example
114
+ # GoodJob.configure_active_record do
115
+ # connects_to database: :special_database
116
+ # end
117
+ def self.configure_active_record(&block)
118
+ self._active_record_configuration = block
119
+ end
120
+ mattr_accessor :_active_record_configuration, default: nil
121
+
108
122
  # Stop executing jobs.
109
123
  # GoodJob does its work in pools of background threads.
110
124
  # When forking processes you should shut down these background threads before forking, and restart them after forking.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.15.14
4
+ version: 3.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-03 00:00:00.000000000 Z
11
+ date: 2023-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -343,8 +343,11 @@ files:
343
343
  - app/frontend/good_job/vendor/rails_ujs.js
344
344
  - app/frontend/good_job/vendor/stimulus.js
345
345
  - app/helpers/good_job/application_helper.rb
346
+ - app/models/concerns/good_job/error_events.rb
346
347
  - app/models/concerns/good_job/filterable.rb
348
+ - app/models/concerns/good_job/lockable.rb
347
349
  - app/models/concerns/good_job/reportable.rb
350
+ - app/models/good_job/active_record_parent_class.rb
348
351
  - app/models/good_job/base_execution.rb
349
352
  - app/models/good_job/base_record.rb
350
353
  - app/models/good_job/batch.rb
@@ -353,8 +356,8 @@ files:
353
356
  - app/models/good_job/discrete_execution.rb
354
357
  - app/models/good_job/execution.rb
355
358
  - app/models/good_job/execution_result.rb
359
+ - app/models/good_job/i18n_config.rb
356
360
  - app/models/good_job/job.rb
357
- - app/models/good_job/lockable.rb
358
361
  - app/models/good_job/process.rb
359
362
  - app/models/good_job/setting.rb
360
363
  - app/views/good_job/batches/_jobs.erb
@@ -394,7 +397,7 @@ files:
394
397
  - config/locales/nl.yml
395
398
  - config/locales/ru.yml
396
399
  - config/locales/tr.yml
397
- - config/locales/ua.yml
400
+ - config/locales/uk.yml
398
401
  - config/routes.rb
399
402
  - exe/good_job
400
403
  - lib/active_job/queue_adapters/good_job_adapter.rb
@@ -405,6 +408,7 @@ files:
405
408
  - lib/generators/good_job/templates/update/migrations/03_create_index_good_jobs_jobs_on_priority_created_at_when_unfinished.rb.erb
406
409
  - lib/generators/good_job/templates/update/migrations/04_create_good_job_batches.rb.erb
407
410
  - lib/generators/good_job/templates/update/migrations/05_create_good_job_executions.rb.erb
411
+ - lib/generators/good_job/templates/update/migrations/06_create_good_jobs_error_event.rb.erb
408
412
  - lib/generators/good_job/update_generator.rb
409
413
  - lib/good_job.rb
410
414
  - lib/good_job/active_job_extensions/batches.rb
@@ -426,6 +430,7 @@ files:
426
430
  - lib/good_job/interrupt_error.rb
427
431
  - lib/good_job/job_performer.rb
428
432
  - lib/good_job/log_subscriber.rb
433
+ - lib/good_job/metrics.rb
429
434
  - lib/good_job/multi_scheduler.rb
430
435
  - lib/good_job/notifier.rb
431
436
  - lib/good_job/notifier/process_heartbeat.rb