tabs 0.5.6 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZGVkZDU2ZjE2NzVjMDFiNjRjZDVmZWQyODQzNmE0MzZmMWE3MzRkZA==
5
+ data.tar.gz: !binary |-
6
+ ZThhYTI3ODI5ZGNhM2M5ZmU2MWE4YTg0YzljOTQ3NDIyNDg1Njc4YQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MDJjNzY3YzM5MTAyYmJkOWUyYWMwYmNhYmU0YWY2YjcwYWJmMjQ2ZWRiZGZj
10
+ MDU0MjU2MzJiMTE5NzNlMDcwMzY0YmI2MTJmNzg5YzdmMmIyMjdkMjg5NjAz
11
+ ZDJiMjExMjNhNGI2YjY3MWJmNjg0ODJjODk3Mjc0ZjJhM2QxYjc=
12
+ data.tar.gz: !binary |-
13
+ M2U3MDczNmZhNjJhYzE5MDA2NzlkMzliYjQ0MzBmOWUxNmVmNDU0Zjc0MWRk
14
+ ZWNiNDVmOTEzNDdjMzQ3YWRlMzRkNGI2ZTk3MWYyNmYwYTk5MjMyYzVkNDQ3
15
+ OTRlZjA1ZTIzYzY0ZjQ5ZTljNjQ2MGZiYTkwMTkwYmVhNzZmMDg=
@@ -1,5 +1,3 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
- - jruby-19mode # JRuby in 1.9 mode
5
- - rbx-19mode
data/README.md CHANGED
@@ -18,6 +18,14 @@ Or install it yourself as:
18
18
 
19
19
  $ gem install tabs
20
20
 
21
+ ## Breaking Changes in v0.6.0
22
+
23
+ Please note that when the library version went from 0.5.6 to 0.6.0 some of
24
+ the key patterns used to store metrics in Redis were changed. If you upgrade
25
+ an app to 0.6.0 the previous set of data will not be picked up by tabs.
26
+ Please us 0.6.0 on new applications only. However, the 'Task' metric
27
+ type will only be available in v0.6.0 and above.
28
+
21
29
  ## Usage
22
30
 
23
31
  Metrics come in two flavors: counters and values.
@@ -26,34 +34,42 @@ Metrics come in two flavors: counters and values.
26
34
 
27
35
  A counter metric simply records the number of events that occur within a given timeframe. To create a counter metric called ‘website-visits’, simply call:
28
36
 
29
- Tabs.create_metric(“website-visits”, “counter”)
37
+ ```ruby
38
+ Tabs.create_metric("website-visits", "counter")
39
+ ```
30
40
 
31
41
  Tabs will also create a counter metric automatically the first time you
32
42
  increment the counter.
33
43
 
34
44
  To increment a metric counter, simply call:
35
45
 
36
- Tabs.increment_counter(“website-visits”)
46
+ ```ruby
47
+ Tabs.increment_counter("website-visits")
48
+ ```
37
49
 
38
50
  To retrieve the counts for a given time period just call `Tabs#get_stats` with the name of the metric, a range of times defining the period for which you want stats, and the resolution at which the data should be aggregated.
39
51
 
40
- Tabs.get_stats(“website-visits”, (Time.now - 10.days)..Time.now, :hour)
52
+ ```ruby
53
+ Tabs.get_stats("website-visits", (Time.now - 10.days)..Time.now, :hour)
54
+ ```
41
55
 
42
56
  This will return stats for the last 10 days by hour as an array of hashes in which the keys are an instance of `Time` and the value is the count for that time.
43
57
 
44
- [
45
- { 2000-01-01 00:00:00 UTC => 1 },
46
- { 2000-01-01 01:00:00 UTC => 0 },
47
- { 2000-01-01 02:00:00 UTC => 10 },
48
- { 2000-01-01 03:00:00 UTC => 1 },
49
- { 2000-01-01 04:00:00 UTC => 0 },
50
- { 2000-01-01 05:00:00 UTC => 0 },
51
- { 2000-01-01 06:00:00 UTC => 3 },
52
- { 2000-01-01 07:00:00 UTC => 0 },
53
- ...
54
- ]
58
+ ```ruby
59
+ [
60
+ { 2000-01-01 00:00:00 UTC => 1 },
61
+ { 2000-01-01 01:00:00 UTC => 0 },
62
+ { 2000-01-01 02:00:00 UTC => 10 },
63
+ { 2000-01-01 03:00:00 UTC => 1 },
64
+ { 2000-01-01 04:00:00 UTC => 0 },
65
+ { 2000-01-01 05:00:00 UTC => 0 },
66
+ { 2000-01-01 06:00:00 UTC => 3 },
67
+ { 2000-01-01 07:00:00 UTC => 0 },
68
+ ...
69
+ ]
70
+ ```
55
71
 
56
- Times for the given period in which no events occurred will be filled in with a zero value to make visualizations easier.
72
+ Times for the given period in which no events occurred will be "filled in" with a zero value to make visualizations easier.
57
73
 
58
74
  The `Time` keys are also normalized. For example, in hour resolution, the minutes and seconds of the `Time` object are set to 00:00. Likewise for the week resolution, the day is set to the first day of the week.
59
75
 
@@ -63,29 +79,74 @@ Value metrics take a value and record the min, max, avg, and sum for a given tim
63
79
 
64
80
  To record a value, simply call `Tabs#record_value`.
65
81
 
66
- Tabs.record_value(“new-user-age”, 32)
82
+ ```ruby
83
+ Tabs.record_value("new-user-age", 32)
84
+ ```
67
85
 
68
86
  This will also create a value metric the first time, you can manually create
69
87
  a metric as well:
70
88
 
71
- Tabs.create_metric("new-user-age", "value")
89
+ ```ruby
90
+ Tabs.create_metric("new-user-age", "value")
91
+ ```
72
92
 
73
93
  Retrieving the stats for a value metric is just like retrieving a counter metric.
74
94
 
75
- Tabs.get_stats(“new-user-age”, (Time.now - 6.months)..Time.now, :month)
95
+ ```ruby
96
+ Tabs.get_stats("new-user-age", (Time.now - 6.months)..Time.now, :month)
97
+ ```
76
98
 
77
99
  This will return a familiar value, but with an expanded set of values.
78
100
 
79
- [
80
- { 2000-01-01 00:00:00 UTC => { min: 19, max: 54, sum: 226, avg: 38 } },
81
- { 2000-02-01 01:00:00 UTC => { min: 0, max: 0, sum: 0, avg: 0 } },
82
- { 2000-03-01 02:00:00 UTC => { min: 22, max: 34, sum: 180, avg: 26 } },
83
- ...
84
- ]
101
+ ```ruby
102
+ [
103
+ { 2000-01-01 00:00:00 UTC => { min: 19, max: 54, sum: 226, avg: 38 } },
104
+ { 2000-02-01 01:00:00 UTC => { min: 0, max: 0, sum: 0, avg: 0 } },
105
+ { 2000-03-01 02:00:00 UTC => { min: 22, max: 34, sum: 180, avg: 26 } },
106
+ ...
107
+ ]
108
+ ```
109
+
110
+ ### Task Metrics
111
+
112
+ Task metrics allow you to track the beginning and ending of a process.
113
+ For example, tracking a user who downloads you mobile application and
114
+ later visits your website to make a purchase.
115
+
116
+ ```ruby
117
+ Tabs.start_task("mobile-to-purchase", "2g4hj17787s")
118
+ ```
119
+
120
+ The first argument is the metric key and the second is a unique token
121
+ used to identify the given process. You can use any string for the
122
+ token but it needs to be unique. Use the `complete_task` method to
123
+ finish the task:
124
+
125
+ ```ruby
126
+ Tabs.complete_task("mobile-to-purchase", "2g4hj17787s")
127
+ ```
128
+
129
+ Retrieving stats for a task metric is just like the other types:
130
+
131
+ ```ruby
132
+ Tabs.get_stats("mobile-to-purchase", (Time.now - 6.hours)..Time.now), : minute)
133
+ ```
134
+
135
+ This will return a hash like this:
136
+
137
+ ```ruby
138
+ {
139
+ started: 3, #=> number of items started within the period
140
+ completed: 2, #=> number of items completed within the period
141
+ completed_within_period: 2, #=> number started AND completed within the period
142
+ completion_rate: 0.18, #=> rate of completion
143
+ average_completion_time: 1.5 #=> average completion time in the specified resolution
144
+ }
145
+ ```
85
146
 
86
147
  ### Resolutions
87
148
 
88
- When tabs increments a counter or records a value it does so for each of the following resolutions”. You may supply any of these as the last argument to the `Tabs#get_stats` method.
149
+ When tabs increments a counter or records a value it does so for each of the following "resolutions". You may supply any of these as the last argument to the `Tabs#get_stats` method.
89
150
 
90
151
  :minute, :hour, :day, :week, :month, :year
91
152
 
@@ -95,22 +156,30 @@ It automatically aggregates multiple events for the same period. For instance w
95
156
 
96
157
  You can list all metrics using `list_metrics`:
97
158
 
98
- Tabs.list_metrics #=> ["website-visits", "new-user-age"]
159
+ ```ruby
160
+ Tabs.list_metrics #=> ["website-visits", "new-user-age"]
161
+ ```
99
162
 
100
163
  You can check a metric's type (counter of value) by calling
101
164
  `metric_type`:
102
165
 
103
- Tabs.metric_type("website-visits") #=> "counter"
166
+ ```ruby
167
+ Tabs.metric_type("website-visits") #=> "counter"
168
+ ```
104
169
 
105
170
  And you can quickly check if a metric exists:
106
171
 
107
- Tabs.metric_exists?("foobar") #=> false
172
+ ```ruby
173
+ Tabs.metric_exists?("foobar") #=> false
174
+ ```
108
175
 
109
176
  ### Drop a Metric
110
177
 
111
178
  To drop a metric, just call `Tabs#drop_metric`
112
179
 
113
- Tabs.drop_metric(“website-visits”)
180
+ ```ruby
181
+ Tabs.drop_metric("website-visits")
182
+ ```
114
183
 
115
184
  This will drop all recorded values for the metric so it may not be un-done...be careful.
116
185
 
@@ -118,15 +187,17 @@ This will drop all recorded values for the metric so it may not be un-done...be
118
187
 
119
188
  There really isn’t much to configure with Tabs, it just works out of the box. You can use the following configure block to set the Redis connection instance that Tabs will use.
120
189
 
121
- Tabs.configure do |config|
122
-
123
- # set it to an existing connection
124
- config.redis = Redis.current
125
-
126
- # pass a config hash that will be passed to Redis.new
127
- config.redis = { :host => 'localhost', :port => 6379 }
128
-
129
- end
190
+ ```ruby
191
+ Tabs.configure do |config|
192
+
193
+ # set it to an existing connection
194
+ config.redis = Redis.current
195
+
196
+ # pass a config hash that will be passed to Redis.new
197
+ config.redis = { :host => 'localhost', :port => 6379 }
198
+
199
+ end
200
+ ```
130
201
 
131
202
  ## Contributing
132
203
 
@@ -17,5 +17,7 @@ require "tabs/resolutions/year"
17
17
 
18
18
  require "tabs/metrics/counter"
19
19
  require "tabs/metrics/value"
20
+ require "tabs/metrics/task"
21
+ require "tabs/metrics/task/token"
20
22
 
21
23
  require "tabs/tabs"
@@ -2,15 +2,15 @@ module Tabs
2
2
  module Helpers
3
3
  extend self
4
4
 
5
- def timestamp_range(period, resolution, default_value=0)
5
+ def timestamp_range(period, resolution)
6
6
  period = normalize_period(period, resolution)
7
7
  dt = period.first
8
- Hash[([].tap do |arr|
8
+ [].tap do |arr|
9
9
  arr << dt
10
10
  while (dt = dt + 1.send(resolution)) <= period.last
11
11
  arr << dt.utc
12
12
  end
13
- end).map { |ts| [ts, default_value] }]
13
+ end
14
14
  end
15
15
 
16
16
  def normalize_period(period, resolution)
@@ -25,10 +25,15 @@ module Tabs
25
25
  end
26
26
 
27
27
  def fill_missing_dates(period, date_value_pairs, resolution, default_value=0)
28
- all_timestamps = timestamp_range(period, resolution, default_value)
29
- merged = all_timestamps.merge(Hash[date_value_pairs])
28
+ all_timestamps = timestamp_range(period, resolution)
29
+ default_value_timestamps = Hash[all_timestamps.map { |t| [t, default_value] }]
30
+ merged = default_value_timestamps.merge(Hash[date_value_pairs])
30
31
  merged.to_a
31
32
  end
32
33
 
34
+ def round_float(f)
35
+ (f*100).round / 100.0
36
+ end
37
+
33
38
  end
34
39
  end
@@ -1,8 +1,8 @@
1
1
  module Tabs
2
2
  module Metrics
3
3
  class Counter
4
- include Tabs::Storage
5
- include Tabs::Helpers
4
+ include Storage
5
+ include Helpers
6
6
 
7
7
  attr_reader :key
8
8
 
@@ -21,7 +21,7 @@ module Tabs
21
21
 
22
22
  def stats(period, resolution)
23
23
  period = normalize_period(period, resolution)
24
- keys = smembers("stat:keys:#{key}:#{resolution}")
24
+ keys = smembers("stat:counter:#{key}:keys:#{resolution}")
25
25
  dates = keys.map { |k| extract_date_from_key(k, resolution) }
26
26
  values = mget(*keys).map(&:to_i)
27
27
  pairs = dates.zip(values)
@@ -31,20 +31,20 @@ module Tabs
31
31
  end
32
32
 
33
33
  def total
34
- get("stat:#{key}:total").to_i
34
+ get("stat:counter:#{key}:total").to_i
35
35
  end
36
36
 
37
37
  private
38
38
 
39
39
  def increment_resolution(resolution, timestamp)
40
40
  formatted_time = Tabs::Resolution.serialize(resolution, timestamp)
41
- stat_key = "stat:value:#{key}:count:#{formatted_time}"
42
- sadd("stat:keys:#{key}:#{resolution}", stat_key)
41
+ stat_key = "stat:counter:#{key}:count:#{formatted_time}"
42
+ sadd("stat:counter:#{key}:keys:#{resolution}", stat_key)
43
43
  incr(stat_key)
44
44
  end
45
45
 
46
46
  def increment_total
47
- incr("stat:#{key}:total")
47
+ incr("stat:counter:#{key}:total")
48
48
  end
49
49
 
50
50
  end
@@ -0,0 +1,51 @@
1
+ module Tabs
2
+ module Metrics
3
+ class Task
4
+ include Tabs::Storage
5
+ include Tabs::Helpers
6
+
7
+ class UnstartedTaskMetricError < Exception; end
8
+
9
+ attr_reader :key
10
+
11
+ def initialize(key)
12
+ @key = key
13
+ end
14
+
15
+ def start(token)
16
+ Token.new(token, key).start
17
+ true
18
+ end
19
+
20
+ def complete(token)
21
+ Token.new(token, key).complete
22
+ true
23
+ end
24
+
25
+ def stats(period, resolution)
26
+ range = timestamp_range(period, resolution)
27
+ started_tokens = tokens_for_period(range, resolution, "started")
28
+ completed_tokens = tokens_for_period(range, resolution, "completed")
29
+ matching_tokens = started_tokens & completed_tokens
30
+ completion_rate = round_float(matching_tokens.size.to_f / range.size)
31
+ elapsed_times = matching_tokens.map { |t| t.time_elapsed(resolution) }
32
+ average_completion_time = (elapsed_times.inject(&:+)) / matching_tokens.size
33
+ {
34
+ started: started_tokens.size,
35
+ completed: completed_tokens.size,
36
+ completed_within_period: matching_tokens.size,
37
+ completion_rate: completion_rate,
38
+ average_completion_time: average_completion_time
39
+ }
40
+ end
41
+
42
+ private
43
+
44
+ def tokens_for_period(range, resolution, type)
45
+ keys = Task::Token.keys_for_range(key, range, resolution, type)
46
+ mget(*keys).compact.map(&:to_a).flatten.map { |t| Token.new(t, key) }
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,80 @@
1
+ module Tabs
2
+ module Metrics
3
+ class Task
4
+ class Token < String
5
+ include Storage
6
+
7
+ def self.keys_for_range(key, range, resolution, type)
8
+ range.map do |date|
9
+ formatted_time = Tabs::Resolution.serialize(resolution, date)
10
+ "stat:task:#{key}:#{type}:#{formatted_time}"
11
+ end
12
+ end
13
+
14
+ attr_reader :key
15
+
16
+ def initialize(token, key)
17
+ @key = key
18
+ super(token)
19
+ end
20
+
21
+ def start_key
22
+ "stat:started:#{key}:#{self}"
23
+ end
24
+
25
+ def end_key
26
+ "stat:completed:#{key}:#{self}"
27
+ end
28
+
29
+ def start
30
+ self.start_time = Time.now.utc
31
+ sadd("stat:task:#{key}:tokens", self)
32
+ Tabs::RESOLUTIONS.each { |res| record_start(res, start_time) }
33
+ end
34
+
35
+ def complete
36
+ unless sismember("stat:task:#{key}:tokens", self)
37
+ raise UnstartedTaskMetricError.new("No task for metric '#{key}' was started with token '#{self}'")
38
+ end
39
+ self.complete_time = Time.now.utc
40
+ Tabs::RESOLUTIONS.each { |res| record_complete(res, complete_time) }
41
+ end
42
+
43
+ def time_elapsed(resolution)
44
+ Tabs::Resolution.from_seconds(resolution, complete_time - start_time)
45
+ end
46
+
47
+ private
48
+
49
+ def record_start(resolution, timestamp)
50
+ formatted_time = Tabs::Resolution.serialize(resolution, timestamp)
51
+ sadd("stat:task:#{key}:started:#{formatted_time}", self)
52
+ end
53
+
54
+ def record_complete(resolution, timestamp)
55
+ formatted_time = Tabs::Resolution.serialize(resolution, timestamp)
56
+ sadd("stat:task:#{key}:completed:#{formatted_time}", self)
57
+ end
58
+
59
+ def start_time=(timestamp)
60
+ set("stat:task:#{key}:#{self}:started_time", timestamp)
61
+ @start_time = timestamp
62
+ end
63
+
64
+ def start_time
65
+ @start_time ||= Time.parse(get("stat:task:#{key}:#{self}:started_time"))
66
+ end
67
+
68
+ def complete_time=(timestamp)
69
+ set("stat:task:#{key}:#{self}:completed_time", timestamp)
70
+ @complete_time = timestamp
71
+ end
72
+
73
+ def complete_time
74
+ @complete_time ||= Time.parse(get("stat:task:#{key}:#{self}:completed_time"))
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,8 +1,8 @@
1
1
  module Tabs
2
2
  module Metrics
3
3
  class Value
4
- include Tabs::Storage
5
- include Tabs::Helpers
4
+ include Storage
5
+ include Helpers
6
6
 
7
7
  attr_reader :key
8
8
 
@@ -14,8 +14,8 @@ module Tabs
14
14
  timestamp = Time.now.utc
15
15
  Tabs::RESOLUTIONS.each do |resolution|
16
16
  formatted_time = Tabs::Resolution.serialize(resolution, timestamp)
17
- stat_key = "stat:value:#{key}:#{formatted_time}"
18
- sadd("stat:keys:#{key}:#{resolution}", stat_key)
17
+ stat_key = "stat:value:#{key}:data:#{formatted_time}"
18
+ sadd("stat:value:#{key}:keys:#{resolution}", stat_key)
19
19
  update_values(stat_key, value)
20
20
  end
21
21
  true
@@ -23,7 +23,7 @@ module Tabs
23
23
 
24
24
  def stats(period, resolution)
25
25
  period = normalize_period(period, resolution)
26
- keys = smembers("stat:keys:#{key}:#{resolution}")
26
+ keys = smembers("stat:value:#{key}:keys:#{resolution}")
27
27
  dates = keys.map { |k| extract_date_from_key(k, resolution) }
28
28
  values = mget(*keys).map { |v| JSON.parse(v) }
29
29
  pairs = dates.zip(values)
@@ -10,6 +10,10 @@ module Tabs
10
10
  resolution_klass(resolution).deserialize(str)
11
11
  end
12
12
 
13
+ def from_seconds(resolution, s)
14
+ resolution_klass(resolution).from_seconds(s)
15
+ end
16
+
13
17
  def normalize(resolution, timestamp)
14
18
  resolution_klass(resolution).normalize(timestamp)
15
19
  end
@@ -14,6 +14,10 @@ module Tabs
14
14
  self.normalize(dt)
15
15
  end
16
16
 
17
+ def from_seconds(s)
18
+ s / 86400
19
+ end
20
+
17
21
  def normalize(ts)
18
22
  Time.utc(ts.year, ts.month, ts.day)
19
23
  end
@@ -14,6 +14,10 @@ module Tabs
14
14
  self.normalize(dt)
15
15
  end
16
16
 
17
+ def from_seconds(s)
18
+ s / 3600
19
+ end
20
+
17
21
  def normalize(ts)
18
22
  Time.utc(ts.year, ts.month, ts.day, ts.hour)
19
23
  end
@@ -14,6 +14,10 @@ module Tabs
14
14
  self.normalize(dt)
15
15
  end
16
16
 
17
+ def from_seconds(s)
18
+ s / 60.0
19
+ end
20
+
17
21
  def normalize(ts)
18
22
  Time.utc(ts.year, ts.month, ts.day, ts.hour, ts.min)
19
23
  end
@@ -14,6 +14,10 @@ module Tabs
14
14
  self.normalize(dt)
15
15
  end
16
16
 
17
+ def from_seconds(s)
18
+ s / 1728000
19
+ end
20
+
17
21
  def normalize(ts)
18
22
  Time.utc(ts.year, ts.month)
19
23
  end
@@ -16,6 +16,10 @@ module Tabs
16
16
  self.normalize(dt)
17
17
  end
18
18
 
19
+ def from_seconds(s)
20
+ s / 432000
21
+ end
22
+
19
23
  def normalize(ts)
20
24
  Time.utc(ts.year, ts.month, ts.day).beginning_of_week
21
25
  end
@@ -14,6 +14,10 @@ module Tabs
14
14
  self.normalize(dt)
15
15
  end
16
16
 
17
+ def from_seconds(s)
18
+ s / 31536000
19
+ end
20
+
17
21
  def normalize(ts)
18
22
  Time.utc(ts.year)
19
23
  end
@@ -44,6 +44,10 @@ module Tabs
44
44
  redis.smembers("tabs:#{key}")
45
45
  end
46
46
 
47
+ def sismember(key, value)
48
+ redis.sismember("tabs:#{key}", value)
49
+ end
50
+
47
51
  def hget(key, field)
48
52
  redis.hget("tabs:#{key}", field)
49
53
  end
@@ -7,7 +7,7 @@ module Tabs
7
7
  class UnknownMetricError < Exception; end
8
8
  class MetricTypeMismatchError < Exception; end
9
9
 
10
- METRIC_TYPES = ["counter", "value"]
10
+ METRIC_TYPES = ["counter", "value", "task"]
11
11
 
12
12
  RESOLUTIONS = [:minute, :hour, :day, :week, :month, :year]
13
13
 
@@ -35,6 +35,17 @@ module Tabs
35
35
  get_metric(key).record(value)
36
36
  end
37
37
 
38
+ def start_task(key, token)
39
+ create_metric(key, "task")
40
+ raise MetricTypeMismatchError.new("Only task metrics can start a task") unless metric_type(key) == "task"
41
+ get_metric(key).start(token)
42
+ end
43
+
44
+ def complete_task(key, token)
45
+ raise MetricTypeMismatchError.new("Only task metrics can complete a task") unless metric_type(key) == "task"
46
+ get_metric(key).complete(token)
47
+ end
48
+
38
49
  def create_metric(key, type)
39
50
  raise UnknownTypeError.new("Unknown metric type: #{type}") unless METRIC_TYPES.include?(type)
40
51
  raise DuplicateMetricError.new("Metric already exists: #{key}") if metric_exists?(key)
@@ -1,3 +1,3 @@
1
1
  module Tabs
2
- VERSION = "0.5.6"
2
+ VERSION = "0.6.1"
3
3
  end
@@ -14,6 +14,7 @@ describe Tabs::Metrics::Counter do
14
14
  stats = metric.stats(((now - 2.hours)..(now + 4.hours)), :hour)
15
15
  expect(stats).to include({ time => 1 })
16
16
  end
17
+
17
18
  end
18
19
 
19
20
  describe "total count" do
@@ -0,0 +1,48 @@
1
+ require "spec_helper"
2
+
3
+ describe Tabs::Metrics::Task do
4
+
5
+ let(:metric) { Tabs.create_metric("foo", "task") }
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" }
10
+
11
+ describe ".start" do
12
+
13
+ end
14
+
15
+ describe ".complete" do
16
+
17
+ it "raises an UnstartedTaskMetricError if the metric has not yet been started" do
18
+ lambda { metric.complete("foobar") }.should raise_error(Tabs::Metrics::Task::UnstartedTaskMetricError)
19
+ end
20
+
21
+ end
22
+
23
+ describe ".stats" do
24
+
25
+ it "returns the expected value" do
26
+ Timecop.freeze(now)
27
+ metric.start(token_1)
28
+ metric.start(token_2)
29
+ Timecop.freeze(now + 2.minutes)
30
+ metric.complete(token_1)
31
+ metric.start(token_3)
32
+ Timecop.freeze(now + 3.minutes)
33
+ metric.complete(token_3)
34
+ stats = metric.stats((now - 5.minutes)..(now + 5.minutes), :minute)
35
+ expect(stats).to eq(
36
+ {
37
+ started: 3,
38
+ completed: 2,
39
+ completed_within_period: 2,
40
+ completion_rate: 0.18,
41
+ average_completion_time: 1.5
42
+ }
43
+ )
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -25,8 +25,10 @@ describe Tabs do
25
25
  it "adds the metric's key to the list_metrics" do
26
26
  Tabs.create_metric("foo", "value")
27
27
  Tabs.create_metric("bar", "counter")
28
+ Tabs.create_metric("baz", "task")
28
29
  expect(Tabs.list_metrics).to include("foo")
29
30
  expect(Tabs.list_metrics).to include("bar")
31
+ expect(Tabs.list_metrics).to include("baz")
30
32
  end
31
33
 
32
34
  end
@@ -78,7 +80,7 @@ describe Tabs do
78
80
  it "removes the metrics values from redis" do
79
81
  Tabs.increment_counter("foo")
80
82
  keys = smembers("tabs:stat:keys:foo:hour")
81
- expect(redis.keys).to include("tabs:stat:keys:foo:hour")
83
+ expect(redis.keys).to include("tabs:stat:counter:foo:keys:hour")
82
84
  Tabs.drop_metric("foo")
83
85
  expect(redis.keys).to_not include(keys[0])
84
86
  end
@@ -18,6 +18,13 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
+ gem.post_install_message = <<TXT
22
+ **** NOTICE ****
23
+ Please note there are breaking changes in this version of tabs!
24
+ Existing data cannot be read because of changes to the redis key patterns.
25
+ Please continue to use version 0.5.6 if you need to access existing metric data."
26
+ TXT
27
+
21
28
  gem.add_dependency "activesupport", ">= 3.2.12"
22
29
  gem.add_dependency "json", "~> 1.7.7"
23
30
  gem.add_dependency "redis", "~> 3.0.2"
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tabs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.6
5
- prerelease:
4
+ version: 0.6.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - JC Grubbs
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-03-09 00:00:00.000000000 Z
11
+ date: 2013-03-17 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: activesupport
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ! '>='
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ! '>='
28
25
  - !ruby/object:Gem::Version
@@ -30,7 +27,6 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: json
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ~>
36
32
  - !ruby/object:Gem::Version
@@ -38,7 +34,6 @@ dependencies:
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ~>
44
39
  - !ruby/object:Gem::Version
@@ -46,7 +41,6 @@ dependencies:
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: redis
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - ~>
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - ~>
60
53
  - !ruby/object:Gem::Version
@@ -62,7 +55,6 @@ dependencies:
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: fakeredis
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
59
  - - ! '>='
68
60
  - !ruby/object:Gem::Version
@@ -70,7 +62,6 @@ dependencies:
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
66
  - - ! '>='
76
67
  - !ruby/object:Gem::Version
@@ -78,7 +69,6 @@ dependencies:
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: pry
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
73
  - - ! '>='
84
74
  - !ruby/object:Gem::Version
@@ -86,7 +76,6 @@ dependencies:
86
76
  type: :development
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
80
  - - ! '>='
92
81
  - !ruby/object:Gem::Version
@@ -94,7 +83,6 @@ dependencies:
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: rake
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
87
  - - ! '>='
100
88
  - !ruby/object:Gem::Version
@@ -102,7 +90,6 @@ dependencies:
102
90
  type: :development
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
94
  - - ! '>='
108
95
  - !ruby/object:Gem::Version
@@ -110,7 +97,6 @@ dependencies:
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: rspec
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
101
  - - ! '>='
116
102
  - !ruby/object:Gem::Version
@@ -118,7 +104,6 @@ dependencies:
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
108
  - - ! '>='
124
109
  - !ruby/object:Gem::Version
@@ -126,7 +111,6 @@ dependencies:
126
111
  - !ruby/object:Gem::Dependency
127
112
  name: timecop
128
113
  requirement: !ruby/object:Gem::Requirement
129
- none: false
130
114
  requirements:
131
115
  - - ! '>='
132
116
  - !ruby/object:Gem::Version
@@ -134,7 +118,6 @@ dependencies:
134
118
  type: :development
135
119
  prerelease: false
136
120
  version_requirements: !ruby/object:Gem::Requirement
137
- none: false
138
121
  requirements:
139
122
  - - ! '>='
140
123
  - !ruby/object:Gem::Version
@@ -158,6 +141,8 @@ files:
158
141
  - lib/tabs/config.rb
159
142
  - lib/tabs/helpers.rb
160
143
  - lib/tabs/metrics/counter.rb
144
+ - lib/tabs/metrics/task.rb
145
+ - lib/tabs/metrics/task/token.rb
161
146
  - lib/tabs/metrics/value.rb
162
147
  - lib/tabs/resolution.rb
163
148
  - lib/tabs/resolutions/day.rb
@@ -171,36 +156,45 @@ files:
171
156
  - lib/tabs/version.rb
172
157
  - spec/lib/tabs/metrics/counter_spec.rb
173
158
  - spec/lib/tabs/metrics/value_spec.rb
159
+ - spec/lib/tabs/task_spec.rb
174
160
  - spec/lib/tabs_spec.rb
175
161
  - spec/spec_helper.rb
176
162
  - tabs.gemspec
177
163
  homepage: https://github.com/thegrubbsian/tabs
178
164
  licenses: []
179
- post_install_message:
165
+ metadata: {}
166
+ post_install_message: ! '**** NOTICE ****
167
+
168
+ Please note there are breaking changes in this version of tabs!
169
+
170
+ Existing data cannot be read because of changes to the redis key patterns.
171
+
172
+ Please continue to use version 0.5.6 if you need to access existing metric data."
173
+
174
+ '
180
175
  rdoc_options: []
181
176
  require_paths:
182
177
  - lib
183
178
  required_ruby_version: !ruby/object:Gem::Requirement
184
- none: false
185
179
  requirements:
186
180
  - - ! '>='
187
181
  - !ruby/object:Gem::Version
188
182
  version: '0'
189
183
  required_rubygems_version: !ruby/object:Gem::Requirement
190
- none: false
191
184
  requirements:
192
185
  - - ! '>='
193
186
  - !ruby/object:Gem::Version
194
187
  version: '0'
195
188
  requirements: []
196
189
  rubyforge_project:
197
- rubygems_version: 1.8.23
190
+ rubygems_version: 2.0.3
198
191
  signing_key:
199
- specification_version: 3
192
+ specification_version: 4
200
193
  summary: A redis-backed metrics tracker for keeping tabs on pretty much anything ;)
201
194
  test_files:
202
195
  - spec/lib/tabs/metrics/counter_spec.rb
203
196
  - spec/lib/tabs/metrics/value_spec.rb
197
+ - spec/lib/tabs/task_spec.rb
204
198
  - spec/lib/tabs_spec.rb
205
199
  - spec/spec_helper.rb
206
200
  has_rdoc: