tabs 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|