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 +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +28 -0
- data/README.md +51 -0
- data/Rakefile +6 -0
- data/bitmap-counter.gemspec +22 -0
- data/lib/bitmap/counter.rb +50 -0
- data/lib/bitmap/date_counter.rb +47 -0
- data/lib/bitmap_counter.rb +4 -0
- data/spec/bitmap/counter_spec.rb +47 -0
- data/spec/bitmap/date_counter_spec.rb +53 -0
- data/spec/spec_helper.rb +11 -0
- metadata +96 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.rvmrc
|
data/Gemfile
ADDED
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,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,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
|
data/spec/spec_helper.rb
ADDED
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
|