tabs 0.8.2 → 0.9.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.
- data/README.md +67 -3
- data/lib/tabs.rb +2 -1
- data/lib/tabs/config.rb +4 -0
- data/lib/tabs/metrics/counter.rb +1 -1
- data/lib/tabs/metrics/task.rb +2 -2
- data/lib/tabs/metrics/task/token.rb +22 -13
- data/lib/tabs/metrics/value.rb +1 -1
- data/lib/tabs/resolution.rb +17 -1
- data/lib/tabs/resolutionable.rb +23 -0
- data/lib/tabs/resolutions/day.rb +1 -0
- data/lib/tabs/resolutions/hour.rb +1 -0
- data/lib/tabs/resolutions/minute.rb +1 -0
- data/lib/tabs/resolutions/month.rb +1 -0
- data/lib/tabs/resolutions/week.rb +1 -0
- data/lib/tabs/resolutions/year.rb +1 -0
- data/lib/tabs/tabs.rb +0 -2
- data/lib/tabs/version.rb +1 -1
- data/spec/lib/tabs/metrics/counter_spec.rb +2 -2
- data/spec/lib/tabs/metrics/value_spec.rb +2 -2
- data/spec/lib/tabs/resolution_spec.rb +29 -0
- data/spec/lib/tabs/task_spec.rb +3 -3
- data/spec/support/custom_resolutions.rb +28 -0
- data/tabs.gemspec +0 -1
- metadata +7 -18
data/README.md
CHANGED
@@ -201,6 +201,67 @@ When tabs increments a counter or records a value it does so for each of the fol
|
|
201
201
|
|
202
202
|
It automatically aggregates multiple events for the same period. For instance when you increment a counter metric, 1 will be added for each of the resolutions for the current time. Repeating the event 5 minutes later will increment a different minute slot, but the same hour, date, week, etc. When you retrieve metrics, all timestamps will be in UTC.
|
203
203
|
|
204
|
+
#### Custom Resolutions
|
205
|
+
|
206
|
+
If the built-in resolutions above don't work you can add your own. All
|
207
|
+
that's necessary is a module that conforms to the following protocol:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
module SecondResolution
|
211
|
+
extend Tabs::Resolutionable
|
212
|
+
extend self
|
213
|
+
|
214
|
+
PATTERN = "%Y-%m-%d-%H-%M-%S"
|
215
|
+
|
216
|
+
def serialize(timestamp)
|
217
|
+
timestamp.strftime(PATTERN)
|
218
|
+
end
|
219
|
+
|
220
|
+
def deserialize(str)
|
221
|
+
dt = DateTime.strptime(str, PATTERN)
|
222
|
+
self.normalize(dt)
|
223
|
+
end
|
224
|
+
|
225
|
+
def from_seconds(s)
|
226
|
+
s / 1
|
227
|
+
end
|
228
|
+
|
229
|
+
def normalize(ts)
|
230
|
+
Time.utc(ts.year, ts.month, ts.day, ts.hour, ts.min, ts.sec)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
```
|
235
|
+
|
236
|
+
A little description on each of the above methods:
|
237
|
+
|
238
|
+
*`serialize`*: converts the timestamp to a string. The return value
|
239
|
+
here will be used as part of the Redis key storing values associated
|
240
|
+
with a given metric.
|
241
|
+
|
242
|
+
*`deserialize`*: converts the string representation of a timestamp back
|
243
|
+
into an actual DateTime value.
|
244
|
+
|
245
|
+
*`from_seconds`*: should return the number of periods in the given
|
246
|
+
number of seconds. For example, there are 60 seconds in a minute.
|
247
|
+
|
248
|
+
*`normalize`*: should simply return the first timestamp for the period.
|
249
|
+
For example, the week resolution returns the first hour of the first day
|
250
|
+
of the week.
|
251
|
+
|
252
|
+
*NOTE: If you're doing a custom resolution you should probably look into
|
253
|
+
the code a bit.*
|
254
|
+
|
255
|
+
Once you have a module that conforms to the resolution protocol you need
|
256
|
+
to register it with Tabs. You can do this in one of two ways:
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
# This call can be anywhere before you start using tabs
|
260
|
+
Tabs::Resolution.register(:second, SecondResolution)
|
261
|
+
|
262
|
+
# or, you can use the config block described below
|
263
|
+
```
|
264
|
+
|
204
265
|
### Inspecting Metrics
|
205
266
|
|
206
267
|
You can list all metrics using `list_metrics`:
|
@@ -255,12 +316,15 @@ Tabs.configure do |config|
|
|
255
316
|
# affects stat averages and task completion rate
|
256
317
|
config.decimal_precision = 2
|
257
318
|
|
319
|
+
# registers a custom resolution
|
320
|
+
config.register_resolution :second, SecondResolution
|
321
|
+
|
258
322
|
end
|
259
323
|
```
|
260
324
|
|
261
325
|
## Breaking Changes
|
262
326
|
|
263
|
-
###
|
327
|
+
### v0.6.0
|
264
328
|
|
265
329
|
Please note that when the library version went from v0.5.6 to v0.6.0 some of
|
266
330
|
the key patterns used to store metrics in Redis were changed. If you upgrade
|
@@ -268,7 +332,7 @@ an app to v0.6.0 the previous set of data will not be picked up by tabs.
|
|
268
332
|
Please use v0.6.x on new applications only. However, the 'Task' metric
|
269
333
|
type will only be available in v0.6.0 and above.
|
270
334
|
|
271
|
-
###
|
335
|
+
### v0.8.0
|
272
336
|
|
273
337
|
In version 0.8.0 and higher the get_stats method returns a more robust
|
274
338
|
object instead of just an array of hashes. These stats objects are
|
@@ -276,7 +340,7 @@ enumerable and most existing code utilizing tabs should continue to
|
|
276
340
|
function. However, please review the docs for more information if you
|
277
341
|
encounter issues when upgrading.
|
278
342
|
|
279
|
-
###
|
343
|
+
### v0.8.2
|
280
344
|
|
281
345
|
In version 0.8.2 and higher the storage keys for value metrics have been
|
282
346
|
changed. Originally the various pieces (avg, sum, count, etc) were
|
data/lib/tabs.rb
CHANGED
@@ -7,13 +7,14 @@ require "tabs/config"
|
|
7
7
|
require "tabs/storage"
|
8
8
|
require "tabs/helpers"
|
9
9
|
|
10
|
-
require "tabs/
|
10
|
+
require "tabs/resolutionable"
|
11
11
|
require "tabs/resolutions/minute"
|
12
12
|
require "tabs/resolutions/hour"
|
13
13
|
require "tabs/resolutions/day"
|
14
14
|
require "tabs/resolutions/week"
|
15
15
|
require "tabs/resolutions/month"
|
16
16
|
require "tabs/resolutions/year"
|
17
|
+
require "tabs/resolution"
|
17
18
|
|
18
19
|
require "tabs/metrics/counter/stats"
|
19
20
|
require "tabs/metrics/counter"
|
data/lib/tabs/config.rb
CHANGED
data/lib/tabs/metrics/counter.rb
CHANGED
data/lib/tabs/metrics/task.rb
CHANGED
@@ -34,10 +34,10 @@ module Tabs
|
|
34
34
|
range = timestamp_range(period, resolution)
|
35
35
|
started_tokens = tokens_for_period(range, resolution, "started")
|
36
36
|
completed_tokens = tokens_for_period(range, resolution, "completed")
|
37
|
-
matching_tokens = started_tokens
|
37
|
+
matching_tokens = started_tokens.select { |token| completed_tokens.include? token }
|
38
38
|
completion_rate = (matching_tokens.size.to_f / range.size).round(Config.decimal_precision)
|
39
39
|
elapsed_times = matching_tokens.map { |t| t.time_elapsed(resolution) }
|
40
|
-
average_completion_time = (elapsed_times.
|
40
|
+
average_completion_time = (elapsed_times.sum) / matching_tokens.size
|
41
41
|
Stats.new(
|
42
42
|
started_tokens.size,
|
43
43
|
completed_tokens.size,
|
@@ -1,62 +1,71 @@
|
|
1
1
|
module Tabs
|
2
2
|
module Metrics
|
3
3
|
class Task
|
4
|
-
class Token
|
4
|
+
class Token
|
5
5
|
include Storage
|
6
6
|
|
7
7
|
attr_reader :key
|
8
|
+
attr_reader :token
|
8
9
|
|
9
10
|
def initialize(token, key)
|
10
11
|
@key = key
|
11
|
-
|
12
|
+
@token = token
|
12
13
|
end
|
13
14
|
|
14
15
|
def start(timestamp=Time.now)
|
15
16
|
self.start_time = timestamp.utc
|
16
|
-
sadd("stat:task:#{key}:tokens",
|
17
|
-
Tabs::
|
17
|
+
sadd("stat:task:#{key}:tokens", token)
|
18
|
+
Tabs::Resolution.all.each { |res| record_start(res, start_time) }
|
18
19
|
end
|
19
20
|
|
20
21
|
def complete(timestamp=Time.now)
|
21
22
|
self.complete_time = timestamp.utc
|
22
|
-
unless sismember("stat:task:#{key}:tokens",
|
23
|
-
raise UnstartedTaskMetricError.new("No task for metric '#{key}' was started with token '#{
|
23
|
+
unless sismember("stat:task:#{key}:tokens", token)
|
24
|
+
raise UnstartedTaskMetricError.new("No task for metric '#{key}' was started with token '#{token}'")
|
24
25
|
end
|
25
|
-
Tabs::
|
26
|
+
Tabs::Resolution.all.each { |res| record_complete(res, complete_time) }
|
26
27
|
end
|
27
28
|
|
28
29
|
def time_elapsed(resolution)
|
29
30
|
Tabs::Resolution.from_seconds(resolution, complete_time - start_time)
|
30
31
|
end
|
31
32
|
|
33
|
+
def ==(other_token)
|
34
|
+
self.token == other_token.token
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"#{super}:#{token}"
|
39
|
+
end
|
40
|
+
|
32
41
|
private
|
33
42
|
|
34
43
|
def record_start(resolution, timestamp)
|
35
44
|
formatted_time = Tabs::Resolution.serialize(resolution, timestamp)
|
36
|
-
sadd("stat:task:#{key}:started:#{formatted_time}",
|
45
|
+
sadd("stat:task:#{key}:started:#{formatted_time}", token)
|
37
46
|
end
|
38
47
|
|
39
48
|
def record_complete(resolution, timestamp)
|
40
49
|
formatted_time = Tabs::Resolution.serialize(resolution, timestamp)
|
41
|
-
sadd("stat:task:#{key}:completed:#{formatted_time}",
|
50
|
+
sadd("stat:task:#{key}:completed:#{formatted_time}", token)
|
42
51
|
end
|
43
52
|
|
44
53
|
def start_time=(timestamp)
|
45
|
-
set("stat:task:#{key}:#{
|
54
|
+
set("stat:task:#{key}:#{token}:started_time", timestamp)
|
46
55
|
@start_time = timestamp
|
47
56
|
end
|
48
57
|
|
49
58
|
def start_time
|
50
|
-
|
59
|
+
Time.parse(get("stat:task:#{key}:#{token}:started_time"))
|
51
60
|
end
|
52
61
|
|
53
62
|
def complete_time=(timestamp)
|
54
|
-
set("stat:task:#{key}:#{
|
63
|
+
set("stat:task:#{key}:#{token}:completed_time", timestamp)
|
55
64
|
@complete_time = timestamp
|
56
65
|
end
|
57
66
|
|
58
67
|
def complete_time
|
59
|
-
|
68
|
+
Time.parse(get("stat:task:#{key}:#{token}:completed_time"))
|
60
69
|
end
|
61
70
|
|
62
71
|
end
|
data/lib/tabs/metrics/value.rb
CHANGED
@@ -12,7 +12,7 @@ module Tabs
|
|
12
12
|
|
13
13
|
def record(value, timestamp=Time.now)
|
14
14
|
timestamp.utc
|
15
|
-
Tabs::
|
15
|
+
Tabs::Resolution.all.each do |resolution|
|
16
16
|
formatted_time = Tabs::Resolution.serialize(resolution, timestamp)
|
17
17
|
stat_key = "stat:value:#{key}:data:#{formatted_time}"
|
18
18
|
update_values(stat_key, value)
|
data/lib/tabs/resolution.rb
CHANGED
@@ -2,6 +2,11 @@ module Tabs
|
|
2
2
|
module Resolution
|
3
3
|
extend self
|
4
4
|
|
5
|
+
def register(resolution, klass)
|
6
|
+
@@resolution_classes ||= {}
|
7
|
+
@@resolution_classes[resolution] = klass
|
8
|
+
end
|
9
|
+
|
5
10
|
def serialize(resolution, timestamp)
|
6
11
|
resolution_klass(resolution).serialize(timestamp)
|
7
12
|
end
|
@@ -18,11 +23,22 @@ module Tabs
|
|
18
23
|
resolution_klass(resolution).normalize(timestamp)
|
19
24
|
end
|
20
25
|
|
26
|
+
def all
|
27
|
+
@@resolution_classes.keys
|
28
|
+
end
|
29
|
+
|
21
30
|
private
|
22
31
|
|
23
32
|
def resolution_klass(resolution)
|
24
|
-
|
33
|
+
@@resolution_classes[resolution]
|
25
34
|
end
|
26
35
|
|
27
36
|
end
|
28
37
|
end
|
38
|
+
|
39
|
+
Tabs::Resolution.register(:minute, Tabs::Resolutions::Minute)
|
40
|
+
Tabs::Resolution.register(:hour, Tabs::Resolutions::Hour)
|
41
|
+
Tabs::Resolution.register(:day, Tabs::Resolutions::Day)
|
42
|
+
Tabs::Resolution.register(:week, Tabs::Resolutions::Week)
|
43
|
+
Tabs::Resolution.register(:month, Tabs::Resolutions::Month)
|
44
|
+
Tabs::Resolution.register(:year, Tabs::Resolutions::Year)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Tabs
|
2
|
+
module Resolutionable
|
3
|
+
extend self
|
4
|
+
|
5
|
+
class MethodNotImplementedException < Exception; end
|
6
|
+
|
7
|
+
def serialize
|
8
|
+
raise MethodNotImplementedException
|
9
|
+
end
|
10
|
+
|
11
|
+
def deserialize
|
12
|
+
raise MethodNotImplementedException
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_seconds
|
16
|
+
raise MethodNotImplementedException
|
17
|
+
end
|
18
|
+
|
19
|
+
def normalize
|
20
|
+
raise MethodNotImplementedException
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/tabs/resolutions/day.rb
CHANGED
data/lib/tabs/tabs.rb
CHANGED
data/lib/tabs/version.rb
CHANGED
@@ -126,7 +126,7 @@ describe Tabs::Metrics::Counter do
|
|
126
126
|
before do
|
127
127
|
3.times { metric.increment }
|
128
128
|
expect(exists("stat:counter:foo:total")).to be_true
|
129
|
-
@count_keys = (Tabs::
|
129
|
+
@count_keys = (Tabs::Resolution.all.map do |res|
|
130
130
|
smembers("stat:counter:foo:keys:#{res}")
|
131
131
|
end).flatten
|
132
132
|
metric.drop!
|
@@ -143,7 +143,7 @@ describe Tabs::Metrics::Counter do
|
|
143
143
|
end
|
144
144
|
|
145
145
|
it "deletes all resolution key collection keys" do
|
146
|
-
Tabs::
|
146
|
+
Tabs::Resolution.all.each do |res|
|
147
147
|
expect(exists("stat:counter:foo:keys:#{res}")).to be_false
|
148
148
|
end
|
149
149
|
end
|
@@ -101,7 +101,7 @@ describe Tabs::Metrics::Value do
|
|
101
101
|
|
102
102
|
before do
|
103
103
|
3.times { metric.record(rand(30)) }
|
104
|
-
@count_keys = (Tabs::
|
104
|
+
@count_keys = (Tabs::Resolution.all.map do |res|
|
105
105
|
smembers("stat:value:foo:keys:#{res}")
|
106
106
|
end).flatten
|
107
107
|
metric.drop!
|
@@ -114,7 +114,7 @@ describe Tabs::Metrics::Value do
|
|
114
114
|
end
|
115
115
|
|
116
116
|
it "deletes all resolution key collection keys" do
|
117
|
-
Tabs::
|
117
|
+
Tabs::Resolution.all.each do |res|
|
118
118
|
expect(exists("stat:value:foo:keys:#{res}")).to be_false
|
119
119
|
end
|
120
120
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require File.expand_path('../../../support/custom_resolutions', __FILE__)
|
3
|
+
|
4
|
+
describe Tabs::Resolution do
|
5
|
+
describe '#register' do
|
6
|
+
it 'registers a new resolution' do
|
7
|
+
Tabs::Resolution.register(:test, Tabs::Resolutions::Minute)
|
8
|
+
expect(Tabs::Resolution.all).to include :test
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'with a custom resolution' do
|
12
|
+
it 'does not return nil' do
|
13
|
+
expect(WellFormedResolution.serialize(Time.now)).to_not be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'gets stats for custom resolution' do
|
17
|
+
Tabs::Resolution.register(:seconds, WellFormedResolution)
|
18
|
+
Timecop.freeze(Time.now)
|
19
|
+
|
20
|
+
Tabs.increment_counter('foo')
|
21
|
+
expect(Tabs.get_stats('foo', (Time.now - 5.seconds..Time.now), :seconds).values.size).to eq(6)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'raises an error when method not implemented' do
|
25
|
+
expect{BadlyFormedResolution.normalize}.to raise_error Tabs::Resolutionable::MethodNotImplementedException
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/lib/tabs/task_spec.rb
CHANGED
@@ -4,9 +4,9 @@ describe Tabs::Metrics::Task do
|
|
4
4
|
|
5
5
|
let(:metric) { Tabs.create_metric("foo", "task") }
|
6
6
|
let(:now) { Time.utc(2000, 1, 1, 0, 0) }
|
7
|
-
let(:token_1) { "
|
8
|
-
let(:token_2) { "
|
9
|
-
let(:token_3) { "
|
7
|
+
let(:token_1) { "aaaa" }
|
8
|
+
let(:token_2) { "bbbb" }
|
9
|
+
let(:token_3) { "cccc" }
|
10
10
|
|
11
11
|
describe ".start" do
|
12
12
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BadlyFormedResolution
|
2
|
+
extend Tabs::Resolutionable
|
3
|
+
extend self
|
4
|
+
end
|
5
|
+
|
6
|
+
module WellFormedResolution
|
7
|
+
extend Tabs::Resolutionable
|
8
|
+
extend self
|
9
|
+
|
10
|
+
PATTERN = "%Y-%m-%d-%H-%M-%S"
|
11
|
+
|
12
|
+
def serialize(timestamp)
|
13
|
+
timestamp.strftime(PATTERN)
|
14
|
+
end
|
15
|
+
|
16
|
+
def deserialize(str)
|
17
|
+
dt = DateTime.strptime(str, PATTERN)
|
18
|
+
self.normalize(dt)
|
19
|
+
end
|
20
|
+
|
21
|
+
def from_seconds(s)
|
22
|
+
s / 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def normalize(ts)
|
26
|
+
Time.utc(ts.year, ts.month, ts.day, ts.hour, ts.min, ts.sec)
|
27
|
+
end
|
28
|
+
end
|
data/tabs.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tabs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -27,22 +27,6 @@ dependencies:
|
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '3.2'
|
30
|
-
- !ruby/object:Gem::Dependency
|
31
|
-
name: json
|
32
|
-
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
|
-
requirements:
|
35
|
-
- - ! '>='
|
36
|
-
- !ruby/object:Gem::Version
|
37
|
-
version: '1.7'
|
38
|
-
type: :runtime
|
39
|
-
prerelease: false
|
40
|
-
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
|
-
requirements:
|
43
|
-
- - ! '>='
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version: '1.7'
|
46
30
|
- !ruby/object:Gem::Dependency
|
47
31
|
name: redis
|
48
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +148,7 @@ files:
|
|
164
148
|
- lib/tabs/metrics/value.rb
|
165
149
|
- lib/tabs/metrics/value/stats.rb
|
166
150
|
- lib/tabs/resolution.rb
|
151
|
+
- lib/tabs/resolutionable.rb
|
167
152
|
- lib/tabs/resolutions/day.rb
|
168
153
|
- lib/tabs/resolutions/hour.rb
|
169
154
|
- lib/tabs/resolutions/minute.rb
|
@@ -178,9 +163,11 @@ files:
|
|
178
163
|
- spec/lib/tabs/metrics/task/token_spec.rb
|
179
164
|
- spec/lib/tabs/metrics/value/stats_spec.rb
|
180
165
|
- spec/lib/tabs/metrics/value_spec.rb
|
166
|
+
- spec/lib/tabs/resolution_spec.rb
|
181
167
|
- spec/lib/tabs/task_spec.rb
|
182
168
|
- spec/lib/tabs_spec.rb
|
183
169
|
- spec/spec_helper.rb
|
170
|
+
- spec/support/custom_resolutions.rb
|
184
171
|
- tabs.gemspec
|
185
172
|
homepage: https://github.com/thegrubbsian/tabs
|
186
173
|
licenses: []
|
@@ -251,7 +238,9 @@ test_files:
|
|
251
238
|
- spec/lib/tabs/metrics/task/token_spec.rb
|
252
239
|
- spec/lib/tabs/metrics/value/stats_spec.rb
|
253
240
|
- spec/lib/tabs/metrics/value_spec.rb
|
241
|
+
- spec/lib/tabs/resolution_spec.rb
|
254
242
|
- spec/lib/tabs/task_spec.rb
|
255
243
|
- spec/lib/tabs_spec.rb
|
256
244
|
- spec/spec_helper.rb
|
245
|
+
- spec/support/custom_resolutions.rb
|
257
246
|
has_rdoc:
|