tabstabs 2.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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +421 -0
- data/Rakefile +5 -0
- data/lib/tabs_tabs.rb +26 -0
- data/lib/tabs_tabs/config.rb +65 -0
- data/lib/tabs_tabs/helpers.rb +27 -0
- data/lib/tabs_tabs/metrics/counter.rb +69 -0
- data/lib/tabs_tabs/metrics/counter/stats.rb +51 -0
- data/lib/tabs_tabs/metrics/task.rb +72 -0
- data/lib/tabs_tabs/metrics/task/token.rb +89 -0
- data/lib/tabs_tabs/metrics/value.rb +91 -0
- data/lib/tabs_tabs/metrics/value/stats.rb +55 -0
- data/lib/tabs_tabs/resolution.rb +65 -0
- data/lib/tabs_tabs/resolutionable.rb +48 -0
- data/lib/tabs_tabs/resolutions/day.rb +40 -0
- data/lib/tabs_tabs/resolutions/hour.rb +40 -0
- data/lib/tabs_tabs/resolutions/minute.rb +40 -0
- data/lib/tabs_tabs/resolutions/month.rb +40 -0
- data/lib/tabs_tabs/resolutions/week.rb +40 -0
- data/lib/tabs_tabs/resolutions/year.rb +40 -0
- data/lib/tabs_tabs/storage.rb +105 -0
- data/lib/tabs_tabs/tabs_tabs.rb +117 -0
- data/lib/tabs_tabs/version.rb +3 -0
- data/spec/lib/tabs_tabs/config_spec.rb +60 -0
- data/spec/lib/tabs_tabs/metrics/counter/stats_spec.rb +42 -0
- data/spec/lib/tabs_tabs/metrics/counter_spec.rb +196 -0
- data/spec/lib/tabs_tabs/metrics/task/token_spec.rb +18 -0
- data/spec/lib/tabs_tabs/metrics/task_spec.rb +103 -0
- data/spec/lib/tabs_tabs/metrics/value/stats_spec.rb +61 -0
- data/spec/lib/tabs_tabs/metrics/value_spec.rb +160 -0
- data/spec/lib/tabs_tabs/resolution_spec.rb +52 -0
- data/spec/lib/tabs_tabs/resolutionable_spec.rb +53 -0
- data/spec/lib/tabs_tabs/resolutions/day_spec.rb +23 -0
- data/spec/lib/tabs_tabs/resolutions/hour_spec.rb +23 -0
- data/spec/lib/tabs_tabs/resolutions/minute_spec.rb +23 -0
- data/spec/lib/tabs_tabs/resolutions/month_spec.rb +23 -0
- data/spec/lib/tabs_tabs/resolutions/week_spec.rb +24 -0
- data/spec/lib/tabs_tabs/resolutions/year_spec.rb +23 -0
- data/spec/lib/tabs_tabs/storage_spec.rb +138 -0
- data/spec/lib/tabs_tabs_spec.rb +223 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/custom_resolutions.rb +40 -0
- data/tabs_tabs.gemspec +31 -0
- metadata +213 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Metrics
|
3
|
+
class Value
|
4
|
+
class Stats
|
5
|
+
|
6
|
+
include Enumerable
|
7
|
+
include Helpers
|
8
|
+
|
9
|
+
attr_reader :period, :resolution, :values
|
10
|
+
|
11
|
+
def initialize(period, resolution, values)
|
12
|
+
@period, @resolution, @values = period, resolution, values
|
13
|
+
end
|
14
|
+
|
15
|
+
def first
|
16
|
+
values.first
|
17
|
+
end
|
18
|
+
|
19
|
+
def last
|
20
|
+
values.last
|
21
|
+
end
|
22
|
+
|
23
|
+
def count
|
24
|
+
@count ||= values.map { |v| v["count"] }.sum
|
25
|
+
end
|
26
|
+
|
27
|
+
def sum
|
28
|
+
@sum ||= values.map { |v| v["sum"] }.sum
|
29
|
+
end
|
30
|
+
|
31
|
+
def min
|
32
|
+
@min ||= values.min_by { |v| v["min"] }["min"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def max
|
36
|
+
@max ||= values.max_by { |v| v["max"] }["max"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def avg
|
40
|
+
return 0 if count.zero?
|
41
|
+
(self.sum.to_f / self.count.to_f).round(Config.decimal_precision)
|
42
|
+
end
|
43
|
+
|
44
|
+
def each(&block)
|
45
|
+
values.each(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_a
|
49
|
+
values
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Resolution
|
3
|
+
include Resolutionable
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def register(klass)
|
7
|
+
@@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) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize(resolution, timestamp)
|
17
|
+
resolution_klass(resolution).serialize(timestamp)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deserialize(resolution, str)
|
21
|
+
resolution_klass(resolution).deserialize(str)
|
22
|
+
end
|
23
|
+
|
24
|
+
def from_seconds(resolution, s)
|
25
|
+
resolution_klass(resolution).from_seconds(s)
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(resolution, ts, num)
|
29
|
+
resolution_klass(resolution).add(ts, num)
|
30
|
+
end
|
31
|
+
|
32
|
+
def normalize(resolution, timestamp)
|
33
|
+
resolution_klass(resolution).normalize(timestamp)
|
34
|
+
end
|
35
|
+
|
36
|
+
def all
|
37
|
+
@@resolution_classes.keys
|
38
|
+
end
|
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
|
+
TabsTabs::Resolution.register(TabsTabs::Resolutions::Minute)
|
47
|
+
TabsTabs::Resolution.register(TabsTabs::Resolutions::Hour)
|
48
|
+
TabsTabs::Resolution.register(TabsTabs::Resolutions::Day)
|
49
|
+
TabsTabs::Resolution.register(TabsTabs::Resolutions::Week)
|
50
|
+
TabsTabs::Resolution.register(TabsTabs::Resolutions::Month)
|
51
|
+
TabsTabs::Resolution.register(TabsTabs::Resolutions::Year)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def resolution_klass(resolution)
|
57
|
+
klass = @@resolution_classes[resolution]
|
58
|
+
raise TabsTabs::ResolutionMissingError.new(resolution) unless klass
|
59
|
+
klass
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
TabsTabs::Resolution.reset_default_resolutions
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Resolutionable
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
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
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def expire(key, timestamp)
|
41
|
+
return unless TabsTabs::Config.expires?(name)
|
42
|
+
resolution_ends_at = timestamp.utc.to_i + to_seconds
|
43
|
+
expires_at = resolution_ends_at + TabsTabs::Config.expires_in(name)
|
44
|
+
Storage.expireat(key, expires_at)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Resolutions
|
3
|
+
module Day
|
4
|
+
include TabsTabs::Resolutionable
|
5
|
+
extend self
|
6
|
+
|
7
|
+
PATTERN = "%Y-%m-%d"
|
8
|
+
|
9
|
+
def name
|
10
|
+
:day
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(timestamp)
|
14
|
+
timestamp.strftime(PATTERN)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(str)
|
18
|
+
dt = DateTime.strptime(str, PATTERN)
|
19
|
+
self.normalize(dt)
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_seconds(s)
|
23
|
+
s / 1.day
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_seconds
|
27
|
+
1.day
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(ts, num)
|
31
|
+
ts + num.days
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize(ts)
|
35
|
+
Time.utc(ts.year, ts.month, ts.day)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Resolutions
|
3
|
+
module Hour
|
4
|
+
include TabsTabs::Resolutionable
|
5
|
+
extend self
|
6
|
+
|
7
|
+
PATTERN = "%Y-%m-%d-%H"
|
8
|
+
|
9
|
+
def name
|
10
|
+
:hour
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(timestamp)
|
14
|
+
timestamp.strftime(PATTERN)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(str)
|
18
|
+
dt = DateTime.strptime(str, PATTERN)
|
19
|
+
self.normalize(dt)
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_seconds(s)
|
23
|
+
s / 1.hour
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_seconds
|
27
|
+
1.hour
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(ts, num)
|
31
|
+
ts + num.hours
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize(ts)
|
35
|
+
Time.utc(ts.year, ts.month, ts.day, ts.hour)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Resolutions
|
3
|
+
module Minute
|
4
|
+
include TabsTabs::Resolutionable
|
5
|
+
extend self
|
6
|
+
|
7
|
+
PATTERN = "%Y-%m-%d-%H-%M"
|
8
|
+
|
9
|
+
def name
|
10
|
+
:minute
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(timestamp)
|
14
|
+
timestamp.strftime(PATTERN)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(str)
|
18
|
+
dt = DateTime.strptime(str, PATTERN)
|
19
|
+
self.normalize(dt)
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_seconds(s)
|
23
|
+
s / 1.minute
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_seconds
|
27
|
+
1.minute
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(ts, num)
|
31
|
+
ts + num.minutes
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize(ts)
|
35
|
+
Time.utc(ts.year, ts.month, ts.day, ts.hour, ts.min)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Resolutions
|
3
|
+
module Month
|
4
|
+
include TabsTabs::Resolutionable
|
5
|
+
extend self
|
6
|
+
|
7
|
+
PATTERN = "%Y-%m"
|
8
|
+
|
9
|
+
def name
|
10
|
+
:month
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(timestamp)
|
14
|
+
timestamp.strftime(PATTERN)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(str)
|
18
|
+
dt = DateTime.strptime(str, PATTERN)
|
19
|
+
self.normalize(dt)
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_seconds(s)
|
23
|
+
s / 1.month
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_seconds
|
27
|
+
1.month
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(ts, num)
|
31
|
+
ts + num.months
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize(ts)
|
35
|
+
Time.utc(ts.year, ts.month)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Resolutions
|
3
|
+
module Week
|
4
|
+
include TabsTabs::Resolutionable
|
5
|
+
extend self
|
6
|
+
|
7
|
+
PATTERN = "%Y-%m-%d"
|
8
|
+
|
9
|
+
def name
|
10
|
+
:week
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(timestamp)
|
14
|
+
normalize(timestamp).strftime(PATTERN)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(str)
|
18
|
+
dt = DateTime.strptime(str, PATTERN)
|
19
|
+
self.normalize(dt)
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_seconds(s)
|
23
|
+
s / 1.week
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_seconds
|
27
|
+
1.week
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(ts, num)
|
31
|
+
ts + num.weeks
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize(ts)
|
35
|
+
Time.utc(ts.year, ts.month, ts.day).beginning_of_week
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Resolutions
|
3
|
+
module Year
|
4
|
+
include TabsTabs::Resolutionable
|
5
|
+
extend self
|
6
|
+
|
7
|
+
PATTERN = "%Y"
|
8
|
+
|
9
|
+
def name
|
10
|
+
:year
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(timestamp)
|
14
|
+
timestamp.strftime(PATTERN)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(str)
|
18
|
+
dt = DateTime.strptime(str, PATTERN)
|
19
|
+
self.normalize(dt)
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_seconds(s)
|
23
|
+
s / 1.year
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_seconds
|
27
|
+
1.year
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(ts, num)
|
31
|
+
ts + num.years
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize(ts)
|
35
|
+
Time.utc(ts.year)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module TabsTabs
|
2
|
+
module Storage
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def redis
|
6
|
+
@redis ||= Config.redis
|
7
|
+
end
|
8
|
+
|
9
|
+
def tabs_key(key)
|
10
|
+
if TabsTabs::Config.prefix.blank?
|
11
|
+
"tabstabs:#{key}"
|
12
|
+
else
|
13
|
+
"tabstabs:#{TabsTabs::Config.prefix}:#{key}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def exists(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))
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(key)
|
30
|
+
redis.get(tabs_key(key))
|
31
|
+
end
|
32
|
+
|
33
|
+
def mget(*keys)
|
34
|
+
prefixed_keys = keys.map { |k| tabs_key(k) }
|
35
|
+
redis.mget(*prefixed_keys)
|
36
|
+
end
|
37
|
+
|
38
|
+
def set(key, value)
|
39
|
+
redis.set(tabs_key(key), value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def del(*keys)
|
43
|
+
return 0 if keys.empty?
|
44
|
+
prefixed_keys = keys.map { |k| tabs_key(k) }
|
45
|
+
redis.del(*prefixed_keys)
|
46
|
+
end
|
47
|
+
|
48
|
+
def del_by_prefix(pattern)
|
49
|
+
keys = redis.keys("#{tabs_key(pattern)}*")
|
50
|
+
return 0 if keys.empty?
|
51
|
+
redis.del(*keys)
|
52
|
+
end
|
53
|
+
|
54
|
+
def incr(key)
|
55
|
+
redis.incr(tabs_key(key))
|
56
|
+
end
|
57
|
+
|
58
|
+
def rpush(key, value)
|
59
|
+
redis.rpush(tabs_key(key), value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def sadd(key, *values)
|
63
|
+
redis.sadd(tabs_key(key), *values)
|
64
|
+
end
|
65
|
+
|
66
|
+
def smembers(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
|
74
|
+
end
|
75
|
+
|
76
|
+
def sismember(key, value)
|
77
|
+
redis.sismember(tabs_key(key), value)
|
78
|
+
end
|
79
|
+
|
80
|
+
def hget(key, field)
|
81
|
+
redis.hget(tabs_key(key), field)
|
82
|
+
end
|
83
|
+
|
84
|
+
def hset(key, field, value)
|
85
|
+
redis.hset(tabs_key(key), field, value)
|
86
|
+
end
|
87
|
+
|
88
|
+
def hdel(key, field)
|
89
|
+
redis.hdel(tabs_key(key), field)
|
90
|
+
end
|
91
|
+
|
92
|
+
def hkeys(key)
|
93
|
+
redis.hkeys(tabs_key(key))
|
94
|
+
end
|
95
|
+
|
96
|
+
def hincrby(key, field, value)
|
97
|
+
redis.hincrby(tabs_key(key), field, value)
|
98
|
+
end
|
99
|
+
|
100
|
+
def hgetall(key)
|
101
|
+
redis.hgetall(tabs_key(key))
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|