53 Commits

Author SHA1 Message Date
8710f4a51f Merge branch 'release/v0.1.0' 2011-03-04 17:41:39 +00:00
96e9b0a736 Version bump to 0.1.0 2011-03-04 17:41:14 +00:00
102fb41a6b Merge branch 'feature/grouping' into dev 2011-03-04 17:40:17 +00:00
b0a44a6abc some more sanity checks to Label spec 2011-03-04 17:40:02 +00:00
f8dfb034af added label grouping to Key and Summary classes 2011-03-04 17:39:51 +00:00
15904e8a94 added grouping support to Redistat::Label 2011-03-04 16:25:31 +00:00
fe221c3f31 added enable_grouping option to disable grouping features, enabled by default 2011-03-04 13:02:20 +00:00
7b1feda061 added key grouping for statistics Hash
Example:
store(“message”, {“count/private” => 1})
store(“message”, {“count/public” => 1})
fetch("message", 2.minutes.ago, Time.now)
  #=> { "count" => 2,
        "count/private" => 1,
        "count/public" => 1 }
2011-03-04 12:54:50 +00:00
968ef47ac5 fixed name of SystemTimer gem in readme 2011-03-04 12:17:55 +00:00
e3c4a5da9a added Gemfile.lock to .gitignore file 2011-03-04 12:17:39 +00:00
b215c6d56c made rcov rake task work 2011-03-04 12:16:49 +00:00
8d5c73a539 Merge branch 'release/v0.0.9' into dev 2011-02-23 18:11:54 +00:00
0d5170bc26 Merge branch 'release/v0.0.9' 2011-02-23 18:11:50 +00:00
4692780d1e Version bump to 0.0.9 2011-02-23 18:11:15 +00:00
f8ec626762 removed Gemfile.lock from git repo (this is a gem
not an application ^_^)
2011-02-23 18:10:09 +00:00
ec54385192 updated activesupport dependency to ">= 2.3.6" as
redistat breaks with earlier versions
2011-02-23 18:09:19 +00:00
4808a97d19 added .rvmrc file for development ease 2011-02-23 18:08:21 +00:00
1dce2780e0 Merge branch 'Oscil8-master' into dev 2011-02-23 18:00:16 +00:00
Ariel Salomon
cab7ed5633 Fix for use with Active Support 2.3.x 2011-02-23 07:42:58 -08:00
861d040187 Merge branch 'release/v0.0.8' into dev 2011-01-12 16:13:18 +00:00
3267ee1eb9 Merge branch 'release/v0.0.8' 2011-01-12 16:13:13 +00:00
6309e4b217 Version bump to 0.0.8 2011-01-12 16:12:55 +00:00
776ee8ac97 make version available in code via
Redistat::VERSION
2011-01-12 16:11:19 +00:00
3b346e88e0 moved DateHelper module to it's own files for the
sake of transparency
2011-01-12 16:10:38 +00:00
c3fe861b10 connection handling was so thread-safe that it
stopped working in newly created threads
2011-01-12 16:04:42 +00:00
bc5034b6bb Merge branch 'release/v0.0.7' into dev 2010-12-29 17:29:20 +00:00
66510fe344 Merge branch 'release/v0.0.7' 2010-12-29 17:29:13 +00:00
745473862f added note about system_timer gem to readme 2010-12-29 17:28:56 +00:00
a5c8fc6fbf Version bump to 0.0.7 2010-12-29 17:27:19 +00:00
115b223d7c added class_name option to Model warpper for
customizing the scope used in Redis keys
2010-12-29 17:26:22 +00:00
0597b587fd Merge branch 'feature/ruby19' into dev 2010-12-29 17:14:40 +00:00
89932759ef Merge branch 'master' of https://github.com/JamesHarrison/redistat into feature/ruby19 2010-12-29 16:42:50 +00:00
55e0687837 Merge branch 'dev' into feature/ruby19 2010-12-29 16:42:36 +00:00
James Harrison
93360dbeb9 Specs pass again - problem with Time.now resolution surpassing that provided by values stored in a Redistat::Date, truncated to seconds by using to_s, which is accurate enough for testing purposes 2010-12-28 23:55:39 +00:00
James Harrison
6a66605e0b Adds Ruby 1.9.2 compat (references to TimeExt#round collide with the new real Time#now, changed to TimeExt#beginning_of_closest), 3 specs failing relating to time equality testing 2010-12-28 23:46:25 +00:00
James Harrison
d9ce0daade Removes SystemTimer for Ruby 1.9.2 compat 2010-12-28 22:11:33 +00:00
f6ec2e97b2 Merge branch 'dev' of github.com:jimeh/redistat into dev 2010-12-09 22:42:55 +00:00
67dc9433c7 fixed typo in deprecation warning 2010-12-09 22:42:27 +00:00
f0fcd2110d Merge branch 'release/v0.0.6' into dev 2010-12-01 13:41:48 +00:00
24112e4705 Merge branch 'release/v0.0.6' 2010-12-01 13:41:44 +00:00
b9752ff92f Version bump to 0.0.6 2010-12-01 13:41:30 +00:00
14a093d79b updated gem dependencies to less specific versions
as older versions should work
2010-12-01 13:40:55 +00:00
84a05363dd Merge branch 'release/v0.0.5' into dev 2010-11-28 11:56:23 +00:00
690d1d9407 Merge branch 'release/v0.0.5' 2010-11-28 11:56:15 +00:00
2aedd4eee3 Version bump to 0.0.5 2010-11-28 11:55:50 +00:00
f906cf068e added a spec for Collection#total 2010-11-28 11:52:23 +00:00
cbb9050c80 fixed a typo 2010-11-28 11:51:50 +00:00
58a2fb560c Merge branch 'feature/multi-conf' into dev 2010-11-28 11:48:27 +00:00
6bae8ce2bc Updated Connection spec so the specs actually pass
without a local Redis server running on the
default port of 6379.
2010-11-28 11:48:10 +00:00
18e6125c6a Added support for connection_ref's down throughout
the code, so models can connect to specific Redis
servers.

I believe a lot of the code needs some
restructuring at some point down the line to
handle multiple connections in a cleaner way, but
for now it'll do.
2010-11-28 11:47:26 +00:00
dc162e0c89 initial work to being able to use per-model redis
configurations
2010-11-28 10:10:58 +00:00
5338676a5f moved all dependencies to gemspec 2010-11-25 22:48:57 +00:00
02fe41082a Merge branch 'release/v0.0.4' into dev 2010-11-24 13:51:13 +00:00
35 changed files with 562 additions and 196 deletions

1
.gitignore vendored
View File

@@ -19,6 +19,7 @@ rdoc
pkg/*
*.gem
.bundle
Gemfile.lock
## PROJECT::SPECIFIC
.yardoc/*

1
.rvmrc Normal file
View File

@@ -0,0 +1 @@
rvm gemset use redistat

View File

@@ -2,8 +2,3 @@ source 'http://rubygems.org/'
# Specify your gem's dependencies in redistat.gemspec
gemspec
group :development do
gem 'rspec', '>= 2.1.0'
gem 'yard', '>= 0.6.3'
end

View File

@@ -1,44 +0,0 @@
PATH
remote: .
specs:
redistat (0.0.4)
activesupport (>= 2.3.0)
json (>= 1.4.6)
redis (>= 2.1.1)
system_timer (>= 1.0.0)
time_ext (>= 0.2.8)
GEM
remote: http://rubygems.org/
specs:
activesupport (3.0.3)
diff-lcs (1.1.2)
i18n (0.4.2)
json (1.4.6)
redis (2.1.1)
rspec (2.1.0)
rspec-core (~> 2.1.0)
rspec-expectations (~> 2.1.0)
rspec-mocks (~> 2.1.0)
rspec-core (2.1.0)
rspec-expectations (2.1.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.1.0)
system_timer (1.0)
time_ext (0.2.8)
activesupport (>= 2.3.0)
i18n (>= 0.4.2)
yard (0.6.3)
PLATFORMS
ruby
DEPENDENCIES
activesupport (>= 2.3.0)
json (>= 1.4.6)
redis (>= 2.1.1)
redistat!
rspec (>= 2.1.0)
system_timer (>= 1.0.0)
time_ext (>= 0.2.8)
yard (>= 0.6.3)

View File

@@ -14,6 +14,8 @@ Redis fits perfectly with all of these requirements. It has atomic operations li
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
The simplest way to use Redistat is through the model wrapper.

View File

@@ -14,6 +14,7 @@ end
RSpec::Core::RakeTask.new(:rcov) do |spec|
spec.pattern = 'spec/**/*_spec.rb'
spec.rcov = true
spec.rcov_opts = ['--exclude', 'spec']
end
task :default => [:start, :spec, :stop]

View File

@@ -1,7 +1,7 @@
require 'rubygems'
require 'active_support'
require 'active_support/hash_with_indifferent_access' if !Hash.respond_to?(:with_indifferent_access) # Active Support 2.x and 3.x
require 'active_support/hash_with_indifferent_access' if !{}.respond_to?(:with_indifferent_access) # Active Support 2.x and 3.x
require 'redis'
require 'date'
require 'time'
@@ -10,8 +10,10 @@ require 'json'
require 'digest/sha1'
require 'redistat/collection'
require 'redistat/connection'
require 'redistat/database'
require 'redistat/date'
require 'redistat/date_helper'
require 'redistat/event'
require 'redistat/finder'
require 'redistat/finder/date_set'
@@ -21,10 +23,12 @@ require 'redistat/model'
require 'redistat/result'
require 'redistat/scope'
require 'redistat/summary'
require 'redistat/version'
require 'redistat/core_ext/date'
require 'redistat/core_ext/time'
require 'redistat/core_ext/fixnum'
require 'redistat/core_ext/bignum'
module Redistat
@@ -32,53 +36,31 @@ module Redistat
KEY_EVENT = ".event:"
KEY_LEBELS = "Redistat.lables:"
KEY_EVENT_IDS = ".event_ids"
GROUP_SEPARATOR = "/"
class InvalidOptions < ArgumentError; end
# Provides access to the Redis database. This is shared accross all models and instances.
def redis
threaded[:redis] ||= connection(*options)
end
def redis=(connection)
threaded[:redis] = connection
end
def threaded
Thread.current[:redistat] ||= {}
end
# Connect to a redis database.
#
# @param options [Hash] options to create a message with.
# @option options [#to_s] :host ('127.0.0.1') Host of the redis database.
# @option options [#to_s] :port (6379) Port number.
# @option options [#to_s] :db (0) Database number.
# @option options [#to_s] :timeout (0) Database timeout in seconds.
# @example Connect to a database in port 6380.
# Redistat.connect(:port => 6380)
def connect(*options)
self.redis = nil
@options = options
end
# Return a connection to Redis.
#
# This is a wapper around Redis.new(options)
def connection(*options)
Redis.new(*options)
end
def options
@options = [] unless defined? @options
@options
end
# Clear the database.
def flush
redis.flushdb
end
class RedisServerIsTooOld < Exception; end
module_function :connect, :connection, :flush, :redis, :redis=, :options, :threaded
class << self
def connection(ref = nil)
Connection.get(ref)
end
alias :redis :connection
def connection=(connection)
Connection.add(connection)
end
alias :redis= :connection=
def connect(options)
Connection.create(options)
end
def flush
puts "WARNING: Redistat.flush is deprecated. Use Redistat.redis.flushdb instead."
connection.flushdb
end
end
end

View File

@@ -12,5 +12,9 @@ module Redistat
@depth = options[:depth] ||= nil
end
def total
@total ||= {}
end
end
end

View File

@@ -0,0 +1,64 @@
module Redistat
module Connection
REQUIRED_SERVER_VERSION = "1.3.10"
class << self
def get(ref = nil)
ref ||= :default
connections[references[ref]] || create
end
def add(conn, ref = nil)
ref ||= :default
check_redis_version(conn)
references[ref] = conn.client.id
connections[conn.client.id] = conn
end
def create(options = {})
#TODO clean/remove all ref-less connections
ref = options.delete(:ref) || :default
options.reverse_merge!(default_options)
conn = (connections[connection_id(options)] ||= connection(options))
references[ref] = conn.client.id
conn
end
def connections
@connections ||= {}
end
def references
@references ||= {}
end
private
def check_redis_version(conn)
raise RedisServerIsTooOld if conn.info["redis_version"] < REQUIRED_SERVER_VERSION
conn
end
def connection(options)
check_redis_version(Redis.new(options))
end
def connection_id(options = {})
options.reverse_merge!(default_options)
"redis://#{options[:host]}:#{options[:port]}/#{options[:db]}"
end
def default_options
{
:host => '127.0.0.1',
:port => 6379,
:db => 0,
:timeout => 5
}
end
end
end
end

View File

@@ -0,0 +1,8 @@
class Bignum
include Redistat::DateHelper
def to_time
Time.at(self)
end
end

View File

@@ -3,8 +3,8 @@ module Redistat
def self.included(base)
base.extend(Database)
end
def db
Redistat.redis
def db(ref = nil)
Redistat.connection(ref)
end
end
end

View File

@@ -22,6 +22,8 @@ module Redistat
from_string(input)
elsif input.is_a?(::Fixnum)
from_integer(input)
elsif input.is_a?(::Bignum)
from_integer(input)
end
end
@@ -83,11 +85,4 @@ module Redistat
end
end
module DateHelper
def to_redistat(depth = nil)
Redistat::Date.new(self, depth)
end
alias :to_rs :to_redistat
end
end

View File

@@ -0,0 +1,8 @@
module Redistat
module DateHelper
def to_redistat(depth = nil)
Redistat::Date.new(self, depth)
end
alias :to_rs :to_redistat
end
end

View File

@@ -4,21 +4,37 @@ module Redistat
attr_reader :id
attr_reader :key
attr_reader :connection_ref
attr_accessor :stats
attr_accessor :meta
attr_accessor :options
def initialize(scope, label = nil, date = nil, stats = {}, options = {}, meta = {}, is_new = true)
@options = default_options.merge(options)
@options = parse_options(options)
@connection_ref = @options[:connection_ref]
@key = Key.new(scope, label, date, @options)
@stats = stats ||= {}
@meta = meta ||= {}
@new = is_new
end
def db
super(@connection_ref)
end
def parse_options(options)
default_options.each do |opt, val|
options[opt] = val if options[opt].nil?
end
options
end
def default_options
{ :depth => :hour, :store_event => false }
{ :depth => :hour,
:store_event => false,
:connection_ref => nil,
:enable_grouping => true }
end
def new?
@@ -59,7 +75,7 @@ module Redistat
def save
return false if !self.new?
Summary.update_all(@key, @stats, depth_limit)
Summary.update_all(@key, @stats, depth_limit, @connection_ref, @options[:enable_grouping])
if @options[:store_event]
@id = self.next_id
db.hmset("#{self.scope}#{KEY_EVENT}#{@id}",

View File

@@ -8,6 +8,10 @@ module Redistat
@options = options
end
def db
super(@options[:connection_ref])
end
def valid_options?
return true if !@options[:scope].blank? && !@options[:label].blank? && !@options[:from].blank? && !@options[:till].blank?
false

View File

@@ -42,7 +42,7 @@ module Redistat
return find_start_year_for(start_date, end_date, lowest_depth) if unit == :year
index = Date::DEPTHS.index(unit)
nunit = Date::DEPTHS[(index > 0) ? index-1 : index]
if start_date < start_date.round(nunit) || start_date.next(nunit).beginning_of(nunit) > end_date.beginning_of(nunit)
if start_date < start_date.beginning_of_closest(nunit) || start_date.next(nunit).beginning_of(nunit) > end_date.beginning_of(nunit)
add = []
start_date.beginning_of_each(unit, :include_start => lowest_depth).until(start_date.end_of(nunit)) do |t|
add << t.to_rs.to_s(unit) if t < end_date.beginning_of(unit)
@@ -59,7 +59,7 @@ module Redistat
index = Date::DEPTHS.index(unit)
nunit = Date::DEPTHS[(index > 0) ? index-1 : index]
has_nunit = end_date.prev(nunit).beginning_of(nunit) >= start_date.beginning_of(nunit)
nearest_nunit = end_date.round(nunit)
nearest_nunit = end_date.beginning_of_closest(nunit)
if end_date >= nearest_nunit && has_nunit
add = []
end_date.beginning_of(nunit).beginning_of_each(unit, :include_start => true, :include_end => lowest_depth).until(end_date) do |t|

View File

@@ -39,10 +39,20 @@ module Redistat
@label.hash
end
def label_groups
@label.groups
end
def label=(input)
@label = (input.instance_of?(Redistat::Label)) ? input : Label.create(input, @options)
end
def groups
@groups ||= label_groups.map do |label_name|
self.class.new(@scope, label_name, self.date, @options)
end
end
def to_s(depth = nil)
depth ||= @options[:depth]
key = self.prefix

View File

@@ -3,11 +3,20 @@ module Redistat
include Database
attr_reader :raw
attr_reader :connection_ref
def self.create(name, options = {})
self.new(name, options).save
end
def initialize(str, options = {})
@options = options
@raw = str.to_s
end
def db
super(@options[:connection_ref])
end
def name
@options[:hashed_label] ? hash : @raw
@@ -26,8 +35,18 @@ module Redistat
@saved ||= false
end
def self.create(name, options = {})
self.new(name, options).save
def groups
return @groups if @groups
@groups = []
parent = ""
@raw.split(GROUP_SEPARATOR).each do |part|
if !part.blank?
group = ((parent.blank?) ? "" : "#{parent}/") + part
@groups << group
parent = group
end
end
@groups.reverse!
end
end

View File

@@ -1,5 +1,6 @@
module Redistat
module Model
include Redistat::Database
def self.included(base)
base.extend(self)
@@ -10,6 +11,16 @@ module Redistat
end
alias :event :store
def connect_to(opts = {})
Connection.create(opts.merge(:ref => name))
options[:connection_ref] = name
end
def connection
db(options[:connection_ref])
end
alias :redis :connection
def fetch(label, from, till, opts = {})
Finder.find({
:scope => name,
@@ -28,6 +39,15 @@ module Redistat
end
end
def class_name(class_name = nil)
if !class_name.nil?
options[:class_name] = class_name
else
options[:class_name] || nil
end
end
alias :scope :class_name
def depth(depth = nil)
if !depth.nil?
options[:depth] = depth
@@ -51,7 +71,7 @@ module Redistat
private
def name
@name ||= self.to_s
options[:class_name] || (@name ||= self.to_s)
end
end

View File

@@ -1,5 +1,5 @@
module Redistat
class Result < ::ActiveSupport::HashWithIndifferentAccess
class Result < HashWithIndifferentAccess
attr_accessor :from
attr_accessor :till

View File

@@ -2,23 +2,57 @@ module Redistat
class Summary
include Database
def self.update_all(key, stats = {}, depth_limit = nil)
def self.update_all(key, stats = {}, depth_limit = nil, connection_ref = nil, enable_grouping = nil)
stats ||= {}
depth_limit ||= key.depth
return nil if stats.size == 0
Date::DEPTHS.each do |depth|
update(key, stats, depth)
break if depth == depth_limit
depth_limit ||= key.depth
enable_grouping = true if enable_grouping.nil?
if enable_grouping
stats = inject_group_summaries(stats)
key.groups.each { |k|
update_key(k, stats, depth_limit, connection_ref)
}
else
update_key(key, stats, depth_limit, connection_ref)
end
end
private
def self.update(key, stats, depth)
stats.each do |field, value|
db.hincrby key.to_s(depth), field, value
def self.update_key(key, stats, depth_limit, connection_ref)
Date::DEPTHS.each do |depth|
update(key, stats, depth, connection_ref)
break if depth == depth_limit
end
end
def self.update(key, stats, depth, connection_ref = nil)
stats.each do |field, value|
db(connection_ref).hincrby key.to_s(depth), field, value
end
end
def self.inject_group_summaries!(stats)
stats.each do |key, value|
parts = key.to_s.split(GROUP_SEPARATOR)
parts.pop
if parts.size > 0
sum_parts = []
parts.each do |part|
sum_parts << part
sum_key = sum_parts.join(GROUP_SEPARATOR)
(stats.has_key?(sum_key)) ? stats[sum_key] += value : stats[sum_key] = value
end
end
end
stats
end
def self.inject_group_summaries(stats)
inject_group_summaries!(stats.clone)
end
end
end

View File

@@ -1,3 +1,3 @@
module Redistat
VERSION = "0.0.4"
VERSION = "0.1.0"
end

View File

@@ -19,9 +19,12 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.add_runtime_dependency 'activesupport', '>= 2.3.0'
s.add_runtime_dependency 'json', '>= 1.4.6'
s.add_runtime_dependency 'redis', '>= 2.1.1'
s.add_runtime_dependency 'system_timer', '>= 1.0.0'
s.add_runtime_dependency 'activesupport', '>= 2.3.6'
s.add_runtime_dependency 'json', '>= 1.4.0'
s.add_runtime_dependency 'redis', '>= 2.1.0'
s.add_runtime_dependency 'time_ext', '>= 0.2.8'
s.add_development_dependency 'rspec', '>= 2.1.0'
s.add_development_dependency 'rcov', '>= 0.9.9'
s.add_development_dependency 'yard', '>= 0.6.3'
end

View File

@@ -1,34 +0,0 @@
require "spec_helper"
describe Redistat do
include Redistat::Database
before(:each) do
db.flushdb
end
it "should have a valid Redis client instance" do
db.should_not be_nil
end
it "should be connected to the testing server" do
db.client.port.should == 8379
db.client.host.should == "127.0.0.1"
end
it "should be able to set and get data" do
db.set("hello", "world")
db.get("hello").should == "world"
db.del("hello").should be_true
end
it "should be able to store hashes to Redis" do
db.hset("key", "field", "1")
db.hget("key", "field").should == "1"
db.hincrby("key", "field", 1)
db.hget("key", "field").should == "2"
db.hincrby("key", "field", -1)
db.hget("key", "field").should == "1"
end
end

View File

@@ -2,7 +2,7 @@ require "spec_helper"
describe Redistat::Collection do
it "should should initialize properly" do
it "should initialize properly" do
options = {:from => "from", :till => "till", :depth => "depth"}
result = Redistat::Collection.new(options)
result.from.should == options[:from]
@@ -10,4 +10,11 @@ describe Redistat::Collection do
result.depth.should == options[:depth]
end
it "should have a total property" do
col = Redistat::Collection.new()
col.total.should == {}
col.total = {:foo => "bar"}
col.total.should == {:foo => "bar"}
end
end

61
spec/connection_spec.rb Normal file
View File

@@ -0,0 +1,61 @@
require "spec_helper"
include Redistat
describe Redistat::Connection do
it "should have a valid Redis client instance" do
Redistat.redis.should_not be_nil
end
it "should have initialized custom testing connection" do
redis = Redistat.redis
redis.client.host.should == '127.0.0.1'
redis.client.port.should == 8379
redis.client.db.should == 15
end
it "should be able to set and get data" do
redis = Redistat.redis
redis.set("hello", "world")
redis.get("hello").should == "world"
redis.del("hello").should be_true
end
it "should be able to store hashes to Redis" do
redis = Redistat.redis
redis.hset("hash", "field", "1")
redis.hget("hash", "field").should == "1"
redis.hincrby("hash", "field", 1)
redis.hget("hash", "field").should == "2"
redis.hincrby("hash", "field", -1)
redis.hget("hash", "field").should == "1"
redis.del("hash")
end
it "should be accessible from Redistat module" do
Redistat.redis.should == Connection.get
Redistat.redis.should == Redistat.connection
end
it "should handle multiple connections with refs" do
Redistat.redis.client.db.should == 15
Redistat.connect(:port => 8379, :db => 14, :ref => "Custom")
Redistat.redis.client.db.should == 15
Redistat.redis("Custom").client.db.should == 14
end
it "should be able to overwrite default and custom refs" do
Redistat.redis.client.db.should == 15
Redistat.connect(:port => 8379, :db => 14)
Redistat.redis.client.db.should == 14
Redistat.redis("Custom").client.db.should == 14
Redistat.connect(:port => 8379, :db => 15, :ref => "Custom")
Redistat.redis("Custom").client.db.should == 15
# Reset the default connection to the testing server or all hell
# might brake loose from the rest of the specs
Redistat.connect(:port => 8379, :db => 15)
end
end

10
spec/database_spec.rb Normal file
View File

@@ -0,0 +1,10 @@
require "spec_helper"
describe Redistat::Database do
include Redistat::Database
it "should make #db method available when included" do
db.should == Redistat.redis
end
end

View File

@@ -20,7 +20,7 @@ describe Redistat::Event do
@event.scope.should == @scope
@event.label.should == @label
@event.label_hash.should == @label_hash
@event.date.to_time.should == @date
@event.date.to_time.to_s.should == @date.to_s
@event.stats.should == @stats
@event.meta.should == @meta
@event.options.should == @event.default_options.merge(@options)
@@ -28,10 +28,10 @@ describe Redistat::Event do
it "should allow changing attributes" do
# date
@event.date.to_time.should == @date
@event.date.to_time.to_s.should == @date.to_s
@date = Time.now
@event.date = @date
@event.date.to_time.should == @date
@event.date.to_time.to_s.should == @date.to_s
# label
@event.label.should == @label
@event.label_hash.should == @label_hash

View File

@@ -14,6 +14,7 @@ describe Redistat::Key do
@key.scope.should == @scope
@key.label.should == @label
@key.label_hash.should == @label_hash
@key.label_groups.should == @key.instance_variable_get("@label").groups
@key.date.should be_instance_of(Redistat::Date)
@key.date.to_time.to_s.should == @date.to_s
end
@@ -46,10 +47,10 @@ describe Redistat::Key do
@key.scope = @scope
@key.scope.should == @scope
# date
@key.date.to_time.should == @date
@key.date.to_time.to_s.should == @date.to_s
@date = Time.now
@key.date = @date
@key.date.to_time.should == @date
@key.date.to_time.to_s.should == @date.to_s
# label
@key.label.should == @label
@key.label_hash == @label_hash
@@ -60,4 +61,16 @@ describe Redistat::Key do
@key.label_hash == @label_hash
end
it "should create a group of keys from label group" do
label = 'message/public/offensive'
result = [ "message/public/offensive",
"message/public",
"message" ]
key = Redistat::Key.new(@scope, label, @date, {:depth => :hour})
key.label_groups.should == result
key.groups.map { |k| k.label }.should == result
end
end

View File

@@ -25,4 +25,24 @@ describe Redistat::Label do
db.get("#{Redistat::KEY_LEBELS}#{label.hash}").should == name
end
it "should separate label names into groups" do
name = "message/public/offensive"
label = Redistat::Label.new(name)
label.name.should == name
label.groups.should == [ "message/public/offensive",
"message/public",
"message" ]
name = "/message/public/"
label = Redistat::Label.new(name)
label.name.should == name
label.groups.should == [ "message/public",
"message" ]
name = "message"
label = Redistat::Label.new(name)
label.name.should == name
label.groups.should == [ "message" ]
end
end

View File

@@ -1,6 +1,6 @@
require "redistat"
class ModelHelper
class ModelHelper1
include Redistat::Model
@@ -13,4 +13,18 @@ class ModelHelper2
store_event true
hashed_label true
end
class ModelHelper3
include Redistat::Model
connect_to :port => 8379, :db => 14
end
class ModelHelper4
include Redistat::Model
class_name "FancyHelper"
end

View File

@@ -5,11 +5,14 @@ describe Redistat::Model do
include Redistat::Database
before(:each) do
db.flushdb
ModelHelper1.redis.flushdb
ModelHelper2.redis.flushdb
ModelHelper3.redis.flushdb
ModelHelper4.redis.flushdb
end
it "should should name itself correctly" do
ModelHelper.send(:name).should == "ModelHelper"
ModelHelper1.send(:name).should == "ModelHelper1"
ModelHelper2.send(:name).should == "ModelHelper2"
end
@@ -17,50 +20,86 @@ describe Redistat::Model do
ModelHelper2.depth.should == :day
ModelHelper2.store_event.should == true
ModelHelper2.hashed_label.should == true
ModelHelper2.class_name.should be_nil
ModelHelper.depth.should == nil
ModelHelper.store_event.should == nil
ModelHelper.hashed_label.should == nil
ModelHelper.depth(:hour)
ModelHelper.depth.should == :hour
ModelHelper.store_event(true)
ModelHelper.store_event.should == true
ModelHelper.hashed_label(true)
ModelHelper.hashed_label.should == true
ModelHelper.options[:depth] = nil
ModelHelper.options[:store_event] = nil
ModelHelper.options[:hashed_label] = nil
ModelHelper.depth.should == nil
ModelHelper.store_event.should == nil
ModelHelper.hashed_label.should == nil
ModelHelper1.depth.should == nil
ModelHelper1.store_event.should == nil
ModelHelper1.hashed_label.should == nil
ModelHelper1.depth(:hour)
ModelHelper1.depth.should == :hour
ModelHelper1.store_event(true)
ModelHelper1.store_event.should == true
ModelHelper1.hashed_label(true)
ModelHelper1.hashed_label.should == true
ModelHelper1.options[:depth] = nil
ModelHelper1.options[:store_event] = nil
ModelHelper1.options[:hashed_label] = nil
ModelHelper1.depth.should == nil
ModelHelper1.store_event.should == nil
ModelHelper1.hashed_label.should == nil
ModelHelper4.class_name.should == "FancyHelper"
ModelHelper4.send(:name).should == "FancyHelper"
end
it "should store and fetch stats" do
ModelHelper.store("sheep.black", {:count => 6, :weight => 461}, 4.hours.ago)
ModelHelper.store("sheep.black", {:count => 2, :weight => 156})
ModelHelper1.store("sheep.black", {:count => 6, :weight => 461}, 4.hours.ago)
ModelHelper1.store("sheep.black", {:count => 2, :weight => 156})
stats = ModelHelper.fetch("sheep.black", 2.hours.ago, 1.hour.from_now)
stats = ModelHelper1.fetch("sheep.black", 2.hours.ago, 1.hour.from_now)
stats.total["count"].should == 2
stats.total["weight"].should == 156
stats.first.should == stats.total
stats = ModelHelper.fetch("sheep.black", 5.hours.ago, 1.hour.from_now)
stats = ModelHelper1.fetch("sheep.black", 5.hours.ago, 1.hour.from_now)
stats.total[:count].should == 8
stats.total[:weight].should == 617
stats.first.should == stats.total
ModelHelper.store("sheep.white", {:count => 5, :weight => 393}, 4.hours.ago)
ModelHelper.store("sheep.white", {:count => 4, :weight => 316})
ModelHelper1.store("sheep.white", {:count => 5, :weight => 393}, 4.hours.ago)
ModelHelper1.store("sheep.white", {:count => 4, :weight => 316})
stats = ModelHelper.fetch("sheep.white", 2.hours.ago, 1.hour.from_now)
stats = ModelHelper1.fetch("sheep.white", 2.hours.ago, 1.hour.from_now)
stats.total[:count].should == 4
stats.total[:weight].should == 316
stats.first.should == stats.total
stats = ModelHelper.fetch("sheep.white", 5.hours.ago, 1.hour.from_now)
stats = ModelHelper1.fetch("sheep.white", 5.hours.ago, 1.hour.from_now)
stats.total[:count].should == 9
stats.total[:weight].should == 709
stats.first.should == stats.total
end
end
it "should connect to different Redis servers on a per-model basis" do
ModelHelper3.redis.client.db.should == 14
ModelHelper3.store("sheep.black", {:count => 6, :weight => 461}, 4.hours.ago)
ModelHelper3.store("sheep.black", {:count => 2, :weight => 156})
db.keys("*").should be_empty
ModelHelper1.redis.keys("*").should be_empty
db("ModelHelper3").keys("*").should have(5).items
ModelHelper3.redis.keys("*").should have(5).items
stats = ModelHelper3.fetch("sheep.black", 2.hours.ago, 1.hour.from_now)
stats.total["count"].should == 2
stats.total["weight"].should == 156
stats = ModelHelper3.fetch("sheep.black", 5.hours.ago, 1.hour.from_now)
stats.total[:count].should == 8
stats.total[:weight].should == 617
ModelHelper3.connect_to(:port => 8379, :db => 13)
ModelHelper3.redis.client.db.should == 13
stats = ModelHelper3.fetch("sheep.black", 5.hours.ago, 1.hour.from_now)
stats.total.should == {}
ModelHelper3.connect_to(:port => 8379, :db => 14)
ModelHelper3.redis.client.db.should == 14
stats = ModelHelper3.fetch("sheep.black", 5.hours.ago, 1.hour.from_now)
stats.total[:count].should == 8
stats.total[:weight].should == 617
end
end

View File

@@ -8,5 +8,5 @@ require 'rspec'
require 'rspec/autorun'
# use the test Redistat instance
Redistat.connect({:port => 8379, :db => 15})
Redistat.flush
Redistat.connect(:port => 8379, :db => 15, :thread_safe => true)
Redistat.redis.flushdb

View File

@@ -46,4 +46,78 @@ describe Redistat::Summary do
end
end
it "should inject stats key grouping summaries" do
hash = { "count/hello" => 3, "count/world" => 7,
"death/bomb" => 4, "death/unicorn" => 3,
:"od/sugar" => 7, :"od/meth" => 8 }
res = Redistat::Summary.send(:inject_group_summaries, hash)
res.should == { "count" => 10, "count/hello" => 3, "count/world" => 7,
"death" => 7, "death/bomb" => 4, "death/unicorn" => 3,
"od" => 15, :"od/sugar" => 7, :"od/meth" => 8 }
end
it "should properly store key group summaries" do
stats = {"views" => 3, "visitors/eu" => 2, "visitors/us" => 4}
Redistat::Summary.update_all(@key, stats, :hour)
summary = db.hgetall(@key.to_s(:hour))
summary.should have(4).items
summary["views"].should == "3"
summary["visitors"].should == "6"
summary["visitors/eu"].should == "2"
summary["visitors/us"].should == "4"
end
it "should not store key group summaries when option is disabled" do
stats = {"views" => 3, "visitors/eu" => 2, "visitors/us" => 4}
Redistat::Summary.update_all(@key, stats, :hour, nil, false)
summary = db.hgetall(@key.to_s(:hour))
summary.should have(3).items
summary["views"].should == "3"
summary["visitors/eu"].should == "2"
summary["visitors/us"].should == "4"
end
it "should store label-based grouping enabled stats" do
stats = {"views" => 3, "visitors/eu" => 2, "visitors/us" => 4}
label = "views/about_us"
key = Redistat::Key.new(@scope, label, @date)
Redistat::Summary.update_all(key, stats, :hour)
key.groups[0].label.should == "views/about_us"
key.groups[1].label.should == "views"
child1 = key.groups[0]
parent = key.groups[1]
label = "views/contact"
key = Redistat::Key.new(@scope, label, @date)
Redistat::Summary.update_all(key, stats, :hour)
key.groups[0].label.should == "views/contact"
key.groups[1].label.should == "views"
child2 = key.groups[0]
summary = db.hgetall(child1.to_s(:hour))
summary["views"].should == "3"
summary["visitors/eu"].should == "2"
summary["visitors/us"].should == "4"
summary = db.hgetall(child2.to_s(:hour))
summary["views"].should == "3"
summary["visitors/eu"].should == "2"
summary["visitors/us"].should == "4"
summary = db.hgetall(parent.to_s(:hour))
summary["views"].should == "6"
summary["visitors/eu"].should == "4"
summary["visitors/us"].should == "8"
end
end

View File

@@ -0,0 +1,39 @@
require "spec_helper"
describe "Thread-Safety" do
include Redistat::Database
before(:each) do
db.flushdb
end
#TODO should have more comprehensive thread-safe tests
it "should incr in multiple threads" do
threads = []
50.times do
threads << Thread.new {
db.incr("spec:incr")
}
end
threads.each { |t| t.join }
db.get("spec:incr").should == "50"
end
it "should store event in multiple threads" do
class ThreadSafetySpec
include Redistat::Model
end
threads = []
50.times do
threads << Thread.new {
ThreadSafetySpec.store("spec:threadsafe", {:count => 1, :rand => rand(5)})
}
end
threads.each { |t| t.join }
result = ThreadSafetySpec.fetch("spec:threadsafe", 5.hours.ago, 5.hours.from_now)
result.total[:count].should == 50
result.total[:rand].should <= 250
end
end