redlics 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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