prometheus_exporter 2.0.2 → 2.0.3
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/CHANGELOG +6 -0
- data/README.md +28 -0
- data/lib/prometheus_exporter/instrumentation/method_profiler.rb +61 -24
- data/lib/prometheus_exporter/instrumentation/sidekiq.rb +20 -12
- data/lib/prometheus_exporter/metric/histogram.rb +3 -3
- data/lib/prometheus_exporter/middleware.rb +8 -6
- data/lib/prometheus_exporter/server/web_server.rb +3 -1
- data/lib/prometheus_exporter/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: 07f116c0fdf15835af1b9520ca81b47c1d7a6011b3b8991cf9b569127d7a22fc
|
4
|
+
data.tar.gz: f631d2d3259d72577e896a4f2a650f7af65e70b5703f24a02cbcebadd75f97d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81894939793a45953be6efbf90db9506abb6cc5ca196e03fd0bfe92035430284240820e1311e3f9532933602fa337790eb828e99809f003378cb6290b74f1eba
|
7
|
+
data.tar.gz: 151c49417eee04b2f9b931d9bda160fd19df22d2d9ff474f2f54b57bc828504eaf2d2f047eae2be1f121ce4d0b3ed74e9695959bcd51da8a52949da8a3931604
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
2.0.3 - 2022-05-23
|
2
|
+
|
3
|
+
- FEATURE: new ping endpoint for keepalive checks
|
4
|
+
- FIX: order histogram correctly for GCP support
|
5
|
+
- FIX: improve sidekiq instrumentation
|
6
|
+
|
1
7
|
2.0.2 - 2022-02-25
|
2
8
|
|
3
9
|
- FIX: runner was not requiring unicorn integration correctly leading to a crash
|
data/README.md
CHANGED
@@ -201,6 +201,14 @@ Ensure you run the exporter in a monitored background process:
|
|
201
201
|
$ bundle exec prometheus_exporter
|
202
202
|
```
|
203
203
|
|
204
|
+
#### Choosing the style of method patching
|
205
|
+
|
206
|
+
By default, `prometheus_exporter` uses `alias_method` to instrument methods used by SQL and Redis as it is the fastest approach (see [this article](https://samsaffron.com/archive/2017/10/18/fastest-way-to-profile-a-method-in-ruby)). You may desire to add additional instrumentation libraries beyond `prometheus_exporter` to your app. This can become problematic if these other libraries instead use `prepend` to instrument methods. To resolve this, you can tell the middleware to instrument using `prepend` by passing an `instrument` option like so:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
Rails.application.middleware.unshift PrometheusExporter::Middleware, instrument: :prepend
|
210
|
+
```
|
211
|
+
|
204
212
|
#### Metrics collected by Rails integration middleware
|
205
213
|
|
206
214
|
| Type | Name | Description |
|
@@ -952,6 +960,26 @@ You can also pass a log level (default is [`Logger::WARN`](https://ruby-doc.org/
|
|
952
960
|
PrometheusExporter::Client.new(log_level: Logger::DEBUG)
|
953
961
|
```
|
954
962
|
|
963
|
+
## Docker/Kubernetes Healthcheck
|
964
|
+
|
965
|
+
A `/ping` endpoint which only returns `PONG` is available so you can run container healthchecks :
|
966
|
+
|
967
|
+
Example:
|
968
|
+
|
969
|
+
```yml
|
970
|
+
services:
|
971
|
+
rails-exporter:
|
972
|
+
command:
|
973
|
+
- bin/prometheus_exporter
|
974
|
+
- -b
|
975
|
+
- 0.0.0.0
|
976
|
+
healthcheck:
|
977
|
+
test: ["CMD", "curl", "--silent", "--show-error", "--fail", "--max-time", "3", "http://0.0.0.0:9394/ping"]
|
978
|
+
timeout: 3s
|
979
|
+
interval: 10s
|
980
|
+
retries: 5
|
981
|
+
```
|
982
|
+
|
955
983
|
## Contributing
|
956
984
|
|
957
985
|
Bug reports and pull requests are welcome on GitHub at https://github.com/discourse/prometheus_exporter. 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.
|
@@ -4,30 +4,14 @@
|
|
4
4
|
module PrometheusExporter::Instrumentation; end
|
5
5
|
|
6
6
|
class PrometheusExporter::Instrumentation::MethodProfiler
|
7
|
-
def self.patch(klass, methods, name)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
return #{method_name}__mp_unpatched(*args, &blk)
|
16
|
-
end
|
17
|
-
begin
|
18
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
19
|
-
#{method_name}__mp_unpatched(*args, &blk)
|
20
|
-
ensure
|
21
|
-
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
22
|
-
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
23
|
-
data[:calls] += 1
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
RUBY
|
28
|
-
end.join("\n")
|
29
|
-
|
30
|
-
klass.class_eval patches, __FILE__, patch_source_line
|
7
|
+
def self.patch(klass, methods, name, instrument:)
|
8
|
+
if instrument == :alias_method
|
9
|
+
patch_using_alias_method(klass, methods, name)
|
10
|
+
elsif instrument == :prepend
|
11
|
+
patch_using_prepend(klass, methods, name)
|
12
|
+
else
|
13
|
+
raise ArgumentError, "instrument must be :alias_method or :prepend"
|
14
|
+
end
|
31
15
|
end
|
32
16
|
|
33
17
|
def self.transfer
|
@@ -55,4 +39,57 @@ class PrometheusExporter::Instrumentation::MethodProfiler
|
|
55
39
|
end
|
56
40
|
data
|
57
41
|
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def self.patch_using_prepend(klass, methods, name)
|
46
|
+
prepend_instument = Module.new
|
47
|
+
patch_source_line = __LINE__ + 3
|
48
|
+
patches = methods.map do |method_name|
|
49
|
+
<<~RUBY
|
50
|
+
def #{method_name}(*args, &blk)
|
51
|
+
unless prof = Thread.current[:_method_profiler]
|
52
|
+
return super
|
53
|
+
end
|
54
|
+
begin
|
55
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
56
|
+
super
|
57
|
+
ensure
|
58
|
+
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
59
|
+
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
60
|
+
data[:calls] += 1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
RUBY
|
64
|
+
end.join("\n")
|
65
|
+
|
66
|
+
prepend_instument.module_eval patches, __FILE__, patch_source_line
|
67
|
+
klass.prepend(prepend_instument)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.patch_using_alias_method(klass, methods, name)
|
71
|
+
patch_source_line = __LINE__ + 3
|
72
|
+
patches = methods.map do |method_name|
|
73
|
+
<<~RUBY
|
74
|
+
unless defined?(#{method_name}__mp_unpatched)
|
75
|
+
alias_method :#{method_name}__mp_unpatched, :#{method_name}
|
76
|
+
def #{method_name}(*args, &blk)
|
77
|
+
unless prof = Thread.current[:_method_profiler]
|
78
|
+
return #{method_name}__mp_unpatched(*args, &blk)
|
79
|
+
end
|
80
|
+
begin
|
81
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
82
|
+
#{method_name}__mp_unpatched(*args, &blk)
|
83
|
+
ensure
|
84
|
+
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
85
|
+
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
86
|
+
data[:calls] += 1
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
RUBY
|
91
|
+
end.join("\n")
|
92
|
+
|
93
|
+
klass.class_eval patches, __FILE__, patch_source_line
|
94
|
+
end
|
58
95
|
end
|
@@ -16,12 +16,12 @@ module PrometheusExporter::Instrumentation
|
|
16
16
|
job_is_fire_and_forget = job["retry"] == false
|
17
17
|
|
18
18
|
worker_class = Object.const_get(job["class"])
|
19
|
-
worker_custom_labels = self.get_worker_custom_labels(worker_class)
|
19
|
+
worker_custom_labels = self.get_worker_custom_labels(worker_class, job)
|
20
20
|
|
21
21
|
unless job_is_fire_and_forget
|
22
22
|
PrometheusExporter::Client.default.send_json(
|
23
23
|
type: "sidekiq",
|
24
|
-
name: job["class"],
|
24
|
+
name: get_name(job["class"], job),
|
25
25
|
dead: true,
|
26
26
|
custom_labels: worker_custom_labels
|
27
27
|
)
|
@@ -29,12 +29,21 @@ module PrometheusExporter::Instrumentation
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
def self.get_worker_custom_labels(worker_class)
|
33
|
-
worker_class.respond_to?(:custom_labels)
|
32
|
+
def self.get_worker_custom_labels(worker_class, msg)
|
33
|
+
return {} unless worker_class.respond_to?(:custom_labels)
|
34
|
+
|
35
|
+
# TODO remove when version 3.0.0 is released
|
36
|
+
method_arity = worker_class.method(:custom_labels).arity
|
37
|
+
|
38
|
+
if method_arity > 0
|
39
|
+
worker_class.custom_labels(msg)
|
40
|
+
else
|
41
|
+
worker_class.custom_labels
|
42
|
+
end
|
34
43
|
end
|
35
44
|
|
36
|
-
def initialize(client: nil)
|
37
|
-
@client = client || PrometheusExporter::Client.default
|
45
|
+
def initialize(options = { client: nil })
|
46
|
+
@client = options.fetch(:client, nil) || PrometheusExporter::Client.default
|
38
47
|
end
|
39
48
|
|
40
49
|
def call(worker, msg, queue)
|
@@ -51,19 +60,18 @@ module PrometheusExporter::Instrumentation
|
|
51
60
|
duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
|
52
61
|
@client.send_json(
|
53
62
|
type: "sidekiq",
|
54
|
-
name: get_name(worker, msg),
|
63
|
+
name: self.class.get_name(worker.class.to_s, msg),
|
55
64
|
queue: queue,
|
56
65
|
success: success,
|
57
66
|
shutdown: shutdown,
|
58
67
|
duration: duration,
|
59
|
-
custom_labels: self.class.get_worker_custom_labels(worker.class)
|
68
|
+
custom_labels: self.class.get_worker_custom_labels(worker.class, msg)
|
60
69
|
)
|
61
70
|
end
|
62
71
|
|
63
72
|
private
|
64
73
|
|
65
|
-
def get_name(
|
66
|
-
class_name = worker.class.to_s
|
74
|
+
def self.get_name(class_name, msg)
|
67
75
|
if class_name == JOB_WRAPPER_CLASS_NAME
|
68
76
|
get_job_wrapper_name(msg)
|
69
77
|
elsif DELAYED_CLASS_NAMES.include?(class_name)
|
@@ -73,11 +81,11 @@ module PrometheusExporter::Instrumentation
|
|
73
81
|
end
|
74
82
|
end
|
75
83
|
|
76
|
-
def get_job_wrapper_name(msg)
|
84
|
+
def self.get_job_wrapper_name(msg)
|
77
85
|
msg['wrapped']
|
78
86
|
end
|
79
87
|
|
80
|
-
def get_delayed_name(msg, class_name)
|
88
|
+
def self.get_delayed_name(msg, class_name)
|
81
89
|
begin
|
82
90
|
# fallback to class_name since we're relying on the internal implementation
|
83
91
|
# of the delayed extensions
|
@@ -19,7 +19,7 @@ module PrometheusExporter::Metric
|
|
19
19
|
|
20
20
|
def initialize(name, help, opts = {})
|
21
21
|
super(name, help)
|
22
|
-
@buckets = (opts[:buckets] || self.class.default_buckets).sort
|
22
|
+
@buckets = (opts[:buckets] || self.class.default_buckets).sort
|
23
23
|
reset!
|
24
24
|
end
|
25
25
|
|
@@ -57,11 +57,11 @@ module PrometheusExporter::Metric
|
|
57
57
|
first = false
|
58
58
|
count = @counts[labels]
|
59
59
|
sum = @sums[labels]
|
60
|
-
text << "#{prefix(@name)}_bucket#{labels_text(with_bucket(labels, "+Inf"))} #{count}\n"
|
61
60
|
@buckets.each do |bucket|
|
62
61
|
value = @observations[labels][bucket]
|
63
62
|
text << "#{prefix(@name)}_bucket#{labels_text(with_bucket(labels, bucket.to_s))} #{value}\n"
|
64
63
|
end
|
64
|
+
text << "#{prefix(@name)}_bucket#{labels_text(with_bucket(labels, "+Inf"))} #{count}\n"
|
65
65
|
text << "#{prefix(@name)}_count#{labels_text(labels)} #{count}\n"
|
66
66
|
text << "#{prefix(@name)}_sum#{labels_text(labels)} #{sum}"
|
67
67
|
end
|
@@ -91,7 +91,7 @@ module PrometheusExporter::Metric
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def fill_buckets(value, buckets)
|
94
|
-
@buckets.each do |b|
|
94
|
+
@buckets.reverse.each do |b|
|
95
95
|
break if value > b
|
96
96
|
buckets[b] += 1
|
97
97
|
end
|
@@ -6,23 +6,25 @@ require 'prometheus_exporter/client'
|
|
6
6
|
class PrometheusExporter::Middleware
|
7
7
|
MethodProfiler = PrometheusExporter::Instrumentation::MethodProfiler
|
8
8
|
|
9
|
-
def initialize(app, config = { instrument:
|
9
|
+
def initialize(app, config = { instrument: :alias_method, client: nil })
|
10
10
|
@app = app
|
11
11
|
@client = config[:client] || PrometheusExporter::Client.default
|
12
12
|
|
13
13
|
if config[:instrument]
|
14
14
|
if defined? Redis::Client
|
15
|
-
MethodProfiler.patch(Redis::Client, [
|
15
|
+
MethodProfiler.patch(Redis::Client, [
|
16
|
+
:call, :call_pipeline
|
17
|
+
], :redis, instrument: config[:instrument])
|
16
18
|
end
|
17
19
|
if defined? PG::Connection
|
18
20
|
MethodProfiler.patch(PG::Connection, [
|
19
21
|
:exec, :async_exec, :exec_prepared, :send_query_prepared, :query
|
20
|
-
], :sql)
|
22
|
+
], :sql, instrument: config[:instrument])
|
21
23
|
end
|
22
24
|
if defined? Mysql2::Client
|
23
|
-
MethodProfiler.patch(Mysql2::Client, [:query], :sql)
|
24
|
-
MethodProfiler.patch(Mysql2::Statement, [:execute], :sql)
|
25
|
-
MethodProfiler.patch(Mysql2::Result, [:each], :sql)
|
25
|
+
MethodProfiler.patch(Mysql2::Client, [:query], :sql, instrument: config[:instrument])
|
26
|
+
MethodProfiler.patch(Mysql2::Statement, [:execute], :sql, instrument: config[:instrument])
|
27
|
+
MethodProfiler.patch(Mysql2::Result, [:each], :sql, instrument: config[:instrument])
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
@@ -73,9 +73,11 @@ module PrometheusExporter::Server
|
|
73
73
|
end
|
74
74
|
elsif req.path == '/send-metrics'
|
75
75
|
handle_metrics(req, res)
|
76
|
+
elsif req.path == '/ping'
|
77
|
+
res.body = 'PONG'
|
76
78
|
else
|
77
79
|
res.status = 404
|
78
|
-
res.body = "Not Found! The Prometheus Ruby Exporter only listens on /metrics and /send-metrics"
|
80
|
+
res.body = "Not Found! The Prometheus Ruby Exporter only listens on /ping, /metrics and /send-metrics"
|
79
81
|
end
|
80
82
|
end
|
81
83
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prometheus_exporter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-05-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: webrick
|