minuteman 1.0.3 → 2.0.0.pre

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.
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