prometheus_exporter 0.7.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +82 -25
- data/Appraisals +7 -3
- data/CHANGELOG +104 -24
- data/Dockerfile +9 -0
- data/README.md +258 -51
- data/bin/prometheus_exporter +19 -6
- data/examples/custom_collector.rb +1 -1
- data/gemfiles/ar_70.gemfile +5 -0
- data/lib/prometheus_exporter/client.rb +48 -23
- data/lib/prometheus_exporter/instrumentation/active_record.rb +11 -29
- data/lib/prometheus_exporter/instrumentation/delayed_job.rb +5 -2
- data/lib/prometheus_exporter/instrumentation/good_job.rb +30 -0
- data/lib/prometheus_exporter/instrumentation/method_profiler.rb +63 -23
- data/lib/prometheus_exporter/instrumentation/periodic_stats.rb +62 -0
- data/lib/prometheus_exporter/instrumentation/process.rb +5 -21
- data/lib/prometheus_exporter/instrumentation/puma.rb +34 -27
- data/lib/prometheus_exporter/instrumentation/resque.rb +35 -0
- data/lib/prometheus_exporter/instrumentation/sidekiq.rb +53 -23
- data/lib/prometheus_exporter/instrumentation/sidekiq_process.rb +52 -0
- data/lib/prometheus_exporter/instrumentation/sidekiq_queue.rb +32 -24
- data/lib/prometheus_exporter/instrumentation/sidekiq_stats.rb +37 -0
- data/lib/prometheus_exporter/instrumentation/unicorn.rb +10 -15
- data/lib/prometheus_exporter/instrumentation.rb +5 -0
- data/lib/prometheus_exporter/metric/base.rb +12 -10
- data/lib/prometheus_exporter/metric/gauge.rb +4 -0
- data/lib/prometheus_exporter/metric/histogram.rb +15 -3
- data/lib/prometheus_exporter/middleware.rb +45 -19
- data/lib/prometheus_exporter/server/active_record_collector.rb +9 -12
- data/lib/prometheus_exporter/server/collector.rb +4 -0
- data/lib/prometheus_exporter/server/delayed_job_collector.rb +24 -18
- data/lib/prometheus_exporter/server/good_job_collector.rb +52 -0
- data/lib/prometheus_exporter/server/metrics_container.rb +66 -0
- data/lib/prometheus_exporter/server/process_collector.rb +8 -13
- data/lib/prometheus_exporter/server/puma_collector.rb +14 -12
- data/lib/prometheus_exporter/server/resque_collector.rb +50 -0
- data/lib/prometheus_exporter/server/runner.rb +14 -3
- data/lib/prometheus_exporter/server/sidekiq_collector.rb +1 -1
- data/lib/prometheus_exporter/server/sidekiq_process_collector.rb +43 -0
- data/lib/prometheus_exporter/server/sidekiq_queue_collector.rb +6 -7
- data/lib/prometheus_exporter/server/sidekiq_stats_collector.rb +48 -0
- data/lib/prometheus_exporter/server/type_collector.rb +2 -0
- data/lib/prometheus_exporter/server/unicorn_collector.rb +32 -33
- data/lib/prometheus_exporter/server/web_collector.rb +17 -17
- data/lib/prometheus_exporter/server/web_server.rb +72 -41
- data/lib/prometheus_exporter/server.rb +4 -0
- data/lib/prometheus_exporter/version.rb +1 -1
- data/lib/prometheus_exporter.rb +12 -13
- data/prometheus_exporter.gemspec +6 -6
- metadata +53 -14
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "socket"
|
4
|
+
require "logger"
|
5
5
|
|
6
6
|
module PrometheusExporter
|
7
7
|
class Client
|
@@ -24,7 +24,9 @@ module PrometheusExporter
|
|
24
24
|
keys: keys,
|
25
25
|
value: value
|
26
26
|
}
|
27
|
-
values[
|
27
|
+
values[
|
28
|
+
:prometheus_exporter_action
|
29
|
+
] = prometheus_exporter_action if prometheus_exporter_action
|
28
30
|
values[:opts] = @opts if @opts
|
29
31
|
values
|
30
32
|
end
|
@@ -53,14 +55,23 @@ module PrometheusExporter
|
|
53
55
|
MAX_SOCKET_AGE = 25
|
54
56
|
MAX_QUEUE_SIZE = 10_000
|
55
57
|
|
58
|
+
attr_reader :logger
|
59
|
+
|
56
60
|
def initialize(
|
57
|
-
host: ENV.fetch(
|
58
|
-
port: ENV.fetch(
|
61
|
+
host: ENV.fetch("PROMETHEUS_EXPORTER_HOST", "localhost"),
|
62
|
+
port: ENV.fetch(
|
63
|
+
"PROMETHEUS_EXPORTER_PORT",
|
64
|
+
PrometheusExporter::DEFAULT_PORT
|
65
|
+
),
|
59
66
|
max_queue_size: nil,
|
60
67
|
thread_sleep: 0.5,
|
61
68
|
json_serializer: nil,
|
62
|
-
custom_labels: nil
|
69
|
+
custom_labels: nil,
|
70
|
+
logger: Logger.new(STDERR),
|
71
|
+
log_level: Logger::WARN
|
63
72
|
)
|
73
|
+
@logger = logger
|
74
|
+
@logger.level = log_level
|
64
75
|
@metrics = []
|
65
76
|
|
66
77
|
@queue = Queue.new
|
@@ -72,7 +83,7 @@ module PrometheusExporter
|
|
72
83
|
max_queue_size ||= MAX_QUEUE_SIZE
|
73
84
|
max_queue_size = max_queue_size.to_i
|
74
85
|
|
75
|
-
if max_queue_size
|
86
|
+
if max_queue_size <= 0
|
76
87
|
raise ArgumentError, "max_queue_size must be larger than 0"
|
77
88
|
end
|
78
89
|
|
@@ -83,7 +94,8 @@ module PrometheusExporter
|
|
83
94
|
@mutex = Mutex.new
|
84
95
|
@thread_sleep = thread_sleep
|
85
96
|
|
86
|
-
@json_serializer =
|
97
|
+
@json_serializer =
|
98
|
+
json_serializer == :oj ? PrometheusExporter::OjCompat : JSON
|
87
99
|
|
88
100
|
@custom_labels = custom_labels
|
89
101
|
end
|
@@ -93,7 +105,14 @@ module PrometheusExporter
|
|
93
105
|
end
|
94
106
|
|
95
107
|
def register(type, name, help, opts = nil)
|
96
|
-
metric =
|
108
|
+
metric =
|
109
|
+
RemoteMetric.new(
|
110
|
+
type: type,
|
111
|
+
name: name,
|
112
|
+
help: help,
|
113
|
+
client: self,
|
114
|
+
opts: opts
|
115
|
+
)
|
97
116
|
@metrics << metric
|
98
117
|
metric
|
99
118
|
end
|
@@ -125,7 +144,7 @@ module PrometheusExporter
|
|
125
144
|
def send(str)
|
126
145
|
@queue << str
|
127
146
|
if @queue.length > @max_queue_size
|
128
|
-
|
147
|
+
logger.warn "Prometheus Exporter client is dropping message cause queue is full"
|
129
148
|
@queue.pop
|
130
149
|
end
|
131
150
|
|
@@ -143,7 +162,7 @@ module PrometheusExporter
|
|
143
162
|
@socket.write(message)
|
144
163
|
@socket.write("\r\n")
|
145
164
|
rescue => e
|
146
|
-
|
165
|
+
logger.warn "Prometheus Exporter is dropping a message: #{e}"
|
147
166
|
@socket = nil
|
148
167
|
raise
|
149
168
|
end
|
@@ -154,9 +173,7 @@ module PrometheusExporter
|
|
154
173
|
@mutex.synchronize do
|
155
174
|
wait_for_empty_queue_with_timeout(wait_timeout_seconds)
|
156
175
|
@worker_thread&.kill
|
157
|
-
while @worker_thread&.alive?
|
158
|
-
sleep 0.001
|
159
|
-
end
|
176
|
+
sleep 0.001 while @worker_thread&.alive?
|
160
177
|
@worker_thread = nil
|
161
178
|
close_socket!
|
162
179
|
end
|
@@ -168,7 +185,7 @@ module PrometheusExporter
|
|
168
185
|
close_socket_if_old!
|
169
186
|
process_queue
|
170
187
|
rescue => e
|
171
|
-
|
188
|
+
logger.error "Prometheus Exporter, failed to send message #{e}"
|
172
189
|
end
|
173
190
|
|
174
191
|
def ensure_worker_thread!
|
@@ -176,14 +193,18 @@ module PrometheusExporter
|
|
176
193
|
@mutex.synchronize do
|
177
194
|
return if @worker_thread&.alive?
|
178
195
|
|
179
|
-
@worker_thread =
|
180
|
-
|
181
|
-
|
182
|
-
|
196
|
+
@worker_thread =
|
197
|
+
Thread.new do
|
198
|
+
while true
|
199
|
+
worker_loop
|
200
|
+
sleep @thread_sleep
|
201
|
+
end
|
183
202
|
end
|
184
|
-
end
|
185
203
|
end
|
186
204
|
end
|
205
|
+
rescue ThreadError => e
|
206
|
+
raise unless e.message =~ /can't alloc thread/
|
207
|
+
logger.error "Prometheus Exporter, failed to send message ThreadError #{e}"
|
187
208
|
end
|
188
209
|
|
189
210
|
def close_socket!
|
@@ -202,7 +223,8 @@ module PrometheusExporter
|
|
202
223
|
end
|
203
224
|
|
204
225
|
def close_socket_if_old!
|
205
|
-
if @socket_pid == Process.pid && @socket && @socket_started &&
|
226
|
+
if @socket_pid == Process.pid && @socket && @socket_started &&
|
227
|
+
((@socket_started + MAX_SOCKET_AGE) < Time.now.to_f)
|
206
228
|
close_socket!
|
207
229
|
end
|
208
230
|
end
|
@@ -230,7 +252,7 @@ module PrometheusExporter
|
|
230
252
|
end
|
231
253
|
|
232
254
|
nil
|
233
|
-
rescue
|
255
|
+
rescue StandardError
|
234
256
|
@socket = nil
|
235
257
|
@socket_started = nil
|
236
258
|
@socket_pid = nil
|
@@ -240,7 +262,10 @@ module PrometheusExporter
|
|
240
262
|
def wait_for_empty_queue_with_timeout(timeout_seconds)
|
241
263
|
start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
242
264
|
while @queue.length > 0
|
243
|
-
|
265
|
+
if start_time + timeout_seconds <
|
266
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
267
|
+
break
|
268
|
+
end
|
244
269
|
sleep(0.05)
|
245
270
|
end
|
246
271
|
end
|
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
# collects stats from currently running process
|
4
4
|
module PrometheusExporter::Instrumentation
|
5
|
-
class ActiveRecord
|
5
|
+
class ActiveRecord < PeriodicStats
|
6
6
|
ALLOWED_CONFIG_LABELS = %i(database username host port)
|
7
7
|
|
8
8
|
def self.start(client: nil, frequency: 30, custom_labels: {}, config_labels: [])
|
9
|
+
client ||= PrometheusExporter::Client.default
|
9
10
|
|
10
|
-
# Not all rails versions support
|
11
|
+
# Not all rails versions support connection pool stats
|
11
12
|
unless ::ActiveRecord::Base.connection_pool.respond_to?(:stat)
|
12
|
-
|
13
|
+
client.logger.error("ActiveRecord connection pool stats not supported in your rails version")
|
13
14
|
return
|
14
15
|
end
|
15
16
|
|
@@ -18,22 +19,12 @@ module PrometheusExporter::Instrumentation
|
|
18
19
|
|
19
20
|
active_record_collector = new(custom_labels, config_labels)
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
22
|
+
worker_loop do
|
23
|
+
metrics = active_record_collector.collect
|
24
|
+
metrics.each { |metric| client.send_json metric }
|
36
25
|
end
|
26
|
+
|
27
|
+
super
|
37
28
|
end
|
38
29
|
|
39
30
|
def self.validate_config_labels(config_labels)
|
@@ -41,13 +32,6 @@ module PrometheusExporter::Instrumentation
|
|
41
32
|
raise "Invalid Config Labels, available options #{ALLOWED_CONFIG_LABELS}" if (config_labels - ALLOWED_CONFIG_LABELS).size > 0
|
42
33
|
end
|
43
34
|
|
44
|
-
def self.stop
|
45
|
-
if t = @thread
|
46
|
-
t.kill
|
47
|
-
@thread = nil
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
35
|
def initialize(metric_labels, config_labels)
|
52
36
|
@metric_labels = metric_labels
|
53
37
|
@config_labels = config_labels
|
@@ -81,15 +65,13 @@ module PrometheusExporter::Instrumentation
|
|
81
65
|
private
|
82
66
|
|
83
67
|
def labels(pool)
|
84
|
-
if
|
68
|
+
if ::ActiveRecord.version < Gem::Version.new("6.1.0.rc1")
|
85
69
|
@metric_labels.merge(pool_name: pool.spec.name).merge(pool.spec.config
|
86
70
|
.select { |k, v| @config_labels.include? k }
|
87
71
|
.map { |k, v| [k.to_s.dup.prepend("dbconfig_"), v] }.to_h)
|
88
|
-
|
72
|
+
else
|
89
73
|
@metric_labels.merge(pool_name: pool.db_config.name).merge(
|
90
74
|
@config_labels.each_with_object({}) { |l, acc| acc["dbconfig_#{l}"] = pool.db_config.public_send(l) })
|
91
|
-
else
|
92
|
-
raise "Unsupported connection pool"
|
93
75
|
end
|
94
76
|
end
|
95
77
|
end
|
@@ -13,8 +13,8 @@ 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
|
-
enqueued_count = Delayed::Job.count
|
17
|
-
pending_count = Delayed::Job.where(attempts: 0, locked_at: nil).count
|
16
|
+
enqueued_count = Delayed::Job.where(queue: job.queue).count
|
17
|
+
pending_count = Delayed::Job.where(attempts: 0, locked_at: nil, queue: job.queue).count
|
18
18
|
instrumenter.call(job, max_attempts, enqueued_count, pending_count, *args, &block)
|
19
19
|
end
|
20
20
|
end
|
@@ -31,6 +31,7 @@ module PrometheusExporter::Instrumentation
|
|
31
31
|
def call(job, max_attempts, enqueued_count, pending_count, *args, &block)
|
32
32
|
success = false
|
33
33
|
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
34
|
+
latency = Time.current - job.run_at
|
34
35
|
attempts = job.attempts + 1 # Increment because we're adding the current attempt
|
35
36
|
result = block.call(job, *args)
|
36
37
|
success = true
|
@@ -41,8 +42,10 @@ module PrometheusExporter::Instrumentation
|
|
41
42
|
@client.send_json(
|
42
43
|
type: "delayed_job",
|
43
44
|
name: job.handler.to_s.match(JOB_CLASS_REGEXP).to_a[1].to_s,
|
45
|
+
queue_name: job.queue,
|
44
46
|
success: success,
|
45
47
|
duration: duration,
|
48
|
+
latency: latency,
|
46
49
|
attempts: attempts,
|
47
50
|
max_attempts: max_attempts,
|
48
51
|
enqueued: enqueued_count,
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# collects stats from GoodJob
|
4
|
+
module PrometheusExporter::Instrumentation
|
5
|
+
class GoodJob < PeriodicStats
|
6
|
+
def self.start(client: nil, frequency: 30)
|
7
|
+
good_job_collector = new
|
8
|
+
client ||= PrometheusExporter::Client.default
|
9
|
+
|
10
|
+
worker_loop do
|
11
|
+
client.send_json(good_job_collector.collect)
|
12
|
+
end
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def collect
|
18
|
+
{
|
19
|
+
type: "good_job",
|
20
|
+
scheduled: ::GoodJob::Job.scheduled.size,
|
21
|
+
retried: ::GoodJob::Job.retried.size,
|
22
|
+
queued: ::GoodJob::Job.queued.size,
|
23
|
+
running: ::GoodJob::Job.running.size,
|
24
|
+
finished: ::GoodJob::Job.finished.size,
|
25
|
+
succeeded: ::GoodJob::Job.succeeded.size,
|
26
|
+
discarded: ::GoodJob::Job.discarded.size
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -4,29 +4,14 @@
|
|
4
4
|
module PrometheusExporter::Instrumentation; end
|
5
5
|
|
6
6
|
class PrometheusExporter::Instrumentation::MethodProfiler
|
7
|
-
def self.patch(klass, methods, name)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
begin
|
17
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
18
|
-
#{method_name}__mp_unpatched(*args, &blk)
|
19
|
-
ensure
|
20
|
-
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
21
|
-
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
22
|
-
data[:calls] += 1
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
RUBY
|
27
|
-
end.join("\n")
|
28
|
-
|
29
|
-
klass.class_eval patches
|
7
|
+
def self.patch(klass, methods, name, instrument:)
|
8
|
+
if instrument == :alias_method
|
9
|
+
patch_using_alias_method(klass, methods, name)
|
10
|
+
elsif instrument == :prepend
|
11
|
+
patch_using_prepend(klass, methods, name)
|
12
|
+
else
|
13
|
+
raise ArgumentError, "instrument must be :alias_method or :prepend"
|
14
|
+
end
|
30
15
|
end
|
31
16
|
|
32
17
|
def self.transfer
|
@@ -54,4 +39,59 @@ class PrometheusExporter::Instrumentation::MethodProfiler
|
|
54
39
|
end
|
55
40
|
data
|
56
41
|
end
|
42
|
+
|
43
|
+
def self.define_methods_on_module(klass, methods, name)
|
44
|
+
patch_source_line = __LINE__ + 3
|
45
|
+
patches = methods.map do |method_name|
|
46
|
+
<<~RUBY
|
47
|
+
def #{method_name}(*args, &blk)
|
48
|
+
unless prof = Thread.current[:_method_profiler]
|
49
|
+
return super
|
50
|
+
end
|
51
|
+
begin
|
52
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
53
|
+
super
|
54
|
+
ensure
|
55
|
+
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
56
|
+
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
57
|
+
data[:calls] += 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
RUBY
|
61
|
+
end.join("\n")
|
62
|
+
|
63
|
+
klass.module_eval patches, __FILE__, patch_source_line
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.patch_using_prepend(klass, methods, name)
|
67
|
+
prepend_instrument = Module.new
|
68
|
+
define_methods_on_module(prepend_instrument, methods, name)
|
69
|
+
klass.prepend(prepend_instrument)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.patch_using_alias_method(klass, methods, name)
|
73
|
+
patch_source_line = __LINE__ + 3
|
74
|
+
patches = methods.map do |method_name|
|
75
|
+
<<~RUBY
|
76
|
+
unless defined?(#{method_name}__mp_unpatched)
|
77
|
+
alias_method :#{method_name}__mp_unpatched, :#{method_name}
|
78
|
+
def #{method_name}(*args, &blk)
|
79
|
+
unless prof = Thread.current[:_method_profiler]
|
80
|
+
return #{method_name}__mp_unpatched(*args, &blk)
|
81
|
+
end
|
82
|
+
begin
|
83
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
84
|
+
#{method_name}__mp_unpatched(*args, &blk)
|
85
|
+
ensure
|
86
|
+
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
87
|
+
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
88
|
+
data[:calls] += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
RUBY
|
93
|
+
end.join("\n")
|
94
|
+
|
95
|
+
klass.class_eval patches, __FILE__, patch_source_line
|
96
|
+
end
|
57
97
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrometheusExporter::Instrumentation
|
4
|
+
class PeriodicStats
|
5
|
+
|
6
|
+
def self.start(*args, frequency:, client: nil, **kwargs)
|
7
|
+
client ||= PrometheusExporter::Client.default
|
8
|
+
|
9
|
+
if !(Numeric === frequency)
|
10
|
+
raise ArgumentError.new("Expected frequency to be a number")
|
11
|
+
end
|
12
|
+
|
13
|
+
if frequency < 0
|
14
|
+
raise ArgumentError.new("Expected frequency to be a positive number")
|
15
|
+
end
|
16
|
+
|
17
|
+
if !@worker_loop
|
18
|
+
raise ArgumentError.new("Worker loop was not set")
|
19
|
+
end
|
20
|
+
|
21
|
+
klass = self
|
22
|
+
|
23
|
+
stop
|
24
|
+
|
25
|
+
@stop_thread = false
|
26
|
+
|
27
|
+
@thread = Thread.new do
|
28
|
+
while !@stop_thread
|
29
|
+
begin
|
30
|
+
@worker_loop.call
|
31
|
+
rescue => e
|
32
|
+
client.logger.error("#{klass} Prometheus Exporter Failed To Collect Stats #{e}")
|
33
|
+
ensure
|
34
|
+
sleep frequency
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.started?
|
42
|
+
!!@thread&.alive?
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.worker_loop(&blk)
|
46
|
+
@worker_loop = blk
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.stop
|
50
|
+
# to avoid a warning
|
51
|
+
@thread = nil if !defined?(@thread)
|
52
|
+
|
53
|
+
if @thread&.alive?
|
54
|
+
@stop_thread = true
|
55
|
+
@thread.wakeup
|
56
|
+
@thread.join
|
57
|
+
end
|
58
|
+
@thread = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -2,8 +2,7 @@
|
|
2
2
|
|
3
3
|
# collects stats from currently running process
|
4
4
|
module PrometheusExporter::Instrumentation
|
5
|
-
class Process
|
6
|
-
@thread = nil if !defined?(@thread)
|
5
|
+
class Process < PeriodicStats
|
7
6
|
|
8
7
|
def self.start(client: nil, type: "ruby", frequency: 30, labels: nil)
|
9
8
|
|
@@ -19,27 +18,12 @@ module PrometheusExporter::Instrumentation
|
|
19
18
|
process_collector = new(metric_labels)
|
20
19
|
client ||= PrometheusExporter::Client.default
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
while true
|
26
|
-
begin
|
27
|
-
metric = process_collector.collect
|
28
|
-
client.send_json metric
|
29
|
-
rescue => e
|
30
|
-
STDERR.puts("Prometheus Exporter Failed To Collect Process Stats #{e}")
|
31
|
-
ensure
|
32
|
-
sleep frequency
|
33
|
-
end
|
34
|
-
end
|
21
|
+
worker_loop do
|
22
|
+
metric = process_collector.collect
|
23
|
+
client.send_json metric
|
35
24
|
end
|
36
|
-
end
|
37
25
|
|
38
|
-
|
39
|
-
if t = @thread
|
40
|
-
t.kill
|
41
|
-
@thread = nil
|
42
|
-
end
|
26
|
+
super
|
43
27
|
end
|
44
28
|
|
45
29
|
def initialize(metric_labels)
|
@@ -4,39 +4,46 @@ require "json"
|
|
4
4
|
|
5
5
|
# collects stats from puma
|
6
6
|
module PrometheusExporter::Instrumentation
|
7
|
-
class Puma
|
8
|
-
def self.start(client: nil, frequency: 30)
|
9
|
-
puma_collector = new
|
7
|
+
class Puma < PeriodicStats
|
8
|
+
def self.start(client: nil, frequency: 30, labels: {})
|
9
|
+
puma_collector = new(labels)
|
10
10
|
client ||= PrometheusExporter::Client.default
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
client.send_json metric
|
16
|
-
rescue => e
|
17
|
-
STDERR.puts("Prometheus Exporter Failed To Collect Puma Stats #{e}")
|
18
|
-
ensure
|
19
|
-
sleep frequency
|
20
|
-
end
|
21
|
-
end
|
11
|
+
|
12
|
+
worker_loop do
|
13
|
+
metric = puma_collector.collect
|
14
|
+
client.send_json metric
|
22
15
|
end
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(metric_labels = {})
|
21
|
+
@metric_labels = metric_labels
|
23
22
|
end
|
24
23
|
|
25
24
|
def collect
|
26
|
-
metric = {
|
27
|
-
|
25
|
+
metric = {
|
26
|
+
pid: pid,
|
27
|
+
type: "puma",
|
28
|
+
hostname: ::PrometheusExporter.hostname,
|
29
|
+
metric_labels: @metric_labels
|
30
|
+
}
|
28
31
|
collect_puma_stats(metric)
|
29
32
|
metric
|
30
33
|
end
|
31
34
|
|
35
|
+
def pid
|
36
|
+
@pid = ::Process.pid
|
37
|
+
end
|
38
|
+
|
32
39
|
def collect_puma_stats(metric)
|
33
40
|
stats = JSON.parse(::Puma.stats)
|
34
41
|
|
35
42
|
if stats.key?("workers")
|
36
43
|
metric[:phase] = stats["phase"]
|
37
|
-
metric[:
|
38
|
-
metric[:
|
39
|
-
metric[:
|
44
|
+
metric[:workers] = stats["workers"]
|
45
|
+
metric[:booted_workers] = stats["booted_workers"]
|
46
|
+
metric[:old_workers] = stats["old_workers"]
|
40
47
|
|
41
48
|
stats["worker_status"].each do |worker|
|
42
49
|
next if worker["last_status"].empty?
|
@@ -50,15 +57,15 @@ module PrometheusExporter::Instrumentation
|
|
50
57
|
private
|
51
58
|
|
52
59
|
def collect_worker_status(metric, status)
|
53
|
-
metric[:
|
54
|
-
metric[:
|
55
|
-
metric[:
|
56
|
-
metric[:
|
60
|
+
metric[:request_backlog] ||= 0
|
61
|
+
metric[:running_threads] ||= 0
|
62
|
+
metric[:thread_pool_capacity] ||= 0
|
63
|
+
metric[:max_threads] ||= 0
|
57
64
|
|
58
|
-
metric[:
|
59
|
-
metric[:
|
60
|
-
metric[:
|
61
|
-
metric[:
|
65
|
+
metric[:request_backlog] += status["backlog"]
|
66
|
+
metric[:running_threads] += status["running"]
|
67
|
+
metric[:thread_pool_capacity] += status["pool_capacity"]
|
68
|
+
metric[:max_threads] += status["max_threads"]
|
62
69
|
end
|
63
70
|
end
|
64
71
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# collects stats from resque
|
4
|
+
module PrometheusExporter::Instrumentation
|
5
|
+
class Resque < PeriodicStats
|
6
|
+
def self.start(client: nil, frequency: 30)
|
7
|
+
resque_collector = new
|
8
|
+
client ||= PrometheusExporter::Client.default
|
9
|
+
|
10
|
+
worker_loop do
|
11
|
+
client.send_json(resque_collector.collect)
|
12
|
+
end
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def collect
|
18
|
+
metric = {}
|
19
|
+
metric[:type] = "resque"
|
20
|
+
collect_resque_stats(metric)
|
21
|
+
metric
|
22
|
+
end
|
23
|
+
|
24
|
+
def collect_resque_stats(metric)
|
25
|
+
info = ::Resque.info
|
26
|
+
|
27
|
+
metric[:processed_jobs] = info[:processed]
|
28
|
+
metric[:failed_jobs] = info[:failed]
|
29
|
+
metric[:pending_jobs] = info[:pending]
|
30
|
+
metric[:queues] = info[:queues]
|
31
|
+
metric[:worker] = info[:workers]
|
32
|
+
metric[:working] = info[:working]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|