tabs 0.9.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +3 -0
- data/README.md +85 -5
- data/lib/tabs/config.rb +37 -2
- data/lib/tabs/metrics/counter.rb +14 -5
- data/lib/tabs/metrics/task.rb +8 -6
- data/lib/tabs/metrics/task/token.rb +25 -10
- data/lib/tabs/metrics/value.rb +35 -27
- data/lib/tabs/resolution.rb +26 -10
- data/lib/tabs/resolutionable.rb +36 -13
- data/lib/tabs/resolutions/day.rb +9 -1
- data/lib/tabs/resolutions/hour.rb +9 -1
- data/lib/tabs/resolutions/minute.rb +9 -1
- data/lib/tabs/resolutions/month.rb +9 -1
- data/lib/tabs/resolutions/week.rb +13 -7
- data/lib/tabs/resolutions/year.rb +9 -1
- data/lib/tabs/storage.rb +39 -17
- data/lib/tabs/tabs.rb +12 -4
- data/lib/tabs/version.rb +1 -1
- data/spec/lib/tabs/config_spec.rb +60 -0
- data/spec/lib/tabs/metrics/counter_spec.rb +44 -1
- data/spec/lib/tabs/{task_spec.rb → metrics/task_spec.rb} +31 -3
- data/spec/lib/tabs/metrics/value_spec.rb +36 -0
- data/spec/lib/tabs/resolution_spec.rb +26 -3
- data/spec/lib/tabs/resolutionable_spec.rb +53 -0
- data/spec/lib/tabs/resolutions/day_spec.rb +23 -0
- data/spec/lib/tabs/resolutions/hour_spec.rb +23 -0
- data/spec/lib/tabs/resolutions/minute_spec.rb +23 -0
- data/spec/lib/tabs/resolutions/month_spec.rb +23 -0
- data/spec/lib/tabs/resolutions/week_spec.rb +24 -0
- data/spec/lib/tabs/resolutions/year_spec.rb +23 -0
- data/spec/lib/tabs/storage_spec.rb +138 -0
- data/spec/lib/tabs_spec.rb +28 -1
- data/spec/spec_helper.rb +9 -1
- data/spec/support/custom_resolutions.rb +10 -2
- data/tabs.gemspec +6 -21
- metadata +48 -81
data/lib/tabs/resolution.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
module Tabs
|
2
2
|
module Resolution
|
3
|
-
|
3
|
+
include Resolutionable
|
4
4
|
extend self
|
5
5
|
|
6
|
-
def register(
|
6
|
+
def register(klass)
|
7
7
|
@@resolution_classes ||= {}
|
8
|
-
@@resolution_classes[
|
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.
|
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
|
data/lib/tabs/resolutionable.rb
CHANGED
@@ -1,25 +1,48 @@
|
|
1
1
|
module Tabs
|
2
2
|
module Resolutionable
|
3
|
-
extend self
|
4
3
|
|
5
|
-
def
|
6
|
-
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
7
6
|
end
|
8
7
|
|
9
|
-
|
10
|
-
raise "Must implement deserialize in the concrete resolution module"
|
11
|
-
end
|
8
|
+
module ClassMethods
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
22
|
-
|
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
|
data/lib/tabs/resolutions/day.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
module Tabs
|
2
2
|
module Resolutions
|
3
3
|
module Day
|
4
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
4
|
+
include Tabs::Resolutionable
|
5
5
|
extend self
|
6
6
|
|
7
|
-
PATTERN = "%Y-%
|
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
|
-
|
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
|
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
|
-
|
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
|
data/lib/tabs/storage.rb
CHANGED
@@ -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.
|
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(
|
30
|
+
redis.get(tabs_key(key))
|
15
31
|
end
|
16
32
|
|
17
33
|
def mget(*keys)
|
18
|
-
prefixed_keys = keys.map { |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(
|
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|
|
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("
|
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(
|
55
|
+
redis.incr(tabs_key(key))
|
40
56
|
end
|
41
57
|
|
42
58
|
def rpush(key, value)
|
43
|
-
redis.rpush(
|
59
|
+
redis.rpush(tabs_key(key), value)
|
44
60
|
end
|
45
61
|
|
46
62
|
def sadd(key, *values)
|
47
|
-
redis.sadd(
|
63
|
+
redis.sadd(tabs_key(key), *values)
|
48
64
|
end
|
49
65
|
|
50
66
|
def smembers(key)
|
51
|
-
redis.smembers(
|
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(
|
77
|
+
redis.sismember(tabs_key(key), value)
|
56
78
|
end
|
57
79
|
|
58
80
|
def hget(key, field)
|
59
|
-
redis.hget(
|
81
|
+
redis.hget(tabs_key(key), field)
|
60
82
|
end
|
61
83
|
|
62
84
|
def hset(key, field, value)
|
63
|
-
redis.hset(
|
85
|
+
redis.hset(tabs_key(key), field, value)
|
64
86
|
end
|
65
87
|
|
66
88
|
def hdel(key, field)
|
67
|
-
redis.hdel(
|
89
|
+
redis.hdel(tabs_key(key), field)
|
68
90
|
end
|
69
91
|
|
70
92
|
def hkeys(key)
|
71
|
-
redis.hkeys(
|
93
|
+
redis.hkeys(tabs_key(key))
|
72
94
|
end
|
73
95
|
|
74
96
|
def hincrby(key, field, value)
|
75
|
-
redis.hincrby(
|
97
|
+
redis.hincrby(tabs_key(key), field, value)
|
76
98
|
end
|
77
99
|
|
78
100
|
def hgetall(key)
|
79
|
-
redis.hgetall(
|
101
|
+
redis.hgetall(tabs_key(key))
|
80
102
|
end
|
81
103
|
|
82
104
|
end
|
data/lib/tabs/tabs.rb
CHANGED
@@ -2,10 +2,11 @@ module Tabs
|
|
2
2
|
extend self
|
3
3
|
extend Tabs::Storage
|
4
4
|
|
5
|
-
class UnknownTypeError <
|
6
|
-
class DuplicateMetricError <
|
7
|
-
class UnknownMetricError <
|
8
|
-
class MetricTypeMismatchError <
|
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)
|
data/lib/tabs/version.rb
CHANGED