prometheus_exporter 2.0.2 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
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