bc-prometheus-ruby 0.2.4 → 0.3.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: 777810436bcae4dfe552e0376be8e174b7d751497e4e9fc7b5ada857d606cc50
4
- data.tar.gz: 720817317a900dee51a5c8c3e418f3012e1695150326cce6b23d031b5f5922cb
3
+ metadata.gz: 31329daa503c8b88c1c31df265199e8fd417ddd66b10d64d3329246e43e0f215
4
+ data.tar.gz: f1a9a94e4a3ca36f5bbf4e94fe916865c661b3eabc8e9edf038c3a8eb0fbcbdc
5
5
  SHA512:
6
- metadata.gz: 68dce4e18b32c60a9a857050934754bbc659242ad34928414c5a437dd5990d2cc46eca24aa7e7a8f5ec44743856109332a6b65c9deef4e64bc2b6cedfc77ea56
7
- data.tar.gz: 84f41b8ab19e3b650e34f09868fa8d9c6f46f355ad96898cab37fe9724d21ee78a77072ca0e944a74d374f04e227a1632e9b46ce68e69a0fc112f2102f4b5212
6
+ metadata.gz: '08ed46adf2df0d5d3ec9e6e97e05598bb8e9cdf467b119369427aa0f1aae5174fdac8ef6782845578df2c4f8c202ba0cb21089e7f3d55d09fde590e701cd6644'
7
+ data.tar.gz: 56b31060bba9d40eeb82e787798c2b7114b2d8869fd9ac8ba68f6cc7f123a534b7b91df17cf06ea5af980b2e6d120f80a3ee394ae9fefdb78a7ec38b9a8f367a
@@ -2,6 +2,15 @@ Changelog for the bc-prometheus-ruby gem.
2
2
 
3
3
  ### Pending Release
4
4
 
5
+ ### 0.3.0
6
+
7
+ - Support for only Ruby 2.6+ going forward
8
+ - Updated README around custom metrics and collectors
9
+ - Add ability to pass custom resque and hutch Collectors/TypeCollectors
10
+ - Add ENV support for all configuration elements
11
+ - Fix issue where base collector did not use Bigcommerce::Prometheus.client
12
+ - Expose new `push` method for Collectors::Base to ease use of custom ad hoc metrics
13
+
5
14
  ### 0.2.4
6
15
 
7
16
  - Fix cant modify frozen array error when using bc-prometheus-ruby outside a web process
data/README.md CHANGED
@@ -49,6 +49,127 @@ After requiring the main file, you can further configure with:
49
49
  | server_port | The port to run the exporter on | 9394 |
50
50
  | process_name | What the current process name is. Used in logging. | `ENV['PROCESS']` |
51
51
 
52
+ ## Custom Collectors
53
+
54
+ To create custom metrics and collectors, simply create two files: a collector (the class that runs and collects metrics),
55
+ and the type collector, which runs on the threaded prometheus server and
56
+
57
+ ### Type Collector
58
+
59
+ First, create a type collector. Note that the "type" of this will be the full name of the class, with `TypeCollector`
60
+ stripped. This is important later. Our example here will have a "type" of "app".
61
+
62
+ ```ruby
63
+ class AppTypeCollector < ::Bigcommerce::Prometheus::TypeCollectors::Base
64
+ def build_metrics
65
+ {
66
+ honks: PrometheusExporter::Metric::Counter.new('honks', 'Running counter of honks'),
67
+ points: PrometheusExporter::Metric::Gauge.new('points', 'Current amount of points')
68
+ }
69
+ end
70
+
71
+ def collect_metrics(data:, labels: {})
72
+ metric(:points).observe(data.fetch('points', 0))
73
+ metric(:honks).observe(1, labels) if data.fetch('honks', 0).to_i.positive?
74
+ end
75
+ end
76
+ ```
77
+
78
+ There are two important methods here: `build_metrics`, which registers the different metrics you want to measure, and
79
+ `collect_metrics`, which actually takes in the metrics and prepares them to be rendered so that Prometheus can scrape
80
+ them.
81
+
82
+ Note also in the example the different ways of observing Gauges vs Counters.
83
+
84
+ ### Collector
85
+
86
+ Next, create a collector. Your "type" of the Collector must match the type collector above, so that bc-prometheus-ruby
87
+ knows how to map the metrics to the right TypeCollector. This is inferred from the class name. Here, it is "app":
88
+
89
+ ```ruby
90
+ class AppCollector < ::Bigcommerce::Prometheus::Collectors::Base
91
+ def honk!
92
+ push(
93
+ honks: 1,
94
+ custom_labels: {
95
+ volume: 'loud'
96
+ }
97
+ )
98
+ end
99
+
100
+ def collect(metrics)
101
+ metrics[:points] = rand(1..100)
102
+ metrics
103
+ end
104
+ end
105
+ ```
106
+
107
+ There are two types of metrics here: on-demand, and polled. Let's look at the first:
108
+
109
+ #### On-Demand Metrics
110
+
111
+ To issue an on-demand metric (usually a counter) that then automatically updates, in your application code, you would
112
+ then run:
113
+
114
+ ```ruby
115
+ app_collector = AppCollector.new
116
+ app_collector.honk!
117
+ ```
118
+
119
+ This will "push" the metrics to our `AppTypeCollector` instance, which will render them as:
120
+
121
+ ```
122
+ # HELP ruby_honks Running counter of honks
123
+ # TYPE ruby_honks counter
124
+ ruby_honks{volume="loud"} 2
125
+ ```
126
+
127
+ As you can see this will respect any custom labels we push in as well.
128
+
129
+ ### Polling Metrics
130
+
131
+ Using our same AppCollector, if you note the `collect` method: this method will run on a 15 second polled basis
132
+ (the frequency of which is configurable in the initializer of the AppCollector). Here we're just spitting out random
133
+ points, so it'll look something like this:
134
+
135
+ ```
136
+ # HELP ruby_points Current amount of points
137
+ # TYPE ruby_points gauge
138
+ ruby_points 42
139
+ ```
140
+
141
+ ### Registering Our Collectors
142
+
143
+ Each different type of integration will need to have the collectors passed into them, where appropriate. For example,
144
+ if we want these collectors to run on our web, resque, and hutch processes, we'll need to:
145
+
146
+ ```ruby
147
+ ::Bigcommerce::Prometheus.configure do |c|
148
+ c.web_collectors = [AppCollector]
149
+ c.web_type_collectors = [AppTypeCollector.new]
150
+ c.resque_collectors = [AppCollector]
151
+ c.resque_type_collectors = [AppTypeCollector.new]
152
+ c.hutch_collectors = [AppCollector]
153
+ c.hutch_type_collectors = [AppTypeCollector.new]
154
+ end
155
+ ```
156
+
157
+ #### Custom Server Integrations
158
+
159
+ For custom integrations that initialize their own server, you'll need to pass your TypeCollector instance via the
160
+ `.add_type_collector` method on the prometheus server instance before starting it:
161
+
162
+ ```ruby
163
+ server = ::Bigcommerce::Prometheus::Server.new
164
+ Bigcommerce::Prometheus.web_type_collectors.each do |tc|
165
+ server.add_type_collector(tc)
166
+ end
167
+
168
+ # and for polling:
169
+
170
+ AppCollector.start
171
+ ```
172
+
52
173
  ## License
53
174
 
54
175
  Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved
@@ -31,6 +31,7 @@ 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 = '>= 2.6'
34
35
 
35
36
  spec.add_development_dependency 'rake', '>= 10.0'
36
37
  spec.add_development_dependency 'rspec', '>= 3.8'
@@ -24,16 +24,16 @@ module Bigcommerce
24
24
  include Singleton
25
25
  include Loggable
26
26
 
27
- def initialize
27
+ def initialize(host: nil, port: nil, max_queue_size: nil, thread_sleep: nil, custom_labels: nil, process_name: nil)
28
28
  super(
29
- host: Bigcommerce::Prometheus.server_host,
30
- port: Bigcommerce::Prometheus.server_port,
31
- max_queue_size: Bigcommerce::Prometheus.client_max_queue_size,
32
- thread_sleep: Bigcommerce::Prometheus.client_thread_sleep,
33
- custom_labels: Bigcommerce::Prometheus.client_custom_labels
29
+ host: host || Bigcommerce::Prometheus.server_host,
30
+ port: port || Bigcommerce::Prometheus.server_port,
31
+ max_queue_size: max_queue_size || Bigcommerce::Prometheus.client_max_queue_size,
32
+ thread_sleep: thread_sleep || Bigcommerce::Prometheus.client_thread_sleep,
33
+ custom_labels: custom_labels || Bigcommerce::Prometheus.client_custom_labels
34
34
  )
35
35
  PrometheusExporter::Client.default = self
36
- @process_name = ::Bigcommerce::Prometheus.process_name
36
+ @process_name = process_name || ::Bigcommerce::Prometheus.process_name
37
37
  end
38
38
 
39
39
  ##
@@ -55,7 +55,7 @@ module Bigcommerce
55
55
 
56
56
  ##
57
57
  # @param [String] path
58
- # @return [URI]
58
+ # @return [Module<URI>]
59
59
  #
60
60
  def uri_path(path)
61
61
  URI("http://#{@host}:#{@port}#{path}")
@@ -49,15 +49,15 @@ module Bigcommerce
49
49
  end
50
50
 
51
51
  ##
52
- # @param [PrometheusExporter::Client] client
52
+ # @param [Bigcommerce::Prometheus::Client] client
53
53
  # @param [String] type
54
54
  # @param [Integer] frequency
55
55
  # @param [Hash] options
56
56
  #
57
57
  def initialize(client: nil, type: nil, frequency: nil, options: nil)
58
- @client = client || PrometheusExporter::Client.default
58
+ @client = client || Bigcommerce::Prometheus.client
59
59
  @type = type || self.class.to_s.downcase.gsub('::', '_').gsub('collector', '')
60
- @frequency = frequency || 15
60
+ @frequency = frequency || Bigcommerce::Prometheus.collector_collection_frequency
61
61
  @options = options || {}
62
62
  @logger = Bigcommerce::Prometheus.logger
63
63
  end
@@ -66,12 +66,9 @@ module Bigcommerce
66
66
  # Run the collector and send stats
67
67
  #
68
68
  def run
69
- metrics = { type: @type }
69
+ metrics = {}
70
70
  metrics = collect(metrics)
71
- @logger.debug "[bigcommerce-prometheus] Pushing #{@type} metrics to type collector: #{metrics.inspect}"
72
- @client.send_json(metrics)
73
- rescue StandardError => e
74
- @logger.error("Prometheus Exporter Failed To Collect #{@type} Stats #{e}")
71
+ push(metrics)
75
72
  ensure
76
73
  sleep @frequency
77
74
  end
@@ -85,6 +82,19 @@ module Bigcommerce
85
82
  def collect(metrics = {})
86
83
  metrics
87
84
  end
85
+
86
+ private
87
+
88
+ ##
89
+ # @param [Hash] metric
90
+ #
91
+ def push(metric)
92
+ metric[:type] = @type unless metric.key?(:type)
93
+ @logger.debug("[bigcommerce-prometheus] Pushing #{metric[:type]} metrics to type collector: #{metric.inspect}")
94
+ @client.send_json(metric)
95
+ rescue StandardError => e
96
+ @logger.error("[bigcommerce-prometheus] Prometheus Exporter failed to send #{metric[:type]} stats to type collector #{e}")
97
+ end
88
98
  end
89
99
  end
90
100
  end
@@ -23,18 +23,32 @@ module Bigcommerce
23
23
  module Configuration
24
24
  VALID_CONFIG_KEYS = {
25
25
  logger: nil,
26
+ enabled: ENV.fetch('PROMETHEUS_ENABLED', 1).to_i.positive?,
27
+
28
+ # Client configuration
26
29
  client_custom_labels: nil,
27
- client_max_queue_size: 10_000,
28
- client_thread_sleep: 0.5,
29
- enabled: true,
30
- puma_collection_frequency: 30,
31
- puma_process_label: 'web',
32
- resque_collection_frequency: 30,
33
- resque_process_label: 'resque',
34
- server_host: '0.0.0.0',
35
- server_port: PrometheusExporter::DEFAULT_PORT,
36
- server_timeout: PrometheusExporter::DEFAULT_TIMEOUT,
37
- server_prefix: PrometheusExporter::DEFAULT_PREFIX,
30
+ client_max_queue_size: ENV.fetch('PROMETHEUS_CLIENT_MAX_QUEUE_SIZE', 10_000).to_i,
31
+ client_thread_sleep: ENV.fetch('PROMETHEUS_CLIENT_THREAD_SLEEP', 0.5).to_f,
32
+
33
+ # Integration configuration
34
+ puma_collection_frequency: ENV.fetch('PROMETHEUS_PUMA_COLLECTION_FREQUENCY', 30).to_i,
35
+ puma_process_label: ENV.fetch('PROMETHEUS_PUMA_PROCESS_LABEL', 'web').to_s,
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,
38
+
39
+ # Server configuration
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,
41
+ server_host: ENV.fetch('PROMETHEUS_SERVER_HOST', '0.0.0.0').to_s,
42
+ server_port: ENV.fetch('PROMETHEUS_SERVER_PORT', PrometheusExporter::DEFAULT_PORT).to_i,
43
+ server_timeout: ENV.fetch('PROMETHEUS_DEFAULT_TIMEOUT', PrometheusExporter::DEFAULT_TIMEOUT).to_i,
44
+ server_prefix: ENV.fetch('PROMETHEUS_DEFAULT_PREFIX', PrometheusExporter::DEFAULT_PREFIX).to_s,
45
+
46
+ # Custom collector configuration
47
+ collector_collection_frequency: ENV.fetch('PROMETHEUS_DEFAULT_COLLECTOR_COLLECTION_FREQUENCY_SEC', 15).to_i,
48
+ hutch_collectors: [],
49
+ hutch_type_collectors: [],
50
+ resque_collectors: [],
51
+ resque_type_collectors: [],
38
52
  web_collectors: [],
39
53
  web_type_collectors: []
40
54
  }.freeze
@@ -85,12 +99,7 @@ module Bigcommerce
85
99
  send("#{k}=".to_sym, v)
86
100
  end
87
101
  determine_logger
88
- self.enabled = ENV.fetch('PROMETHEUS_ENABLED', 1).to_i.positive?
89
- self.server_host = ENV.fetch('PROMETHEUS_SERVER_HOST', '0.0.0.0').to_s
90
- self.server_port = ENV.fetch('PROMETHEUS_SERVER_PORT', PrometheusExporter::DEFAULT_PORT).to_i
91
102
 
92
- self.puma_process_label = ENV.fetch('PROMETHEUS_PUMA_PROCESS_LABEL', 'web').to_s
93
- self.puma_collection_frequency = ENV.fetch('PROMETHEUS_PUMA_COLLECTION_FREQUENCY', 30).to_i
94
103
  self.web_type_collectors = []
95
104
  end
96
105
 
@@ -31,6 +31,8 @@ module Bigcommerce
31
31
  @server_port = Bigcommerce::Prometheus.server_port
32
32
  @server_timeout = Bigcommerce::Prometheus.server_timeout
33
33
  @server_prefix = Bigcommerce::Prometheus.server_prefix
34
+ @collectors = Bigcommerce::Prometheus.hutch_collectors || []
35
+ @type_collectors = Bigcommerce::Prometheus.hutch_type_collectors || []
34
36
  end
35
37
 
36
38
  ##
@@ -44,6 +46,9 @@ module Bigcommerce
44
46
 
45
47
  server.add_type_collector(PrometheusExporter::Server::ActiveRecordCollector.new)
46
48
  server.add_type_collector(PrometheusExporter::Server::HutchCollector.new)
49
+ @type_collectors.each do |tc|
50
+ server.add_type_collector(tc)
51
+ end
47
52
  server.start
48
53
  setup_middleware
49
54
  rescue StandardError => e
@@ -65,6 +70,9 @@ module Bigcommerce
65
70
  require 'hutch'
66
71
  ::Hutch::Config.set(:tracer, PrometheusExporter::Instrumentation::Hutch)
67
72
  @app.middleware.unshift(PrometheusExporter::Middleware, client: Bigcommerce::Prometheus.client)
73
+ @collectors.each(&:start)
74
+ rescue StandardError => e
75
+ logger.warn "[bigcommerce-prometheus][#{@process_name}] Failed to setup hutch prometheus middleware: #{e.message}"
68
76
  end
69
77
  end
70
78
  end
@@ -31,6 +31,8 @@ module Bigcommerce
31
31
  @server_port = Bigcommerce::Prometheus.server_port
32
32
  @server_timeout = Bigcommerce::Prometheus.server_timeout
33
33
  @server_prefix = Bigcommerce::Prometheus.server_prefix
34
+ @collectors = Bigcommerce::Prometheus.resque_collectors || []
35
+ @type_collectors = Bigcommerce::Prometheus.resque_type_collectors || []
34
36
  end
35
37
 
36
38
  ##
@@ -44,6 +46,9 @@ module Bigcommerce
44
46
 
45
47
  server.add_type_collector(PrometheusExporter::Server::ActiveRecordCollector.new)
46
48
  server.add_type_collector(Bigcommerce::Prometheus::TypeCollectors::Resque.new)
49
+ @type_collectors.each do |tc|
50
+ server.add_type_collector(tc)
51
+ end
47
52
  server.start
48
53
  setup_middleware
49
54
  rescue StandardError => e
@@ -63,7 +68,8 @@ module Bigcommerce
63
68
  def setup_middleware
64
69
  logger.info "[bigcommerce-prometheus][#{@process_name}] Setting up resque prometheus middleware"
65
70
  ::Resque.before_first_fork do
66
- ::Bigcommerce::Prometheus::Integrations::Resque.start
71
+ ::Bigcommerce::Prometheus::Integrations::Resque.start(client: Bigcommerce::Prometheus.client)
72
+ @collectors.each(&:start)
67
73
  end
68
74
  end
69
75
  end
@@ -77,7 +77,7 @@ module Bigcommerce
77
77
  def setup_after_fork
78
78
  @app.config.after_fork_callbacks = [] unless @app.config.after_fork_callbacks
79
79
  @app.config.after_fork_callbacks << lambda do
80
- ::Bigcommerce::Prometheus::Integrations::Puma.start
80
+ ::Bigcommerce::Prometheus::Integrations::Puma.start(client: Bigcommerce::Prometheus.client)
81
81
  @collectors.each(&:start)
82
82
  end
83
83
  end
@@ -25,13 +25,13 @@ module Bigcommerce
25
25
  ##
26
26
  # Start the resque integration
27
27
  #
28
- def self.start
28
+ def self.start(client: nil)
29
29
  ::PrometheusExporter::Instrumentation::Process.start(
30
- client: ::Bigcommerce::Prometheus.client,
30
+ client: client || ::Bigcommerce::Prometheus.client,
31
31
  type: ::Bigcommerce::Prometheus.resque_process_label
32
32
  )
33
33
  ::Bigcommerce::Prometheus::Collectors::Resque.start(
34
- client: ::Bigcommerce::Prometheus.client,
34
+ client: client || ::Bigcommerce::Prometheus.client,
35
35
  frequency: ::Bigcommerce::Prometheus.resque_collection_frequency
36
36
  )
37
37
  end
@@ -26,7 +26,7 @@ module Bigcommerce
26
26
  class NotFoundController < BaseController
27
27
  def call
28
28
  @response.status = 404
29
- @response.body << 'Not Found! The Prometheus Ruby Exporter only listens on /metrics and /send-metrics'
29
+ @response.body << Bigcommerce::Prometheus.not_found_body
30
30
  end
31
31
  end
32
32
  end
@@ -24,9 +24,9 @@ module Bigcommerce
24
24
  #
25
25
  class Server < ::Thin::Server
26
26
  def initialize(port:, host: nil, timeout: nil, logger: nil)
27
- @port = port || ::PrometheusExporter::DEFAULT_PORT
28
- @host = host || '0.0.0.0'
29
- @timeout = timeout || ::PrometheusExporter::DEFAULT_TIMEOUT
27
+ @port = port || ::Bigcommerce::Prometheus.server_port
28
+ @host = host || ::Bigcommerce::Prometheus.server_host
29
+ @timeout = timeout || ::Bigcommerce::Prometheus.server_timeout
30
30
  @logger = logger || ::Bigcommerce::Prometheus.logger
31
31
  @rack_app = ::Bigcommerce::Prometheus::Servers::Thin::RackApp.new(timeout: timeout, logger: logger)
32
32
  super(@host, @port, @rack_app)
@@ -17,6 +17,6 @@
17
17
  #
18
18
  module Bigcommerce
19
19
  module Prometheus
20
- VERSION = '0.2.4'
20
+ VERSION = '0.3.0'
21
21
  end
22
22
  end
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.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shaun McCormick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-22 00:00:00.000000000 Z
11
+ date: 2020-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -211,7 +211,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
211
211
  requirements:
212
212
  - - ">="
213
213
  - !ruby/object:Gem::Version
214
- version: '0'
214
+ version: '2.6'
215
215
  required_rubygems_version: !ruby/object:Gem::Requirement
216
216
  requirements:
217
217
  - - ">="