prometheus_exporter 0.4.17 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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