tabs 0.9.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +3 -0
  5. data/README.md +85 -5
  6. data/lib/tabs/config.rb +37 -2
  7. data/lib/tabs/metrics/counter.rb +14 -5
  8. data/lib/tabs/metrics/task.rb +8 -6
  9. data/lib/tabs/metrics/task/token.rb +25 -10
  10. data/lib/tabs/metrics/value.rb +35 -27
  11. data/lib/tabs/resolution.rb +26 -10
  12. data/lib/tabs/resolutionable.rb +36 -13
  13. data/lib/tabs/resolutions/day.rb +9 -1
  14. data/lib/tabs/resolutions/hour.rb +9 -1
  15. data/lib/tabs/resolutions/minute.rb +9 -1
  16. data/lib/tabs/resolutions/month.rb +9 -1
  17. data/lib/tabs/resolutions/week.rb +13 -7
  18. data/lib/tabs/resolutions/year.rb +9 -1
  19. data/lib/tabs/storage.rb +39 -17
  20. data/lib/tabs/tabs.rb +12 -4
  21. data/lib/tabs/version.rb +1 -1
  22. data/spec/lib/tabs/config_spec.rb +60 -0
  23. data/spec/lib/tabs/metrics/counter_spec.rb +44 -1
  24. data/spec/lib/tabs/{task_spec.rb → metrics/task_spec.rb} +31 -3
  25. data/spec/lib/tabs/metrics/value_spec.rb +36 -0
  26. data/spec/lib/tabs/resolution_spec.rb +26 -3
  27. data/spec/lib/tabs/resolutionable_spec.rb +53 -0
  28. data/spec/lib/tabs/resolutions/day_spec.rb +23 -0
  29. data/spec/lib/tabs/resolutions/hour_spec.rb +23 -0
  30. data/spec/lib/tabs/resolutions/minute_spec.rb +23 -0
  31. data/spec/lib/tabs/resolutions/month_spec.rb +23 -0
  32. data/spec/lib/tabs/resolutions/week_spec.rb +24 -0
  33. data/spec/lib/tabs/resolutions/year_spec.rb +23 -0
  34. data/spec/lib/tabs/storage_spec.rb +138 -0
  35. data/spec/lib/tabs_spec.rb +28 -1
  36. data/spec/spec_helper.rb +9 -1
  37. data/spec/support/custom_resolutions.rb +10 -2
  38. data/tabs.gemspec +6 -21
  39. metadata +48 -81
@@ -1,11 +1,16 @@
1
1
  module Tabs
2
2
  module Resolution
3
- extend Resolutionable
3
+ include Resolutionable
4
4
  extend self
5
5
 
6
- def register(resolution, klass)
6
+ def register(klass)
7
7
  @@resolution_classes ||= {}
8
- @@resolution_classes[resolution] = klass
8
+ @@resolution_classes[klass.name] = klass
9
+ end
10
+
11
+ def unregister(resolutions)
12
+ resolutions = Array[resolutions].flatten
13
+ resolutions.each{ |res| @@resolution_classes.delete(res) }
9
14
  end
10
15
 
11
16
  def serialize(resolution, timestamp)
@@ -32,18 +37,29 @@ module Tabs
32
37
  @@resolution_classes.keys
33
38
  end
34
39
 
40
+ def expire(resolution, key, timestamp)
41
+ resolution_klass(resolution).expire(key, timestamp)
42
+ end
43
+
44
+ def reset_default_resolutions
45
+ @@resolution_classes = {}
46
+ Tabs::Resolution.register(Tabs::Resolutions::Minute)
47
+ Tabs::Resolution.register(Tabs::Resolutions::Hour)
48
+ Tabs::Resolution.register(Tabs::Resolutions::Day)
49
+ Tabs::Resolution.register(Tabs::Resolutions::Week)
50
+ Tabs::Resolution.register(Tabs::Resolutions::Month)
51
+ Tabs::Resolution.register(Tabs::Resolutions::Year)
52
+ end
53
+
35
54
  private
36
55
 
37
56
  def resolution_klass(resolution)
38
- @@resolution_classes[resolution]
57
+ klass = @@resolution_classes[resolution]
58
+ raise Tabs::ResolutionMissingError.new(resolution) unless klass
59
+ klass
39
60
  end
40
61
 
41
62
  end
42
63
  end
43
64
 
44
- Tabs::Resolution.register(:minute, Tabs::Resolutions::Minute)
45
- Tabs::Resolution.register(:hour, Tabs::Resolutions::Hour)
46
- Tabs::Resolution.register(:day, Tabs::Resolutions::Day)
47
- Tabs::Resolution.register(:week, Tabs::Resolutions::Week)
48
- Tabs::Resolution.register(:month, Tabs::Resolutions::Month)
49
- Tabs::Resolution.register(:year, Tabs::Resolutions::Year)
65
+ Tabs::Resolution.reset_default_resolutions
@@ -1,25 +1,48 @@
1
1
  module Tabs
2
2
  module Resolutionable
3
- extend self
4
3
 
5
- def serialize
6
- raise "Must implement serialize in the concrete resolution module"
4
+ def self.included(base)
5
+ base.extend ClassMethods
7
6
  end
8
7
 
9
- def deserialize
10
- raise "Must implement deserialize in the concrete resolution module"
11
- end
8
+ module ClassMethods
12
9
 
13
- def from_seconds
14
- raise "Must implement from_seconds in the concrete resolution module"
15
- end
10
+ def name
11
+ raise "Must implement #name in the concrete resolution module"
12
+ end
13
+
14
+ def serialize
15
+ raise "Must implement #serialize in the concrete resolution module"
16
+ end
17
+
18
+ def deserialize
19
+ raise "Must implement #deserialize in the concrete resolution module"
20
+ end
21
+
22
+ def from_seconds
23
+ raise "Must implement #from_seconds in the concrete resolution module"
24
+ end
25
+
26
+ def to_seconds
27
+ raise "Must implement #to_seconds in the concrete resolution module"
28
+ end
29
+
30
+ def add
31
+ raise "Must implement #to_seconds in the concrete resolution module"
32
+ end
33
+
34
+ def normalize
35
+ raise "Must implement #normalize in the concrete resolution module"
36
+ end
16
37
 
17
- def add
18
- raise "Must implement to_seconds in the concrete resolution module"
19
38
  end
20
39
 
21
- def normalize
22
- raise "Must implement normalize in the concrete resolution module"
40
+ def expire(key, timestamp)
41
+ return unless Tabs::Config.expires?(name)
42
+ resolution_ends_at = timestamp.utc.to_i + to_seconds
43
+ expires_at = resolution_ends_at + Tabs::Config.expires_in(name)
44
+ Storage.expireat(key, expires_at)
23
45
  end
46
+
24
47
  end
25
48
  end
@@ -1,11 +1,15 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Day
4
- extend Resolutionable
4
+ include Tabs::Resolutionable
5
5
  extend self
6
6
 
7
7
  PATTERN = "%Y-%m-%d"
8
8
 
9
+ def name
10
+ :day
11
+ end
12
+
9
13
  def serialize(timestamp)
10
14
  timestamp.strftime(PATTERN)
11
15
  end
@@ -19,6 +23,10 @@ module Tabs
19
23
  s / 1.day
20
24
  end
21
25
 
26
+ def to_seconds
27
+ 1.day
28
+ end
29
+
22
30
  def add(ts, num)
23
31
  ts + num.days
24
32
  end
@@ -1,11 +1,15 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Hour
4
- extend Tabs::Resolutionable
4
+ include Tabs::Resolutionable
5
5
  extend self
6
6
 
7
7
  PATTERN = "%Y-%m-%d-%H"
8
8
 
9
+ def name
10
+ :hour
11
+ end
12
+
9
13
  def serialize(timestamp)
10
14
  timestamp.strftime(PATTERN)
11
15
  end
@@ -19,6 +23,10 @@ module Tabs
19
23
  s / 1.hour
20
24
  end
21
25
 
26
+ def to_seconds
27
+ 1.hour
28
+ end
29
+
22
30
  def add(ts, num)
23
31
  ts + num.hours
24
32
  end
@@ -1,11 +1,15 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Minute
4
- extend Tabs::Resolutionable
4
+ include Tabs::Resolutionable
5
5
  extend self
6
6
 
7
7
  PATTERN = "%Y-%m-%d-%H-%M"
8
8
 
9
+ def name
10
+ :minute
11
+ end
12
+
9
13
  def serialize(timestamp)
10
14
  timestamp.strftime(PATTERN)
11
15
  end
@@ -19,6 +23,10 @@ module Tabs
19
23
  s / 1.minute
20
24
  end
21
25
 
26
+ def to_seconds
27
+ 1.minute
28
+ end
29
+
22
30
  def add(ts, num)
23
31
  ts + num.minutes
24
32
  end
@@ -1,11 +1,15 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Month
4
- extend Tabs::Resolutionable
4
+ include Tabs::Resolutionable
5
5
  extend self
6
6
 
7
7
  PATTERN = "%Y-%m"
8
8
 
9
+ def name
10
+ :month
11
+ end
12
+
9
13
  def serialize(timestamp)
10
14
  timestamp.strftime(PATTERN)
11
15
  end
@@ -19,6 +23,10 @@ module Tabs
19
23
  s / 1.month
20
24
  end
21
25
 
26
+ def to_seconds
27
+ 1.month
28
+ end
29
+
22
30
  def add(ts, num)
23
31
  ts + num.months
24
32
  end
@@ -1,26 +1,32 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Week
4
- extend Tabs::Resolutionable
4
+ include Tabs::Resolutionable
5
5
  extend self
6
6
 
7
- PATTERN = "%Y-%W"
7
+ PATTERN = "%Y-%m-%d"
8
+
9
+ def name
10
+ :week
11
+ end
8
12
 
9
13
  def serialize(timestamp)
10
- timestamp.strftime(PATTERN)
14
+ normalize(timestamp).strftime(PATTERN)
11
15
  end
12
16
 
13
17
  def deserialize(str)
14
- year, week = str.split("-").map(&:to_i)
15
- week = 1 if week == 0
16
- dt = DateTime.strptime("#{year}-#{week}", PATTERN)
18
+ dt = DateTime.strptime(str, PATTERN)
17
19
  self.normalize(dt)
18
20
  end
19
21
 
20
- def seconds(s)
22
+ def from_seconds(s)
21
23
  s / 1.week
22
24
  end
23
25
 
26
+ def to_seconds
27
+ 1.week
28
+ end
29
+
24
30
  def add(ts, num)
25
31
  ts + num.weeks
26
32
  end
@@ -1,11 +1,15 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Year
4
- extend Tabs::Resolutionable
4
+ include Tabs::Resolutionable
5
5
  extend self
6
6
 
7
7
  PATTERN = "%Y"
8
8
 
9
+ def name
10
+ :year
11
+ end
12
+
9
13
  def serialize(timestamp)
10
14
  timestamp.strftime(PATTERN)
11
15
  end
@@ -19,6 +23,10 @@ module Tabs
19
23
  s / 1.year
20
24
  end
21
25
 
26
+ def to_seconds
27
+ 1.year
28
+ end
29
+
22
30
  def add(ts, num)
23
31
  ts + num.years
24
32
  end
@@ -6,77 +6,99 @@ module Tabs
6
6
  @redis ||= Config.redis
7
7
  end
8
8
 
9
+ def tabs_key(key)
10
+ if Tabs::Config.prefix.blank?
11
+ "tabs:#{key}"
12
+ else
13
+ "tabs:#{Tabs::Config.prefix}:#{key}"
14
+ end
15
+ end
16
+
9
17
  def exists(key)
10
- redis.get("tabs:#{key}")
18
+ redis.exists(tabs_key(key))
19
+ end
20
+
21
+ def expireat(key, unix_timestamp)
22
+ redis.expireat(tabs_key(key), unix_timestamp)
23
+ end
24
+
25
+ def ttl(key)
26
+ redis.ttl(tabs_key(key))
11
27
  end
12
28
 
13
29
  def get(key)
14
- redis.get("tabs:#{key}")
30
+ redis.get(tabs_key(key))
15
31
  end
16
32
 
17
33
  def mget(*keys)
18
- prefixed_keys = keys.map { |k| "tabs:#{k}" }
34
+ prefixed_keys = keys.map { |k| tabs_key(k) }
19
35
  redis.mget(*prefixed_keys)
20
36
  end
21
37
 
22
38
  def set(key, value)
23
- redis.set("tabs:#{key}", value)
39
+ redis.set(tabs_key(key), value)
24
40
  end
25
41
 
26
42
  def del(*keys)
27
43
  return 0 if keys.empty?
28
- prefixed_keys = keys.map { |k| "tabs:#{k}" }
44
+ prefixed_keys = keys.map { |k| tabs_key(k) }
29
45
  redis.del(*prefixed_keys)
30
46
  end
31
47
 
32
48
  def del_by_prefix(pattern)
33
- keys = redis.keys("tabs:#{pattern}*")
49
+ keys = redis.keys("#{tabs_key(pattern)}*")
34
50
  return 0 if keys.empty?
35
51
  redis.del(*keys)
36
52
  end
37
53
 
38
54
  def incr(key)
39
- redis.incr("tabs:#{key}")
55
+ redis.incr(tabs_key(key))
40
56
  end
41
57
 
42
58
  def rpush(key, value)
43
- redis.rpush("tabs:#{key}", value)
59
+ redis.rpush(tabs_key(key), value)
44
60
  end
45
61
 
46
62
  def sadd(key, *values)
47
- redis.sadd("tabs:#{key}", *values)
63
+ redis.sadd(tabs_key(key), *values)
48
64
  end
49
65
 
50
66
  def smembers(key)
51
- redis.smembers("tabs:#{key}")
67
+ redis.smembers(tabs_key(key))
68
+ end
69
+
70
+ def smembers_all(*keys)
71
+ redis.pipelined do
72
+ keys.map{ |key| smembers(key)}
73
+ end
52
74
  end
53
75
 
54
76
  def sismember(key, value)
55
- redis.sismember("tabs:#{key}", value)
77
+ redis.sismember(tabs_key(key), value)
56
78
  end
57
79
 
58
80
  def hget(key, field)
59
- redis.hget("tabs:#{key}", field)
81
+ redis.hget(tabs_key(key), field)
60
82
  end
61
83
 
62
84
  def hset(key, field, value)
63
- redis.hset("tabs:#{key}", field, value)
85
+ redis.hset(tabs_key(key), field, value)
64
86
  end
65
87
 
66
88
  def hdel(key, field)
67
- redis.hdel("tabs:#{key}", field)
89
+ redis.hdel(tabs_key(key), field)
68
90
  end
69
91
 
70
92
  def hkeys(key)
71
- redis.hkeys("tabs:#{key}")
93
+ redis.hkeys(tabs_key(key))
72
94
  end
73
95
 
74
96
  def hincrby(key, field, value)
75
- redis.hincrby("tabs:#{key}", field, value)
97
+ redis.hincrby(tabs_key(key), field, value)
76
98
  end
77
99
 
78
100
  def hgetall(key)
79
- redis.hgetall("tabs:#{key}")
101
+ redis.hgetall(tabs_key(key))
80
102
  end
81
103
 
82
104
  end
@@ -2,10 +2,11 @@ module Tabs
2
2
  extend self
3
3
  extend Tabs::Storage
4
4
 
5
- class UnknownTypeError < Exception; end
6
- class DuplicateMetricError < Exception; end
7
- class UnknownMetricError < Exception; end
8
- class MetricTypeMismatchError < Exception; end
5
+ class UnknownTypeError < StandardError; end
6
+ class DuplicateMetricError < StandardError; end
7
+ class UnknownMetricError < StandardError; end
8
+ class MetricTypeMismatchError < StandardError; end
9
+ class ResolutionMissingError < StandardError; end
9
10
 
10
11
  METRIC_TYPES = ["counter", "value", "task"]
11
12
 
@@ -94,6 +95,13 @@ module Tabs
94
95
  metrics.each { |key| self.drop_metric! key }
95
96
  end
96
97
 
98
+ def drop_resolution_for_metric!(key, resolution)
99
+ raise UnknownMetricError.new("Unknown metric: #{key}") unless metric_exists?(key)
100
+ raise ResolutionMissingError.new(resolution) unless Tabs::Resolution.all.include? resolution
101
+ metric = get_metric(key)
102
+ metric.drop_by_resolution!(resolution) unless metric_type(key) == "task"
103
+ end
104
+
97
105
  private
98
106
 
99
107
  def metric_klass(type)
@@ -1,3 +1,3 @@
1
1
  module Tabs
2
- VERSION = "0.9.1"
2
+ VERSION = "1.0.0"
3
3
  end