tabs 0.5.6 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.travis.yml +0 -2
- data/README.md +109 -38
- data/lib/tabs.rb +2 -0
- data/lib/tabs/helpers.rb +10 -5
- data/lib/tabs/metrics/counter.rb +7 -7
- data/lib/tabs/metrics/task.rb +51 -0
- data/lib/tabs/metrics/task/token.rb +80 -0
- data/lib/tabs/metrics/value.rb +5 -5
- data/lib/tabs/resolution.rb +4 -0
- data/lib/tabs/resolutions/day.rb +4 -0
- data/lib/tabs/resolutions/hour.rb +4 -0
- data/lib/tabs/resolutions/minute.rb +4 -0
- data/lib/tabs/resolutions/month.rb +4 -0
- data/lib/tabs/resolutions/week.rb +4 -0
- data/lib/tabs/resolutions/year.rb +4 -0
- data/lib/tabs/storage.rb +4 -0
- data/lib/tabs/tabs.rb +12 -1
- data/lib/tabs/version.rb +1 -1
- data/spec/lib/tabs/metrics/counter_spec.rb +1 -0
- data/spec/lib/tabs/task_spec.rb +48 -0
- data/spec/lib/tabs_spec.rb +3 -1
- data/tabs.gemspec +7 -0
- metadata +18 -24
checksums.yaml
ADDED
@@ -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=
|
data/.travis.yml
CHANGED
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
|
data/lib/tabs.rb
CHANGED
data/lib/tabs/helpers.rb
CHANGED
@@ -2,15 +2,15 @@ module Tabs
|
|
2
2
|
module Helpers
|
3
3
|
extend self
|
4
4
|
|
5
|
-
def timestamp_range(period, resolution
|
5
|
+
def timestamp_range(period, resolution)
|
6
6
|
period = normalize_period(period, resolution)
|
7
7
|
dt = period.first
|
8
|
-
|
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
|
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
|
29
|
-
|
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
|
data/lib/tabs/metrics/counter.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Tabs
|
2
2
|
module Metrics
|
3
3
|
class Counter
|
4
|
-
include
|
5
|
-
include
|
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:
|
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:
|
42
|
-
sadd("stat:
|
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
|
data/lib/tabs/metrics/value.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Tabs
|
2
2
|
module Metrics
|
3
3
|
class Value
|
4
|
-
include
|
5
|
-
include
|
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:
|
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:
|
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)
|
data/lib/tabs/resolution.rb
CHANGED
@@ -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
|
data/lib/tabs/resolutions/day.rb
CHANGED
data/lib/tabs/storage.rb
CHANGED
data/lib/tabs/tabs.rb
CHANGED
@@ -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)
|
data/lib/tabs/version.rb
CHANGED
@@ -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
|
data/spec/lib/tabs_spec.rb
CHANGED
@@ -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:
|
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
|
data/tabs.gemspec
CHANGED
@@ -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
|
-
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-
|
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
|
-
|
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:
|
190
|
+
rubygems_version: 2.0.3
|
198
191
|
signing_key:
|
199
|
-
specification_version:
|
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:
|