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 +4 -4
- data/CHANGELOG +7 -0
- data/README.md +66 -0
- data/lib/prometheus_exporter/client.rb +5 -5
- data/lib/prometheus_exporter/instrumentation.rb +1 -0
- data/lib/prometheus_exporter/instrumentation/active_record.rb +98 -0
- data/lib/prometheus_exporter/instrumentation/delayed_job.rb +7 -3
- data/lib/prometheus_exporter/instrumentation/unicorn.rb +1 -1
- data/lib/prometheus_exporter/server.rb +1 -0
- data/lib/prometheus_exporter/server/active_record_collector.rb +56 -0
- data/lib/prometheus_exporter/server/collector.rb +8 -2
- data/lib/prometheus_exporter/server/delayed_job_collector.rb +12 -1
- data/lib/prometheus_exporter/server/process_collector.rb +1 -0
- data/lib/prometheus_exporter/server/unicorn_collector.rb +3 -1
- data/lib/prometheus_exporter/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86155ae93296d42918b6419efbdae95665e5149a89083894dd9ba5daaf9f84bf
|
4
|
+
data.tar.gz: 49bee52b66976743b17794f7515cc57c7c6979338d479a66ec49ae0ff30d9f7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
@@ -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
|
-
|
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
|
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
|
@@ -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.")
|
@@ -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
|
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.
|
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-
|
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
|