prometheus_exporter 0.4.14 → 0.4.15

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: c0851cbef41df3faf274a45e60e176b3a34906209a82e280624a58fa72ad1929
4
- data.tar.gz: d6abfafdd5178e9801df4b09bef7a0597f77549f99036423e3a4c870d6b47db2
3
+ metadata.gz: 86155ae93296d42918b6419efbdae95665e5149a89083894dd9ba5daaf9f84bf
4
+ data.tar.gz: 49bee52b66976743b17794f7515cc57c7c6979338d479a66ec49ae0ff30d9f7f
5
5
  SHA512:
6
- metadata.gz: 7e538b3d2e02c1d20e5a8b4cd30a11c8bbb6f3ffb6d3630384b4e1047ba69fdde331352cbe34cffed58927051a55eac25ab2e012b79700b80ec9052b8696cc67
7
- data.tar.gz: d3e52bfbeeaeb14c5044c5a7301370993f900a471ed0c7a2b7c1b58b3881a51f3e144e896e4d6ab011e66dabb06af6cf4cde5de8fade2091f2b557efdc8cca5d
6
+ metadata.gz: c2ebf589c432b5c64339d15c4c25b99d4cca49228c466ba9a334c4af12b2ac7c7922a8607c9c90ea38cf1afd9a2f2efa5dde087ab4aed8f124e4f2577510ee11
7
+ data.tar.gz: e1a2f8f93f59b62296ce1a2448f70c3decd8f970787ee5619593080da6e181c02c432e82e52ac9de11f626309eb9aa513e6070c56349ef84fcf3a84d4b44ea45
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ 0.4.15 - 04-11-2019
2
+
3
+ - FEATURE: Improve delayed job collector, add pending counts
4
+ - FEATURE: New ActiveRecord collector (documented in readme)
5
+ - FEATURE: Allow passing in histogram and summary options
6
+ - FEATURE: Allow custom labels for unicorn collector
7
+
1
8
  0.4.14 - 10-09-2019
2
9
 
3
10
  - FEATURE: allow finding metrics by name RemoteMetric #find_registered_metric
data/README.md CHANGED
@@ -13,6 +13,7 @@ To learn more see [Instrumenting Rails with Prometheus](https://samsaffron.com/a
13
13
  * [Rails integration](#rails-integration)
14
14
  * [Per-process stats](#per-process-stats)
15
15
  * [Sidekiq metrics](#sidekiq-metrics)
16
+ * [ActiveRecord Connection Pool Metrics](#activerecord-connection-pool-metrics)
16
17
  * [Delayed Job plugin](#delayed-job-plugin)
17
18
  * [Hutch metrics](#hutch-message-processing-tracer)
18
19
  * [Puma metrics](#puma-metrics)
@@ -148,6 +149,17 @@ awesome 10
148
149
 
149
150
  ```
150
151
 
152
+ Custom quantiles for summaries and buckets for histograms can also be passed in.
153
+
154
+ ```ruby
155
+ require 'prometheus_exporter/client'
156
+
157
+ client = PrometheusExporter::Client.default
158
+ histogram = client.register(:histogram, "api_time", "time to call api", buckets: [0.1, 0.5, 1])
159
+
160
+ histogram.observe(0.2, api: 'twitter')
161
+ ```
162
+
151
163
  ### Rails integration
152
164
 
153
165
  You can easily integrate into any Rack application.
@@ -175,6 +187,60 @@ Ensure you run the exporter in a monitored background process:
175
187
  $ bundle exec prometheus_exporter
176
188
  ```
177
189
 
190
+ #### Activerecord Connection Pool Metrics
191
+
192
+ This collects activerecord connection pool metrics.
193
+
194
+ It supports injection of custom labels and the connection config options (`username`, `database`, `host`, `port`) as labels.
195
+
196
+ For Puma single mode
197
+ ```ruby
198
+ #in puma.rb
199
+ require 'prometheus_exporter/instrumentation'
200
+ PrometheusExporter::Instrumentation::ActiveRecord.start(
201
+ custom_labels: { type: "puma_single_mode" }, #optional params
202
+ config_labels: [:database, :host] #optional params
203
+ )
204
+ ```
205
+
206
+ For Puma cluster mode
207
+
208
+ ```ruby
209
+ # in puma.rb
210
+ on_worker_boot do
211
+ require 'prometheus_exporter/instrumentation'
212
+ PrometheusExporter::Instrumentation::ActiveRecord.start(
213
+ custom_labels: { type: "puma_worker" }, #optional params
214
+ config_labels: [:database, :host] #optional params
215
+ )
216
+ end
217
+ ```
218
+
219
+ For Unicorn / Passenger
220
+
221
+ ```ruby
222
+ after_fork do
223
+ require 'prometheus_exporter/instrumentation'
224
+ PrometheusExporter::Instrumentation::ActiveRecord.start(
225
+ custom_labels: { type: "unicorn_worker" }, #optional params
226
+ config_labels: [:database, :host] #optional params
227
+ )
228
+ end
229
+ ```
230
+
231
+ For Sidekiq
232
+ ```ruby
233
+ Sidekiq.configure_server do |config|
234
+ config.on :startup do
235
+ require 'prometheus_exporter/instrumentation'
236
+ PrometheusExporter::Instrumentation::ActiveRecord.start(
237
+ custom_labels: { type: "sidekiq" }, #optional params
238
+ config_labels: [:database, :host] #optional params
239
+ )
240
+ end
241
+ end
242
+ ```
243
+
178
244
  #### Per-process stats
179
245
 
180
246
  You may also be interested in per-process stats. This collects memory and GC stats:
@@ -4,16 +4,16 @@ require 'socket'
4
4
  require 'thread'
5
5
 
6
6
  module PrometheusExporter
7
-
8
7
  class Client
9
8
  class RemoteMetric
10
9
  attr_reader :name, :type, :help
11
10
 
12
- def initialize(name:, help:, type:, client:)
11
+ def initialize(name:, help:, type:, client:, opts: nil)
13
12
  @name = name
14
13
  @help = help
15
14
  @client = client
16
15
  @type = type
16
+ @opts = opts
17
17
  end
18
18
 
19
19
  def standard_values(value, keys, prometheus_exporter_action = nil)
@@ -25,6 +25,7 @@ module PrometheusExporter
25
25
  value: value
26
26
  }
27
27
  values[:prometheus_exporter_action] = prometheus_exporter_action if prometheus_exporter_action
28
+ values[:opts] = @opts if @opts
28
29
  values
29
30
  end
30
31
 
@@ -39,7 +40,6 @@ module PrometheusExporter
39
40
  def decrement(keys = nil, value = 1)
40
41
  @client.send_json(standard_values(value, keys, :decrement))
41
42
  end
42
-
43
43
  end
44
44
 
45
45
  def self.default
@@ -83,8 +83,8 @@ module PrometheusExporter
83
83
  @custom_labels = custom_labels
84
84
  end
85
85
 
86
- def register(type, name, help)
87
- metric = RemoteMetric.new(type: type, name: name, help: help, client: self)
86
+ def register(type, name, help, opts = nil)
87
+ metric = RemoteMetric.new(type: type, name: name, help: help, client: self, opts: opts)
88
88
  @metrics << metric
89
89
  metric
90
90
  end
@@ -8,3 +8,4 @@ require_relative "instrumentation/delayed_job"
8
8
  require_relative "instrumentation/puma"
9
9
  require_relative "instrumentation/hutch"
10
10
  require_relative "instrumentation/unicorn"
11
+ require_relative "instrumentation/active_record"
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # collects stats from currently running process
4
+ module PrometheusExporter::Instrumentation
5
+ class ActiveRecord
6
+ ALLOWED_CONFIG_LABELS = %i(database username host port)
7
+
8
+ def self.start(client: nil, frequency: 30, custom_labels: {}, config_labels: [])
9
+
10
+ # Not all rails versions support coonection pool stats
11
+ unless ::ActiveRecord::Base.connection_pool.respond_to?(:stat)
12
+ STDERR.puts("ActiveRecord connection pool stats not supported in your rails version")
13
+ return
14
+ end
15
+
16
+ config_labels.map!(&:to_sym)
17
+ validate_config_labels(config_labels)
18
+
19
+ active_record_collector = new(custom_labels, config_labels)
20
+
21
+ client ||= PrometheusExporter::Client.default
22
+
23
+ stop if @thread
24
+
25
+ @thread = Thread.new do
26
+ while true
27
+ begin
28
+ metrics = active_record_collector.collect
29
+ metrics.each { |metric| client.send_json metric }
30
+ rescue => e
31
+ STDERR.puts("Prometheus Exporter Failed To Collect Process Stats #{e}")
32
+ ensure
33
+ sleep frequency
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.validate_config_labels(config_labels)
40
+ return if config_labels.size == 0
41
+ raise "Invalid Config Labels, available options #{ALLOWED_CONFIG_LABELS}" if (config_labels - ALLOWED_CONFIG_LABELS).size > 0
42
+ end
43
+
44
+ def self.stop
45
+ if t = @thread
46
+ t.kill
47
+ @thread = nil
48
+ end
49
+ end
50
+
51
+ def initialize(metric_labels, config_labels)
52
+ @metric_labels = metric_labels
53
+ @config_labels = config_labels
54
+ @hostname = nil
55
+ end
56
+
57
+ def hostname
58
+ @hostname ||=
59
+ begin
60
+ `hostname`.strip
61
+ rescue => e
62
+ STDERR.puts "Unable to lookup hostname #{e}"
63
+ "unknown-host"
64
+ end
65
+ end
66
+
67
+ def collect
68
+ metrics = []
69
+ collect_active_record_pool_stats(metrics)
70
+ metrics
71
+ end
72
+
73
+ def pid
74
+ @pid = ::Process.pid
75
+ end
76
+
77
+ def collect_active_record_pool_stats(metrics)
78
+ ObjectSpace.each_object(::ActiveRecord::ConnectionAdapters::ConnectionPool) do |pool|
79
+ next if pool.connections.nil?
80
+
81
+ labels_from_config = pool.spec.config
82
+ .select { |k, v| @config_labels.include? k }
83
+ .map { |k, v| [k.to_s.prepend("dbconfig_"), v] }
84
+
85
+ labels = @metric_labels.merge(pool_name: pool.spec.name).merge(Hash[labels_from_config])
86
+
87
+ metric = {
88
+ pid: pid,
89
+ type: "active_record",
90
+ hostname: hostname,
91
+ metric_labels: labels
92
+ }
93
+ metric.merge!(pool.stat)
94
+ metrics << metric
95
+ end
96
+ end
97
+ end
98
+ end
@@ -13,7 +13,9 @@ 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
- instrumenter.call(job, max_attempts, *args, &block)
16
+ enqueued_count = Delayed::Job.count
17
+ pending_count = Delayed::Job.where(attempts: 0, locked_at: nil).count
18
+ instrumenter.call(job, max_attempts, enqueued_count, pending_count, *args, &block)
17
19
  end
18
20
  end
19
21
  end
@@ -26,7 +28,7 @@ module PrometheusExporter::Instrumentation
26
28
  @client = client || PrometheusExporter::Client.default
27
29
  end
28
30
 
29
- def call(job, max_attempts, *args, &block)
31
+ def call(job, max_attempts, enqueued_count, pending_count, *args, &block)
30
32
  success = false
31
33
  start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
32
34
  attempts = job.attempts + 1 # Increment because we're adding the current attempt
@@ -42,7 +44,9 @@ module PrometheusExporter::Instrumentation
42
44
  success: success,
43
45
  duration: duration,
44
46
  attempts: attempts,
45
- max_attempts: max_attempts
47
+ max_attempts: max_attempts,
48
+ enqueued: enqueued_count,
49
+ pending: pending_count
46
50
  )
47
51
  end
48
52
  end
@@ -9,7 +9,7 @@ end
9
9
  module PrometheusExporter::Instrumentation
10
10
  # collects stats from unicorn
11
11
  class Unicorn
12
- def self.start(pid_file:, listener_address:, client:, frequency: 30)
12
+ def self.start(pid_file:, listener_address:, client: nil, frequency: 30)
13
13
  unicorn_collector = new(pid_file: pid_file, listener_address: listener_address)
14
14
  client ||= PrometheusExporter::Client.default
15
15
  Thread.new do
@@ -13,3 +13,4 @@ require_relative "server/runner"
13
13
  require_relative "server/puma_collector"
14
14
  require_relative "server/hutch_collector"
15
15
  require_relative "server/unicorn_collector"
16
+ require_relative "server/active_record_collector"
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrometheusExporter::Server
4
+ class ActiveRecordCollector < TypeCollector
5
+ MAX_ACTIVERECORD_METRIC_AGE = 60
6
+ ACTIVE_RECORD_GAUGES = {
7
+ connections: "Total connections in pool",
8
+ busy: "Connections in use in pool",
9
+ dead: "Dead connections in pool",
10
+ idle: "Idle connections in pool",
11
+ waiting: "Connection requests waiting",
12
+ size: "Maximum allowed connection pool size"
13
+ }
14
+
15
+ def initialize
16
+ @active_record_metrics = []
17
+ end
18
+
19
+ def type
20
+ "active_record"
21
+ end
22
+
23
+ def metrics
24
+ return [] if @active_record_metrics.length == 0
25
+
26
+ metrics = {}
27
+
28
+ @active_record_metrics.map do |m|
29
+ metric_key = (m["metric_labels"] || {}).merge("pid" => m["pid"])
30
+
31
+ ACTIVE_RECORD_GAUGES.map do |k, help|
32
+ k = k.to_s
33
+ if v = m[k]
34
+ g = metrics[k] ||= PrometheusExporter::Metric::Gauge.new("active_record_connection_pool_#{k}", help)
35
+ g.observe(v, metric_key)
36
+ end
37
+ end
38
+ end
39
+
40
+ metrics.values
41
+ end
42
+
43
+ def collect(obj)
44
+ now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
45
+
46
+ obj["created_at"] = now
47
+
48
+ @active_record_metrics.delete_if do |current|
49
+ (obj["pid"] == current["pid"] && obj["hostname"] == current["hostname"]) ||
50
+ (current["created_at"] + MAX_ACTIVERECORD_METRIC_AGE < now)
51
+ end
52
+
53
+ @active_record_metrics << obj
54
+ end
55
+ end
56
+ end
@@ -17,6 +17,7 @@ module PrometheusExporter::Server
17
17
  register_collector(PumaCollector.new)
18
18
  register_collector(HutchCollector.new)
19
19
  register_collector(UnicornCollector.new)
20
+ register_collector(ActiveRecordCollector.new)
20
21
  end
21
22
 
22
23
  def register_collector(collector)
@@ -72,6 +73,7 @@ module PrometheusExporter::Server
72
73
  def register_metric_unsafe(obj)
73
74
  name = obj["name"]
74
75
  help = obj["help"]
76
+ opts = symbolize_keys(obj["opts"] || {})
75
77
 
76
78
  metric =
77
79
  case obj["type"]
@@ -80,9 +82,9 @@ module PrometheusExporter::Server
80
82
  when "counter"
81
83
  PrometheusExporter::Metric::Counter.new(name, help)
82
84
  when "summary"
83
- PrometheusExporter::Metric::Summary.new(name, help)
85
+ PrometheusExporter::Metric::Summary.new(name, help, opts)
84
86
  when "histogram"
85
- PrometheusExporter::Metric::Histogram.new(name, help)
87
+ PrometheusExporter::Metric::Histogram.new(name, help, opts)
86
88
  end
87
89
 
88
90
  if metric
@@ -91,5 +93,9 @@ module PrometheusExporter::Server
91
93
  STDERR.puts "failed to register metric #{obj}"
92
94
  end
93
95
  end
96
+
97
+ def symbolize_keys(hash)
98
+ hash.inject({}) { |memo, k| memo[k.first.to_sym] = k.last; memo }
99
+ end
94
100
  end
95
101
  end
@@ -21,12 +21,15 @@ module PrometheusExporter::Server
21
21
  @delayed_job_duration_seconds_summary.observe(obj["duration"], status: "success") if obj["success"]
22
22
  @delayed_job_duration_seconds_summary.observe(obj["duration"], status: "failed") if !obj["success"]
23
23
  @delayed_job_attempts_summary.observe(obj["attempts"]) if obj["success"]
24
+ @delayed_jobs_enqueued.observe(obj["enqueued"])
25
+ @delayed_jobs_pending.observe(obj["pending"])
24
26
  end
25
27
 
26
28
  def metrics
27
29
  if @delayed_jobs_total
28
30
  [@delayed_job_duration_seconds, @delayed_jobs_total, @delayed_failed_jobs_total,
29
- @delayed_jobs_max_attempts_reached_total, @delayed_job_duration_seconds_summary, @delayed_job_attempts_summary]
31
+ @delayed_jobs_max_attempts_reached_total, @delayed_job_duration_seconds_summary, @delayed_job_attempts_summary,
32
+ @delayed_jobs_enqueued, @delayed_jobs_pending]
30
33
  else
31
34
  []
32
35
  end
@@ -45,6 +48,14 @@ module PrometheusExporter::Server
45
48
  PrometheusExporter::Metric::Counter.new(
46
49
  "delayed_jobs_total", "Total number of delayed jobs executed.")
47
50
 
51
+ @delayed_jobs_enqueued =
52
+ PrometheusExporter::Metric::Gauge.new(
53
+ "delayed_jobs_enqueued", "Number of enqueued delayed jobs.")
54
+
55
+ @delayed_jobs_pending =
56
+ PrometheusExporter::Metric::Gauge.new(
57
+ "delayed_jobs_pending", "Number of pending delayed jobs.")
58
+
48
59
  @delayed_failed_jobs_total =
49
60
  PrometheusExporter::Metric::Counter.new(
50
61
  "delayed_failed_jobs_total", "Total number failed delayed jobs executed.")
@@ -35,6 +35,7 @@ module PrometheusExporter::Server
35
35
 
36
36
  @process_metrics.map do |m|
37
37
  metric_key = m["metric_labels"].merge("pid" => m["pid"])
38
+ metric_key.merge!(m["custom_labels"] || {})
38
39
 
39
40
  PROCESS_GAUGES.map do |k, help|
40
41
  k = k.to_s
@@ -25,11 +25,13 @@ class PrometheusExporter::Server::UnicornCollector < PrometheusExporter::Server:
25
25
  metrics = {}
26
26
 
27
27
  @unicorn_metrics.map do |m|
28
+ labels = m["custom_labels"] || {}
29
+
28
30
  UNICORN_GAUGES.map do |k, help|
29
31
  k = k.to_s
30
32
  if (v = m[k])
31
33
  g = metrics[k] ||= PrometheusExporter::Metric::Gauge.new("unicorn_#{k}", help)
32
- g.observe(v)
34
+ g.observe(v, labels)
33
35
  end
34
36
  end
35
37
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PrometheusExporter
4
- VERSION = "0.4.14"
4
+ VERSION = "0.4.15"
5
5
  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.4.14
4
+ version: 0.4.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-08 00:00:00.000000000 Z
11
+ date: 2019-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -174,6 +174,7 @@ files:
174
174
  - lib/prometheus_exporter.rb
175
175
  - lib/prometheus_exporter/client.rb
176
176
  - lib/prometheus_exporter/instrumentation.rb
177
+ - lib/prometheus_exporter/instrumentation/active_record.rb
177
178
  - lib/prometheus_exporter/instrumentation/delayed_job.rb
178
179
  - lib/prometheus_exporter/instrumentation/hutch.rb
179
180
  - lib/prometheus_exporter/instrumentation/method_profiler.rb
@@ -189,6 +190,7 @@ files:
189
190
  - lib/prometheus_exporter/metric/summary.rb
190
191
  - lib/prometheus_exporter/middleware.rb
191
192
  - lib/prometheus_exporter/server.rb
193
+ - lib/prometheus_exporter/server/active_record_collector.rb
192
194
  - lib/prometheus_exporter/server/collector.rb
193
195
  - lib/prometheus_exporter/server/collector_base.rb
194
196
  - lib/prometheus_exporter/server/delayed_job_collector.rb