rrdsimple 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,6 @@
1
+ --color
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
5
+ --backtrace
6
+
@@ -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