rails_health_checks 0.2.0 → 0.4.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 +4 -4
- data/README.md +9 -1
- data/lib/rails_health_checks/check_registry.rb +16 -1
- data/lib/rails_health_checks/checks/cache_check.rb +21 -0
- data/lib/rails_health_checks/checks/disk_check.rb +42 -0
- data/lib/rails_health_checks/checks/good_job_check.rb +31 -0
- data/lib/rails_health_checks/checks/http_check.rb +26 -0
- data/lib/rails_health_checks/checks/memory_check.rb +35 -0
- data/lib/rails_health_checks/checks/resque_check.rb +28 -0
- data/lib/rails_health_checks/checks/sidekiq_check.rb +28 -0
- data/lib/rails_health_checks/checks/solid_queue_check.rb +27 -0
- data/lib/rails_health_checks/configuration.rb +13 -1
- data/lib/rails_health_checks/version.rb +1 -1
- data/lib/rails_health_checks.rb +8 -0
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0f595984abf8a7db84183766d61918bdf684642b6ea67b2075afbda8f4cbae6
|
|
4
|
+
data.tar.gz: ab35a0deb32d298eae8dadc04c39dbf8ca34b8e77018625ebad8ba81e1901f34
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f2db831794c15dbf59c6763ed65a092e9bc56db1dc87e35d4565cccd63303cfe9555a7b36f738e6a861086c0362f8eb98459e99ea46f5838bfeb3862130ebf5
|
|
7
|
+
data.tar.gz: 3d5b057c32506961cf0416d37ea45c70f5218767961ddeed208b37887abd7781187fc1f480aff2e5fb522bfad8400e355fa745a04ec876a064e1aeb938e91c78
|
data/README.md
CHANGED
|
@@ -76,7 +76,7 @@ Status values: `ok` | `degraded` | `critical`. Overall status is `critical` if a
|
|
|
76
76
|
```ruby
|
|
77
77
|
# config/initializers/rails_health_checks.rb
|
|
78
78
|
RailsHealthChecks.configure do |config|
|
|
79
|
-
config.checks = [:database] # checks to run (default: [:database])
|
|
79
|
+
config.checks = [:database, :cache] # checks to run (default: [:database])
|
|
80
80
|
config.timeout = 5 # global timeout per check in seconds (default: 5)
|
|
81
81
|
end
|
|
82
82
|
```
|
|
@@ -126,6 +126,14 @@ The block receives the `ActionDispatch::Request` object and must return a truthy
|
|
|
126
126
|
| Check | Description |
|
|
127
127
|
|-------|-------------|
|
|
128
128
|
| `:database` | ActiveRecord `SELECT 1` against the primary connection, includes latency |
|
|
129
|
+
| `:cache` | `Rails.cache` read/write probe; works with Redis, Memcached, or in-process store |
|
|
130
|
+
| `:sidekiq` | Sidekiq Redis connectivity; optional `config.sidekiq_queue_size` threshold for queue depth |
|
|
131
|
+
| `:solid_queue` | Solid Queue DB connectivity; optional `config.solid_queue_job_count` threshold for pending jobs |
|
|
132
|
+
| `:good_job` | GoodJob queue latency; optional `config.good_job_latency` (seconds) threshold for oldest pending job |
|
|
133
|
+
| `:resque` | Resque Redis connectivity; optional `config.resque_queue_size` threshold for total queue depth |
|
|
134
|
+
| `:disk` | Free disk bytes via `df`; optional `config.disk_warn_threshold` / `config.disk_critical_threshold` (bytes) and `config.disk_path` (default: `/`) |
|
|
135
|
+
| `:memory` | Process RSS via `ps`; optional `config.memory_threshold` (bytes) reports `degraded` when exceeded |
|
|
136
|
+
| `:http` | HTTP GET to `config.http_url`; reports `critical` if response code differs from `config.http_expected_status` (default: `200`) or a network error occurs |
|
|
129
137
|
|
|
130
138
|
[↑ Back to top](#table-of-contents)
|
|
131
139
|
|
|
@@ -5,7 +5,22 @@ require "timeout"
|
|
|
5
5
|
module RailsHealthChecks
|
|
6
6
|
class CheckRegistry
|
|
7
7
|
BUILT_INS = {
|
|
8
|
-
database: -> { Checks::DatabaseCheck.new }
|
|
8
|
+
database: -> { Checks::DatabaseCheck.new },
|
|
9
|
+
cache: -> { Checks::CacheCheck.new },
|
|
10
|
+
sidekiq: -> { Checks::SidekiqCheck.new(queue_size: RailsHealthChecks.configuration.sidekiq_queue_size) },
|
|
11
|
+
solid_queue: -> { Checks::SolidQueueCheck.new(job_count: RailsHealthChecks.configuration.solid_queue_job_count) },
|
|
12
|
+
good_job: -> { Checks::GoodJobCheck.new(latency: RailsHealthChecks.configuration.good_job_latency) },
|
|
13
|
+
resque: -> { Checks::ResqueCheck.new(queue_size: RailsHealthChecks.configuration.resque_queue_size) },
|
|
14
|
+
memory: -> { Checks::MemoryCheck.new(threshold: RailsHealthChecks.configuration.memory_threshold) },
|
|
15
|
+
http: -> { Checks::HttpCheck.new(
|
|
16
|
+
url: RailsHealthChecks.configuration.http_url,
|
|
17
|
+
expected_status: RailsHealthChecks.configuration.http_expected_status
|
|
18
|
+
) },
|
|
19
|
+
disk: -> { Checks::DiskCheck.new(
|
|
20
|
+
warn_threshold: RailsHealthChecks.configuration.disk_warn_threshold,
|
|
21
|
+
critical_threshold: RailsHealthChecks.configuration.disk_critical_threshold,
|
|
22
|
+
path: RailsHealthChecks.configuration.disk_path
|
|
23
|
+
) }
|
|
9
24
|
}.freeze
|
|
10
25
|
|
|
11
26
|
def self.build(check_names)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsHealthChecks
|
|
4
|
+
module Checks
|
|
5
|
+
class CacheCheck < Check
|
|
6
|
+
PROBE_KEY = "rails_health_checks:cache_probe"
|
|
7
|
+
PROBE_VALUE = "ok"
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
measure do
|
|
11
|
+
Rails.cache.write(PROBE_KEY, PROBE_VALUE, expires_in: 10)
|
|
12
|
+
result = Rails.cache.read(PROBE_KEY)
|
|
13
|
+
raise "unexpected value: #{result.inspect}" unless result == PROBE_VALUE
|
|
14
|
+
end
|
|
15
|
+
pass
|
|
16
|
+
rescue StandardError => e
|
|
17
|
+
fail_with(e.message)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module RailsHealthChecks
|
|
6
|
+
module Checks
|
|
7
|
+
class DiskCheck < Check
|
|
8
|
+
def initialize(warn_threshold: nil, critical_threshold: nil, path: "/")
|
|
9
|
+
@warn_threshold = warn_threshold
|
|
10
|
+
@critical_threshold = critical_threshold
|
|
11
|
+
@path = path
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
free = nil
|
|
16
|
+
measure { free = free_bytes }
|
|
17
|
+
|
|
18
|
+
if @critical_threshold && free < @critical_threshold
|
|
19
|
+
return fail_with("free disk space #{free} bytes below critical threshold #{@critical_threshold} bytes")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if @warn_threshold && free < @warn_threshold
|
|
23
|
+
return warn_with("free disk space #{free} bytes below warn threshold #{@warn_threshold} bytes")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
pass
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
fail_with(e.message)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def free_bytes
|
|
34
|
+
out, _, status = Open3.capture3("df", "-Pk", @path.to_s)
|
|
35
|
+
raise "df command failed with exit status #{status.exitstatus}" unless status.success?
|
|
36
|
+
|
|
37
|
+
parts = out.split("\n").last.split
|
|
38
|
+
parts[3].to_i * 1024
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsHealthChecks
|
|
4
|
+
module Checks
|
|
5
|
+
class GoodJobCheck < Check
|
|
6
|
+
def initialize(latency: nil)
|
|
7
|
+
unless defined?(::GoodJob)
|
|
8
|
+
raise LoadError, "GoodJob is not installed. Add `gem 'good_job'` to your Gemfile to use the :good_job check."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
@latency = latency
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
measure do
|
|
16
|
+
oldest = ::GoodJob::Job
|
|
17
|
+
.where(finished_at: nil, performed_at: nil)
|
|
18
|
+
.minimum(:created_at)
|
|
19
|
+
|
|
20
|
+
if @latency && oldest
|
|
21
|
+
age = (Time.current - oldest).to_i
|
|
22
|
+
return warn_with("queue latency #{age}s exceeds threshold #{@latency}s") if age > @latency
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
pass
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
fail_with(e.message)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module RailsHealthChecks
|
|
7
|
+
module Checks
|
|
8
|
+
class HttpCheck < Check
|
|
9
|
+
def initialize(url:, expected_status: 200)
|
|
10
|
+
@url = url
|
|
11
|
+
@expected_status = expected_status
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
measure do
|
|
16
|
+
response = Net::HTTP.get_response(URI.parse(@url))
|
|
17
|
+
code = response.code.to_i
|
|
18
|
+
return fail_with("HTTP GET #{@url} returned #{code}, expected #{@expected_status}") if code != @expected_status
|
|
19
|
+
end
|
|
20
|
+
pass
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
fail_with(e.message)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module RailsHealthChecks
|
|
6
|
+
module Checks
|
|
7
|
+
class MemoryCheck < Check
|
|
8
|
+
def initialize(threshold: nil)
|
|
9
|
+
@threshold = threshold
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
rss = nil
|
|
14
|
+
measure { rss = rss_bytes }
|
|
15
|
+
|
|
16
|
+
if @threshold && rss > @threshold
|
|
17
|
+
return warn_with("process RSS #{rss} bytes exceeds threshold #{@threshold} bytes")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
pass
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
fail_with(e.message)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def rss_bytes
|
|
28
|
+
out, _, status = Open3.capture3("ps", "-o", "rss=", "-p", Process.pid.to_s)
|
|
29
|
+
raise "ps command failed with exit status #{status.exitstatus}" unless status.success?
|
|
30
|
+
|
|
31
|
+
out.strip.to_i * 1024
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsHealthChecks
|
|
4
|
+
module Checks
|
|
5
|
+
class ResqueCheck < Check
|
|
6
|
+
def initialize(queue_size: nil)
|
|
7
|
+
unless defined?(::Resque)
|
|
8
|
+
raise LoadError, "Resque is not installed. Add `gem 'resque'` to your Gemfile to use the :resque check."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
@queue_size = queue_size
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
measure { ::Resque.redis.ping }
|
|
16
|
+
|
|
17
|
+
if @queue_size
|
|
18
|
+
depth = ::Resque.queues.sum { |q| ::Resque.size(q) }
|
|
19
|
+
return warn_with("queue depth #{depth} exceeds threshold #{@queue_size}") if depth > @queue_size
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
fail_with(e.message)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsHealthChecks
|
|
4
|
+
module Checks
|
|
5
|
+
class SidekiqCheck < Check
|
|
6
|
+
def initialize(queue_size: nil)
|
|
7
|
+
unless defined?(::Sidekiq)
|
|
8
|
+
raise LoadError, "Sidekiq is not installed. Add `gem 'sidekiq'` to your Gemfile to use the :sidekiq check."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
@queue_size = queue_size
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
measure { ::Sidekiq.redis { |conn| conn.ping } }
|
|
16
|
+
|
|
17
|
+
if @queue_size
|
|
18
|
+
depth = ::Sidekiq::Queue.all.sum(&:size)
|
|
19
|
+
return warn_with("queue depth #{depth} exceeds threshold #{@queue_size}") if depth > @queue_size
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
fail_with(e.message)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsHealthChecks
|
|
4
|
+
module Checks
|
|
5
|
+
class SolidQueueCheck < Check
|
|
6
|
+
def initialize(job_count: nil)
|
|
7
|
+
unless defined?(::SolidQueue)
|
|
8
|
+
raise LoadError, "SolidQueue is not installed. Add `gem 'solid_queue'` to your Gemfile to use the :solid_queue check."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
@job_count = job_count
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
measure do
|
|
16
|
+
pending = ::SolidQueue::ReadyExecution.count
|
|
17
|
+
if @job_count && pending > @job_count
|
|
18
|
+
return warn_with("pending job count #{pending} exceeds threshold #{@job_count}")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
pass
|
|
22
|
+
rescue StandardError => e
|
|
23
|
+
fail_with(e.message)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module RailsHealthChecks
|
|
4
4
|
class Configuration
|
|
5
|
-
attr_accessor :checks, :timeout, :allowed_ips, :token
|
|
5
|
+
attr_accessor :checks, :timeout, :allowed_ips, :token, :sidekiq_queue_size, :solid_queue_job_count, :good_job_latency,
|
|
6
|
+
:resque_queue_size, :disk_warn_threshold, :disk_critical_threshold, :disk_path,
|
|
7
|
+
:memory_threshold, :http_url, :http_expected_status
|
|
6
8
|
attr_reader :authenticate_block
|
|
7
9
|
|
|
8
10
|
def initialize
|
|
@@ -11,6 +13,16 @@ module RailsHealthChecks
|
|
|
11
13
|
@allowed_ips = nil
|
|
12
14
|
@token = nil
|
|
13
15
|
@authenticate_block = nil
|
|
16
|
+
@sidekiq_queue_size = nil
|
|
17
|
+
@solid_queue_job_count = nil
|
|
18
|
+
@good_job_latency = nil
|
|
19
|
+
@resque_queue_size = nil
|
|
20
|
+
@disk_warn_threshold = nil
|
|
21
|
+
@disk_critical_threshold = nil
|
|
22
|
+
@disk_path = "/"
|
|
23
|
+
@memory_threshold = nil
|
|
24
|
+
@http_url = nil
|
|
25
|
+
@http_expected_status = 200
|
|
14
26
|
end
|
|
15
27
|
|
|
16
28
|
def authenticate(&block)
|
data/lib/rails_health_checks.rb
CHANGED
|
@@ -6,6 +6,14 @@ require "rails_health_checks/configuration"
|
|
|
6
6
|
require "rails_health_checks/authentication"
|
|
7
7
|
require "rails_health_checks/check"
|
|
8
8
|
require "rails_health_checks/checks/database_check"
|
|
9
|
+
require "rails_health_checks/checks/cache_check"
|
|
10
|
+
require "rails_health_checks/checks/sidekiq_check"
|
|
11
|
+
require "rails_health_checks/checks/solid_queue_check"
|
|
12
|
+
require "rails_health_checks/checks/good_job_check"
|
|
13
|
+
require "rails_health_checks/checks/resque_check"
|
|
14
|
+
require "rails_health_checks/checks/disk_check"
|
|
15
|
+
require "rails_health_checks/checks/memory_check"
|
|
16
|
+
require "rails_health_checks/checks/http_check"
|
|
9
17
|
require "rails_health_checks/check_registry"
|
|
10
18
|
require "rails_health_checks/response_builder"
|
|
11
19
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_health_checks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -44,7 +44,15 @@ files:
|
|
|
44
44
|
- lib/rails_health_checks/authentication.rb
|
|
45
45
|
- lib/rails_health_checks/check.rb
|
|
46
46
|
- lib/rails_health_checks/check_registry.rb
|
|
47
|
+
- lib/rails_health_checks/checks/cache_check.rb
|
|
47
48
|
- lib/rails_health_checks/checks/database_check.rb
|
|
49
|
+
- lib/rails_health_checks/checks/disk_check.rb
|
|
50
|
+
- lib/rails_health_checks/checks/good_job_check.rb
|
|
51
|
+
- lib/rails_health_checks/checks/http_check.rb
|
|
52
|
+
- lib/rails_health_checks/checks/memory_check.rb
|
|
53
|
+
- lib/rails_health_checks/checks/resque_check.rb
|
|
54
|
+
- lib/rails_health_checks/checks/sidekiq_check.rb
|
|
55
|
+
- lib/rails_health_checks/checks/solid_queue_check.rb
|
|
48
56
|
- lib/rails_health_checks/configuration.rb
|
|
49
57
|
- lib/rails_health_checks/engine.rb
|
|
50
58
|
- lib/rails_health_checks/response_builder.rb
|