mirror of
https://github.com/jimeh/redistat.git
synced 2026-02-19 05:16:39 +00:00
Merge branch 'master' into key-expiry
This commit is contained in:
40
README.md
40
README.md
@@ -1,4 +1,4 @@
|
||||
# Redistat #
|
||||
# Redistat [](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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user