Skip to content

Commit

Permalink
Add Sidekiq queue size and queue latency checks
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasleese committed Jul 12, 2018
1 parent 0f9c330 commit 671596b
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 2 deletions.
37 changes: 35 additions & 2 deletions docs/healthchecks.md
Expand Up @@ -71,8 +71,7 @@ This checks that the app has a connection to the database via ActiveRecord.
### `ThresholdCheck`

This class is the basis for a check which compares a value with a warning or a
critical threshold. To implement this kind of check in your application, you
can inherit from the class.
critical threshold.

```ruby
class MyThresholdCheck < GovukHealthcheck::ThresholdCheck
Expand All @@ -98,3 +97,37 @@ class MyThresholdCheck < GovukHealthcheck::ThresholdCheck
end
end
```

### `SidekiqQueueLatencyCheck`

This class is the basis for a check which compares the Sidekiq queue latencies
with warning or critical thresholds.

```ruby
class MySidekiqQueueLatencyCheck < GovukHealthcheck::SidekiqQueueLatencyCheck
def warning_threshold(queue:)
# the warning threshold for a particular queue
end

def critical_threshold(queue:)
# the critical threshold for a particular queue
end
end
```

### `SidekiqQueueSizeCheck`

This class is the basis for a check which compares the Sidekiq queue sizes
with warning or critical thresholds.

```ruby
class MySidekiqQueueSizeCheck < GovukHealthcheck::SidekiqQueueSizeCheck
def warning_threshold(queue:)
# the warning threshold for a particular queue
end

def critical_threshold(queue:)
# the critical threshold for a particular queue
end
end
```
3 changes: 3 additions & 0 deletions lib/govuk_app_config/govuk_healthcheck.rb
@@ -1,6 +1,9 @@
require "govuk_app_config/govuk_healthcheck/checkup"
require "govuk_app_config/govuk_healthcheck/active_record"
require "govuk_app_config/govuk_healthcheck/sidekiq_redis"
require "govuk_app_config/govuk_healthcheck/sidekiq_queue_check"
require "govuk_app_config/govuk_healthcheck/sidekiq_queue_latency_check"
require "govuk_app_config/govuk_healthcheck/sidekiq_queue_size_check"
require "govuk_app_config/govuk_healthcheck/threshold_check"
require "json"

Expand Down
62 changes: 62 additions & 0 deletions lib/govuk_app_config/govuk_healthcheck/sidekiq_queue_check.rb
@@ -0,0 +1,62 @@
module GovukHealthcheck
class SidekiqQueueCheck
def status
queues.each do |name, value|
if value >= critical_threshold(queue: name)
return :critical
elsif value >= warning_threshold(queue: name)
return :warning
end
end

:ok
end

def message
messages = queues.map do |name, value|
critical = critical_threshold(queue: name)
warning = warning_threshold(queue: name)

if value >= critical
"#{name} (#{value}) is above the critical threshold (#{critical})"
elsif value >= warning
"#{name} (#{value}) is above the warning threshold (#{warning})"
end
end

messages = messages.compact

if messages.empty?
"all queues are below the critical and warning thresholds"
else
messages.join("\n")
end
end

def details
{
queues: queues.each_with_object({}) do |(name, value), hash|
hash[name] = {
value: value,
thresholds: {
critical: critical_threshold(queue: name),
warning: warning_threshold(queue: name),
},
}
end,
}
end

def queues
raise "This method must be overriden to be a hash of queue names and data."
end

def critical_threshold(queue:)
raise "This method must be overriden to be the critical threshold."
end

def warning_threshold(queue:)
raise "This method must be overriden to be the warning threshold."
end
end
end
@@ -0,0 +1,13 @@
module GovukHealthcheck
class SidekiqQueueLatencyCheck < SidekiqQueueCheck
def name
:sidekiq_queue_latency
end

def queues
@queues ||= Sidekiq::Stats.new.queues.keys.each_with_object({}) do |name, hash|
hash[name] = Sidekiq::Queue.new(name).latency
end
end
end
end
11 changes: 11 additions & 0 deletions lib/govuk_app_config/govuk_healthcheck/sidekiq_queue_size_check.rb
@@ -0,0 +1,11 @@
module GovukHealthcheck
class SidekiqQueueSizeCheck < SidekiqQueueCheck
def name
:sidekiq_queue_size
end

def queues
@queues ||= Sidekiq::Stats.new.queues
end
end
end
6 changes: 6 additions & 0 deletions spec/lib/govuk_healthcheck/shared_interface.rb
Expand Up @@ -17,4 +17,10 @@
expect(healthcheck.details).not_to have_key(:status)
end
end

it "optionally returns a `message` string" do
if healthcheck.respond_to?(:message)
expect(healthcheck.message).to be_a(String)
end
end
end
72 changes: 72 additions & 0 deletions spec/lib/govuk_healthcheck/sidekiq_queue_check_spec.rb
@@ -0,0 +1,72 @@
require "spec_helper"
require "govuk_app_config/govuk_healthcheck"
require_relative "shared_interface"

RSpec.describe GovukHealthcheck::SidekiqQueueCheck do
context "an ok check" do
subject { TestQueueCheck.new({ queue: 0 }, 10, 20) }

it_behaves_like "a healthcheck"

its(:status) { is_expected.to eq(:ok) }
its(:message) { is_expected.to match(/below the critical and warning thresholds/) }
its(:details) do
is_expected.to match(
queues: {
queue: hash_including(value: 0, thresholds: { warning: 10, critical: 20 })
}
)
end
end

context "a warning check" do
subject { TestQueueCheck.new({ queue: 11 }, 10, 20) }

it_behaves_like "a healthcheck"

its(:status) { is_expected.to eq(:warning) }
its(:message) { is_expected.to match(/above the warning threshold/) }
its(:details) do
is_expected.to match(
queues: {
queue: hash_including(value: 11, thresholds: { warning: 10, critical: 20 })
}
)
end
end

context "a critical check" do
subject { TestQueueCheck.new({ queue: 21 }, 10, 20) }

it_behaves_like "a healthcheck"

its(:status) { is_expected.to eq(:critical) }
its(:message) { is_expected.to match(/above the critical threshold/) }
its(:details) do
is_expected.to match(
queues: {
queue: hash_including(value: 21, thresholds: { warning: 10, critical: 20 })
}
)
end
end

class TestQueueCheck < GovukHealthcheck::SidekiqQueueCheck
def initialize(queues, warning_threshold, critical_threshold, name = :test)
@queues = queues
@warning_threshold = warning_threshold
@critical_threshold = critical_threshold
@name = name
end

attr_reader :queues, :name

def warning_threshold(queue:)
@warning_threshold
end

def critical_threshold(queue:)
@critical_threshold
end
end
end
33 changes: 33 additions & 0 deletions spec/lib/govuk_healthcheck/sidekiq_queue_latency_check_spec.rb
@@ -0,0 +1,33 @@
require "spec_helper"
require "govuk_app_config/govuk_healthcheck"
require_relative "shared_interface"

RSpec.describe GovukHealthcheck::SidekiqQueueLatencyCheck do
subject { TestQueueLatencyCheck.new }

let(:sidekiq_stats) { double(queues: { test: 10 }) }
let(:sidekiq_queue) { double(latency: 5) }

let(:sidekiq_stats_class) { double }
let(:sidekiq_queue_class) { double }

before do
allow(sidekiq_stats_class).to receive(:new).and_return(sidekiq_stats)
allow(sidekiq_queue_class).to receive(:new).with(:test).and_return(sidekiq_queue)

stub_const("Sidekiq::Stats", sidekiq_stats_class)
stub_const("Sidekiq::Queue", sidekiq_queue_class)
end

it_behaves_like "a healthcheck"

class TestQueueLatencyCheck < GovukHealthcheck::SidekiqQueueLatencyCheck
def warning_threshold(queue:)
10
end

def critical_threshold(queue:)
20
end
end
end
27 changes: 27 additions & 0 deletions spec/lib/govuk_healthcheck/sidekiq_queue_size_check_spec.rb
@@ -0,0 +1,27 @@
require "spec_helper"
require "govuk_app_config/govuk_healthcheck"
require_relative "shared_interface"

RSpec.describe GovukHealthcheck::SidekiqQueueSizeCheck do
subject { TestQueueSizeCheck.new }

let(:sidekiq_stats) { double(queues: { test: 10 }) }
let(:sidekiq_stats_class) { double }

before do
allow(sidekiq_stats_class).to receive(:new).and_return(sidekiq_stats)
stub_const("Sidekiq::Stats", sidekiq_stats_class)
end

it_behaves_like "a healthcheck"

class TestQueueSizeCheck < GovukHealthcheck::SidekiqQueueSizeCheck
def warning_threshold(queue:)
100
end

def critical_threshold(queue:)
200
end
end
end

0 comments on commit 671596b

Please sign in to comment.