Merge branch 'master' into key-expiry

This commit is contained in:
2012-04-18 11:41:07 +01:00
5 changed files with 142 additions and 73 deletions

View File

@@ -1,4 +1,4 @@
# Redistat #
# Redistat [![Build Status](https://secure.travis-ci.org/jimeh/redistat.png)](http://travis-ci.org/jimeh/redistat)
A Redis-backed statistics storage and querying library written in Ruby.
@@ -15,14 +15,14 @@ like increment, and it's lightning fast, meaning if the data is structured
well, the initial stats reporting call will store data in a format that's
instantly retrievable just as fast.
## Installation ##
## Installation
gem install redistat
If you are using Ruby 1.8.x, it's recommended you also install the
`SystemTimer` gem, as the Redis gem will otherwise complain.
## Usage (Crash Course) ##
## Usage (Crash Course)
view\_stats.rb:
@@ -39,7 +39,7 @@ Redistat.thread_safe = true
```
### Simple Example ###
### Simple Example
Store:
@@ -60,7 +60,7 @@ ViewStats.find('hello', 3.hour.ago, 1.hour.from_now).total
```
### Advanced Example ###
### Advanced Example
Store page view on product #44 from Chrome 11:
@@ -131,16 +131,16 @@ finder.children.map { |child| child.total }
```
## Terminology ##
## Terminology
### Scope ###
### Scope
A type of global-namespace for storing data. When using the `Redistat::Model`
wrapper, the scope is automatically set to the class name. In the examples
above, the scope is `ViewStats`. Can be overridden by calling the `#scope`
class method on your model class.
### Label ###
### Label
Identifier string to separate different types and groups of statistics from
each other. The first argument of the `#store`, `#find`, and `#fetch` methods
@@ -156,7 +156,7 @@ causes twice as many `hincrby` calls to Redis as not using the grouping
feature. Hence using 10 grouping levels, causes 10 times as many write calls
to Redis.
### Input Statistics Data ###
### Input Statistics Data
You provide Redistat with the data you want to store using a Ruby Hash. This
data is then stored in a corresponding Redis hash with identical key/field
@@ -166,7 +166,7 @@ Key names in the hash also support grouping features similar to those
available for Labels. Again, the more levels you use, the more write calls to
Redis, so avoid using 10-15 levels.
### Depth (Storage Accuracy) ###
### Depth (Storage Accuracy)
Define how accurately data should be stored, and how accurately it's looked up
when fetching it again. By default Redistat uses a depth value of `:hour`,
@@ -176,7 +176,7 @@ and 10:23. In Redis they are both stored within a date key of `2011031610`.
You can set depth within your model using the `#depth` class method. Available
depths are: `:year`, `:month`, `:day`, `:hour`, `:min`, `:sec`
### Time Ranges ###
### Time Ranges
When you fetch data, you need to specify a start and an end time. The
selection behavior can seem a bit weird at first when, but makes sense when
@@ -189,7 +189,7 @@ key for the 13th hour. If both 13:00 and 14:00 was returned, you would get
results from two whole hours. Hence if you want up to the second data, use an
end time of `1.hour.from_now`.
### The Finder Object ###
### The Finder Object
Calling the `#find` method on a Redistat model class returns a
`Redistat::Finder` object. The finder is a lazy-loaded gateway to your
@@ -203,9 +203,9 @@ comes to the finder.
## Internals ##
## Internals
### Storing / Writing ###
### Storing / Writing
Redistat stores all data into a Redis hash keys. The Redis key name the used
consists of three parts. The scope, label, and datetime:
@@ -258,7 +258,7 @@ accurate. 39 calls is however not a problem for Redis, most calls happen in
less than 0.15ms (0.00015 seconds) on my local machine.
### Fetching / Reading ###
### Fetching / Reading
By default when fetching statistics, Redistat will figure out how to do the
least number of reads from Redis. First it checks how long range you're
@@ -271,7 +271,7 @@ instead it would fetch the data for the whole month and the first two days,
which are then removed from the summary of the whole month. This means three
calls to `hgetall` instead of 29 if each whole day was fetched.
### Buffer ###
### Buffer
The buffer is a new, still semi-beta, feature aimed to reduce the number of
Redis `hincrby` that Redistat sends. This should only really be useful when
@@ -287,21 +287,21 @@ Redistat how many `store` calls to buffer in memory before writing all data to
Redis.
## Todo ##
## Todo
* More details in Readme.
* Documentation.
* Anything else that becomes apparent after real-world use.
## Credits ##
## Credits
[Global Personals](http://globalpersonals.co.uk/) deserves a thank
you. Currently the primary user of Redistat, they've allowed me to spend some
company time to further develop the project.
## Note on Patches/Pull Requests ##
## Note on Patches/Pull Requests
* Fork the project.
* Make your feature addition or bug fix.
@@ -313,7 +313,7 @@ company time to further develop the project.
* Send me a pull request. Bonus points for topic branches.
## License and Copyright ##
## License and Copyright
Copyright (c) 2011 Jim Myhrberg.

View File

@@ -14,7 +14,8 @@ module Redistat
def thread_safe
monitor.synchronize do
@thread_safe ||= false
return @thread_safe unless @thread_safe.nil?
@thread_safe = false
end
end

View File

@@ -27,4 +27,5 @@ Gem::Specification.new do |s|
s.add_development_dependency 'rake', '>= 0.8.7'
s.add_development_dependency 'rspec', '>= 2.1.0'
s.add_development_dependency 'yard', '>= 0.6.3'
s.add_development_dependency 'simplecov', '>= 0.6.1'
end

View File

@@ -2,6 +2,12 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'simplecov'
SimpleCov.start do
add_filter '/spec'
add_filter '/vendor'
end
# require stuff
require 'redistat'
require 'rspec'

View File

@@ -1,63 +1,124 @@
require "spec_helper"
describe Redistat::Synchronize do
it { should respond_to(:monitor) }
it { should respond_to(:thread_safe) }
it { should respond_to(:thread_safe=) }
module Redistat
describe Synchronize do
describe "instanciated class with Redistat::Synchronize included" do
subject { SynchronizeSpecHelper.new }
it { should respond_to(:monitor) }
it { should respond_to(:thread_safe) }
it { should respond_to(:thread_safe=) }
it { should respond_to(:synchronize) }
let(:klass) { Synchronize }
end
describe '.included' do
it 'includes InstanceMethods in passed object' do
base = mock('Base')
base.should_receive(:include).with(klass::InstanceMethods)
klass.included(base)
end
end # included
describe "#synchronize method" do
describe '.monitor' do
it 'returns a Monitor instance' do
klass.monitor.should be_a(Monitor)
end
it 'caches Monitor instance' do
klass.monitor.object_id.should == klass.monitor.object_id
end
end # monitor
describe '.thread_safe' do
after { klass.instance_variable_set('@thread_safe', nil) }
it 'returns value of @thread_safe' do
klass.instance_variable_set('@thread_safe', true)
klass.thread_safe.should be_true
end
it 'defaults to false' do
klass.thread_safe.should be_false
end
it 'uses #synchronize' do
klass.monitor.should_receive(:synchronize).once
klass.thread_safe.should be_nil
end
end # thread_safe
describe '.thread_safe=' do
after { klass.instance_variable_set('@thread_safe', nil) }
it 'sets @thread_safe' do
klass.instance_variable_get('@thread_safe').should be_nil
klass.thread_safe = true
klass.instance_variable_get('@thread_safe').should be_true
end
it 'uses #synchronize' do
klass.monitor.should_receive(:synchronize).once
klass.thread_safe = true
klass.instance_variable_get('@thread_safe').should be_nil
end
end # thread_safe=
describe "InstanceMethods" do
subject { SynchronizeSpecHelper.new }
describe '.monitor' do
it 'defers to Redistat::Synchronize' do
klass.should_receive(:monitor).once
subject.monitor
end
end # monitor
describe '.thread_safe' do
it ' defers to Redistat::Synchronize' do
klass.should_receive(:thread_safe).once
subject.thread_safe
end
end # thread_safe
describe '.thread_safe=' do
it 'defers to Redistat::Synchronize' do
klass.should_receive(:thread_safe=).once.with(true)
subject.thread_safe = true
end
end # thread_safe=
describe 'when #thread_safe is true' do
before { subject.stub(:thread_safe).and_return(true) }
describe '.synchronize' do
it 'defers to #monitor' do
subject.monitor.should_receive(:synchronize).once
subject.synchronize { 'foo' }
end
it 'passes block along to #monitor.synchronize' do
yielded = false
subject.synchronize { yielded = true }
yielded.should be_true
end
end # synchronize
end # when #thread_safe is true
describe 'when #thread_safe is false' do
before { subject.stub(:thread_safe).and_return(false) }
describe '.synchronize' do
it 'does not defer to #monitor' do
subject.monitor.should_not_receive(:synchronize)
subject.synchronize { 'foo' }
end
it 'yields block' do
yielded = false
subject.synchronize { yielded = true }
yielded.should be_true
end
end # synchronize
end # when #thread_safe is false
before(:each) do
Redistat::Synchronize.instance_variable_set("@thread_safe", nil)
@obj = SynchronizeSpecHelper.new
end
it "should share single Monitor object across all objects" do
@obj.monitor.should == Redistat::Synchronize.monitor
end
it "should share thread_safe option across all objects" do
obj2 = SynchronizeSpecHelper.new
Redistat::Synchronize.thread_safe.should be_false
@obj.thread_safe.should be_false
obj2.thread_safe.should be_false
@obj.thread_safe = true
Redistat::Synchronize.thread_safe.should be_true
@obj.thread_safe.should be_true
obj2.thread_safe.should be_true
end
it "should not synchronize when thread_safe is disabled" do
# monitor receives :synchronize twice cause #thread_safe is _always_ synchronized
Redistat::Synchronize.monitor.should_receive(:synchronize).twice
@obj.thread_safe.should be_false # first #synchronize call
@obj.synchronize { 'foo' } # one #synchronize call while checking #thread_safe
end
it "should synchronize when thread_safe is enabled" do
Monitor.class_eval {
# we're stubbing synchronize to ensure it's being called correctly, but still need it :P
alias :real_synchronize :synchronize
}
Redistat::Synchronize.monitor.should_receive(:synchronize).with.exactly(4).times.and_return { |block|
Redistat::Synchronize.monitor.real_synchronize(&block)
}
@obj.thread_safe.should be_false # first synchronize call
Redistat::Synchronize.thread_safe = true # second synchronize call
@obj.synchronize { 'foo' } # two synchronize calls, once while checking thread_safe, once to call black
end
end
end
end # Synchronize
end # Redistat
class SynchronizeSpecHelper
include Redistat::Synchronize