minuteman 1.0.3 → 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gems +3 -0
  3. data/.travis.yml +4 -3
  4. data/ORIGIN.md +21 -0
  5. data/README.md +115 -122
  6. data/Rakefile +3 -3
  7. data/SPEC.md +21 -0
  8. data/lib/minuteman.rb +88 -122
  9. data/lib/minuteman/analyzable.rb +96 -0
  10. data/lib/minuteman/analyzer.rb +21 -0
  11. data/lib/minuteman/configuration.rb +25 -0
  12. data/lib/minuteman/counter.rb +21 -0
  13. data/lib/minuteman/event.rb +12 -0
  14. data/lib/minuteman/lua/operations.lua +37 -0
  15. data/lib/minuteman/model.rb +36 -0
  16. data/lib/minuteman/result.rb +12 -0
  17. data/lib/minuteman/trigger.rb +4 -0
  18. data/lib/minuteman/user.rb +38 -0
  19. data/minuteman.gemspec +4 -5
  20. data/test/helper.rb +2 -0
  21. data/test/minuteman_bench.rb +72 -0
  22. data/test/minuteman_test.rb +204 -0
  23. metadata +44 -73
  24. data/Gemfile +0 -4
  25. data/Gemfile.lock +0 -30
  26. data/lib/minuteman/bit_operations.rb +0 -97
  27. data/lib/minuteman/bit_operations/data.rb +0 -33
  28. data/lib/minuteman/bit_operations/operation.rb +0 -115
  29. data/lib/minuteman/bit_operations/plain.rb +0 -34
  30. data/lib/minuteman/bit_operations/result.rb +0 -15
  31. data/lib/minuteman/bit_operations/with_data.rb +0 -56
  32. data/lib/minuteman/keys_methods.rb +0 -23
  33. data/lib/minuteman/time_events.rb +0 -18
  34. data/lib/minuteman/time_span.rb +0 -36
  35. data/lib/minuteman/time_spans.rb +0 -7
  36. data/lib/minuteman/time_spans/day.rb +0 -17
  37. data/lib/minuteman/time_spans/hour.rb +0 -19
  38. data/lib/minuteman/time_spans/minute.rb +0 -19
  39. data/lib/minuteman/time_spans/month.rb +0 -17
  40. data/lib/minuteman/time_spans/week.rb +0 -18
  41. data/lib/minuteman/time_spans/year.rb +0 -17
  42. data/test/bench/minuteman_bench.rb +0 -37
  43. data/test/test_helper.rb +0 -9
  44. data/test/unit/minuteman_test.rb +0 -225
@@ -1,34 +0,0 @@
1
- require "minuteman/keys_methods"
2
- require "minuteman/bit_operations/result"
3
-
4
- # Public: Minuteman core classs
5
- #
6
- class Minuteman
7
- module BitOperations
8
- # Public: The class to handle operations with others timespans
9
- #
10
- # type: The operation type
11
- # timespan: The timespan to be permuted
12
- # source_key: The original key to do the operation
13
- #
14
- class Plain < Struct.new(:type, :timespan, :source_key)
15
- extend Forwardable
16
- include KeysMethods
17
-
18
- def_delegators :Minuteman, :redis, :safe
19
-
20
- def call
21
- events = if source_key == timespan
22
- Array(source_key)
23
- else
24
- [source_key, timespan.key]
25
- end
26
-
27
- key = destination_key(type, events)
28
- safe { redis.bitop(type, key, events) }
29
-
30
- Result.new(key)
31
- end
32
- end
33
- end
34
- end
@@ -1,15 +0,0 @@
1
- require "minuteman/bit_operations"
2
-
3
- # Public: Minuteman core classs
4
- #
5
- class Minuteman
6
- module BitOperations
7
- # Public: The result of intersecting results
8
- #
9
- # key - The key where the result it's stored
10
- #
11
- class Result < Struct.new(:key)
12
- include BitOperations
13
- end
14
- end
15
- end
@@ -1,56 +0,0 @@
1
- require "minuteman/keys_methods"
2
- require "minuteman/bit_operations/data"
3
-
4
- # Public: Minuteman core classs
5
- #
6
- class Minuteman
7
- module BitOperations
8
- # Public: The class to handle operations with datasets
9
- #
10
- # type: The operation type
11
- # data: The data to be permuted
12
- # source_key: The original key to do the operation
13
- #
14
- class WithData < Struct.new(:type, :data, :source_key)
15
- extend Forwardable
16
- include KeysMethods
17
-
18
- def_delegators :Minuteman, :redis, :safe
19
-
20
- def call
21
- key = destination_key("data-#{type}", normalized_data)
22
-
23
- if !safe { redis.exists(key) }
24
- intersected_data.each { |id| safe { redis.setbit(key, id, 1) } }
25
- end
26
-
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
44
- end
45
- end
46
-
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
53
- end
54
- end
55
- end
56
- end
@@ -1,23 +0,0 @@
1
- # Public: Minuteman core classs
2
- #
3
- class Minuteman
4
- module KeysMethods
5
- BIT_OPERATION_PREFIX = "bitop"
6
-
7
- private
8
-
9
- # Private: The destination key for the operation
10
- #
11
- # type - The bitwise operation
12
- # events - The events to permuted
13
- #
14
- def destination_key(type, events)
15
- [
16
- Minuteman::PREFIX,
17
- BIT_OPERATION_PREFIX,
18
- type,
19
- events.join("-")
20
- ].join("_")
21
- end
22
- end
23
- end
@@ -1,18 +0,0 @@
1
- require "minuteman/time_spans"
2
-
3
- # Public: Minuteman core classs
4
- #
5
- class Minuteman
6
- module TimeEvents
7
- # Public: Helper to get all the time trackers ready
8
- #
9
- # event_name - The event to be tracked
10
- # date - A given Time object
11
- #
12
- def self.start(time_spans, event_name, time)
13
- time_spans.map do |t|
14
- t.new(event_name, time)
15
- end
16
- end
17
- end
18
- end
@@ -1,36 +0,0 @@
1
- require "minuteman/bit_operations"
2
-
3
- # Public: Minuteman core classs
4
- #
5
- class Minuteman
6
- # Public: The timespan class. All the time span classes inherit from this one
7
- #
8
- class TimeSpan
9
- include BitOperations
10
-
11
- attr_reader :key
12
-
13
- DATE_FORMAT = "%s-%02d-%02d"
14
- TIME_FORMAT = "%02d:%02d"
15
-
16
- # Public: Initializes the base TimeSpan class
17
- #
18
- # event_name - The event to be tracked
19
- # date - A given Time object
20
- #
21
- def initialize(event_name, date)
22
- @key = build_key(event_name, time_format(date))
23
- end
24
-
25
- private
26
-
27
- # Private: The redis key that's going to be used
28
- #
29
- # event_name - The event to be tracked
30
- # date - A given Time object
31
- #
32
- def build_key(event_name, date)
33
- [Minuteman::PREFIX, event_name, date.join("-")].join("_")
34
- end
35
- end
36
- end
@@ -1,7 +0,0 @@
1
- require "minuteman/time_span"
2
- require "minuteman/time_spans/year"
3
- require "minuteman/time_spans/month"
4
- require "minuteman/time_spans/week"
5
- require "minuteman/time_spans/day"
6
- require "minuteman/time_spans/hour"
7
- require "minuteman/time_spans/minute"
@@ -1,17 +0,0 @@
1
- # Public: Minuteman core classs
2
- #
3
- class Minuteman
4
- # Public: Day TimeSpan class
5
- #
6
- class Day < TimeSpan
7
- private
8
-
9
- # Private: The format that's going the be used for the date part of the key
10
- #
11
- # date - A given Time object
12
- #
13
- def time_format(date)
14
- [DATE_FORMAT % [date.year, date.month, date.day]]
15
- end
16
- end
17
- end
@@ -1,19 +0,0 @@
1
- # Public: Minuteman core classs
2
- #
3
- class Minuteman
4
- # Public: Hour TimeSpan class
5
- #
6
- class Hour < TimeSpan
7
- private
8
-
9
- # Private: The format that's going the be used for the date part of the key
10
- #
11
- # date - A given Time object
12
- #
13
- def time_format(date)
14
- full_date = DATE_FORMAT % [date.year, date.month, date.day]
15
- time = TIME_FORMAT % [date.hour, 0]
16
- [full_date + " " + time]
17
- end
18
- end
19
- end
@@ -1,19 +0,0 @@
1
- # Public: Minuteman core classs
2
- #
3
- class Minuteman
4
- # Public: Minute TimeSpan class
5
- #
6
- class Minute < TimeSpan
7
- private
8
-
9
- # Private: The format that's going the be used for the date part of the key
10
- #
11
- # date - A given Time object
12
- #
13
- def time_format(date)
14
- full_date = DATE_FORMAT % [date.year, date.month, date.day]
15
- time = TIME_FORMAT % [date.hour, date.min]
16
- [full_date + " " + time]
17
- end
18
- end
19
- end
@@ -1,17 +0,0 @@
1
- # Public: Minuteman core classs
2
- #
3
- class Minuteman
4
- # Public: Month TimeSpan class
5
- #
6
- class Month < TimeSpan
7
- private
8
-
9
- # Private: The format that's going the be used for the date part of the key
10
- #
11
- # date - A given Time object
12
- #
13
- def time_format(date)
14
- [date.year, "%02d" % date.month]
15
- end
16
- end
17
- end
@@ -1,18 +0,0 @@
1
- # Public: Minuteman core classs
2
- #
3
- class Minuteman
4
- # Public: Month TimeSpan class
5
- #
6
- class Week < TimeSpan
7
- private
8
-
9
- # Private: The format that's going the be used for the date part of the key
10
- #
11
- # date - A given Time object
12
- #
13
- def time_format(date)
14
- week = date.strftime("%W")
15
- [date.year, "W" + week]
16
- end
17
- end
18
- end
@@ -1,17 +0,0 @@
1
- # Public: Minuteman core classs
2
- #
3
- class Minuteman
4
- # Public: Year TimeSpan class
5
- #
6
- class Year < TimeSpan
7
- private
8
-
9
- # Private: The format that's going the be used for the date part of the key
10
- #
11
- # date - A given Time object
12
- #
13
- def time_format(date)
14
- [date.year]
15
- end
16
- end
17
- end
@@ -1,37 +0,0 @@
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.track("login", 12)
11
- @analytics.track("login", [2, 42])
12
- @analytics.track("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
@@ -1,9 +0,0 @@
1
- $:.unshift File.dirname(__FILE__) + '/../lib'
2
-
3
- require "bundler/setup"
4
- require "minitest/spec"
5
- require "minitest/pride"
6
- require "minitest/given"
7
- require "minitest/autorun"
8
- require "minuteman"
9
- require "redis-namespace"
@@ -1,225 +0,0 @@
1
- require_relative "../test_helper"
2
-
3
- describe Minuteman do
4
- Given(:analytics) { Minuteman.new }
5
-
6
- after { analytics.reset_all }
7
-
8
- context "configuration" do
9
- Then { analytics.redis }
10
- Then { analytics.options[:cache] == true }
11
-
12
- context "switching options" do
13
- Given(:minuteman) { Minuteman.new }
14
-
15
- When { minuteman.options[:cache] = false }
16
- Then { minuteman.options[:cache] == false }
17
- end
18
-
19
- context "changing time spans" do
20
- Given(:time_spans) { %w[year month day hour] }
21
- Given(:minuteman) { Minuteman.new(time_spans: time_spans) }
22
-
23
- When { minuteman.track("login", 12) }
24
-
25
- Then { minuteman.respond_to?(:year) }
26
- Then { minuteman.respond_to?(:month) }
27
- Then { minuteman.respond_to?(:day) }
28
- Then { minuteman.respond_to?(:hour) }
29
-
30
- Then { !minuteman.respond_to?(:minute) }
31
- Then { !minuteman.respond_to?(:week) }
32
-
33
- Then { minuteman.redis.keys.size == 4 }
34
- Then { minuteman.options[:time_spans] == time_spans }
35
- end
36
-
37
- context "fail silently" do
38
- Given(:minuteman) { Minuteman.new(silent: true, redis: { port: 1234 }) }
39
- When(:result) { minuteman.track("test", 1) }
40
- Then { result == nil }
41
- end
42
-
43
- context "fail loudly" do
44
- Given(:minuteman) { Minuteman.new(redis: { port: 1234 }) }
45
- When(:result) { minuteman.track("test", 1) }
46
- Then { result == Failure(Redis::CannotConnectError) }
47
- end
48
-
49
- context "changing Redis connection" do
50
- Given(:redis) { Redis.new }
51
- Then { Minuteman.redis != redis }
52
-
53
- context "return the correct connection" do
54
- When(:minuteman) { Minuteman.new(redis: redis) }
55
-
56
- Then { minuteman.redis == redis }
57
- end
58
-
59
- context "switching the connection" do
60
- Given(:minuteman) { Minuteman.new }
61
- When { minuteman.redis = redis }
62
- Then { redis == Minuteman.redis }
63
- end
64
-
65
- context "using Redis::Namespace" do
66
- Given(:namespace) { Redis::Namespace.new(:ns, redis: Redis.new) }
67
- Given(:minuteman) { Minuteman.new(redis: namespace) }
68
-
69
- Then { minuteman.redis == namespace }
70
- end
71
- end
72
- end
73
-
74
- context "event tracking" do
75
- Given(:today) { Time.now.utc }
76
- Given(:last_month) { today - (3600 * 24 * 30) }
77
- Given(:last_week) { today - (3600 * 24 * 7) }
78
- Given(:last_minute) { today - 120 }
79
-
80
- Given(:year_events) { analytics.year("login", today) }
81
- Given(:week_events) { analytics.week("login", today) }
82
- Given(:month_events) { analytics.month("login", today) }
83
- Given(:day_events) { analytics.day("login", today) }
84
- Given(:hour_events) { analytics.hour("login", today) }
85
- Given(:minute_events) { analytics.minute("login", today) }
86
- Given(:last_week_events) { analytics.week("login", last_week) }
87
- Given(:last_minute_events) { analytics.minute("login", last_minute) }
88
- Given(:last_month_events) { analytics.month("login:successful", last_month) }
89
-
90
- before do
91
- analytics.track("login", 12)
92
- analytics.track("login", [2, 42])
93
- analytics.track("login", 2, last_week)
94
- analytics.track("login:successful", 567, last_month)
95
- end
96
-
97
- Then { analytics.events.size == 2 }
98
- Then { year_events.length == 3 }
99
- Then { week_events.length == 3 }
100
- Then { last_week_events.length == 1 }
101
- Then { last_month_events.length == 1 }
102
-
103
- context "reseting" do
104
- before { analytics.reset_all }
105
- Then { analytics.events.size == 0 }
106
-
107
- context "bit operations" do
108
- before { week_events & last_week_events }
109
-
110
- When { analytics.reset_operations_cache }
111
- Then { analytics.operations.size == 0 }
112
- end
113
- end
114
-
115
- context "on a given time" do
116
- Then { year_events.length == 3 }
117
- Then { week_events.length == 3 }
118
-
119
- Then { week_events.include?(12, 2, 1) == [true, true, false] }
120
- Then { year_events.include?(12) }
121
- Then { month_events.include?(12) }
122
- Then { day_events.include?(12) }
123
- Then { hour_events.include?(12) }
124
- Then { minute_events.include?(12) }
125
-
126
- Then { last_week_events.include?(2) }
127
- Then { !month_events.include?(5) }
128
- Then { !last_minute_events.include?(12) }
129
- Then { last_month_events.include?(567) }
130
- end
131
-
132
- context "listing events" do
133
- Then { analytics.events.size == 2 }
134
- Then { analytics.events.sort == ["login", "login:successful"] }
135
- end
136
-
137
- context "composing" do
138
- context "using AND" do
139
- Given(:and_operation) { week_events & last_week_events }
140
-
141
- Then { week_events.include?(2) }
142
- Then { week_events.include?(12) }
143
-
144
- Then { last_week_events.include?(2) }
145
- Then { !last_week_events.include?(12) }
146
-
147
- Then { !and_operation.include?(12) }
148
- Then { and_operation.include?(2) }
149
- Then { and_operation.length == 1 }
150
- end
151
-
152
- context "using OR" do
153
- Given(:or_operation) { week_events | last_week_events }
154
-
155
- Then { week_events.include?(2) }
156
- Then { last_week_events.include?(2) }
157
- Then { !last_week_events.include?(12) }
158
-
159
- Then { or_operation.include?(12) }
160
- Then { or_operation.include?(2) }
161
- Then { or_operation.length == 3 }
162
- end
163
-
164
- context "using NOT" do
165
- Given(:not_operation) { ~week_events }
166
-
167
- Then { week_events.include?(2) }
168
- Then { week_events.include?(12) }
169
-
170
- Then { !not_operation.include?(12) }
171
- Then { !not_operation.include?(2) }
172
- end
173
-
174
- context "using OR alias (+)" do
175
- Given(:or_operation) { week_events + last_week_events }
176
-
177
- Then { week_events.include?(2) }
178
- Then { last_week_events.include?(2) }
179
- Then { !last_week_events.include?(12) }
180
-
181
- Then { or_operation.include?(12) }
182
- Then { or_operation.include?(2) }
183
- Then { or_operation.length == 3 }
184
- end
185
-
186
- context "using MINUS" do
187
- Given(:substract_operation) { year_events - week_events }
188
-
189
- Then { week_events.include?(2) }
190
- Then { year_events.include?(2) }
191
- Then { !substract_operation.include?(2) }
192
- end
193
- end
194
-
195
- context "composing multiple operations" do
196
- Given(:multi_operation) { week_events & last_week_events | year_events }
197
- Then { multi_operation.is_a?(Minuteman::BitOperations::Result) }
198
- end
199
-
200
- context "composing against arrays" do
201
- context "using AND returns the intersection" do
202
- Given(:ids) { week_events & [2, 12, 43] }
203
-
204
- Then { ids.is_a?(Minuteman::BitOperations::Data) }
205
- Then { ids == [2, 12] }
206
- end
207
-
208
- context "using MINUS returns the difference" do
209
- Given(:ids) { week_events - [2, 12, 43] }
210
-
211
- Then { ids.is_a?(Minuteman::BitOperations::Data) }
212
- Then { ids == [43] }
213
- end
214
-
215
- context "returns an object that behaves like Array" do
216
- Given(:ids) { week_events & [2, 12, 43] }
217
-
218
- Then { ids.each.is_a?(Enumerator) }
219
- Then { ids.map.is_a?(Enumerator) }
220
- Then { ids.size == 2 }
221
- end
222
-
223
- end
224
- end
225
- end