diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 0fc7cc8..c473f5f 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,4 +1,6 @@ #### Unreleased +* Add a check for SolidCache + > stevenchanin & sidk: https://github.com/emmahsax/okcomputer/pull/15 * Update MIT license copyright holder > emmahsax: https://github.com/emmahsax/okcomputer/pull/10 * Added `bin/release` script to make it easier to make new version bumps and releases without other tools like soyuz or octopolo diff --git a/README.markdown b/README.markdown index 4478b56..1d99fa4 100644 --- a/README.markdown +++ b/README.markdown @@ -137,6 +137,9 @@ OkComputer::Registry.register "resque_backed_up", OkComputer::ResqueBackedUpChec # This check works on 2.4.0 and above versions of resque-scheduler OkComputer::Registry.register "resque_scheduler_down", OkComputer::ResqueSchedulerCheck.new + +# If you're using SolidCache instead of Memcached, use this check instead of CacheCheck +OkComputer::Registry.register "cache", OkComputer::CacheCheckSolidCache.new ``` ### Registering Custom Checks diff --git a/lib/ok_computer/built_in_checks/cache_check_solid_cache.rb b/lib/ok_computer/built_in_checks/cache_check_solid_cache.rb new file mode 100644 index 0000000..2f15d53 --- /dev/null +++ b/lib/ok_computer/built_in_checks/cache_check_solid_cache.rb @@ -0,0 +1,49 @@ +module OkComputer + # Verifies that Rails cache is set up and can speak with SolidCache + class CacheCheckSolidCache < Check + # Public: Check whether the cache is active + def check + mark_message "Cache is available (#{stats})" + rescue ConnectionFailed => e + mark_failure + mark_message "Error: '#{e}'" + end + + # Public: Outputs stats string for cache + def stats + return "" unless Rails.cache.respond_to? :stats + + stats = Rails.cache.stats + connection_stats = stats[:connection_stats][SolidCache::Entry.current_shard] + + max_entries = connection_stats[:max_entries] + current_entries = connection_stats[:entries] + max_age = connection_stats[:max_age] + oldest_age = connection_stats[:oldest_age] + + age_text = if oldest_age + "oldest: #{format_duration(oldest_age)}, max: #{format_duration(max_age)}" + else + "no entries" + end + + "entries: #{current_entries}/#{max_entries}, #{age_text}, #{stats[:connections]} connections" + rescue => e + raise ConnectionFailed, e + end + + private + + def format_duration(seconds) + if seconds < 60 + "#{seconds.round}s" + elsif seconds < 3600 + "#{(seconds / 60).round}m" + else + "#{(seconds / 3600).round}h" + end + end + + ConnectionFailed = Class.new(StandardError) + end +end diff --git a/lib/okcomputer.rb b/lib/okcomputer.rb index affddd6..dbf959d 100644 --- a/lib/okcomputer.rb +++ b/lib/okcomputer.rb @@ -15,6 +15,7 @@ require "ok_computer/built_in_checks/active_record_migrations_check" require "ok_computer/built_in_checks/app_version_check" require "ok_computer/built_in_checks/cache_check" +require "ok_computer/built_in_checks/cache_check_solid_cache" require "ok_computer/built_in_checks/default_check" require "ok_computer/built_in_checks/delayed_job_backed_up_check" require "ok_computer/built_in_checks/directory_check" diff --git a/spec/ok_computer/built_in_checks/cache_check_solid_cache_spec.rb b/spec/ok_computer/built_in_checks/cache_check_solid_cache_spec.rb new file mode 100644 index 0000000..0be2ab6 --- /dev/null +++ b/spec/ok_computer/built_in_checks/cache_check_solid_cache_spec.rb @@ -0,0 +1,105 @@ +require "rails_helper" + +module OkComputer + describe CacheCheckSolidCache do + let(:stats) do + { + connections: 2, + connection_stats: { + "shard1" => { + max_age: 3600, + oldest_age: 1800, + max_entries: 1000, + entries: 500 + } + } + } + end + + before do + stub_const("SolidCache::Entry", Class.new) + allow(SolidCache::Entry).to receive(:current_shard).and_return("shard1") + end + + it "is a Check" do + expect(subject).to be_a Check + end + + context "#check" do + let(:error_message) { "Error message" } + + context "with a successful connection" do + before do + expect(subject).to receive(:stats).and_return("test stats") + end + + it { is_expected.to be_successful_check } + it { is_expected.to have_message "Cache is available (test stats)" } + end + + context "with an unsuccessful connection" do + before do + expect(subject).to receive(:stats).and_raise(CacheCheckSolidCache::ConnectionFailed, error_message) + end + + it { is_expected.not_to be_successful_check } + it { is_expected.to have_message "Error: '#{error_message}'" } + end + end + + context "#stats" do + context "when can connect to cache" do + before do + allow(Rails).to receive_message_chain(:cache, :stats).and_return(stats) + end + + it "returns a formatted stats string" do + expect(subject.stats).to eq "entries: 500/1000, oldest: 30m, max: 1h, 2 connections" + end + end + + context "when no entries exist" do + before do + stats[:connection_stats]["shard1"][:oldest_age] = nil + allow(Rails).to receive_message_chain(:cache, :stats).and_return(stats) + end + + it "indicates no entries exist" do + expect(subject.stats).to eq "entries: 500/1000, no entries, 2 connections" + end + end + + context "when cannot connect to cache" do + before do + allow(Rails).to receive_message_chain(:cache, :stats).and_raise("broken") + end + + it { expect { subject.stats }.to raise_error(CacheCheckSolidCache::ConnectionFailed) } + end + + context "when using a cache without stats" do + before do + allow(Rails.cache).to receive(:respond_to?).with(:stats).and_return(false) + end + + it "returns an empty string" do + expect(subject.stats).to eq "" + end + end + end + + context "#format_duration" do + it "formats seconds" do + expect(subject.send(:format_duration, 45)).to eq "45s" + end + + it "formats minutes" do + expect(subject.send(:format_duration, 180)).to eq "3m" + end + + it "formats hours" do + expect(subject.send(:format_duration, 7200)).to eq "2h" + end + end + end +end