prometheus_exporter 0.4.17 → 0.6.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 +36 -0
- data/.rubocop.yml +2 -1
- data/CHANGELOG +23 -1
- data/README.md +248 -7
- data/bin/prometheus_exporter +28 -1
- data/lib/prometheus_exporter.rb +14 -0
- data/lib/prometheus_exporter/client.rb +31 -3
- data/lib/prometheus_exporter/instrumentation.rb +2 -0
- data/lib/prometheus_exporter/instrumentation/active_record.rb +2 -13
- data/lib/prometheus_exporter/instrumentation/process.rb +3 -12
- data/lib/prometheus_exporter/instrumentation/shoryuken.rb +31 -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 +13 -2
- data/lib/prometheus_exporter/server.rb +2 -0
- data/lib/prometheus_exporter/server/active_record_collector.rb +1 -0
- data/lib/prometheus_exporter/server/collector.rb +2 -0
- data/lib/prometheus_exporter/server/delayed_job_collector.rb +11 -0
- data/lib/prometheus_exporter/server/hutch_collector.rb +6 -0
- data/lib/prometheus_exporter/server/runner.rb +26 -27
- data/lib/prometheus_exporter/server/shoryuken_collector.rb +67 -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 +5 -0
- data/lib/prometheus_exporter/server/web_server.rb +29 -16
- data/lib/prometheus_exporter/version.rb +1 -1
- data/prometheus_exporter.gemspec +16 -14
- metadata +17 -12
- data/.travis.yml +0 -12
@@ -44,14 +44,25 @@ class PrometheusExporter::Middleware
|
|
44
44
|
controller = params["controller"]
|
45
45
|
end
|
46
46
|
|
47
|
-
|
47
|
+
obj = {
|
48
48
|
type: "web",
|
49
49
|
timings: info,
|
50
50
|
queue_time: queue_time,
|
51
51
|
action: action,
|
52
52
|
controller: controller,
|
53
53
|
status: status
|
54
|
-
|
54
|
+
}
|
55
|
+
labels = custom_labels(env)
|
56
|
+
if labels
|
57
|
+
obj = obj.merge(custom_labels: labels)
|
58
|
+
end
|
59
|
+
|
60
|
+
@client.send_json(obj)
|
61
|
+
end
|
62
|
+
|
63
|
+
# allows subclasses to add custom labels based on env
|
64
|
+
def custom_labels(env)
|
65
|
+
nil
|
55
66
|
end
|
56
67
|
|
57
68
|
private
|
@@ -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"
|
@@ -14,3 +15,4 @@ require_relative "server/puma_collector"
|
|
14
15
|
require_relative "server/hutch_collector"
|
15
16
|
require_relative "server/unicorn_collector"
|
16
17
|
require_relative "server/active_record_collector"
|
18
|
+
require_relative "server/shoryuken_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
|
@@ -13,11 +13,13 @@ 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)
|
22
|
+
register_collector(ShoryukenCollector.new)
|
21
23
|
end
|
22
24
|
|
23
25
|
def register_collector(collector)
|
@@ -2,6 +2,17 @@
|
|
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"
|
@@ -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,73 +42,62 @@ 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
|
-
|
40
|
-
|
49
|
+
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
|
51
|
+
|
52
|
+
def auth
|
53
|
+
@auth || nil
|
41
54
|
end
|
42
55
|
|
43
|
-
def
|
44
|
-
@
|
56
|
+
def realm
|
57
|
+
@realm || PrometheusExporter::DEFAULT_REALM
|
45
58
|
end
|
46
59
|
|
47
|
-
def
|
48
|
-
@
|
60
|
+
def prefix
|
61
|
+
@prefix || PrometheusExporter::DEFAULT_PREFIX
|
49
62
|
end
|
50
63
|
|
51
64
|
def port
|
52
65
|
@port || PrometheusExporter::DEFAULT_PORT
|
53
66
|
end
|
54
67
|
|
55
|
-
def
|
56
|
-
@
|
68
|
+
def bind
|
69
|
+
@bind || PrometheusExporter::DEFAULT_BIND_ADDRESS
|
57
70
|
end
|
58
71
|
|
59
72
|
def collector_class
|
60
73
|
@collector_class || PrometheusExporter::Server::Collector
|
61
74
|
end
|
62
75
|
|
63
|
-
def type_collectors=(type_collectors)
|
64
|
-
@type_collectors = type_collectors
|
65
|
-
end
|
66
|
-
|
67
76
|
def type_collectors
|
68
77
|
@type_collectors || []
|
69
78
|
end
|
70
79
|
|
71
|
-
def timeout=(timeout)
|
72
|
-
@timeout = timeout
|
73
|
-
end
|
74
|
-
|
75
80
|
def timeout
|
76
81
|
@timeout || PrometheusExporter::DEFAULT_TIMEOUT
|
77
82
|
end
|
78
83
|
|
79
|
-
def verbose=(verbose)
|
80
|
-
@verbose = verbose
|
81
|
-
end
|
82
|
-
|
83
84
|
def verbose
|
84
85
|
return @verbose if defined? @verbose
|
85
86
|
false
|
86
87
|
end
|
87
88
|
|
88
|
-
def server_class=(server_class)
|
89
|
-
@server_class = server_class
|
90
|
-
end
|
91
|
-
|
92
89
|
def server_class
|
93
90
|
@server_class || PrometheusExporter::Server::WebServer
|
94
91
|
end
|
95
92
|
|
96
|
-
attr_accessor :unicorn_listen_address, :unicorn_pid_file
|
97
|
-
|
98
93
|
def collector
|
99
94
|
@_collector ||= collector_class.new
|
100
95
|
end
|
101
96
|
|
97
|
+
def label
|
98
|
+
@label ||= PrometheusExporter::DEFAULT_LABEL
|
99
|
+
end
|
100
|
+
|
102
101
|
private
|
103
102
|
|
104
103
|
def register_type_collectors
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrometheusExporter::Server
|
4
|
+
class ShoryukenCollector < TypeCollector
|
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
|
+
|
14
|
+
def type
|
15
|
+
"shoryuken"
|
16
|
+
end
|
17
|
+
|
18
|
+
def collect(obj)
|
19
|
+
default_labels = { job_name: obj['name'] , queue_name: obj['queue'] }
|
20
|
+
custom_labels = obj['custom_labels']
|
21
|
+
labels = custom_labels.nil? ? default_labels : default_labels.merge(custom_labels)
|
22
|
+
|
23
|
+
ensure_shoryuken_metrics
|
24
|
+
@shoryuken_job_duration_seconds.observe(obj["duration"], labels)
|
25
|
+
@shoryuken_jobs_total.observe(1, labels)
|
26
|
+
@shoryuken_restarted_jobs_total.observe(1, labels) if obj["shutdown"]
|
27
|
+
@shoryuken_failed_jobs_total.observe(1, labels) if !obj["success"] && !obj["shutdown"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def metrics
|
31
|
+
if @shoryuken_jobs_total
|
32
|
+
[
|
33
|
+
@shoryuken_job_duration_seconds,
|
34
|
+
@shoryuken_jobs_total,
|
35
|
+
@shoryuken_restarted_jobs_total,
|
36
|
+
@shoryuken_failed_jobs_total,
|
37
|
+
]
|
38
|
+
else
|
39
|
+
[]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def ensure_shoryuken_metrics
|
46
|
+
if !@shoryuken_jobs_total
|
47
|
+
|
48
|
+
@shoryuken_job_duration_seconds =
|
49
|
+
PrometheusExporter::Metric::Counter.new(
|
50
|
+
"shoryuken_job_duration_seconds", "Total time spent in shoryuken jobs.")
|
51
|
+
|
52
|
+
@shoryuken_jobs_total =
|
53
|
+
PrometheusExporter::Metric::Counter.new(
|
54
|
+
"shoryuken_jobs_total", "Total number of shoryuken jobs executed.")
|
55
|
+
|
56
|
+
@shoryuken_restarted_jobs_total =
|
57
|
+
PrometheusExporter::Metric::Counter.new(
|
58
|
+
"shoryuken_restarted_jobs_total", "Total number of shoryuken jobs that we restarted because of a shoryuken shutdown.")
|
59
|
+
|
60
|
+
@shoryuken_failed_jobs_total =
|
61
|
+
PrometheusExporter::Metric::Counter.new(
|
62
|
+
"shoryuken_failed_jobs_total", "Total number of failed shoryuken jobs.")
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
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
|
@@ -4,6 +4,11 @@ module PrometheusExporter::Server
|
|
4
4
|
class WebCollector < TypeCollector
|
5
5
|
def initialize
|
6
6
|
@metrics = {}
|
7
|
+
@http_requests_total = nil
|
8
|
+
@http_duration_seconds = nil
|
9
|
+
@http_redis_duration_seconds = nil
|
10
|
+
@http_sql_duration_seconds = nil
|
11
|
+
@http_queue_duration_seconds = nil
|
7
12
|
end
|
8
13
|
|
9
14
|
def type
|
@@ -9,9 +9,14 @@ module PrometheusExporter::Server
|
|
9
9
|
class WebServer
|
10
10
|
attr_reader :collector
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
|
14
|
-
@
|
12
|
+
def initialize(opts)
|
13
|
+
@port = opts[:port] || PrometheusExporter::DEFAULT_PORT
|
14
|
+
@bind = opts[:bind] || PrometheusExporter::DEFAULT_BIND_ADDRESS
|
15
|
+
@collector = opts[:collector] || Collector.new
|
16
|
+
@timeout = opts[:timeout] || PrometheusExporter::DEFAULT_TIMEOUT
|
17
|
+
@verbose = opts[:verbose] || false
|
18
|
+
@auth = opts[:auth]
|
19
|
+
@realm = opts[:realm] || PrometheusExporter::DEFAULT_REALM
|
15
20
|
|
16
21
|
@metrics_total = PrometheusExporter::Metric::Counter.new("collector_metrics_total", "Total metrics processed by exporter web.")
|
17
22
|
|
@@ -23,32 +28,33 @@ module PrometheusExporter::Server
|
|
23
28
|
@sessions_total.observe(0)
|
24
29
|
@bad_metrics_total.observe(0)
|
25
30
|
|
26
|
-
access_log, logger = nil
|
31
|
+
@access_log, @logger = nil
|
27
32
|
|
28
|
-
if verbose
|
29
|
-
access_log = [
|
33
|
+
if @verbose
|
34
|
+
@access_log = [
|
30
35
|
[$stderr, WEBrick::AccessLog::COMMON_LOG_FORMAT],
|
31
36
|
[$stderr, WEBrick::AccessLog::REFERER_LOG_FORMAT],
|
32
37
|
]
|
33
|
-
logger = WEBrick::Log.new($stderr)
|
38
|
+
@logger = WEBrick::Log.new($stderr)
|
34
39
|
else
|
35
|
-
access_log = []
|
36
|
-
logger = WEBrick::Log.new("/dev/null")
|
40
|
+
@access_log = []
|
41
|
+
@logger = WEBrick::Log.new("/dev/null")
|
37
42
|
end
|
38
43
|
|
44
|
+
@logger.info "Using Basic Authentication via #{@auth}" if @verbose && @auth
|
45
|
+
|
39
46
|
@server = WEBrick::HTTPServer.new(
|
40
|
-
Port: port,
|
41
|
-
|
42
|
-
|
47
|
+
Port: @port,
|
48
|
+
BindAddress: @bind,
|
49
|
+
Logger: @logger,
|
50
|
+
AccessLog: @access_log,
|
43
51
|
)
|
44
52
|
|
45
|
-
@collector = collector || Collector.new
|
46
|
-
@port = port
|
47
|
-
@timeout = timeout
|
48
|
-
|
49
53
|
@server.mount_proc '/' do |req, res|
|
50
54
|
res['Content-Type'] = 'text/plain; charset=utf-8'
|
51
55
|
if req.path == '/metrics'
|
56
|
+
authenticate(req, res) if @auth
|
57
|
+
|
52
58
|
res.status = 200
|
53
59
|
if req.header["accept-encoding"].to_s.include?("gzip")
|
54
60
|
sio = StringIO.new
|
@@ -158,5 +164,12 @@ module PrometheusExporter::Server
|
|
158
164
|
gauge
|
159
165
|
end
|
160
166
|
|
167
|
+
def authenticate(req, res)
|
168
|
+
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(@auth)
|
169
|
+
basic_auth = WEBrick::HTTPAuth::BasicAuth.new({ Realm: @realm, UserDB: htpasswd, Logger: @logger })
|
170
|
+
|
171
|
+
basic_auth.authenticate(req, res)
|
172
|
+
end
|
173
|
+
|
161
174
|
end
|
162
175
|
end
|