rrdsimple 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/README +0 -0
- data/Rakefile +62 -0
- data/lib/rrdsimple.rb +117 -0
- data/spec/rrdsimple_spec.rb +186 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +19 -0
- metadata +96 -0
data/README
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
$:.unshift File.join(File.dirname(__FILE__), 'lib')
|
5
|
+
require 'rrdsimple'
|
6
|
+
|
7
|
+
GEM = 'rrdsimple'
|
8
|
+
GEM_NAME = 'rrdsimple'
|
9
|
+
GEM_VERSION = RRDSimple::VERSION
|
10
|
+
AUTHORS = ['Edgar Gonzalez']
|
11
|
+
EMAIL = "edgargonzalez@gmail.com"
|
12
|
+
HOMEPAGE = "http://github.com/edgar/RRDSimple"
|
13
|
+
SUMMARY = "A simple round robin database pattern via Redis"
|
14
|
+
|
15
|
+
begin
|
16
|
+
require "jeweler"
|
17
|
+
Jeweler::Tasks.new do |gemspec|
|
18
|
+
gemspec.name = GEM
|
19
|
+
gemspec.version = GEM_VERSION
|
20
|
+
gemspec.summary = SUMMARY
|
21
|
+
gemspec.platform = Gem::Platform::RUBY
|
22
|
+
gemspec.description = gemspec.summary
|
23
|
+
gemspec.email = EMAIL
|
24
|
+
gemspec.homepage = HOMEPAGE
|
25
|
+
gemspec.authors = AUTHORS
|
26
|
+
gemspec.required_ruby_version = ">= 1.8"
|
27
|
+
gemspec.add_dependency("redis", ">= 2.0.3")
|
28
|
+
gemspec.add_development_dependency "rspec", ">= 1.3.0"
|
29
|
+
gemspec.rubyforge_project = GEM
|
30
|
+
end
|
31
|
+
|
32
|
+
Jeweler::GemcutterTasks.new
|
33
|
+
rescue LoadError
|
34
|
+
puts "Jeweler not available: gem install jeweler"
|
35
|
+
end
|
36
|
+
|
37
|
+
require 'spec/rake/spectask'
|
38
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
39
|
+
spec.libs << 'lib' << 'spec'
|
40
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
41
|
+
end
|
42
|
+
|
43
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
44
|
+
spec.libs << 'lib' << 'spec'
|
45
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
46
|
+
spec.rcov = true
|
47
|
+
end
|
48
|
+
|
49
|
+
task :spec => :check_dependencies
|
50
|
+
|
51
|
+
task :default => :spec
|
52
|
+
|
53
|
+
require 'rake/rdoctask'
|
54
|
+
Rake::RDocTask.new do |rdoc|
|
55
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
56
|
+
|
57
|
+
rdoc.rdoc_dir = 'rdoc'
|
58
|
+
rdoc.title = "models #{version}"
|
59
|
+
rdoc.rdoc_files.include('README*')
|
60
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
61
|
+
end
|
62
|
+
|
data/lib/rrdsimple.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'redis', '>= 2.0.3'
|
3
|
+
require 'redis'
|
4
|
+
|
5
|
+
class RRDSimple
|
6
|
+
VERSION = "0.0.1"
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
@buckets = opts[:buckets]
|
10
|
+
@step = opts[:step]
|
11
|
+
@debug = opts[:debug] || false
|
12
|
+
@db = opts[:db] || Redis.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def current_epoch
|
16
|
+
Time.now.utc.to_i / @step
|
17
|
+
end
|
18
|
+
|
19
|
+
def current_bucket
|
20
|
+
current_epoch % @buckets
|
21
|
+
end
|
22
|
+
|
23
|
+
def last_epoch_key(k)
|
24
|
+
"#{k}:epoch"
|
25
|
+
end
|
26
|
+
|
27
|
+
def last_epoch(k)
|
28
|
+
@db.get(last_epoch_key(k)).to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_last_epoch(k,v = Time.now.utc.to_i)
|
32
|
+
@db.set(last_epoch_key(k), v)
|
33
|
+
end
|
34
|
+
|
35
|
+
def last_bucket(set)
|
36
|
+
last_epoch(set) % @buckets
|
37
|
+
end
|
38
|
+
|
39
|
+
def bucket_key(k,i)
|
40
|
+
"#{k}:#{i}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def bucket(k,i)
|
44
|
+
@db.get(bucket_key(k,i)).to_i
|
45
|
+
end
|
46
|
+
|
47
|
+
def relative_bucket(value, i)
|
48
|
+
b = value - i
|
49
|
+
b = (b < 0) ? @buckets + b : b
|
50
|
+
end
|
51
|
+
|
52
|
+
def epochs_ago(k, num)
|
53
|
+
bucket_key(k, relative_bucket(current_bucket,num))
|
54
|
+
end
|
55
|
+
|
56
|
+
def buckets(k)
|
57
|
+
a = []
|
58
|
+
i = 0
|
59
|
+
last_b = last_bucket(k)
|
60
|
+
while (i < @buckets) do
|
61
|
+
a.push bucket_key(k,relative_bucket(last_b,i))
|
62
|
+
i += 1
|
63
|
+
end
|
64
|
+
a
|
65
|
+
end
|
66
|
+
|
67
|
+
def values(k)
|
68
|
+
a = []
|
69
|
+
i = 0
|
70
|
+
last_e = last_epoch(k)
|
71
|
+
last_b = last_bucket(k)
|
72
|
+
while (i < @buckets) do
|
73
|
+
v = bucket(k,relative_bucket(last_b,i))
|
74
|
+
a.push({:value => v, :epoch => last_e - i}) if v != 0
|
75
|
+
i += 1
|
76
|
+
end
|
77
|
+
a
|
78
|
+
end
|
79
|
+
|
80
|
+
def epoch(k)
|
81
|
+
current_e = current_epoch
|
82
|
+
last_e = last_epoch(k)
|
83
|
+
if current_e != last_e
|
84
|
+
[(current_e - last_e).abs, @buckets].min.times do |n|
|
85
|
+
clear_bucket(epochs_ago(k, n))
|
86
|
+
end
|
87
|
+
set_last_epoch(k, current_e)
|
88
|
+
end
|
89
|
+
bucket_key(k, current_bucket)
|
90
|
+
end
|
91
|
+
|
92
|
+
def incr(k, val=1)
|
93
|
+
debug [:incr, epoch(k), val]
|
94
|
+
@db.incrby(epoch(k), val).to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
def set(k, val)
|
98
|
+
debug [:set, epoch(k), val]
|
99
|
+
@db.set(epoch(k), val)
|
100
|
+
end
|
101
|
+
|
102
|
+
def clear(k)
|
103
|
+
@db.del(last_epoch_key(k))
|
104
|
+
buckets(k){|b| clear_bucket(b)}
|
105
|
+
end
|
106
|
+
|
107
|
+
protected
|
108
|
+
|
109
|
+
def clear_bucket(b)
|
110
|
+
debug [:clearing_epoch, b]
|
111
|
+
@db.del(b)
|
112
|
+
end
|
113
|
+
|
114
|
+
def debug(msg); puts msg if @debug; end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "RRDSimple" do
|
4
|
+
|
5
|
+
let(:rr) { RRDSimple.new(:step => 60, :buckets => 60, :db => redis) }
|
6
|
+
let(:jan01) {Time.utc(2010,1,1.0,0)}
|
7
|
+
|
8
|
+
before(:each) { time_travel_to(jan01) }
|
9
|
+
before(:each) { redis.flushdb }
|
10
|
+
|
11
|
+
it "should return the proper key for last_epoch" do
|
12
|
+
rr.last_epoch_key('k').should == 'k:epoch'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return zero as last_epoch for an empty rrdsimple" do
|
16
|
+
rr.last_epoch('k').should == 0
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not exist in redis last epoch key related to an empty rrdsimple" do
|
20
|
+
redis.exists("k:epoch").should be_false
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should not exist in redis any bucket key related to an empty rrdsimple" do
|
24
|
+
60.times do |i|
|
25
|
+
redis.exists("k:{i}").should be_false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should increment buckets within correct epoch" do
|
30
|
+
rr.epoch("k").should match(/k:0/)
|
31
|
+
rr.incr("k").should == 1
|
32
|
+
rr.incr("k", 2).should == 3
|
33
|
+
rr.incr("k").should == 4
|
34
|
+
# The bucket should have the counter
|
35
|
+
redis.get('k:0').to_i.should == 4
|
36
|
+
# The epoch should be consistent
|
37
|
+
rr.last_epoch('k').should == jan01.to_i / 60
|
38
|
+
v = rr.values('k')
|
39
|
+
v.size.should == 1
|
40
|
+
v[0][:value].should == 4
|
41
|
+
# The following to assertion are equivalent
|
42
|
+
v[0][:epoch].should == jan01.to_i / 60
|
43
|
+
Time.at(v[0][:epoch]*60).should == jan01
|
44
|
+
|
45
|
+
jump 60 # jump one minute
|
46
|
+
rr.incr("k").should == 1
|
47
|
+
rr.incr("k").should == 2
|
48
|
+
|
49
|
+
# The bucket should have the counter
|
50
|
+
redis.get('k:0').to_i.should == 4
|
51
|
+
redis.get('k:1').to_i.should == 2
|
52
|
+
# The epoch should be consistent
|
53
|
+
rr.last_epoch('k').should == (jan01 + 60).to_i / 60
|
54
|
+
|
55
|
+
# values
|
56
|
+
v = rr.values('k')
|
57
|
+
v.size.should == 2
|
58
|
+
|
59
|
+
v[0][:value].should == 2
|
60
|
+
# The following to assertion are equivalent
|
61
|
+
v[0][:epoch].should == (jan01 + 60).to_i / 60
|
62
|
+
Time.at(v[0][:epoch]*60).should == jan01 + 60
|
63
|
+
|
64
|
+
v[1][:value].should == 4
|
65
|
+
# The following to assertion are equivalent
|
66
|
+
v[1][:epoch].should == jan01.to_i / 60
|
67
|
+
Time.at(v[1][:epoch]*60).utc.should == jan01
|
68
|
+
|
69
|
+
|
70
|
+
jump 60*2 # jump two minute
|
71
|
+
rr.incr("k").should == 1
|
72
|
+
rr.incr("k").should == 2
|
73
|
+
rr.incr("k").should == 3
|
74
|
+
|
75
|
+
# The bucket should have the counter
|
76
|
+
redis.get('k:0').to_i.should == 4
|
77
|
+
redis.get('k:1').to_i.should == 2
|
78
|
+
redis.exists('k:2').should be_false
|
79
|
+
redis.get('k:3').to_i.should == 3
|
80
|
+
# The epoch should be consistent
|
81
|
+
rr.last_epoch('k').should == (jan01 + 60 + 120).to_i / 60
|
82
|
+
|
83
|
+
# values
|
84
|
+
v = rr.values('k')
|
85
|
+
v.size.should == 3
|
86
|
+
|
87
|
+
v[0][:value].should == 3
|
88
|
+
# The following to assertion are equivalent
|
89
|
+
v[0][:epoch].should == (jan01 + 60 + 60*2).to_i / 60
|
90
|
+
Time.at(v[0][:epoch]*60).should == jan01 + 60 + 60*2
|
91
|
+
|
92
|
+
v[1][:value].should == 2
|
93
|
+
# The following to assertion are equivalent
|
94
|
+
v[1][:epoch].should == (jan01 + 60).to_i / 60
|
95
|
+
Time.at(v[1][:epoch]*60).should == jan01 + 60
|
96
|
+
|
97
|
+
v[2][:value].should == 4
|
98
|
+
# The following to assertion are equivalent
|
99
|
+
v[2][:epoch].should == jan01.to_i / 60
|
100
|
+
Time.at(v[2][:epoch]*60).utc.should == jan01
|
101
|
+
|
102
|
+
jump 60*30 # jump 30 minutes
|
103
|
+
|
104
|
+
rr.incr('k').should == 1
|
105
|
+
# The bucket should have the counter
|
106
|
+
redis.get('k:0').to_i.should == 4
|
107
|
+
redis.get('k:1').to_i.should == 2
|
108
|
+
redis.exists('k:2').should be_false
|
109
|
+
redis.get('k:3').to_i.should == 3
|
110
|
+
(4..32).each do |i|
|
111
|
+
redis.exists("k:#{i}").should be_false
|
112
|
+
end
|
113
|
+
redis.get('k:33').to_i.should == 1
|
114
|
+
|
115
|
+
# The epoch should be consistent
|
116
|
+
rr.last_epoch('k').should == (jan01 + 60 + 60*2 + 60*30).to_i / 60
|
117
|
+
|
118
|
+
# values
|
119
|
+
v = rr.values('k')
|
120
|
+
v.size.should == 4
|
121
|
+
|
122
|
+
v[0][:value].should == 1
|
123
|
+
# The following to assertion are equivalent
|
124
|
+
v[0][:epoch].should == (jan01 + 60 + 60*2 + 60*30).to_i / 60
|
125
|
+
Time.at(v[0][:epoch]*60).should == jan01 + 60 + 60*2 + 60*30
|
126
|
+
|
127
|
+
v[1][:value].should == 3
|
128
|
+
# The following to assertion are equivalent
|
129
|
+
v[1][:epoch].should == (jan01 + 60 + 60*2).to_i / 60
|
130
|
+
Time.at(v[1][:epoch]*60).should == jan01 + 60 + 60*2
|
131
|
+
|
132
|
+
v[2][:value].should == 2
|
133
|
+
# The following to assertion are equivalent
|
134
|
+
v[2][:epoch].should == (jan01 + 60).to_i / 60
|
135
|
+
Time.at(v[2][:epoch]*60).should == jan01 + 60
|
136
|
+
|
137
|
+
v[3][:value].should == 4
|
138
|
+
# The following to assertion are equivalent
|
139
|
+
v[3][:epoch].should == jan01.to_i / 60
|
140
|
+
Time.at(v[3][:epoch]*60).utc.should == jan01
|
141
|
+
|
142
|
+
jump 60*45 # jump 45 minutes
|
143
|
+
rr.last_epoch('k').should == (jan01 + 60 + 60*2 + 60*30).to_i / 60
|
144
|
+
|
145
|
+
rr.incr('k').should == 1
|
146
|
+
rr.incr('k').should == 2
|
147
|
+
rr.incr('k').should == 3
|
148
|
+
rr.incr('k').should == 4
|
149
|
+
rr.incr('k').should == 5
|
150
|
+
|
151
|
+
# The bucket should have the counter
|
152
|
+
(0..17).each do |i|
|
153
|
+
redis.exists("k:#{i}").should be_false
|
154
|
+
end
|
155
|
+
redis.get('k:18').to_i.should == 5
|
156
|
+
(19..32).each do |i|
|
157
|
+
redis.exists("k:#{i}").should be_false
|
158
|
+
end
|
159
|
+
redis.get('k:33').to_i.should == 1
|
160
|
+
(34..59).each do |i|
|
161
|
+
redis.exists("k:#{i}").should be_false
|
162
|
+
end
|
163
|
+
|
164
|
+
# values
|
165
|
+
v = rr.values('k')
|
166
|
+
v.size.should == 2
|
167
|
+
|
168
|
+
v[0][:value].should == 5
|
169
|
+
# The following to assertion are equivalent
|
170
|
+
v[0][:epoch].should == (jan01 + 60 + 60*2 + 60*30 + 60*45).to_i / 60
|
171
|
+
Time.at(v[0][:epoch]*60).should == jan01 + 60 + 60*2 + 60*30 + 60*45
|
172
|
+
|
173
|
+
v[1][:value].should == 1
|
174
|
+
# The following to assertion are equivalent
|
175
|
+
v[1][:epoch].should == (jan01 + 60 + 60*2 + 60*30).to_i / 60
|
176
|
+
Time.at(v[1][:epoch]*60).should == jan01 + 60 + 60*2 + 60*30
|
177
|
+
|
178
|
+
|
179
|
+
rr.clear('k')
|
180
|
+
redis.exists("k:epoch").should be_false
|
181
|
+
(0..59).each do |i|
|
182
|
+
redis.exists("k:{i}").should be_false
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'rrdsimple'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
require 'delorean'
|
7
|
+
require 'ruby-debug'
|
8
|
+
|
9
|
+
def redis
|
10
|
+
@redis ||= Redis.new(:host => 'localhost', :port => 6379, :db => 15 )
|
11
|
+
end
|
12
|
+
|
13
|
+
Spec::Runner.configure do |config|
|
14
|
+
config.include Delorean
|
15
|
+
config.after :suite do
|
16
|
+
redis.flushdb
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rrdsimple
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Edgar Gonzalez
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-02 00:00:00 -04:30
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: redis
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 0
|
30
|
+
- 3
|
31
|
+
version: 2.0.3
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rspec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 3
|
44
|
+
- 0
|
45
|
+
version: 1.3.0
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: A simple round robin database pattern via Redis
|
49
|
+
email: edgargonzalez@gmail.com
|
50
|
+
executables: []
|
51
|
+
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files:
|
55
|
+
- README
|
56
|
+
files:
|
57
|
+
- README
|
58
|
+
- Rakefile
|
59
|
+
- lib/rrdsimple.rb
|
60
|
+
- spec/rrdsimple_spec.rb
|
61
|
+
- spec/spec.opts
|
62
|
+
- spec/spec_helper.rb
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: http://github.com/edgar/RRDSimple
|
65
|
+
licenses: []
|
66
|
+
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options:
|
69
|
+
- --charset=UTF-8
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 8
|
79
|
+
version: "1.8"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project: rrdsimple
|
90
|
+
rubygems_version: 1.3.6
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: A simple round robin database pattern via Redis
|
94
|
+
test_files:
|
95
|
+
- spec/rrdsimple_spec.rb
|
96
|
+
- spec/spec_helper.rb
|