20 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
16 changed files with 186 additions and 54 deletions

1
.gitignore vendored
View File

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

1
.rvmrc Normal file
View File

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

View File

@@ -1,41 +0,0 @@
PATH
remote: .
specs:
redistat (0.0.8)
activesupport (>= 2.3.0)
json (>= 1.4.0)
redis (>= 2.1.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)
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.0)
redis (>= 2.1.0)
redistat!
rspec (>= 2.1.0)
time_ext (>= 0.2.8)
yard (>= 0.6.3)

View File

@@ -14,7 +14,7 @@ Redis fits perfectly with all of these requirements. It has atomic operations li
gem install redistat gem install redistat
If you are using Ruby 1.8.x, it's recommended you also install the `system_timer` gem, as the Redis gem will otherwise complain. 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 ## Usage

View File

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

View File

@@ -1,7 +1,7 @@
require 'rubygems' require 'rubygems'
require 'active_support' 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 'redis'
require 'date' require 'date'
require 'time' require 'time'
@@ -36,6 +36,7 @@ module Redistat
KEY_EVENT = ".event:" KEY_EVENT = ".event:"
KEY_LEBELS = "Redistat.lables:" KEY_LEBELS = "Redistat.lables:"
KEY_EVENT_IDS = ".event_ids" KEY_EVENT_IDS = ".event_ids"
GROUP_SEPARATOR = "/"
class InvalidOptions < ArgumentError; end class InvalidOptions < ArgumentError; end
class RedisServerIsTooOld < Exception; end class RedisServerIsTooOld < Exception; end

View File

@@ -31,7 +31,10 @@ module Redistat
end end
def default_options def default_options
{ :depth => :hour, :store_event => false, :connection_ref => nil } { :depth => :hour,
:store_event => false,
:connection_ref => nil,
:enable_grouping => true }
end end
def new? def new?
@@ -72,7 +75,7 @@ module Redistat
def save def save
return false if !self.new? return false if !self.new?
Summary.update_all(@key, @stats, depth_limit, @connection_ref) Summary.update_all(@key, @stats, depth_limit, @connection_ref, @options[:enable_grouping])
if @options[:store_event] if @options[:store_event]
@id = self.next_id @id = self.next_id
db.hmset("#{self.scope}#{KEY_EVENT}#{@id}", db.hmset("#{self.scope}#{KEY_EVENT}#{@id}",

View File

@@ -39,10 +39,20 @@ module Redistat
@label.hash @label.hash
end end
def label_groups
@label.groups
end
def label=(input) def label=(input)
@label = (input.instance_of?(Redistat::Label)) ? input : Label.create(input, @options) @label = (input.instance_of?(Redistat::Label)) ? input : Label.create(input, @options)
end 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) def to_s(depth = nil)
depth ||= @options[:depth] depth ||= @options[:depth]
key = self.prefix key = self.prefix

View File

@@ -5,6 +5,10 @@ module Redistat
attr_reader :raw attr_reader :raw
attr_reader :connection_ref attr_reader :connection_ref
def self.create(name, options = {})
self.new(name, options).save
end
def initialize(str, options = {}) def initialize(str, options = {})
@options = options @options = options
@raw = str.to_s @raw = str.to_s
@@ -31,8 +35,18 @@ module Redistat
@saved ||= false @saved ||= false
end end
def self.create(name, options = {}) def groups
self.new(name, options).save 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
end end

View File

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

View File

@@ -2,23 +2,57 @@ module Redistat
class Summary class Summary
include Database include Database
def self.update_all(key, stats = {}, depth_limit = nil, connection_ref = nil) def self.update_all(key, stats = {}, depth_limit = nil, connection_ref = nil, enable_grouping = nil)
stats ||= {} stats ||= {}
depth_limit ||= key.depth
return nil if stats.size == 0 return nil if stats.size == 0
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(key, stats, depth_limit, connection_ref)
Date::DEPTHS.each do |depth| Date::DEPTHS.each do |depth|
update(key, stats, depth, connection_ref) update(key, stats, depth, connection_ref)
break if depth == depth_limit break if depth == depth_limit
end end
end end
private
def self.update(key, stats, depth, connection_ref = nil) def self.update(key, stats, depth, connection_ref = nil)
stats.each do |field, value| stats.each do |field, value|
db(connection_ref).hincrby key.to_s(depth), field, value db(connection_ref).hincrby key.to_s(depth), field, value
end end
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
end end

View File

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

View File

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

View File

@@ -14,6 +14,7 @@ describe Redistat::Key do
@key.scope.should == @scope @key.scope.should == @scope
@key.label.should == @label @key.label.should == @label
@key.label_hash.should == @label_hash @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.should be_instance_of(Redistat::Date)
@key.date.to_time.to_s.should == @date.to_s @key.date.to_time.to_s.should == @date.to_s
end end
@@ -60,4 +61,16 @@ describe Redistat::Key do
@key.label_hash == @label_hash @key.label_hash == @label_hash
end 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 end

View File

@@ -25,4 +25,24 @@ describe Redistat::Label do
db.get("#{Redistat::KEY_LEBELS}#{label.hash}").should == name db.get("#{Redistat::KEY_LEBELS}#{label.hash}").should == name
end 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 end

View File

@@ -46,4 +46,78 @@ describe Redistat::Summary do
end end
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 end