prometheus_exporter 2.0.2 → 2.0.4

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: f4f6fca67df8cdcd10a86edb00a89cc61a807af54f80794a6573bea5a8805bc0
4
+ data.tar.gz: 66e4867922235be2bb18df90b8ecbf3ad6b3d08d4ad92f62219ea26d80ca2515
5
5
  SHA512:
6
- metadata.gz: 26c52753448d360c229e0827a08f42e8bcc3cbd602caf7c530d0f7306b1c0d848a8604aa2959115c97aae1eaf41ebf1b1b8bad1735ddf89a11c7d4174d62c71c
7
- data.tar.gz: c80255a6d646371390f2f3157b736878a1a2df6b0a45c0905ec518debf4fe33f1a4dfaa02203f24141132991ef1f570114417120b25b37d949b0c343da7448bf
6
+ metadata.gz: 7a31e11d9ba63709048ccebe7352f636f601800dd19d00aa3e29448a22d1e0de6436882d32ecb2e5911e16df0409acf1b1fb71d351f67afd38b3fb6e39976099
7
+ data.tar.gz: ba2c7fb01384299fa8f16656dd106ba99e82c721aca7d6442cd8f8a19a0b0ce82ff2b39935eeb0d8e19c096e5436c564ee1defe813dcf93bd3c41e6c0fc8dedc
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ 2.0.4 - 2022-11-10
2
+
3
+ - FIX/FEATURE: support for Redis 5 gem instrumentation
4
+
5
+ 2.0.3 - 2022-05-23
6
+
7
+ - FEATURE: new ping endpoint for keepalive checks
8
+ - FIX: order histogram correctly for GCP support
9
+ - FIX: improve sidekiq instrumentation
10
+
1
11
  2.0.2 - 2022-02-25
2
12
 
3
13
  - FIX: runner was not requiring unicorn integration correctly leading to a crash
data/README.md CHANGED
@@ -5,7 +5,7 @@ Prometheus Exporter allows you to aggregate custom metrics from multiple process
5
5
  To learn more see [Instrumenting Rails with Prometheus](https://samsaffron.com/archive/2018/02/02/instrumenting-rails-with-prometheus) (it has pretty pictures!)
6
6
 
7
7
  * [Requirements](#requirements)
8
- * [Migrating from v0.x](#migrating-from-v0.x)
8
+ * [Migrating from v0.x](#migrating-from-v0x)
9
9
  * [Installation](#installation)
10
10
  * [Usage](#usage)
11
11
  * [Single process mode](#single-process-mode)
@@ -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.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class MyCustomCollector < PrometheusExporter::Server::Collector
3
+ class MyCustomCollector < PrometheusExporter::Server::BaseCollector
4
4
  def initialize
5
5
  @gauge1 = PrometheusExporter::Metric::Gauge.new("thing1", "I am thing 1")
6
6
  @gauge2 = PrometheusExporter::Metric::Gauge.new("thing2", "I am thing 2")
@@ -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,59 @@ class PrometheusExporter::Instrumentation::MethodProfiler
55
39
  end
56
40
  data
57
41
  end
42
+
43
+ def self.define_methods_on_module(klass, methods, name)
44
+ patch_source_line = __LINE__ + 3
45
+ patches = methods.map do |method_name|
46
+ <<~RUBY
47
+ def #{method_name}(*args, &blk)
48
+ unless prof = Thread.current[:_method_profiler]
49
+ return super
50
+ end
51
+ begin
52
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
53
+ super
54
+ ensure
55
+ data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
56
+ data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
57
+ data[:calls] += 1
58
+ end
59
+ end
60
+ RUBY
61
+ end.join("\n")
62
+
63
+ klass.module_eval patches, __FILE__, patch_source_line
64
+ end
65
+
66
+ def self.patch_using_prepend(klass, methods, name)
67
+ prepend_instument = Module.new
68
+ define_methods_on_module(klass, methods, name)
69
+ klass.prepend(prepend_instument)
70
+ end
71
+
72
+ def self.patch_using_alias_method(klass, methods, name)
73
+ patch_source_line = __LINE__ + 3
74
+ patches = methods.map do |method_name|
75
+ <<~RUBY
76
+ unless defined?(#{method_name}__mp_unpatched)
77
+ alias_method :#{method_name}__mp_unpatched, :#{method_name}
78
+ def #{method_name}(*args, &blk)
79
+ unless prof = Thread.current[:_method_profiler]
80
+ return #{method_name}__mp_unpatched(*args, &blk)
81
+ end
82
+ begin
83
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
84
+ #{method_name}__mp_unpatched(*args, &blk)
85
+ ensure
86
+ data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
87
+ data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
88
+ data[:calls] += 1
89
+ end
90
+ end
91
+ end
92
+ RUBY
93
+ end.join("\n")
94
+
95
+ klass.class_eval patches, __FILE__, patch_source_line
96
+ end
58
97
  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,30 @@ 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
- if defined? Redis::Client
15
- MethodProfiler.patch(Redis::Client, [:call, :call_pipeline], :redis)
14
+ if defined?(RedisClient)
15
+ apply_redis_client_middleware!
16
+ end
17
+ if defined?(Redis::VERSION) && (Gem::Version.new(Redis::VERSION) >= Gem::Version.new('5.0.0'))
18
+ # redis 5 support handled via RedisClient
19
+ elsif defined? Redis::Client
20
+ MethodProfiler.patch(Redis::Client, [
21
+ :call, :call_pipeline
22
+ ], :redis, instrument: config[:instrument])
16
23
  end
17
24
  if defined? PG::Connection
18
25
  MethodProfiler.patch(PG::Connection, [
19
26
  :exec, :async_exec, :exec_prepared, :send_query_prepared, :query
20
- ], :sql)
27
+ ], :sql, instrument: config[:instrument])
21
28
  end
22
29
  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)
30
+ MethodProfiler.patch(Mysql2::Client, [:query], :sql, instrument: config[:instrument])
31
+ MethodProfiler.patch(Mysql2::Statement, [:execute], :sql, instrument: config[:instrument])
32
+ MethodProfiler.patch(Mysql2::Result, [:each], :sql, instrument: config[:instrument])
26
33
  end
27
34
  end
28
35
  end
@@ -114,4 +121,14 @@ class PrometheusExporter::Middleware
114
121
 
115
122
  end
116
123
 
124
+ private
125
+
126
+ module RedisInstrumenter
127
+ MethodProfiler.define_methods_on_module(self, ["call", "call_pipelined"], "redis")
128
+ end
129
+
130
+ def apply_redis_client_middleware!
131
+ RedisClient.register(RedisInstrumenter)
132
+ end
133
+
117
134
  end
@@ -60,7 +60,7 @@ module PrometheusExporter::Server
60
60
  custom_labels = obj['custom_labels']
61
61
  labels = custom_labels.nil? ? default_labels : default_labels.merge(custom_labels)
62
62
 
63
- @http_requests_total.observe(1, labels.merge(status: obj["status"]))
63
+ @http_requests_total.observe(1, labels.merge("status" => obj["status"]))
64
64
 
65
65
  if timings = obj["timings"]
66
66
  @http_request_duration_seconds.observe(timings["total_duration"], labels)
@@ -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.4'
5
5
  end
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency "rubocop", ">= 0.69"
30
30
  spec.add_development_dependency "bundler", ">= 2.1.4"
31
31
  spec.add_development_dependency "rake", "~> 13.0"
32
- spec.add_development_dependency "minitest", "~> 5.0"
32
+ spec.add_development_dependency "minitest", "~> 5.15.0" # https://github.com/qrush/m/issues/93
33
33
  spec.add_development_dependency "guard", "~> 2.0"
34
34
  spec.add_development_dependency "mini_racer", "~> 0.5.0"
35
35
  spec.add_development_dependency "guard-minitest", "~> 2.0"
@@ -39,6 +39,8 @@ Gem::Specification.new do |spec|
39
39
  spec.add_development_dependency "rubocop-discourse", ">2"
40
40
  spec.add_development_dependency "appraisal", "~> 2.3"
41
41
  spec.add_development_dependency "activerecord", "~> 6.0.0"
42
+ spec.add_development_dependency "redis", "> 5"
43
+ spec.add_development_dependency "m"
42
44
  if !RUBY_ENGINE == 'jruby'
43
45
  spec.add_development_dependency "raindrops", "~> 0.19"
44
46
  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.4
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-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: webrick
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '5.0'
75
+ version: 5.15.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '5.0'
82
+ version: 5.15.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: guard
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +206,34 @@ dependencies:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
208
  version: 6.0.0
209
+ - !ruby/object:Gem::Dependency
210
+ name: redis
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">"
214
+ - !ruby/object:Gem::Version
215
+ version: '5'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">"
221
+ - !ruby/object:Gem::Version
222
+ version: '5'
223
+ - !ruby/object:Gem::Dependency
224
+ name: m
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
209
237
  description: Prometheus metric collector and exporter for Ruby
210
238
  email:
211
239
  - sam.saffron@gmail.com