okcomputer 1.19.2 → 1.20.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b698c4f15d160137298b7023a1c8e661f6f4a49f2378a8aae23e2451e012306
4
- data.tar.gz: 315252dc778cb44706720dbab9420f778e237e7350e388640889b06c260c9490
3
+ metadata.gz: ff40aaa50d94381ec00151daa8e8f06007dd371275c41606daf292a74f9a5b92
4
+ data.tar.gz: 8fdf9fe8d7fb3f3b12bc80f921bd240f883de707b8fa3960f2c3297436eda242
5
5
  SHA512:
6
- metadata.gz: 2ded6dc9dfa8d24a9adb8fe80826e403a9cb408818ebb5d8bf504f7390e848e498448b50c5c4af4dfd60d28ab5ef975803b51126b48f53c40839968e5580945c
7
- data.tar.gz: '087e5ae51b7fa551879135a86819dd2d95c28b1edaf35c716b4f9c4408fb9cf02238225f7ccd6fe214b117ee6eed0e16d9656a3a04a9a9370c76646b7f7136b0'
6
+ metadata.gz: 1e82ee5929483cd2dd98af763bfcd71e1a15a02d0e5d9800e6d14155563a73cf2a1d1cd3a181e603495089f713d250455030063d0ef391434d2be7ff0bea8817
7
+ data.tar.gz: 26c74cd196d72d6747b27eea7212efd49699dcb38ef1c848b1c85213f978f97fc80f32614f0c4969883670fadf2bff08a237c65db2dd5df0a796eb7d198f7c6d
data/README.markdown CHANGED
@@ -15,22 +15,19 @@ your own custom checks.
15
15
  For more insight into why we built this, check out [our blog post introducing
16
16
  OkComputer](http://pulse.sportngin.com/news_article/show/267646?referrer_id=543230).
17
17
 
18
- OkComputer currently fully supports the following Rails versions:
18
+ OkComputer supports the following Rails versions, as tested by the CI build matrix:
19
19
 
20
+ * 8.1
21
+ * 8.0
22
+ * 7.2
23
+ * 7.1
20
24
  * 7.0
21
25
  * 6.1
22
26
  * 6.0
23
27
  * 5.2
24
28
  * 5.1
25
- * 4.2
26
-
27
- In addition, the CI tests are passing on, but is not guaranteed to work with, the following Rails versions:
28
-
29
- * 8.1
30
- * 8.0
31
- * 7.2
32
- * 7.1
33
29
  * 5.0
30
+ * 4.2
34
31
  * 4.1
35
32
  * 4.0
36
33
 
@@ -60,8 +57,43 @@ $ gem install okcomputer
60
57
 
61
58
  ## Usage
62
59
 
63
- To perform the default checks (application running and ActiveRecord database
64
- connection), do nothing other than adding to your application's Gemfile.
60
+ Adding OkComputer to your Gemfile mounts its routes at `/okcomputer` and
61
+ registers a simple application check named `default`. When ActiveRecord is
62
+ loaded, it also registers an ActiveRecord connection check named
63
+ `database`. If Sequel is loaded instead, it registers a Sequel database check.
64
+
65
+ Test the application check without any additional configuration:
66
+
67
+ ```
68
+ $ curl http://localhost:3000/okcomputer
69
+ default: PASSED Application is running (0.000s)
70
+ ```
71
+
72
+ ### Endpoints
73
+
74
+ | Endpoint | Checks performed |
75
+ | --- | --- |
76
+ | `/okcomputer` | The `default` application check |
77
+ | `/okcomputer/database` | The registered `database` check, when present |
78
+ | `/okcomputer/all` | All checks and collections in the default collection, except those registered with `skip_all: true` |
79
+ | `/okcomputer/:name` | The registered check or collection named `:name` |
80
+
81
+ A successful check returns HTTP 200. A failed check, or an aggregate containing
82
+ a failed check, returns HTTP 500. Requesting an unregistered check returns HTTP
83
+ 404.
84
+
85
+ Responses are plain text by default. Append `.json` or send an
86
+ `Accept: application/json` header to receive JSON:
87
+
88
+ ```json
89
+ {
90
+ "default": {
91
+ "message": "Application is running",
92
+ "success": true,
93
+ "time": 0.000123
94
+ }
95
+ }
96
+ ```
65
97
 
66
98
  ### If Not Using ActiveRecord
67
99
 
@@ -141,6 +173,22 @@ OkComputer::Registry.register "resque_scheduler_down", OkComputer::ResqueSchedul
141
173
 
142
174
  # If you're using SolidCache instead of Memcached, use this check instead of CacheCheck
143
175
  OkComputer::Registry.register "cache", OkComputer::CacheCheckSolidCache.new
176
+
177
+ # If you're using SolidQueue, these checks monitor its health and throughput.
178
+ OkComputer::Registry.register "solid_queue", OkComputer::SolidQueueCheck.new
179
+
180
+ # Optionally, alert when a specific queue's backlog of ready jobs gets too high:
181
+ OkComputer::Registry.register "solid_queue_backed_up", OkComputer::SolidQueueBackedUpCheck.new("default", 100)
182
+
183
+ # Optionally, alert when scheduled jobs are overdue — a sign the dispatcher has
184
+ # stalled and is not promoting jobs to ready.
185
+ OkComputer::Registry.register "solid_queue_scheduled_backed_up", OkComputer::SolidQueueScheduledBackedUpCheck.new(0, grace: 2.minutes)
186
+
187
+ # Optionally, alert when too many jobs have failed in total:
188
+ OkComputer::Registry.register "solid_queue_failed_jobs", OkComputer::SolidQueueFailedJobsCheck.new(25)
189
+
190
+ # Optionally, alert on a rapid increase in failures (more than 10 failures in 300 sec)
191
+ OkComputer::Registry.register "solid_queue_failed_jobs_rate", OkComputer::SolidQueueFailedJobsRateCheck.new(10, 300)
144
192
  ```
145
193
 
146
194
  ### Registering Custom Checks
@@ -166,6 +214,32 @@ end
166
214
  OkComputer::Registry.register "check_for_odds", MyCustomCheck.new
167
215
  ```
168
216
 
217
+ ### Grouping Checks
218
+
219
+ Use a `CheckCollection` to expose several related checks from one endpoint. Register
220
+ the collection with `skip_all: true` when the group should not run as part of the
221
+ default `/okcomputer/all` endpoint:
222
+
223
+ ```ruby
224
+ # config/initializers/okcomputer.rb
225
+ versions = OkComputer::CheckCollection.new("Versions")
226
+
227
+ OkComputer::Registry.register "versions", versions, skip_all: true
228
+ OkComputer::Registry.register "ruby_version", OkComputer::RubyVersionCheck.new, "versions"
229
+ OkComputer::Registry.register "app_version", OkComputer::AppVersionCheck.new, "versions"
230
+ ```
231
+
232
+ The group is available at `/okcomputer/versions` and `/okcomputer/versions.json`.
233
+ Its checks remain individually available, but neither the group nor its checks run
234
+ at `/okcomputer/all`.
235
+
236
+ An individual check can also be omitted from `/okcomputer/all` while retaining its
237
+ own endpoint:
238
+
239
+ ```ruby
240
+ OkComputer::Registry.register "ruby_version", OkComputer::RubyVersionCheck.new, skip_all: true
241
+ ```
242
+
169
243
  ### Registering Optional Checks
170
244
 
171
245
  Register an optional check like so:
@@ -196,16 +270,6 @@ By default, OkComputer runs checks in sequence. If you'd like to run them in par
196
270
  OkComputer.check_in_parallel = true
197
271
  ```
198
272
 
199
- ## Performing Checks
200
-
201
- * Perform a simple up check: http://example.com/okcomputer
202
- * Perform all installed checks: http://example.com/okcomputer/all
203
- * Perform a specific installed check: http://example.com/okcomputer/database
204
-
205
- Checks are available as plain text (by default) or JSON by appending .json, e.g.:
206
- * http://example.com/okcomputer.json
207
- * http://example.com/okcomputer/all.json
208
-
209
273
  ## OkComputer NewRelic Ignore
210
274
 
211
275
  If NewRelic is installed, OkComputer automatically disables NewRelic monitoring for uptime checks,
@@ -14,11 +14,34 @@ module OkComputer
14
14
 
15
15
  # Public: Return the status of the check
16
16
  def check
17
- tcp_socket_request
18
- mark_message "#{klass} check to #{host}:#{port} successful"
19
- rescue => e
20
- mark_message "#{klass} at #{host}:#{port} is not accepting connections: '#{e}'"
21
- mark_failure
17
+ case klass.delivery_method
18
+ when :smtp
19
+ begin
20
+ tcp_socket_request
21
+ mark_message "#{klass} check to #{host}:#{port} successful"
22
+ rescue => e
23
+ mark_message "#{klass} at #{host}:#{port} is not accepting connections: '#{e}'"
24
+ mark_failure
25
+ end
26
+ when :sendmail
27
+ begin
28
+ location = klass.sendmail_settings[:location]
29
+ if File.executable?(location)
30
+ mark_message "#{klass} sendmail executable #{location} can be executed"
31
+ else
32
+ mark_message "#{klass} sendmail executable #{location} is not executable"
33
+ mark_failure
34
+ end
35
+ rescue => e
36
+ mark_message "#{klass} error checking sendmail executable: '#{e}'"
37
+ mark_failure
38
+ end
39
+ when :test
40
+ mark_message "#{klass} is in test mode"
41
+ else
42
+ mark_message "unknown delivery method #{klass.delivery_method}"
43
+ mark_failure
44
+ end
22
45
  end
23
46
  end
24
47
  end
@@ -0,0 +1,22 @@
1
+ module OkComputer
2
+ class SolidQueueBackedUpCheck < SizeThresholdCheck
3
+ attr_accessor :queue
4
+ attr_accessor :threshold
5
+
6
+ # Public: Initialize a check for a backed-up SolidQueue queue
7
+ #
8
+ # queue - The name of the SolidQueue queue to check
9
+ # threshold - An Integer to compare the queue's number of ready jobs
10
+ # against to consider it backed up
11
+ def initialize(queue, threshold)
12
+ self.queue = queue
13
+ self.threshold = Integer(threshold)
14
+ self.name = "SolidQueue queue '#{queue}'"
15
+ end
16
+
17
+ # Public: The number of ready (pending) jobs in the check's queue
18
+ def size
19
+ SolidQueue::Queue.new(queue).size
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,66 @@
1
+ module OkComputer
2
+ # Verifies that SolidQueue is up and processing jobs by confirming that at
3
+ # least one worker process has a recent heartbeat, and reports a summary of
4
+ # the current job counts.
5
+ #
6
+ # See https://github.com/rails/solid_queue
7
+ class SolidQueueCheck < Check
8
+ # Public: Check whether SolidQueue has live workers and a live dispatcher,
9
+ # and report job stats
10
+ def check
11
+ if live_workers.zero?
12
+ mark_failure
13
+ mark_message "SolidQueue is DOWN. No workers are alive. (#{stats})"
14
+ elsif live_dispatchers.zero?
15
+ mark_failure
16
+ mark_message "SolidQueue dispatcher is DOWN. Scheduled jobs will not run. (#{stats})"
17
+ else
18
+ mark_message "SolidQueue is up (#{live_workers} worker(s), #{live_dispatchers} dispatcher(s) alive). Job Counts: #{stats}"
19
+ end
20
+ rescue => e
21
+ mark_failure
22
+ mark_message "Error: '#{e}'"
23
+ end
24
+
25
+ # Public: The number of worker processes whose heartbeat is within
26
+ # SolidQueue's configured alive threshold (default: 5 minutes)
27
+ def live_workers
28
+ alive_processes.where(kind: "Worker").count
29
+ end
30
+
31
+ # Public: The number of dispatcher processes whose heartbeat is recent enough
32
+ # to be considered alive
33
+ def live_dispatchers
34
+ alive_processes.where(kind: "Dispatcher").count
35
+ end
36
+
37
+ # Public: A summary of the current job counts across SolidQueue
38
+ def stats
39
+ "ready: #{ready}, scheduled: #{scheduled}, in progress: #{in_progress}, failed: #{failed}"
40
+ end
41
+
42
+ private
43
+
44
+ # SolidQueue::Process records that have sent a heartbeat recently enough to
45
+ # be considered alive. Mirrors SolidQueue's own Prunable logic.
46
+ def alive_processes
47
+ SolidQueue::Process.where("last_heartbeat_at > ?", SolidQueue.process_alive_threshold.ago)
48
+ end
49
+
50
+ def ready
51
+ SolidQueue::ReadyExecution.count
52
+ end
53
+
54
+ def scheduled
55
+ SolidQueue::ScheduledExecution.count
56
+ end
57
+
58
+ def in_progress
59
+ SolidQueue::ClaimedExecution.count
60
+ end
61
+
62
+ def failed
63
+ SolidQueue::FailedExecution.count
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,19 @@
1
+ module OkComputer
2
+ class SolidQueueFailedJobsCheck < SizeThresholdCheck
3
+ attr_accessor :threshold
4
+
5
+ # Public: Initialize a check for the total number of failed SolidQueue jobs
6
+ #
7
+ # threshold - An Integer to compare the failed job count against to
8
+ # consider it over threshold
9
+ def initialize(threshold)
10
+ self.threshold = Integer(threshold)
11
+ self.name = "SolidQueue Failed Jobs"
12
+ end
13
+
14
+ # Public: The total number of failed jobs
15
+ def size
16
+ SolidQueue::FailedExecution.count
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ module OkComputer
2
+ # Detects rapid increases in failed SolidQueue jobs by counting failures
3
+ # that occurred within a rolling time window, rather than the total
4
+ # accumulated failures. This is stateless across requests: it relies on the
5
+ # created_at timestamp of each failed execution.
6
+ class SolidQueueFailedJobsRateCheck < SizeThresholdCheck
7
+ attr_accessor :threshold
8
+ attr_accessor :window
9
+
10
+ # Public: Initialize a check for the rate of failing SolidQueue jobs
11
+ #
12
+ # threshold - An Integer number of failures within the window to tolerate
13
+ # before the check is considered failed
14
+ # window - The size of the rolling window to count failures within. Accepts
15
+ # either a number of seconds or an ActiveSupport::Duration (e.g.
16
+ # 5.minutes). Defaults to 300 seconds (5 minutes).
17
+ def initialize(threshold, window = 300)
18
+ self.threshold = Integer(threshold)
19
+ self.window = window
20
+ self.name = "SolidQueue Failed Jobs Rate"
21
+ end
22
+
23
+ # Public: The number of jobs that have failed within the window
24
+ def size
25
+ cutoff = window.respond_to?(:ago) ? window.ago : Time.now - window
26
+ SolidQueue::FailedExecution.where("created_at > ?", cutoff).count
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ module OkComputer
2
+ # Detects a stalled SolidQueue dispatcher by counting scheduled jobs that are
3
+ # overdue — i.e. their scheduled_at is more than `grace` in the past, so a
4
+ # healthy dispatcher should already have promoted them to ready_executions.
5
+ #
6
+ # This is distinct from SolidQueueBackedUpCheck, which measures ready (already
7
+ # promoted) depth. A dead/behind dispatcher leaves jobs stuck in scheduled and
8
+ # invisible to that check; this check surfaces them.
9
+ class SolidQueueScheduledBackedUpCheck < SizeThresholdCheck
10
+ attr_accessor :threshold
11
+ attr_accessor :grace
12
+
13
+ # Public: Initialize a check for overdue scheduled SolidQueue jobs
14
+ #
15
+ # threshold - An Integer; the number of overdue scheduled jobs to tolerate
16
+ # before considering the dispatcher backed up.
17
+ # grace - An ActiveSupport::Duration; how far past scheduled_at a job must be
18
+ # before it counts as overdue. The dispatcher polls roughly every second
19
+ # (config/queue.yml polling_interval), so sub-poll lateness is normal and a
20
+ # grace window prevents flapping. Defaults to 1 minute.
21
+ def initialize(threshold, grace: 1.minute)
22
+ self.threshold = Integer(threshold)
23
+ self.grace = grace
24
+ self.name = "SolidQueue overdue scheduled jobs"
25
+ end
26
+
27
+ # Public: Count of scheduled jobs overdue by more than `grace`. A healthy
28
+ # dispatcher keeps this at 0.
29
+ def size
30
+ SolidQueue::ScheduledExecution.where("scheduled_at <= ?", grace.ago).count
31
+ end
32
+ end
33
+ end
@@ -3,7 +3,7 @@ require "benchmark"
3
3
  module OkComputer
4
4
  class Check
5
5
  # to be set by Registry upon registration
6
- attr_accessor :registrant_name
6
+ attr_accessor :registrant_name, :skip_all
7
7
  # nil by default, only set to true if the check deems itself failed
8
8
  attr_accessor :failure_occurred
9
9
  # nil by default, set by #check to control the output
@@ -1,13 +1,16 @@
1
1
  module OkComputer
2
2
  class CheckCollection
3
- attr_accessor :collection, :registrant_name, :display
3
+ attr_accessor :collection, :registrant_name, :display, :skip_all
4
4
 
5
5
  # Public: Initialize a new CheckCollection
6
6
  #
7
7
  # display - the display name for the Check Collection
8
- def initialize(display)
8
+ # exclude_skipped_checks - whether checks marked skip_all should be omitted
9
+ def initialize(display, exclude_skipped_checks=false)
9
10
  self.display = display
10
11
  self.collection = {}
12
+ self.skip_all = false
13
+ @exclude_skipped_checks = exclude_skipped_checks
11
14
  end
12
15
 
13
16
  # Public: Run the collection's checks
@@ -37,7 +40,7 @@ module OkComputer
37
40
  #
38
41
  # Returns an Array of the collection's values
39
42
  def checks
40
- collection.values
43
+ included_collection.values
41
44
  end
42
45
 
43
46
  def <=>(check)
@@ -51,13 +54,13 @@ module OkComputer
51
54
  alias_method :values, :checks
52
55
 
53
56
  def check_names
54
- collection.keys
57
+ included_collection.keys
55
58
  end
56
59
 
57
60
  alias_method :keys, :check_names
58
61
 
59
62
  def sub_collections
60
- checks.select{ |c| c.is_a?(CheckCollection)}
63
+ collection.values.select{ |c| c.is_a?(CheckCollection)}
61
64
  end
62
65
 
63
66
  def self_and_sub_collections
@@ -108,15 +111,17 @@ module OkComputer
108
111
 
109
112
  private
110
113
 
114
+ def included_collection
115
+ return collection unless @exclude_skipped_checks
116
+ collection.reject{ |_name, check| check.respond_to?(:skip_all) && check.skip_all }
117
+ end
118
+
111
119
  def check_in_sequence
112
120
  checks.each(&:run)
113
121
  end
114
122
 
115
123
  def check_in_parallel
116
- threads = checks.map do |check|
117
- Thread.new { check.run }
118
- end
119
- threads.each(&:join)
124
+ checks.map{ |check| Thread.new(check, &:run) }.each(&:join)
120
125
  end
121
126
  end
122
127
  end
@@ -31,7 +31,7 @@ module OkComputer
31
31
  #
32
32
  # Returns @default_collection
33
33
  def self.default_collection
34
- @default_collection ||= CheckCollection.new('Default Collection')
34
+ @default_collection ||= CheckCollection.new('Default Collection', true)
35
35
  end
36
36
 
37
37
  # Public: Register the given check with OkComputer
@@ -39,8 +39,27 @@ module OkComputer
39
39
  # check_name - The name of the check to retrieve
40
40
  # check_object - Instance of Checker to register
41
41
  # collection_name - The name of the check collection the check should be registered to
42
- def self.register(check_name, check_object, collection_name=nil)
43
- find_collection(collection_name).register(check_name, check_object)
42
+ # options - Set skip_all to true to omit the check from the default collection's results
43
+ def self.register(check_name, check_object, collection_name=nil, options={})
44
+ if collection_name.is_a?(Hash)
45
+ options = collection_name
46
+ collection_name = nil
47
+ end
48
+
49
+ if collection_name && options[:skip_all]
50
+ raise ArgumentError, "skip_all is only supported in the default collection"
51
+ end
52
+
53
+ if !collection_name && options.key?(:skip_all)
54
+ if check_object.respond_to?(:skip_all=)
55
+ check_object.skip_all = !!options[:skip_all]
56
+ elsif options[:skip_all]
57
+ raise ArgumentError, "skip_all requires a check that supports skip_all="
58
+ end
59
+ end
60
+
61
+ collection = find_collection(collection_name)
62
+ collection.register(check_name, check_object)
44
63
  end
45
64
 
46
65
  # Public: Remove the check of the given name being checked
@@ -1,3 +1,3 @@
1
1
  module OkComputer
2
- VERSION = '1.19.2'
2
+ VERSION = '1.20.0'
3
3
  end
data/lib/okcomputer.rb CHANGED
@@ -34,6 +34,11 @@ require "ok_computer/built_in_checks/resque_failure_threshold_check"
34
34
  require "ok_computer/built_in_checks/ruby_version_check"
35
35
  require "ok_computer/built_in_checks/sequel_check"
36
36
  require "ok_computer/built_in_checks/sidekiq_latency_check"
37
+ require "ok_computer/built_in_checks/solid_queue_check"
38
+ require "ok_computer/built_in_checks/solid_queue_backed_up_check"
39
+ require "ok_computer/built_in_checks/solid_queue_scheduled_backed_up_check"
40
+ require "ok_computer/built_in_checks/solid_queue_failed_jobs_check"
41
+ require "ok_computer/built_in_checks/solid_queue_failed_jobs_rate_check"
37
42
  require "ok_computer/built_in_checks/solr_check"
38
43
 
39
44
  OkComputer::Registry.register "default", OkComputer::DefaultCheck.new
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: okcomputer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.19.2
4
+ version: 1.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Byrne
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2026-05-31 00:00:00.000000000 Z
15
+ date: 2026-09-11 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: benchmark
@@ -75,6 +75,11 @@ files:
75
75
  - lib/ok_computer/built_in_checks/sequel_check.rb
76
76
  - lib/ok_computer/built_in_checks/sidekiq_latency_check.rb
77
77
  - lib/ok_computer/built_in_checks/size_threshold_check.rb
78
+ - lib/ok_computer/built_in_checks/solid_queue_backed_up_check.rb
79
+ - lib/ok_computer/built_in_checks/solid_queue_check.rb
80
+ - lib/ok_computer/built_in_checks/solid_queue_failed_jobs_check.rb
81
+ - lib/ok_computer/built_in_checks/solid_queue_failed_jobs_rate_check.rb
82
+ - lib/ok_computer/built_in_checks/solid_queue_scheduled_backed_up_check.rb
78
83
  - lib/ok_computer/built_in_checks/solr_check.rb
79
84
  - lib/ok_computer/check.rb
80
85
  - lib/ok_computer/check_collection.rb