minuteman 1.0.0.pre → 1.0.2
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/Gemfile.lock +4 -1
- data/README.md +29 -18
- data/Rakefile +6 -1
- data/lib/minuteman.rb +81 -20
- data/lib/minuteman/bit_operations.rb +10 -4
- data/lib/minuteman/bit_operations/data.rb +3 -2
- data/lib/minuteman/bit_operations/operation.rb +8 -4
- data/lib/minuteman/bit_operations/plain.rb +8 -4
- data/lib/minuteman/bit_operations/result.rb +3 -2
- data/lib/minuteman/bit_operations/with_data.rb +32 -12
- data/lib/minuteman/keys_methods.rb +2 -0
- data/lib/minuteman/time_events.rb +4 -3
- data/lib/minuteman/time_span.rb +6 -4
- data/lib/minuteman/time_spans/day.rb +4 -0
- data/lib/minuteman/time_spans/hour.rb +4 -0
- data/lib/minuteman/time_spans/minute.rb +4 -0
- data/lib/minuteman/time_spans/month.rb +4 -0
- data/lib/minuteman/time_spans/week.rb +4 -0
- data/lib/minuteman/time_spans/year.rb +4 -0
- data/minuteman.gemspec +4 -2
- data/test/bench/minuteman_bench.rb +37 -0
- data/test/test_helper.rb +1 -0
- data/test/unit/minuteman_test.rb +61 -6
- metadata +25 -6
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
minuteman (1.0.
|
4
|
+
minuteman (1.0.2)
|
5
5
|
redis (~> 3.0.2)
|
6
6
|
|
7
7
|
GEM
|
@@ -10,6 +10,8 @@ GEM
|
|
10
10
|
minitest (4.2.0)
|
11
11
|
rake (0.9.2.2)
|
12
12
|
redis (3.0.2)
|
13
|
+
redis-namespace (1.2.1)
|
14
|
+
redis (~> 3.0.0)
|
13
15
|
|
14
16
|
PLATFORMS
|
15
17
|
ruby
|
@@ -18,3 +20,4 @@ DEPENDENCIES
|
|
18
20
|
minitest (~> 4.2.0)
|
19
21
|
minuteman!
|
20
22
|
rake
|
23
|
+
redis-namespace (~> 1.2.1)
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+

|
2
|
+
|
1
3
|
# Minuteman
|
2
4
|
[](https://codeclimate.com/github/elcuervo/minuteman)
|
3
5
|
[](https://travis-ci.org/elcuervo/minuteman)
|
@@ -8,10 +10,6 @@ during the American Revolutionary War. _They provided a highly mobile, rapidly
|
|
8
10
|
deployed force that allowed the colonies to respond immediately to war threats,
|
9
11
|
hence the name._
|
10
12
|
|
11
|
-

|
12
|
-
|
13
|
-
Fast analytics using Redis bitwise operations
|
14
|
-
|
15
13
|
## Origin
|
16
14
|
Freenode - #cuba.rb - 2012/10/30 15:20 UYT
|
17
15
|
|
@@ -58,29 +56,42 @@ gem install minuteman
|
|
58
56
|
|
59
57
|
## Usage
|
60
58
|
|
59
|
+
Currently Minutemen supports two options `:silent (default: false)` and `:redis
|
60
|
+
(default: {})`
|
61
|
+
|
62
|
+
### Options
|
63
|
+
|
64
|
+
**silent**: when `true` the operations will not raise errors to prevent failures
|
65
|
+
|
66
|
+
**redis**: can be a Hash with the options to be sent to `Redis.new` or a `Redis`
|
67
|
+
connection already established (`Redis::Namespace` works as well).
|
68
|
+
|
61
69
|
```ruby
|
62
70
|
require "minuteman"
|
63
71
|
|
64
|
-
# Accepts an options hash that will be sent as is to Redis.new
|
72
|
+
# Accepts an options[:redis] hash that will be sent as is to Redis.new
|
65
73
|
analytics = Minuteman.new
|
66
74
|
|
75
|
+
# You can also reuse your Redis or Redis::Namespace connection
|
76
|
+
analytics = Minuteman.new(redis: Redis::Namespace.new(:mm, redis: Redis.new))
|
77
|
+
|
67
78
|
# Mark an event for a given id
|
68
|
-
analytics.
|
69
|
-
analytics.
|
79
|
+
analytics.track("login:successful", user.id)
|
80
|
+
analytics.track("login:successful", other_user.id)
|
70
81
|
|
71
82
|
# Mark in bulk
|
72
|
-
analytics.
|
83
|
+
analytics.track("programming:love:ruby", User.where(favorite: "ruby").pluck(:id))
|
73
84
|
|
74
|
-
# Fetch events for a given time
|
85
|
+
# Fetch events for a given time (default is Time.now.utc)
|
75
86
|
today_events = analytics.day("login:successful", Time.now.utc)
|
76
87
|
|
77
88
|
# This also exists
|
78
|
-
analytics.year("login:successful"
|
79
|
-
analytics.month("login:successful"
|
80
|
-
analytics.week("login:successful"
|
81
|
-
analytics.day("login:successful"
|
82
|
-
analytics.hour("login:successful"
|
83
|
-
analytics.minute("login:successful"
|
89
|
+
analytics.year("login:successful")
|
90
|
+
analytics.month("login:successful")
|
91
|
+
analytics.week("login:successful")
|
92
|
+
analytics.day("login:successful")
|
93
|
+
analytics.hour("login:successful")
|
94
|
+
analytics.minute("login:successful")
|
84
95
|
|
85
96
|
# Lists all the tracked events
|
86
97
|
analytics.events
|
@@ -123,7 +134,7 @@ set1 ^ set2
|
|
123
134
|
Let's assume this scenario:
|
124
135
|
|
125
136
|
You have a list of users and want to know which of them have been going throught
|
126
|
-
some of the
|
137
|
+
some of the tracks you made.
|
127
138
|
|
128
139
|
```ruby
|
129
140
|
paid = analytics.month("buy:complete")
|
@@ -138,8 +149,8 @@ Currently the supported commands to interact with arrays are `&` and `-`
|
|
138
149
|
### Example
|
139
150
|
|
140
151
|
```ruby
|
141
|
-
invited = analytics.month("email:invitation"
|
142
|
-
successful_buys = analytics.month("buy:complete"
|
152
|
+
invited = analytics.month("email:invitation")
|
153
|
+
successful_buys = analytics.month("buy:complete")
|
143
154
|
|
144
155
|
successful_buys_after_invitation = invited & successful_buys
|
145
156
|
successful_buys_after_invitation.include?(user.id)
|
data/Rakefile
CHANGED
@@ -4,5 +4,10 @@ Rake::TestTask.new("spec") do |t|
|
|
4
4
|
t.pattern = "test/**/*_test.rb"
|
5
5
|
end
|
6
6
|
|
7
|
+
Rake::TestTask.new("bench") do |t|
|
8
|
+
t.pattern = "test/bench/*_bench.rb"
|
9
|
+
end
|
10
|
+
|
7
11
|
task :default => [:test]
|
8
|
-
task :
|
12
|
+
task :all => [:test, :bench]
|
13
|
+
task :test => [:spec]
|
data/lib/minuteman.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "redis"
|
2
2
|
require "time"
|
3
|
+
require "forwardable"
|
3
4
|
require "minuteman/time_events"
|
4
5
|
|
5
6
|
# Until redis gem gets updated
|
@@ -17,17 +18,36 @@ class Redis
|
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
21
|
+
# Public: Minuteman core classs
|
22
|
+
#
|
20
23
|
class Minuteman
|
21
|
-
|
24
|
+
extend Forwardable
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_accessor :redis, :options
|
28
|
+
|
29
|
+
# Public: Prevents a fatal error if the options are set to silent
|
30
|
+
#
|
31
|
+
def safe(&block)
|
32
|
+
yield if block
|
33
|
+
rescue Redis::BaseError => e
|
34
|
+
raise e unless options[:silent]
|
35
|
+
end
|
36
|
+
end
|
22
37
|
|
23
38
|
PREFIX = "minuteman"
|
24
39
|
|
40
|
+
def_delegators self, :redis, :redis=, :options, :options=, :safe
|
41
|
+
|
25
42
|
# Public: Initializes Minuteman
|
26
43
|
#
|
27
|
-
#
|
44
|
+
# options - An options hash to change how Minuteman behaves
|
28
45
|
#
|
29
46
|
def initialize(options = {})
|
30
|
-
|
47
|
+
redis_options = options.delete(:redis) || {}
|
48
|
+
|
49
|
+
self.options = default_options.merge!(options)
|
50
|
+
self.redis = define_connection(redis_options)
|
31
51
|
end
|
32
52
|
|
33
53
|
# Public: Generates the methods to fech data
|
@@ -36,9 +56,12 @@ class Minuteman
|
|
36
56
|
# date - A Time object used to do the search
|
37
57
|
#
|
38
58
|
%w[year month week day hour minute].each do |method_name|
|
39
|
-
define_method(method_name) do |
|
59
|
+
define_method(method_name) do |*args|
|
60
|
+
event_name, date = *args
|
61
|
+
date ||= Time.now.utc
|
62
|
+
|
40
63
|
constructor = self.class.const_get(method_name.capitalize)
|
41
|
-
constructor.new(
|
64
|
+
constructor.new(event_name, date)
|
42
65
|
end
|
43
66
|
end
|
44
67
|
|
@@ -50,49 +73,87 @@ class Minuteman
|
|
50
73
|
# Examples
|
51
74
|
#
|
52
75
|
# analytics = Minuteman.new
|
53
|
-
# analytics.
|
54
|
-
# analytics.
|
76
|
+
# analytics.track("login", 1)
|
77
|
+
# analytics.track("login", [2, 3, 4])
|
55
78
|
#
|
56
|
-
def
|
79
|
+
def track(event_name, ids, time = Time.now.utc)
|
57
80
|
event_time = time.kind_of?(Time) ? time : Time.parse(time.to_s)
|
58
|
-
time_events = TimeEvents.start(
|
81
|
+
time_events = TimeEvents.start(event_name, event_time)
|
59
82
|
|
60
|
-
|
61
|
-
time_events.each do |event|
|
62
|
-
Array(ids).each { |id| redis.setbit(event.key, id, 1) }
|
63
|
-
end
|
64
|
-
end
|
83
|
+
track_events(time_events, Array(ids))
|
65
84
|
end
|
66
85
|
|
67
86
|
# Public: List all the events given the minuteman namespace
|
68
87
|
#
|
69
88
|
def events
|
70
|
-
keys =
|
89
|
+
keys = safe { redis.keys([PREFIX, "*", "????"].join("_")) }
|
71
90
|
keys.map { |key| key.split("_")[1] }
|
72
91
|
end
|
73
92
|
|
74
93
|
# Public: List all the operations executed in a given the minuteman namespace
|
75
94
|
#
|
76
95
|
def operations
|
77
|
-
|
96
|
+
safe { redis.keys([operations_cache_key_prefix, "*"].join("_")) }
|
78
97
|
end
|
79
98
|
|
80
99
|
# Public: Resets the bit operation cache keys
|
81
100
|
#
|
82
101
|
def reset_operations_cache
|
83
|
-
keys =
|
84
|
-
|
102
|
+
keys = safe { redis.keys([operations_cache_key_prefix, "*"].join("_")) }
|
103
|
+
safe { redis.del(keys) } if keys.any?
|
85
104
|
end
|
86
105
|
|
87
106
|
# Public: Resets all the used keys
|
88
107
|
#
|
89
108
|
def reset_all
|
90
|
-
keys =
|
91
|
-
|
109
|
+
keys = safe { redis.keys([PREFIX, "*"].join("_")) }
|
110
|
+
safe { redis.del(keys) } if keys.any?
|
92
111
|
end
|
93
112
|
|
94
113
|
private
|
95
114
|
|
115
|
+
# Private: Default configuration options
|
116
|
+
#
|
117
|
+
def default_options
|
118
|
+
{
|
119
|
+
cache: true,
|
120
|
+
silent: false
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
# Private: Determines to use or instance a Redis connection
|
125
|
+
#
|
126
|
+
# object: Can be the options to instance a Redis connection or a connection
|
127
|
+
# itself
|
128
|
+
#
|
129
|
+
def define_connection(object)
|
130
|
+
case object
|
131
|
+
when Redis, defined?(Redis::Namespace) && Redis::Namespace
|
132
|
+
object
|
133
|
+
else
|
134
|
+
Redis.new(object)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Private: Marks ids for a given time events
|
139
|
+
#
|
140
|
+
# time_events: A set of TimeEvents
|
141
|
+
# ids: The ids to be tracked
|
142
|
+
#
|
143
|
+
def track_events(time_events, ids)
|
144
|
+
safe_multi do
|
145
|
+
time_events.each do |event|
|
146
|
+
ids.each { |id| safe { redis.setbit(event.key, id, 1) } }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Private: Executes a block within a safe connection using redis.multi
|
152
|
+
#
|
153
|
+
def safe_multi(&block)
|
154
|
+
safe { redis.multi(&block) }
|
155
|
+
end
|
156
|
+
|
96
157
|
# Private: The prefix key of all the operations
|
97
158
|
#
|
98
159
|
def operations_cache_key_prefix
|
@@ -1,7 +1,13 @@
|
|
1
1
|
require "minuteman/bit_operations/operation"
|
2
2
|
|
3
|
+
# Public: Minuteman core classs
|
4
|
+
#
|
3
5
|
class Minuteman
|
4
6
|
module BitOperations
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :Minuteman, :safe, :redis
|
10
|
+
|
5
11
|
# Public: Checks for the existance of ids on a given set
|
6
12
|
#
|
7
13
|
# ids - Array of ids
|
@@ -14,13 +20,13 @@ class Minuteman
|
|
14
20
|
# Public: Resets the current key
|
15
21
|
#
|
16
22
|
def reset
|
17
|
-
redis.rem(key)
|
23
|
+
safe { redis.rem(key) }
|
18
24
|
end
|
19
25
|
|
20
26
|
# Public: Cheks for the amount of ids stored on the current key
|
21
27
|
#
|
22
28
|
def length
|
23
|
-
redis.bitcount(key)
|
29
|
+
safe { redis.bitcount(key) }
|
24
30
|
end
|
25
31
|
|
26
32
|
# Public: Calculates the NOT of the current key
|
@@ -70,7 +76,7 @@ class Minuteman
|
|
70
76
|
# id: The bit
|
71
77
|
#
|
72
78
|
def getbit(id)
|
73
|
-
redis.getbit(key, id) == 1
|
79
|
+
safe { redis.getbit(key, id) == 1 }
|
74
80
|
end
|
75
81
|
|
76
82
|
# Private: Cxecutes an operation between the current timespan and another
|
@@ -85,7 +91,7 @@ class Minuteman
|
|
85
91
|
# Private: Memoizes the operation class
|
86
92
|
#
|
87
93
|
def operate
|
88
|
-
@_operate ||= Operation.new(
|
94
|
+
@_operate ||= Operation.new(self)
|
89
95
|
end
|
90
96
|
end
|
91
97
|
end
|
@@ -1,14 +1,15 @@
|
|
1
1
|
require "minuteman/bit_operations"
|
2
2
|
|
3
|
+
# Public: Minuteman core classs
|
4
|
+
#
|
3
5
|
class Minuteman
|
4
6
|
module BitOperations
|
5
7
|
# Public: The conversion of an array to an operable class
|
6
8
|
#
|
7
|
-
# redis - The Redis connection
|
8
9
|
# key - The key where the result it's stored
|
9
10
|
# data - The original data of the intersection
|
10
11
|
#
|
11
|
-
class Data < Struct.new(:
|
12
|
+
class Data < Struct.new(:key, :data)
|
12
13
|
include BitOperations
|
13
14
|
include Enumerable
|
14
15
|
|
@@ -1,16 +1,17 @@
|
|
1
1
|
require "minuteman/bit_operations/plain"
|
2
2
|
require "minuteman/bit_operations/with_data"
|
3
3
|
|
4
|
+
# Public: Minuteman core classs
|
5
|
+
#
|
4
6
|
class Minuteman
|
5
7
|
module BitOperations
|
6
8
|
# Public: Handles the operations between two timespans
|
7
9
|
#
|
8
|
-
# redis: The Redis connection
|
9
10
|
# type: The operation type
|
10
11
|
# timespan: One of the timespans to be permuted
|
11
12
|
# other: The other timespan to be permuted
|
12
13
|
#
|
13
|
-
class Operation < Struct.new(:
|
14
|
+
class Operation < Struct.new(:timespan)
|
14
15
|
# Public: Caches operations against Array
|
15
16
|
#
|
16
17
|
class Cache
|
@@ -68,7 +69,7 @@ class Minuteman
|
|
68
69
|
return minus_operation if type == "MINUS" && operable?
|
69
70
|
return cache[other] if cache.include?(other)
|
70
71
|
|
71
|
-
caching { klass.new(
|
72
|
+
caching { klass.new(type, other, timespan.key).call }
|
72
73
|
end
|
73
74
|
|
74
75
|
private
|
@@ -83,7 +84,10 @@ class Minuteman
|
|
83
84
|
#
|
84
85
|
def caching
|
85
86
|
executed_class = yield
|
86
|
-
|
87
|
+
if other.is_a?(Array) && Minuteman.options[:cache]
|
88
|
+
cache[other] = executed_class
|
89
|
+
end
|
90
|
+
|
87
91
|
executed_class
|
88
92
|
end
|
89
93
|
|
@@ -1,18 +1,22 @@
|
|
1
1
|
require "minuteman/keys_methods"
|
2
2
|
require "minuteman/bit_operations/result"
|
3
3
|
|
4
|
+
# Public: Minuteman core classs
|
5
|
+
#
|
4
6
|
class Minuteman
|
5
7
|
module BitOperations
|
6
8
|
# Public: The class to handle operations with others timespans
|
7
9
|
#
|
8
|
-
# redis: The Redis connection
|
9
10
|
# type: The operation type
|
10
11
|
# timespan: The timespan to be permuted
|
11
12
|
# source_key: The original key to do the operation
|
12
13
|
#
|
13
|
-
class Plain < Struct.new(:
|
14
|
+
class Plain < Struct.new(:type, :timespan, :source_key)
|
15
|
+
extend Forwardable
|
14
16
|
include KeysMethods
|
15
17
|
|
18
|
+
def_delegators :Minuteman, :redis, :safe
|
19
|
+
|
16
20
|
def call
|
17
21
|
events = if source_key == timespan
|
18
22
|
Array(source_key)
|
@@ -21,9 +25,9 @@ class Minuteman
|
|
21
25
|
end
|
22
26
|
|
23
27
|
key = destination_key(type, events)
|
24
|
-
redis.bitop(type, key, events)
|
28
|
+
safe { redis.bitop(type, key, events) }
|
25
29
|
|
26
|
-
Result.new(
|
30
|
+
Result.new(key)
|
27
31
|
end
|
28
32
|
end
|
29
33
|
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
require "minuteman/bit_operations"
|
2
2
|
|
3
|
+
# Public: Minuteman core classs
|
4
|
+
#
|
3
5
|
class Minuteman
|
4
6
|
module BitOperations
|
5
7
|
# Public: The result of intersecting results
|
6
8
|
#
|
7
|
-
# redis - The Redis connection
|
8
9
|
# key - The key where the result it's stored
|
9
10
|
#
|
10
|
-
class Result < Struct.new(:
|
11
|
+
class Result < Struct.new(:key)
|
11
12
|
include BitOperations
|
12
13
|
end
|
13
14
|
end
|
@@ -1,35 +1,55 @@
|
|
1
1
|
require "minuteman/keys_methods"
|
2
2
|
require "minuteman/bit_operations/data"
|
3
3
|
|
4
|
+
# Public: Minuteman core classs
|
5
|
+
#
|
4
6
|
class Minuteman
|
5
7
|
module BitOperations
|
6
8
|
# Public: The class to handle operations with datasets
|
7
9
|
#
|
8
|
-
# redis: The Redis connection
|
9
10
|
# type: The operation type
|
10
11
|
# data: The data to be permuted
|
11
12
|
# source_key: The original key to do the operation
|
12
13
|
#
|
13
|
-
class WithData < Struct.new(:
|
14
|
+
class WithData < Struct.new(:type, :data, :source_key)
|
15
|
+
extend Forwardable
|
14
16
|
include KeysMethods
|
15
17
|
|
18
|
+
def_delegators :Minuteman, :redis, :safe
|
19
|
+
|
16
20
|
def call
|
17
|
-
normalized_data = Array(data)
|
18
21
|
key = destination_key("data-#{type}", normalized_data)
|
19
|
-
command = case type
|
20
|
-
when "AND" then :select
|
21
|
-
when "MINUS" then :reject
|
22
|
-
end
|
23
22
|
|
24
|
-
|
25
|
-
redis.
|
23
|
+
if !safe { redis.exists(key) }
|
24
|
+
intersected_data.each { |id| safe { redis.setbit(key, id, 1) } }
|
26
25
|
end
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
Data.new(key, intersected_data)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Private: Normalized data
|
33
|
+
#
|
34
|
+
def normalized_data
|
35
|
+
Array(data)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Private: Defines command to get executed based on the type
|
39
|
+
#
|
40
|
+
def command
|
41
|
+
case type
|
42
|
+
when "AND" then :select
|
43
|
+
when "MINUS" then :reject
|
30
44
|
end
|
45
|
+
end
|
31
46
|
|
32
|
-
|
47
|
+
# Private: The intersected data depending on the command executed
|
48
|
+
#
|
49
|
+
def intersected_data
|
50
|
+
normalized_data.send(command) do |id|
|
51
|
+
Minuteman.redis.getbit(source_key, id) == 1
|
52
|
+
end
|
33
53
|
end
|
34
54
|
end
|
35
55
|
end
|
@@ -1,16 +1,17 @@
|
|
1
1
|
require "minuteman/time_spans"
|
2
2
|
|
3
|
+
# Public: Minuteman core classs
|
4
|
+
#
|
3
5
|
class Minuteman
|
4
6
|
module TimeEvents
|
5
7
|
# Public: Helper to get all the time trackers ready
|
6
8
|
#
|
7
|
-
# redis - The Redis connection
|
8
9
|
# event_name - The event to be tracked
|
9
10
|
# date - A given Time object
|
10
11
|
#
|
11
|
-
def self.start(
|
12
|
+
def self.start(event_name, time)
|
12
13
|
[Year, Month, Week, Day, Hour, Minute].map do |t|
|
13
|
-
t.new(
|
14
|
+
t.new(event_name, time)
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
data/lib/minuteman/time_span.rb
CHANGED
@@ -1,22 +1,24 @@
|
|
1
1
|
require "minuteman/bit_operations"
|
2
2
|
|
3
|
+
# Public: Minuteman core classs
|
4
|
+
#
|
3
5
|
class Minuteman
|
6
|
+
# Public: The timespan class. All the time span classes inherit from this one
|
7
|
+
#
|
4
8
|
class TimeSpan
|
5
9
|
include BitOperations
|
6
10
|
|
7
|
-
attr_reader :key
|
11
|
+
attr_reader :key
|
8
12
|
|
9
13
|
DATE_FORMAT = "%s-%02d-%02d"
|
10
14
|
TIME_FORMAT = "%02d:%02d"
|
11
15
|
|
12
16
|
# Public: Initializes the base TimeSpan class
|
13
17
|
#
|
14
|
-
# redis - The Redis connection
|
15
18
|
# event_name - The event to be tracked
|
16
19
|
# date - A given Time object
|
17
20
|
#
|
18
|
-
def initialize(
|
19
|
-
@redis = redis
|
21
|
+
def initialize(event_name, date)
|
20
22
|
@key = build_key(event_name, time_format(date))
|
21
23
|
end
|
22
24
|
|
data/minuteman.gemspec
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "minuteman"
|
3
|
-
s.version = "1.0.
|
3
|
+
s.version = "1.0.2"
|
4
4
|
s.summary = "Bit Analytics"
|
5
5
|
s.description = "Fast and furious tracking system using Redis bitwise operations"
|
6
6
|
s.authors = ["elcuervo"]
|
7
|
+
s.licenses = ["MIT"]
|
7
8
|
s.email = ["yo@brunoaguirre.com"]
|
8
9
|
s.homepage = "http://github.com/elcuervo/minuteman"
|
9
10
|
s.files = `git ls-files`.split("\n")
|
@@ -11,5 +12,6 @@ Gem::Specification.new do |s|
|
|
11
12
|
|
12
13
|
s.add_dependency("redis", "~> 3.0.2")
|
13
14
|
|
14
|
-
s.add_development_dependency("minitest",
|
15
|
+
s.add_development_dependency("minitest", "~> 4.2.0")
|
16
|
+
s.add_development_dependency("redis-namespace", "~> 1.2.1")
|
15
17
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative "../test_helper"
|
2
|
+
require "minitest/benchmark"
|
3
|
+
|
4
|
+
describe Minuteman do
|
5
|
+
before do
|
6
|
+
today = Time.now.utc
|
7
|
+
last_week = today - (3600 * 24 * 7)
|
8
|
+
|
9
|
+
@analytics = Minuteman.new
|
10
|
+
@analytics.mark("login", 12)
|
11
|
+
@analytics.mark("login", [2, 42])
|
12
|
+
@analytics.mark("login:successful", 567, last_week)
|
13
|
+
|
14
|
+
@week_events = @analytics.week("login")
|
15
|
+
@last_week_events = @analytics.week("login", last_week)
|
16
|
+
@last_week_events2 = @analytics.month("login:successful", last_week)
|
17
|
+
end
|
18
|
+
|
19
|
+
bench_performance_constant("AND") { @week_events & @last_week_events }
|
20
|
+
bench_performance_constant("OR") { @week_events | @last_week_events }
|
21
|
+
bench_performance_constant("XOR") { @week_events ^ @last_week_events }
|
22
|
+
bench_performance_constant("NOT") { ~@week_events }
|
23
|
+
bench_performance_constant("MINUS") { @week_events - @last_week_events }
|
24
|
+
|
25
|
+
bench_performance_constant "complex operations" do
|
26
|
+
@week_events & (@last_week_events ^ @last_week_events2)
|
27
|
+
end
|
28
|
+
|
29
|
+
bench_performance_constant "intersections using cache" do
|
30
|
+
5.times { @week_events & [2, 12, 43] }
|
31
|
+
end
|
32
|
+
|
33
|
+
bench_performance_constant "intersections not using cache" do
|
34
|
+
@analytics.options[:cache] = false
|
35
|
+
5.times { @week_events & [2, 12, 43] }
|
36
|
+
end
|
37
|
+
end
|
data/test/test_helper.rb
CHANGED
data/test/unit/minuteman_test.rb
CHANGED
@@ -5,14 +5,14 @@ describe Minuteman do
|
|
5
5
|
@analytics = Minuteman.new
|
6
6
|
|
7
7
|
today = Time.now.utc
|
8
|
-
last_month
|
9
|
-
last_week
|
8
|
+
last_month = today - (3600 * 24 * 30)
|
9
|
+
last_week = today - (3600 * 24 * 7)
|
10
10
|
last_minute = today - 120
|
11
11
|
|
12
|
-
@analytics.
|
13
|
-
@analytics.
|
14
|
-
@analytics.
|
15
|
-
@analytics.
|
12
|
+
@analytics.track("login", 12)
|
13
|
+
@analytics.track("login", [2, 42])
|
14
|
+
@analytics.track("login", 2, last_week)
|
15
|
+
@analytics.track("login:successful", 567, last_month)
|
16
16
|
|
17
17
|
@year_events = @analytics.year("login", today)
|
18
18
|
@week_events = @analytics.week("login", today)
|
@@ -160,3 +160,58 @@ describe Minuteman do
|
|
160
160
|
assert_equal 2, ids.size
|
161
161
|
end
|
162
162
|
end
|
163
|
+
|
164
|
+
describe "Using options" do
|
165
|
+
it "should be able to stop using the cache" do
|
166
|
+
minuteman = Minuteman.new
|
167
|
+
assert_equal true, minuteman.options[:cache]
|
168
|
+
|
169
|
+
minuteman.options[:cache] = false
|
170
|
+
|
171
|
+
assert_equal false, minuteman.options[:cache]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "Changing Minuteman redis connections" do
|
176
|
+
it "should support using a Redis instance" do
|
177
|
+
redis = Redis.new
|
178
|
+
minuteman = Minuteman.new(redis: redis)
|
179
|
+
|
180
|
+
assert_equal redis, Minuteman.redis
|
181
|
+
assert_equal redis, minuteman.redis
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should support changing the current connection" do
|
185
|
+
redis = Redis.new
|
186
|
+
minuteman = Minuteman.new
|
187
|
+
|
188
|
+
assert redis != Minuteman.redis
|
189
|
+
|
190
|
+
minuteman.redis = redis
|
191
|
+
|
192
|
+
assert_equal redis, minuteman.redis
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should support Redis::Namespace" do
|
196
|
+
namespace = Redis::Namespace.new(:ns, redis: Redis.new)
|
197
|
+
|
198
|
+
minuteman = Minuteman.new(redis: namespace)
|
199
|
+
|
200
|
+
assert_equal namespace, Minuteman.redis
|
201
|
+
assert_equal namespace, minuteman.redis
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should fail silently" do
|
205
|
+
minuteman = Minuteman.new(silent: true, redis: { port: 1234 })
|
206
|
+
|
207
|
+
minuteman.track("test", 1)
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should fail loudly" do
|
211
|
+
minuteman = Minuteman.new(redis: { port: 1234 })
|
212
|
+
|
213
|
+
assert_raises Redis::CannotConnectError do
|
214
|
+
minuteman.track("test", 1)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minuteman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.2
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- elcuervo
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -43,6 +43,22 @@ dependencies:
|
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: 4.2.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: redis-namespace
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.2.1
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.2.1
|
46
62
|
description: Fast and furious tracking system using Redis bitwise operations
|
47
63
|
email:
|
48
64
|
- yo@brunoaguirre.com
|
@@ -74,10 +90,12 @@ files:
|
|
74
90
|
- lib/minuteman/time_spans/week.rb
|
75
91
|
- lib/minuteman/time_spans/year.rb
|
76
92
|
- minuteman.gemspec
|
93
|
+
- test/bench/minuteman_bench.rb
|
77
94
|
- test/test_helper.rb
|
78
95
|
- test/unit/minuteman_test.rb
|
79
96
|
homepage: http://github.com/elcuervo/minuteman
|
80
|
-
licenses:
|
97
|
+
licenses:
|
98
|
+
- MIT
|
81
99
|
post_install_message:
|
82
100
|
rdoc_options: []
|
83
101
|
require_paths:
|
@@ -91,9 +109,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
91
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
110
|
none: false
|
93
111
|
requirements:
|
94
|
-
- - ! '
|
112
|
+
- - ! '>='
|
95
113
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
114
|
+
version: '0'
|
97
115
|
requirements: []
|
98
116
|
rubyforge_project:
|
99
117
|
rubygems_version: 1.8.23
|
@@ -101,5 +119,6 @@ signing_key:
|
|
101
119
|
specification_version: 3
|
102
120
|
summary: Bit Analytics
|
103
121
|
test_files:
|
122
|
+
- test/bench/minuteman_bench.rb
|
104
123
|
- test/test_helper.rb
|
105
124
|
- test/unit/minuteman_test.rb
|