time_series 0.0.1

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