prometheus_exporter 2.0.2 → 2.0.4

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: 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