56 Commits

Author SHA1 Message Date
ecbd15ef1e Merge branch 'release/v0.2.4' 2011-03-14 11:14:01 +00:00
2b2461dd9f started release v0.2.4 2011-03-14 11:13:50 +00:00
3e177c9ae4 changed json dependency back to '>= 1.4.0' for backwards compatibility with certain older projects/gems, make sure you're using 1.5.0 or later of the json gem for JRuby support 2011-03-14 11:13:06 +00:00
d4cd5402bc create Label#join method for easily joining Labels 2011-03-14 11:10:24 +00:00
d560a7deff killed an old line of commented out code 2011-03-14 10:38:16 +00:00
3df6666704 additions to specs 2011-03-14 10:37:56 +00:00
80fd63059b updated time_ext dependency to ensure there's no loading issues within Rails applications 2011-03-13 23:47:02 +00:00
d0b7f097a8 updated json dependency to '>= 1.5.0' to ensure JRuby compatability 2011-03-13 23:46:00 +00:00
875d16b01c Merge branch 'release/v0.2.3' into dev 2011-03-13 20:30:21 +00:00
00791d36d8 Merge branch 'release/v0.2.3' 2011-03-13 20:30:17 +00:00
94b589c5e6 started release v0.2.3 2011-03-13 20:30:11 +00:00
cdf52869d6 added #find_event method to Model 2011-03-13 20:28:17 +00:00
8b711d4d9c fixed a bug with Event#find 2011-03-13 20:24:06 +00:00
e4aaedfe58 made Key#scope return Scope object instead of Scope#to_s 2011-03-13 20:23:41 +00:00
ea820d44f4 fixed typo in Finder spec 2011-03-13 19:52:30 +00:00
acedf071d1 improved options passed into Finder object, :depth option is not needed if :interval is set to a depth value instead of true 2011-03-13 19:51:04 +00:00
108b6ab02e Finder's options methods now set the option when an argument is supplied and returns self for method chaining. When no argument is supplied it returns the option value itself.
Example:

    finder = Redistat::Finder.new
    finder.scope("Foo") #=> Finder object
    finder.scope        #=> Scope object
    finder.scope.to_s   #=> "Foo"
2011-03-13 19:46:52 +00:00
9920c0dc2f Merge branch 'release/v0.2.2' into dev 2011-03-12 22:26:58 +00:00
a72ad31c51 Merge branch 'release/v0.2.2' 2011-03-12 22:26:52 +00:00
008228660e started release v0.2.2 2011-03-12 22:26:12 +00:00
b8ddcdf71a Merge remote-tracking branch 'origin/dev' into dev 2011-03-12 22:25:35 +00:00
94fcd5b4ae fixed a ruby 1.9.x issue 2011-03-12 22:24:06 +00:00
e2a551d01c syntax beautification 2011-03-12 22:23:06 +00:00
43fc8bc2dd updated Hash extensions and specs 2011-03-12 22:21:15 +00:00
dcca3556ea some cleanup 2011-03-12 22:00:21 +00:00
d5f79b82a9 somewhat better loading procedure 2011-03-12 21:59:52 +00:00
0938781cd1 extend ::Hash with #set_or_incr and #merge_and_incr methods 2011-03-12 21:47:54 +00:00
82119fcf69 wrong usage of interval method in Finder spec 2011-03-11 15:38:10 +00:00
746d0fea8f Merge branch 'release/v0.2.1' into dev 2011-03-10 16:31:42 +00:00
331bf81e3a Merge branch 'release/v0.2.1' 2011-03-10 16:31:34 +00:00
f2c026c1eb started release v0.2.1 2011-03-10 16:27:24 +00:00
57517983f6 added #parent method to Finder objects 2011-03-10 16:26:38 +00:00
53aee885bd removed defunct TODO comment 2011-03-10 10:42:24 +00:00
8001a98a26 fixed a typo... ffs... 2011-03-10 10:42:10 +00:00
34331a655e Merge branch 'release/v0.2.0' into dev 2011-03-10 00:49:22 +00:00
91ad5b2d3c Merge branch 'release/v0.2.0' 2011-03-10 00:49:18 +00:00
9fd5ae8545 started release v0.2.0 2011-03-10 00:49:07 +00:00
14b7f4768e added credits section to readme 2011-03-10 00:47:14 +00:00
57274ffb21 updated reverse label hash lookup storage format, which might be a pain if you have been using the hashed_label option 2011-03-10 00:46:55 +00:00
8d063c98e5 Merge branch 'feature/options' into dev 2011-03-10 00:27:26 +00:00
d39d5d8dde most components use new Options helper module 2011-03-10 00:27:13 +00:00
3a00353f83 created Options module to help organize the multiple options passed from one object to another 2011-03-10 00:24:35 +00:00
49fc2afcfd added a FIXME comment about broken model spec till index_labels option is implemented 2011-03-09 22:50:04 +00:00
cfbe58a509 support indexing top-level labels too 2011-03-09 22:48:27 +00:00
629f46ed89 Merge branch 'feature/label-indexing' into dev 2011-03-09 17:28:46 +00:00
9faa0db7b8 drastic change in label indexing 2011-03-09 17:05:10 +00:00
834614ab79 added ruby-debug to development dependencies 2011-03-09 15:57:12 +00:00
47a1b0737c better readability 2011-03-09 11:57:34 +00:00
5d3c181641 update Label index when saving Label object if grouping is enabled and used 2011-03-09 11:57:18 +00:00
e3f23433d9 cleaned up #sub_labels feature in Label object 2011-03-09 11:56:42 +00:00
482253f517 make Finder work with Scope and Label objects rather than strings 2011-03-09 11:55:42 +00:00
66b9f4e949 less code duplication 2011-03-09 11:54:51 +00:00
7a28d1210f added a todo item about a typo 2011-03-09 11:00:08 +00:00
d74dc41110 added label indexing features when using label groupings 2011-03-09 10:59:53 +00:00
ac338bb4f0 added #parent_group method to Label and Key objects 2011-03-09 10:25:37 +00:00
325a264411 Merge branch 'release/v0.1.1' into dev 2011-03-09 01:34:55 +00:00
25 changed files with 603 additions and 254 deletions

View File

@@ -66,6 +66,11 @@ When retrieving statistics for a given date range, Redistat figures out how to d
* Anything else that becomes apparent after real-world use.
## 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
* Fork the project.

View File

@@ -1,22 +1,28 @@
require 'rubygems'
require 'active_support'
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'
require 'time_ext'
require 'json'
require 'digest/sha1'
require 'redistat/collection'
# Active Support 2.x or 3.x
require 'active_support'
if !{}.respond_to?(:with_indifferent_access)
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/reverse_merge'
end
require 'time_ext'
require 'redis'
require 'json'
require 'redistat/options'
require 'redistat/connection'
require 'redistat/database'
require 'redistat/collection'
require 'redistat/date'
require 'redistat/date_helper'
require 'redistat/event'
require 'redistat/finder'
require 'redistat/finder/date_set'
require 'redistat/key'
require 'redistat/label'
require 'redistat/model'
@@ -25,17 +31,15 @@ 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'
require 'redistat/core_ext'
module Redistat
KEY_NEXT_ID = ".next_id"
KEY_EVENT = ".event:"
KEY_LEBELS = "Redistat.lables:"
KEY_LABELS = "Redistat.labels:" # used for reverse label hash lookup
KEY_EVENT_IDS = ".event_ids"
LABEL_INDEX = ".label_index:"
GROUP_SEPARATOR = "/"
class InvalidOptions < ArgumentError; end

5
lib/redistat/core_ext.rb Normal file
View File

@@ -0,0 +1,5 @@
require 'redistat/core_ext/bignum'
require 'redistat/core_ext/date'
require 'redistat/core_ext/fixnum'
require 'redistat/core_ext/hash'
require 'redistat/core_ext/time'

View File

@@ -0,0 +1,23 @@
class Hash
def merge_and_incr(hash)
self.clone.merge_and_incr!(hash)
end
def merge_and_incr!(hash)
raise ArgumentError unless hash.is_a?(Hash)
hash.each do |key, value|
self[key] = value unless self.set_or_incr(key, value)
end
self
end
def set_or_incr(key, value)
return false unless value.is_a?(Numeric)
self[key] = 0 unless self.has_key?(key)
return false unless self[key].is_a?(Numeric)
self[key] += value
true
end
end

View File

@@ -4,6 +4,7 @@ module Redistat
base.extend(Database)
end
def db(ref = nil)
ref ||= @options[:connection_ref] if !@options.nil?
Redistat.connection(ref)
end
end

View File

@@ -1,40 +1,28 @@
module Redistat
class Event
include Database
include Options
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 = 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,
:connection_ref => nil,
:enable_grouping => true }
:enable_grouping => true,
:label_indexing => true }
end
def initialize(scope, label = nil, date = nil, stats = {}, opts = {}, meta = {}, is_new = true)
parse_options(opts)
@key = Key.new(scope, label, date, @options)
@stats = stats ||= {}
@meta = meta ||= {}
@new = is_new
end
def new?
@@ -75,7 +63,7 @@ module Redistat
def save
return false if !self.new?
Summary.update_all(@key, @stats, depth_limit, @connection_ref, @options[:enable_grouping])
Summary.update_all(@key, @stats, depth_limit, @options)
if @options[:store_event]
@id = self.next_id
db.hmset("#{self.scope}#{KEY_EVENT}#{@id}",
@@ -103,7 +91,7 @@ module Redistat
event = db.hgetall "#{scope}#{KEY_EVENT}#{id}"
return nil if event.size == 0
self.new( event["scope"], event["label"], event["date"], JSON.parse(event["stats"]),
JSON.parse(event["meta"]), JSON.parse(event["options"]), false )
JSON.parse(event["options"]), JSON.parse(event["meta"]), false )
end
end

View File

@@ -1,3 +1,5 @@
require 'redistat/finder/date_set'
module Redistat
class Finder
include Database
@@ -40,8 +42,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,58 +71,75 @@ module Redistat
all.each_with_index(&block)
end
def connection_ref(ref)
reset! if @options[:connection_ref] != ref
@options[:connection_ref] = ref
def parent
@parent ||= self.class.new(options.merge(:label => options[:label].parent)) unless options[:label].nil?
end
def children
build_key.children.map { |key|
self.class.new(options.merge(:label => key.label.to_s))
}
end
def connection_ref(ref = nil)
return options[:connection_ref] if ref.nil?
reset! if options[:connection_ref] != ref
options[:connection_ref] = ref
self
end
def scope(scope)
reset! if @options[:scope] != scope
@options[:scope] = scope
def scope(input = nil)
return options[:scope] if input.nil?
reset! if !options[:scope].nil? && options[:scope].to_s != input.to_s
options[:scope] = Scope.new(input)
self
end
def label(label)
reset! if @options[:label] != label
@options[:label] = label
def label(input = nil)
return options[:label] if input.nil?
reset! if options.has_key?(:label) && options[:label].to_s != input.to_s
options[:label] = (!input.nil?) ? Label.new(input) : nil
self
end
def dates(from, till)
from(from).till(till)
def dates(start, finish)
from(start).till(finish)
end
alias :date :dates
def from(date)
reset! if @options[:from] != date
@options[:from] = date
def from(date = nil)
return options[:from] if date.nil?
reset! if options[:from] != date
options[:from] = date
self
end
def till(date)
reset! if @options[:till] != date
@options[:till] = date
def till(date = nil)
return options[:till] if date.nil?
reset! if options[:till] != date
options[:till] = date
self
end
alias :until :till
def depth(unit)
reset! if @options[:depth] != unit
@options[:depth] = unit
def depth(unit = nil)
return options[:depth] if unit.nil?
reset! if options[:depth] != unit
options[:depth] = unit
self
end
def interval(unit)
reset! if @options[:interval] != unit
@options[:interval] = unit
def interval(unit = nil)
return options[:interval] if unit.nil?
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 +153,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 +175,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
@@ -171,19 +194,20 @@ module Redistat
def reset!
@result = nil
@parent = nil
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 +229,7 @@ module Redistat
end
def db
super(@options[:connection_ref])
super(options[:connection_ref])
end
end

View File

@@ -9,6 +9,10 @@ module Redistat
end
def find_date_sets(start_date, end_date, depth = nil, interval = false)
if depth.nil? && interval.is_a?(Symbol)
depth = interval
interval = true
end
start_date = start_date.to_time if start_date.is_a?(::Date)
end_date = end_date.to_time if end_date.is_a?(::Date)
if !interval

View File

@@ -1,24 +1,22 @@
module Redistat
class Key
include Database
include Options
attr_accessor :scope
attr_accessor :date
attr_accessor :options
def default_options
{ :depth => :hour }
end
def initialize(scope, label_name = nil, time_stamp = nil, options = {})
@options = default_options.merge(options || {})
@scope = scope
def initialize(scope, label_name = nil, time_stamp = nil, opts = {})
parse_options(opts)
self.scope = scope
self.label = label_name if !label_name.nil?
self.date = time_stamp ||= Time.now
end
def default_options
{ :depth => :hour, :hashed_label => false }
end
def prefix
key = "#{@scope}"
key << "/#{label}" if !label.nil?
key << "/#{label.name}" if !label.nil?
key << ":"
key
end
@@ -26,30 +24,51 @@ 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]
options[:depth]
end
def label
@label.name
# def scope
# @scope.to_s
# end
def scope=(input)
@scope = (input.instance_of?(Redistat::Scope)) ? input : Scope.new(input)
end
attr_reader :scope
def label=(input)
@label = (input.instance_of?(Redistat::Label)) ? input : Label.create(input, @options)
end
attr_reader :label
def label_hash
@label.hash
end
def label_groups
@label.groups
def parent
@parent ||= self.class.new(self.scope, @label.parent, self.date, @options) unless @label.parent.nil?
end
def label=(input)
@label = (input.instance_of?(Redistat::Label)) ? input : Label.create(input, @options)
def children
db.smembers("#{scope}#{LABEL_INDEX}#{@label}").map { |member|
child_label = [@label, member].reject { |i| i.nil? }
self.class.new(self.scope, child_label.join(GROUP_SEPARATOR), self.date, @options)
}
end
def update_index
@label.groups.each do |label|
parent = (label.parent || "")
db.sadd("#{scope}#{LABEL_INDEX}#{parent}", label.me)
end
end
def groups
@groups ||= label_groups.map do |label_name|
self.class.new(@scope, label_name, self.date, @options)
@groups ||= @label.groups.map do |label|
self.class.new(@scope, label, self.date, @options)
end
end

View File

@@ -1,48 +1,64 @@
module Redistat
class Label
include Database
include Options
attr_reader :raw
attr_reader :connection_ref
def self.create(name, options = {})
self.new(name, options).save
def default_options
{ :hashed_label => false }
end
def initialize(str, options = {})
@options = options
def self.create(name, opts = {})
self.new(name, opts).save
end
def self.join(*args)
args = args.map {|i| i.to_s}
self.new(args.reject {|i| i.blank? }.join(GROUP_SEPARATOR))
end
def initialize(str, opts = {})
parse_options(opts)
@raw = str.to_s
end
def db
super(@options[:connection_ref])
def to_s
@raw
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.hset(KEY_LABELS, hash, self.to_s) if @options[:hashed_label]
self
end
def saved?
return true unless @options[:hashed_label]
@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

@@ -1,17 +1,19 @@
module Redistat
module Model
include Redistat::Database
include Database
include Options
def self.included(base)
base.extend(self)
end
#
# statistics store/fetch methods
#
def store(label, stats = {}, date = nil, meta = {}, opts = {})
Event.new(name, label, date, stats, options.merge(opts), meta).save
def store(label, stats = {}, date = nil, opts = {}, meta = {})
Event.new(self.name, label, date, stats, options.merge(opts), meta).save
end
alias :event :store
@@ -21,53 +23,34 @@ 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)) )
end
def find_event(event_id)
Event.find(self.name, event_id)
end
#
# options methods
#
option_accessor :depth
option_accessor :class_name
option_accessor :store_event
option_accessor :hashed_label
option_accessor :label_indexing
alias :scope :class_name
def connect_to(opts = {})
Connection.create(opts.merge(:ref => name))
options[:connection_ref] = name
end
def hashed_label(boolean = nil)
if !boolean.nil?
options[:hashed_label] = boolean
else
options[:hashed_label] || nil
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
else
options[:depth] || nil
end
end
def store_event(boolean = nil)
if !boolean.nil?
options[:store_event] = boolean
else
options[:store_event] || nil
end
end
#
# resource access methods
@@ -78,10 +61,6 @@ module Redistat
end
alias :redis :connection
def options
@options ||= {}
end
def name
options[:class_name] || (@name ||= self.to_s)
end

41
lib/redistat/options.rb Normal file
View File

@@ -0,0 +1,41 @@
module Redistat
module Options
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def option_accessor(*opts)
opts.each do |option|
define_method(option) do |*args|
if !args.first.nil?
options[option.to_sym] = args.first
else
options[option.to_sym] || nil
end
end
end
end
end
def parse_options(opts)
opts ||= {}
@raw_options = opts
@options = default_options.merge(opts.reject { |k,v| v.nil? })
end
def default_options
{}
end
def options
@options ||= {}
end
def raw_options
@raw_options ||= {}
end
end
end

View File

@@ -12,12 +12,5 @@ module Redistat
@till = options[:till] ||= nil
end
def set_or_incr(key, value)
self[key] = 0 if !self.has_key?(key)
self[key] += value
self
end
end
end
end

View File

@@ -2,20 +2,28 @@ module Redistat
class Summary
include Database
def self.update_all(key, stats = {}, depth_limit = nil, connection_ref = nil, enable_grouping = nil)
def self.default_options
{ :enable_grouping => true,
:label_indexing => true,
:connection_ref => nil }
end
def self.update_all(key, stats = {}, depth_limit = nil, opts = {})
stats ||= {}
return nil if stats.size == 0
depth_limit ||= key.depth
enable_grouping = true if enable_grouping.nil?
options = default_options.merge((opts || {}).reject { |k,v| v.nil? })
if enable_grouping
depth_limit ||= key.depth
if options[:enable_grouping]
stats = inject_group_summaries(stats)
key.groups.each { |k|
update_key(k, stats, depth_limit, connection_ref)
}
key.groups.each do |k|
update_key(k, stats, depth_limit, options[:connection_ref])
k.update_index if options[:label_indexing]
end
else
update_key(key, stats, depth_limit, connection_ref)
update_key(key, stats, depth_limit, options[:connection_ref])
end
end
@@ -35,6 +43,7 @@ module Redistat
end
def self.inject_group_summaries!(stats)
summaries = {}
stats.each do |key, value|
parts = key.to_s.split(GROUP_SEPARATOR)
parts.pop
@@ -43,11 +52,11 @@ module Redistat
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
(summaries.has_key?(sum_key)) ? summaries[sum_key] += value : summaries[sum_key] = value
end
end
end
stats
stats.merge_and_incr!(summaries)
end
def self.inject_group_summaries(stats)

View File

@@ -1,3 +1,3 @@
module Redistat
VERSION = "0.1.1"
VERSION = "0.2.4"
end

View File

@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
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_runtime_dependency 'time_ext', '>= 0.2.9'
s.add_development_dependency 'rspec', '>= 2.1.0'
s.add_development_dependency 'rcov', '>= 0.9.9'

View File

@@ -0,0 +1,30 @@
require "spec_helper"
describe Hash do
it "should #set_or_incr values" do
hash = {:count => 1}
hash.set_or_incr(:sum, 3).should be_true
hash.should == {:count => 1, :sum => 3}
hash.set_or_incr(:count, 4).should be_true
hash.should == {:count => 5, :sum => 3}
hash.set_or_incr(:count, 'test').should be_false
hash.set_or_incr(:view, 'test').should be_false
hash.should == {:count => 5, :sum => 3}
hash[:view] = 'test'
hash.set_or_incr(:view, 3).should be_false
end
it "should #merge_and_incr hashes" do
hash = { :count => 1, :city => 'hell', :sum => 3, :name => 'john' }
new_hash = { :count => 3, :city => 'slum', :views => 2 }
hash.clone.merge_and_incr(new_hash).should == { :count => 4, :city => 'slum', :views => 2,
:sum => 3, :name => 'john' }
new_hash = { :count => 'six', :city => 'slum', :views => 2, :time => 'late' }
hash.clone.merge_and_incr(new_hash).should == { :count => 'six', :city => 'slum', :views => 2,
:sum => 3, :name => 'john', :time => 'late' }
end
end

View File

@@ -8,8 +8,8 @@ describe Redistat::Event do
@scope = "PageViews"
@label = "about_us"
@label_hash = Digest::SHA1.hexdigest(@label)
@stats = {:views => 1}
@meta = {:user_id => 239}
@stats = {'views' => 1}
@meta = {'user_id' => 239}
@options = {:depth => :hour}
@date = Time.now
@event = Redistat::Event.new(@scope, @label, @date, @stats, @options, @meta)
@@ -17,8 +17,8 @@ describe Redistat::Event do
it "should initialize properly" do
@event.id.should be_nil
@event.scope.should == @scope
@event.label.should == @label
@event.scope.to_s.should == @scope
@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
@@ -63,9 +63,11 @@ describe Redistat::Event do
it "should find event by id" 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.scope.to_s.should == fetched.scope.to_s
@event.label.to_s.should == fetched.label.to_s
@event.date.to_s.should == fetched.date.to_s
@event.stats.should == fetched.stats
@event.meta.should == fetched.meta
end
it "should store summarized statistics" do

View File

@@ -28,11 +28,13 @@ describe Redistat::Finder::DateSet do
result = Redistat::Finder::DateSet.new.find_date_sets(t_start, t_end, :hour, true)
result[0][:add].should == ["2010082818", "2010082819", "2010082820", "2010082821", "2010082822"]
result[0][:rem].should == []
result.should == Redistat::Finder::DateSet.new(t_start, t_end, nil, :hour)
t_end = t_start + 4.days
result = Redistat::Finder::DateSet.new.find_date_sets(t_start, t_end, :day, true)
result[0][:add].should == ["20100828", "20100829", "20100830", "20100831", "20100901"]
result[0][:rem].should == []
result.should == Redistat::Finder::DateSet.new(t_start, t_end, nil, :day)
end
it "should find start keys properly" do

View File

@@ -9,42 +9,52 @@ 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.scope("hello")
finder.options[:scope].to_s.should == "hello"
finder.scope.to_s.should == "hello"
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.label("hello")
finder.options[:label].to_s.should == "hello"
finder.label.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.dates(@two_hours_ago, @one_hour_ago)
finder.options[:from].should == @two_hours_ago
finder.options[:till].should == @one_hour_ago
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.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.till.should == @one_hour_ago
finder = Redistat::Finder.depth(:hour)
finder.options[:depth].should == :hour
finder.depth.should == :hour
finder = Redistat::Finder.interval(true)
finder.options[:interval].should be_true
finder.interval.should be_true
finder = Redistat::Finder.interval(false)
finder.options[:interval].should be_false
finder.interval.should be_false
end
it "should fetch stats properly" do
@@ -89,6 +99,38 @@ describe Redistat::Finder do
lambda { Redistat::Finder.find(:from => 3.hours.ago) }.should raise_error(Redistat::InvalidOptions)
end
describe "Grouping" do
before(:each) do
@options = {:scope => "PageViews", :label => "message/public", :from => @two_hours_ago, :till => @one_hour_ago, :depth => :hour, :interval => :hour}
@finder = Redistat::Finder.new(@options)
end
it "should return parent finder" do
@finder.instance_variable_get("@parent").should be_nil
@finder.parent.should be_a(Redistat::Finder)
@finder.instance_variable_get("@parent").should_not be_nil
@finder.parent.options[:label].to_s.should == 'message'
@finder.label('message')
@finder.instance_variable_get("@parent").should be_nil
@finder.parent.should_not be_nil
@finder.parent.options[:label].should be_nil
@finder.parent.parent.should be_nil
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'
@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
end
describe "Lazy-Loading" do
before(:each) do
@@ -103,7 +145,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 +187,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)
@@ -11,10 +13,10 @@ describe Redistat::Key do
end
it "should initialize properly" do
@key.scope.should == @scope
@key.label.should == @label
@key.scope.to_s.should == @scope
@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
@@ -26,6 +28,8 @@ describe Redistat::Key do
@key.to_s(props.last).should == "#{@scope}/#{@label}:#{@key.date.to_s(props.last)}"
props.pop
end
key = Redistat::Key.new(@scope, nil, @date, {:depth => :hour})
key.to_s.should == "#{@scope}:#{key.date.to_s(:hour)}"
end
it "should abide to hashed_label option" do
@@ -42,35 +46,84 @@ describe Redistat::Key do
it "should allow changing attributes" do
# scope
@key.scope.should == @scope
@key.scope.to_s.should == @scope
@scope = "VisitorCount"
@key.scope = @scope
@key.scope.should == @scope
@key.scope.to_s.should == @scope
# date
@key.date.to_time.to_s.should == @date.to_s
@date = Time.now
@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}") # checking ''
members.should have(1).item
members.should include('message')
key.parent.should be_nil
key = Redistat::Key.new("PageViews")
key.children.should have(1).item
key.children.map { |k| k.label.me }.should include('message')
end
end
end

View File

@@ -17,32 +17,55 @@ describe Redistat::Label do
it "should store a label hash lookup key" do
label = Redistat::Label.new(@name, {:hashed_label => true}).save
label.saved?.should be_true
db.get("#{Redistat::KEY_LEBELS}#{label.hash}").should == @name
db.hget(Redistat::KEY_LABELS, label.hash).should == @name
name = "contact_us"
label = Redistat::Label.create(name, {:hashed_label => true})
label.saved?.should be_true
db.get("#{Redistat::KEY_LEBELS}#{label.hash}").should == name
db.hget(Redistat::KEY_LABELS, 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" ]
it "should join labels" do
include Redistat
label = Label.join('email', 'message', 'public')
label.should be_a(Label)
label.to_s.should == 'email/message/public'
label = Label.join(Label.new('email'), Label.new('message'), Label.new('public'))
label.should be_a(Label)
label.to_s.should == 'email/message/public'
label = Label.join('email', '', 'message', nil, 'public')
label.should be_a(Label)
label.to_s.should == 'email/message/public'
end
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,12 +22,17 @@ 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
it "should #find_event" do
Redistat::Event.should_receive(:find).with('ModelHelper1', 1)
ModelHelper1.find_event(1)
end
it "should listen to model-defined options" do
ModelHelper2.depth.should == :day
ModelHelper2.store_event.should == true
@@ -82,34 +87,71 @@ 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
ModelHelper3.store("sheep.black", {:count => 6, :weight => 461}, @time.hours_ago(4))
ModelHelper3.store("sheep.black", {:count => 2, :weight => 156}, @time)
ModelHelper3.store("sheep.black", {:count => 6, :weight => 461}, @time.hours_ago(4), :label_indexing => false)
ModelHelper3.store("sheep.black", {:count => 2, :weight => 156}, @time, :label_indexing => false)
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", @time.hours_ago(2), @time.hours_since(1))
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(2), @time.hours_since(1), :label_indexing => false)
stats.total["count"].should == 2
stats.total["weight"].should == 156
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1), :label_indexing => false)
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", @time.hours_ago(5), @time.hours_since(1))
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1), :label_indexing => false)
stats.total.should == {}
ModelHelper3.connect_to(:port => 8379, :db => 14)
ModelHelper3.redis.client.db.should == 14
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1), :label_indexing => false)
stats.total[:count].should == 8
stats.total[:weight].should == 617
end

36
spec/options_spec.rb Normal file
View File

@@ -0,0 +1,36 @@
require "spec_helper"
describe Redistat::Options do
before(:each) do
@helper = OptionsHelper.new
@helper.parse_options(:wtf => 'dude', :foo => 'booze')
end
it "should #parse_options" do
@helper.options[:hello].should == 'world'
@helper.options[:foo].should == 'booze'
@helper.options[:wtf].should == 'dude'
@helper.raw_options.should_not have_key(:hello)
end
it "should create option_accessors" do
@helper.hello.should == 'world'
@helper.hello('woooo')
@helper.hello.should == 'woooo'
end
end
class OptionsHelper
include Redistat::Options
option_accessor :hello
def default_options
{ :hello => 'world',
:foo => 'bar' }
end
end

View File

@@ -46,6 +46,15 @@ describe Redistat::Summary do
end
end
it "should update summaries even if no label is set" do
key = Redistat::Key.new(@scope, nil, @date, {:depth => :day})
Redistat::Summary.update(key, @stats, :hour)
summary = db.hgetall(key.to_s(:hour))
summary.should have(2).items
summary["views"].should == "3"
summary["visitors"].should == "2"
end
it "should inject stats key grouping summaries" do
hash = { "count/hello" => 3, "count/world" => 7,
"death/bomb" => 4, "death/unicorn" => 3,
@@ -69,7 +78,7 @@ describe Redistat::Summary do
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)
Redistat::Summary.update_all(@key, stats, :hour, {:enable_grouping => false})
summary = db.hgetall(@key.to_s(:hour))
summary.should have(3).items
summary["views"].should == "3"
@@ -83,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/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 +101,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))