Merge branch 'feature/label-indexing' into dev

This commit is contained in:
2011-03-09 17:28:46 +00:00
13 changed files with 272 additions and 120 deletions

View File

@@ -34,8 +34,9 @@ module Redistat
KEY_NEXT_ID = ".next_id"
KEY_EVENT = ".event:"
KEY_LEBELS = "Redistat.lables:"
KEY_LEBELS = "Redistat.lables:" # TODO: Fix this typo, at some point. Might brack backwards compatability ^_^
KEY_EVENT_IDS = ".event_ids"
LABEL_INDEX = ".label_index:"
GROUP_SEPARATOR = "/"
class InvalidOptions < ArgumentError; end

View File

@@ -40,8 +40,12 @@ module Redistat
attr_reader :options
def initialize(options = {})
@options = options
def initialize(opts = {})
set_options(opts)
end
def options
@options ||= {}
end
def all(reload = false)
@@ -65,21 +69,27 @@ module Redistat
all.each_with_index(&block)
end
def children
build_key.children.map { |key|
self.class.new(options.merge(:label => key.label.to_s))
}
end
def connection_ref(ref)
reset! if @options[:connection_ref] != ref
@options[:connection_ref] = ref
reset! if options[:connection_ref] != ref
options[:connection_ref] = ref
self
end
def scope(scope)
reset! if @options[:scope] != scope
@options[:scope] = scope
reset! if !options[:scope].nil? && options[:scope].to_s != scope
options[:scope] = Scope.new(scope)
self
end
def label(label)
reset! if @options[:label] != label
@options[:label] = label
reset! if !options[:label].nil? && options[:label].to_s != label
options[:label] = Label.new(label)
self
end
@@ -89,34 +99,34 @@ module Redistat
alias :date :dates
def from(date)
reset! if @options[:from] != date
@options[:from] = date
reset! if options[:from] != date
options[:from] = date
self
end
def till(date)
reset! if @options[:till] != date
@options[:till] = date
reset! if options[:till] != date
options[:till] = date
self
end
alias :until :till
def depth(unit)
reset! if @options[:depth] != unit
@options[:depth] = unit
reset! if options[:depth] != unit
options[:depth] = unit
self
end
def interval(unit)
reset! if @options[:interval] != unit
@options[:interval] = unit
reset! if options[:interval] != unit
options[:interval] = unit
self
end
def find(options = {})
set_options(options)
def find(opts = {})
set_options(opts)
raise InvalidOptions.new if !valid_options?
if @options[:interval].nil? || !@options[:interval]
if options[:interval].nil? || !options[:interval]
find_by_magic
else
find_by_interval
@@ -130,14 +140,14 @@ module Redistat
opts.each do |key, value|
self.send(key, opts.delete(key)) if self.respond_to?(key)
end
@options.merge!(opts)
self.options.merge!(opts)
end
def find_by_interval(options = {})
def find_by_interval
raise InvalidOptions.new if !valid_options?
key = build_key
col = Collection.new(@options)
col.total = Result.new(@options)
col = Collection.new(options)
col.total = Result.new(options)
build_date_sets.each do |set|
set[:add].each do |date|
result = Result.new
@@ -152,11 +162,11 @@ module Redistat
col
end
def find_by_magic(options = {})
def find_by_magic
raise InvalidOptions.new if !valid_options?
key = Key.new(@options[:scope], @options[:label])
col = Collection.new(@options)
col.total = Result.new(@options)
key = build_key
col = Collection.new(options)
col.total = Result.new(options)
col << col.total
build_date_sets.each do |set|
sum = Result.new
@@ -174,16 +184,16 @@ module Redistat
end
def valid_options?
return true if !@options[:scope].blank? && !@options[:label].blank? && !@options[:from].blank? && !@options[:till].blank?
return true if !options[:scope].blank? && !options[:label].blank? && !options[:from].blank? && !options[:till].blank?
false
end
def build_date_sets
Finder::DateSet.new(@options[:from], @options[:till], @options[:depth], @options[:interval])
Finder::DateSet.new(options[:from], options[:till], options[:depth], options[:interval])
end
def build_key
Key.new(@options[:scope], @options[:label])
Key.new(options[:scope], options[:label])
end
def summarize_add_keys(sets, key, sum)
@@ -205,7 +215,7 @@ module Redistat
end
def db
super(@options[:connection_ref])
super(options[:connection_ref])
end
end

View File

@@ -1,13 +1,12 @@
module Redistat
class Key
include Redistat::Database
attr_accessor :scope
attr_accessor :date
attr_accessor :options
def initialize(scope, label_name = nil, time_stamp = nil, options = {})
@options = default_options.merge(options || {})
@scope = scope
self.scope = scope
self.label = label_name if !label_name.nil?
self.date = time_stamp ||= Time.now
end
@@ -18,7 +17,7 @@ module Redistat
def prefix
key = "#{@scope}"
key << "/#{label}" if !label.nil?
key << "/#{label.name}" if !label.nil?
key << ":"
key
end
@@ -26,30 +25,49 @@ module Redistat
def date=(input)
@date = (input.instance_of?(Redistat::Date)) ? input : Date.new(input) # Redistat::Date, not ::Date
end
attr_reader :date
def depth
@options[:depth]
end
def label
@label.name
def scope
@scope.to_s
end
def label_hash
@label.hash
end
def label_groups
@label.groups
def scope=(input)
@scope = (input.instance_of?(Redistat::Scope)) ? input : Scope.new(input)
end
def label=(input)
@label = (input.instance_of?(Redistat::Label)) ? input : Label.create(input, @options)
end
attr_reader :label
def groups
@groups ||= label_groups.map do |label_name|
self.class.new(@scope, label_name, self.date, @options)
def label_hash
@label.hash
end
def parent
@parent ||= self.class.new(self.scope, @label.parent, self.date, @options) unless @label.parent.nil?
end
def children
db.smembers("#{scope}#{LABEL_INDEX}#{@label}").map { |member|
self.class.new(self.scope, "#{@label}#{GROUP_SEPARATOR}#{member}", self.date, @options)
}
end
def update_index
@label.groups.each do |label|
break if label.parent.nil?
db.sadd("#{scope}#{LABEL_INDEX}#{label.parent}", label.me)
end
end
def groups # TODO: Is this useless?
@groups ||= @label.groups.map do |label|
self.class.new(@scope, label, self.date, @options)
end
end

View File

@@ -2,7 +2,6 @@ module Redistat
class Label
include Database
attr_reader :raw
attr_reader :connection_ref
def self.create(name, options = {})
@@ -13,21 +12,25 @@ module Redistat
@options = options
@raw = str.to_s
end
def to_s
@raw
end
def db
super(@options[:connection_ref])
end
def name
@options[:hashed_label] ? hash : @raw
@options[:hashed_label] ? hash : self.to_s
end
def hash
@hash ||= Digest::SHA1.hexdigest(@raw)
@hash ||= Digest::SHA1.hexdigest(self.to_s)
end
def save
@saved = (db.set("#{KEY_LEBELS}#{hash}", @raw) == "OK") if @options[:hashed_label]
@saved = (db.set("#{KEY_LEBELS}#{hash}", self.to_s) == "OK") if @options[:hashed_label]
self
end
@@ -35,14 +38,22 @@ module Redistat
@saved ||= false
end
def parent
@parent ||= groups[1] if groups.size > 1
end
def me
self.to_s.split(GROUP_SEPARATOR).last
end
def groups
return @groups if @groups
return @groups unless @groups.nil?
@groups = []
parent = ""
@raw.split(GROUP_SEPARATOR).each do |part|
self.to_s.split(GROUP_SEPARATOR).each do |part|
if !part.blank?
group = ((parent.blank?) ? "" : "#{parent}/") + part
@groups << group
group = ((parent.blank?) ? "" : "#{parent}#{GROUP_SEPARATOR}") + part
@groups << Label.new(group)
parent = group
end
end

View File

@@ -21,7 +21,7 @@ module Redistat
alias :lookup :fetch
def find(label, from, till, opts = {})
Finder.new( { :scope => name,
Finder.new( { :scope => self.name,
:label => label,
:from => from,
:till => till }.merge(options.merge(opts)) )

View File

@@ -13,6 +13,7 @@ module Redistat
stats = inject_group_summaries(stats)
key.groups.each { |k|
update_key(k, stats, depth_limit, connection_ref)
k.update_index # TODO: add a label_indexing option
}
else
update_key(key, stats, depth_limit, connection_ref)

View File

@@ -27,4 +27,5 @@ Gem::Specification.new do |s|
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 'ruby-debug'
end

View File

@@ -18,7 +18,7 @@ describe Redistat::Event do
it "should initialize properly" do
@event.id.should be_nil
@event.scope.should == @scope
@event.label.should == @label
@event.label.to_s.should == @label
@event.label_hash.should == @label_hash
@event.date.to_time.to_s.should == @date.to_s
@event.stats.should == @stats
@@ -33,12 +33,12 @@ describe Redistat::Event do
@event.date = @date
@event.date.to_time.to_s.should == @date.to_s
# label
@event.label.should == @label
@event.label.to_s.should == @label
@event.label_hash.should == @label_hash
@label = "contact_us"
@label_hash = Digest::SHA1.hexdigest(@label)
@event.label = @label
@event.label.should == @label
@event.label.to_s.should == @label
@event.label_hash.should == @label_hash
end
@@ -64,7 +64,7 @@ describe Redistat::Event do
@event = Redistat::Event.new(@scope, @label, @date, @stats, @options.merge({:store_event => true}), @meta).save
fetched = Redistat::Event.find(@scope, @event.id)
@event.scope.should == fetched.scope
@event.label.should == fetched.label
@event.label.to_s.should == fetched.label.to_s
@event.date.to_s.should == fetched.date.to_s
end

View File

@@ -9,41 +9,43 @@ describe Redistat::Finder do
@label = "about_us"
@date = Time.now
@key = Redistat::Key.new(@scope, @label, @date, {:depth => :day})
@stats = {"views" => 3, "visitors" => 2}
@stats = {"views" => 3, "visitors" => 2}
@two_hours_ago = 2.hours.ago
@one_hour_ago = 1.hour.ago
end
it "should initialize properly" do
two_hours_ago = 2.hours.ago
one_hour_ago = 1.hour.ago
options = {:scope => "PageViews", :label => "Label", :from => two_hours_ago, :till => one_hour_ago, :depth => :hour, :interval => :hour}
finder = Redistat::Finder.new(options)
finder.options.should == options
options = {:scope => "PageViews", :label => "Label", :from => @two_hours_ago, :till => @one_hour_ago, :depth => :hour, :interval => :hour}
finder = Redistat::Finder.new
finder.send(:set_options, options)
finder.options.should == options
finder = Redistat::Finder.dates(two_hours_ago, one_hour_ago).scope("PageViews").label("Label").depth(:hour).interval(:hour)
finder.options.should == options
finder = Redistat::Finder.scope("PageViews").label("Label").from(two_hours_ago).till(one_hour_ago).depth(:hour).interval(:hour)
finder.options.should == options
finder.options[:scope].should be_a(Redistat::Scope)
finder.options[:scope].to_s.should == options[:scope]
finder.options[:label].should be_a(Redistat::Label)
finder.options[:label].to_s.should == options[:label]
finder.options.should == options.merge(:scope => finder.options[:scope], :label => finder.options[:label])
finder = Redistat::Finder.label("Label").from(two_hours_ago).till(one_hour_ago).depth(:hour).interval(:hour).scope("PageViews")
finder.options.should == options
finder = Redistat::Finder.dates(@two_hours_ago, @one_hour_ago)
finder.options[:from].should == @two_hours_ago
finder.options[:till].should == @one_hour_ago
finder = Redistat::Finder.from(two_hours_ago).till(one_hour_ago).depth(:hour).interval(:hour).scope("PageViews").label("Label")
finder.options.should == options
finder = Redistat::Finder.scope("hello")
finder.options[:scope].to_s.should == "hello"
finder = Redistat::Finder.till(one_hour_ago).depth(:hour).interval(:hour).scope("PageViews").label("Label").from(two_hours_ago)
finder.options.should == options
finder = Redistat::Finder.label("hello")
finder.options[:label].to_s.should == "hello"
finder = Redistat::Finder.depth(:hour).interval(:hour).scope("PageViews").label("Label").from(two_hours_ago).till(one_hour_ago)
finder.options.should == options
finder = Redistat::Finder.from(@two_hours_ago)
finder.options[:from].should == @two_hours_ago
finder = Redistat::Finder.interval(:hour).scope("PageViews").label("Label").from(two_hours_ago).till(one_hour_ago).depth(:hour)
finder.options.should == options
finder = Redistat::Finder.till(@one_hour_ago)
finder.options[:till].should == @one_hour_ago
finder = Redistat::Finder.depth(:hour)
finder.options[:depth].should == :hour
finder = Redistat::Finder.interval(:hour)
finder.options[:interval].should == :hour
end
@@ -89,6 +91,21 @@ describe Redistat::Finder do
lambda { Redistat::Finder.find(:from => 3.hours.ago) }.should raise_error(Redistat::InvalidOptions)
end
it "should find children" do
Redistat::Key.new("PageViews", "message/public/die").update_index
Redistat::Key.new("PageViews", "message/public/live").update_index
Redistat::Key.new("PageViews", "message/public/fester").update_index
members = db.smembers("#{@scope}#{Redistat::LABEL_INDEX}message/public") # checking 'message/public'
options = {:scope => "PageViews", :label => "message/public", :from => @two_hours_ago, :till => @one_hour_ago, :depth => :hour, :interval => :hour}
finder = Redistat::Finder.new(options)
finder.children.first.should be_a(Redistat::Finder)
subs = finder.children.map { |f| f.options[:label].me }
subs.should have(3).items
subs.should include('die')
subs.should include('live')
subs.should include('fester')
end
describe "Lazy-Loading" do
before(:each) do
@@ -103,7 +120,6 @@ describe Redistat::Finder do
end
it "should lazy-load" do
@finder.instance_variable_get("@result").should be_nil
stats = @finder.all
@finder.instance_variable_get("@result").should_not be_nil
@@ -146,7 +162,7 @@ describe Redistat::Finder do
res.should == match
end
end
end # "Lazy-Loading"
# helper methods

View File

@@ -1,8 +1,10 @@
require "spec_helper"
describe Redistat::Key do
include Redistat::Database
before(:each) do
db.flushdb
@scope = "PageViews"
@label = "about_us"
@label_hash = Digest::SHA1.hexdigest(@label)
@@ -12,9 +14,9 @@ describe Redistat::Key do
it "should initialize properly" do
@key.scope.should == @scope
@key.label.should == @label
@key.label.to_s.should == @label
@key.label_hash.should == @label_hash
@key.label_groups.should == @key.instance_variable_get("@label").groups
@key.groups.map { |k| k.instance_variable_get("@label") }.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
@@ -52,25 +54,70 @@ describe Redistat::Key do
@key.date = @date
@key.date.to_time.to_s.should == @date.to_s
# label
@key.label.should == @label
@key.label.to_s.should == @label
@key.label_hash == @label_hash
@label = "contact_us"
@label_hash = Digest::SHA1.hexdigest(@label)
@key.label = @label
@key.label.should == @label
@key.label.to_s.should == @label
@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" ]
describe "Grouping" do
before(:each) do
@label = "message/public/offensive"
@key = Redistat::Key.new(@scope, @label, @date, {:depth => :hour})
end
key = Redistat::Key.new(@scope, label, @date, {:depth => :hour})
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.groups.map { |k| k.label.to_s }.should == result
end
key.label_groups.should == result
key.groups.map { |k| k.label }.should == result
it "should know it's parent" do
@key.parent.should be_a(Redistat::Key)
@key.parent.label.to_s.should == 'message/public'
Redistat::Key.new(@scope, 'hello', @date).parent.should be_nil
end
it "should update label index and return children" do
db.smembers("#{@scope}#{Redistat::LABEL_INDEX}#{@key.label.parent}").should == []
@key.children.should have(0).items
@key.update_index # indexing 'message/publish/offensive'
Redistat::Key.new("PageViews", "message/public/die").update_index # indexing 'message/publish/die'
Redistat::Key.new("PageViews", "message/public/live").update_index # indexing 'message/publish/live'
members = db.smembers("#{@scope}#{Redistat::LABEL_INDEX}#{@key.label.parent}") # checking 'message/public'
members.should have(3).item
members.should include('offensive')
members.should include('live')
members.should include('die')
key = @key.parent
key.children.first.should be_a(Redistat::Key)
key.children.should have(3).item
key.children.map { |k| k.label.me }.should == members
members = db.smembers("#{@scope}#{Redistat::LABEL_INDEX}#{key.label.parent}") # checking 'message'
members.should have(1).item
members.should include('public')
key = key.parent
key.children.should have(1).item
key.children.map { |k| k.label.me }.should == members
members = db.smembers("#{@scope}#{Redistat::LABEL_INDEX}#{key.label.parent}") # checking ''
members.should have(0).item
key.parent.should be_nil
end
end
end

View File

@@ -25,24 +25,34 @@ 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" ]
describe "Grouping" do
before(:each) do
@name = "message/public/offensive"
@label = Redistat::Label.new(@name)
end
it "should know it's parent label group" do
@label.parent.to_s.should == 'message/public'
Redistat::Label.new('hello').parent.should be_nil
end
it "should separate label names into groups" do
@label.name.should == @name
@label.groups.map { |l| l.to_s }.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/public/"
@label = Redistat::Label.new(@name)
@label.name.should == @name
@label.groups.map { |l| l.to_s }.should == [ "message/public",
"message" ]
name = "message"
label = Redistat::Label.new(name)
label.name.should == name
label.groups.should == [ "message" ]
@name = "message"
@label = Redistat::Label.new(@name)
@label.name.should == @name
@label.groups.map { |l| l.to_s }.should == [ "message" ]
end
end
end

View File

@@ -22,8 +22,8 @@ describe Redistat::Model do
one_hour_ago = 1.hour.ago
finder = ModelHelper1.find('label', two_hours_ago, one_hour_ago)
finder.should be_a(Redistat::Finder)
finder.options[:scope].should == 'ModelHelper1'
finder.options[:label].should == 'label'
finder.options[:scope].to_s.should == 'ModelHelper1'
finder.options[:label].to_s.should == 'label'
finder.options[:from].should == two_hours_ago
finder.options[:till].should == one_hour_ago
end
@@ -82,6 +82,43 @@ describe Redistat::Model do
stats.first.should == stats.total
end
it "should store and fetch grouping enabled stats" do
ModelHelper1.store("sheep/black", {:count => 6, :weight => 461}, @time.hours_ago(4))
ModelHelper1.store("sheep/black", {:count => 2, :weight => 156}, @time)
ModelHelper1.store("sheep/white", {:count => 5, :weight => 393}, @time.hours_ago(4))
ModelHelper1.store("sheep/white", {:count => 4, :weight => 316}, @time)
stats = ModelHelper1.fetch("sheep/black", @time.hours_ago(2), @time.hours_since(1))
stats.total["count"].should == 2
stats.total["weight"].should == 156
stats.first.should == stats.total
stats = ModelHelper1.fetch("sheep/black", @time.hours_ago(5), @time.hours_since(1))
stats.total[:count].should == 8
stats.total[:weight].should == 617
stats.first.should == stats.total
stats = ModelHelper1.fetch("sheep/white", @time.hours_ago(2), @time.hours_since(1))
stats.total[:count].should == 4
stats.total[:weight].should == 316
stats.first.should == stats.total
stats = ModelHelper1.fetch("sheep/white", @time.hours_ago(5), @time.hours_since(1))
stats.total[:count].should == 9
stats.total[:weight].should == 709
stats.first.should == stats.total
stats = ModelHelper1.fetch("sheep", @time.hours_ago(2), @time.hours_since(1))
stats.total[:count].should == 6
stats.total[:weight].should == 472
stats.first.should == stats.total
stats = ModelHelper1.fetch("sheep", @time.hours_ago(5), @time.hours_since(1))
stats.total[:count].should == 17
stats.total[:weight].should == 1326
stats.first.should == stats.total
end
it "should connect to different Redis servers on a per-model basis" do
ModelHelper3.redis.client.db.should == 14

View File

@@ -83,8 +83,8 @@ describe Redistat::Summary do
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"
key.groups[0].label.to_s.should == "views/about_us"
key.groups[1].label.to_s.should == "views"
child1 = key.groups[0]
parent = key.groups[1]
@@ -92,8 +92,8 @@ describe Redistat::Summary do
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"
key.groups[0].label.to_s.should == "views/contact"
key.groups[1].label.to_s.should == "views"
child2 = key.groups[0]
summary = db.hgetall(child1.to_s(:hour))