prometheus_exporter 0.6.0 → 1.0.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 +17 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +5 -0
- data/Appraisals +10 -0
- data/CHANGELOG +33 -1
- data/README.md +115 -18
- data/bin/prometheus_exporter +17 -4
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/ar_60.gemfile +5 -0
- data/gemfiles/ar_61.gemfile +7 -0
- data/lib/prometheus_exporter/client.rb +16 -6
- data/lib/prometheus_exporter/instrumentation/active_record.rb +19 -12
- data/lib/prometheus_exporter/instrumentation/delayed_job.rb +3 -2
- data/lib/prometheus_exporter/instrumentation/method_profiler.rb +2 -1
- data/lib/prometheus_exporter/instrumentation/process.rb +1 -1
- data/lib/prometheus_exporter/instrumentation/puma.rb +28 -16
- data/lib/prometheus_exporter/instrumentation/resque.rb +40 -0
- data/lib/prometheus_exporter/instrumentation/sidekiq_process.rb +58 -0
- data/lib/prometheus_exporter/instrumentation/sidekiq_queue.rb +27 -13
- data/lib/prometheus_exporter/instrumentation/unicorn.rb +4 -4
- data/lib/prometheus_exporter/instrumentation.rb +2 -0
- data/lib/prometheus_exporter/metric/base.rb +9 -0
- data/lib/prometheus_exporter/metric/gauge.rb +4 -0
- data/lib/prometheus_exporter/middleware.rb +31 -19
- data/lib/prometheus_exporter/server/active_record_collector.rb +2 -1
- data/lib/prometheus_exporter/server/collector.rb +2 -0
- data/lib/prometheus_exporter/server/delayed_job_collector.rb +17 -17
- data/lib/prometheus_exporter/server/puma_collector.rb +16 -8
- data/lib/prometheus_exporter/server/resque_collector.rb +54 -0
- data/lib/prometheus_exporter/server/runner.rb +11 -2
- data/lib/prometheus_exporter/server/sidekiq_collector.rb +1 -1
- data/lib/prometheus_exporter/server/sidekiq_process_collector.rb +46 -0
- data/lib/prometheus_exporter/server/sidekiq_queue_collector.rb +1 -1
- data/lib/prometheus_exporter/server/unicorn_collector.rb +3 -3
- data/lib/prometheus_exporter/server/web_collector.rb +6 -9
- data/lib/prometheus_exporter/server/web_server.rb +6 -8
- data/lib/prometheus_exporter/server.rb +2 -0
- data/lib/prometheus_exporter/version.rb +1 -1
- data/prometheus_exporter.gemspec +7 -3
- metadata +62 -12
@@ -5,8 +5,8 @@ require "json"
|
|
5
5
|
# collects stats from puma
|
6
6
|
module PrometheusExporter::Instrumentation
|
7
7
|
class Puma
|
8
|
-
def self.start(client: nil, frequency: 30)
|
9
|
-
puma_collector = new
|
8
|
+
def self.start(client: nil, frequency: 30, labels: {})
|
9
|
+
puma_collector = new(labels)
|
10
10
|
client ||= PrometheusExporter::Client.default
|
11
11
|
Thread.new do
|
12
12
|
while true
|
@@ -14,7 +14,7 @@ module PrometheusExporter::Instrumentation
|
|
14
14
|
metric = puma_collector.collect
|
15
15
|
client.send_json metric
|
16
16
|
rescue => e
|
17
|
-
|
17
|
+
client.logger.error("Prometheus Exporter Failed To Collect Puma Stats #{e}")
|
18
18
|
ensure
|
19
19
|
sleep frequency
|
20
20
|
end
|
@@ -22,21 +22,33 @@ module PrometheusExporter::Instrumentation
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
def initialize(metric_labels = {})
|
26
|
+
@metric_labels = metric_labels
|
27
|
+
end
|
28
|
+
|
25
29
|
def collect
|
26
|
-
metric = {
|
27
|
-
|
30
|
+
metric = {
|
31
|
+
pid: pid,
|
32
|
+
type: "puma",
|
33
|
+
hostname: ::PrometheusExporter.hostname,
|
34
|
+
metric_labels: @metric_labels
|
35
|
+
}
|
28
36
|
collect_puma_stats(metric)
|
29
37
|
metric
|
30
38
|
end
|
31
39
|
|
40
|
+
def pid
|
41
|
+
@pid = ::Process.pid
|
42
|
+
end
|
43
|
+
|
32
44
|
def collect_puma_stats(metric)
|
33
45
|
stats = JSON.parse(::Puma.stats)
|
34
46
|
|
35
47
|
if stats.key?("workers")
|
36
48
|
metric[:phase] = stats["phase"]
|
37
|
-
metric[:
|
38
|
-
metric[:
|
39
|
-
metric[:
|
49
|
+
metric[:workers] = stats["workers"]
|
50
|
+
metric[:booted_workers] = stats["booted_workers"]
|
51
|
+
metric[:old_workers] = stats["old_workers"]
|
40
52
|
|
41
53
|
stats["worker_status"].each do |worker|
|
42
54
|
next if worker["last_status"].empty?
|
@@ -50,15 +62,15 @@ module PrometheusExporter::Instrumentation
|
|
50
62
|
private
|
51
63
|
|
52
64
|
def collect_worker_status(metric, status)
|
53
|
-
metric[:
|
54
|
-
metric[:
|
55
|
-
metric[:
|
56
|
-
metric[:
|
65
|
+
metric[:request_backlog] ||= 0
|
66
|
+
metric[:running_threads] ||= 0
|
67
|
+
metric[:thread_pool_capacity] ||= 0
|
68
|
+
metric[:max_threads] ||= 0
|
57
69
|
|
58
|
-
metric[:
|
59
|
-
metric[:
|
60
|
-
metric[:
|
61
|
-
metric[:
|
70
|
+
metric[:request_backlog] += status["backlog"]
|
71
|
+
metric[:running_threads] += status["running"]
|
72
|
+
metric[:thread_pool_capacity] += status["pool_capacity"]
|
73
|
+
metric[:max_threads] += status["max_threads"]
|
62
74
|
end
|
63
75
|
end
|
64
76
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# collects stats from resque
|
4
|
+
module PrometheusExporter::Instrumentation
|
5
|
+
class Resque
|
6
|
+
def self.start(client: nil, frequency: 30)
|
7
|
+
resque_collector = new
|
8
|
+
client ||= PrometheusExporter::Client.default
|
9
|
+
Thread.new do
|
10
|
+
while true
|
11
|
+
begin
|
12
|
+
client.send_json(resque_collector.collect)
|
13
|
+
rescue => e
|
14
|
+
client.logger.error("Prometheus Exporter Failed To Collect Resque Stats #{e}")
|
15
|
+
ensure
|
16
|
+
sleep frequency
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def collect
|
23
|
+
metric = {}
|
24
|
+
metric[:type] = "resque"
|
25
|
+
collect_resque_stats(metric)
|
26
|
+
metric
|
27
|
+
end
|
28
|
+
|
29
|
+
def collect_resque_stats(metric)
|
30
|
+
info = ::Resque.info
|
31
|
+
|
32
|
+
metric[:processed_jobs] = info[:processed]
|
33
|
+
metric[:failed_jobs] = info[:failed]
|
34
|
+
metric[:pending_jobs] = info[:pending]
|
35
|
+
metric[:queues] = info[:queues]
|
36
|
+
metric[:worker] = info[:workers]
|
37
|
+
metric[:working] = info[:working]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrometheusExporter::Instrumentation
|
4
|
+
class SidekiqProcess
|
5
|
+
def self.start(client: nil, frequency: 30)
|
6
|
+
client ||= PrometheusExporter::Client.default
|
7
|
+
sidekiq_process_collector = new
|
8
|
+
|
9
|
+
Thread.new do
|
10
|
+
loop do
|
11
|
+
begin
|
12
|
+
client.send_json(sidekiq_process_collector.collect)
|
13
|
+
rescue StandardError => e
|
14
|
+
STDERR.puts("Prometheus Exporter Failed To Collect Sidekiq Processes metrics #{e}")
|
15
|
+
ensure
|
16
|
+
sleep frequency
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@pid = ::Process.pid
|
24
|
+
@hostname = Socket.gethostname
|
25
|
+
end
|
26
|
+
|
27
|
+
def collect
|
28
|
+
{
|
29
|
+
type: 'sidekiq_process',
|
30
|
+
process: collect_stats
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def collect_stats
|
35
|
+
process = current_process
|
36
|
+
return {} unless process
|
37
|
+
|
38
|
+
{
|
39
|
+
busy: process['busy'],
|
40
|
+
concurrency: process['concurrency'],
|
41
|
+
labels: {
|
42
|
+
labels: process['labels'].sort.join(','),
|
43
|
+
queues: process['queues'].sort.join(','),
|
44
|
+
quiet: process['quiet'],
|
45
|
+
tag: process['tag'],
|
46
|
+
hostname: process['hostname'],
|
47
|
+
identity: process['identity'],
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def current_process
|
53
|
+
::Sidekiq::ProcessSet.new.find do |sp|
|
54
|
+
sp['hostname'] == @hostname && sp['pid'] == @pid
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -2,16 +2,16 @@
|
|
2
2
|
|
3
3
|
module PrometheusExporter::Instrumentation
|
4
4
|
class SidekiqQueue
|
5
|
-
def self.start(client: nil, frequency: 30)
|
5
|
+
def self.start(client: nil, frequency: 30, all_queues: false)
|
6
6
|
client ||= PrometheusExporter::Client.default
|
7
|
-
sidekiq_queue_collector = new
|
7
|
+
sidekiq_queue_collector = new(all_queues: all_queues)
|
8
8
|
|
9
9
|
Thread.new do
|
10
10
|
loop do
|
11
11
|
begin
|
12
12
|
client.send_json(sidekiq_queue_collector.collect)
|
13
13
|
rescue StandardError => e
|
14
|
-
|
14
|
+
client.logger.error("Prometheus Exporter Failed To Collect Sidekiq Queue metrics #{e}")
|
15
15
|
ensure
|
16
16
|
sleep frequency
|
17
17
|
end
|
@@ -19,6 +19,12 @@ module PrometheusExporter::Instrumentation
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
def initialize(all_queues: false)
|
23
|
+
@all_queues = all_queues
|
24
|
+
@pid = ::Process.pid
|
25
|
+
@hostname = Socket.gethostname
|
26
|
+
end
|
27
|
+
|
22
28
|
def collect
|
23
29
|
{
|
24
30
|
type: 'sidekiq_queue',
|
@@ -27,24 +33,32 @@ module PrometheusExporter::Instrumentation
|
|
27
33
|
end
|
28
34
|
|
29
35
|
def collect_queue_stats
|
30
|
-
|
31
|
-
pid = ::Process.pid
|
32
|
-
ps = ::Sidekiq::ProcessSet.new
|
36
|
+
sidekiq_queues = ::Sidekiq::Queue.all
|
33
37
|
|
34
|
-
|
35
|
-
|
38
|
+
unless @all_queues
|
39
|
+
queues = collect_current_process_queues
|
40
|
+
sidekiq_queues.select! { |sidekiq_queue| queues.include?(sidekiq_queue.name) }
|
36
41
|
end
|
37
42
|
|
38
|
-
|
39
|
-
|
40
|
-
::Sidekiq::Queue.all.map do |queue|
|
41
|
-
next unless queues.include? queue.name
|
43
|
+
sidekiq_queues.map do |queue|
|
42
44
|
{
|
43
|
-
|
45
|
+
backlog: queue.size,
|
44
46
|
latency_seconds: queue.latency.to_i,
|
45
47
|
labels: { queue: queue.name }
|
46
48
|
}
|
47
49
|
end.compact
|
48
50
|
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def collect_current_process_queues
|
55
|
+
ps = ::Sidekiq::ProcessSet.new
|
56
|
+
|
57
|
+
process = ps.find do |sp|
|
58
|
+
sp['hostname'] == @hostname && sp['pid'] == @pid
|
59
|
+
end
|
60
|
+
|
61
|
+
process.nil? ? [] : process['queues']
|
62
|
+
end
|
49
63
|
end
|
50
64
|
end
|
@@ -18,7 +18,7 @@ module PrometheusExporter::Instrumentation
|
|
18
18
|
metric = unicorn_collector.collect
|
19
19
|
client.send_json metric
|
20
20
|
rescue StandardError => e
|
21
|
-
|
21
|
+
client.logger.error("Prometheus Exporter Failed To Collect Unicorn Stats #{e}")
|
22
22
|
ensure
|
23
23
|
sleep frequency
|
24
24
|
end
|
@@ -42,9 +42,9 @@ module PrometheusExporter::Instrumentation
|
|
42
42
|
def collect_unicorn_stats(metric)
|
43
43
|
stats = listener_address_stats
|
44
44
|
|
45
|
-
metric[:
|
46
|
-
metric[:
|
47
|
-
metric[:
|
45
|
+
metric[:active_workers] = stats.active
|
46
|
+
metric[:request_backlog] = stats.queued
|
47
|
+
metric[:workers] = worker_process_count
|
48
48
|
end
|
49
49
|
|
50
50
|
private
|
@@ -5,9 +5,11 @@ require_relative "instrumentation/process"
|
|
5
5
|
require_relative "instrumentation/method_profiler"
|
6
6
|
require_relative "instrumentation/sidekiq"
|
7
7
|
require_relative "instrumentation/sidekiq_queue"
|
8
|
+
require_relative "instrumentation/sidekiq_process"
|
8
9
|
require_relative "instrumentation/delayed_job"
|
9
10
|
require_relative "instrumentation/puma"
|
10
11
|
require_relative "instrumentation/hutch"
|
11
12
|
require_relative "instrumentation/unicorn"
|
12
13
|
require_relative "instrumentation/active_record"
|
13
14
|
require_relative "instrumentation/shoryuken"
|
15
|
+
require_relative "instrumentation/resque"
|
@@ -5,6 +5,7 @@ module PrometheusExporter::Metric
|
|
5
5
|
|
6
6
|
@default_prefix = nil if !defined?(@default_prefix)
|
7
7
|
@default_labels = nil if !defined?(@default_labels)
|
8
|
+
@default_aggregation = nil if !defined?(@default_aggregation)
|
8
9
|
|
9
10
|
# prefix applied to all metrics
|
10
11
|
def self.default_prefix=(name)
|
@@ -23,6 +24,14 @@ module PrometheusExporter::Metric
|
|
23
24
|
@default_labels || {}
|
24
25
|
end
|
25
26
|
|
27
|
+
def self.default_aggregation=(aggregation)
|
28
|
+
@default_aggregation = aggregation
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.default_aggregation
|
32
|
+
@default_aggregation ||= Summary
|
33
|
+
end
|
34
|
+
|
26
35
|
attr_accessor :help, :name, :data
|
27
36
|
|
28
37
|
def initialize(name, help)
|
@@ -36,21 +36,12 @@ class PrometheusExporter::Middleware
|
|
36
36
|
|
37
37
|
result
|
38
38
|
ensure
|
39
|
-
status = (result && result[0]) || -1
|
40
|
-
params = env["action_dispatch.request.parameters"]
|
41
|
-
action, controller = nil
|
42
|
-
if params
|
43
|
-
action = params["action"]
|
44
|
-
controller = params["controller"]
|
45
|
-
end
|
46
39
|
|
47
40
|
obj = {
|
48
41
|
type: "web",
|
49
42
|
timings: info,
|
50
43
|
queue_time: queue_time,
|
51
|
-
|
52
|
-
controller: controller,
|
53
|
-
status: status
|
44
|
+
default_labels: default_labels(env, result)
|
54
45
|
}
|
55
46
|
labels = custom_labels(env)
|
56
47
|
if labels
|
@@ -60,6 +51,22 @@ class PrometheusExporter::Middleware
|
|
60
51
|
@client.send_json(obj)
|
61
52
|
end
|
62
53
|
|
54
|
+
def default_labels(env, result)
|
55
|
+
status = (result && result[0]) || -1
|
56
|
+
params = env["action_dispatch.request.parameters"]
|
57
|
+
action = controller = nil
|
58
|
+
if params
|
59
|
+
action = params["action"]
|
60
|
+
controller = params["controller"]
|
61
|
+
end
|
62
|
+
|
63
|
+
{
|
64
|
+
action: action || "other",
|
65
|
+
controller: controller || "other",
|
66
|
+
status: status
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
63
70
|
# allows subclasses to add custom labels based on env
|
64
71
|
def custom_labels(env)
|
65
72
|
nil
|
@@ -83,19 +90,24 @@ class PrometheusExporter::Middleware
|
|
83
90
|
Process.clock_gettime(Process::CLOCK_REALTIME)
|
84
91
|
end
|
85
92
|
|
86
|
-
#
|
93
|
+
# determine queue start from well-known trace headers
|
87
94
|
def queue_start(env)
|
95
|
+
|
96
|
+
# get the content of the x-queue-start or x-request-start header
|
88
97
|
value = env['HTTP_X_REQUEST_START'] || env['HTTP_X_QUEUE_START']
|
89
98
|
unless value.nil? || value == ''
|
90
|
-
|
99
|
+
# nginx returns time as milliseconds with 3 decimal places
|
100
|
+
# apache returns time as microseconds without decimal places
|
101
|
+
# this method takes care to convert both into a proper second + fractions timestamp
|
102
|
+
value = value.to_s.gsub(/t=|\./, '')
|
103
|
+
return "#{value[0, 10]}.#{value[10, 13]}".to_f
|
91
104
|
end
|
92
|
-
end
|
93
105
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
"#{str[0, 10]}.#{str[10, 13]}".to_f
|
106
|
+
# get the content of the x-amzn-trace-id header
|
107
|
+
# see also: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-request-tracing.html
|
108
|
+
value = env['HTTP_X_AMZN_TRACE_ID']
|
109
|
+
value&.split('Root=')&.last&.split('-')&.fetch(1)&.to_i(16)
|
110
|
+
|
100
111
|
end
|
112
|
+
|
101
113
|
end
|
@@ -47,7 +47,8 @@ module PrometheusExporter::Server
|
|
47
47
|
obj["created_at"] = now
|
48
48
|
|
49
49
|
@active_record_metrics.delete_if do |current|
|
50
|
-
(obj["pid"] == current["pid"] && obj["hostname"] == current["hostname"]
|
50
|
+
(obj["pid"] == current["pid"] && obj["hostname"] == current["hostname"] &&
|
51
|
+
obj["metric_labels"]["pool_name"] == current["metric_labels"]["pool_name"]) ||
|
51
52
|
(current["created_at"] + MAX_ACTIVERECORD_METRIC_AGE < now)
|
52
53
|
end
|
53
54
|
|
@@ -14,12 +14,14 @@ module PrometheusExporter::Server
|
|
14
14
|
register_collector(ProcessCollector.new)
|
15
15
|
register_collector(SidekiqCollector.new)
|
16
16
|
register_collector(SidekiqQueueCollector.new)
|
17
|
+
register_collector(SidekiqProcessCollector.new)
|
17
18
|
register_collector(DelayedJobCollector.new)
|
18
19
|
register_collector(PumaCollector.new)
|
19
20
|
register_collector(HutchCollector.new)
|
20
21
|
register_collector(UnicornCollector.new)
|
21
22
|
register_collector(ActiveRecordCollector.new)
|
22
23
|
register_collector(ShoryukenCollector.new)
|
24
|
+
register_collector(ResqueCollector.new)
|
23
25
|
end
|
24
26
|
|
25
27
|
def register_collector(collector)
|
@@ -19,21 +19,21 @@ module PrometheusExporter::Server
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def collect(obj)
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
custom_labels = obj['custom_labels'] || {}
|
23
|
+
gauge_labels = { queue_name: obj['queue_name'] }.merge(custom_labels)
|
24
|
+
counter_labels = gauge_labels.merge(job_name: obj['name'])
|
25
25
|
|
26
26
|
ensure_delayed_job_metrics
|
27
|
-
@delayed_job_duration_seconds.observe(obj["duration"],
|
28
|
-
@delayed_jobs_total.observe(1,
|
29
|
-
@delayed_failed_jobs_total.observe(1,
|
30
|
-
@delayed_jobs_max_attempts_reached_total.observe(1) if obj["attempts"] >= obj["max_attempts"]
|
31
|
-
@delayed_job_duration_seconds_summary.observe(obj["duration"])
|
32
|
-
@delayed_job_duration_seconds_summary.observe(obj["duration"], status: "success") if obj["success"]
|
33
|
-
@delayed_job_duration_seconds_summary.observe(obj["duration"], status: "failed") if !obj["success"]
|
34
|
-
@delayed_job_attempts_summary.observe(obj["attempts"]) if obj["success"]
|
35
|
-
@delayed_jobs_enqueued.observe(obj["enqueued"])
|
36
|
-
@delayed_jobs_pending.observe(obj["pending"])
|
27
|
+
@delayed_job_duration_seconds.observe(obj["duration"], counter_labels)
|
28
|
+
@delayed_jobs_total.observe(1, counter_labels)
|
29
|
+
@delayed_failed_jobs_total.observe(1, counter_labels) if !obj["success"]
|
30
|
+
@delayed_jobs_max_attempts_reached_total.observe(1, counter_labels) if obj["attempts"] >= obj["max_attempts"]
|
31
|
+
@delayed_job_duration_seconds_summary.observe(obj["duration"], counter_labels)
|
32
|
+
@delayed_job_duration_seconds_summary.observe(obj["duration"], counter_labels.merge(status: "success")) if obj["success"]
|
33
|
+
@delayed_job_duration_seconds_summary.observe(obj["duration"], counter_labels.merge(status: "failed")) if !obj["success"]
|
34
|
+
@delayed_job_attempts_summary.observe(obj["attempts"], counter_labels) if obj["success"]
|
35
|
+
@delayed_jobs_enqueued.observe(obj["enqueued"], gauge_labels)
|
36
|
+
@delayed_jobs_pending.observe(obj["pending"], gauge_labels)
|
37
37
|
end
|
38
38
|
|
39
39
|
def metrics
|
@@ -76,12 +76,12 @@ module PrometheusExporter::Server
|
|
76
76
|
"delayed_jobs_max_attempts_reached_total", "Total number of delayed jobs that reached max attempts.")
|
77
77
|
|
78
78
|
@delayed_job_duration_seconds_summary =
|
79
|
-
PrometheusExporter::Metric::
|
80
|
-
|
79
|
+
PrometheusExporter::Metric::Base.default_aggregation.new("delayed_job_duration_seconds_summary",
|
80
|
+
"Summary of the time it takes jobs to execute.")
|
81
81
|
|
82
82
|
@delayed_job_attempts_summary =
|
83
|
-
PrometheusExporter::Metric::
|
84
|
-
|
83
|
+
PrometheusExporter::Metric::Base.default_aggregation.new("delayed_job_attempts_summary",
|
84
|
+
"Summary of the amount of attempts it takes delayed jobs to succeed.")
|
85
85
|
end
|
86
86
|
end
|
87
87
|
end
|
@@ -4,13 +4,13 @@ module PrometheusExporter::Server
|
|
4
4
|
class PumaCollector < TypeCollector
|
5
5
|
MAX_PUMA_METRIC_AGE = 30
|
6
6
|
PUMA_GAUGES = {
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
workers: "Number of puma workers.",
|
8
|
+
booted_workers: "Number of puma workers booted.",
|
9
|
+
old_workers: "Number of old puma workers.",
|
10
|
+
running_threads: "Number of puma threads currently running.",
|
11
|
+
request_backlog: "Number of requests waiting to be processed by a puma thread.",
|
12
|
+
thread_pool_capacity: "Number of puma threads available at current scale.",
|
13
|
+
max_threads: "Number of puma threads at available at max scale.",
|
14
14
|
}
|
15
15
|
|
16
16
|
def initialize
|
@@ -34,6 +34,9 @@ module PrometheusExporter::Server
|
|
34
34
|
if m["custom_labels"]
|
35
35
|
labels.merge!(m["custom_labels"])
|
36
36
|
end
|
37
|
+
if m["metric_labels"]
|
38
|
+
labels.merge!(m["metric_labels"])
|
39
|
+
end
|
37
40
|
|
38
41
|
PUMA_GAUGES.map do |k, help|
|
39
42
|
k = k.to_s
|
@@ -51,7 +54,12 @@ module PrometheusExporter::Server
|
|
51
54
|
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
52
55
|
|
53
56
|
obj["created_at"] = now
|
54
|
-
|
57
|
+
|
58
|
+
@puma_metrics.delete_if do |current|
|
59
|
+
(obj["pid"] == current["pid"] && obj["hostname"] == current["hostname"]) ||
|
60
|
+
(current["created_at"] + MAX_PUMA_METRIC_AGE < now)
|
61
|
+
end
|
62
|
+
|
55
63
|
@puma_metrics << obj
|
56
64
|
end
|
57
65
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrometheusExporter::Server
|
4
|
+
class ResqueCollector < TypeCollector
|
5
|
+
MAX_RESQUE_METRIC_AGE = 30
|
6
|
+
RESQUE_GAUGES = {
|
7
|
+
processed_jobs: "Total number of processed Resque jobs.",
|
8
|
+
failed_jobs: "Total number of failed Resque jobs.",
|
9
|
+
pending_jobs: "Total number of pending Resque jobs.",
|
10
|
+
queues: "Total number of Resque queues.",
|
11
|
+
workers: "Total number of Resque workers running.",
|
12
|
+
working: "Total number of Resque workers working."
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@resque_metrics = []
|
17
|
+
@gauges = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def type
|
21
|
+
"resque"
|
22
|
+
end
|
23
|
+
|
24
|
+
def metrics
|
25
|
+
return [] if resque_metrics.length == 0
|
26
|
+
|
27
|
+
resque_metrics.map do |metric|
|
28
|
+
labels = metric.fetch("custom_labels", {})
|
29
|
+
|
30
|
+
RESQUE_GAUGES.map do |name, help|
|
31
|
+
name = name.to_s
|
32
|
+
if value = metric[name]
|
33
|
+
gauge = gauges[name] ||= PrometheusExporter::Metric::Gauge.new("resque_#{name}", help)
|
34
|
+
gauge.observe(value, labels)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
gauges.values
|
40
|
+
end
|
41
|
+
|
42
|
+
def collect(object)
|
43
|
+
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
44
|
+
|
45
|
+
object["created_at"] = now
|
46
|
+
resque_metrics.delete_if { |metric| metric["created_at"] + MAX_RESQUE_METRIC_AGE < now }
|
47
|
+
resque_metrics << object
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_reader :resque_metrics, :gauges
|
53
|
+
end
|
54
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative '../client'
|
4
4
|
require_relative '../instrumentation/unicorn'
|
5
5
|
|
6
6
|
module PrometheusExporter::Server
|
@@ -17,6 +17,7 @@ module PrometheusExporter::Server
|
|
17
17
|
@prefix = nil
|
18
18
|
@auth = nil
|
19
19
|
@realm = nil
|
20
|
+
@histogram = nil
|
20
21
|
|
21
22
|
options.each do |k, v|
|
22
23
|
send("#{k}=", v) if self.class.method_defined?("#{k}=")
|
@@ -27,6 +28,10 @@ module PrometheusExporter::Server
|
|
27
28
|
PrometheusExporter::Metric::Base.default_prefix = prefix
|
28
29
|
PrometheusExporter::Metric::Base.default_labels = label
|
29
30
|
|
31
|
+
if histogram
|
32
|
+
PrometheusExporter::Metric::Base.default_aggregation = PrometheusExporter::Metric::Histogram
|
33
|
+
end
|
34
|
+
|
30
35
|
register_type_collectors
|
31
36
|
|
32
37
|
unless collector.is_a?(PrometheusExporter::Server::CollectorBase)
|
@@ -47,7 +52,7 @@ module PrometheusExporter::Server
|
|
47
52
|
end
|
48
53
|
|
49
54
|
attr_accessor :unicorn_listen_address, :unicorn_pid_file
|
50
|
-
attr_writer :prefix, :port, :bind, :collector_class, :type_collectors, :timeout, :verbose, :server_class, :label, :auth, :realm
|
55
|
+
attr_writer :prefix, :port, :bind, :collector_class, :type_collectors, :timeout, :verbose, :server_class, :label, :auth, :realm, :histogram
|
51
56
|
|
52
57
|
def auth
|
53
58
|
@auth || nil
|
@@ -98,6 +103,10 @@ module PrometheusExporter::Server
|
|
98
103
|
@label ||= PrometheusExporter::DEFAULT_LABEL
|
99
104
|
end
|
100
105
|
|
106
|
+
def histogram
|
107
|
+
@histogram || false
|
108
|
+
end
|
109
|
+
|
101
110
|
private
|
102
111
|
|
103
112
|
def register_type_collectors
|
@@ -52,7 +52,7 @@ module PrometheusExporter::Server
|
|
52
52
|
if !@sidekiq_jobs_total
|
53
53
|
|
54
54
|
@sidekiq_job_duration_seconds =
|
55
|
-
PrometheusExporter::Metric::
|
55
|
+
PrometheusExporter::Metric::Base.default_aggregation.new(
|
56
56
|
"sidekiq_job_duration_seconds", "Total time spent in sidekiq jobs.")
|
57
57
|
|
58
58
|
@sidekiq_jobs_total =
|