lib/breakers/service.rb in breakers-0.1.0 vs lib/breakers/service.rb in breakers-0.1.1
- old
+ new
@@ -1,56 +1,110 @@
module Breakers
+ # A service defines a backend system that your application relies upon. This class
+ # allows you to configure the outage detection for a service as well as to define which
+ # requests belong to it.
class Service
DEFAULT_OPTS = {
seconds_before_retry: 60,
error_threshold: 50,
data_retention_seconds: 60 * 60 * 24 * 30
}.freeze
+ # Create a new service
+ #
+ # @param [Hash] opts the options to create the service with
+ # @option opts [String] :name The name of the service for reporting and logging purposes
+ # @option opts [Proc] :request_matcher A proc taking a Faraday::Env as an argument that returns true if the service handles that request
+ # @option opts [Integer] :seconds_before_retry The number of seconds to wait after an outage begins before testing with a new request
+ # @option opts [Integer] :error_threshold The percentage of errors over the last two minutes that indicates an outage
+ # @option opts [Integer] :data_retention_seconds The number of seconds to retain success and error data in Redis
def initialize(opts)
@configuration = DEFAULT_OPTS.merge(opts)
end
+ # Get the name of the service
+ #
+ # @return [String] the name
def name
@configuration[:name]
end
- def handles_request?(request_env)
+ # Given a Faraday::Env, return true if this service handles the request, via its matcher
+ #
+ # @param request_env [Faraday::Env] the request environment
+ # @return [Boolean] should the service handle the request
+ def handles_request?(request_env:)
@configuration[:request_matcher].call(request_env)
end
+ # Get the seconds before retry parameter
+ #
+ # @return [Integer] the value
def seconds_before_retry
@configuration[:seconds_before_retry]
end
+ # Indicate that an error has occurred and potentially create an outage
def add_error
increment_key(key: errors_key)
maybe_create_outage
end
+ # Indicate that a successful response has occurred
def add_success
increment_key(key: successes_key)
end
- def last_outage
- Outage.find_last(service: self)
+ # Force an outage to begin on the service. Forced outages are not periodically retested.
+ def begin_forced_outage!
+ Outage.create(service: self, forced: true)
end
+ # End a forced outage on the service.
+ def end_forced_outage!
+ latest = Outage.find_latest(service: self)
+ if latest.forced?
+ latest.end!
+ end
+ end
+
+ # Return the most recent outage on the service
+ def latest_outage
+ Outage.find_latest(service: self)
+ end
+
+ # Return a list of all outages in the given time range
+ #
+ # @param start_time [Time] the beginning of the range
+ # @param end_time [Time] the end of the range
+ # @return [Array[Outage]] a list of outages that began in the range
def outages_in_range(start_time:, end_time:)
Outage.in_range(
service: self,
start_time: start_time,
end_time: end_time
)
end
- def successes_in_range(start_time:, end_time:, sample_seconds: 3600)
- values_in_range(start_time: start_time, end_time: end_time, type: :successes, sample_seconds: sample_seconds)
+ # Return data about the successful request counts in the time range
+ #
+ # @param start_time [Time] the beginning of the range
+ # @param end_time [Time] the end of the range
+ # @param sample_minutes [Integer] the rate at which to sample the data
+ # @return [Array[Hash]] a list of hashes in the form: { count: Integer, time: Unix Timestamp }
+ def successes_in_range(start_time:, end_time:, sample_minutes: 60)
+ values_in_range(start_time: start_time, end_time: end_time, type: :successes, sample_minutes: sample_minutes)
end
- def errors_in_range(start_time:, end_time:, sample_seconds: 3600)
- values_in_range(start_time: start_time, end_time: end_time, type: :errors, sample_seconds: sample_seconds)
+ # Return data about the failed request counts in the time range
+ #
+ # @param start_time [Time] the beginning of the range
+ # @param end_time [Time] the end of the range
+ # @param sample_minutes [Integer] the rate at which to sample the data
+ # @return [Array[Hash]] a list of hashes in the form: { count: Integer, time: Unix Timestamp }
+ def errors_in_range(start_time:, end_time:, sample_minutes: 60)
+ values_in_range(start_time: start_time, end_time: end_time, type: :errors, sample_minutes: sample_minutes)
end
protected
def errors_key(time: nil)
@@ -59,11 +113,11 @@
def successes_key(time: nil)
"#{Breakers.redis_prefix}#{name}-successes-#{align_time_on_minute(time: time).to_i}"
end
- def values_in_range(start_time:, end_time:, type:, sample_seconds:)
+ def values_in_range(start_time:, end_time:, type:, sample_minutes:)
start_time = align_time_on_minute(time: start_time)
end_time = align_time_on_minute(time: end_time)
keys = []
times = []
while start_time <= end_time
@@ -71,10 +125,10 @@
if type == :errors
keys << errors_key(time: start_time)
elsif type == :successes
keys << successes_key(time: start_time)
end
- start_time += sample_seconds
+ start_time += sample_minutes * 60
end
Breakers.client.redis_connection.mget(keys).each_with_index.map do |value, idx|
{ count: value.to_i, time: times[idx] }
end
end