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 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