bc-prometheus-ruby 0.8.0 → 0.8.1

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: a7a2ca2f975be9d346e217c4a13e4ca702a573889383c43292094f66aa82c562
4
+ data.tar.gz: 2724f8826c1ee9d668357676834f15dc24836ed1d05337f7b217133119fb4e26
5
5
  SHA512:
6
- metadata.gz: d489b1017fcd86d43adaf0f9e1eb7a504c5a52b1b68c56e04cae26d1d4068f0e75cf54814c7f09d1e70d9ae257ca022b9cc403bb8fb8adc5d50319c27d057e0b
7
- data.tar.gz: 9c856f97bfda20fa39431d2fc328e2eb05c8f9f63ef54f192322b1850da0fdc604d592a94fc902450f5262b54ccd7daa3effa95af5afb64155e1d72d0b395d38
6
+ metadata.gz: c1e174d621f686a1d32b4db922d4409f1cdffa1f0afd2c6f6b853f51978150b53be7ae3237fc0a1b8daf139423c98f0186ad317345b16a070eb0824f00249ef3
7
+ data.tar.gz: 8c215d4cb4349041e082bedaee24580afbc4f567bf6e8af848c0e00afa42b228a982790f6d21acfdbf6023de82aaa9cfecc2e55b625730e75102443c1e946ef1
@@ -38,4 +38,6 @@ Gem::Specification.new do |spec|
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
  #
@@ -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
  ##
@@ -38,6 +38,8 @@ module Bigcommerce
38
38
  else
39
39
  @response.write(collected_metrics)
40
40
  end
41
+
42
+ set_header('Content-Type', 'text/plain; charset=utf-8')
41
43
  @response
42
44
  end
43
45
 
@@ -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
@@ -17,6 +17,6 @@
17
17
  #
18
18
  module Bigcommerce
19
19
  module Prometheus
20
- VERSION = '0.8.0'
20
+ VERSION = '0.8.1'
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,14 @@
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shaun McCormick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-23 00:00:00.000000000 Z
11
+ date: 2026-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigcommerce-multitrap
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '6.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '6.0'
83
97
  description: Simple integration of ruby and puma servers with prometheus
84
98
  email:
85
99
  - shaun.mccormick@bigcommerce.com
@@ -99,6 +113,7 @@ files:
99
113
  - lib/bigcommerce/prometheus/instrumentors/hutch.rb
100
114
  - lib/bigcommerce/prometheus/instrumentors/resque.rb
101
115
  - lib/bigcommerce/prometheus/instrumentors/web.rb
116
+ - lib/bigcommerce/prometheus/integrations/active_record.rb
102
117
  - lib/bigcommerce/prometheus/integrations/puma.rb
103
118
  - lib/bigcommerce/prometheus/integrations/railtie.rb
104
119
  - lib/bigcommerce/prometheus/integrations/resque.rb
@@ -112,6 +127,7 @@ files:
112
127
  - lib/bigcommerce/prometheus/servers/puma/rack_app.rb
113
128
  - lib/bigcommerce/prometheus/servers/puma/server.rb
114
129
  - lib/bigcommerce/prometheus/servers/puma/server_metrics.rb
130
+ - lib/bigcommerce/prometheus/type_collectors/active_record.rb
115
131
  - lib/bigcommerce/prometheus/type_collectors/base.rb
116
132
  - lib/bigcommerce/prometheus/type_collectors/resque.rb
117
133
  - lib/bigcommerce/prometheus/version.rb
@@ -134,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
150
  - !ruby/object:Gem::Version
135
151
  version: '0'
136
152
  requirements: []
137
- rubygems_version: 3.5.22
153
+ rubygems_version: 3.5.7
138
154
  signing_key:
139
155
  specification_version: 4
140
156
  summary: Simple integration of ruby and puma servers with prometheus