prometheus_exporter 0.4.14 → 0.4.15

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: 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