tabs 0.9.1 → 1.0.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.
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