Permalink
Browse files

oh hai rrrdtool

  • Loading branch information...
1 parent 80f77ac commit 48fba22df3473d231d7b7516334a0a7d51f1f21b @igrigorik committed May 9, 2010
Showing with 51 additions and 27 deletions.
  1. +15 −13 README.md
  2. +5 −5 Rakefile
  3. +19 −0 examples/simple.rb
  4. +5 −5 lib/{rrstat.rb → rrrdtool.rb}
  5. +7 −4 spec/{rrstat_spec.rb → rrrdtool_spec.rb}
View
@@ -1,28 +1,30 @@
-# RRStat
+# RRRDTool
-Implements a round-robin database pattern on top of Redis sorted sets. Ideal for
-answering top/last N queries in (almost) fixed (memory) space. Specify the period
-and precision of each collection bucket, and RRStat will do the rest.
+Implements a round-robin database (circular buffer) pattern on top of Redis sorted
+sets. Ideal for answering top/last N queries in (almost) fixed (memory) space - actual
+footprint depends on the number of unique keys you are tracking. Specify the period
+and precision (step) of each collection bucket, and RRStat will do the rest.
Memory footprint will be limited to number of buckets * number of keys in each. New
samples will be automatically placed into correct epoch/bucket. Ex:
-## Store up to 60s worth of samples, in 10s buckets:
- rr = RRStat.new(:precision => 10, :buckets => 6)
+## Store up to 5s worth of samples, in 1s buckets:
+ rr = RRRDTool.new(:step => 1, :buckets => 5)
+
rr.incr("namespace", "key")
rr.incr("namespace", "key", 5)
- rr.score("namespace", "key") => 6
+ p rr.score("namespace", "key") # => 6
- sleep (10)
+ sleep (1)
rr.incr("namespace", "key")
- rr.score("namespace", "key") => 7
- rr.first("namespace", 1, :with_scores => true) => {"key" => 7}
+ p rr.score("namespace", "key") # => 7
+ p rr.first("namespace", 1, :with_scores => true) # => {"key"=>"7"}
- sleep(50)
+ sleep(4)
- # epoch will rollover, eliminating first bucket
- rr.score("namespace", "key") => 1
+ p rr.score("namespace", "key") # => 1
+ p rr.stats("namespace") # => {:buckets=>5, :unique_keys=>1, :key_count=>{0=>0, 1=>0, 2=>0, 3=>1, 4=>0}}
# License
View
@@ -3,15 +3,15 @@ require "rake"
begin
require "jeweler"
Jeweler::Tasks.new do |gemspec|
- gemspec.name = "rrstat"
- gemspec.summary = "Round robin database via Redis sorted sets"
+ gemspec.name = "rrrdtool"
+ gemspec.summary = "Round robin database pattern via Redis sorted sets"
gemspec.description = gemspec.summary
gemspec.email = "ilya@igvita.com"
- gemspec.homepage = "http://github.com/igrigorik/rrstat"
+ gemspec.homepage = "http://github.com/igrigorik/rrrdtool"
gemspec.authors = ["Ilya Grigorik"]
gemspec.required_ruby_version = ">= 1.8"
- gemspec.add_dependency("redis", ">= 2.0.0")
- gemspec.rubyforge_project = "rrstat"
+ gemspec.add_dependency("redis", ">= 1.9")
+ gemspec.rubyforge_project = "rrrdtool"
end
Jeweler::GemcutterTasks.new
View
@@ -0,0 +1,19 @@
+require 'lib/rrrdtool'
+
+rr = RRRDTool.new(:step => 1, :buckets => 5)
+rr.flushdb
+
+rr.incr("namespace", "key")
+rr.incr("namespace", "key", 5)
+p rr.score("namespace", "key") # => 6
+
+sleep (1)
+
+rr.incr("namespace", "key")
+p rr.score("namespace", "key") # => 7
+p rr.first("namespace", 1, :with_scores => true) # => {"key"=>"7"}
+
+sleep(4)
+
+p rr.score("namespace", "key") # => 1
+p rr.stats("namespace") # => {:buckets=>5, :unique_keys=>1, :key_count=>{0=>0, 1=>0, 2=>0, 3=>1, 4=>0}}
@@ -1,9 +1,9 @@
require 'redis'
-class RRStat
+class RRRDTool
def initialize(opts)
- @precision = opts[:precision]
@buckets = opts[:buckets]
+ @step = opts[:step]
@debug = opts[:debug] || false
@current = nil
@@ -12,7 +12,7 @@ def initialize(opts)
@db = Redis.new
end
- def time_epoch; (Time.now.to_i / @precision) % @buckets; end
+ def time_epoch; (Time.now.to_i / @step) % @buckets; end
def epochs_ago(set, num)
b = time_epoch-num
b = (b < 0) ? @buckets + b : b
@@ -26,13 +26,13 @@ def buckets(set)
def epoch(set)
e = time_epoch
- s = Time.now.to_i / @precision
+ s = Time.now.to_i / @step
now = set + ":" + e.to_s
if now != @current and s != @signature
debug [:new_epoch, e]
@current = now
- @signature = Time.now.to_i / @precision
+ @signature = Time.now.to_i / @step
clear_bucket(epochs_ago(set, @buckets))
end
@@ -2,19 +2,19 @@
require 'delorean'
require 'time'
-require 'lib/rrstat'
+require 'lib/rrrdtool'
-describe RRStat do
+describe RRRDTool do
include Delorean
- let(:rr) { RRStat.new(:precision => 10, :buckets => 6) }
+ let(:rr) { RRRDTool.new(:step => 10, :buckets => 6) }
before(:each) { time_travel_to("Jan 1 2010") }
before(:each) { rr.clear("test") }
it "should initialize db" do
lambda {
- RRStat.new(:precision => 10, :buckets => 6)
+ RRRDTool.new(:step => 10, :buckets => 6)
}.should_not raise_error
end
@@ -112,6 +112,9 @@
:key_count => { 0 => 0, 1 => 1, 2 => 0, 3 => 0, 4 => 0, 5 => 0 }
}
end
+ end
+ it "should store & verify epoch signatures for each bucket" do
+ pending "otherwise, if we skip several buckets, they won't get cleared"
end
end

0 comments on commit 48fba22

Please sign in to comment.