bitmap-counter 0.0.0

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/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in em-apn.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bitmap-counter (0.0.0)
5
+ bitset
6
+ redis
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ bitset (0.1.0)
12
+ diff-lcs (1.1.3)
13
+ redis (2.2.2)
14
+ rspec (2.6.0)
15
+ rspec-core (~> 2.6.0)
16
+ rspec-expectations (~> 2.6.0)
17
+ rspec-mocks (~> 2.6.0)
18
+ rspec-core (2.6.4)
19
+ rspec-expectations (2.6.0)
20
+ diff-lcs (~> 1.1.2)
21
+ rspec-mocks (2.6.0)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ bitmap-counter!
28
+ rspec (~> 2.6.0)
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Bitmap Counter
2
+
3
+ An efficient id-based counter. Current implementation uses redis.
4
+
5
+ # Usage
6
+
7
+ counter = Bitmap::Counter.new("name")
8
+
9
+ counter.add(id = 1) # a numeric user_id for example
10
+ counter.count # 2
11
+ counter.includes?(1) # true
12
+ counter.includes?(2) # false
13
+
14
+ ## DateCounter
15
+
16
+ Count ids for a series of dates.
17
+
18
+ counter = Bitmap::DateCounter.new("name")
19
+ counter.add(1, Date.today)
20
+ counter.add(2, Date.today)
21
+ counter.add(1, Date.today - 1)
22
+
23
+ You can inspect a specific date:
24
+
25
+ counter.count(Date.today) # 2
26
+ counter.count(Date.today - 1) # 1
27
+
28
+ counter.includes?(1, Date.today) # true
29
+ counter.includes?(2, Date.today) # true
30
+ counter.includes?(1, Date.today - 1) # true
31
+ counter.includes?(2, Date.today - 1) # false
32
+
33
+ Or a range of dates:
34
+
35
+ counter.count((Date.today - 1)..Date.today) # 2
36
+
37
+ counter.includes?(1, (Date.today - 1)..Date.today) # true
38
+ counter.includes?(2, (Date.today - 1)..Date.today) # true
39
+ counter.includes?(3, (Date.today - 1)..Date.today) # false
40
+
41
+ ## Credits
42
+
43
+ Inspired by the awesome [post](http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/) by Chandra Patni.
44
+
45
+ ## Ruby Version
46
+
47
+ This was developed on ruby 1.9.2p180 and not tested on other versions...yet :)
48
+
49
+ ## TODO
50
+
51
+ * Add different backends (File, Postgres, etc)
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,22 @@
1
+ # -*- mode: ruby; encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "bitmap/counter"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "bitmap-counter"
7
+ s.version = Bitmap::Counter::VERSION
8
+ s.authors = ["Brandon Keene"]
9
+ s.email = ["bkeene@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{A unique id counter implemented with redis bitmaps. Count the unique users across a date range.}
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_dependency "redis"
19
+ s.add_dependency "bitset"
20
+
21
+ s.add_development_dependency "rspec", "~> 2.6.0"
22
+ end
@@ -0,0 +1,50 @@
1
+ module Bitmap
2
+ class Counter
3
+ VERSION = "0.0.0"
4
+
5
+ class << self
6
+ def redis=(connection)
7
+ @redis = connection
8
+ end
9
+
10
+ def redis
11
+ @redis ||= Redis.new
12
+ end
13
+ end
14
+
15
+ def initialize(name)
16
+ @name = name
17
+ end
18
+
19
+ def count(id)
20
+ self.class.redis.setbit(key, id, 1)
21
+ end
22
+
23
+ def counted?(id)
24
+ self.class.redis.getbit(key, id) == 1
25
+ end
26
+
27
+ def unique
28
+ bitset.cardinality
29
+ end
30
+
31
+ def delete
32
+ self.class.redis.del(key)
33
+ end
34
+
35
+ def to_s
36
+ bitset.to_s
37
+ end
38
+
39
+ private
40
+
41
+ def bitset
42
+ string = self.class.redis.get(key).unpack("b*").first
43
+ Bitset.from_s(string)
44
+ end
45
+
46
+ def key
47
+ @key ||= "redis-bitmap-counter:#{@name}"
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,47 @@
1
+ module Bitmap
2
+ class DateCounter < Bitmap::Counter
3
+ def count(id, date = Date.today)
4
+ self.class.redis.setbit(key(date), id, 1)
5
+ end
6
+
7
+ def counted?(id, date = Date.today)
8
+ self.class.redis.getbit(key(date), id) == 1
9
+ end
10
+
11
+ def unique(range = [Date.today])
12
+ range = [range] if range.is_a?(Date)
13
+ set = nil
14
+ range.each do |date|
15
+ next unless s = bitset(date)
16
+ set ||= s
17
+ set |= s
18
+ end
19
+ set.cardinality
20
+ end
21
+
22
+ def delete
23
+ keys = self.class.redis.keys("#{prefix}*")
24
+ self.class.redis.pipelined do
25
+ keys.each do |k|
26
+ self.class.redis.del(k)
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def bitset(date)
34
+ if string = self.class.redis.get(key(date))
35
+ Bitset.from_s(string.unpack("b*").first)
36
+ end
37
+ end
38
+
39
+ def key(date)
40
+ "#{prefix}#{date}"
41
+ end
42
+
43
+ def prefix
44
+ "redis-bitmap-counter:#{@name}:"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,4 @@
1
+ require "redis"
2
+ require "bitset"
3
+ require "bitmap/counter"
4
+ require "bitmap/date_counter"
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bitmap::Counter do
4
+ let(:counter) { Bitmap::Counter.new("test") }
5
+
6
+ before do
7
+ counter.delete
8
+ counter.count(1)
9
+ counter.count(3)
10
+ counter.count(7)
11
+ end
12
+
13
+ describe ".redis" do
14
+ it "uses a supplied redis instance" do
15
+ counter.counted?(1).should be_true
16
+
17
+ other_redis = Redis.new
18
+ other_redis.select(1)
19
+
20
+ Bitmap::Counter.redis = other_redis
21
+ Bitmap::Counter.redis.should == other_redis
22
+
23
+ other_counter = Bitmap::Counter.new("test")
24
+ other_counter.delete
25
+
26
+ other_counter.count(2)
27
+ other_counter.counted?(1).should be_false
28
+ other_counter.counted?(2).should be_true
29
+ end
30
+ end
31
+
32
+ describe "#unique" do
33
+ it "returns the sum of all unique ids" do
34
+ counter.unique.should == 3 # "10001010"
35
+ end
36
+ end
37
+
38
+ describe "#counted?" do
39
+ it "returns true/false if id has been counted" do
40
+ counter.counted?(1).should be_true
41
+ counter.counted?(2).should be_false
42
+ counter.counted?(3).should be_true
43
+ counter.counted?(7).should be_true
44
+ counter.counted?(99).should be_false
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bitmap::DateCounter do
4
+ let(:counter) { Bitmap::DateCounter.new("test") }
5
+
6
+ before do
7
+ counter.delete
8
+ end
9
+
10
+ it "defaults to today" do
11
+ counter.count(1)
12
+ counter.counted?(1).should be_true
13
+ counter.unique.should == 1
14
+ end
15
+
16
+ it "reuses redis connections" do
17
+ lambda {
18
+ Bitmap::Counter.new("test")
19
+ Bitmap::Counter.new("test")
20
+ Bitmap::DateCounter.new("test")
21
+ }.should_not change { Bitmap::Counter.redis.info["connected_clients"] }
22
+ end
23
+
24
+ describe "with multiple days of data" do
25
+ before do
26
+ counter.count(1, Date.today)
27
+ counter.count(3, Date.today)
28
+ counter.count(7, Date.today)
29
+ counter.count(1, Date.today - 1)
30
+ end
31
+
32
+ it "returns counted? for an id on a date" do
33
+ counter.counted?(3, Date.today).should be_true
34
+ counter.counted?(3, Date.today - 1).should be_false
35
+
36
+ counter.counted?(1, Date.today).should be_true
37
+ counter.counted?(1, Date.today - 1).should be_true
38
+ end
39
+
40
+ it "returns number of unique ids for specific dates" do
41
+ counter.unique(Date.today).should == 3
42
+ counter.unique(Date.today - 1).should == 1
43
+ end
44
+
45
+ it "returns number of unique ids for a date range" do
46
+ counter.unique((Date.today - 1)..Date.today).should == 3
47
+ end
48
+
49
+ it "is not affected by out of range errors" do
50
+ counter.unique((Date.today - 2)..Date.today).should == 3
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ Bundler.require :default, :development
4
+
5
+ require_relative "../lib/bitmap_counter"
6
+
7
+ RSpec.configure do |config|
8
+ config.before(:each) do
9
+ # ???
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitmap-counter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brandon Keene
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-30 00:00:00.000000000 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: redis
17
+ requirement: &2152201380 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2152201380
26
+ - !ruby/object:Gem::Dependency
27
+ name: bitset
28
+ requirement: &2152200960 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *2152200960
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &2152200460 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 2.6.0
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2152200460
48
+ description:
49
+ email:
50
+ - bkeene@gmail.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - Gemfile
57
+ - Gemfile.lock
58
+ - README.md
59
+ - Rakefile
60
+ - bitmap-counter.gemspec
61
+ - lib/bitmap/counter.rb
62
+ - lib/bitmap/date_counter.rb
63
+ - lib/bitmap_counter.rb
64
+ - spec/bitmap/counter_spec.rb
65
+ - spec/bitmap/date_counter_spec.rb
66
+ - spec/spec_helper.rb
67
+ has_rdoc: true
68
+ homepage: ''
69
+ licenses: []
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 1.6.2
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: A unique id counter implemented with redis bitmaps. Count the unique users
92
+ across a date range.
93
+ test_files:
94
+ - spec/bitmap/counter_spec.rb
95
+ - spec/bitmap/date_counter_spec.rb
96
+ - spec/spec_helper.rb