redlics 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +24 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +64 -0
- data/Rakefile +9 -0
- data/lib/redlics.rb +94 -0
- data/lib/redlics/config.rb +62 -0
- data/lib/redlics/connection.rb +79 -0
- data/lib/redlics/counter.rb +94 -0
- data/lib/redlics/exception.rb +30 -0
- data/lib/redlics/granularity.rb +49 -0
- data/lib/redlics/key.rb +244 -0
- data/lib/redlics/lua/script.lua +82 -0
- data/lib/redlics/operators.rb +51 -0
- data/lib/redlics/query.rb +234 -0
- data/lib/redlics/query/operation.rb +135 -0
- data/lib/redlics/time_frame.rb +110 -0
- data/lib/redlics/tracker.rb +64 -0
- data/lib/redlics/version.rb +4 -0
- data/redlics.gemspec +33 -0
- data/test/redlics_test.rb +13 -0
- metadata +167 -0
@@ -0,0 +1,234 @@
|
|
1
|
+
module Redlics
|
2
|
+
|
3
|
+
# Query class
|
4
|
+
class Query
|
5
|
+
|
6
|
+
|
7
|
+
# Include Redlics operators.
|
8
|
+
include Redlics::Operators
|
9
|
+
|
10
|
+
|
11
|
+
# Gives read access to the listed instance variables.
|
12
|
+
attr_reader :namespaces
|
13
|
+
|
14
|
+
|
15
|
+
# Initialization of a query object
|
16
|
+
#
|
17
|
+
# @param event [String] event name with eventual Redis namespace separator
|
18
|
+
# @param time_object [Symbol] time object predefined in Redlics::TimeFrame.init_with_symbol
|
19
|
+
# @param time_object [Hash] time object with keys `from` and `to`
|
20
|
+
# @param time_object [Range] time object as range
|
21
|
+
# @param time_object [Time] time object
|
22
|
+
# @param options [Hash] configuration options
|
23
|
+
# @return [Redlics::Query] query object
|
24
|
+
def initialize(event, time_object, options = {})
|
25
|
+
@event = event.freeze
|
26
|
+
@time_object = time_object.freeze
|
27
|
+
@options = options
|
28
|
+
@namespaces = []
|
29
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(namespaces)) if Redlics.config.auto_clean
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Get or process counts on Redis.
|
34
|
+
# @return [Integer] count result of given query
|
35
|
+
def counts
|
36
|
+
@counts ||= (
|
37
|
+
result = Redlics.script(Redlics::LUA_SCRIPT, [], ['counts'.to_msgpack, realize_counts!.to_msgpack,
|
38
|
+
{ bucketized: Redlics.config.bucket }.to_msgpack])
|
39
|
+
result.is_a?(Array) ? result.map(&:to_i).reduce(0, :+) : result.to_i
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Get or process tracks on Redis.
|
45
|
+
# @return [Integer] tracks result of given query
|
46
|
+
def tracks
|
47
|
+
@tracks ||= Redlics.redis.bitcount(track_bits)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# Get or process track bits on Redis.
|
52
|
+
# @return [String] key of track bits result
|
53
|
+
def track_bits
|
54
|
+
@track_bits ||= (
|
55
|
+
@track_bits_namespace = Key.unique_namespace
|
56
|
+
@namespaces << @track_bits_namespace
|
57
|
+
Redlics.script(Redlics::LUA_SCRIPT, [], ['operation'.to_msgpack, realize_tracks!.to_msgpack,
|
58
|
+
{ operator: 'OR', dest: Key.with_namespace(@track_bits_namespace) }.to_msgpack])
|
59
|
+
@track_bits_namespace
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Check if object id exists in track bits.
|
65
|
+
# @return [Boolean] true if exists, false if not
|
66
|
+
# @return [NilClass] nil if no object id is given
|
67
|
+
def exists?
|
68
|
+
@exists ||= @options[:id] ? Redlics.redis.getbit(track_bits, @options[:id]) == 1 : nil
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Get or process counts and plot.
|
73
|
+
#
|
74
|
+
# @return [Hash] with date times and counts
|
75
|
+
# @return [NilClass] nil if result has errors
|
76
|
+
def plot_counts
|
77
|
+
@plot_counts ||= (
|
78
|
+
result = JSON.parse(
|
79
|
+
Redlics.script(Redlics::LUA_SCRIPT, [], ['plot_counts'.to_msgpack, realize_counts!.to_msgpack,
|
80
|
+
{ bucketized: Redlics.config.bucket }.to_msgpack])
|
81
|
+
)
|
82
|
+
format_plot(Redlics::CONTEXTS[:counter], result)
|
83
|
+
)
|
84
|
+
rescue JSON::ParserError
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Get or process tracks and plot.
|
90
|
+
#
|
91
|
+
# @return [Hash] with date times and counts
|
92
|
+
# @return [NilClass] nil if result has errors
|
93
|
+
def plot_tracks
|
94
|
+
@plot_tracks ||= (
|
95
|
+
result = JSON.parse(
|
96
|
+
Redlics.script(Redlics::LUA_SCRIPT, [], ['plot_tracks'.to_msgpack, realize_tracks!.to_msgpack,
|
97
|
+
{}.to_msgpack])
|
98
|
+
)
|
99
|
+
format_plot(Redlics::CONTEXTS[:tracker], result)
|
100
|
+
)
|
101
|
+
rescue JSON::ParserError
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# Get or process counts and show keys to analyze.
|
107
|
+
# @return [Array] list of keys to analyze
|
108
|
+
def realize_counts!
|
109
|
+
@realize_counts ||= (
|
110
|
+
keys = Key.timeframed(Redlics::CONTEXTS[:counter], @event, @time_object, @options)
|
111
|
+
raise Exception::LuaRangeError if keys.length > 8000
|
112
|
+
keys
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
# Get or process tracks and show keys to analyze.
|
118
|
+
# @return [Array] list of keys to analyze
|
119
|
+
def realize_tracks!
|
120
|
+
@realize_tracks ||= (
|
121
|
+
keys = Key.timeframed(Redlics::CONTEXTS[:tracker], @event, @time_object, @options)
|
122
|
+
raise Exception::LuaRangeError if keys.length > 8000
|
123
|
+
keys
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# Reset processed data (also operation keys on Redis).
|
129
|
+
#
|
130
|
+
# @param space [Symbol] define space to reset
|
131
|
+
# @param space [String] define space to reset
|
132
|
+
# @return [Boolean] true
|
133
|
+
def reset!(space = nil)
|
134
|
+
space = space.to_sym if space
|
135
|
+
case space
|
136
|
+
when :counts, :plot_counts, :plot_tracks, :realize_counts, :realize_tracks
|
137
|
+
instance_variable_set("@#{space}", nil)
|
138
|
+
when :tracks, :exists
|
139
|
+
instance_variable_set("@#{space}", nil)
|
140
|
+
reset_track_bits
|
141
|
+
when :counter
|
142
|
+
@counts, @plot_counts, @realize_counts = [nil] * 3
|
143
|
+
when :tracker
|
144
|
+
@tracks, @exists, @plot_tracks, @realize_tracks = [nil] * 4
|
145
|
+
reset_track_bits
|
146
|
+
else
|
147
|
+
@counts, @tracks, @exists, @plot_counts, @plot_tracks, @realize_counts, @realize_tracks = [nil] * 7
|
148
|
+
reset_track_bits
|
149
|
+
self.class.reset_redis_namespaces(@namespaces)
|
150
|
+
@namespaces = []
|
151
|
+
end
|
152
|
+
return true
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
# Check if query is a leaf. A query is always a leaf.
|
157
|
+
# This method is required for query operations.
|
158
|
+
# @return [Boolean] true
|
159
|
+
def is_leaf?
|
160
|
+
true
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# Singleton class
|
165
|
+
class << self
|
166
|
+
|
167
|
+
# Short query access to analyze data.
|
168
|
+
#
|
169
|
+
# @param *args [Array] list of arguments of the query
|
170
|
+
# @return [Redlics::Query] instantiated query object
|
171
|
+
def analyze(*args)
|
172
|
+
options = args.last.instance_of?(Hash) ? args.pop : {}
|
173
|
+
query = case args.size
|
174
|
+
when 2
|
175
|
+
Query.new(args[0], args[1], options)
|
176
|
+
when 3
|
177
|
+
Query.new(args[0], args[1], options.merge!({ id: args[2].to_i }))
|
178
|
+
end
|
179
|
+
return yield query if block_given?
|
180
|
+
query
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
# Finalize query called from garbage collector.
|
185
|
+
#
|
186
|
+
# @param namespaces [Array] list of created operation keys in Redis
|
187
|
+
# @return [Integer] result of Redis delete keys
|
188
|
+
# @return [NilClass] nil if namespaces are empty
|
189
|
+
def finalize(namespaces)
|
190
|
+
proc { reset_redis_namespaces(namespaces); puts 'Redlics clean!' }
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
# Reset Redis created namespace keys.
|
195
|
+
#
|
196
|
+
# @param namespaces [Array] list of created operation keys in Redis
|
197
|
+
# @return [Integer] result of Redis delete keys
|
198
|
+
# @return [NilClass] nil if namespaces are empty
|
199
|
+
def reset_redis_namespaces(namespaces)
|
200
|
+
Redlics.redis.del(namespaces) if namespaces.any?
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
# Format plot result with time objects as keys.
|
209
|
+
#
|
210
|
+
# @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
|
211
|
+
# @param result [Hash] the result hash with Redis keys as hash keys
|
212
|
+
# @return [Hash] the formatted result hash
|
213
|
+
def format_plot(context, result)
|
214
|
+
granularity = Granularity.validate(context, @options[:granularity]).first
|
215
|
+
pattern = Redlics.config.granularities[granularity][:pattern]
|
216
|
+
el = Key.bucketize?(context, @options) ? -2 : -1
|
217
|
+
result.keys.each { |k|
|
218
|
+
result[Time.strptime(k.split(Redlics.config.separator)[el], pattern)] = result.delete(k)
|
219
|
+
}
|
220
|
+
result
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
# Reset track bits (also operation key on Redis).
|
225
|
+
# @return [NilClass] nil
|
226
|
+
def reset_track_bits
|
227
|
+
self.class.reset_redis_namespaces([@track_bits_namespace])
|
228
|
+
@namespaces.delete(@track_bits_namespace)
|
229
|
+
@track_bits, @track_bits_namespace = nil, nil
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Redlics
|
2
|
+
class Query
|
3
|
+
|
4
|
+
# Operation class
|
5
|
+
class Operation
|
6
|
+
|
7
|
+
# Include Redlics operators.
|
8
|
+
include Redlics::Operators
|
9
|
+
|
10
|
+
|
11
|
+
# Gives read access to the listed instance variables.
|
12
|
+
attr_reader :namespaces
|
13
|
+
|
14
|
+
|
15
|
+
# Initialization of a query operation object.
|
16
|
+
#
|
17
|
+
# @param operator [String] operator to calculate
|
18
|
+
# @param queries [Array] queries to calculate with the given operator
|
19
|
+
# @return [Redlics::Query::Operation] query operation object
|
20
|
+
def initialize(operator, queries)
|
21
|
+
@operator = operator.upcase.freeze
|
22
|
+
@queries = queries.freeze
|
23
|
+
@track_bits = nil
|
24
|
+
@namespaces = []
|
25
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(namespaces)) if Redlics.config.auto_clean
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# Get or process tracks on Redis.
|
30
|
+
# @return [Integer] tracks result of given query operation
|
31
|
+
def tracks
|
32
|
+
@tracks ||= (
|
33
|
+
Redlics.redis.bitcount(@track_bits || traverse)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Get or process track bits on Redis.
|
39
|
+
# @return [String] key of track bits result
|
40
|
+
def track_bits
|
41
|
+
@track_bits ||= (
|
42
|
+
keys = []
|
43
|
+
track_bits_namespace = Key.unique_namespace
|
44
|
+
@namespaces << track_bits_namespace
|
45
|
+
if @operator == 'NOT'
|
46
|
+
keys << Key.with_namespace(@queries[0].track_bits)
|
47
|
+
else
|
48
|
+
@queries.each { |q| keys << Key.with_namespace(q.track_bits) }
|
49
|
+
end
|
50
|
+
Redlics.script(Redlics::LUA_SCRIPT, [], ['operation'.to_msgpack, keys.to_msgpack,
|
51
|
+
{ operator: @operator, dest: Key.with_namespace(track_bits_namespace) }.to_msgpack])
|
52
|
+
track_bits_namespace
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# Check if object id exists in track bits.
|
58
|
+
#
|
59
|
+
# @param [Integer] the object id to check
|
60
|
+
# @return [Boolean] true if exists, false if not
|
61
|
+
def exists?(id)
|
62
|
+
Redlics.redis.getbit(@track_bits || traverse, id.to_i) == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Reset processed data (also operation keys on Redis).
|
67
|
+
#
|
68
|
+
# @param space [Symbol] define space to reset
|
69
|
+
# @param space [String] define space to reset
|
70
|
+
# @return [Boolean] true
|
71
|
+
def reset!(space = nil)
|
72
|
+
space = space.to_sym if space
|
73
|
+
case space
|
74
|
+
when :tree
|
75
|
+
@queries.each { |q| q.reset!(:tree) }
|
76
|
+
reset!
|
77
|
+
else
|
78
|
+
@tracks, @track_bits = nil, nil
|
79
|
+
self.class.reset_redis_namespaces(@namespaces)
|
80
|
+
@namespaces = []
|
81
|
+
end
|
82
|
+
return true
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Check if query operation is a leaf in the binary tree.
|
87
|
+
# @return [Boolean] true if a leaf, false if not
|
88
|
+
def is_leaf?
|
89
|
+
is_a?(Redlics::Query::Operation) && @track_bits.nil?
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Singleton class
|
94
|
+
class << self
|
95
|
+
|
96
|
+
# Finalize query operation called from garbage collector.
|
97
|
+
#
|
98
|
+
# @param namespaces [Array] list of created operation keys in Redis
|
99
|
+
# @return [Integer] result of Redis delete keys
|
100
|
+
# @return [NilClass] nil if namespaces are empty
|
101
|
+
def finalize(namespaces)
|
102
|
+
proc { reset_redis_namespaces(namespaces) }
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# Reset Redis created namespace keys.
|
107
|
+
#
|
108
|
+
# @param namespaces [Array] list of created operation keys in Redis
|
109
|
+
# @return [Integer] result of Redis delete keys
|
110
|
+
# @return [NilClass] nil if namespaces are empty
|
111
|
+
def reset_redis_namespaces(namespaces)
|
112
|
+
Redlics.redis.del(namespaces) if namespaces.any?
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
|
121
|
+
# Traverse query operation binary tree and calculate operation leafs.
|
122
|
+
# @return [String] result operation key in Redis
|
123
|
+
def traverse
|
124
|
+
if @operator == 'NOT'
|
125
|
+
@queries[0].traverse unless @queries[0].is_leaf?
|
126
|
+
track_bits
|
127
|
+
else
|
128
|
+
@queries.each { |q| q.traverse unless q.is_leaf? }
|
129
|
+
track_bits
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Redlics
|
2
|
+
|
3
|
+
# Time Frame class
|
4
|
+
class TimeFrame
|
5
|
+
|
6
|
+
# Gives read access to the listed instance variables.
|
7
|
+
attr_reader :from, :to, :granularity
|
8
|
+
|
9
|
+
|
10
|
+
# Initialization of a time frame object.
|
11
|
+
#
|
12
|
+
# @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
|
13
|
+
# @param time_object [Symbol] time object predefined in Redlics::TimeFrame.init_with_symbol
|
14
|
+
# @param time_object [Hash] time object with keys `from` and `to`
|
15
|
+
# @param time_object [Range] time object as range
|
16
|
+
# @param time_object [Time] time object
|
17
|
+
# @param options [Hash] configuration options
|
18
|
+
# @return [Redlics::TimeFrame] time frame object
|
19
|
+
def initialize(context, time_object, options = {})
|
20
|
+
raise ArgumentError, 'TimeFrame should be initialized with Symbol, Hash, Range or Time' unless [Symbol, Hash, Range, Time].include?(time_object.class)
|
21
|
+
@from, @to = self.send("init_with_#{time_object.class.name.demodulize.underscore}", time_object, context)
|
22
|
+
@granularity = Granularity.validate(context, options[:granularity]).first
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Construct keys by time frame steps.
|
27
|
+
# @return [Array] keys
|
28
|
+
def splat
|
29
|
+
[].tap do |keys|
|
30
|
+
(from.to_i .. to.to_i).step(Redlics.config.granularities[@granularity][:step]) do |t|
|
31
|
+
keys << (block_given? ? (yield Time.at(t)) : Time.at(t))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Initialize time frames `from` and `to` by time.
|
40
|
+
#
|
41
|
+
# @param time [Time] a time
|
42
|
+
# @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
|
43
|
+
# @return [Array] with `from` and `to` time
|
44
|
+
def init_with_time(time, context)
|
45
|
+
[time.beginning_of_day, time.end_of_day]
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Initialize time frames `from` and `to` by symbol.
|
50
|
+
#
|
51
|
+
# @param symbol [Symbol] a time span
|
52
|
+
# @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
|
53
|
+
# @return [Array] with `from` and `to` time
|
54
|
+
def init_with_symbol(symbol, context)
|
55
|
+
case symbol
|
56
|
+
when :hour, :day, :week, :month, :year
|
57
|
+
return 1.send(symbol).ago, Time.now
|
58
|
+
when :today
|
59
|
+
return Time.now.beginning_of_day, Time.now
|
60
|
+
when :yesterday
|
61
|
+
return 1.day.ago.beginning_of_day, 1.day.ago.end_of_day
|
62
|
+
when :this_week
|
63
|
+
return Time.now.beginning_of_week, Time.now
|
64
|
+
when :last_week
|
65
|
+
return 1.week.ago.beginning_of_week, 1.week.ago.end_of_week
|
66
|
+
when :this_month
|
67
|
+
return Time.now.beginning_of_month, Time.now
|
68
|
+
when :last_month
|
69
|
+
return 1.month.ago.beginning_of_month, 1.month.ago.end_of_month
|
70
|
+
when :this_year
|
71
|
+
return Time.now.beginning_of_year, Time.now
|
72
|
+
when :last_year
|
73
|
+
return 1.year.ago.beginning_of_year, 1.year.ago.end_of_year
|
74
|
+
else
|
75
|
+
return default(context), Time.now
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# Initialize time frames `from` and `to` by hash.
|
81
|
+
#
|
82
|
+
# @param hash [Hash] a time hash with keys `from` and `to`
|
83
|
+
# @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
|
84
|
+
# @return [Array] with `from` and `to` time
|
85
|
+
def init_with_hash(hash, context)
|
86
|
+
[ hash[:from] && hash[:from].is_a?(String) && Time.parse(hash[:from]) || hash[:from] || default(context),
|
87
|
+
hash[:to] && hash[:to].is_a?(String) && Time.parse(hash[:to]) || hash[:to] || Time.now ]
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# Initialize time frames `from` and `to` by hash.
|
92
|
+
#
|
93
|
+
# @param range [Range] a time range
|
94
|
+
# @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
|
95
|
+
# @return [Array] with `from` and `to` time
|
96
|
+
def init_with_range(range, context)
|
97
|
+
init_with_hash({ from: range.first, to: range.last }, context)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
# Get default granularity by given context.
|
102
|
+
#
|
103
|
+
# @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
|
104
|
+
# @return [ActiveSupport::TimeWithZone] a time
|
105
|
+
def default(context)
|
106
|
+
Redlics.config.granularities[Granularity.default(context).first][:step].ago
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|