prometheus_exporter 0.5.1 → 0.8.0
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 +4 -4
- data/.github/workflows/ci.yml +42 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +7 -1
- data/Appraisals +10 -0
- data/CHANGELOG +36 -3
- data/README.md +278 -5
- data/bin/prometheus_exporter +21 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/ar_60.gemfile +5 -0
- data/gemfiles/ar_61.gemfile +7 -0
- data/lib/prometheus_exporter.rb +2 -0
- data/lib/prometheus_exporter/client.rb +27 -3
- data/lib/prometheus_exporter/instrumentation.rb +2 -0
- data/lib/prometheus_exporter/instrumentation/active_record.rb +14 -7
- 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 +2 -0
- data/lib/prometheus_exporter/instrumentation/puma.rb +16 -4
- data/lib/prometheus_exporter/instrumentation/resque.rb +40 -0
- data/lib/prometheus_exporter/instrumentation/sidekiq.rb +44 -3
- data/lib/prometheus_exporter/instrumentation/sidekiq_queue.rb +50 -0
- data/lib/prometheus_exporter/metric/base.rb +4 -0
- data/lib/prometheus_exporter/metric/counter.rb +4 -0
- data/lib/prometheus_exporter/metric/gauge.rb +4 -0
- data/lib/prometheus_exporter/metric/histogram.rb +6 -0
- data/lib/prometheus_exporter/metric/summary.rb +7 -0
- data/lib/prometheus_exporter/middleware.rb +40 -17
- data/lib/prometheus_exporter/server.rb +2 -0
- data/lib/prometheus_exporter/server/active_record_collector.rb +3 -1
- data/lib/prometheus_exporter/server/collector.rb +2 -0
- data/lib/prometheus_exporter/server/delayed_job_collector.rb +20 -8
- data/lib/prometheus_exporter/server/hutch_collector.rb +6 -0
- data/lib/prometheus_exporter/server/puma_collector.rb +9 -1
- data/lib/prometheus_exporter/server/resque_collector.rb +54 -0
- data/lib/prometheus_exporter/server/runner.rb +24 -2
- data/lib/prometheus_exporter/server/shoryuken_collector.rb +8 -0
- data/lib/prometheus_exporter/server/sidekiq_collector.rb +11 -2
- data/lib/prometheus_exporter/server/sidekiq_queue_collector.rb +46 -0
- data/lib/prometheus_exporter/server/web_collector.rb +7 -5
- data/lib/prometheus_exporter/server/web_server.rb +29 -17
- data/lib/prometheus_exporter/version.rb +1 -1
- data/prometheus_exporter.gemspec +9 -5
- metadata +67 -17
- data/.travis.yml +0 -12
@@ -36,22 +36,40 @@ class PrometheusExporter::Middleware
|
|
36
36
|
|
37
37
|
result
|
38
38
|
ensure
|
39
|
+
|
40
|
+
obj = {
|
41
|
+
type: "web",
|
42
|
+
timings: info,
|
43
|
+
queue_time: queue_time,
|
44
|
+
default_labels: default_labels(env, result)
|
45
|
+
}
|
46
|
+
labels = custom_labels(env)
|
47
|
+
if labels
|
48
|
+
obj = obj.merge(custom_labels: labels)
|
49
|
+
end
|
50
|
+
|
51
|
+
@client.send_json(obj)
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_labels(env, result)
|
39
55
|
status = (result && result[0]) || -1
|
40
56
|
params = env["action_dispatch.request.parameters"]
|
41
|
-
action
|
57
|
+
action = controller = nil
|
42
58
|
if params
|
43
59
|
action = params["action"]
|
44
60
|
controller = params["controller"]
|
45
61
|
end
|
46
62
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
queue_time: queue_time,
|
51
|
-
action: action,
|
52
|
-
controller: controller,
|
63
|
+
{
|
64
|
+
action: action || "other",
|
65
|
+
controller: controller || "other",
|
53
66
|
status: status
|
54
|
-
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
# allows subclasses to add custom labels based on env
|
71
|
+
def custom_labels(env)
|
72
|
+
nil
|
55
73
|
end
|
56
74
|
|
57
75
|
private
|
@@ -72,19 +90,24 @@ class PrometheusExporter::Middleware
|
|
72
90
|
Process.clock_gettime(Process::CLOCK_REALTIME)
|
73
91
|
end
|
74
92
|
|
75
|
-
#
|
93
|
+
# determine queue start from well-known trace headers
|
76
94
|
def queue_start(env)
|
95
|
+
|
96
|
+
# get the content of the x-queue-start or x-request-start header
|
77
97
|
value = env['HTTP_X_REQUEST_START'] || env['HTTP_X_QUEUE_START']
|
78
98
|
unless value.nil? || value == ''
|
79
|
-
|
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
|
80
104
|
end
|
81
|
-
end
|
82
105
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
"#{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
|
+
|
89
111
|
end
|
112
|
+
|
90
113
|
end
|
@@ -5,6 +5,7 @@ require_relative "server/type_collector"
|
|
5
5
|
require_relative "server/web_collector"
|
6
6
|
require_relative "server/process_collector"
|
7
7
|
require_relative "server/sidekiq_collector"
|
8
|
+
require_relative "server/sidekiq_queue_collector"
|
8
9
|
require_relative "server/delayed_job_collector"
|
9
10
|
require_relative "server/collector_base"
|
10
11
|
require_relative "server/collector"
|
@@ -15,3 +16,4 @@ require_relative "server/hutch_collector"
|
|
15
16
|
require_relative "server/unicorn_collector"
|
16
17
|
require_relative "server/active_record_collector"
|
17
18
|
require_relative "server/shoryuken_collector"
|
19
|
+
require_relative "server/resque_collector"
|
@@ -27,6 +27,7 @@ module PrometheusExporter::Server
|
|
27
27
|
|
28
28
|
@active_record_metrics.map do |m|
|
29
29
|
metric_key = (m["metric_labels"] || {}).merge("pid" => m["pid"])
|
30
|
+
metric_key.merge!(m["custom_labels"]) if m["custom_labels"]
|
30
31
|
|
31
32
|
ACTIVE_RECORD_GAUGES.map do |k, help|
|
32
33
|
k = k.to_s
|
@@ -46,7 +47,8 @@ module PrometheusExporter::Server
|
|
46
47
|
obj["created_at"] = now
|
47
48
|
|
48
49
|
@active_record_metrics.delete_if do |current|
|
49
|
-
(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"]) ||
|
50
52
|
(current["created_at"] + MAX_ACTIVERECORD_METRIC_AGE < now)
|
51
53
|
end
|
52
54
|
|
@@ -13,12 +13,14 @@ module PrometheusExporter::Server
|
|
13
13
|
register_collector(WebCollector.new)
|
14
14
|
register_collector(ProcessCollector.new)
|
15
15
|
register_collector(SidekiqCollector.new)
|
16
|
+
register_collector(SidekiqQueueCollector.new)
|
16
17
|
register_collector(DelayedJobCollector.new)
|
17
18
|
register_collector(PumaCollector.new)
|
18
19
|
register_collector(HutchCollector.new)
|
19
20
|
register_collector(UnicornCollector.new)
|
20
21
|
register_collector(ActiveRecordCollector.new)
|
21
22
|
register_collector(ShoryukenCollector.new)
|
23
|
+
register_collector(ResqueCollector.new)
|
22
24
|
end
|
23
25
|
|
24
26
|
def register_collector(collector)
|
@@ -2,27 +2,39 @@
|
|
2
2
|
|
3
3
|
module PrometheusExporter::Server
|
4
4
|
class DelayedJobCollector < TypeCollector
|
5
|
+
def initialize
|
6
|
+
@delayed_jobs_total = nil
|
7
|
+
@delayed_job_duration_seconds = nil
|
8
|
+
@delayed_jobs_total = nil
|
9
|
+
@delayed_failed_jobs_total = nil
|
10
|
+
@delayed_jobs_max_attempts_reached_total = nil
|
11
|
+
@delayed_job_duration_seconds_summary = nil
|
12
|
+
@delayed_job_attempts_summary = nil
|
13
|
+
@delayed_jobs_enqueued = nil
|
14
|
+
@delayed_jobs_pending = nil
|
15
|
+
end
|
5
16
|
|
6
17
|
def type
|
7
18
|
"delayed_job"
|
8
19
|
end
|
9
20
|
|
10
21
|
def collect(obj)
|
11
|
-
default_labels = { job_name: obj['name'] }
|
22
|
+
default_labels = { job_name: obj['name'], queue_name: obj['queue_name'] }
|
12
23
|
custom_labels = obj['custom_labels']
|
24
|
+
|
13
25
|
labels = custom_labels.nil? ? default_labels : default_labels.merge(custom_labels)
|
14
26
|
|
15
27
|
ensure_delayed_job_metrics
|
16
28
|
@delayed_job_duration_seconds.observe(obj["duration"], labels)
|
17
29
|
@delayed_jobs_total.observe(1, labels)
|
18
30
|
@delayed_failed_jobs_total.observe(1, labels) if !obj["success"]
|
19
|
-
@delayed_jobs_max_attempts_reached_total.observe(1) if obj["attempts"] >= obj["max_attempts"]
|
20
|
-
@delayed_job_duration_seconds_summary.observe(obj["duration"])
|
21
|
-
@delayed_job_duration_seconds_summary.observe(obj["duration"], status: "success") if obj["success"]
|
22
|
-
@delayed_job_duration_seconds_summary.observe(obj["duration"], status: "failed") if !obj["success"]
|
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"])
|
31
|
+
@delayed_jobs_max_attempts_reached_total.observe(1, labels) if obj["attempts"] >= obj["max_attempts"]
|
32
|
+
@delayed_job_duration_seconds_summary.observe(obj["duration"], labels)
|
33
|
+
@delayed_job_duration_seconds_summary.observe(obj["duration"], labels.merge(status: "success")) if obj["success"]
|
34
|
+
@delayed_job_duration_seconds_summary.observe(obj["duration"], labels.merge(status: "failed")) if !obj["success"]
|
35
|
+
@delayed_job_attempts_summary.observe(obj["attempts"], labels) if obj["success"]
|
36
|
+
@delayed_jobs_enqueued.observe(obj["enqueued"], labels)
|
37
|
+
@delayed_jobs_pending.observe(obj["pending"], labels)
|
26
38
|
end
|
27
39
|
|
28
40
|
def metrics
|
@@ -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: "Total number of processed Resque jobs.",
|
8
|
+
failed_jobs_total: "Total number of failed Resque jobs.",
|
9
|
+
pending_jobs_total: "Total number of pending Resque jobs.",
|
10
|
+
queues_total: "Total number of Resque queues.",
|
11
|
+
workers_total: "Total number of Resque workers running.",
|
12
|
+
working_total: "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
|
@@ -9,6 +9,15 @@ module PrometheusExporter::Server
|
|
9
9
|
|
10
10
|
class Runner
|
11
11
|
def initialize(options = {})
|
12
|
+
@timeout = nil
|
13
|
+
@port = nil
|
14
|
+
@bind = nil
|
15
|
+
@collector_class = nil
|
16
|
+
@type_collectors = nil
|
17
|
+
@prefix = nil
|
18
|
+
@auth = nil
|
19
|
+
@realm = nil
|
20
|
+
|
12
21
|
options.each do |k, v|
|
13
22
|
send("#{k}=", v) if self.class.method_defined?("#{k}=")
|
14
23
|
end
|
@@ -16,6 +25,7 @@ module PrometheusExporter::Server
|
|
16
25
|
|
17
26
|
def start
|
18
27
|
PrometheusExporter::Metric::Base.default_prefix = prefix
|
28
|
+
PrometheusExporter::Metric::Base.default_labels = label
|
19
29
|
|
20
30
|
register_type_collectors
|
21
31
|
|
@@ -32,12 +42,20 @@ module PrometheusExporter::Server
|
|
32
42
|
)
|
33
43
|
end
|
34
44
|
|
35
|
-
server = server_class.new
|
45
|
+
server = server_class.new(port: port, bind: bind, collector: collector, timeout: timeout, verbose: verbose, auth: auth, realm: realm)
|
36
46
|
server.start
|
37
47
|
end
|
38
48
|
|
39
49
|
attr_accessor :unicorn_listen_address, :unicorn_pid_file
|
40
|
-
attr_writer :prefix, :port, :bind, :collector_class, :type_collectors, :timeout, :verbose, :server_class
|
50
|
+
attr_writer :prefix, :port, :bind, :collector_class, :type_collectors, :timeout, :verbose, :server_class, :label, :auth, :realm
|
51
|
+
|
52
|
+
def auth
|
53
|
+
@auth || nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def realm
|
57
|
+
@realm || PrometheusExporter::DEFAULT_REALM
|
58
|
+
end
|
41
59
|
|
42
60
|
def prefix
|
43
61
|
@prefix || PrometheusExporter::DEFAULT_PREFIX
|
@@ -76,6 +94,10 @@ module PrometheusExporter::Server
|
|
76
94
|
@_collector ||= collector_class.new
|
77
95
|
end
|
78
96
|
|
97
|
+
def label
|
98
|
+
@label ||= PrometheusExporter::DEFAULT_LABEL
|
99
|
+
end
|
100
|
+
|
79
101
|
private
|
80
102
|
|
81
103
|
def register_type_collectors
|
@@ -3,6 +3,14 @@
|
|
3
3
|
module PrometheusExporter::Server
|
4
4
|
class ShoryukenCollector < TypeCollector
|
5
5
|
|
6
|
+
def initialize
|
7
|
+
@shoryuken_jobs_total = nil
|
8
|
+
@shoryuken_job_duration_seconds = nil
|
9
|
+
@shoryuken_jobs_total = nil
|
10
|
+
@shoryuken_restarted_jobs_total = nil
|
11
|
+
@shoryuken_failed_jobs_total = nil
|
12
|
+
end
|
13
|
+
|
6
14
|
def type
|
7
15
|
"shoryuken"
|
8
16
|
end
|
@@ -3,12 +3,21 @@
|
|
3
3
|
module PrometheusExporter::Server
|
4
4
|
class SidekiqCollector < TypeCollector
|
5
5
|
|
6
|
+
def initialize
|
7
|
+
@sidekiq_jobs_total = nil
|
8
|
+
@sidekiq_job_duration_seconds = nil
|
9
|
+
@sidekiq_jobs_total = nil
|
10
|
+
@sidekiq_restarted_jobs_total = nil
|
11
|
+
@sidekiq_failed_jobs_total = nil
|
12
|
+
@sidekiq_dead_jobs_total = nil
|
13
|
+
end
|
14
|
+
|
6
15
|
def type
|
7
16
|
"sidekiq"
|
8
17
|
end
|
9
18
|
|
10
19
|
def collect(obj)
|
11
|
-
default_labels = { job_name: obj['name'] }
|
20
|
+
default_labels = { job_name: obj['name'], queue: obj['queue'] }
|
12
21
|
custom_labels = obj['custom_labels']
|
13
22
|
labels = custom_labels.nil? ? default_labels : default_labels.merge(custom_labels)
|
14
23
|
|
@@ -43,7 +52,7 @@ module PrometheusExporter::Server
|
|
43
52
|
if !@sidekiq_jobs_total
|
44
53
|
|
45
54
|
@sidekiq_job_duration_seconds =
|
46
|
-
PrometheusExporter::Metric::
|
55
|
+
PrometheusExporter::Metric::Summary.new(
|
47
56
|
"sidekiq_job_duration_seconds", "Total time spent in sidekiq jobs.")
|
48
57
|
|
49
58
|
@sidekiq_jobs_total =
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module PrometheusExporter::Server
|
3
|
+
class SidekiqQueueCollector < TypeCollector
|
4
|
+
MAX_SIDEKIQ_METRIC_AGE = 60
|
5
|
+
|
6
|
+
SIDEKIQ_QUEUE_GAUGES = {
|
7
|
+
'backlog_total' => 'Size of the sidekiq queue.',
|
8
|
+
'latency_seconds' => 'Latency of the sidekiq queue.',
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
attr_reader :sidekiq_metrics, :gauges
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@sidekiq_metrics = []
|
15
|
+
@gauges = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def type
|
19
|
+
'sidekiq_queue'
|
20
|
+
end
|
21
|
+
|
22
|
+
def metrics
|
23
|
+
sidekiq_metrics.map do |metric|
|
24
|
+
labels = metric.fetch("labels", {})
|
25
|
+
SIDEKIQ_QUEUE_GAUGES.map do |name, help|
|
26
|
+
if (value = metric[name])
|
27
|
+
gauge = gauges[name] ||= PrometheusExporter::Metric::Gauge.new("sidekiq_queue_#{name}", help)
|
28
|
+
gauge.observe(value, labels)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
gauges.values
|
34
|
+
end
|
35
|
+
|
36
|
+
def collect(object)
|
37
|
+
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
38
|
+
object['queues'].each do |queue|
|
39
|
+
queue["created_at"] = now
|
40
|
+
queue["labels"].merge!(object['custom_labels']) if object['custom_labels']
|
41
|
+
sidekiq_metrics.delete_if { |metric| metric['created_at'] + MAX_SIDEKIQ_METRIC_AGE < now }
|
42
|
+
sidekiq_metrics << queue
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|