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 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
- ### Breaking Changes in v0.6.0
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
- ### Breaking Changes in v0.8.0
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
- ### Breaking Changes in v0.8.2
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
@@ -7,13 +7,14 @@ require "tabs/config"
7
7
  require "tabs/storage"
8
8
  require "tabs/helpers"
9
9
 
10
- require "tabs/resolution"
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"
@@ -22,5 +22,9 @@ module Tabs
22
22
  @redis ||= Redis.new
23
23
  end
24
24
 
25
+ def register_resolution(resolution, klass)
26
+ Tabs::Resolution.register(resolution, klass)
27
+ end
28
+
25
29
  end
26
30
  end
@@ -12,7 +12,7 @@ module Tabs
12
12
 
13
13
  def increment(timestamp=Time.now)
14
14
  timestamp.utc
15
- Tabs::RESOLUTIONS.each do |resolution|
15
+ Tabs::Resolution.all.each do |resolution|
16
16
  increment_resolution(resolution, timestamp)
17
17
  end
18
18
  increment_total
@@ -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 & completed_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.inject(&:+)) / matching_tokens.size
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 < String
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
- super(token)
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", self)
17
- Tabs::RESOLUTIONS.each { |res| record_start(res, start_time) }
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", self)
23
- raise UnstartedTaskMetricError.new("No task for metric '#{key}' was started with token '#{self}'")
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::RESOLUTIONS.each { |res| record_complete(res, complete_time) }
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}", self)
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}", self)
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}:#{self}:started_time", timestamp)
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
- @start_time ||= Time.parse(get("stat:task:#{key}:#{self}:started_time"))
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}:#{self}:completed_time", timestamp)
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
- @complete_time ||= Time.parse(get("stat:task:#{key}:#{self}:completed_time"))
68
+ Time.parse(get("stat:task:#{key}:#{token}:completed_time"))
60
69
  end
61
70
 
62
71
  end
@@ -12,7 +12,7 @@ module Tabs
12
12
 
13
13
  def record(value, timestamp=Time.now)
14
14
  timestamp.utc
15
- Tabs::RESOLUTIONS.each do |resolution|
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)
@@ -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
- "Tabs::Resolutions::#{resolution.to_s.classify}".constantize
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
@@ -1,6 +1,7 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Day
4
+ extend Resolutionable
4
5
  extend self
5
6
 
6
7
  PATTERN = "%Y-%m-%d"
@@ -1,6 +1,7 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Hour
4
+ extend Tabs::Resolutionable
4
5
  extend self
5
6
 
6
7
  PATTERN = "%Y-%m-%d-%H"
@@ -1,6 +1,7 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Minute
4
+ extend Tabs::Resolutionable
4
5
  extend self
5
6
 
6
7
  PATTERN = "%Y-%m-%d-%H-%M"
@@ -1,6 +1,7 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Month
4
+ extend Tabs::Resolutionable
4
5
  extend self
5
6
 
6
7
  PATTERN = "%Y-%m"
@@ -1,6 +1,7 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Week
4
+ extend Tabs::Resolutionable
4
5
  extend self
5
6
 
6
7
  PATTERN = "%Y-%W"
@@ -1,6 +1,7 @@
1
1
  module Tabs
2
2
  module Resolutions
3
3
  module Year
4
+ extend Tabs::Resolutionable
4
5
  extend self
5
6
 
6
7
  PATTERN = "%Y"
@@ -9,8 +9,6 @@ module Tabs
9
9
 
10
10
  METRIC_TYPES = ["counter", "value", "task"]
11
11
 
12
- RESOLUTIONS = [:minute, :hour, :day, :week, :month, :year]
13
-
14
12
  def configure
15
13
  yield(Config)
16
14
  end
@@ -1,3 +1,3 @@
1
1
  module Tabs
2
- VERSION = "0.8.2"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -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::RESOLUTIONS.map do |res|
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::RESOLUTIONS.each do |res|
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::RESOLUTIONS.map do |res|
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::RESOLUTIONS.each do |res|
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
@@ -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) { "2gd7672gjh3" }
8
- let(:token_2) { "17985jh34gj" }
9
- let(:token_3) { "27f98fhgh1x" }
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
@@ -42,7 +42,6 @@ continue to use v0.8.1 or lower if that is an issue.
42
42
  EOS
43
43
 
44
44
  gem.add_dependency "activesupport", ">= 3.2"
45
- gem.add_dependency "json", ">= 1.7"
46
45
  gem.add_dependency "redis", "~> 3.0.0"
47
46
 
48
47
  gem.add_development_dependency "fakeredis"
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.8.2
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-02 00:00:00.000000000 Z
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: