redlics 0.1.0

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