diff --git a/README.md b/README.md index 76009fe..22fe6dd 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/Rakefile b/Rakefile index 3152edd..96c9257 100644 --- a/Rakefile +++ b/Rakefile @@ -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 diff --git a/examples/simple.rb b/examples/simple.rb new file mode 100644 index 0000000..cf3204c --- /dev/null +++ b/examples/simple.rb @@ -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}} \ No newline at end of file diff --git a/lib/rrstat.rb b/lib/rrrdtool.rb similarity index 91% rename from lib/rrstat.rb rename to lib/rrrdtool.rb index fe7f28b..2a11f12 100644 --- a/lib/rrstat.rb +++ b/lib/rrrdtool.rb @@ -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 diff --git a/spec/rrstat_spec.rb b/spec/rrrdtool_spec.rb similarity index 90% rename from spec/rrstat_spec.rb rename to spec/rrrdtool_spec.rb index d880bd2..9814d2f 100644 --- a/spec/rrstat_spec.rb +++ b/spec/rrrdtool_spec.rb @@ -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