bit_analytics 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +28 -0
- data/README.md +115 -0
- data/Rakefile +7 -0
- data/bit_analytics.gemspec +25 -0
- data/lib/bit_analytics/version.rb +3 -0
- data/lib/bit_analytics.rb +259 -0
- data/test/bit_analytics_test.rb +142 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: 8978bfb9dd2bf699d69081776c673859b7893687
|
4
|
+
data.tar.gz: 22dce94749ec582804d3fba2de027effa55de4b9
|
5
|
+
!binary "U0hBNTEy":
|
6
|
+
metadata.gz: a24a71ba813bdb1b5daf2177450a91bd7fb544d322cbb7e10dd58e82006751678cf6a74a79781606b034b61703f5672b16584c7cbd39aae612076bdbe1a4f039
|
7
|
+
data.tar.gz: c5fdc49e40652c6eb5529f1cb23106ce3bf1f3cf5f0557d0112157da15aaf7f1cc4da956ab09c829ed63d08692c03ff6e534686db5add1e01e25a738fdbef3df
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Copyright: 2012 by Doist Ltd., Amir Salihefendic
|
2
|
+
Copyright (c) 2014 Jure Triglav.
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without modification,
|
6
|
+
are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
9
|
+
this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright
|
12
|
+
notice, this list of conditions and the following disclaimer in the
|
13
|
+
documentation and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
3. Neither the name of BitAnalytics nor the names of its contributors may be used
|
16
|
+
to endorse or promote products derived from this software without
|
17
|
+
specific prior written permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
20
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
21
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
23
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
24
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
25
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
26
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
28
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# bit_analytics: a powerful analytics library for Redis for Ruby
|
2
|
+
|
3
|
+
This Ruby gem (a port of [@amix's bitmapist library for Python](https://github.com/Doist/bitmapist)) makes it possible to implement real-time, highly scalable analytics that can answer following questions:
|
4
|
+
|
5
|
+
* Has user 123 been online today? This week? This month?
|
6
|
+
* Has user 123 performed action "X"?
|
7
|
+
* How many users have been active have this month? This hour?
|
8
|
+
* How many unique users have performed action "X" this week?
|
9
|
+
* How many % of users that were active last week are still active?
|
10
|
+
* How many % of users that were active last month are still active this month?
|
11
|
+
|
12
|
+
This gem is very easy to use and enables you to create your own reports easily.
|
13
|
+
|
14
|
+
Using Redis bitmaps you can store events for millions of users in a very little amount of memory (megabytes).
|
15
|
+
You should be careful about using huge ids (e.g. 2^32 or bigger) as this could require larger amounts of memory.
|
16
|
+
|
17
|
+
If you want to read more about bitmaps please read following:
|
18
|
+
|
19
|
+
* http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/
|
20
|
+
* http://redis.io/commands/setbit
|
21
|
+
* http://en.wikipedia.org/wiki/Bit_array
|
22
|
+
* http://www.slideshare.net/crashlytics/crashlytics-on-redis-analytics
|
23
|
+
* http://amix.dk/blog/post/19714 [my blog post]
|
24
|
+
|
25
|
+
Requires Redis 2.6+.
|
26
|
+
|
27
|
+
# Installation
|
28
|
+
|
29
|
+
```
|
30
|
+
$ gem install bit_analytics
|
31
|
+
```
|
32
|
+
|
33
|
+
# Ports
|
34
|
+
|
35
|
+
* Original Python library: https://github.com/Doist/bitmapist
|
36
|
+
* PHP port: https://github.com/jeremyFreeAgent/Bitter
|
37
|
+
|
38
|
+
# Examples
|
39
|
+
|
40
|
+
Setting things up:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
require 'date'
|
44
|
+
now = Time.now.getutc
|
45
|
+
last_month = (Date.today << 1).to_time
|
46
|
+
|
47
|
+
# This connects to the default Redis (localhost:6379)
|
48
|
+
@bit_analytics = BitAnalytics.new
|
49
|
+
|
50
|
+
# You can also use custom Redis options
|
51
|
+
@bit_analytics = BitAnalytics.new(host: "10.0.1.1", port: 6380)
|
52
|
+
```
|
53
|
+
|
54
|
+
Mark user 123 as active and has played a song:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
@bit_anaytics.mark_event('active', 123)
|
58
|
+
@bit_anaytics.mark_event('song:played', 123)
|
59
|
+
```
|
60
|
+
|
61
|
+
Answer if user 123 has been active this month:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
@bit_analytics.month_events('active', now.year, now.month).includes?(123)
|
65
|
+
@bit_analytics.month_events('song:played', now.year, now.month).includes?(123)
|
66
|
+
@bit_analytics.month_events('active', now.year, now.month).has_events_marked == true
|
67
|
+
```
|
68
|
+
|
69
|
+
How many users have been active this week?:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
this_week = Time.now.strftime('%V')
|
73
|
+
@bit_analytics.week_events('active', now.year, this_week)
|
74
|
+
```
|
75
|
+
|
76
|
+
Perform bit operations. How many users that have been active last month are still active this month?
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
active_2_months = @bit_analytics.bit_op_and(
|
80
|
+
@bit_analytics.month_events('active', last_month.year, last_month.month),
|
81
|
+
@bit_analytics.month_events('active', now.year, now.month)
|
82
|
+
)
|
83
|
+
puts active_2_months.length
|
84
|
+
|
85
|
+
# Is 123 active for 2 months?
|
86
|
+
active_2_months.includes?(123)
|
87
|
+
```
|
88
|
+
|
89
|
+
Work with nested bit operations (imagine what you can do with this ;))!
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
active_2_months = @bit_analytics.bit_op_and(
|
93
|
+
@bit_analytics.bit_op_and(
|
94
|
+
@bit_analytics.month_events('active', last_month.year, last_month.month),
|
95
|
+
@bit_analytics.month_events('active', now.year, now.month)
|
96
|
+
),
|
97
|
+
@bit_analytics.month_events('active', now.year, now.month)
|
98
|
+
)
|
99
|
+
puts active_2_months.length
|
100
|
+
active_2_months.includes?(123)
|
101
|
+
|
102
|
+
# Delete the temporary AND operation
|
103
|
+
active_2_months.delete
|
104
|
+
```
|
105
|
+
|
106
|
+
Tracking hourly is disabled by default to save memory, but you can supply an extra argument to `mark_event` to track events hourly:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
@bit_analytics.mark_event('active', 123, track_hourly: true)
|
110
|
+
```
|
111
|
+
|
112
|
+
Original library: Copyright: 2012 by Doist Ltd. Developer: Amir Salihefendic ( http://amix.dk ) License: BSD
|
113
|
+
|
114
|
+
Ruby port: Copyright 2014 Jure Triglav License: BSD
|
115
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bit_analytics/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "bit_analytics"
|
8
|
+
spec.version = BitAnalytics::VERSION
|
9
|
+
spec.authors = ["Jure Triglav"]
|
10
|
+
spec.email = ["juretriglav@gmail.com"]
|
11
|
+
spec.description = %q{Analytics library built with Redis bitmaps}
|
12
|
+
spec.summary = %q{Implements a powerful analytics library on top of Redis's support for bitmaps and bitmap operations.}
|
13
|
+
spec.homepage = "http://www.github.com/jure/bit_analytics"
|
14
|
+
spec.license = "BSD"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "redis", "~> 3.0"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "pry"
|
25
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require "bit_analytics/version"
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
class BitAnalytics
|
5
|
+
attr_accessor :redis
|
6
|
+
|
7
|
+
def initialize(options=nil)
|
8
|
+
@redis = if options
|
9
|
+
Redis.new(options)
|
10
|
+
else
|
11
|
+
Redis.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
#--- Events marking and deleting ---
|
16
|
+
# Marks an event for hours, days, weeks and months.
|
17
|
+
# :param :event_name The name of the event, could be "active" or "new_signups"
|
18
|
+
# :param :uuid An unique id, typically user id. The id should not be huge, read Redis documentation why (bitmaps)
|
19
|
+
# :param :now Which date should be used as a reference point, default is `datetime.utcnow`
|
20
|
+
# :param :track_hourly Should hourly stats be tracked, defaults to bitanalytics.TRACK_HOURLY, but an be changed
|
21
|
+
# Examples:
|
22
|
+
# Mark id 1 as active
|
23
|
+
# mark_event('active', 1)
|
24
|
+
# Mark task completed for id 252
|
25
|
+
# mark_event('tasks:completed', 252)
|
26
|
+
def mark_event(event_name, uuid, now: nil, track_hourly: nil)
|
27
|
+
# Has memory applications
|
28
|
+
track_hourly ||= false
|
29
|
+
now ||= Time.now.getutc
|
30
|
+
# E.g. ['2014', '03'] for 17th of January 2014
|
31
|
+
iso_date = now.strftime('%Y-%V').split('-')
|
32
|
+
|
33
|
+
events = [
|
34
|
+
MonthEvents.new(event_name, now.year, now.month),
|
35
|
+
WeekEvents.new(event_name, iso_date[0], iso_date[1]),
|
36
|
+
DayEvents.new(event_name, now.year, now.month, now.day),
|
37
|
+
]
|
38
|
+
|
39
|
+
if track_hourly
|
40
|
+
events << HourEvents.new(event_name, now.year, now.month, now.day, now.hour)
|
41
|
+
end
|
42
|
+
|
43
|
+
@redis.pipelined do
|
44
|
+
events.each do |event|
|
45
|
+
@redis.setbit(event.redis_key, uuid, 1)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Delete all events from the database.
|
51
|
+
def delete_all_events
|
52
|
+
keys = @redis.keys('bitanalytics_*')
|
53
|
+
@redis.del(*keys) unless keys.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Delete all temporary keys that are used when using bit operations.
|
57
|
+
def delete_temporary_bitop_keys
|
58
|
+
keys = @redis.keys('bitanalytics_bitop_*')
|
59
|
+
@redis.del(keys) unless keys.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
#--- Events ---
|
63
|
+
|
64
|
+
def month_events(event_name, year, month)
|
65
|
+
month_events = MonthEvents.new(event_name, year, month)
|
66
|
+
month_events.redis = @redis
|
67
|
+
month_events
|
68
|
+
end
|
69
|
+
|
70
|
+
def week_events(event_name, year, week)
|
71
|
+
week_events = WeekEvents.new(event_name, year, week)
|
72
|
+
week_events.redis = @redis
|
73
|
+
week_events
|
74
|
+
end
|
75
|
+
|
76
|
+
def day_events(event_name, year, month, day)
|
77
|
+
day_events = DayEvents.new(event_name, year, month, day)
|
78
|
+
day_events.redis = @redis
|
79
|
+
day_events
|
80
|
+
end
|
81
|
+
|
82
|
+
def hour_events(event_name, year, month, day, hour)
|
83
|
+
hour_events = HourEvents.new(event_name, year, month, day, hour)
|
84
|
+
hour_events.redis = @redis
|
85
|
+
hour_events
|
86
|
+
end
|
87
|
+
|
88
|
+
#--- BitOps ---
|
89
|
+
|
90
|
+
def bit_op_and(event, *events)
|
91
|
+
bit_operation = BitOperation.new('AND', event, *events)
|
92
|
+
bit_operation.redis = @redis
|
93
|
+
bit_operation.execute
|
94
|
+
bit_operation
|
95
|
+
end
|
96
|
+
|
97
|
+
def bit_op_or(event, *events)
|
98
|
+
bit_operation = BitOperation.new('OR', event, *events)
|
99
|
+
bit_operation.redis = @redis
|
100
|
+
bit_operation.execute
|
101
|
+
bit_operation
|
102
|
+
end
|
103
|
+
|
104
|
+
def bit_op_xor(event, *events)
|
105
|
+
bit_operation = BitOperation.new('XOR', event, *events)
|
106
|
+
bit_operation.redis = @redis
|
107
|
+
bit_operation.execute
|
108
|
+
bit_operation
|
109
|
+
end
|
110
|
+
|
111
|
+
#--- Private ---
|
112
|
+
|
113
|
+
def _prefix_key(event_name, date)
|
114
|
+
return 'bitanalytics_%s_%s' % [event_name, date]
|
115
|
+
end
|
116
|
+
|
117
|
+
#--- Events ---
|
118
|
+
|
119
|
+
# Extends with an obj.has_events_marked()
|
120
|
+
# that returns `True` if there are any events marked,
|
121
|
+
# otherwise `False` is returned.
|
122
|
+
|
123
|
+
# Extens also with a obj.delete()
|
124
|
+
# (useful for deleting temporary calculations).
|
125
|
+
|
126
|
+
module MixinEventsMisc
|
127
|
+
def has_events_marked
|
128
|
+
@redis.get(@redis_key) != nil
|
129
|
+
end
|
130
|
+
def delete
|
131
|
+
@redis.del(@redis_key)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Extends with an obj.get_count() that uses BITCOUNT to
|
136
|
+
# count all the events. Supports also __len__
|
137
|
+
|
138
|
+
module MixinCounts
|
139
|
+
def get_count
|
140
|
+
@redis.bitcount(@redis_key)
|
141
|
+
end
|
142
|
+
|
143
|
+
def length
|
144
|
+
return get_count
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Makes it possible to see if an uuid has been marked.
|
149
|
+
# Example:
|
150
|
+
# user_active_today = 123 in DayEvents('active', 2012, 10, 23)
|
151
|
+
module MixinContains
|
152
|
+
def includes?(uuid)
|
153
|
+
if @redis.getbit(self.redis_key, uuid) == 1
|
154
|
+
true
|
155
|
+
else
|
156
|
+
false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
module RedisConnection
|
162
|
+
attr_accessor :redis
|
163
|
+
attr_reader :redis_key
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
# Events for a month.
|
169
|
+
# Example:
|
170
|
+
# MonthEvents('active', 2012, 10)
|
171
|
+
class MonthEvents < BitAnalytics
|
172
|
+
include RedisConnection
|
173
|
+
include MixinCounts
|
174
|
+
include MixinContains
|
175
|
+
include MixinEventsMisc
|
176
|
+
|
177
|
+
attr_reader :redis_key
|
178
|
+
|
179
|
+
def initialize(event_name, year, month)
|
180
|
+
@redis_key = _prefix_key(event_name,'%s-%s' % [year, month])
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Events for a week.
|
185
|
+
# Example:
|
186
|
+
# WeekEvents('active', 2012, 48)
|
187
|
+
class WeekEvents < BitAnalytics
|
188
|
+
include RedisConnection
|
189
|
+
include MixinCounts
|
190
|
+
include MixinContains
|
191
|
+
include MixinEventsMisc
|
192
|
+
|
193
|
+
def initialize(event_name, year, week)
|
194
|
+
@redis_key = _prefix_key(event_name,'W%s-%s' % [year, week])
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Events for a day.
|
199
|
+
# Example:
|
200
|
+
# DayEvents('active', 2012, 10, 23)
|
201
|
+
class DayEvents < BitAnalytics
|
202
|
+
include RedisConnection
|
203
|
+
include MixinCounts
|
204
|
+
include MixinContains
|
205
|
+
include MixinEventsMisc
|
206
|
+
|
207
|
+
def initialize(event_name, year, month, day)
|
208
|
+
@redis_key = _prefix_key(event_name,'%s-%s-%s' % [year, month, day])
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Events for a hour.
|
213
|
+
# Example:
|
214
|
+
# HourEvents('active', 2012, 10, 23, 13)
|
215
|
+
class HourEvents < BitAnalytics
|
216
|
+
include RedisConnection
|
217
|
+
include MixinCounts
|
218
|
+
include MixinContains
|
219
|
+
include MixinEventsMisc
|
220
|
+
|
221
|
+
def initialize(event_name, year, month, day, hour)
|
222
|
+
@redis_key = _prefix_key(event_name,'%s-%s-%s-%s' % [year, month, day, hour])
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
#--- Bit operations ---
|
227
|
+
# Base class for bit operations (AND, OR, XOR).
|
228
|
+
# Please note that each bit operation creates a new key prefixed with `bitanalytics_bitop_`.
|
229
|
+
# These temporary keys can be deleted with `delete_temporary_bitop_keys`.
|
230
|
+
|
231
|
+
# You can even nest bit operations.
|
232
|
+
# Example:
|
233
|
+
# active_2_months = BitOpAnd(
|
234
|
+
# MonthEvents('active', last_month.year, last_month.month),
|
235
|
+
# MonthEvents('active', now.year, now.month)
|
236
|
+
# )
|
237
|
+
# active_2_months = BitOpAnd(
|
238
|
+
# BitOpAnd(
|
239
|
+
# MonthEvents('active', last_month.year, last_month.month),
|
240
|
+
# MonthEvents('active', now.year, now.month)
|
241
|
+
# ),
|
242
|
+
# MonthEvents('active', now.year, now.month)
|
243
|
+
# )
|
244
|
+
class BitOperation < BitAnalytics
|
245
|
+
include MixinContains
|
246
|
+
include MixinCounts
|
247
|
+
include MixinEventsMisc
|
248
|
+
include RedisConnection
|
249
|
+
|
250
|
+
def initialize(op_name, *events)
|
251
|
+
@op_name = op_name
|
252
|
+
@event_redis_keys = events.map(&:redis_key)
|
253
|
+
@redis_key = 'bitanalytics_bitop_%s_%s' % [@op_name, @event_redis_keys.join('-')]
|
254
|
+
end
|
255
|
+
|
256
|
+
def execute
|
257
|
+
@redis.bitop(@op_name, @redis_key, @event_redis_keys)
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'bit_analytics'
|
4
|
+
|
5
|
+
class BitAnalyticsTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@bit_analytics = BitAnalytics.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_mark_with_diff_days
|
11
|
+
@bit_analytics.delete_all_events
|
12
|
+
@bit_analytics.mark_event('active', 123, track_hourly: true)
|
13
|
+
|
14
|
+
now = Time.now.getutc
|
15
|
+
|
16
|
+
# E.g. ['2014', '03'] for 17th of January 2014
|
17
|
+
iso_date = now.strftime('%Y-%V').split('-')
|
18
|
+
|
19
|
+
# Month
|
20
|
+
assert @bit_analytics.month_events('active', now.year, now.month).includes?(123)
|
21
|
+
assert !@bit_analytics.month_events('active', now.year, now.month).includes?(124)
|
22
|
+
|
23
|
+
# Week
|
24
|
+
assert @bit_analytics.week_events('active', now.year, iso_date[1]).includes?(123)
|
25
|
+
assert !@bit_analytics.week_events('active', now.year, iso_date[1]).includes?(124)
|
26
|
+
|
27
|
+
# Day
|
28
|
+
assert @bit_analytics.day_events('active', now.year, now.month, now.day).includes?(123)
|
29
|
+
assert !@bit_analytics.day_events('active', now.year, now.month, now.day).includes?(124)
|
30
|
+
|
31
|
+
# Hour
|
32
|
+
assert @bit_analytics.hour_events('active', now.year, now.month, now.day, now.hour).includes?(123)
|
33
|
+
assert !@bit_analytics.hour_events('active', now.year, now.month, now.day, now.hour).includes?(124)
|
34
|
+
assert !@bit_analytics.hour_events('active', now.year, now.month, now.day, now.hour-1).includes?(124)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_mark_counts
|
38
|
+
@bit_analytics.delete_all_events
|
39
|
+
|
40
|
+
now = Time.now.getutc
|
41
|
+
assert @bit_analytics.month_events('active', now.year, now.month).get_count == 0
|
42
|
+
|
43
|
+
@bit_analytics.mark_event('active', 123)
|
44
|
+
@bit_analytics.mark_event('active', 23232)
|
45
|
+
|
46
|
+
assert @bit_analytics.month_events('active', now.year, now.month).length == 2
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_different_dates
|
50
|
+
@bit_analytics.delete_all_events
|
51
|
+
|
52
|
+
now = Time.now.getutc
|
53
|
+
yesterday = (Date.today - 1).to_time
|
54
|
+
|
55
|
+
@bit_analytics.mark_event('active', 123, now: now)
|
56
|
+
@bit_analytics.mark_event('active', 23232, now: yesterday)
|
57
|
+
|
58
|
+
assert @bit_analytics.day_events('active',
|
59
|
+
now.year,
|
60
|
+
now.month,
|
61
|
+
now.day).get_count == 1
|
62
|
+
|
63
|
+
assert @bit_analytics.day_events('active',
|
64
|
+
yesterday.year,
|
65
|
+
yesterday.month,
|
66
|
+
yesterday.day).get_count == 1
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_different_buckets
|
70
|
+
@bit_analytics.delete_all_events
|
71
|
+
|
72
|
+
now = Time.now.getutc
|
73
|
+
|
74
|
+
@bit_analytics.mark_event('active', 123)
|
75
|
+
@bit_analytics.mark_event('tasks:completed', 23232)
|
76
|
+
|
77
|
+
assert @bit_analytics.month_events('active', now.year, now.month).get_count == 1
|
78
|
+
assert @bit_analytics.month_events('tasks:completed', now.year, now.month).get_count == 1
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_bit_operations
|
82
|
+
@bit_analytics.delete_all_events
|
83
|
+
|
84
|
+
now = Time.now.getutc
|
85
|
+
last_month = (Date.today << 1).to_time
|
86
|
+
|
87
|
+
# 123 has been active for two months
|
88
|
+
@bit_analytics.mark_event('active', 123, now: now)
|
89
|
+
@bit_analytics.mark_event('active', 123, now: last_month)
|
90
|
+
|
91
|
+
# 224 has only been active last_month
|
92
|
+
@bit_analytics.mark_event('active', 224, now: last_month)
|
93
|
+
|
94
|
+
# Assert basic premises
|
95
|
+
assert @bit_analytics.month_events('active', last_month.year, last_month.month).get_count == 2
|
96
|
+
assert @bit_analytics.month_events('active', now.year, now.month).get_count == 1
|
97
|
+
|
98
|
+
# Try out with bit AND operation
|
99
|
+
active_2_months = @bit_analytics.bit_op_and(
|
100
|
+
@bit_analytics.month_events('active', last_month.year, last_month.month),
|
101
|
+
@bit_analytics.month_events('active', now.year, now.month)
|
102
|
+
)
|
103
|
+
assert active_2_months.get_count == 1
|
104
|
+
assert active_2_months.includes?(123)
|
105
|
+
assert !(active_2_months).includes?(224)
|
106
|
+
active_2_months.delete
|
107
|
+
|
108
|
+
# Try out with bit OR operation
|
109
|
+
assert @bit_analytics.bit_op_or(
|
110
|
+
@bit_analytics.month_events('active', last_month.year, last_month.month),
|
111
|
+
@bit_analytics.month_events('active', now.year, now.month)
|
112
|
+
).get_count == 2
|
113
|
+
|
114
|
+
# Try nested operations
|
115
|
+
active_2_months = @bit_analytics.bit_op_and(
|
116
|
+
@bit_analytics.bit_op_and(
|
117
|
+
@bit_analytics.month_events('active', last_month.year, last_month.month),
|
118
|
+
@bit_analytics.month_events('active', now.year, now.month)
|
119
|
+
),
|
120
|
+
@bit_analytics.month_events('active', now.year, now.month)
|
121
|
+
)
|
122
|
+
|
123
|
+
assert active_2_months.includes?(123)
|
124
|
+
assert !active_2_months.includes?(224)
|
125
|
+
active_2_months.delete
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_events_marked
|
129
|
+
@bit_analytics.delete_all_events
|
130
|
+
|
131
|
+
now = Time.now.getutc
|
132
|
+
|
133
|
+
assert @bit_analytics.month_events('active', now.year, now.month).get_count == 0
|
134
|
+
assert @bit_analytics.month_events('active', now.year, now.month).has_events_marked == false
|
135
|
+
|
136
|
+
@bit_analytics.mark_event('active', 123, now: now)
|
137
|
+
|
138
|
+
assert @bit_analytics.month_events('active', now.year, now.month).get_count == 1
|
139
|
+
assert @bit_analytics.month_events('active', now.year, now.month).has_events_marked == true
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bit_analytics
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jure Triglav
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Analytics library built with Redis bitmaps
|
70
|
+
email:
|
71
|
+
- juretriglav@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- bit_analytics.gemspec
|
82
|
+
- lib/bit_analytics.rb
|
83
|
+
- lib/bit_analytics/version.rb
|
84
|
+
- test/bit_analytics_test.rb
|
85
|
+
homepage: http://www.github.com/jure/bit_analytics
|
86
|
+
licenses:
|
87
|
+
- BSD
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.0.14
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Implements a powerful analytics library on top of Redis's support for bitmaps
|
109
|
+
and bitmap operations.
|
110
|
+
test_files:
|
111
|
+
- test/bit_analytics_test.rb
|