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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +36 -0
  3. data/.rubocop.yml +2 -1
  4. data/CHANGELOG +23 -1
  5. data/README.md +248 -7
  6. data/bin/prometheus_exporter +28 -1
  7. data/lib/prometheus_exporter.rb +14 -0
  8. data/lib/prometheus_exporter/client.rb +31 -3
  9. data/lib/prometheus_exporter/instrumentation.rb +2 -0
  10. data/lib/prometheus_exporter/instrumentation/active_record.rb +2 -13
  11. data/lib/prometheus_exporter/instrumentation/process.rb +3 -12
  12. data/lib/prometheus_exporter/instrumentation/shoryuken.rb +31 -0
  13. data/lib/prometheus_exporter/instrumentation/sidekiq.rb +44 -3
  14. data/lib/prometheus_exporter/instrumentation/sidekiq_queue.rb +50 -0
  15. data/lib/prometheus_exporter/metric/base.rb +4 -0
  16. data/lib/prometheus_exporter/metric/counter.rb +4 -0
  17. data/lib/prometheus_exporter/metric/gauge.rb +4 -0
  18. data/lib/prometheus_exporter/metric/histogram.rb +6 -0
  19. data/lib/prometheus_exporter/metric/summary.rb +7 -0
  20. data/lib/prometheus_exporter/middleware.rb +13 -2
  21. data/lib/prometheus_exporter/server.rb +2 -0
  22. data/lib/prometheus_exporter/server/active_record_collector.rb +1 -0
  23. data/lib/prometheus_exporter/server/collector.rb +2 -0
  24. data/lib/prometheus_exporter/server/delayed_job_collector.rb +11 -0
  25. data/lib/prometheus_exporter/server/hutch_collector.rb +6 -0
  26. data/lib/prometheus_exporter/server/runner.rb +26 -27
  27. data/lib/prometheus_exporter/server/shoryuken_collector.rb +67 -0
  28. data/lib/prometheus_exporter/server/sidekiq_collector.rb +11 -2
  29. data/lib/prometheus_exporter/server/sidekiq_queue_collector.rb +46 -0
  30. data/lib/prometheus_exporter/server/web_collector.rb +5 -0
  31. data/lib/prometheus_exporter/server/web_server.rb +29 -16
  32. data/lib/prometheus_exporter/version.rb +1 -1
  33. data/prometheus_exporter.gemspec +16 -14
  34. metadata +17 -12
  35. data/.travis.yml +0 -12
@@ -32,6 +32,13 @@ module PrometheusExporter::Metric
32
32
  data
33
33
  end
34
34
 
35
+ def remove(labels)
36
+ @counts.delete(labels)
37
+ @sums.delete(labels)
38
+ @buffers[0].delete(labels)
39
+ @buffers[1].delete(labels)
40
+ end
41
+
35
42
  def type
36
43
  "summary"
37
44
  end
@@ -44,14 +44,25 @@ class PrometheusExporter::Middleware
44
44
  controller = params["controller"]
45
45
  end
46
46
 
47
- @client.send_json(
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"
@@ -2,6 +2,12 @@
2
2
 
3
3
  module PrometheusExporter::Server
4
4
  class HutchCollector < TypeCollector
5
+ def initialize
6
+ @hutch_jobs_total = nil
7
+ @hutch_job_duration_seconds = nil
8
+ @hutch_jobs_total = nil
9
+ @hutch_failed_jobs_total = nil
10
+ end
5
11
 
6
12
  def type
7
13
  "hutch"
@@ -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 port: port, collector: collector, timeout: timeout, verbose: verbose
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
- def prefix=(prefix)
40
- @prefix = prefix
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 prefix
44
- @prefix || PrometheusExporter::DEFAULT_PREFIX
56
+ def realm
57
+ @realm || PrometheusExporter::DEFAULT_REALM
45
58
  end
46
59
 
47
- def port=(port)
48
- @port = port
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 collector_class=(collector_class)
56
- @collector_class = collector_class
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::Counter.new(
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(port: , collector: nil, timeout: PrometheusExporter::DEFAULT_TIMEOUT, verbose: false)
13
-
14
- @verbose = verbose
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
- Logger: logger,
42
- AccessLog: access_log,
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