time_series 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.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in redis_timeseries.gemspec
4
+ gemspec
5
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 panos
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # TimeSeries
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'time_series'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install time_series
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+
6
+ `reset`
7
+
8
+ Rake::TestTask.new do |t|
9
+ t.libs << 'lib/time_series'
10
+ t.test_files = FileList['spec/*spec.rb']
11
+ t.verbose = true
12
+ end
13
+
14
+ task :default => :test
data/lib/key.rb ADDED
@@ -0,0 +1,205 @@
1
+ # coding: utf-8
2
+
3
+ require 'bundler/setup'
4
+ require 'redis'
5
+ require 'hiredis'
6
+ require 'date'
7
+ #require 'json'
8
+ require 'awesome_print'
9
+
10
+ class Time
11
+ # def self.from_key key
12
+ # key.gsub!(/^[^:]*:[^:]*:[^:]*:/, '')
13
+ # Time.strptime(key, "%Y:%m:%d:%H:%M:%S") # TODO works only with second-resolution
14
+ # end
15
+
16
+ def human
17
+ a = (Time.now-self).to_i
18
+ case a
19
+ when 0 then 'just now'
20
+ when 1 then 'a second ago'
21
+ when 2..59 then a.to_s+' seconds ago'
22
+ when 60..119 then 'a minute ago' #120 = 2 minutes
23
+ when 120..3540 then (a/60).to_i.to_s+' minutes ago'
24
+ when 3541..7100 then 'an hour ago' # 3600 = 1 hour
25
+ when 7101..82800 then ((a+99)/3600).to_i.to_s+' hours ago'
26
+ when 82801..172000 then 'a day ago' # 86400 = 1 day
27
+ when 172001..518400 then ((a+800)/(60*60*24)).to_i.to_s+' days ago'
28
+ when 518400..1036800 then 'a week ago'
29
+ else ((a+180000)/(60*60*24*7)).to_i.to_s+' weeks ago'
30
+ end
31
+ end
32
+ end
33
+
34
+ class String
35
+
36
+ def redis
37
+ @redis = $redis ||= Redis.new(:path => "/tmp/redis.sock",:driver => :hiredis)
38
+ return @redis
39
+ end
40
+
41
+ def prefix
42
+ matchdata = /(^[^:]+:[^:]+:[^:]+:)/.match(self)
43
+ return matchdata[1] unless matchdata.nil?
44
+ end
45
+
46
+ def exists?
47
+ redis.exists self
48
+ end
49
+
50
+ #deprecated
51
+ def parent_key
52
+ parent
53
+ end
54
+
55
+ def parent
56
+ self.gsub(/:[^:]*$/, '')
57
+ end
58
+
59
+ def has_persistant_children?
60
+ not persistant_children.empty?
61
+ end
62
+
63
+ def has_children?
64
+ has_persistant_children?
65
+ end
66
+
67
+ #deprecated
68
+ def has_parent?
69
+ parent.exists?
70
+ end
71
+
72
+ def volatile?
73
+ redis.ttl(self) >= 0
74
+ end
75
+
76
+ def persistant?
77
+ redis.ttl(self)==-1 and exists?
78
+ end
79
+
80
+ def ttl
81
+ redis.ttl self
82
+ end
83
+
84
+ #deprecated
85
+ def has_persistant_parent?
86
+ redis.ttl(parent_key) < 0 and parent.exists?
87
+ end
88
+
89
+ #deprecated
90
+ def has_volatile_parent?
91
+ redis.ttl(parent_key) >= 0
92
+ end
93
+
94
+ def children
95
+ redis.keys(self+":*")
96
+ end
97
+
98
+ def persistant_children
99
+ keys = redis.keys(self+":*")
100
+ keys.delete_if{|k| k.volatile?}
101
+ return keys
102
+ end
103
+
104
+ def siblings_keys
105
+ redis.keys(self.gsub(/:[^:]*$/, ':*'))
106
+ end
107
+
108
+
109
+ def unionize_persistant_children
110
+ children = persistant_children
111
+ redis.zunionstore self, children
112
+
113
+ if recent?
114
+ redis.expire self, 60
115
+ else
116
+ redis.expire self, 600
117
+ end
118
+
119
+ end
120
+
121
+ def resolution
122
+ case self.count(':')
123
+ when 8 then :second
124
+ when 7 then :minute
125
+ when 6 then :hour
126
+ when 5 then :day
127
+ when 4 then :month
128
+ when 3 then :year
129
+ end
130
+ end
131
+
132
+ def year?; resolution == :year end
133
+ def month?; resolution == :month end
134
+ def day?; resolution == :day end
135
+ def hour?; resolution == :hour end
136
+ def minute?; resolution == :minute end
137
+ def second?; resolution == :second end
138
+
139
+ def year; /[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
140
+ def month; /[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
141
+ def day; /[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
142
+ def hour; /[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
143
+ def minute; /[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
144
+ def second; /[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:([^:]+)/.match(self)[1].to_i end
145
+
146
+ def time
147
+ key = self.gsub(/^[^:]*:[^:]*:[^:]*:/, '') #remove prefix
148
+ case resolution
149
+ when :year then return DateTime.new(year).to_time
150
+ when :month then return DateTime.new(year, month).to_time
151
+ when :day then return DateTime.new(year, month, day).to_time
152
+ when :hour then return DateTime.new(year, month, day, hour, 0, 0, '+3').to_time
153
+ when :minute then return DateTime.new(year, month, day, hour, minute, 0, '+3').to_time
154
+ when :second then return DateTime.new(year, month, day, hour, minute, second, '+3').to_time
155
+ end
156
+ end
157
+
158
+ def recent?
159
+ case resolution
160
+ when :year then year == Time.now.year
161
+ when :month then year == Time.now.year
162
+ when :day then year == Time.now.year and month == Time.now.month
163
+ when :hour then year == Time.now.year and month == Time.now.month and day == Time.now.day
164
+ when :minute then year == Time.now.year and month == Time.now.month and day == Time.now.day and hour == Time.now.hour
165
+ when :second then year == Time.now.year and month == Time.now.month and day == Time.now.day and hour == Time.now.hour and minute == Time.now.min
166
+ end
167
+ end
168
+
169
+ def get_array
170
+ redis.zrevrange self, 0, -1, :with_scores => true
171
+ end
172
+
173
+ def array_to_hash array
174
+ hash = Hash.new
175
+ array.each{|a| hash[a[0]] = a[1].to_f }
176
+ return hash
177
+ end
178
+
179
+ def get
180
+ if exists?
181
+ ;
182
+ elsif has_children?
183
+ unionize_persistant_children
184
+ else
185
+ if not year?
186
+ parent.get
187
+ # http://rubydoc.info/github/redis/redis-rb/Redis:zunionstore
188
+ p = parent #TODO Test the case when the immediate parent has no persistant children so weights has infinite values
189
+ while !p.has_children?
190
+ p = p.parent
191
+ end
192
+ weights = [ 1.0 / p.persistant_children.size ]
193
+ redis.zunionstore self, [parent], :weights => weights
194
+ if recent?
195
+ redis.expire self, 60
196
+ else
197
+ redis.expire self, 600
198
+ end
199
+ else
200
+ return {}
201
+ end
202
+ end
203
+ array_to_hash get_array
204
+ end
205
+ end
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+ require 'bundler/setup'
3
+ require 'redis'
4
+ require 'hiredis'
5
+
6
+
7
+ # Initialize a connection to Redis.
8
+ # Currently the path to redis socket is hardcoded (/tmp/redis.sock),
9
+ # as well as the driver (:hiredis).
10
+ # global variable $redis gets set.
11
+ #
12
+ #
13
+ class RedisConnection
14
+ attr_reader :redis
15
+ def initialize *args
16
+ #@key= key
17
+ $redis ||= Redis.new(:path => "/tmp/redis.sock",:driver => :hiredis)
18
+ @redis = $redis
19
+ #puts green "Connected to Redis"
20
+ end # end initialize
21
+ end
@@ -0,0 +1,383 @@
1
+ # coding: utf-8
2
+ require 'bundler/setup'
3
+
4
+ require 'active_support/core_ext/numeric/time.rb'
5
+
6
+ #require 'active_support/core_ext/time/calculations.rb'
7
+ require_relative "version"
8
+ require_relative 'redis_connection'
9
+ require_relative 'key'
10
+
11
+ ##
12
+ # TimeSeries Class
13
+ #
14
+ class TimeSeries < RedisConnection
15
+ attr_reader :name, :duration, :resolution
16
+
17
+ ##
18
+ # Create Timeseries.
19
+ #
20
+ # @param [String] name The timeseries name.
21
+ # @param [Hash] options The options hash.
22
+ # @option options [String] :resolution The time resolution: :year, :month, :day, :hour, :minute, :second
23
+ # @option options [Integer] :duration Duration is under development. It will allow for example 10, 20 or 30 seconds keys. Now only keys with :minute resolution are available.
24
+ def initialize name, options={}
25
+ super # initialize RedisConnection
26
+
27
+ @name = name
28
+ @prefix="#{$app_prefix}:ts:#{@name}:"
29
+
30
+ # parse the resolution option
31
+ if options.has_key? :resolution
32
+ @resolution = options[:resolution]
33
+ resolutions = [:year, :month, :day, :hour, :minute, :second]
34
+ #if the resolution option is invalid raise an exception
35
+ unless resolutions.include?(@resolution) #or @resolution.is_a?(Integer)
36
+ raise ArgumentError.new("resolution can be either :year or :month or :day or :hour or :minute or :second")
37
+ end
38
+ elsif keys.empty? # default resolution is :second
39
+ @resolution = :second
40
+ else # try to guess resolution from existing keys
41
+ max_res = 0
42
+ keys.each do |k|
43
+ res = k.count(':')
44
+ max_res = res if res > max_res
45
+ end
46
+
47
+ case max_res
48
+ when 8 then @resolution = :second
49
+ when 7 then @resolution = :minute
50
+ when 6 then @resolution = :hour
51
+ when 5 then @resolution = :day
52
+ when 4 then @resolution = :month
53
+ when 3 then @resolution = :year
54
+ else raise ArgumentError.new("Cannot guess resolution from existing keys")
55
+ end #case
56
+ end # if
57
+
58
+ # define the @duration based on @resolution
59
+ case @resolution
60
+ when :year then @duration = 12*30*24*3600
61
+ when :month then @duration = 30*24*3600
62
+ when :day then @duration = 24*3600
63
+ when :hour then @duration = 3600
64
+ when :minute then @duration = 60
65
+ when :second then @duration = options[:duration] ||= 20
66
+ end
67
+ end
68
+
69
+ # Returns the current time.
70
+ #
71
+ # @return [Time] the current time
72
+ def current_time
73
+ time=Time.at(Time.now.to_i) # this way nsec and usec is 0
74
+ if @resolution == :second
75
+ sec = time.strftime("%S").to_i % @duration
76
+ time = time - sec
77
+ end
78
+ return time
79
+ end
80
+
81
+ # Returns the time of the last key.
82
+ #
83
+ # @return [Time] the last key's time
84
+ def last_time
85
+ current_time - @duration
86
+ end
87
+
88
+ # Returns the time of the previous key.
89
+ #
90
+ # @return [Time] the previous key's time
91
+ def previous_time
92
+ current_time - 2 * @duration
93
+ end
94
+
95
+ # Returns the current key
96
+ #
97
+ # @return [String] current key
98
+ def current_key
99
+ time_to_key current_time, @resolution
100
+ end
101
+
102
+ # Returns the last key
103
+ #
104
+ # @return [String] last key
105
+ def last_key
106
+ time_to_key last_time, @resolution
107
+ end
108
+
109
+ # Returns the previous key
110
+ #
111
+ # @return [String] previous key
112
+ def previous_key
113
+ time_to_key previous_time, @resolution
114
+ end
115
+
116
+ # Returns all the keys
117
+ #
118
+ # @return [Array] all the keys in a String Array
119
+ def keys
120
+ return @redis.keys"#{$app_prefix}:ts:#{@name}:*"
121
+ end
122
+
123
+ # Deletes all the keys
124
+ #
125
+ # @return Number of keys deleted
126
+ def clear
127
+ i = 0
128
+ keys.each{|k| @redis.del k; i+=1}
129
+ return i
130
+ end
131
+
132
+ # Returns the contents of all the keys
133
+ # TODO Considering to remove this method
134
+ #
135
+ # @return [Hash] contents of all the keys
136
+ def all
137
+ all = Hash.new
138
+ keys.each{ |k| all[k.gsub(/#{@prefix}/,'')]=k.get}
139
+ return all
140
+ end
141
+
142
+ # Returns the contents of the last key
143
+ #
144
+ # @return [Hash] contents of the last key
145
+ def last
146
+ last_key.get
147
+ end
148
+
149
+ # Returns the contents of the previous key
150
+ #
151
+ # @return [Hash] contents of the previous key
152
+ def previous
153
+ previous_key.get
154
+ end
155
+
156
+ # Push a new Term into the Timeseries
157
+ def push term
158
+ @redis.zincrby current_key, 1, term
159
+ end
160
+
161
+ # Convert a Time object to the respective Key
162
+ # TODO Refactoring
163
+ #
164
+ # @param [Time] time The Time
165
+ # @option [String] resolution The time resolution: :year, :month, :day, :hour, :minute, :second
166
+ # @return [String] The Key
167
+ def time_to_key time, *resolution
168
+ if resolution.empty?
169
+ return time.strftime("#{@prefix}%Y:%m:%d:%H:%M:%S")
170
+ else
171
+ resolution = resolution.first
172
+ case resolution
173
+ when :year then return time.strftime("#{@prefix}%Y")
174
+ when :month then return time.strftime("#{@prefix}%Y:%m")
175
+ when :day then return time.strftime("#{@prefix}%Y:%m:%d")
176
+ when :hour then return time.strftime("#{@prefix}%Y:%m:%d:%H")
177
+ when :minute then return time.strftime("#{@prefix}%Y:%m:%d:%H:%M")
178
+ when :second then return time.strftime("#{@prefix}%Y:%m:%d:%H:%M:%S")
179
+ else puts red "wrong resolution in time_to_key"
180
+ end
181
+ end
182
+ end
183
+
184
+ # Removes recent keys from a key array
185
+ #
186
+ # @param [Array] array Array of Keys
187
+ # @param [Integer] Number of seconds that a key is considered recent
188
+ # @return [Array] The new array
189
+ def array_older_than array, time
190
+ array.keep_if { |k| k.time <= Time.now - time }
191
+ end
192
+
193
+ # Keys with second resolution
194
+ #
195
+ # @param [Array] time
196
+ # @return [Array] Array with the keys
197
+ def seconds *time
198
+ array = keys.keep_if { |k| k.second? and k.persistant?}
199
+ if time.empty?
200
+ return array
201
+ else
202
+ time = time.first
203
+ return array_older_than array, time
204
+ end
205
+ end
206
+
207
+ # Keys with minute resolution
208
+ #
209
+ # @param [Array] time
210
+ # @return [Array] Array with the keys
211
+ def minutes *time
212
+ array = keys.keep_if { |k| k.minute? and k.persistant?}
213
+ if time.empty?
214
+ return array
215
+ else
216
+ time = time.first
217
+ return array_older_than array, time
218
+ end
219
+ end
220
+
221
+ # Keys with hour resolution
222
+ #
223
+ # @param [Array] time
224
+ # @return [Array] Array with the keys
225
+ def hours *time
226
+ array = keys.keep_if { |k| k.hour? and k.persistant?}
227
+ if time.empty?
228
+ return array
229
+ else
230
+ time = time.first
231
+ return array_older_than array, time
232
+ end
233
+ end
234
+
235
+ # Keys with day resolution
236
+ #
237
+ # @param [Array] time
238
+ # @return [Array] Array with the keys
239
+ def days *time
240
+ array = keys.keep_if { |k| k.day? and k.persistant?}
241
+ if time.empty?
242
+ return array
243
+ else
244
+ time = time.first
245
+ return array_older_than array, time
246
+ end
247
+ end
248
+
249
+ # Keys with month resolution
250
+ #
251
+ # @param [Array] time
252
+ # @return [Array] Array with the keys
253
+ def months *time
254
+ array = keys.keep_if { |k| k.month? and k.persistant?}
255
+ if time.empty?
256
+ return array
257
+ else
258
+ time = time.first
259
+ return array_older_than array, time
260
+ end
261
+
262
+ end
263
+
264
+ # Keys with year resolution
265
+ #
266
+ # @param [Array] time
267
+ # @return [Array] Array with the keys
268
+ def years *time
269
+ array = keys.keep_if { |k| k.year? and k.persistant?}
270
+ if time.empty?
271
+ return array
272
+ else
273
+ time = time.first
274
+ return array_older_than array, time
275
+ end
276
+
277
+ end
278
+
279
+ # Compress keys
280
+ # Key compression merges the given keys of the same resolution to keys
281
+ # of greater resolution. For example seconds are merged into minutes and
282
+ # days are merged into months.
283
+ # The values of the keys are merged too.
284
+ # After the merge the keys are deleted.
285
+ #
286
+ # @param [Array] keys to be compressed
287
+ # @return [Integer] Number of keys compressed
288
+ def compress keys
289
+ parents = []
290
+ keys.each do |key|
291
+ unless key.recent? or key.year?
292
+ parent = key.parent
293
+ parents << parent unless parents.include? parent
294
+ end
295
+ end
296
+
297
+ i = 0
298
+ parents.each do |parent|
299
+ unless parent.recent?
300
+ children = parent.persistant_children
301
+ @redis.zunionstore parent, children
302
+ children.each{|k| @redis.del k; i+=1}
303
+ end
304
+ end
305
+ return i
306
+ end
307
+
308
+ # Remove terms with low scores
309
+ #
310
+ # @param [Array] keys that will be examined
311
+ # @return [Array] Number of keys the operation took place, it doesn't mean that something changed
312
+ def remove_by_score keys, *population
313
+ if population.empty?
314
+ population = 1
315
+ else
316
+ population = population.first
317
+ end
318
+ i = 0
319
+ keys.each {|k| @redis.zremrangebyscore(k, '-inf', population); i+=1} # TODO What zremrangebyscore returns?
320
+ return i
321
+ end
322
+
323
+ #def term_weights terms, factor
324
+ # terms.each do |term, value|
325
+ # terms[term]=value*factor
326
+ # end
327
+ # return terms
328
+ #end
329
+
330
+ def year time; time_to_key(time, :year).get end
331
+ def month time; time_to_key(time, :month).get end
332
+ def day time; time_to_key(time, :day).get end
333
+ def hour time; time_to_key(time, :hour).get end
334
+ def minute time; time_to_key(time, :minute).get end
335
+ def second time; time_to_key(time, :second).get end
336
+
337
+ end
338
+
339
+
340
+
341
+ __END__
342
+ =begin
343
+ def get_by_time time
344
+ key = time_to_key time
345
+ if key.exists?
346
+ return get(key)
347
+ elsif false
348
+ return nil
349
+ end
350
+ #array_to_hash @redis.zrange key, 0, -1, :with_scores => true
351
+ end
352
+
353
+
354
+ def get_by_resolution time, resolution
355
+ key = time_to_key time, resolution
356
+ if key.exists?
357
+ ;
358
+ elsif key.has_children?
359
+ key.unionize_persistant_children
360
+ else
361
+ return get_parent time, resolution
362
+ # fix data
363
+ # add ttl
364
+ end
365
+
366
+ return get(key)
367
+ end
368
+
369
+ def get_parent time, resolution
370
+ case resolution
371
+ when :month then get_year time
372
+ when :day then get_month time
373
+ when :hour then get_day time
374
+ when :minute then get_hour time
375
+ when :second then get_minute time
376
+ else get_year time
377
+ end
378
+ end
379
+
380
+
381
+ =end
382
+
383
+