prometheus_exporter 2.0.8 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +51 -4
  3. data/.rubocop +1 -0
  4. data/.rubocop.yml +12 -1
  5. data/Appraisals +6 -2
  6. data/CHANGELOG +12 -0
  7. data/README.md +45 -8
  8. data/bin/prometheus_exporter +2 -2
  9. data/gemfiles/ar_70.gemfile +7 -0
  10. data/gemfiles/ar_71.gemfile +7 -0
  11. data/lib/prometheus_exporter/client.rb +34 -19
  12. data/lib/prometheus_exporter/instrumentation/delayed_job.rb +8 -5
  13. data/lib/prometheus_exporter/instrumentation/good_job.rb +30 -0
  14. data/lib/prometheus_exporter/instrumentation/method_profiler.rb +4 -4
  15. data/lib/prometheus_exporter/instrumentation/process.rb +2 -0
  16. data/lib/prometheus_exporter/instrumentation/sidekiq.rb +16 -14
  17. data/lib/prometheus_exporter/instrumentation.rb +1 -0
  18. data/lib/prometheus_exporter/metric/histogram.rb +1 -1
  19. data/lib/prometheus_exporter/middleware.rb +1 -1
  20. data/lib/prometheus_exporter/server/active_record_collector.rb +9 -13
  21. data/lib/prometheus_exporter/server/collector.rb +1 -0
  22. data/lib/prometheus_exporter/server/delayed_job_collector.rb +7 -1
  23. data/lib/prometheus_exporter/server/good_job_collector.rb +52 -0
  24. data/lib/prometheus_exporter/server/process_collector.rb +10 -13
  25. data/lib/prometheus_exporter/server/puma_collector.rb +1 -1
  26. data/lib/prometheus_exporter/server/resque_collector.rb +3 -7
  27. data/lib/prometheus_exporter/server/sidekiq_process_collector.rb +2 -2
  28. data/lib/prometheus_exporter/server/sidekiq_queue_collector.rb +2 -2
  29. data/lib/prometheus_exporter/server/sidekiq_stats_collector.rb +2 -2
  30. data/lib/prometheus_exporter/server/unicorn_collector.rb +32 -33
  31. data/lib/prometheus_exporter/server/web_server.rb +65 -39
  32. data/lib/prometheus_exporter/server.rb +1 -0
  33. data/lib/prometheus_exporter/version.rb +1 -1
  34. data/lib/prometheus_exporter.rb +12 -13
  35. data/prometheus_exporter.gemspec +5 -7
  36. metadata +19 -16
  37. data/.github/workflows/docker.yml +0 -47
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af71a5656b5fe6a90de6fe6ad1b76efa05ccfb67d4fdb3cdcc740f539097546f
4
- data.tar.gz: cd6ef704ecdc7110d5f78dd999354e1ea093adcba51f2355a0211e99e0fb3c67
3
+ metadata.gz: e5381fb78e6440d77a61269f3bf7c40a84b44a53594d687db4a683be3cb82725
4
+ data.tar.gz: 18216fbeae6978d5427fa9f97a67bbf3f0ee36e92091a2606b2dadcaa6497d83
5
5
  SHA512:
6
- metadata.gz: adf3c09a5609699bd055094bc509e997e320a72833b7c2beca03186d0c3d3fa0fa65fc005708b7faf496a9bf32c68c489cc7a1985525eed7a35ceb6b2d53a9df
7
- data.tar.gz: 3975585eb3842c5cf409793d9257a184e33af062d4dd45e5bf64164bbba88c87d14aaea946bed519f9db1034afb8695fbdeecef337286bcaf00132cd2cd7a5f2
6
+ metadata.gz: 626d1f7c05e5bf21bc499e69fd1ab0d73c94c5a565ac7eae81ec9d43fc93f6cf96b83a6fd72b25846e49371079a9c87a497e719d74c20dfa57c0fb5cbe4507e5
7
+ data.tar.gz: b43a57442d6326648cbc3539c50400556575e425756358d45ad39832e099671ca617ea66b6754d16d66693b9fbdf9045b96a95713b36f1f135ab8ab0e38a6d0b
@@ -8,6 +8,13 @@ on:
8
8
  schedule:
9
9
  - cron: "0 0 * * 0" # weekly
10
10
 
11
+ permissions:
12
+ contents: write
13
+ packages: write
14
+
15
+ env:
16
+ DOCKER_REPO: ghcr.io/discourse/prometheus_exporter
17
+
11
18
  jobs:
12
19
  build:
13
20
  runs-on: ubuntu-latest
@@ -20,15 +27,16 @@ jobs:
20
27
  strategy:
21
28
  fail-fast: false
22
29
  matrix:
23
- ruby: ['2.6', '2.7', '3.0', '3.1', '3.2']
24
- activerecord: [60, 61]
30
+ ruby: ['3.1', '3.2', '3.3']
31
+ activerecord: [61, 70, 71]
25
32
 
26
33
  steps:
27
- - uses: actions/checkout@v2
34
+ - uses: actions/checkout@v4
28
35
 
29
36
  - uses: ruby/setup-ruby@v1
30
37
  with:
31
38
  ruby-version: ${{ matrix.ruby }}
39
+ bundler: latest
32
40
  bundler-cache: true
33
41
 
34
42
  - name: Rubocop
@@ -42,12 +50,51 @@ jobs:
42
50
  needs: build
43
51
  runs-on: ubuntu-latest
44
52
 
53
+ outputs:
54
+ new_version_published: ${{ steps.release.outputs.new_version }}
55
+
45
56
  steps:
46
- - uses: actions/checkout@v2
57
+ - uses: actions/checkout@v4
47
58
 
48
59
  - name: Release gem
60
+ id: release
49
61
  uses: discourse/publish-rubygems-action@v2
50
62
  env:
51
63
  RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
52
64
  GIT_EMAIL: team@discourse.org
53
65
  GIT_NAME: discoursebot
66
+
67
+ publish_docker:
68
+ needs: publish
69
+ if: needs.publish.outputs.new_version_published == 'true'
70
+ runs-on: ubuntu-latest
71
+ timeout-minutes: 20
72
+
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+ - uses: docker/setup-qemu-action@v2
76
+ - uses: docker/setup-buildx-action@v2
77
+
78
+ - name: Set vars
79
+ id: vars
80
+ run: |
81
+ ruby -r ./lib/prometheus_exporter/version.rb -e 'print "version=#{PrometheusExporter::VERSION}"' >> $GITHUB_OUTPUT
82
+
83
+ - name: Login to Github Container Registry
84
+ uses: docker/login-action@v2
85
+ with:
86
+ registry: ghcr.io
87
+ username: ${{ github.actor }}
88
+ password: ${{ secrets.GITHUB_TOKEN }}
89
+
90
+ - name: Build and push images
91
+ uses: docker/build-push-action@v3
92
+ with:
93
+ context: .
94
+ push: true
95
+ platforms: linux/amd64,linux/arm64
96
+ build-args: |
97
+ GEM_VERSION=${{ steps.vars.outputs.version }}
98
+ tags: |
99
+ ${{ env.DOCKER_REPO }}:${{ steps.vars.outputs.version }}
100
+ ${{ env.DOCKER_REPO }}:latest
data/.rubocop ADDED
@@ -0,0 +1 @@
1
+ --ignore-unrecognized-cops
data/.rubocop.yml CHANGED
@@ -4,4 +4,15 @@ inherit_gem:
4
4
  AllCops:
5
5
  Exclude:
6
6
  - 'gemfiles/**/*'
7
- - 'vendor/**/*'
7
+ - 'vendor/**/*'
8
+
9
+ Discourse/Plugins/NoMonkeyPatching:
10
+ Enabled: false
11
+
12
+ Discourse/Plugins/NamespaceMethods:
13
+ Exclude:
14
+ - bin/prometheus_exporter
15
+
16
+ Style/InvertibleUnlessCondition:
17
+ Exclude:
18
+ - '*.gemspec'
data/Appraisals CHANGED
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  appraise "ar-60" do
4
- # we are using this version as default in gemspec
5
- # gem "activerecord", "~> 6.0.0"
4
+ gem "activerecord", "~> 6.0.0"
6
5
  end
7
6
 
8
7
  appraise "ar-61" do
9
8
  gem "activerecord", "~> 6.1.1"
10
9
  end
10
+
11
+ appraise "ar-70" do
12
+ # latest version
13
+ gem "activerecord", "~> 7.1.2"
14
+ end
data/CHANGELOG CHANGED
@@ -1,3 +1,15 @@
1
+ 2.1.1 - 2024-06-19
2
+
3
+ - FEATURE: improve good_job instrumentation
4
+ - FIX: improve Ruby 3.X support
5
+ - FEATURE: imstrumentation for malloc / oldmalloc increace in GC stats
6
+
7
+ 2.1.0 - 2024-01-08
8
+
9
+ - FEATURE: good_job instrumentation
10
+ - PERF: improve performance of histogram
11
+ - DEV: use new metric collector pattern so we reuse code between collectors
12
+
1
13
  2.0.8 - 2023-01-20
2
14
 
3
15
  - FEATURE: attempting to make our first docker release
data/README.md CHANGED
@@ -21,6 +21,7 @@ To learn more see [Instrumenting Rails with Prometheus](https://samsaffron.com/a
21
21
  * [Puma metrics](#puma-metrics)
22
22
  * [Unicorn metrics](#unicorn-process-metrics)
23
23
  * [Resque metrics](#resque-metrics)
24
+ * [GoodJob metrics](#goodjob-metrics)
24
25
  * [Custom type collectors](#custom-type-collectors)
25
26
  * [Multi process mode with custom collector](#multi-process-mode-with-custom-collector)
26
27
  * [GraphQL support](#graphql-support)
@@ -28,7 +29,7 @@ To learn more see [Instrumenting Rails with Prometheus](https://samsaffron.com/a
28
29
  * [Client default labels](#client-default-labels)
29
30
  * [Client default host](#client-default-host)
30
31
  * [Histogram mode](#histogram-mode)
31
- * [Histogram - custom buckets](#histogram-custom-buckets)
32
+ * [Histogram - custom buckets](#histogram---custom-buckets)
32
33
  * [Transport concerns](#transport-concerns)
33
34
  * [JSON generation and parsing](#json-generation-and-parsing)
34
35
  * [Logging](#logging)
@@ -39,7 +40,7 @@ To learn more see [Instrumenting Rails with Prometheus](https://samsaffron.com/a
39
40
 
40
41
  ## Requirements
41
42
 
42
- Minimum Ruby of version 2.6.0 is required, Ruby 2.5.0 is EOL as of March 31st 2021.
43
+ Minimum Ruby of version 3.0.0 is required, Ruby 2.7 is EOL as of March 31st 2023.
43
44
 
44
45
  ## Migrating from v0.x
45
46
 
@@ -188,7 +189,7 @@ gem 'prometheus_exporter'
188
189
  In an initializer:
189
190
 
190
191
  ```ruby
191
- unless Rails.env == "test"
192
+ unless Rails.env.test?
192
193
  require 'prometheus_exporter/middleware'
193
194
 
194
195
  # This reports stats per request like HTTP status and timings
@@ -341,7 +342,7 @@ You may also be interested in per-process stats. This collects memory and GC sta
341
342
 
342
343
  ```ruby
343
344
  # in an initializer
344
- unless Rails.env == "test"
345
+ unless Rails.env.test?
345
346
  require 'prometheus_exporter/instrumentation'
346
347
 
347
348
  # this reports basic process stats like RSS and GC info
@@ -522,7 +523,7 @@ All metrics have labels for `job_name` and `queue_name`.
522
523
  In an initializer:
523
524
 
524
525
  ```ruby
525
- unless Rails.env == "test"
526
+ unless Rails.env.test?
526
527
  require 'prometheus_exporter/instrumentation'
527
528
  PrometheusExporter::Instrumentation::DelayedJob.register_plugin
528
529
  end
@@ -533,6 +534,7 @@ end
533
534
  | Type | Name | Description | Labels |
534
535
  | --- | --- | --- | --- |
535
536
  | Counter | `delayed_job_duration_seconds` | Total time spent in delayed jobs | `job_name` |
537
+ | Counter | `delayed_job_latency_seconds_total` | Total delayed jobs latency | `job_name` |
536
538
  | Counter | `delayed_jobs_total` | Total number of delayed jobs executed | `job_name` |
537
539
  | Gauge | `delayed_jobs_enqueued` | Number of enqueued delayed jobs | - |
538
540
  | Gauge | `delayed_jobs_pending` | Number of pending delayed jobs | - |
@@ -542,13 +544,14 @@ end
542
544
  | Summary | `delayed_job_attempts_summary` | Summary of the amount of attempts it takes delayed jobs to succeed | - |
543
545
 
544
546
  All metrics have labels for `job_name` and `queue_name`.
547
+ `delayed_job_latency_seconds_total` is considering delayed job's [sleep_delay](https://github.com/collectiveidea/delayed_job#:~:text=If%20no%20jobs%20are%20found%2C%20the%20worker%20sleeps%20for%20the%20amount%20of%20time%20specified%20by%20the%20sleep%20delay%20option.%20Set%20Delayed%3A%3AWorker.sleep_delay%20%3D%2060%20for%20a%2060%20second%20sleep%20time.) parameter, so please be aware of this in case you are looking for high latency precision.
545
548
 
546
549
  #### Hutch Message Processing Tracer
547
550
 
548
551
  Capture [Hutch](https://github.com/gocardless/hutch) metrics (how many jobs ran? how many failed? how long did they take?)
549
552
 
550
553
  ```ruby
551
- unless Rails.env == "test"
554
+ unless Rails.env.test?
552
555
  require 'prometheus_exporter/instrumentation'
553
556
  Hutch::Config.set(:tracer, PrometheusExporter::Instrumentation::Hutch)
554
557
  end
@@ -570,7 +573,7 @@ Request Queueing is defined as the time it takes for a request to reach your app
570
573
 
571
574
  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).
572
575
 
573
- 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.
576
+ 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. Please keep in mind request time start is reported as epoch time (in seconds) and lacks precision, which may introduce additional latency in reported metrics. For more information, please consult your software manual.
574
577
 
575
578
  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`.
576
579
 
@@ -580,6 +583,17 @@ The puma metrics are using the `Puma.stats` method and hence need to be started
580
583
  workers has been booted and from a Puma thread otherwise the metrics won't be accessible.
581
584
  The easiest way to gather this metrics is to put the following in your `puma.rb` config:
582
585
 
586
+ For Puma single mode
587
+ ```ruby
588
+ # puma.rb config
589
+ require 'prometheus_exporter/instrumentation'
590
+ # optional check, avoids spinning up and down threads per worker
591
+ if !PrometheusExporter::Instrumentation::Puma.started?
592
+ PrometheusExporter::Instrumentation::Puma.start
593
+ end
594
+ ```
595
+
596
+ For Puma clustered mode
583
597
  ```ruby
584
598
  # puma.rb config
585
599
  after_worker_boot do
@@ -627,6 +641,29 @@ PrometheusExporter::Instrumentation::Resque.start
627
641
  | Gauge | `resque_workers` | Total number of Resque workers running |
628
642
  | Gauge | `resque_working` | Total number of Resque workers working |
629
643
 
644
+ ### GoodJob metrics
645
+
646
+ The metrics are generated from the database using the relevant scopes. To start monitoring your GoodJob
647
+ installation, you'll need to start the instrumentation:
648
+
649
+ ```ruby
650
+ # e.g. config/initializers/good_job.rb
651
+ require 'prometheus_exporter/instrumentation'
652
+ PrometheusExporter::Instrumentation::GoodJob.start
653
+ ```
654
+
655
+ #### Metrics collected by GoodJob Instrumentation
656
+
657
+ | Type | Name | Description |
658
+ | --- |----------------------|-----------------------------------------|
659
+ | Gauge | `good_job_scheduled` | Total number of scheduled GoodJob jobs. |
660
+ | Gauge | `good_job_retried` | Total number of retried GoodJob jobs. |
661
+ | Gauge | `good_job_queued` | Total number of queued GoodJob jobs. |
662
+ | Gauge | `good_job_running` | Total number of running GoodJob jobs. |
663
+ | Gauge | `good_job_finished` | Total number of finished GoodJob jobs. |
664
+ | Gauge | `good_job_succeeded` | Total number of succeeded GoodJob jobs. |
665
+ | Gauge | `good_job_discarded` | Total number of discarded GoodJob jobs |
666
+
630
667
  ### Unicorn process metrics
631
668
 
632
669
  In order to gather metrics from unicorn processes, we use `rainbows`, which exposes `Rainbows::Linux.tcp_listener_stats` to gather information about active workers and queued requests. To start monitoring your unicorn processes, you'll need to know both the path to unicorn PID file and the listen address (`pid_file` and `listen` in your unicorn config file)
@@ -858,7 +895,7 @@ prometheus_exporter -p 8080 \
858
895
  --prefix 'foo_'
859
896
  ```
860
897
 
861
- You can use `-b` option to bind the `prometheus_exporter` web server to any IPv4 interface with `-b 0.0.0.0`,
898
+ You can use `-b` option to bind the `prometheus_exporter` web server to any IPv4 interface with `-b 0.0.0.0`,
862
899
  any IPv6 interface with `-b ::`, or `-b ANY` to any IPv4/IPv6 interfaces available on your host system.
863
900
 
864
901
  #### Enabling Basic Authentication
@@ -74,7 +74,7 @@ def run
74
74
  end.parse!
75
75
 
76
76
  logger = Logger.new(options[:logger_path])
77
- logger.level = Logger::WARN
77
+ logger.level = Logger::INFO
78
78
 
79
79
  if options.has_key?(:realm) && !options.has_key?(:auth)
80
80
  logger.warn "Providing REALM without AUTH has no effect"
@@ -121,7 +121,7 @@ def run
121
121
 
122
122
  runner = PrometheusExporter::Server::Runner.new(options)
123
123
 
124
- puts "#{Time.now} Starting prometheus exporter on #{runner.bind}:#{runner.port}"
124
+ logger.info "Starting prometheus exporter on #{runner.bind}:#{runner.port}"
125
125
  runner.start
126
126
  sleep
127
127
  end
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 7.0.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 7.1.0"
6
+
7
+ gemspec path: "../"
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'socket'
4
- require 'thread'
5
- require 'logger'
3
+ require "socket"
4
+ require "logger"
6
5
 
7
6
  module PrometheusExporter
8
7
  class Client
@@ -25,7 +24,9 @@ module PrometheusExporter
25
24
  keys: keys,
26
25
  value: value
27
26
  }
28
- values[:prometheus_exporter_action] = prometheus_exporter_action if prometheus_exporter_action
27
+ values[
28
+ :prometheus_exporter_action
29
+ ] = prometheus_exporter_action if prometheus_exporter_action
29
30
  values[:opts] = @opts if @opts
30
31
  values
31
32
  end
@@ -57,8 +58,11 @@ module PrometheusExporter
57
58
  attr_reader :logger
58
59
 
59
60
  def initialize(
60
- host: ENV.fetch('PROMETHEUS_EXPORTER_HOST', 'localhost'),
61
- port: ENV.fetch('PROMETHEUS_EXPORTER_PORT', PrometheusExporter::DEFAULT_PORT),
61
+ host: ENV.fetch("PROMETHEUS_EXPORTER_HOST", "localhost"),
62
+ port: ENV.fetch(
63
+ "PROMETHEUS_EXPORTER_PORT",
64
+ PrometheusExporter::DEFAULT_PORT
65
+ ),
62
66
  max_queue_size: nil,
63
67
  thread_sleep: 0.5,
64
68
  json_serializer: nil,
@@ -90,7 +94,8 @@ module PrometheusExporter
90
94
  @mutex = Mutex.new
91
95
  @thread_sleep = thread_sleep
92
96
 
93
- @json_serializer = json_serializer == :oj ? PrometheusExporter::OjCompat : JSON
97
+ @json_serializer =
98
+ json_serializer == :oj ? PrometheusExporter::OjCompat : JSON
94
99
 
95
100
  @custom_labels = custom_labels
96
101
  end
@@ -100,7 +105,14 @@ module PrometheusExporter
100
105
  end
101
106
 
102
107
  def register(type, name, help, opts = nil)
103
- metric = RemoteMetric.new(type: type, name: name, help: help, client: self, opts: opts)
108
+ metric =
109
+ RemoteMetric.new(
110
+ type: type,
111
+ name: name,
112
+ help: help,
113
+ client: self,
114
+ opts: opts
115
+ )
104
116
  @metrics << metric
105
117
  metric
106
118
  end
@@ -161,9 +173,7 @@ module PrometheusExporter
161
173
  @mutex.synchronize do
162
174
  wait_for_empty_queue_with_timeout(wait_timeout_seconds)
163
175
  @worker_thread&.kill
164
- while @worker_thread&.alive?
165
- sleep 0.001
166
- end
176
+ sleep 0.001 while @worker_thread&.alive?
167
177
  @worker_thread = nil
168
178
  close_socket!
169
179
  end
@@ -183,12 +193,13 @@ module PrometheusExporter
183
193
  @mutex.synchronize do
184
194
  return if @worker_thread&.alive?
185
195
 
186
- @worker_thread = Thread.new do
187
- while true
188
- worker_loop
189
- sleep @thread_sleep
196
+ @worker_thread =
197
+ Thread.new do
198
+ while true
199
+ worker_loop
200
+ sleep @thread_sleep
201
+ end
190
202
  end
191
- end
192
203
  end
193
204
  end
194
205
  rescue ThreadError => e
@@ -212,7 +223,8 @@ module PrometheusExporter
212
223
  end
213
224
 
214
225
  def close_socket_if_old!
215
- if @socket_pid == Process.pid && @socket && @socket_started && ((@socket_started + MAX_SOCKET_AGE) < Time.now.to_f)
226
+ if @socket_pid == Process.pid && @socket && @socket_started &&
227
+ ((@socket_started + MAX_SOCKET_AGE) < Time.now.to_f)
216
228
  close_socket!
217
229
  end
218
230
  end
@@ -240,7 +252,7 @@ module PrometheusExporter
240
252
  end
241
253
 
242
254
  nil
243
- rescue
255
+ rescue StandardError
244
256
  @socket = nil
245
257
  @socket_started = nil
246
258
  @socket_pid = nil
@@ -250,7 +262,10 @@ module PrometheusExporter
250
262
  def wait_for_empty_queue_with_timeout(timeout_seconds)
251
263
  start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
252
264
  while @queue.length > 0
253
- break if start_time + timeout_seconds < ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
265
+ if start_time + timeout_seconds <
266
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
267
+ break
268
+ end
254
269
  sleep(0.05)
255
270
  end
256
271
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module PrometheusExporter::Instrumentation
4
4
  class DelayedJob
5
- JOB_CLASS_REGEXP = %r{job_class: (\w+:{0,2})+}.freeze
5
+ JOB_CLASS_REGEXP = %r{job_class: ((\w+:{0,2})+)}.freeze
6
6
 
7
7
  class << self
8
- def register_plugin(client: nil)
8
+ def register_plugin(client: nil, include_module_name: false)
9
9
  instrumenter = self.new(client: client)
10
10
  return unless defined?(Delayed::Plugin)
11
11
 
@@ -15,7 +15,8 @@ module PrometheusExporter::Instrumentation
15
15
  max_attempts = Delayed::Worker.max_attempts
16
16
  enqueued_count = Delayed::Job.where(queue: job.queue).count
17
17
  pending_count = Delayed::Job.where(attempts: 0, locked_at: nil, queue: job.queue).count
18
- instrumenter.call(job, max_attempts, enqueued_count, pending_count, *args, &block)
18
+ instrumenter.call(job, max_attempts, enqueued_count, pending_count, include_module_name,
19
+ *args, &block)
19
20
  end
20
21
  end
21
22
  end
@@ -28,9 +29,10 @@ module PrometheusExporter::Instrumentation
28
29
  @client = client || PrometheusExporter::Client.default
29
30
  end
30
31
 
31
- def call(job, max_attempts, enqueued_count, pending_count, *args, &block)
32
+ def call(job, max_attempts, enqueued_count, pending_count, include_module_name, *args, &block)
32
33
  success = false
33
34
  start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
35
+ latency = Time.current - job.run_at
34
36
  attempts = job.attempts + 1 # Increment because we're adding the current attempt
35
37
  result = block.call(job, *args)
36
38
  success = true
@@ -40,10 +42,11 @@ module PrometheusExporter::Instrumentation
40
42
 
41
43
  @client.send_json(
42
44
  type: "delayed_job",
43
- name: job.handler.to_s.match(JOB_CLASS_REGEXP).to_a[1].to_s,
45
+ name: job.handler.to_s.match(JOB_CLASS_REGEXP).to_a[include_module_name ? 1 : 2].to_s,
44
46
  queue_name: job.queue,
45
47
  success: success,
46
48
  duration: duration,
49
+ latency: latency,
47
50
  attempts: attempts,
48
51
  max_attempts: max_attempts,
49
52
  enqueued: enqueued_count,
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # collects stats from GoodJob
4
+ module PrometheusExporter::Instrumentation
5
+ class GoodJob < PeriodicStats
6
+ def self.start(client: nil, frequency: 30)
7
+ good_job_collector = new
8
+ client ||= PrometheusExporter::Client.default
9
+
10
+ worker_loop do
11
+ client.send_json(good_job_collector.collect)
12
+ end
13
+
14
+ super
15
+ end
16
+
17
+ def collect
18
+ {
19
+ type: "good_job",
20
+ scheduled: ::GoodJob::Job.scheduled.size,
21
+ retried: ::GoodJob::Job.retried.size,
22
+ queued: ::GoodJob::Job.queued.size,
23
+ running: ::GoodJob::Job.running.size,
24
+ finished: ::GoodJob::Job.finished.size,
25
+ succeeded: ::GoodJob::Job.succeeded.size,
26
+ discarded: ::GoodJob::Job.discarded.size
27
+ }
28
+ end
29
+ end
30
+ end
@@ -44,7 +44,7 @@ class PrometheusExporter::Instrumentation::MethodProfiler
44
44
  patch_source_line = __LINE__ + 3
45
45
  patches = methods.map do |method_name|
46
46
  <<~RUBY
47
- def #{method_name}(*args, &blk)
47
+ def #{method_name}(...)
48
48
  unless prof = Thread.current[:_method_profiler]
49
49
  return super
50
50
  end
@@ -75,13 +75,13 @@ class PrometheusExporter::Instrumentation::MethodProfiler
75
75
  <<~RUBY
76
76
  unless defined?(#{method_name}__mp_unpatched)
77
77
  alias_method :#{method_name}__mp_unpatched, :#{method_name}
78
- def #{method_name}(*args, &blk)
78
+ def #{method_name}(...)
79
79
  unless prof = Thread.current[:_method_profiler]
80
- return #{method_name}__mp_unpatched(*args, &blk)
80
+ return #{method_name}__mp_unpatched(...)
81
81
  end
82
82
  begin
83
83
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
84
- #{method_name}__mp_unpatched(*args, &blk)
84
+ #{method_name}__mp_unpatched(...)
85
85
  ensure
86
86
  data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
87
87
  data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
@@ -63,6 +63,8 @@ module PrometheusExporter::Instrumentation
63
63
  metric[:major_gc_ops_total] = stat[:major_gc_count]
64
64
  metric[:minor_gc_ops_total] = stat[:minor_gc_count]
65
65
  metric[:allocated_objects_total] = stat[:total_allocated_objects]
66
+ metric[:malloc_increase_bytes_limit] = stat[:malloc_increase_bytes_limit]
67
+ metric[:oldmalloc_increase_bytes_limit] = stat[:oldmalloc_increase_bytes_limit]
66
68
  end
67
69
 
68
70
  def collect_v8_stats(metric)
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
3
+ require "yaml"
4
4
 
5
5
  module PrometheusExporter::Instrumentation
6
- JOB_WRAPPER_CLASS_NAME = 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'
7
- DELAYED_CLASS_NAMES = [
8
- 'Sidekiq::Extensions::DelayedClass',
9
- 'Sidekiq::Extensions::DelayedModel',
10
- 'Sidekiq::Extensions::DelayedMailer',
6
+ JOB_WRAPPER_CLASS_NAME =
7
+ "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
8
+ DELAYED_CLASS_NAMES = %w[
9
+ Sidekiq::Extensions::DelayedClass
10
+ Sidekiq::Extensions::DelayedModel
11
+ Sidekiq::Extensions::DelayedMailer
11
12
  ]
12
13
 
13
14
  class Sidekiq
14
15
  def self.death_handler
15
- -> (job, ex) do
16
+ ->(job, ex) do
16
17
  job_is_fire_and_forget = job["retry"] == false
17
18
 
18
19
  worker_class = Object.const_get(job["class"])
@@ -43,7 +44,8 @@ module PrometheusExporter::Instrumentation
43
44
  end
44
45
 
45
46
  def initialize(options = { client: nil })
46
- @client = options.fetch(:client, nil) || PrometheusExporter::Client.default
47
+ @client =
48
+ options.fetch(:client, nil) || PrometheusExporter::Client.default
47
49
  end
48
50
 
49
51
  def call(worker, msg, queue)
@@ -82,7 +84,7 @@ module PrometheusExporter::Instrumentation
82
84
  end
83
85
 
84
86
  def self.get_job_wrapper_name(msg)
85
- msg['wrapped']
87
+ msg["wrapped"]
86
88
  end
87
89
 
88
90
  def self.get_delayed_name(msg, class_name)
@@ -90,17 +92,17 @@ module PrometheusExporter::Instrumentation
90
92
  # fallback to class_name since we're relying on the internal implementation
91
93
  # of the delayed extensions
92
94
  # https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/extensions/class_methods.rb
93
- (target, method_name, _args) = YAML.load(msg['args'].first) # rubocop:disable Security/YAMLLoad
95
+ target, method_name, _args = YAML.load(msg["args"].first)
94
96
  if target.class == Class
95
97
  "#{target.name}##{method_name}"
96
98
  else
97
99
  "#{target.class.name}##{method_name}"
98
100
  end
99
101
  rescue Psych::DisallowedClass, ArgumentError
100
- parsed = Psych.parse(msg['args'].first)
102
+ parsed = Psych.parse(msg["args"].first)
101
103
  children = parsed.root.children
102
- target = (children[0].value || children[0].tag).sub('!', '')
103
- method_name = (children[1].value || children[1].tag).sub(':', '')
104
+ target = (children[0].value || children[0].tag).sub("!", "")
105
+ method_name = (children[1].value || children[1].tag).sub(":", "")
104
106
 
105
107
  if target && method_name
106
108
  "#{target}##{method_name}"
@@ -108,7 +110,7 @@ module PrometheusExporter::Instrumentation
108
110
  class_name
109
111
  end
110
112
  end
111
- rescue
113
+ rescue StandardError
112
114
  class_name
113
115
  end
114
116
  end
@@ -15,3 +15,4 @@ require_relative "instrumentation/unicorn"
15
15
  require_relative "instrumentation/active_record"
16
16
  require_relative "instrumentation/shoryuken"
17
17
  require_relative "instrumentation/resque"
18
+ require_relative "instrumentation/good_job"
@@ -91,7 +91,7 @@ module PrometheusExporter::Metric
91
91
  end
92
92
 
93
93
  def fill_buckets(value, buckets)
94
- @buckets.reverse.each do |b|
94
+ @buckets.reverse_each do |b|
95
95
  break if value > b
96
96
  buckets[b] += 1
97
97
  end
@@ -23,7 +23,7 @@ class PrometheusExporter::Middleware
23
23
  end
24
24
  if defined? PG::Connection
25
25
  MethodProfiler.patch(PG::Connection, [
26
- :exec, :async_exec, :exec_prepared, :send_query_prepared, :query
26
+ :exec, :async_exec, :exec_prepared, :exec_params, :send_query_prepared, :query
27
27
  ], :sql, instrument: config[:instrument])
28
28
  end
29
29
  if defined? Mysql2::Client