promenade 0.5.0 → 0.6.0
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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +18 -0
- data/lib/promenade/client/rack/http_request_duration_collector.rb +92 -0
- data/lib/promenade/client/rack/http_request_queue_time_collector.rb +55 -0
- data/lib/promenade/client/rack/middleware_base.rb +36 -0
- data/lib/promenade/client/rack/queue_time_duration.rb +50 -0
- data/lib/promenade/configuration.rb +4 -1
- data/lib/promenade/railtie.rb +5 -2
- data/lib/promenade/version.rb +1 -1
- metadata +7 -4
- data/lib/promenade/client/rack/collector.rb +0 -113
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 235f95d0b77870a9975ff9870428ed5a995b9174380b981c2df7d8a338519ffb
|
4
|
+
data.tar.gz: 7d6e327e2d602fd2b07342fb45c9be29e36ad266212ad4ed98a72eafee58bc9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8596f53e3110b636696cb38f2d3bfa872715de3e331fdf260da62e30c31949a1753702b935c3343545c7be19da56f0e30a65d89568eefd419b94a5ef80fe2f9
|
7
|
+
data.tar.gz: 7e65f494c50b2bc25b05b169794f73c02c64ad1548c3425399b1d2cb4e7077d930e0e7d9c99ea88088c76e1026bb64cfbda246ab1a6b09c057b4f2b717b130d8
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
promenade (0.
|
4
|
+
promenade (0.6.0)
|
5
5
|
actionpack
|
6
6
|
activesupport (> 6.0, < 8.0)
|
7
7
|
prometheus-client-mmap (~> 0.16.0)
|
@@ -140,7 +140,7 @@ GEM
|
|
140
140
|
parallel (1.22.1)
|
141
141
|
parser (3.1.2.0)
|
142
142
|
ast (~> 2.4.1)
|
143
|
-
prometheus-client-mmap (0.16.
|
143
|
+
prometheus-client-mmap (0.16.2)
|
144
144
|
pry (0.14.1)
|
145
145
|
coderay (~> 1.1)
|
146
146
|
method_source (~> 1.0)
|
data/README.md
CHANGED
@@ -181,6 +181,20 @@ Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions,
|
|
181
181
|
exception_handler: exception_handler
|
182
182
|
```
|
183
183
|
|
184
|
+
#### Customising the histogram buckets
|
185
|
+
|
186
|
+
The default buckets cover a range of latencies from 5 ms to 10s see [Promenade::Configuration::DEFAULT_RACK_LATENCY_BUCKETS](https://github.com/errm/promenade/blob/ea7eb54c04257770a601b7e28b3e13db5d2430bb/lib/promenade/configuration.rb#L5). This is intended to capture the typical range of latencies for a web application. However, this might not be suitable for your Service-Level Agreements (SLAs), and other bucket size intervals may be required (see [histogram bins](https://en.wikipedia.org/wiki/Histogram#Number_of_bins_and_width)).
|
187
|
+
|
188
|
+
If you would like to customise the histogram buckets, you can do so by configuring Promenade in an initializer:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# config/initializers/promenade.rb
|
192
|
+
|
193
|
+
Promenade.configure do |config|
|
194
|
+
config.rack_latency_buckets = [0.25, 0.350, 0.5, 1, 1.5, 2.5, 5, 10, 15, 19]
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
184
198
|
### Configuration
|
185
199
|
|
186
200
|
If you are using rails it should load a railtie and configure promenade.
|
@@ -205,6 +219,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/errm/p
|
|
205
219
|
|
206
220
|
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
207
221
|
|
222
|
+
## Acknowledgements
|
223
|
+
|
224
|
+
The original code for the Rack middleware collector class was copied from [Prometheus Client MMap](https://gitlab.com/gitlab-org/prometheus-client-mmap/-/blob/master/lib/prometheus/client/rack/collector.rb).
|
225
|
+
|
208
226
|
## License
|
209
227
|
|
210
228
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require "prometheus/client"
|
2
|
+
require_relative "middleware_base"
|
3
|
+
require_relative "request_labeler"
|
4
|
+
require_relative "exception_handler"
|
5
|
+
require_relative "queue_time_duration"
|
6
|
+
|
7
|
+
module Promenade
|
8
|
+
module Client
|
9
|
+
module Rack
|
10
|
+
class HTTPRequestDurationCollector < MiddlwareBase
|
11
|
+
REQUEST_DURATION_HISTOGRAM_NAME = :http_req_duration_seconds
|
12
|
+
|
13
|
+
REQUESTS_COUNTER_NAME = :http_requests_total
|
14
|
+
|
15
|
+
EXCEPTIONS_COUNTER_NAME = :http_exceptions_total
|
16
|
+
|
17
|
+
private_constant :REQUEST_DURATION_HISTOGRAM_NAME,
|
18
|
+
:REQUESTS_COUNTER_NAME,
|
19
|
+
:EXCEPTIONS_COUNTER_NAME
|
20
|
+
|
21
|
+
def initialize(app,
|
22
|
+
registry: ::Prometheus::Client.registry,
|
23
|
+
label_builder: RequestLabeler,
|
24
|
+
exception_handler: nil)
|
25
|
+
|
26
|
+
@latency_buckets = Promenade.configuration.rack_latency_buckets
|
27
|
+
@_exception_handler = exception_handler
|
28
|
+
|
29
|
+
super(app, registry: registry, label_builder: label_builder)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :latency_buckets, :queue_time_buckets
|
35
|
+
|
36
|
+
def trace(env)
|
37
|
+
start = current_time
|
38
|
+
begin
|
39
|
+
response = yield
|
40
|
+
record_request_duration(labels(env, response), duration_since(start))
|
41
|
+
response
|
42
|
+
rescue StandardError => e
|
43
|
+
exception_handler.call(e, env, duration_since(start))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def record_request_duration(labels, duration)
|
48
|
+
requests_counter.increment(labels)
|
49
|
+
durations_histogram.observe(labels, duration)
|
50
|
+
end
|
51
|
+
|
52
|
+
def duration_since(start_time)
|
53
|
+
current_time - start_time
|
54
|
+
end
|
55
|
+
|
56
|
+
def current_time
|
57
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
58
|
+
end
|
59
|
+
|
60
|
+
def durations_histogram
|
61
|
+
registry.get(REQUEST_DURATION_HISTOGRAM_NAME)
|
62
|
+
end
|
63
|
+
|
64
|
+
def requests_counter
|
65
|
+
registry.get(REQUESTS_COUNTER_NAME)
|
66
|
+
end
|
67
|
+
|
68
|
+
def register_metrics!
|
69
|
+
registry.counter(REQUESTS_COUNTER_NAME,
|
70
|
+
"A counter of the total number of HTTP requests made.")
|
71
|
+
registry.histogram(REQUEST_DURATION_HISTOGRAM_NAME,
|
72
|
+
"A histogram of the response latency.", {}, latency_buckets)
|
73
|
+
registry.counter(EXCEPTIONS_COUNTER_NAME,
|
74
|
+
"A counter of the total number of exceptions raised.")
|
75
|
+
end
|
76
|
+
|
77
|
+
def exception_handler
|
78
|
+
@_exception_handler ||= default_exception_handler
|
79
|
+
end
|
80
|
+
|
81
|
+
def default_exception_handler
|
82
|
+
ExceptionHandler.initialize_singleton(
|
83
|
+
histogram_name: REQUEST_DURATION_HISTOGRAM_NAME,
|
84
|
+
requests_counter_name: REQUESTS_COUNTER_NAME,
|
85
|
+
exceptions_counter_name: EXCEPTIONS_COUNTER_NAME,
|
86
|
+
registry: registry,
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "prometheus/client"
|
2
|
+
require_relative "middleware_base"
|
3
|
+
require_relative "request_labeler"
|
4
|
+
require_relative "queue_time_duration"
|
5
|
+
|
6
|
+
module Promenade
|
7
|
+
module Client
|
8
|
+
module Rack
|
9
|
+
class HTTPRequestQueueTimeCollector < MiddlwareBase
|
10
|
+
REQUEST_QUEUE_TIME_HISTOGRAM_NAME = :http_req_queue_time_seconds
|
11
|
+
|
12
|
+
private_constant :REQUEST_QUEUE_TIME_HISTOGRAM_NAME
|
13
|
+
|
14
|
+
def initialize(app,
|
15
|
+
registry: ::Prometheus::Client.registry,
|
16
|
+
label_builder: RequestLabeler)
|
17
|
+
|
18
|
+
@queue_time_buckets = Promenade.configuration.queue_time_buckets
|
19
|
+
|
20
|
+
super(app, registry: registry, label_builder: label_builder)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :queue_time_buckets
|
26
|
+
|
27
|
+
def trace(env)
|
28
|
+
start_timestamp = Time.now.utc
|
29
|
+
response = yield
|
30
|
+
record_request_queue_time(labels: labels(env, response),
|
31
|
+
env: env,
|
32
|
+
request_received_time: start_timestamp)
|
33
|
+
response
|
34
|
+
end
|
35
|
+
|
36
|
+
def record_request_queue_time(labels:, env:, request_received_time:)
|
37
|
+
request_queue_duration = QueueTimeDuration.new(env: env,
|
38
|
+
request_received_time: request_received_time)
|
39
|
+
return unless request_queue_duration.valid_header_present?
|
40
|
+
|
41
|
+
queue_time_histogram.observe(labels, request_queue_duration.queue_time_seconds)
|
42
|
+
end
|
43
|
+
|
44
|
+
def register_metrics!
|
45
|
+
registry.histogram(REQUEST_QUEUE_TIME_HISTOGRAM_NAME,
|
46
|
+
"A histogram of request queue time", {}, queue_time_buckets)
|
47
|
+
end
|
48
|
+
|
49
|
+
def queue_time_histogram
|
50
|
+
registry.get(REQUEST_QUEUE_TIME_HISTOGRAM_NAME)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Promenade
|
2
|
+
module Client
|
3
|
+
module Rack
|
4
|
+
class MiddlwareBase
|
5
|
+
def initialize(app, registry:, label_builder:)
|
6
|
+
@app = app
|
7
|
+
@registry = registry
|
8
|
+
@label_builder = label_builder
|
9
|
+
|
10
|
+
register_metrics!
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
trace(env) { app.call(env) }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :app, :label_builder, :registry
|
20
|
+
|
21
|
+
def trace(env)
|
22
|
+
raise NotImplementedError,
|
23
|
+
"Please define #{__method__} in #{self.class}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def labels(env, response)
|
27
|
+
label_builder.call(env).merge!(code: response.first.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def register_metrics!
|
31
|
+
# :noop:
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Promenade
|
2
|
+
module Client
|
3
|
+
module Rack
|
4
|
+
class QueueTimeDuration
|
5
|
+
REQUEST_START_HEADER = "HTTP_X_REQUEST_START".freeze
|
6
|
+
|
7
|
+
QUEUE_START_HEADER = "HTTP_X_QUEUE_START".freeze
|
8
|
+
|
9
|
+
HEADER_VALUE_MATCHER = /^(?:t=)(?<timestamp>\d{10}(?:\.\d+))$/.freeze
|
10
|
+
|
11
|
+
def initialize(env:, request_received_time:)
|
12
|
+
@env = env
|
13
|
+
@request_queued_time_ms = extract_request_queued_time_from_env(env)
|
14
|
+
@valid_header_present = @request_queued_time_ms.is_a?(Float)
|
15
|
+
@request_received_time_ms = request_received_time.utc.to_f
|
16
|
+
|
17
|
+
freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid_header_present?
|
21
|
+
@valid_header_present
|
22
|
+
end
|
23
|
+
|
24
|
+
def queue_time_seconds
|
25
|
+
return unless valid_header_present?
|
26
|
+
|
27
|
+
queue_time.round(3)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :env, :request_queued_time_ms, :request_received_time_ms
|
33
|
+
|
34
|
+
def queue_time
|
35
|
+
request_received_time_ms - request_queued_time_ms
|
36
|
+
end
|
37
|
+
|
38
|
+
def extract_request_queued_time_from_env(env_hash)
|
39
|
+
header_value = env_hash[REQUEST_START_HEADER] || env_hash[QUEUE_START_HEADER]
|
40
|
+
return if header_value.nil?
|
41
|
+
|
42
|
+
header_time_match = header_value.to_s.match(HEADER_VALUE_MATCHER)
|
43
|
+
return unless header_time_match
|
44
|
+
|
45
|
+
header_time_match[:timestamp].to_f
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
module Promenade
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :rack_latency_buckets
|
3
|
+
attr_accessor :queue_time_buckets, :rack_latency_buckets
|
4
4
|
|
5
5
|
DEFAULT_RACK_LATENCY_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10].freeze
|
6
6
|
|
7
|
+
DEFAULT_QUEUE_TIME_BUCKETS = [0.01, 0.5, 1.0, 10.0, 30.0].freeze
|
8
|
+
|
7
9
|
def initialize
|
8
10
|
@rack_latency_buckets = DEFAULT_RACK_LATENCY_BUCKETS
|
11
|
+
@queue_time_buckets = DEFAULT_QUEUE_TIME_BUCKETS
|
9
12
|
end
|
10
13
|
end
|
11
14
|
end
|
data/lib/promenade/railtie.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
require "promenade/setup"
|
2
2
|
require "promenade/engine"
|
3
|
-
require "promenade/client/rack/
|
3
|
+
require "promenade/client/rack/http_request_duration_collector"
|
4
|
+
require "promenade/client/rack/http_request_queue_time_collector"
|
4
5
|
|
5
6
|
module Promenade
|
6
7
|
class Railtie < ::Rails::Railtie
|
7
8
|
initializer "promenade.configure_rails_initialization" do
|
8
9
|
Promenade.setup
|
9
10
|
Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions,
|
10
|
-
Promenade::Client::Rack::
|
11
|
+
Promenade::Client::Rack::HTTPRequestDurationCollector
|
12
|
+
Rails.application.config.middleware.insert 0,
|
13
|
+
Promenade::Client::Rack::HTTPRequestQueueTimeCollector
|
11
14
|
end
|
12
15
|
end
|
13
16
|
end
|
data/lib/promenade/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: promenade
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ed Robinson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -271,8 +271,11 @@ files:
|
|
271
271
|
- bin/setup
|
272
272
|
- exe/promenade
|
273
273
|
- lib/promenade.rb
|
274
|
-
- lib/promenade/client/rack/collector.rb
|
275
274
|
- lib/promenade/client/rack/exception_handler.rb
|
275
|
+
- lib/promenade/client/rack/http_request_duration_collector.rb
|
276
|
+
- lib/promenade/client/rack/http_request_queue_time_collector.rb
|
277
|
+
- lib/promenade/client/rack/middleware_base.rb
|
278
|
+
- lib/promenade/client/rack/queue_time_duration.rb
|
276
279
|
- lib/promenade/client/rack/request_labeler.rb
|
277
280
|
- lib/promenade/client/rack/singleton_caller.rb
|
278
281
|
- lib/promenade/configuration.rb
|
@@ -312,7 +315,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
312
315
|
- !ruby/object:Gem::Version
|
313
316
|
version: '0'
|
314
317
|
requirements: []
|
315
|
-
rubygems_version: 3.
|
318
|
+
rubygems_version: 3.3.18
|
316
319
|
signing_key:
|
317
320
|
specification_version: 4
|
318
321
|
summary: Promenade makes it simple to instrument Ruby apps for prometheus scraping
|
@@ -1,113 +0,0 @@
|
|
1
|
-
require "prometheus/client"
|
2
|
-
require_relative "request_labeler"
|
3
|
-
require_relative "exception_handler"
|
4
|
-
|
5
|
-
module Promenade
|
6
|
-
module Client
|
7
|
-
module Rack
|
8
|
-
# Original code taken from Prometheus Client MMap
|
9
|
-
# https://gitlab.com/gitlab-org/prometheus-client-mmap/-/blob/master/lib/prometheus/client/rack/collector.rb
|
10
|
-
#
|
11
|
-
# Collector is a Rack middleware that provides a sample implementation of
|
12
|
-
# a HTTP tracer. The default label builder can be modified to export a
|
13
|
-
# different set of labels per recorded metric.
|
14
|
-
class Collector
|
15
|
-
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
16
|
-
|
17
|
-
HTTP_HOST = "HTTP_HOST".freeze
|
18
|
-
|
19
|
-
PATH_INFO = "PATH_INFO".freeze
|
20
|
-
|
21
|
-
HISTOGRAM_NAME = :http_req_duration_seconds
|
22
|
-
|
23
|
-
REQUESTS_COUNTER_NAME = :http_requests_total
|
24
|
-
|
25
|
-
EXCEPTIONS_COUNTER_NAME = :http_exceptions_total
|
26
|
-
|
27
|
-
private_constant *%i(
|
28
|
-
REQUEST_METHOD
|
29
|
-
HTTP_HOST
|
30
|
-
PATH_INFO
|
31
|
-
HISTOGRAM_NAME
|
32
|
-
REQUESTS_COUNTER_NAME
|
33
|
-
EXCEPTIONS_COUNTER_NAME
|
34
|
-
)
|
35
|
-
|
36
|
-
def initialize(app,
|
37
|
-
registry: ::Prometheus::Client.registry,
|
38
|
-
label_builder: RequestLabeler,
|
39
|
-
exception_handler: nil)
|
40
|
-
@app = app
|
41
|
-
@registry = registry
|
42
|
-
@label_builder = label_builder
|
43
|
-
@latency_buckets = Promenade.configuration.rack_latency_buckets
|
44
|
-
@exception_handler = exception_handler || default_exception_handler
|
45
|
-
register_metrics!
|
46
|
-
end
|
47
|
-
|
48
|
-
def call(env)
|
49
|
-
trace(env) { app.call(env) }
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
attr_reader :app,
|
55
|
-
:registry,
|
56
|
-
:label_builder,
|
57
|
-
:latency_buckets,
|
58
|
-
:exception_handler
|
59
|
-
|
60
|
-
def trace(env)
|
61
|
-
start = current_time
|
62
|
-
begin
|
63
|
-
response = yield
|
64
|
-
record(labels(env, response), duration_since(start))
|
65
|
-
response
|
66
|
-
rescue StandardError => e
|
67
|
-
exception_handler.call(e, env, duration_since(start))
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def labels(env, response)
|
72
|
-
label_builder.call(env).merge!(code: response.first.to_s)
|
73
|
-
end
|
74
|
-
|
75
|
-
def record(labels, duration)
|
76
|
-
requests_counter.increment(labels)
|
77
|
-
durations_histogram.observe(labels, duration)
|
78
|
-
end
|
79
|
-
|
80
|
-
def duration_since(start_time)
|
81
|
-
current_time - start_time
|
82
|
-
end
|
83
|
-
|
84
|
-
def current_time
|
85
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
86
|
-
end
|
87
|
-
|
88
|
-
def durations_histogram
|
89
|
-
registry.get(HISTOGRAM_NAME)
|
90
|
-
end
|
91
|
-
|
92
|
-
def requests_counter
|
93
|
-
registry.get(REQUESTS_COUNTER_NAME)
|
94
|
-
end
|
95
|
-
|
96
|
-
def register_metrics!
|
97
|
-
registry.counter(REQUESTS_COUNTER_NAME, "A counter of the total number of HTTP requests made.")
|
98
|
-
registry.histogram(HISTOGRAM_NAME, "A histogram of the response latency.", {}, latency_buckets)
|
99
|
-
registry.counter(EXCEPTIONS_COUNTER_NAME, "A counter of the total number of exceptions raised.")
|
100
|
-
end
|
101
|
-
|
102
|
-
def default_exception_handler
|
103
|
-
ExceptionHandler.initialize_singleton(
|
104
|
-
histogram_name: HISTOGRAM_NAME,
|
105
|
-
requests_counter_name: REQUESTS_COUNTER_NAME,
|
106
|
-
exceptions_counter_name: EXCEPTIONS_COUNTER_NAME,
|
107
|
-
registry: registry,
|
108
|
-
)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|