Permalink
Browse files

Use Redis to store counters

  • Loading branch information...
twinturbo
twinturbo committed Apr 10, 2012
1 parent 4f9ac36 commit 2e4c4e90efe48fb522e4fe026589e1a06df17206
View
@@ -1,7 +1,8 @@
# Harness
Harness connects measurements coming from `ActiveSupport::Notifications`
-to external metric tracking services.
+to external metric tracking services. Counters are stored locally with
+redis before being sent to the service.
Currently Supported Services:
@@ -53,12 +54,18 @@ class MyClass
end
```
-You can do the same with a counter
+You can do the same with a counter. Counter values are automatically
+stored in redis and incremented. This means you can simply pass
+`:counter => true` in instrumentations if you'd like to count it. You
+may also pass `:counter => 5` if you'd like to provide your own value.
+This value is stored in redis so the next time `:counter => true` will
+work correctly. You can reset all the counters back to zero by calling:
+`Harness.reset_counters!`.
```ruby
class MyClass
def important_method(stuff)
- ActiveSupport::Notifications.instrument "important_method.my_class", :counter => 11 do
+ ActiveSupport::Notifications.instrument "important_method.my_class", :counter => true do
do_important_stuff
end
end
@@ -123,6 +130,8 @@ Harness.config.adapter = :librato
Harness.config.librato.email = 'example@example.com'
Harness.config.librato.token = 'your-api-key'
+
+Harness.redis = Redis.new
```
## Rails Integration
@@ -138,6 +147,25 @@ config.librato.email = 'example@example.com'
config.librato.token = 'your-api-key'
```
+Redis will be automatically configured if you `REDISTOGO_URL` or
+`REDIS_URL` environment variables at set. They are wrapped in a
+namespace so there will be no conflicts. If they are not present, the
+default values are used. You can customize this in an initializer:
+
+```ruby
+# config/initializers/harness.rb
+require 'erb'
+
+file = Rails.root.join 'config', 'resque.yml'
+config = YAML.load(ERB.new(File.read(Rails.root.join('config', 'resque.yml'))).result)
+
+Harness.redis = Redis.new(:url => config[Rails.env])
+```
+
+`rake harness:reset_counters` is also added.
+
+### Rails Environments
+
Measurements are completely ignored in the test env. They are processed
in development mode, but not sent to the external service. Everything is
logged in production.
View
@@ -16,6 +16,8 @@ Gem::Specification.new do |gem|
gem.version = Harness::VERSION
gem.add_dependency "activesupport", "~> 3"
+ gem.add_dependency "redis"
+ gem.add_dependency "redis-namespace"
gem.add_development_dependency "simplecov"
gem.add_development_dependency "webmock"
View
@@ -4,6 +4,9 @@
require 'securerandom'
+require 'redis'
+require 'redis/namespace'
+
require 'active_support/notifications'
require 'active_support/core_ext/string'
@@ -63,6 +66,20 @@ def self.logger
def self.logger=(logger)
@logger = logger
end
+
+ def self.redis=(redis)
+ @redis = redis
+ end
+
+ def self.redis
+ @redis
+ end
+
+ def self.reset_counters!
+ redis.smembers('counters').each do |counter|
+ redis.set counter, -1
+ end
+ end
end
require 'harness/measurement'
View
@@ -9,10 +9,18 @@ def self.from_event(event)
counter.id ||= event.name
+ Harness.redis.sadd 'counters', counter.id
+
if event.payload[:counter].is_a? Fixnum
counter.value = event.payload[:counter]
end
+ if counter.value
+ Harness.redis.set counter.id, counter.value
+ else
+ counter.value = Harness.redis.incr(counter.id).to_i
+ end
+
counter
end
end
View
@@ -2,6 +2,10 @@ module Harness
class Railtie < ::Rails::Railtie
config.harness = Harness.config
+ rake_tasks do
+ load "harness/tasks.rake"
+ end
+
initializer "harness.thread" do
Thread.abort_on_exception = Rails.env.development? || Rails.env.test?
end
@@ -20,5 +24,13 @@ class Railtie < ::Rails::Railtie
initializer "harness.logger" do |app|
Harness.logger = Rails.logger
end
+
+ initializer "harness.redis" do
+ if existing_url = ENV['REDISTOGO_URL'] || ENV['REDIS_URL']
+ Harness.redis ||= Redis::Namespace.new('harness', :redis => Redis.connect(:url => existing_url))
+ else
+ Harness.redis ||= Redis::Namespace.new('harness', :redis => Redis.connect(:host => 'localhost', :port => '6379'))
+ end
+ end
end
end
View
@@ -0,0 +1,6 @@
+namespace :harness do
+ desc "Reset all counters back to zero"
+ task :reset_counters => :environment do
+ Harness.reset_counters!
+ end
+end
@@ -0,0 +1,69 @@
+require 'test_helper'
+
+class CountersWithRedis < IntegrationTest
+ def test_stores_name_in_redis
+ instrument "event-counter", :counter => true
+
+ assert_includes redis.smembers('counters'), 'event-counter'
+ assert_equal 1, redis.get('event-counter').to_i
+
+ assert_counter_logged "event-counter"
+ end
+
+ def test_increments_counter_each_instrument
+ instrument "event-counter", :counter => true
+ assert_counter_logged "event-counter"
+ counters.clear
+
+ assert_empty counters
+ instrument "event-counter", :counter => true
+ assert_counter_logged "event-counter"
+
+ assert_equal 2, counters.first.value
+ end
+
+ def test_sets_given_value_in_redis_with_shortform
+ instrument "event-counter", :counter => 10
+
+ assert_equal 10, redis.get("event-counter").to_i
+
+ counters.clear
+
+ instrument "event-counter", :counter => true
+ assert_counter_logged 'event-counter'
+
+ assert_equal 11, counters.first.value
+ end
+
+ def test_sets_given_value_in_redis_with_longform
+ instrument "event-counter", :counter => { :value => 10 }
+
+ assert_equal 10, redis.get("event-counter").to_i
+
+ counters.clear
+
+ instrument "event-counter", :counter => true
+ assert_counter_logged 'event-counter'
+
+ assert_equal 11, counters.first.value
+ end
+
+ def test_resets_counters
+ instrument "event-counter", :counter => true
+ instrument "event-counter2", :counter => true
+
+ assert_equal 1, redis.get("event-counter").to_i
+ assert_equal 1, redis.get("event-counter2").to_i
+
+ Harness.reset_counters!
+
+ assert_equal -1, redis.get("event-counter").to_i
+ assert_equal -1, redis.get("event-counter2").to_i
+
+ counters.clear
+ instrument "event-counter", :counter => true
+ assert_counter_logged 'event-counter'
+
+ assert_equal 0, counters.first.value
+ end
+end
View
@@ -17,10 +17,13 @@
Harness.logger = Logger.new '/dev/null'
+Harness.redis = Redis::Namespace.new 'harness-test', :redis => Redis.connect(:host => 'localhost', :port => '6379')
+
class IntegrationTest < MiniTest::Unit::TestCase
def setup
Harness.config.adapter = :memory
gauges.clear ; counters.clear
+ redis.flushall
end
def assert_gauge_logged(name)
@@ -38,4 +41,14 @@ def gauges
def counters
Harness::MemoryAdapter.counters
end
+
+ def redis
+ Harness.redis
+ end
+
+ def instrument(name, data = {})
+ ActiveSupport::Notifications.instrument name, data do
+ # nothing
+ end
+ end
end
@@ -3,9 +3,10 @@
class CounterTest < MiniTest::Unit::TestCase
def setup
@counter = Harness::Counter.new
+ Harness.redis.flushall
end
- def test_sets_name_from_event
+ def test_sets_id_from_event
event = ActiveSupport::Notifications::Event.new "name", Time.now, Time.now, nil, {}
counter = Harness::Counter.from_event event
@@ -50,4 +51,5 @@ def test_sets_name_from_event
assert_equal 'foo', counter.name
end
+
end

0 comments on commit 2e4c4e9

Please sign in to comment.