librato-rack 0.4.5 → 0.5.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +4 -0
- data.tar.gz.sig +0 -0
- data/README.md +20 -2
- data/lib/librato/collector.rb +7 -4
- data/lib/librato/collector/aggregator.rb +69 -5
- data/lib/librato/collector/counter_cache.rb +4 -4
- data/lib/librato/collector/exceptions.rb +8 -0
- data/lib/librato/rack.rb +3 -3
- data/lib/librato/rack/tracker.rb +11 -4
- data/lib/librato/rack/version.rb +1 -1
- data/test/remote/tracker_test.rb +66 -55
- data/test/unit/collector/aggregator_test.rb +78 -1
- metadata +56 -59
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7be4fbb8ab1659249f7bf95cd6cd3f77726ebda5
|
4
|
+
data.tar.gz: 18b5b1cc87d44a10b1512ae6c1884ea018b1e92c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c5956f91aee12213650b9736bf209af4d7d4f8c6dfadd2e21d9b7d1dcb54d67747c8ce08d0d1f6e2226a12f7bc4657f3d9003bd92e3849d93ab94d2099de8098
|
7
|
+
data.tar.gz: 784ba024019aa150bcf3509182333c2b5e48b55291d1056a26f38322b4e414aeedc1242d03d5c3a0d1bdc4a0cebbae1d606a35060c0af6d55515a964ee1f275b
|
checksums.yaml.gz.sig
ADDED
data.tar.gz.sig
CHANGED
Binary file
|
data/README.md
CHANGED
@@ -64,7 +64,7 @@ If you want to do more complex configuration, use your own environment variables
|
|
64
64
|
|
65
65
|
use Librato::Rack, :config => config
|
66
66
|
|
67
|
-
See the configuration class for all available options.
|
67
|
+
See the [configuration class](https://github.com/librato/librato-rack/blob/master/lib/librato/rack/configuration.rb) for all available options.
|
68
68
|
|
69
69
|
##### Running on Heroku
|
70
70
|
|
@@ -131,6 +131,22 @@ The block form auto-submits the time it took for its contents to execute as the
|
|
131
131
|
@twitter = Twitter.lookup(user)
|
132
132
|
end
|
133
133
|
|
134
|
+
###### percentiles (beta)
|
135
|
+
|
136
|
+
By defaults timings will send the average, sum, max and min for every minute. If you want to send percentiles as well you can specify them inline while instrumenting:
|
137
|
+
|
138
|
+
# track a single percentile
|
139
|
+
Librato.timing 'api.request.time', time, percentile: 95
|
140
|
+
|
141
|
+
# track multiple percentiles
|
142
|
+
Librato.timing 'api.request.time', time, percentile: [95, 99]
|
143
|
+
|
144
|
+
You can also use percentiles with the block form of timings:
|
145
|
+
|
146
|
+
Librato.timing 'my.important.event', percentile: 95 do
|
147
|
+
# do work
|
148
|
+
end
|
149
|
+
|
134
150
|
#### group
|
135
151
|
|
136
152
|
There is also a grouping helper, to make managing nested metrics easier. So this:
|
@@ -155,6 +171,8 @@ Symbols can be used interchangeably with strings for metric names.
|
|
155
171
|
|
156
172
|
Never fear, [we have some guidelines](https://github.com/librato/librato-rails/wiki/Monitoring-Background-Workers) for how to instrument your workers properly.
|
157
173
|
|
174
|
+
If you are using `librato-rack` with sidekiq, [see these notes about setup](https://github.com/librato/librato-rails/wiki/Monitoring-Background-Workers#monitoring-long-running-threaded-workers-sidekiq-etc).
|
175
|
+
|
158
176
|
## Cross-Process Aggregation
|
159
177
|
|
160
178
|
`librato-rack` submits measurements back to the Librato platform on a _per-process_ basis. By default these measurements are then combined into a single measurement per source (default is your hostname) before persisting the data.
|
@@ -185,4 +203,4 @@ If you are debugging setup locally you can set `flush_interval` to something sho
|
|
185
203
|
|
186
204
|
## Copyright
|
187
205
|
|
188
|
-
Copyright (c) 2013 [Librato Inc.](http://librato.com) See LICENSE for details.
|
206
|
+
Copyright (c) 2013-2014 [Librato Inc.](http://librato.com) See LICENSE for details.
|
data/lib/librato/collector.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module Librato
|
2
4
|
# collects and stores measurement values over time so they can be
|
3
5
|
# reported periodically to the Metrics service
|
@@ -10,7 +12,7 @@ module Librato
|
|
10
12
|
|
11
13
|
# access to internal aggregator object
|
12
14
|
def aggregate
|
13
|
-
@aggregator_cache ||= Aggregator.new(:
|
15
|
+
@aggregator_cache ||= Aggregator.new(prefix: @prefix)
|
14
16
|
end
|
15
17
|
|
16
18
|
# access to internal counters object
|
@@ -43,6 +45,7 @@ module Librato
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
require_relative 'collector/aggregator'
|
49
|
+
require_relative 'collector/counter_cache'
|
50
|
+
require_relative 'collector/exceptions'
|
51
|
+
require_relative 'collector/group'
|
@@ -1,14 +1,19 @@
|
|
1
|
+
require 'hetchy'
|
2
|
+
|
1
3
|
module Librato
|
2
4
|
class Collector
|
3
5
|
# maintains storage of timing and measurement type measurements
|
4
6
|
#
|
5
7
|
class Aggregator
|
8
|
+
SOURCE_SEPARATOR = '$$'
|
9
|
+
|
6
10
|
extend Forwardable
|
7
11
|
|
8
12
|
def_delegators :@cache, :empty?, :prefix, :prefix=
|
9
13
|
|
10
14
|
def initialize(options={})
|
11
|
-
@cache = Librato::Metrics::Aggregator.new(:
|
15
|
+
@cache = Librato::Metrics::Aggregator.new(prefix: options[:prefix])
|
16
|
+
@percentiles = {}
|
12
17
|
@lock = Mutex.new
|
13
18
|
end
|
14
19
|
|
@@ -16,8 +21,11 @@ module Librato
|
|
16
21
|
fetch(key)
|
17
22
|
end
|
18
23
|
|
24
|
+
# retrieve current value of a metric/source/percentage. this exists
|
25
|
+
# primarily for debugging/testing and isn't called routinely.
|
19
26
|
def fetch(key, options={})
|
20
27
|
return nil if @cache.empty?
|
28
|
+
return fetch_percentile(key, options) if options[:percentile]
|
21
29
|
gauges = nil
|
22
30
|
source = options[:source]
|
23
31
|
@lock.synchronize { gauges = @cache.queued[:gauges] }
|
@@ -30,17 +38,19 @@ module Librato
|
|
30
38
|
nil
|
31
39
|
end
|
32
40
|
|
41
|
+
# clear all stored values
|
33
42
|
def delete_all
|
34
|
-
@lock.synchronize {
|
43
|
+
@lock.synchronize { clear_storage }
|
35
44
|
end
|
36
45
|
|
37
46
|
# transfer all measurements to queue and reset internal status
|
38
|
-
def flush_to(queue)
|
47
|
+
def flush_to(queue, opts={})
|
39
48
|
queued = nil
|
40
49
|
@lock.synchronize do
|
41
50
|
return if @cache.empty?
|
42
51
|
queued = @cache.queued
|
43
|
-
@
|
52
|
+
flush_percentiles(queue, opts) unless @percentiles.empty?
|
53
|
+
clear_storage unless opts[:preserve]
|
44
54
|
end
|
45
55
|
queue.merge!(queued) if queued
|
46
56
|
end
|
@@ -80,18 +90,72 @@ module Librato
|
|
80
90
|
options = args[-1]
|
81
91
|
end
|
82
92
|
source = options[:source]
|
93
|
+
percentiles = Array(options[:percentile])
|
83
94
|
|
84
95
|
@lock.synchronize do
|
85
96
|
if source
|
86
|
-
@cache.add event => {:
|
97
|
+
@cache.add event => {source: source, value: value}
|
87
98
|
else
|
88
99
|
@cache.add event => value
|
89
100
|
end
|
101
|
+
|
102
|
+
percentiles.each do |perc|
|
103
|
+
store = fetch_percentile_store(event, source)
|
104
|
+
store[:reservoir] << value
|
105
|
+
track_percentile(store, perc)
|
106
|
+
end
|
90
107
|
end
|
91
108
|
returned
|
92
109
|
end
|
93
110
|
alias :timing :measure
|
94
111
|
|
112
|
+
private
|
113
|
+
|
114
|
+
def clear_storage
|
115
|
+
@cache.clear
|
116
|
+
@percentiles.each do |key, val|
|
117
|
+
val[:reservoir].clear
|
118
|
+
val[:percs].clear
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def fetch_percentile(key, options)
|
123
|
+
store = fetch_percentile_store(key, options[:source])
|
124
|
+
return nil unless store
|
125
|
+
store[:reservoir].percentile(options[:percentile])
|
126
|
+
end
|
127
|
+
|
128
|
+
def fetch_percentile_store(event, source)
|
129
|
+
keyname = source ? "#{event}#{SOURCE_SEPARATOR}#{source}" : event
|
130
|
+
@percentiles[keyname] ||= {
|
131
|
+
reservoir: Hetchy::Reservoir.new(size: 1000),
|
132
|
+
percs: Set.new
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def flush_percentiles(queue, opts)
|
137
|
+
@percentiles.each do |key, val|
|
138
|
+
metric, source = key.split(SOURCE_SEPARATOR)
|
139
|
+
val[:percs].each do |perc|
|
140
|
+
perc_name = perc.to_s[0,5].gsub('.','')
|
141
|
+
payload = if source
|
142
|
+
{ value: val[:reservoir].percentile(perc), source: source }
|
143
|
+
else
|
144
|
+
val[:reservoir].percentile(perc)
|
145
|
+
end
|
146
|
+
queue.add "#{metric}.p#{perc_name}" => payload
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def track_percentile(store, perc)
|
152
|
+
if perc < 0.0 || perc > 100.0
|
153
|
+
raise InvalidPercentile, "Percentiles must be between 0.0 and 100.0"
|
154
|
+
end
|
155
|
+
store[:percs].add(perc)
|
156
|
+
end
|
157
|
+
|
95
158
|
end
|
159
|
+
|
96
160
|
end
|
97
161
|
end
|
@@ -44,18 +44,18 @@ module Librato
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# transfer all measurements to queue and reset internal status
|
47
|
-
def flush_to(queue)
|
47
|
+
def flush_to(queue, opts={})
|
48
48
|
counts = nil
|
49
49
|
@lock.synchronize do
|
50
50
|
# work off of a duplicate data set so we block for
|
51
51
|
# as little time as possible
|
52
52
|
counts = @cache.dup
|
53
|
-
reset_cache
|
53
|
+
reset_cache unless opts[:preserve]
|
54
54
|
end
|
55
55
|
counts.each do |metric, value|
|
56
56
|
metric, source = metric.split(SEPARATOR)
|
57
57
|
if source
|
58
|
-
queue.add metric => {:
|
58
|
+
queue.add metric => {value: value, source: source}
|
59
59
|
else
|
60
60
|
queue.add metric => value
|
61
61
|
end
|
@@ -76,7 +76,7 @@ module Librato
|
|
76
76
|
def increment(counter, options={})
|
77
77
|
if options.is_a?(Fixnum)
|
78
78
|
# suppport legacy style
|
79
|
-
options = {:
|
79
|
+
options = {by: options}
|
80
80
|
end
|
81
81
|
by = options[:by] || 1
|
82
82
|
if options[:source]
|
data/lib/librato/rack.rb
CHANGED
@@ -107,10 +107,10 @@ module Librato
|
|
107
107
|
case queue_start.length
|
108
108
|
when 16 # microseconds
|
109
109
|
wait = ((Time.now.to_f * 1000000).to_i - queue_start.to_i) / 1000.0
|
110
|
-
tracker.timing 'rack.request.queue.time', wait
|
110
|
+
tracker.timing 'rack.request.queue.time', wait, percentile: 95
|
111
111
|
when 13 # milliseconds
|
112
112
|
wait = (Time.now.to_f * 1000).to_i - queue_start.to_i
|
113
|
-
tracker.timing 'rack.request.queue.time', wait
|
113
|
+
tracker.timing 'rack.request.queue.time', wait, percentile: 95
|
114
114
|
end
|
115
115
|
end
|
116
116
|
end
|
@@ -119,7 +119,7 @@ module Librato
|
|
119
119
|
return if config.disable_rack_metrics
|
120
120
|
tracker.group 'rack.request' do |group|
|
121
121
|
group.increment 'total'
|
122
|
-
group.timing 'time', duration
|
122
|
+
group.timing 'time', duration, percentile: 95
|
123
123
|
group.increment 'slow' if duration > 200.0
|
124
124
|
|
125
125
|
group.group 'status' do |s|
|
data/lib/librato/rack/tracker.rb
CHANGED
@@ -64,6 +64,13 @@ module Librato
|
|
64
64
|
config.source_pids ? "#{source}.#{$$}" : source
|
65
65
|
end
|
66
66
|
|
67
|
+
# current local instrumentation to be sent on next flush
|
68
|
+
# this is for debugging, don't call rapidly in production as it
|
69
|
+
# may introduce latency
|
70
|
+
def queued
|
71
|
+
build_flush_queue(collector, true).queued
|
72
|
+
end
|
73
|
+
|
67
74
|
# given current state, should the tracker start a reporter thread?
|
68
75
|
def should_start?
|
69
76
|
if !config.user || !config.token
|
@@ -113,11 +120,11 @@ module Librato
|
|
113
120
|
end
|
114
121
|
end
|
115
122
|
|
116
|
-
def build_flush_queue(collector)
|
117
|
-
queue = ValidatingQueue.new( :
|
118
|
-
:
|
123
|
+
def build_flush_queue(collector, preserve=false)
|
124
|
+
queue = ValidatingQueue.new( client: client, source: qualified_source,
|
125
|
+
prefix: config.prefix, skip_measurement_times: true )
|
119
126
|
[collector.counters, collector.aggregate].each do |cache|
|
120
|
-
cache.flush_to(queue)
|
127
|
+
cache.flush_to(queue, preserve: preserve)
|
121
128
|
end
|
122
129
|
queue.add 'rack.processes' => 1
|
123
130
|
trace_queued(queue.queued) #if should_log?(:trace)
|
data/lib/librato/rack/version.rb
CHANGED
data/test/remote/tracker_test.rb
CHANGED
@@ -27,32 +27,32 @@ class TrackerRemoteTest < Minitest::Test
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def test_flush_counters
|
30
|
-
tracker.increment :foo
|
31
|
-
tracker.increment :bar, 2
|
32
|
-
tracker.increment :foo
|
33
|
-
tracker.increment :foo, :
|
30
|
+
tracker.increment :foo # simple
|
31
|
+
tracker.increment :bar, 2 # specified
|
32
|
+
tracker.increment :foo # multincrement
|
33
|
+
tracker.increment :foo, source: 'baz', by: 3 # custom source
|
34
|
+
@queued = tracker.queued
|
34
35
|
tracker.flush
|
35
36
|
|
37
|
+
# metrics are SSA, so should exist but won't have measurements yet
|
36
38
|
metric_names = client.list.map { |m| m['name'] }
|
37
39
|
assert metric_names.include?('foo'), 'foo should be present'
|
38
40
|
assert metric_names.include?('bar'), 'bar should be present'
|
39
41
|
|
40
|
-
|
41
|
-
assert_equal
|
42
|
-
assert_equal 2, foo
|
42
|
+
# interogate queued payload for expected values
|
43
|
+
assert_equal source, @queued[:source]
|
44
|
+
assert_equal 2, queued('foo')
|
43
45
|
|
44
46
|
# custom source
|
45
|
-
assert_equal
|
46
|
-
assert_equal 3, foo['baz'][0]['value']
|
47
|
+
assert_equal 3, queued('foo', source: 'baz')
|
47
48
|
|
48
|
-
|
49
|
-
assert_equal
|
50
|
-
assert_equal 2, bar[source][0]['value']
|
49
|
+
# different counter
|
50
|
+
assert_equal 2, queued('bar')
|
51
51
|
end
|
52
52
|
|
53
53
|
def test_counter_persistent_through_flush
|
54
54
|
tracker.increment 'knightrider'
|
55
|
-
tracker.increment 'badguys', :
|
55
|
+
tracker.increment 'badguys', sporadic: true
|
56
56
|
assert_equal 1, collector.counters['knightrider']
|
57
57
|
assert_equal 1, collector.counters['badguys']
|
58
58
|
|
@@ -65,24 +65,23 @@ class TrackerRemoteTest < Minitest::Test
|
|
65
65
|
tracker.timing 'request.time.total', 122.1
|
66
66
|
tracker.measure 'items_bought', 20
|
67
67
|
tracker.timing 'request.time.total', 81.3
|
68
|
-
tracker.timing 'jobs.queued', 5, :
|
68
|
+
tracker.timing 'jobs.queued', 5, source: 'worker.3'
|
69
|
+
@queued = tracker.queued
|
69
70
|
tracker.flush
|
70
71
|
|
72
|
+
# metrics are SSA, so should exist but won't have measurements yet
|
71
73
|
metric_names = client.list.map { |m| m['name'] }
|
72
74
|
assert metric_names.include?('request.time.total'), 'request.time.total should be present'
|
73
75
|
assert metric_names.include?('items_bought'), 'request.time.db should be present'
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
-
assert_in_delta 203.4, total[source][0]['sum'], 0.1
|
77
|
+
assert_equal 2, queued('request.time.total')[:count]
|
78
|
+
assert_in_delta 203.4, queued('request.time.total')[:sum], 0.1
|
78
79
|
|
79
|
-
|
80
|
-
|
81
|
-
assert_in_delta 20, items[source][0]['sum'], 0.1
|
80
|
+
assert_equal 1, queued('items_bought')[:count]
|
81
|
+
assert_in_delta 20, queued('items_bought')[:sum], 0.1
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
assert_in_delta 5, jobs['worker.3'][0]['sum'], 0.1
|
83
|
+
assert_equal 1, queued('jobs.queued', source: 'worker.3')[:count]
|
84
|
+
assert_in_delta 5, queued('jobs.queued', source: 'worker.3')[:sum], 0.1
|
86
85
|
end
|
87
86
|
|
88
87
|
def test_flush_should_purge_measures_and_timings
|
@@ -90,77 +89,70 @@ class TrackerRemoteTest < Minitest::Test
|
|
90
89
|
tracker.measure 'items_bought', 20
|
91
90
|
tracker.flush
|
92
91
|
|
93
|
-
assert collector.aggregate.empty?,
|
92
|
+
assert collector.aggregate.empty?,
|
93
|
+
'measures and timings should be cleared with flush'
|
94
94
|
end
|
95
95
|
|
96
|
-
# Disabled for now because we always send a running process
|
97
|
-
# count at a minimum
|
98
|
-
#
|
99
|
-
# def test_empty_flush_should_not_be_sent
|
100
|
-
# tracker.flush
|
101
|
-
# assert_equal [], client.list
|
102
|
-
# end
|
103
|
-
|
104
96
|
def test_flush_respects_prefix
|
105
97
|
config.prefix = 'testyprefix'
|
106
98
|
|
107
99
|
tracker.timing 'mytime', 221.1
|
108
100
|
tracker.increment 'mycount', 4
|
101
|
+
@queued = tracker.queued
|
109
102
|
tracker.flush
|
110
103
|
|
111
104
|
metric_names = client.list.map { |m| m['name'] }
|
112
|
-
assert metric_names.include?('testyprefix.mytime'),
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
assert_equal 1, mytime[source][0]['count']
|
105
|
+
assert metric_names.include?('testyprefix.mytime'),
|
106
|
+
'testyprefix.mytime should be present'
|
107
|
+
assert metric_names.include?('testyprefix.mycount'), '
|
108
|
+
testyprefix.mycount should be present'
|
117
109
|
|
118
|
-
|
119
|
-
assert_equal 4, mycount
|
110
|
+
assert_equal 1, queued('testyprefix.mytime')[:count]
|
111
|
+
assert_equal 4, queued('testyprefix.mycount')
|
120
112
|
end
|
121
113
|
|
122
114
|
def test_flush_recovers_from_failure
|
123
115
|
# create a metric foo of counter type
|
124
|
-
client.submit :
|
116
|
+
client.submit foo: {type: :counter, value: 12}
|
125
117
|
|
126
118
|
# failing flush - submit a foo measurement as a gauge (type mismatch)
|
127
119
|
tracker.measure :foo, 2.12
|
128
|
-
tracker.flush
|
129
120
|
|
130
|
-
|
131
|
-
|
132
|
-
assert_nil foo[source] # shouldn't have been accepted
|
121
|
+
# won't be accepted
|
122
|
+
tracker.flush
|
133
123
|
|
134
124
|
tracker.measure :boo, 2.12
|
135
125
|
tracker.flush
|
136
126
|
|
137
|
-
|
138
|
-
|
127
|
+
metric_names = client.list.map { |m| m['name'] }
|
128
|
+
assert metric_names.include?('boo')
|
139
129
|
end
|
140
130
|
|
141
131
|
def test_flush_handles_invalid_metric_names
|
142
132
|
tracker.increment :foo # valid
|
143
133
|
tracker.increment 'fübar' # invalid
|
144
134
|
tracker.measure 'fu/bar/baz', 12.1 # invalid
|
135
|
+
@queued = tracker.queued
|
145
136
|
tracker.flush
|
146
137
|
|
147
138
|
metric_names = client.list.map { |m| m['name'] }
|
148
139
|
assert metric_names.include?('foo')
|
149
140
|
|
150
|
-
# should
|
151
|
-
|
152
|
-
assert_equal 1.0, foo[source][0]["value"]
|
141
|
+
# should be sending value for foo
|
142
|
+
assert_equal 1.0, queued('foo')
|
153
143
|
end
|
154
144
|
|
155
145
|
def test_flush_handles_invalid_sources_names
|
156
|
-
tracker.increment :foo, :
|
157
|
-
tracker.increment :bar, :
|
158
|
-
tracker.measure 'baz', 2.25, :
|
146
|
+
tracker.increment :foo, source: 'atreides' # valid
|
147
|
+
tracker.increment :bar, source: 'glébnöst' # invalid
|
148
|
+
tracker.measure 'baz', 2.25, source: 'b/l/ak/nok' # invalid
|
149
|
+
@queued = tracker.queued
|
159
150
|
tracker.flush
|
160
151
|
|
161
|
-
|
162
|
-
|
163
|
-
|
152
|
+
metric_names = client.list.map { |m| m['name'] }
|
153
|
+
assert metric_names.include?('foo')
|
154
|
+
|
155
|
+
assert_equal 1.0, queued('foo', source: 'atreides')
|
164
156
|
end
|
165
157
|
|
166
158
|
private
|
@@ -181,6 +173,25 @@ class TrackerRemoteTest < Minitest::Test
|
|
181
173
|
@tracker.config
|
182
174
|
end
|
183
175
|
|
176
|
+
# wrapper to make api format more easy to query
|
177
|
+
def queued(name, opts={})
|
178
|
+
raise "No queued found" unless @queued
|
179
|
+
source = opts[:source] || nil
|
180
|
+
|
181
|
+
@queued[:gauges].each do |g|
|
182
|
+
if g[:name] == name.to_s && g[:source] == source
|
183
|
+
if g[:count]
|
184
|
+
# complex metric, return the whole hash
|
185
|
+
return g
|
186
|
+
else
|
187
|
+
# return just the value
|
188
|
+
return g[:value]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
raise "No queued entry with '#{name}' found."
|
193
|
+
end
|
194
|
+
|
184
195
|
def source
|
185
196
|
@tracker.qualified_source
|
186
197
|
end
|
@@ -27,6 +27,52 @@ module Librato
|
|
27
27
|
assert_in_delta @agg['another.task'][:sum], 100, 50
|
28
28
|
end
|
29
29
|
|
30
|
+
def test_percentiles
|
31
|
+
# simple case
|
32
|
+
[0.1, 0.2, 0.3].each do |val|
|
33
|
+
@agg.timing 'a.sample.thing', val, percentile: 50
|
34
|
+
end
|
35
|
+
assert_equal 0.2, @agg.fetch('a.sample.thing', percentile: 50),
|
36
|
+
'can calculate percentile'
|
37
|
+
|
38
|
+
# multiple percentiles
|
39
|
+
[0.2, 0.35].each do |val|
|
40
|
+
@agg.timing 'a.sample.thing', val, percentile: [80, 95]
|
41
|
+
end
|
42
|
+
assert_equal 0.31, @agg.fetch('a.sample.thing', percentile: 65),
|
43
|
+
'can calculate another percentile simultaneously'
|
44
|
+
assert_equal 0.35, @agg.fetch('a.sample.thing', percentile: 95),
|
45
|
+
'can calculate another percentile simultaneously'
|
46
|
+
|
47
|
+
# ensure storage is efficient: this is a little gross because we
|
48
|
+
# have to inquire past the public interface, but important to verify
|
49
|
+
assert_equal 1, @agg.instance_variable_get('@percentiles').length,
|
50
|
+
'maintains all samples for same metric/source in one pool'
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_percentiles_invalid
|
54
|
+
# less than 0.0
|
55
|
+
assert_raises(Librato::Collector::InvalidPercentile) {
|
56
|
+
@agg.timing 'a.sample.thing', 123, percentile: -25.5
|
57
|
+
}
|
58
|
+
|
59
|
+
# greater than 100.0
|
60
|
+
assert_raises(Librato::Collector::InvalidPercentile) {
|
61
|
+
@agg.timing 'a.sample.thing', 123, percentile: 100.2
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_percentiles_with_source
|
66
|
+
Array(1..10).each do |val|
|
67
|
+
@agg.timing 'a.sample.thing', val, percentile: 50, source: 'foo'
|
68
|
+
end
|
69
|
+
assert_equal 5.5,
|
70
|
+
@agg.fetch('a.sample.thing', source: 'foo', percentile: 50),
|
71
|
+
'can calculate percentile with source'
|
72
|
+
end
|
73
|
+
|
74
|
+
# Todo: mult percentiles, block form, with source, invalid percentile
|
75
|
+
|
30
76
|
def test_return_values
|
31
77
|
simple = @agg.timing 'simple', 20
|
32
78
|
assert_equal nil, simple
|
@@ -58,12 +104,43 @@ module Librato
|
|
58
104
|
|
59
105
|
q = Librato::Metrics::Queue.new
|
60
106
|
@agg.flush_to(q)
|
107
|
+
|
61
108
|
expected = Set.new([
|
62
109
|
{:name=>"meaning.of.life", :count=>1, :sum=>1.0, :min=>1.0, :max=>1.0},
|
63
|
-
{:name=>"meaning.of.life", :count=>1, :sum=>42.0, :min=>42.0, :max=>42.0, :source=>"douglas_adams"}
|
110
|
+
{:name=>"meaning.of.life", :count=>1, :sum=>42.0, :min=>42.0, :max=>42.0, :source=>"douglas_adams"}
|
111
|
+
])
|
64
112
|
assert_equal expected, Set.new(q.queued[:gauges])
|
65
113
|
end
|
66
114
|
|
115
|
+
def test_flush_percentiles
|
116
|
+
[1,2,3].each { |i| @agg.timing 'a.timing', i, percentile: 95 }
|
117
|
+
[1,2,3].each { |i| @agg.timing 'b.timing', i, source: 'f', percentile: [50, 99.9] }
|
118
|
+
|
119
|
+
q = Librato::Metrics::Queue.new
|
120
|
+
@agg.flush_to(q)
|
121
|
+
|
122
|
+
queued = q.queued[:gauges]
|
123
|
+
a_timing = queued.detect{ |q| q[:name] == 'a.timing.p95' }
|
124
|
+
b_timing_50 = queued.detect{ |q| q[:name] == 'b.timing.p50' }
|
125
|
+
b_timing_999 = queued.detect{ |q| q[:name] == 'b.timing.p999' }
|
126
|
+
|
127
|
+
refute_nil a_timing, 'sending a.timing percentile'
|
128
|
+
refute_nil b_timing_50, 'sending b.timing 50th percentile'
|
129
|
+
refute_nil b_timing_999, 'sending a.timing 99.9th percentile'
|
130
|
+
|
131
|
+
assert_equal 3, a_timing[:value]
|
132
|
+
assert_equal 2, b_timing_50[:value]
|
133
|
+
assert_equal 3, b_timing_999[:value]
|
134
|
+
|
135
|
+
assert_nil a_timing[:source], 'no source set'
|
136
|
+
assert_equal 'f', b_timing_50[:source], 'proper source set'
|
137
|
+
assert_equal 'f', b_timing_999[:source], 'proper source set'
|
138
|
+
|
139
|
+
# flushing clears percentages to track
|
140
|
+
storage = @agg.instance_variable_get('@percentiles')
|
141
|
+
assert_equal 0, storage['a.timing'][:percs].length, 'clears percentiles'
|
142
|
+
end
|
143
|
+
|
67
144
|
end
|
68
145
|
end
|
69
146
|
end
|
metadata
CHANGED
@@ -1,74 +1,77 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: librato-rack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.5.0.beta
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Matt Sanders
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain:
|
12
|
-
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
UVJYamxYUGttVDMvCnhhODU4QkdSanZVNTlXUEUxM1FHaWJhN1lJZUh0UkV2
|
36
|
-
Tng0MkpJZm9KTVY3NG9mcktJdVR3OUNNdG8yZ3o5WHgKS3gxbmNuMDdBK2JK
|
37
|
-
bktaNmhlblFBRjFDSDk2WmNxY0pIMTc5UzJ0SWlLRE04a2VlUklVT1BDM1dU
|
38
|
-
MGZhb2svMgpnQTJvemRyODUxYy9uQT09Ci0tLS0tRU5EIENFUlRJRklDQVRF
|
39
|
-
LS0tLS0K
|
40
|
-
date: 2014-05-13 00:00:00.000000000 Z
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDaDCCAlCgAwIBAgIBATANBgkqhkiG9w0BAQUFADA9MQ0wCwYDVQQDDARtYXR0
|
14
|
+
MRcwFQYKCZImiZPyLGQBGRYHbGlicmF0bzETMBEGCgmSJomT8ixkARkWA2NvbTAe
|
15
|
+
Fw0xNTA5MDMwMDE4MzBaFw0xNjA5MDIwMDE4MzBaMD0xDTALBgNVBAMMBG1hdHQx
|
16
|
+
FzAVBgoJkiaJk/IsZAEZFgdsaWJyYXRvMRMwEQYKCZImiZPyLGQBGRYDY29tMIIB
|
17
|
+
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9ox6O79gTdEjOHX3JWi4ZDU
|
18
|
+
g4xJoDWh5+NSeL8d2OaS3A8zOtBTbrPB3lKohkM7ZFgEymIr9mD0DVIe2y3PXDJ+
|
19
|
+
POaZLDNzix+08M3sQLoP5MvnuzRIpNbAtVf31CMz830GUkPGtSeXc4dcgkTR+u9t
|
20
|
+
gYLek7X2FXdO4/3hVlyQed5rurXg6IGc3xkznPLJz08v7gBXVTd7ZD/TA9JiVPAb
|
21
|
+
NpDpqeJ0cUGoNOmvr90lENnE4L3QUcXoWnIDokdgrT6e2+u3fqm9Awi4PHIeJRF1
|
22
|
+
CDLNk4fF076J5wldCu9GSDyOR864j8s8P4grqg3/W5nlcA/duj70FdBevXEGdQID
|
23
|
+
AQABo3MwcTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUhLbYf+E+
|
24
|
+
mD2AKqV3UPnSvYHWhoowGwYDVR0RBBQwEoEQbWF0dEBsaWJyYXRvLmNvbTAbBgNV
|
25
|
+
HRIEFDASgRBtYXR0QGxpYnJhdG8uY29tMA0GCSqGSIb3DQEBBQUAA4IBAQAFzeZI
|
26
|
+
B1KPHoagOEfFSRjEOM+Oc8OCch9GrvkXIJmygb7ek0IaamM265rosHkZs/GFJUWM
|
27
|
+
2g/DjyLazNaFJ3k5vHIWdH11oLfoJ2atJJZuv5DoMATv6bZEPy6HEW8UAFbNt1kg
|
28
|
+
e5VTA7yy+JNFm3/3jt2JzOX6cnJs4gUra3zgSCte69sFVYD/lcasMUM+/xRmubPz
|
29
|
+
A2QAjpamhamK23fX5Iu0Taj0/U8VzB8tWC8wbp6Q/2rGBSG31tTM9Mt415XXSuKt
|
30
|
+
9WLxxDpp4oArOj0hucFoQ9V6f68TZdS1u5/LcIw/ZJ+7sXVYmMCgDIjdZ+p7VVwq
|
31
|
+
aqIKyXbNfJC+dTit
|
32
|
+
-----END CERTIFICATE-----
|
33
|
+
date: 2015-09-03 00:00:00.000000000 Z
|
41
34
|
dependencies:
|
42
35
|
- !ruby/object:Gem::Dependency
|
43
36
|
name: librato-metrics
|
44
37
|
requirement: !ruby/object:Gem::Requirement
|
45
|
-
none: false
|
46
38
|
requirements:
|
47
|
-
- - ~>
|
39
|
+
- - "~>"
|
48
40
|
- !ruby/object:Gem::Version
|
49
41
|
version: '1.1'
|
50
42
|
type: :runtime
|
51
43
|
prerelease: false
|
52
44
|
version_requirements: !ruby/object:Gem::Requirement
|
53
|
-
none: false
|
54
45
|
requirements:
|
55
|
-
- - ~>
|
46
|
+
- - "~>"
|
56
47
|
- !ruby/object:Gem::Version
|
57
48
|
version: '1.1'
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: hetchy
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.0'
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '1.0'
|
58
63
|
- !ruby/object:Gem::Dependency
|
59
64
|
name: minitest
|
60
65
|
requirement: !ruby/object:Gem::Requirement
|
61
|
-
none: false
|
62
66
|
requirements:
|
63
|
-
- -
|
67
|
+
- - ">="
|
64
68
|
- !ruby/object:Gem::Version
|
65
69
|
version: '0'
|
66
70
|
type: :development
|
67
71
|
prerelease: false
|
68
72
|
version_requirements: !ruby/object:Gem::Requirement
|
69
|
-
none: false
|
70
73
|
requirements:
|
71
|
-
- -
|
74
|
+
- - ">="
|
72
75
|
- !ruby/object:Gem::Version
|
73
76
|
version: '0'
|
74
77
|
description: Rack middleware to report key app statistics and custom instrumentation
|
@@ -79,10 +82,17 @@ executables: []
|
|
79
82
|
extensions: []
|
80
83
|
extra_rdoc_files: []
|
81
84
|
files:
|
85
|
+
- CHANGELOG.md
|
86
|
+
- LICENSE
|
87
|
+
- README.md
|
88
|
+
- Rakefile
|
89
|
+
- lib/librato-rack.rb
|
90
|
+
- lib/librato/collector.rb
|
82
91
|
- lib/librato/collector/aggregator.rb
|
83
92
|
- lib/librato/collector/counter_cache.rb
|
93
|
+
- lib/librato/collector/exceptions.rb
|
84
94
|
- lib/librato/collector/group.rb
|
85
|
-
- lib/librato/
|
95
|
+
- lib/librato/rack.rb
|
86
96
|
- lib/librato/rack/configuration.rb
|
87
97
|
- lib/librato/rack/errors.rb
|
88
98
|
- lib/librato/rack/logger.rb
|
@@ -90,12 +100,6 @@ files:
|
|
90
100
|
- lib/librato/rack/validating_queue.rb
|
91
101
|
- lib/librato/rack/version.rb
|
92
102
|
- lib/librato/rack/worker.rb
|
93
|
-
- lib/librato/rack.rb
|
94
|
-
- lib/librato-rack.rb
|
95
|
-
- LICENSE
|
96
|
-
- Rakefile
|
97
|
-
- README.md
|
98
|
-
- CHANGELOG.md
|
99
103
|
- test/apps/basic.ru
|
100
104
|
- test/apps/custom.ru
|
101
105
|
- test/apps/deprecated.ru
|
@@ -119,33 +123,26 @@ files:
|
|
119
123
|
homepage: https://github.com/librato/librato-rack
|
120
124
|
licenses:
|
121
125
|
- BSD 3-clause
|
126
|
+
metadata: {}
|
122
127
|
post_install_message:
|
123
128
|
rdoc_options: []
|
124
129
|
require_paths:
|
125
130
|
- lib
|
126
131
|
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
-
none: false
|
128
132
|
requirements:
|
129
|
-
- -
|
133
|
+
- - ">="
|
130
134
|
- !ruby/object:Gem::Version
|
131
135
|
version: '0'
|
132
|
-
segments:
|
133
|
-
- 0
|
134
|
-
hash: 3341764962779611788
|
135
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
-
none: false
|
137
137
|
requirements:
|
138
|
-
- -
|
138
|
+
- - ">"
|
139
139
|
- !ruby/object:Gem::Version
|
140
|
-
version:
|
141
|
-
segments:
|
142
|
-
- 0
|
143
|
-
hash: 3341764962779611788
|
140
|
+
version: 1.3.1
|
144
141
|
requirements: []
|
145
142
|
rubyforge_project:
|
146
|
-
rubygems_version:
|
143
|
+
rubygems_version: 2.2.2
|
147
144
|
signing_key:
|
148
|
-
specification_version:
|
145
|
+
specification_version: 4
|
149
146
|
summary: Use Librato Metrics with your rack application
|
150
147
|
test_files:
|
151
148
|
- test/apps/basic.ru
|
metadata.gz.sig
CHANGED
Binary file
|