prometheus_exporter 0.7.0 → 0.8.0

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: 2f949348bbafc06b8f7a3dd3927693de8fef54090d85a7889afbfbf7dcb167b6
4
- data.tar.gz: adeca8000ebb59c7225c1453135900aeab5c1760902edd5b4fecf9fa4b879a57
3
+ metadata.gz: 562647915cd81e672056a83fc79a031d2a0ebb2b4a7a5e32f5786ae5c7d480a7
4
+ data.tar.gz: 525e28d0cbe4e853c91bb463970fd231b134dba6b5a190c61a2ff7710074fb7b
5
5
  SHA512:
6
- metadata.gz: 7d3829ad57f03a19f6081c3444f9b13906aedd4fa1c5b99237d454096e0d993c27c2df0993482c421b6034a58a55c9603620bcdf7698248e4ac23a5063d63718
7
- data.tar.gz: fb915716acbbb4f24a152c8ebf9eb35bdc97185a7144eb986e50aafad07a36d5cc83cbbbf7c16e8b399207e9c66d90973d2e6f9ddcdc8a17cab797840d13ed8b
6
+ metadata.gz: 324b3b0acd1c97e8b5535dff8f57500b4dc3f2433456372c609accbc78d1baf839d82a1e38eabb543f043bbc9fefb401ab1460ba252ee8b9330fe0a91acffc1d
7
+ data.tar.gz: b2a0082d4e2431cc1f24f11a6af86d6965e2db7c93df1021ed58b38ff8651c84d1e8939b42a13b1b7cac60c9237435ff838a24918ed0ee18d13c3df2aece76d4
data/Appraisals CHANGED
@@ -6,5 +6,5 @@ appraise "ar-60" do
6
6
  end
7
7
 
8
8
  appraise "ar-61" do
9
- gem "activerecord", "~> 6.1.0.rc2"
9
+ gem "activerecord", "~> 6.1.1"
10
10
  end
data/CHANGELOG CHANGED
@@ -1,3 +1,15 @@
1
+ 0.8.0 - 05-07-2021
2
+
3
+ - FIX: handle ThreadError more gracefully in cases where process shuts down
4
+ - FEATURE: add job_name and queue_name labels to delayed job metrics
5
+ - FEATURE: always scope puma metrics on hostname in collector
6
+ - FEATURE: add customizable labels option to puma collector
7
+ - FEATURE: support for Rescue
8
+ - DEV: Remove support for EOL ruby 2.5
9
+ - FIX: Add source location to MethodProfiler patches
10
+ - FEATURE: Improve Active Record instrumentation
11
+ - FEATURE: Support HTTP_X_AMZN_TRACE_ID when supplied
12
+
1
13
  0.7.0 - 29-12-2020
2
14
 
3
15
  - Dev: Removed support from EOL rubies, only 2.5, 2.6, 2.7 and 3.0 are supported now.
data/README.md CHANGED
@@ -19,6 +19,7 @@ To learn more see [Instrumenting Rails with Prometheus](https://samsaffron.com/a
19
19
  * [Hutch metrics](#hutch-message-processing-tracer)
20
20
  * [Puma metrics](#puma-metrics)
21
21
  * [Unicorn metrics](#unicorn-process-metrics)
22
+ * [Resque metrics](#resque-metrics)
22
23
  * [Custom type collectors](#custom-type-collectors)
23
24
  * [Multi process mode with custom collector](#multi-process-mode-with-custom-collector)
24
25
  * [GraphQL support](#graphql-support)
@@ -478,6 +479,8 @@ end
478
479
  | Summary | `delayed_job_duration_seconds_summary` | Summary of the time it takes jobs to execute | `status` |
479
480
  | Summary | `delayed_job_attempts_summary` | Summary of the amount of attempts it takes delayed jobs to succeed | - |
480
481
 
482
+ All metrics have labels for `job_name` and `queue_name`.
483
+
481
484
  #### Hutch Message Processing Tracer
482
485
 
483
486
  Capture [Hutch](https://github.com/gocardless/hutch) metrics (how many jobs ran? how many failed? how long did they take?)
@@ -505,7 +508,7 @@ Request Queueing is defined as the time it takes for a request to reach your app
505
508
 
506
509
  As this metric starts before `prometheus_exporter` can handle the request, you must add a specific HTTP header as early in your infrastructure as possible (we recommend your load balancer or reverse proxy).
507
510
 
508
- Configure your HTTP server / load balancer to add a header `X-Request-Start: t=<MSEC>` when passing the request upstream. For more information, please consult your software manual.
511
+ The Amazon Application Load Balancer [request tracing header](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-request-tracing.html) is natively supported. If you are using another upstream entrypoint, you may configure your HTTP server / load balancer to add a header `X-Request-Start: t=<MSEC>` when passing the request upstream. For more information, please consult your software manual.
509
512
 
510
513
  Hint: we aim to be API-compatible with the big APM solutions, so if you've got requests queueing time configured for them, it should be expected to also work with `prometheus_exporter`.
511
514
 
@@ -535,7 +538,29 @@ end
535
538
  | Gauge | `puma_thread_pool_capacity_total` | Number of puma threads available at current scale |
536
539
  | Gauge | `puma_max_threads_total` | Number of puma threads at available at max scale |
537
540
 
538
- All metrics may have a `phase` label.
541
+ All metrics may have a `phase` label and all custom labels provided with the `labels` option.
542
+
543
+ ### Resque metrics
544
+
545
+ The resque metrics are using the `Resque.info` method, which queries Redis internally. To start monitoring your resque
546
+ installation, you'll need to start the instrumentation:
547
+
548
+ ```ruby
549
+ # e.g. config/initializers/resque.rb
550
+ require 'prometheus_exporter/instrumentation'
551
+ PrometheusExporter::Instrumentation::Resque.start
552
+ ```
553
+
554
+ #### Metrics collected by Resque Instrumentation
555
+
556
+ | Type | Name | Description |
557
+ | --- | --- | --- |
558
+ | Gauge | `processed_jobs_total` | Total number of processed Resque jobs |
559
+ | Gauge | `failed_jobs_total` | Total number of failed Resque jobs |
560
+ | Gauge | `pending_jobs_total` | Total number of pending Resque jobs |
561
+ | Gauge | `queues_total` | Total number of Resque queues |
562
+ | Gauge | `workers_total` | Total number of Resque workers running |
563
+ | Gauge | `working_total` | Total number of Resque workers working |
539
564
 
540
565
  ### Unicorn process metrics
541
566
 
@@ -184,6 +184,9 @@ module PrometheusExporter
184
184
  end
185
185
  end
186
186
  end
187
+ rescue ThreadError => e
188
+ raise unless e.message =~ /can't alloc thread/
189
+ STDERR.puts "Prometheus Exporter, failed to send message ThreadError #{e}"
187
190
  end
188
191
 
189
192
  def close_socket!
@@ -11,3 +11,4 @@ require_relative "instrumentation/hutch"
11
11
  require_relative "instrumentation/unicorn"
12
12
  require_relative "instrumentation/active_record"
13
13
  require_relative "instrumentation/shoryuken"
14
+ require_relative "instrumentation/resque"
@@ -81,15 +81,13 @@ module PrometheusExporter::Instrumentation
81
81
  private
82
82
 
83
83
  def labels(pool)
84
- if pool.respond_to?(:spec) # ActiveRecord <= 6.0
84
+ if ::ActiveRecord.version < Gem::Version.new("6.1.0.rc1")
85
85
  @metric_labels.merge(pool_name: pool.spec.name).merge(pool.spec.config
86
86
  .select { |k, v| @config_labels.include? k }
87
87
  .map { |k, v| [k.to_s.dup.prepend("dbconfig_"), v] }.to_h)
88
- elsif pool.respond_to?(:db_config) # ActiveRecord >= 6.1.rc1
88
+ else
89
89
  @metric_labels.merge(pool_name: pool.db_config.name).merge(
90
90
  @config_labels.each_with_object({}) { |l, acc| acc["dbconfig_#{l}"] = pool.db_config.public_send(l) })
91
- else
92
- raise "Unsupported connection pool"
93
91
  end
94
92
  end
95
93
  end
@@ -13,8 +13,8 @@ module PrometheusExporter::Instrumentation
13
13
  callbacks do |lifecycle|
14
14
  lifecycle.around(:invoke_job) do |job, *args, &block|
15
15
  max_attempts = Delayed::Worker.max_attempts
16
- enqueued_count = Delayed::Job.count
17
- pending_count = Delayed::Job.where(attempts: 0, locked_at: nil).count
16
+ enqueued_count = Delayed::Job.where(queue: job.queue).count
17
+ pending_count = Delayed::Job.where(attempts: 0, locked_at: nil, queue: job.queue).count
18
18
  instrumenter.call(job, max_attempts, enqueued_count, pending_count, *args, &block)
19
19
  end
20
20
  end
@@ -41,6 +41,7 @@ module PrometheusExporter::Instrumentation
41
41
  @client.send_json(
42
42
  type: "delayed_job",
43
43
  name: job.handler.to_s.match(JOB_CLASS_REGEXP).to_a[1].to_s,
44
+ queue_name: job.queue,
44
45
  success: success,
45
46
  duration: duration,
46
47
  attempts: attempts,
@@ -5,6 +5,7 @@ module PrometheusExporter::Instrumentation; end
5
5
 
6
6
  class PrometheusExporter::Instrumentation::MethodProfiler
7
7
  def self.patch(klass, methods, name)
8
+ patch_source_line = __LINE__ + 3
8
9
  patches = methods.map do |method_name|
9
10
  <<~RUBY
10
11
  unless defined?(#{method_name}__mp_unpatched)
@@ -26,7 +27,7 @@ class PrometheusExporter::Instrumentation::MethodProfiler
26
27
  RUBY
27
28
  end.join("\n")
28
29
 
29
- klass.class_eval patches
30
+ klass.class_eval patches, __FILE__, patch_source_line
30
31
  end
31
32
 
32
33
  def self.transfer
@@ -5,8 +5,8 @@ require "json"
5
5
  # collects stats from puma
6
6
  module PrometheusExporter::Instrumentation
7
7
  class Puma
8
- def self.start(client: nil, frequency: 30)
9
- puma_collector = new
8
+ def self.start(client: nil, frequency: 30, labels: {})
9
+ puma_collector = new(labels)
10
10
  client ||= PrometheusExporter::Client.default
11
11
  Thread.new do
12
12
  while true
@@ -22,13 +22,25 @@ module PrometheusExporter::Instrumentation
22
22
  end
23
23
  end
24
24
 
25
+ def initialize(metric_labels = {})
26
+ @metric_labels = metric_labels
27
+ end
28
+
25
29
  def collect
26
- metric = {}
27
- metric[:type] = "puma"
30
+ metric = {
31
+ pid: pid,
32
+ type: "puma",
33
+ hostname: ::PrometheusExporter.hostname,
34
+ metric_labels: @metric_labels
35
+ }
28
36
  collect_puma_stats(metric)
29
37
  metric
30
38
  end
31
39
 
40
+ def pid
41
+ @pid = ::Process.pid
42
+ end
43
+
32
44
  def collect_puma_stats(metric)
33
45
  stats = JSON.parse(::Puma.stats)
34
46
 
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # collects stats from resque
4
+ module PrometheusExporter::Instrumentation
5
+ class Resque
6
+ def self.start(client: nil, frequency: 30)
7
+ resque_collector = new
8
+ client ||= PrometheusExporter::Client.default
9
+ Thread.new do
10
+ while true
11
+ begin
12
+ client.send_json(resque_collector.collect)
13
+ rescue => e
14
+ STDERR.puts("Prometheus Exporter Failed To Collect Resque Stats #{e}")
15
+ ensure
16
+ sleep frequency
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def collect
23
+ metric = {}
24
+ metric[:type] = "resque"
25
+ collect_resque_stats(metric)
26
+ metric
27
+ end
28
+
29
+ def collect_resque_stats(metric)
30
+ info = ::Resque.info
31
+
32
+ metric[:processed_jobs_total] = info[:processed]
33
+ metric[:failed_jobs_total] = info[:failed]
34
+ metric[:pending_jobs_total] = info[:pending]
35
+ metric[:queues_total] = info[:queues]
36
+ metric[:worker_total] = info[:workers]
37
+ metric[:working_total] = info[:working]
38
+ end
39
+ end
40
+ end
@@ -90,19 +90,24 @@ class PrometheusExporter::Middleware
90
90
  Process.clock_gettime(Process::CLOCK_REALTIME)
91
91
  end
92
92
 
93
- # get the content of the x-queue-start or x-request-start header
93
+ # determine queue start from well-known trace headers
94
94
  def queue_start(env)
95
+
96
+ # get the content of the x-queue-start or x-request-start header
95
97
  value = env['HTTP_X_REQUEST_START'] || env['HTTP_X_QUEUE_START']
96
98
  unless value.nil? || value == ''
97
- convert_header_to_ms(value.to_s)
99
+ # nginx returns time as milliseconds with 3 decimal places
100
+ # apache returns time as microseconds without decimal places
101
+ # this method takes care to convert both into a proper second + fractions timestamp
102
+ value = value.to_s.gsub(/t=|\./, '')
103
+ return "#{value[0, 10]}.#{value[10, 13]}".to_f
98
104
  end
99
- end
100
105
 
101
- # nginx returns time as milliseconds with 3 decimal places
102
- # apache returns time as microseconds without decimal places
103
- # this method takes care to convert both into a proper second + fractions timestamp
104
- def convert_header_to_ms(str)
105
- str = str.gsub(/t=|\./, '')
106
- "#{str[0, 10]}.#{str[10, 13]}".to_f
106
+ # get the content of the x-amzn-trace-id header
107
+ # see also: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-request-tracing.html
108
+ value = env['HTTP_X_AMZN_TRACE_ID']
109
+ value&.split('Root=')&.last&.split('-')&.fetch(1)&.to_i(16)
110
+
107
111
  end
112
+
108
113
  end
@@ -16,3 +16,4 @@ require_relative "server/hutch_collector"
16
16
  require_relative "server/unicorn_collector"
17
17
  require_relative "server/active_record_collector"
18
18
  require_relative "server/shoryuken_collector"
19
+ require_relative "server/resque_collector"
@@ -47,7 +47,8 @@ module PrometheusExporter::Server
47
47
  obj["created_at"] = now
48
48
 
49
49
  @active_record_metrics.delete_if do |current|
50
- (obj["pid"] == current["pid"] && obj["hostname"] == current["hostname"]) ||
50
+ (obj["pid"] == current["pid"] && obj["hostname"] == current["hostname"] &&
51
+ obj["metric_labels"]["pool_name"] == current["metric_labels"]["pool_name"]) ||
51
52
  (current["created_at"] + MAX_ACTIVERECORD_METRIC_AGE < now)
52
53
  end
53
54
 
@@ -20,6 +20,7 @@ module PrometheusExporter::Server
20
20
  register_collector(UnicornCollector.new)
21
21
  register_collector(ActiveRecordCollector.new)
22
22
  register_collector(ShoryukenCollector.new)
23
+ register_collector(ResqueCollector.new)
23
24
  end
24
25
 
25
26
  def register_collector(collector)
@@ -19,21 +19,22 @@ module PrometheusExporter::Server
19
19
  end
20
20
 
21
21
  def collect(obj)
22
- default_labels = { job_name: obj['name'] }
22
+ default_labels = { job_name: obj['name'], queue_name: obj['queue_name'] }
23
23
  custom_labels = obj['custom_labels']
24
+
24
25
  labels = custom_labels.nil? ? default_labels : default_labels.merge(custom_labels)
25
26
 
26
27
  ensure_delayed_job_metrics
27
28
  @delayed_job_duration_seconds.observe(obj["duration"], labels)
28
29
  @delayed_jobs_total.observe(1, labels)
29
30
  @delayed_failed_jobs_total.observe(1, labels) if !obj["success"]
30
- @delayed_jobs_max_attempts_reached_total.observe(1) if obj["attempts"] >= obj["max_attempts"]
31
- @delayed_job_duration_seconds_summary.observe(obj["duration"])
32
- @delayed_job_duration_seconds_summary.observe(obj["duration"], status: "success") if obj["success"]
33
- @delayed_job_duration_seconds_summary.observe(obj["duration"], status: "failed") if !obj["success"]
34
- @delayed_job_attempts_summary.observe(obj["attempts"]) if obj["success"]
35
- @delayed_jobs_enqueued.observe(obj["enqueued"])
36
- @delayed_jobs_pending.observe(obj["pending"])
31
+ @delayed_jobs_max_attempts_reached_total.observe(1, labels) if obj["attempts"] >= obj["max_attempts"]
32
+ @delayed_job_duration_seconds_summary.observe(obj["duration"], labels)
33
+ @delayed_job_duration_seconds_summary.observe(obj["duration"], labels.merge(status: "success")) if obj["success"]
34
+ @delayed_job_duration_seconds_summary.observe(obj["duration"], labels.merge(status: "failed")) if !obj["success"]
35
+ @delayed_job_attempts_summary.observe(obj["attempts"], labels) if obj["success"]
36
+ @delayed_jobs_enqueued.observe(obj["enqueued"], labels)
37
+ @delayed_jobs_pending.observe(obj["pending"], labels)
37
38
  end
38
39
 
39
40
  def metrics
@@ -34,6 +34,9 @@ module PrometheusExporter::Server
34
34
  if m["custom_labels"]
35
35
  labels.merge!(m["custom_labels"])
36
36
  end
37
+ if m["metric_labels"]
38
+ labels.merge!(m["metric_labels"])
39
+ end
37
40
 
38
41
  PUMA_GAUGES.map do |k, help|
39
42
  k = k.to_s
@@ -51,7 +54,12 @@ module PrometheusExporter::Server
51
54
  now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
52
55
 
53
56
  obj["created_at"] = now
54
- @puma_metrics.delete_if { |m| m["created_at"] + MAX_PUMA_METRIC_AGE < now }
57
+
58
+ @puma_metrics.delete_if do |current|
59
+ (obj["pid"] == current["pid"] && obj["hostname"] == current["hostname"]) ||
60
+ (current["created_at"] + MAX_PUMA_METRIC_AGE < now)
61
+ end
62
+
55
63
  @puma_metrics << obj
56
64
  end
57
65
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrometheusExporter::Server
4
+ class ResqueCollector < TypeCollector
5
+ MAX_RESQUE_METRIC_AGE = 30
6
+ RESQUE_GAUGES = {
7
+ processed_jobs_total: "Total number of processed Resque jobs.",
8
+ failed_jobs_total: "Total number of failed Resque jobs.",
9
+ pending_jobs_total: "Total number of pending Resque jobs.",
10
+ queues_total: "Total number of Resque queues.",
11
+ workers_total: "Total number of Resque workers running.",
12
+ working_total: "Total number of Resque workers working."
13
+ }
14
+
15
+ def initialize
16
+ @resque_metrics = []
17
+ @gauges = {}
18
+ end
19
+
20
+ def type
21
+ "resque"
22
+ end
23
+
24
+ def metrics
25
+ return [] if resque_metrics.length == 0
26
+
27
+ resque_metrics.map do |metric|
28
+ labels = metric.fetch("custom_labels", {})
29
+
30
+ RESQUE_GAUGES.map do |name, help|
31
+ name = name.to_s
32
+ if value = metric[name]
33
+ gauge = gauges[name] ||= PrometheusExporter::Metric::Gauge.new("resque_#{name}", help)
34
+ gauge.observe(value, labels)
35
+ end
36
+ end
37
+ end
38
+
39
+ gauges.values
40
+ end
41
+
42
+ def collect(object)
43
+ now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
44
+
45
+ object["created_at"] = now
46
+ resque_metrics.delete_if { |metric| metric["created_at"] + MAX_RESQUE_METRIC_AGE < now }
47
+ resque_metrics << object
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :resque_metrics, :gauges
53
+ end
54
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PrometheusExporter
4
- VERSION = '0.7.0'
4
+ VERSION = '0.8.0'
5
5
  end
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency "webrick"
28
28
 
29
29
  spec.add_development_dependency "rubocop", ">= 0.69"
30
- spec.add_development_dependency "bundler", ">= 2.2.2"
30
+ spec.add_development_dependency "bundler", ">= 2.1.4"
31
31
  spec.add_development_dependency "rake", "~> 13.0"
32
32
  spec.add_development_dependency "minitest", "~> 5.0"
33
33
  spec.add_development_dependency "guard", "~> 2.0"
@@ -42,5 +42,5 @@ Gem::Specification.new do |spec|
42
42
  if !RUBY_ENGINE == 'jruby'
43
43
  spec.add_development_dependency "raindrops", "~> 0.19"
44
44
  end
45
- spec.required_ruby_version = '>= 2.5.0'
45
+ spec.required_ruby_version = '>= 2.6.0'
46
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: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-28 00:00:00.000000000 Z
11
+ date: 2021-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: webrick
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 2.2.2
47
+ version: 2.1.4
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 2.2.2
54
+ version: 2.1.4
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -240,6 +240,7 @@ files:
240
240
  - lib/prometheus_exporter/instrumentation/method_profiler.rb
241
241
  - lib/prometheus_exporter/instrumentation/process.rb
242
242
  - lib/prometheus_exporter/instrumentation/puma.rb
243
+ - lib/prometheus_exporter/instrumentation/resque.rb
243
244
  - lib/prometheus_exporter/instrumentation/shoryuken.rb
244
245
  - lib/prometheus_exporter/instrumentation/sidekiq.rb
245
246
  - lib/prometheus_exporter/instrumentation/sidekiq_queue.rb
@@ -259,6 +260,7 @@ files:
259
260
  - lib/prometheus_exporter/server/hutch_collector.rb
260
261
  - lib/prometheus_exporter/server/process_collector.rb
261
262
  - lib/prometheus_exporter/server/puma_collector.rb
263
+ - lib/prometheus_exporter/server/resque_collector.rb
262
264
  - lib/prometheus_exporter/server/runner.rb
263
265
  - lib/prometheus_exporter/server/shoryuken_collector.rb
264
266
  - lib/prometheus_exporter/server/sidekiq_collector.rb
@@ -282,14 +284,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
282
284
  requirements:
283
285
  - - ">="
284
286
  - !ruby/object:Gem::Version
285
- version: 2.5.0
287
+ version: 2.6.0
286
288
  required_rubygems_version: !ruby/object:Gem::Requirement
287
289
  requirements:
288
290
  - - ">="
289
291
  - !ruby/object:Gem::Version
290
292
  version: '0'
291
293
  requirements: []
292
- rubygems_version: 3.2.2
294
+ rubygems_version: 3.1.6
293
295
  signing_key:
294
296
  specification_version: 4
295
297
  summary: Prometheus Exporter