bc-prometheus-ruby 0.8.0 → 0.8.2

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: c45a328936365a6af2f347292b2eb010593389726e55f4fb655db3b1789d10ee
4
- data.tar.gz: 9d29a29091f1720929abff06f8e9e66f0435b56887b7e4c01bfa43e47c5b624d
3
+ metadata.gz: d996c929cb82a48d50a36f4bbd942a5cc7dd41d6ec7d10864808619a6ab08630
4
+ data.tar.gz: 10a28c621098fa9ecb0a06734d66c840663276dd3a3e84f5cdf5bb8bb21760b8
5
5
  SHA512:
6
- metadata.gz: d489b1017fcd86d43adaf0f9e1eb7a504c5a52b1b68c56e04cae26d1d4068f0e75cf54814c7f09d1e70d9ae257ca022b9cc403bb8fb8adc5d50319c27d057e0b
7
- data.tar.gz: 9c856f97bfda20fa39431d2fc328e2eb05c8f9f63ef54f192322b1850da0fdc604d592a94fc902450f5262b54ccd7daa3effa95af5afb64155e1d72d0b395d38
6
+ metadata.gz: 1400c730adf70a8f4f1d836e8a1b78ae5d34c5f11c66848a7cb47001e8e0502d2981b898c52953f2e549945c5c82e0cd1ceaad7aa5901a909241af4a829c85cd
7
+ data.tar.gz: 03dc5e33614b1f4bb27bb1636d91a37704727b2279a981abaa2d1355c3fc679936df78633df774ee2ec240d0c3614ce370f237b10a4a417be7db2ed19f6a8685
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@ Changelog for the bc-prometheus-ruby gem.
2
2
 
3
3
  ### Pending Release
4
4
 
5
+ ## 0.8.2
6
+
7
+ - Add `version=0.0.4` to `Content-Type` header for Prometheus exposition format 0.0.4 compliance
8
+
9
+ ## 0.8.1
10
+
11
+ - Prometheus client respects the enabled setting
12
+ - Upgrade prometheus_exporter
13
+ - Expose SQL metrics for different dashboards
14
+
5
15
  ## 0.8.0
6
16
 
7
17
  - Add support for Ruby 3.4
@@ -31,11 +31,13 @@ Gem::Specification.new do |spec|
31
31
 
32
32
  spec.files = Dir['README.md', 'CHANGELOG.md', 'CODE_OF_CONDUCT.md', 'lib/**/*', 'bc-prometheus-ruby.gemspec']
33
33
  spec.require_paths = ['lib']
34
- spec.required_ruby_version = '>= 3.2'
34
+ spec.required_ruby_version = '>= 3.3'
35
35
 
36
36
  spec.add_runtime_dependency 'bigcommerce-multitrap', '~> 0.1'
37
37
  spec.add_runtime_dependency 'prometheus_exporter', '~> 0.7'
38
38
  spec.add_runtime_dependency 'puma', '> 5'
39
39
  spec.add_runtime_dependency 'rack', '>= 3.0'
40
40
  spec.add_runtime_dependency 'rake', '>= 10.0'
41
+
42
+ spec.add_development_dependency 'activesupport', '>= 6.0'
41
43
  end
@@ -69,6 +69,14 @@ module Bigcommerce
69
69
  URI("http://#{@host}:#{@port}#{path}")
70
70
  end
71
71
 
72
+ ##
73
+ # @param [String] str
74
+ def send(str)
75
+ return unless Bigcommerce::Prometheus.enabled
76
+
77
+ super
78
+ end
79
+
72
80
  ##
73
81
  # Process the current queue and flush to the collector
74
82
  #
@@ -72,6 +72,8 @@ module Bigcommerce
72
72
  metrics = {}
73
73
  metrics = collect(metrics)
74
74
  push(metrics)
75
+ rescue StandardError => e
76
+ @logger.error("[bigcommerce-prometheus] Collector error (#{self.class}), continuing: #{e.message}")
75
77
  ensure
76
78
  sleep @frequency
77
79
  end
@@ -34,7 +34,7 @@ module Bigcommerce
34
34
  puma_collection_frequency: ENV.fetch('PROMETHEUS_PUMA_COLLECTION_FREQUENCY', 30).to_i,
35
35
  puma_process_label: ENV.fetch('PROMETHEUS_PUMA_PROCESS_LABEL', 'web').to_s,
36
36
  resque_collection_frequency: ENV.fetch('PROMETHEUS_RESQUE_COLLECTION_FREQUENCY', 30).to_i,
37
- resque_process_label: ENV.fetch('PROMETHEUS_REQUEST_PROCESS_LABEL', 'resque').to_s,
37
+ resque_process_label: ENV.fetch('PROMETHEUS_RESQUE_PROCESS_LABEL', 'resque').to_s,
38
38
 
39
39
  # Server configuration
40
40
  not_found_body: ENV.fetch('PROMETHEUS_SERVER_NOT_FOUND_BODY', 'Not Found! The Prometheus Ruby Exporter only listens on /metrics and /send-metrics').to_s,
@@ -54,7 +54,8 @@ module Bigcommerce
54
54
  web_type_collectors: [],
55
55
 
56
56
  # Additional configuration
57
- railtie_disabled: ENV.fetch('PROMETHEUS_DISABLE_RAILTIE', 0).to_i.positive?
57
+ railtie_disabled: ENV.fetch('PROMETHEUS_DISABLE_RAILTIE', 0).to_i.positive?,
58
+ active_record_enabled: ENV.fetch('PROMETHEUS_ACTIVE_RECORD_ENABLED', 1).to_i.positive?
58
59
  }.freeze
59
60
 
60
61
  attr_accessor *VALID_CONFIG_KEYS.keys
@@ -46,10 +46,12 @@ module Bigcommerce
46
46
 
47
47
  server.add_type_collector(PrometheusExporter::Server::ActiveRecordCollector.new)
48
48
  server.add_type_collector(PrometheusExporter::Server::HutchCollector.new)
49
+ ::Bigcommerce::Prometheus::Integrations::ActiveRecordSql.register_type_collector(server, process_name: @process_name)
49
50
  @type_collectors.each do |tc|
50
51
  server.add_type_collector(tc)
51
52
  end
52
53
  server.start
54
+ ::Bigcommerce::Prometheus::Integrations::ActiveRecordSql.start_safe(client: Bigcommerce::Prometheus.client, process_name: @process_name)
53
55
  setup_middleware
54
56
  rescue StandardError => e
55
57
  logger.error "[bigcommerce-prometheus][#{@process_name}] Failed to start hutch instrumentation - #{e.message} - #{e.backtrace[0..4].join("\n")}"
@@ -67,6 +67,7 @@ module Bigcommerce
67
67
  server.add_type_collector(PrometheusExporter::Server::ActiveRecordCollector.new)
68
68
  server.add_type_collector(PrometheusExporter::Server::WebCollector.new)
69
69
  server.add_type_collector(PrometheusExporter::Server::PumaCollector.new)
70
+ ::Bigcommerce::Prometheus::Integrations::ActiveRecordSql.register_type_collector(server, process_name: @process_name)
70
71
  @type_collectors.each do |tc|
71
72
  server.add_type_collector(tc)
72
73
  end
@@ -78,6 +79,7 @@ module Bigcommerce
78
79
  @app.config.after_fork_callbacks = [] unless @app.config.after_fork_callbacks
79
80
  @app.config.after_fork_callbacks << lambda do
80
81
  ::Bigcommerce::Prometheus::Integrations::Puma.start(client: Bigcommerce::Prometheus.client)
82
+ ::Bigcommerce::Prometheus::Integrations::ActiveRecordSql.start_safe(client: Bigcommerce::Prometheus.client, process_name: @process_name)
81
83
  @collectors.each(&:start)
82
84
  rescue StandardError => e
83
85
  logger.error "[bigcommerce-prometheus][#{@process_name}] Failed to start web prometheus middleware after fork: #{e.message}"
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Bigcommerce
19
+ module Prometheus
20
+ module Integrations
21
+ ##
22
+ # Subscribes to ActiveSupport sql.active_record notifications and pushes a per-operation
23
+ # SQL query duration histogram to the Prometheus exporter.
24
+ #
25
+ class ActiveRecordSql
26
+ IGNORED_NAMES = %w[SCHEMA CACHE].freeze
27
+ TYPE = 'active_record_sql'
28
+
29
+ # Idempotent: returns the same instance on repeated calls within a process,
30
+ # so calling .start more than once (e.g. from both the gem's instrumentor and a
31
+ # consuming app's initializer) does not register duplicate subscribers and
32
+ # double-count every SQL query.
33
+ #
34
+ # Noop when ActiveRecord is not loaded so non-Rails consumers (or any process that
35
+ # never loads ActiveRecord) can call this safely.
36
+ def self.start(client: nil)
37
+ return unless active_record_loaded?
38
+
39
+ @start ||= new(client: client || ::Bigcommerce::Prometheus.client).tap(&:subscribe!)
40
+ end
41
+
42
+ # @return [Boolean] whether ActiveRecord is loaded in the current process.
43
+ def self.active_record_loaded?
44
+ defined?(::ActiveRecord) ? true : false
45
+ end
46
+
47
+ # Wrapper for instrumentor wiring: registers the AR SQL type collector on the given server,
48
+ # gated on the active_record_enabled config flag, and swallows errors so an additive
49
+ # feature failure cannot take down core instrumentation.
50
+ def self.register_type_collector(server, process_name: nil)
51
+ return unless ::Bigcommerce::Prometheus.active_record_enabled
52
+
53
+ server.add_type_collector(::Bigcommerce::Prometheus::TypeCollectors::ActiveRecordSql.new)
54
+ rescue StandardError => e
55
+ log_warn(process_name, "Failed to register ActiveRecord type collector: #{e.message}")
56
+ end
57
+
58
+ # Wrapper for instrumentor wiring: starts the AR integration, gated on the
59
+ # active_record_enabled config flag, and swallows errors so an additive feature
60
+ # failure cannot take down core instrumentation.
61
+ def self.start_safe(client: nil, process_name: nil)
62
+ return unless ::Bigcommerce::Prometheus.active_record_enabled
63
+
64
+ start(client: client)
65
+ rescue StandardError => e
66
+ log_warn(process_name, "Failed to start ActiveRecord integration: #{e.message}")
67
+ end
68
+
69
+ def self.log_warn(process_name, message)
70
+ process_name ||= ::Bigcommerce::Prometheus.process_name
71
+ ::Bigcommerce::Prometheus.logger&.warn("[bigcommerce-prometheus][#{process_name}] #{message}")
72
+ end
73
+ private_class_method :log_warn
74
+
75
+ def initialize(client:)
76
+ @client = client
77
+ end
78
+
79
+ def subscribe!
80
+ ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
81
+ call(ActiveSupport::Notifications::Event.new(*args))
82
+ end
83
+ end
84
+
85
+ def call(event)
86
+ return if IGNORED_NAMES.include?(event.payload[:name])
87
+
88
+ @client.send_json(
89
+ type: TYPE,
90
+ duration_seconds: event.duration / 1000.0,
91
+ custom_labels: { operation: classify(event.payload[:sql]) }
92
+ )
93
+ rescue StandardError
94
+ # Never let metric instrumentation propagate into the request path.
95
+ nil
96
+ end
97
+
98
+ private
99
+
100
+ def classify(sql)
101
+ return 'other' if sql.nil?
102
+
103
+ first_token = sql.lstrip.split(/\s+/, 2).first&.upcase
104
+ case first_token
105
+ when 'SELECT' then 'select'
106
+ when 'INSERT' then 'insert'
107
+ when 'UPDATE' then 'update'
108
+ when 'DELETE' then 'delete'
109
+ else 'other'
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -51,7 +51,7 @@ module Bigcommerce
51
51
  # @return [Boolean]
52
52
  #
53
53
  def self.active_record_enabled?
54
- defined?(ActiveRecord) && ::ActiveRecord::Base.connection_pool.respond_to?(:stat)
54
+ defined?(::ActiveRecord) && ::ActiveRecord::Base.connection_pool.respond_to?(:stat)
55
55
  end
56
56
 
57
57
  ##
@@ -80,11 +80,12 @@ module Bigcommerce
80
80
  #
81
81
  def stop
82
82
  @server.stop
83
- @run_thread.kill
84
- @running = false
83
+ @run_thread&.kill
85
84
  $stdout.puts "[bigcommerce-prometheus][#{@process_name}] Prometheus exporter cleanly shut down"
86
85
  rescue ::StandardError => e
87
86
  warn "[bigcommerce-prometheus][#{@process_name}] Failed to stop exporter: #{e.message}"
87
+ ensure
88
+ @running = false
88
89
  end
89
90
 
90
91
  ##
@@ -30,13 +30,15 @@ module Bigcommerce
30
30
  # @param [Bigcommerce::Prometheus::Servers::Puma::ServerMetrics]
31
31
  # @param [PrometheusExporter::Server::Collector] collector
32
32
  # @param [Logger] logger
33
+ # @param [Integer, nil] timeout scrape timeout in seconds; nil means no limit
33
34
  #
34
- def initialize(request:, response:, server_metrics:, collector:, logger:)
35
+ def initialize(request:, response:, server_metrics:, collector:, logger:, timeout: nil)
35
36
  @request = request
36
37
  @response = response
37
38
  @collector = collector
38
39
  @server_metrics = server_metrics
39
40
  @logger = logger
41
+ @timeout = timeout
40
42
  end
41
43
 
42
44
  def handle
@@ -34,10 +34,12 @@ module Bigcommerce
34
34
  def call
35
35
  collected_metrics = metrics
36
36
  if @request.accept_encoding.to_s.include?('gzip')
37
- write_gzip(metrics)
37
+ write_gzip(collected_metrics)
38
38
  else
39
39
  @response.write(collected_metrics)
40
40
  end
41
+
42
+ set_header('Content-Type', 'text/plain; version=0.0.4; charset=utf-8')
41
43
  @response
42
44
  end
43
45
 
@@ -77,7 +77,8 @@ module Bigcommerce
77
77
  response: response,
78
78
  server_metrics: @server_metrics,
79
79
  collector: @collector,
80
- logger: @logger
80
+ logger: @logger,
81
+ timeout: @timeout
81
82
  )
82
83
  con.handle
83
84
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Bigcommerce
19
+ module Prometheus
20
+ module TypeCollectors
21
+ ##
22
+ # Render-side counterpart to Integrations::ActiveRecordSql. Aggregates per-operation
23
+ # SQL query duration into a Prometheus Histogram exposed at /metrics.
24
+ #
25
+ class ActiveRecordSql < Bigcommerce::Prometheus::TypeCollectors::Base
26
+ def initialize(
27
+ default_labels: {},
28
+ buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 20, 30, 60]
29
+ )
30
+ @buckets = buckets
31
+ super(type: Bigcommerce::Prometheus::Integrations::ActiveRecordSql::TYPE, default_labels: default_labels)
32
+ end
33
+
34
+ def build_metrics
35
+ {
36
+ sql_query_duration_seconds: PrometheusExporter::Metric::Histogram.new(
37
+ 'sql_query_duration_seconds',
38
+ 'ActiveRecord SQL query duration in seconds, labeled by operation.',
39
+ buckets: @buckets
40
+ )
41
+ }
42
+ end
43
+
44
+ def collect_metrics(data:, labels: {})
45
+ duration = data['duration_seconds']
46
+ return if duration.nil?
47
+
48
+ metric(:sql_query_duration_seconds).observe(duration.to_f, labels)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -48,7 +48,7 @@ module Bigcommerce
48
48
  metric(:jobs_processed_total).observe(data['jobs_processed_total'], labels)
49
49
  metric(:queues_total).observe(data['queues_total'], labels)
50
50
 
51
- data['queues'].each do |name, size|
51
+ (data['queues'] || {}).each do |name, size|
52
52
  metric(:queue_sizes).observe(size, labels.merge(queue: name))
53
53
  end
54
54
  end
@@ -17,6 +17,6 @@
17
17
  #
18
18
  module Bigcommerce
19
19
  module Prometheus
20
- VERSION = '0.8.0'
20
+ VERSION = '0.8.2'
21
21
  end
22
22
  end
@@ -35,6 +35,8 @@ require_relative 'prometheus/collectors/base'
35
35
  require_relative 'prometheus/collectors/resque'
36
36
  require_relative 'prometheus/type_collectors/base'
37
37
  require_relative 'prometheus/type_collectors/resque'
38
+ require_relative 'prometheus/integrations/active_record'
39
+ require_relative 'prometheus/type_collectors/active_record'
38
40
 
39
41
  require_relative 'prometheus/instrumentors/web'
40
42
  require_relative 'prometheus/instrumentors/hutch'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bc-prometheus-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shaun McCormick
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-06-23 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bigcommerce-multitrap
@@ -80,6 +79,20 @@ dependencies:
80
79
  - - ">="
81
80
  - !ruby/object:Gem::Version
82
81
  version: '10.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: activesupport
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '6.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '6.0'
83
96
  description: Simple integration of ruby and puma servers with prometheus
84
97
  email:
85
98
  - shaun.mccormick@bigcommerce.com
@@ -99,6 +112,7 @@ files:
99
112
  - lib/bigcommerce/prometheus/instrumentors/hutch.rb
100
113
  - lib/bigcommerce/prometheus/instrumentors/resque.rb
101
114
  - lib/bigcommerce/prometheus/instrumentors/web.rb
115
+ - lib/bigcommerce/prometheus/integrations/active_record.rb
102
116
  - lib/bigcommerce/prometheus/integrations/puma.rb
103
117
  - lib/bigcommerce/prometheus/integrations/railtie.rb
104
118
  - lib/bigcommerce/prometheus/integrations/resque.rb
@@ -112,6 +126,7 @@ files:
112
126
  - lib/bigcommerce/prometheus/servers/puma/rack_app.rb
113
127
  - lib/bigcommerce/prometheus/servers/puma/server.rb
114
128
  - lib/bigcommerce/prometheus/servers/puma/server_metrics.rb
129
+ - lib/bigcommerce/prometheus/type_collectors/active_record.rb
115
130
  - lib/bigcommerce/prometheus/type_collectors/base.rb
116
131
  - lib/bigcommerce/prometheus/type_collectors/resque.rb
117
132
  - lib/bigcommerce/prometheus/version.rb
@@ -119,7 +134,6 @@ homepage: https://github.com/bigcommerce/bc-prometheus-ruby
119
134
  licenses:
120
135
  - MIT
121
136
  metadata: {}
122
- post_install_message:
123
137
  rdoc_options: []
124
138
  require_paths:
125
139
  - lib
@@ -127,15 +141,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
141
  requirements:
128
142
  - - ">="
129
143
  - !ruby/object:Gem::Version
130
- version: '3.2'
144
+ version: '3.3'
131
145
  required_rubygems_version: !ruby/object:Gem::Requirement
132
146
  requirements:
133
147
  - - ">="
134
148
  - !ruby/object:Gem::Version
135
149
  version: '0'
136
150
  requirements: []
137
- rubygems_version: 3.5.22
138
- signing_key:
151
+ rubygems_version: 3.6.9
139
152
  specification_version: 4
140
153
  summary: Simple integration of ruby and puma servers with prometheus
141
154
  test_files: []