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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5cd7d7181c0dcb2e26efe99605bd72e6f4747394018a74b8eee93a126a10c2ee
4
- data.tar.gz: 94d70aa9f50ef97fee06ed8fa5963588ba55ee98ab702d50136abbbba18b9f56
3
+ metadata.gz: 07f116c0fdf15835af1b9520ca81b47c1d7a6011b3b8991cf9b569127d7a22fc
4
+ data.tar.gz: f631d2d3259d72577e896a4f2a650f7af65e70b5703f24a02cbcebadd75f97d1
5
5
  SHA512:
6
- metadata.gz: 26c52753448d360c229e0827a08f42e8bcc3cbd602caf7c530d0f7306b1c0d848a8604aa2959115c97aae1eaf41ebf1b1b8bad1735ddf89a11c7d4174d62c71c
7
- data.tar.gz: c80255a6d646371390f2f3157b736878a1a2df6b0a45c0905ec518debf4fe33f1a4dfaa02203f24141132991ef1f570114417120b25b37d949b0c343da7448bf
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
- patch_source_line = __LINE__ + 3
9
- patches = methods.map do |method_name|
10
- <<~RUBY
11
- unless defined?(#{method_name}__mp_unpatched)
12
- alias_method :#{method_name}__mp_unpatched, :#{method_name}
13
- def #{method_name}(*args, &blk)
14
- unless prof = Thread.current[:_method_profiler]
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) ? worker_class.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(worker, msg)
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.reverse
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: true, client: nil })
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, [:call, :call_pipeline], :redis)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PrometheusExporter
4
- VERSION = '2.0.2'
4
+ VERSION = '2.0.3'
5
5
  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.2
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-02-25 00:00:00.000000000 Z
11
+ date: 2022-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: webrick