promenade 0.12.17 → 0.12.19
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/README.md +52 -39
- data/lib/promenade/client/rack/http_request_queue_time_collector.rb +6 -17
- data/lib/promenade/client/rack/queue_time_duration.rb +12 -16
- data/lib/promenade/client/rack/request_controller_action_labeler.rb +4 -1
- data/lib/promenade/client/rack/request_labeler.rb +1 -4
- data/lib/promenade/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a7547269d759c687b4c98f3e71ee3e273c41fa9c4501c7bc8af3dee4a84467b
|
|
4
|
+
data.tar.gz: c0c4abe54da62a38090e00d542fafbd0b27453c14561954a83e113b7eb74e89b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 51e565bc41e56d8f1ffb88d1243668584bb96e624239cf0e798e2444e6461f48f4982646b1cf12d706cd31645fddf85f7d394ea19141a7b0c43564eefc701499
|
|
7
|
+
data.tar.gz: c08c64ab3f836b6f7ff41513f86b8fadea0651cbcb0779e6df485f6fa9647106c25bb3c94971ef6e23945e7cd5664c839b523997f19fcefba8c68c54e28de42f
|
data/README.md
CHANGED
|
@@ -6,25 +6,22 @@
|
|
|
6
6
|
|
|
7
7
|
Promenade is a library to simplify instrumenting Ruby applications with Prometheus.
|
|
8
8
|
|
|
9
|
-
It is currently under development.
|
|
10
|
-
|
|
11
9
|
## Usage
|
|
12
10
|
|
|
13
|
-
Add promenade to your
|
|
11
|
+
Add promenade to your Gemfile:
|
|
14
12
|
|
|
15
|
-
```
|
|
13
|
+
```ruby
|
|
16
14
|
gem "promenade"
|
|
17
15
|
```
|
|
18
16
|
|
|
19
17
|
### Built in instrumentation
|
|
20
18
|
|
|
21
|
-
Promenade includes
|
|
19
|
+
Promenade includes built-in instrumentation for several libraries. Require the relevant file in an initializer to enable it.
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
require "promenade/kafka"
|
|
21
|
+
```ruby
|
|
22
|
+
require "promenade/kafka" # ruby-kafka
|
|
23
|
+
require "promenade/karafka" # Karafka consumers
|
|
24
|
+
require "promenade/waterdrop" # WaterDrop producers
|
|
28
25
|
```
|
|
29
26
|
|
|
30
27
|
### Instrumentation DSL
|
|
@@ -52,7 +49,7 @@ class WidgetService
|
|
|
52
49
|
end
|
|
53
50
|
|
|
54
51
|
def batch_create
|
|
55
|
-
You can increment by more than 1 at a time if you need
|
|
52
|
+
# You can increment by more than 1 at a time if you need
|
|
56
53
|
Promenade.metric(:widgets_created).increment({ type: "guinness" }, 100)
|
|
57
54
|
end
|
|
58
55
|
end
|
|
@@ -86,7 +83,7 @@ of all observed values.
|
|
|
86
83
|
class Calculator
|
|
87
84
|
Promenade.histogram :calculator_time_taken do
|
|
88
85
|
doc "Records how long it takes to do the adding"
|
|
89
|
-
# promenade also has some bucket presets like :network and :memory for common
|
|
86
|
+
# promenade also has some bucket presets like :network and :memory for common use cases
|
|
90
87
|
buckets [0.25, 0.5, 1, 2, 4]
|
|
91
88
|
end
|
|
92
89
|
|
|
@@ -122,13 +119,32 @@ end
|
|
|
122
119
|
|
|
123
120
|
### Exporter
|
|
124
121
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
122
|
+
The recommended way to expose metrics is the **Go exporter sidecar** in [`exporter/`](exporter/). It runs as a separate container that reads the `.db` files written by the Ruby application and exposes them at `:9394/metrics`. This keeps scrape overhead entirely off the Ruby application process and also collects TCP connection metrics (busy/queued workers) via Linux netlink without any native extension.
|
|
123
|
+
|
|
124
|
+
The exporter sidecar shares a network namespace and tmpfs volume with your app container. See [`compose.yml`](compose.yml) for a reference deployment:
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
services:
|
|
128
|
+
app:
|
|
129
|
+
# your Ruby app; writes metrics to tmp/promenade on the shared tmpfs
|
|
130
|
+
volumes:
|
|
131
|
+
- tmp:/app/tmp
|
|
132
|
+
environment:
|
|
133
|
+
PROMETHEUS_MULTIPROC_DIR: /app/tmp/promenade
|
|
134
|
+
exporter:
|
|
135
|
+
image: ghcr.io/errm/promenade:latest
|
|
136
|
+
network_mode: service:app # shares the app's network namespace
|
|
137
|
+
volumes:
|
|
138
|
+
- tmp:/app/tmp
|
|
139
|
+
volumes:
|
|
140
|
+
tmp:
|
|
141
|
+
driver: local
|
|
142
|
+
driver_opts:
|
|
143
|
+
type: tmpfs
|
|
144
|
+
device: tmpfs
|
|
145
|
+
```
|
|
130
146
|
|
|
131
|
-
The exporter
|
|
147
|
+
The exporter is configured with `--multiprocess-dir` (or `PROMETHEUS_MULTIPROC_DIR`) and `--metrics-port` (default `9394`).
|
|
132
148
|
|
|
133
149
|
|
|
134
150
|
### Rails Middleware
|
|
@@ -137,15 +153,18 @@ Promenade provides custom Rack middleware to track HTTP response times for reque
|
|
|
137
153
|
|
|
138
154
|
This was originally inspired by [prometheus-client-mmap](https://gitlab.com/gitlab-org/prometheus-client-mmap/-/blob/master/lib/prometheus/client/rack/collector.rb).
|
|
139
155
|
|
|
140
|
-
**
|
|
156
|
+
**The following middleware is automatically added to your Rack stack if your application is a Ruby on Rails app:**
|
|
141
157
|
|
|
142
|
-
|
|
158
|
+
- `Promenade::Client::Rack::HTTPRequestQueueTimeCollector` — inserted at the front of the stack, records time spent in the request queue (via `X-Request-Start` / `X-Queue-Start` headers).
|
|
159
|
+
- `Promenade::Client::Rack::HTTPRequestDurationCollector` — inserted after `ActionDispatch::ShowExceptions`, records response duration and HTTP exception counts.
|
|
160
|
+
- `Promenade::YJIT::Middleware` — appended, records YJIT stats (enabled only when `RubyVM::YJIT` is defined).
|
|
161
|
+
- `Promenade::Pitchfork::Middleware` — appended, records worker and memory metrics (enabled only when Pitchfork is present).
|
|
143
162
|
|
|
144
|
-
If you want to change the position
|
|
163
|
+
If you want to change the position of `HTTPRequestDurationCollector`, or customise its labels and exception handling behaviour, simply remove it from the stack and re-insert it with your own preferences.
|
|
145
164
|
|
|
146
165
|
``` ruby
|
|
147
|
-
Rails.application.middleware.delete(Promenade::Client::Rack::
|
|
148
|
-
Rails.application.middleware.insert_after(Rails::Rack::Logger, Promenade::Client::Rack::
|
|
166
|
+
Rails.application.middleware.delete(Promenade::Client::Rack::HTTPRequestDurationCollector)
|
|
167
|
+
Rails.application.middleware.insert_after(Rails::Rack::Logger, Promenade::Client::Rack::HTTPRequestDurationCollector)
|
|
149
168
|
```
|
|
150
169
|
|
|
151
170
|
#### Customising the labels recorded for each request
|
|
@@ -162,29 +181,29 @@ label_builder = Proc.new do |env|
|
|
|
162
181
|
}
|
|
163
182
|
end
|
|
164
183
|
Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions,
|
|
165
|
-
Promenade::Client::Rack::
|
|
184
|
+
Promenade::Client::Rack::HTTPRequestDurationCollector,
|
|
166
185
|
label_builder: label_builder
|
|
167
186
|
```
|
|
168
187
|
|
|
169
188
|
#### Customising how the middleware handles exceptions
|
|
170
189
|
|
|
171
|
-
The default implementation will capture exceptions, count the
|
|
190
|
+
The default implementation will capture exceptions, count the exception class name (e.g. `"StandardError"`), and then re-raise the exception.
|
|
172
191
|
|
|
173
192
|
If you would like to customise this behaviour, you may do so by customising the middleware installation:
|
|
174
193
|
|
|
175
194
|
``` ruby
|
|
176
|
-
exception_handler = Proc.new do |exception,
|
|
177
|
-
# This simple example just re-raises the
|
|
195
|
+
exception_handler = Proc.new do |exception, env_hash, duration|
|
|
196
|
+
# This simple example just re-raises the exception
|
|
178
197
|
raise exception
|
|
179
198
|
end
|
|
180
199
|
Rails.application.config.middleware.insert_after ActionDispatch::ShowExceptions,
|
|
181
|
-
Promenade::Client::Rack::
|
|
200
|
+
Promenade::Client::Rack::HTTPRequestDurationCollector,
|
|
182
201
|
exception_handler: exception_handler
|
|
183
202
|
```
|
|
184
203
|
|
|
185
204
|
#### Customising the histogram buckets
|
|
186
205
|
|
|
187
|
-
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/
|
|
206
|
+
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/master/lib/promenade/configuration.rb#L5) and [Promenade::Configuration::DEFAULT_QUEUE_TIME_BUCKETS](https://github.com/errm/promenade/blob/master/lib/promenade/configuration.rb#L7). 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)).
|
|
188
207
|
|
|
189
208
|
If you would like to customise the histogram buckets, you can do so by configuring Promenade in an initializer:
|
|
190
209
|
|
|
@@ -192,21 +211,21 @@ If you would like to customise the histogram buckets, you can do so by configuri
|
|
|
192
211
|
# config/initializers/promenade.rb
|
|
193
212
|
|
|
194
213
|
Promenade.configure do |config|
|
|
195
|
-
config.rack_latency_buckets = [0.
|
|
214
|
+
config.rack_latency_buckets = [0.1, 0.25, 0.5, 1, 2.5, 5, 10]
|
|
215
|
+
config.queue_time_buckets = [0.01, 0.5, 1.0, 10.0, 30.0] # optional, for queue time collector
|
|
196
216
|
end
|
|
197
217
|
```
|
|
198
218
|
|
|
199
219
|
### Configuration
|
|
200
220
|
|
|
201
|
-
If you are using
|
|
221
|
+
If you are using Rails it should load a railtie and configure promenade.
|
|
202
222
|
|
|
203
|
-
If are not using
|
|
223
|
+
If you are not using Rails you should call `Promenade.setup` after your environment has loaded.
|
|
204
224
|
|
|
205
225
|
In a typical development environment there should be nothing for you to do. Promenade stores its state files in `tmp/promenade` and will create that directory if it does not exist.
|
|
206
226
|
|
|
207
227
|
In a production environment you should try to store the state files on tmpfs for performance, you can configure the path that promenade will write to by setting the `PROMETHEUS_MULTIPROC_DIR` environment variable.
|
|
208
228
|
|
|
209
|
-
If you are running the stand-alone exporter, you may also set the `PORT` environment variable to bind to a port other than the default (`9394`).
|
|
210
229
|
|
|
211
230
|
## Development
|
|
212
231
|
|
|
@@ -218,8 +237,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
218
237
|
|
|
219
238
|
Bug reports and pull requests are welcome on GitHub at https://github.com/errm/promenade.
|
|
220
239
|
|
|
221
|
-
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.
|
|
222
|
-
|
|
223
240
|
## Acknowledgements
|
|
224
241
|
|
|
225
242
|
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).
|
|
@@ -227,7 +244,3 @@ The original code for the Rack middleware collector class was copied from [Prome
|
|
|
227
244
|
## License
|
|
228
245
|
|
|
229
246
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
230
|
-
|
|
231
|
-
## Code of Conduct
|
|
232
|
-
|
|
233
|
-
Everyone interacting in the Promenade project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/promenade/blob/master/CODE_OF_CONDUCT.md).
|
|
@@ -14,35 +14,24 @@ module Promenade
|
|
|
14
14
|
def initialize(app,
|
|
15
15
|
registry: ::Prometheus::Client.registry,
|
|
16
16
|
label_builder: RequestLabeler)
|
|
17
|
-
@queue_time_buckets = Promenade.configuration.queue_time_buckets
|
|
18
|
-
|
|
19
17
|
super
|
|
20
18
|
end
|
|
21
19
|
|
|
22
20
|
private
|
|
23
21
|
|
|
24
|
-
attr_reader :queue_time_buckets
|
|
25
|
-
|
|
26
22
|
def trace(env)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
record_request_queue_time(labels: labels(env, response),
|
|
30
|
-
env: env,
|
|
31
|
-
request_received_time: start_timestamp)
|
|
32
|
-
response
|
|
23
|
+
record_request_queue_time(env:)
|
|
24
|
+
yield
|
|
33
25
|
end
|
|
34
26
|
|
|
35
|
-
def record_request_queue_time(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return unless request_queue_duration.valid_header_present?
|
|
39
|
-
|
|
40
|
-
queue_time_histogram.observe(labels, request_queue_duration.queue_time_seconds)
|
|
27
|
+
def record_request_queue_time(env:)
|
|
28
|
+
queue_time_seconds = QueueTimeDuration.new(env:).queue_time_seconds
|
|
29
|
+
queue_time_seconds && queue_time_histogram.observe(label_builder.call(env), queue_time_seconds)
|
|
41
30
|
end
|
|
42
31
|
|
|
43
32
|
def register_metrics!
|
|
44
33
|
registry.histogram(REQUEST_QUEUE_TIME_HISTOGRAM_NAME,
|
|
45
|
-
"A histogram of request queue time", {}, queue_time_buckets)
|
|
34
|
+
"A histogram of request queue time", {}, Promenade.configuration.queue_time_buckets)
|
|
46
35
|
end
|
|
47
36
|
|
|
48
37
|
def queue_time_histogram
|
|
@@ -3,40 +3,36 @@ module Promenade
|
|
|
3
3
|
module Rack
|
|
4
4
|
class QueueTimeDuration
|
|
5
5
|
REQUEST_START_HEADER = "HTTP_X_REQUEST_START".freeze
|
|
6
|
-
|
|
7
6
|
QUEUE_START_HEADER = "HTTP_X_QUEUE_START".freeze
|
|
8
7
|
|
|
9
8
|
HEADER_VALUE_MATCHER = /^(?:t=)(?<timestamp>\d{10}(?:\.\d+))$/
|
|
10
9
|
|
|
11
|
-
def initialize(env:, request_received_time:)
|
|
12
|
-
@
|
|
13
|
-
@
|
|
14
|
-
@valid_header_present = @request_queued_time_ms.is_a?(Float)
|
|
15
|
-
@request_received_time_ms = request_received_time.utc.to_f
|
|
16
|
-
|
|
10
|
+
def initialize(env:, request_received_time: Time.now.utc)
|
|
11
|
+
@request_enqueued_time = request_enqueued_time_from(env)
|
|
12
|
+
@request_received_time = request_received_time.utc.to_f
|
|
17
13
|
freeze
|
|
18
14
|
end
|
|
19
15
|
|
|
20
|
-
def valid_header_present?
|
|
21
|
-
@valid_header_present
|
|
22
|
-
end
|
|
23
|
-
|
|
24
16
|
def queue_time_seconds
|
|
25
|
-
|
|
17
|
+
# Enqueued time could not be parsed from headers
|
|
18
|
+
return unless request_enqueued_time
|
|
19
|
+
|
|
20
|
+
# A negative queue time is not valid
|
|
21
|
+
return if queue_time < 0
|
|
26
22
|
|
|
27
23
|
queue_time.round(3)
|
|
28
24
|
end
|
|
29
25
|
|
|
30
26
|
private
|
|
31
27
|
|
|
32
|
-
attr_reader :
|
|
28
|
+
attr_reader :request_enqueued_time, :request_received_time
|
|
33
29
|
|
|
34
30
|
def queue_time
|
|
35
|
-
|
|
31
|
+
request_received_time - request_enqueued_time
|
|
36
32
|
end
|
|
37
33
|
|
|
38
|
-
def
|
|
39
|
-
header_value =
|
|
34
|
+
def request_enqueued_time_from(env)
|
|
35
|
+
header_value = env.values_at(REQUEST_START_HEADER, QUEUE_START_HEADER).compact.first
|
|
40
36
|
return if header_value.nil?
|
|
41
37
|
|
|
42
38
|
header_time_match = header_value.to_s.match(HEADER_VALUE_MATCHER)
|
|
@@ -15,11 +15,14 @@ module Promenade
|
|
|
15
15
|
|
|
16
16
|
SEPARATOR = "#".freeze
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
|
19
|
+
|
|
20
|
+
private_constant :PARAMS_KEY, :CONTROLLER, :ACTION, :UNKNOWN, :SEPARATOR, :REQUEST_METHOD
|
|
19
21
|
|
|
20
22
|
def call(env)
|
|
21
23
|
super.merge({
|
|
22
24
|
controller_action: controller_action_from_env(env),
|
|
25
|
+
method: env[REQUEST_METHOD].to_s.downcase,
|
|
23
26
|
})
|
|
24
27
|
end
|
|
25
28
|
|
|
@@ -5,15 +5,12 @@ module Promenade
|
|
|
5
5
|
require_relative "singleton_caller"
|
|
6
6
|
extend SingletonCaller
|
|
7
7
|
|
|
8
|
-
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
|
9
|
-
|
|
10
8
|
HTTP_HOST = "HTTP_HOST".freeze
|
|
11
9
|
|
|
12
|
-
private_constant :
|
|
10
|
+
private_constant :HTTP_HOST
|
|
13
11
|
|
|
14
12
|
def call(env)
|
|
15
13
|
{
|
|
16
|
-
method: env[REQUEST_METHOD].to_s.downcase,
|
|
17
14
|
host: env[HTTP_HOST].to_s,
|
|
18
15
|
}
|
|
19
16
|
end
|
data/lib/promenade/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: promenade
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.12.
|
|
4
|
+
version: 0.12.19
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ed Robinson
|
|
@@ -311,7 +311,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
311
311
|
- !ruby/object:Gem::Version
|
|
312
312
|
version: '0'
|
|
313
313
|
requirements: []
|
|
314
|
-
rubygems_version: 4.0.
|
|
314
|
+
rubygems_version: 4.0.6
|
|
315
315
|
specification_version: 4
|
|
316
316
|
summary: Promenade makes it simple to instrument Ruby apps for prometheus scraping
|
|
317
317
|
test_files: []
|