redistat 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +25 -0
- data/.rspec +2 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +31 -0
- data/LICENSE +20 -0
- data/README.md +44 -0
- data/Rakefile +71 -0
- data/VERSION +1 -0
- data/lib/redistat.rb +104 -0
- data/lib/redistat/collection.rb +16 -0
- data/lib/redistat/core_ext/date.rb +8 -0
- data/lib/redistat/core_ext/fixnum.rb +8 -0
- data/lib/redistat/core_ext/time.rb +3 -0
- data/lib/redistat/database.rb +10 -0
- data/lib/redistat/date.rb +93 -0
- data/lib/redistat/event.rb +94 -0
- data/lib/redistat/finder.rb +165 -0
- data/lib/redistat/finder/date_set.rb +95 -0
- data/lib/redistat/key.rb +54 -0
- data/lib/redistat/label.rb +27 -0
- data/lib/redistat/model.rb +50 -0
- data/lib/redistat/result.rb +23 -0
- data/lib/redistat/scope.rb +18 -0
- data/lib/redistat/summary.rb +24 -0
- data/spec/_redistat_spec.rb +34 -0
- data/spec/collection_spec.rb +13 -0
- data/spec/date_spec.rb +95 -0
- data/spec/event_spec.rb +83 -0
- data/spec/finder/date_set_spec.rb +525 -0
- data/spec/finder_spec.rb +112 -0
- data/spec/key_spec.rb +63 -0
- data/spec/label_spec.rb +28 -0
- data/spec/model_helper.rb +15 -0
- data/spec/model_spec.rb +60 -0
- data/spec/redis-test.conf +9 -0
- data/spec/result_spec.rb +21 -0
- data/spec/scope_spec.rb +27 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/summary_spec.rb +72 -0
- metadata +216 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module Redistat
|
2
|
+
class Event
|
3
|
+
include Database
|
4
|
+
|
5
|
+
attr_reader :id
|
6
|
+
attr_reader :key
|
7
|
+
|
8
|
+
attr_accessor :stats
|
9
|
+
attr_accessor :meta
|
10
|
+
attr_accessor :options
|
11
|
+
|
12
|
+
def initialize(scope, label = nil, date = nil, stats = {}, options = {}, meta = {}, is_new = true)
|
13
|
+
@options = default_options.merge(options)
|
14
|
+
@key = Key.new(scope, label, date, @options)
|
15
|
+
@stats = stats ||= {}
|
16
|
+
@meta = meta ||= {}
|
17
|
+
@new = is_new
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_options
|
21
|
+
{ :depth => :hour, :store_event => false }
|
22
|
+
end
|
23
|
+
|
24
|
+
def new?
|
25
|
+
@new
|
26
|
+
end
|
27
|
+
|
28
|
+
def date
|
29
|
+
@key.date
|
30
|
+
end
|
31
|
+
|
32
|
+
def date=(input)
|
33
|
+
@key.date = input
|
34
|
+
end
|
35
|
+
|
36
|
+
def scope
|
37
|
+
@key.scope
|
38
|
+
end
|
39
|
+
|
40
|
+
def scope=(input)
|
41
|
+
@key.scope = input
|
42
|
+
end
|
43
|
+
|
44
|
+
def label
|
45
|
+
@key.label
|
46
|
+
end
|
47
|
+
|
48
|
+
def label_hash
|
49
|
+
@key.label_hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def label=(input)
|
53
|
+
@key.label = input
|
54
|
+
end
|
55
|
+
|
56
|
+
def next_id
|
57
|
+
db.incr("#{self.scope}#{KEY_NEXT_ID}")
|
58
|
+
end
|
59
|
+
|
60
|
+
def save
|
61
|
+
return false if !self.new?
|
62
|
+
Summary.update_all(@key, @stats, depth_limit)
|
63
|
+
if @options[:store_event]
|
64
|
+
@id = self.next_id
|
65
|
+
db.hmset("#{self.scope}#{KEY_EVENT}#{@id}",
|
66
|
+
"scope", self.scope,
|
67
|
+
"label", self.label,
|
68
|
+
"date", self.date.to_time.to_s,
|
69
|
+
"stats", self.stats.to_json,
|
70
|
+
"meta", self.meta.to_json,
|
71
|
+
"options", self.options.to_json)
|
72
|
+
db.sadd("#{self.scope}#{KEY_EVENT_IDS}", @id)
|
73
|
+
end
|
74
|
+
@new = false
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def depth_limit
|
79
|
+
@options[:depth] ||= @key.depth
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.create(*args)
|
83
|
+
self.new(*args).save
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.find(scope, id)
|
87
|
+
event = db.hgetall "#{scope}#{KEY_EVENT}#{id}"
|
88
|
+
return nil if event.size == 0
|
89
|
+
self.new( event["scope"], event["label"], event["date"], JSON.parse(event["stats"]),
|
90
|
+
JSON.parse(event["meta"]), JSON.parse(event["options"]), false )
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Redistat
|
2
|
+
class Finder
|
3
|
+
include Database
|
4
|
+
|
5
|
+
attr_reader :options
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid_options?
|
12
|
+
return true if !@options[:scope].blank? && !@options[:label].blank? && !@options[:from].blank? && !@options[:till].blank?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def find(options = {})
|
17
|
+
@options.merge!(options)
|
18
|
+
raise InvalidOptions.new if !valid_options?
|
19
|
+
if @options[:interval].nil? || !@options[:interval]
|
20
|
+
find_by_magic
|
21
|
+
else
|
22
|
+
find_by_interval
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_by_interval(options = {})
|
27
|
+
@options.merge!(options)
|
28
|
+
raise InvalidOptions.new if !valid_options?
|
29
|
+
key = build_key
|
30
|
+
col = Collection.new(@options)
|
31
|
+
col.total = Result.new(@options)
|
32
|
+
build_date_sets.each do |set|
|
33
|
+
set[:add].each do |date|
|
34
|
+
result = Result.new
|
35
|
+
result.date = Date.new(date).to_time
|
36
|
+
db.hgetall("#{key.prefix}#{date}").each do |k, v|
|
37
|
+
result[k] = v
|
38
|
+
col.total.set_or_incr(k, v.to_i)
|
39
|
+
end
|
40
|
+
col << result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
col
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_by_magic(options = {})
|
47
|
+
@options.merge!(options)
|
48
|
+
raise InvalidOptions.new if !valid_options?
|
49
|
+
key = Key.new(@options[:scope], @options[:label])
|
50
|
+
col = Collection.new(@options)
|
51
|
+
col.total = Result.new(@options)
|
52
|
+
col << col.total
|
53
|
+
build_date_sets.each do |set|
|
54
|
+
sum = Result.new
|
55
|
+
sum = summarize_add_keys(set[:add], key, sum)
|
56
|
+
sum = summarize_rem_keys(set[:rem], key, sum)
|
57
|
+
sum.each do |k, v|
|
58
|
+
col.total.set_or_incr(k, v.to_i)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
col
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_date_sets
|
65
|
+
Finder::DateSet.new(@options[:from], @options[:till], @options[:depth], @options[:interval])
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_key
|
69
|
+
Key.new(@options[:scope], @options[:label])
|
70
|
+
end
|
71
|
+
|
72
|
+
def summarize_add_keys(sets, key, sum)
|
73
|
+
sets.each do |date|
|
74
|
+
db.hgetall("#{key.prefix}#{date}").each do |k, v|
|
75
|
+
sum.set_or_incr(k, v.to_i)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
sum
|
79
|
+
end
|
80
|
+
|
81
|
+
def summarize_rem_keys(sets, key, sum)
|
82
|
+
sets.each do |date|
|
83
|
+
db.hgetall("#{key.prefix}#{date}").each do |k, v|
|
84
|
+
sum.set_or_incr(k, -v.to_i)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
sum
|
88
|
+
end
|
89
|
+
|
90
|
+
class << self
|
91
|
+
|
92
|
+
def find(*args)
|
93
|
+
new.find(*args)
|
94
|
+
end
|
95
|
+
|
96
|
+
def scope(scope)
|
97
|
+
new.scope(scope)
|
98
|
+
end
|
99
|
+
|
100
|
+
def label(label)
|
101
|
+
new.label(label)
|
102
|
+
end
|
103
|
+
|
104
|
+
def dates(from, till)
|
105
|
+
new.dates(from, till)
|
106
|
+
end
|
107
|
+
alias :date :dates
|
108
|
+
|
109
|
+
def from(date)
|
110
|
+
new.from(date)
|
111
|
+
end
|
112
|
+
|
113
|
+
def till(date)
|
114
|
+
new.till(date)
|
115
|
+
end
|
116
|
+
alias :untill :till
|
117
|
+
|
118
|
+
def depth(unit)
|
119
|
+
new.depth(unit)
|
120
|
+
end
|
121
|
+
|
122
|
+
def interval(unit)
|
123
|
+
new.interval(unit)
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
def scope(scope)
|
129
|
+
@options[:scope] = scope
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
def label(label)
|
134
|
+
@options[:label] = label
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
def dates(from, till)
|
139
|
+
from(from).till(till)
|
140
|
+
end
|
141
|
+
alias :date :dates
|
142
|
+
|
143
|
+
def from(date)
|
144
|
+
@options[:from] = date
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
def till(date)
|
149
|
+
@options[:till] = date
|
150
|
+
self
|
151
|
+
end
|
152
|
+
alias :until :till
|
153
|
+
|
154
|
+
def depth(unit)
|
155
|
+
@options[:depth] = unit
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
def interval(unit)
|
160
|
+
@options[:interval] = unit
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Redistat
|
2
|
+
class Finder
|
3
|
+
class DateSet < Array
|
4
|
+
|
5
|
+
def initialize(start_date = nil, end_date = nil, depth = nil, interval = false)
|
6
|
+
if !start_date.nil? && !end_date.nil?
|
7
|
+
find_date_sets(start_date, end_date, depth, interval)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_date_sets(start_date, end_date, depth = nil, interval = false)
|
12
|
+
start_date = start_date.to_time if start_date.is_a?(::Date)
|
13
|
+
end_date = end_date.to_time if end_date.is_a?(::Date)
|
14
|
+
if !interval
|
15
|
+
find_date_sets_by_magic(start_date, end_date, depth)
|
16
|
+
else
|
17
|
+
find_date_sets_by_interval(start_date, end_date, depth)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def find_date_sets_by_magic(start_date, end_date, depth = nil)
|
24
|
+
depth ||= :hour
|
25
|
+
depths = Date::DEPTHS[Date::DEPTHS.index(:year), Date::DEPTHS.index(depth)+1].reverse
|
26
|
+
depths.each_with_index do |d, i|
|
27
|
+
sets = [find_start_keys_for(d, start_date, end_date, (i == 0))]
|
28
|
+
sets << find_end_keys_for(d, start_date, end_date, (i == 0))
|
29
|
+
sets.each do |set|
|
30
|
+
self << set if set != { :add => [], :rem => [] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_date_sets_by_interval(start_date, end_date, depth, inclusive = true)
|
37
|
+
depth ||= :hour
|
38
|
+
self << { :add => start_date.map_beginning_of_each(depth, :include_start => inclusive, :include_end => inclusive).until(end_date) { |t| t.to_rs.to_s(depth) }, :rem => [] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_start_keys_for(unit, start_date, end_date, lowest_depth = false)
|
42
|
+
return find_start_year_for(start_date, end_date, lowest_depth) if unit == :year
|
43
|
+
index = Date::DEPTHS.index(unit)
|
44
|
+
nunit = Date::DEPTHS[(index > 0) ? index-1 : index]
|
45
|
+
if start_date < start_date.round(nunit) || start_date.next(nunit).beginning_of(nunit) > end_date.beginning_of(nunit)
|
46
|
+
add = []
|
47
|
+
start_date.beginning_of_each(unit, :include_start => lowest_depth).until(start_date.end_of(nunit)) do |t|
|
48
|
+
add << t.to_rs.to_s(unit) if t < end_date.beginning_of(unit)
|
49
|
+
end
|
50
|
+
{ :add => add, :rem => [] }
|
51
|
+
else
|
52
|
+
{ :add => [start_date.beginning_of(nunit).to_rs.to_s(nunit)],
|
53
|
+
:rem => start_date.beginning_of(nunit).map_beginning_of_each(unit, :include_start => true, :include_end => !lowest_depth).until(start_date) { |t| t.to_rs.to_s(unit) } }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_end_keys_for(unit, start_date, end_date, lowest_depth = false)
|
58
|
+
return find_end_year_for(start_date, end_date, lowest_depth) if unit == :year
|
59
|
+
index = Date::DEPTHS.index(unit)
|
60
|
+
nunit = Date::DEPTHS[(index > 0) ? index-1 : index]
|
61
|
+
has_nunit = end_date.prev(nunit).beginning_of(nunit) >= start_date.beginning_of(nunit)
|
62
|
+
nearest_nunit = end_date.round(nunit)
|
63
|
+
if end_date >= nearest_nunit && has_nunit
|
64
|
+
add = []
|
65
|
+
end_date.beginning_of(nunit).beginning_of_each(unit, :include_start => true, :include_end => lowest_depth).until(end_date) do |t|
|
66
|
+
add << t.to_rs.to_s(unit) if t > start_date.beginning_of(unit)
|
67
|
+
end
|
68
|
+
{ :add => add, :rem => [] }
|
69
|
+
elsif has_nunit
|
70
|
+
{ :add => [end_date.beginning_of(nunit).to_rs.to_s(nunit)],
|
71
|
+
:rem => end_date.map_beginning_of_each(unit, :include_start => !lowest_depth).until(end_date.end_of(nunit)) { |t| t.to_rs.to_s(unit) } }
|
72
|
+
else
|
73
|
+
{ :add => [], :rem => [] }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_start_year_for(start_date, end_date, lowest_depth = false)
|
78
|
+
if start_date.years_since(1).beginning_of_year < end_date.beginning_of_year
|
79
|
+
{ :add => start_date.map_beginning_of_each_year(:include_end => lowest_depth).until(end_date) { |t| t.to_rs.to_s(:year) }, :rem => [] }
|
80
|
+
else
|
81
|
+
{ :add => [], :rem => [] }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_end_year_for(start_date, end_date, lowest_depth = false)
|
86
|
+
if lowest_depth
|
87
|
+
{ :add => [end_date.beginning_of_year.to_rs.to_s(:year)], :rem => [] }
|
88
|
+
else
|
89
|
+
{ :add => [], :rem => [] }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/redistat/key.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Redistat
|
2
|
+
class Key
|
3
|
+
|
4
|
+
attr_accessor :scope
|
5
|
+
attr_accessor :date
|
6
|
+
attr_accessor :options
|
7
|
+
|
8
|
+
def initialize(scope, label = nil, date = nil, options = {})
|
9
|
+
@scope = scope
|
10
|
+
self.label = label if !label.nil?
|
11
|
+
self.date = date ||= Time.now
|
12
|
+
@options = default_options.merge(options ||= {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_options
|
16
|
+
{ :depth => :day }
|
17
|
+
end
|
18
|
+
|
19
|
+
def prefix
|
20
|
+
key = "#{@scope}"
|
21
|
+
key << "/" + ((@options[:label_hash].nil? || @options[:label_hash] == true) ? @label.hash : @label.name) if !label.nil?
|
22
|
+
key << ":"
|
23
|
+
key
|
24
|
+
end
|
25
|
+
|
26
|
+
def date=(input)
|
27
|
+
@date = (input.instance_of?(Redistat::Date)) ? input : Date.new(input) # Redistat::Date, not ::Date
|
28
|
+
end
|
29
|
+
|
30
|
+
def depth
|
31
|
+
@options[:depth]
|
32
|
+
end
|
33
|
+
|
34
|
+
def label
|
35
|
+
@label.name
|
36
|
+
end
|
37
|
+
|
38
|
+
def label_hash
|
39
|
+
@label.hash
|
40
|
+
end
|
41
|
+
|
42
|
+
def label=(input)
|
43
|
+
@label = (input.instance_of?(Redistat::Label)) ? input : Label.create(input)
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s(depth = nil)
|
47
|
+
depth ||= @options[:depth]
|
48
|
+
key = self.prefix
|
49
|
+
key << @date.to_s(depth)
|
50
|
+
key
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Redistat
|
2
|
+
class Label
|
3
|
+
include Database
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :hash
|
7
|
+
|
8
|
+
def initialize(str)
|
9
|
+
@name = str.to_s
|
10
|
+
@hash = Digest::SHA1.hexdigest(@name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def save
|
14
|
+
@saved = (db.set("#{KEY_LEBELS}#{@hash}", @name) == "OK")
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def saved?
|
19
|
+
@saved ||= false
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.create(name)
|
23
|
+
self.new(name).save
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Redistat
|
2
|
+
module Model
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(self)
|
6
|
+
end
|
7
|
+
|
8
|
+
def store(label, stats = {}, date = nil, meta = {}, opts = {})
|
9
|
+
Event.new(name, label, date, stats, options.merge(opts), meta).save
|
10
|
+
end
|
11
|
+
alias :event :store
|
12
|
+
|
13
|
+
def fetch(label, from, till, opts = {})
|
14
|
+
Finder.find({
|
15
|
+
:scope => name,
|
16
|
+
:label => label,
|
17
|
+
:from => from,
|
18
|
+
:till => till
|
19
|
+
}.merge(options.merge(opts)))
|
20
|
+
end
|
21
|
+
alias :find :fetch
|
22
|
+
|
23
|
+
def depth(depth = nil)
|
24
|
+
if !depth.nil?
|
25
|
+
options[:depth] = depth
|
26
|
+
else
|
27
|
+
options[:depth] || nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def store_event(boolean = nil)
|
32
|
+
if !boolean.nil?
|
33
|
+
options[:store_event] = boolean
|
34
|
+
else
|
35
|
+
options[:store_event] || nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def options
|
40
|
+
@options ||= {}
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def name
|
46
|
+
@name ||= self.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|