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
    
        data/bin/prometheus_exporter
    CHANGED
    
    | @@ -2,6 +2,7 @@ | |
| 2 2 | 
             
            # frozen_string_literal: true
         | 
| 3 3 |  | 
| 4 4 | 
             
            require 'optparse'
         | 
| 5 | 
            +
            require 'json'
         | 
| 5 6 |  | 
| 6 7 | 
             
            require_relative "./../lib/prometheus_exporter"
         | 
| 7 8 | 
             
            require_relative "./../lib/prometheus_exporter/server"
         | 
| @@ -19,6 +20,12 @@ def run | |
| 19 20 | 
             
                       "Port exporter should listen on (default: #{PrometheusExporter::DEFAULT_PORT})") do |o|
         | 
| 20 21 | 
             
                  options[:port] = o.to_i
         | 
| 21 22 | 
             
                end
         | 
| 23 | 
            +
                opt.on('-b',
         | 
| 24 | 
            +
                       '--bind STRING',
         | 
| 25 | 
            +
                       String,
         | 
| 26 | 
            +
                       "IP address exporter should listen on (default: #{PrometheusExporter::DEFAULT_BIND_ADDRESS})") do |o|
         | 
| 27 | 
            +
                  options[:bind] = o.to_s
         | 
| 28 | 
            +
                end
         | 
| 22 29 | 
             
                opt.on('-t',
         | 
| 23 30 | 
             
                       '--timeout INTEGER',
         | 
| 24 31 | 
             
                       Integer,
         | 
| @@ -28,6 +35,9 @@ def run | |
| 28 35 | 
             
                opt.on('--prefix METRIC_PREFIX', "Prefix to apply to all metrics (default: #{PrometheusExporter::DEFAULT_PREFIX})") do |o|
         | 
| 29 36 | 
             
                  options[:prefix] = o.to_s
         | 
| 30 37 | 
             
                end
         | 
| 38 | 
            +
                opt.on('--label METRIC_LABEL', "Label to apply to all metrics (default: #{PrometheusExporter::DEFAULT_LABEL})") do |o|
         | 
| 39 | 
            +
                  options[:label] = JSON.parse(o.to_s)
         | 
| 40 | 
            +
                end
         | 
| 31 41 | 
             
                opt.on('-c', '--collector FILE', String, "(optional) Custom collector to run") do |o|
         | 
| 32 42 | 
             
                  custom_collector_filename = o.to_s
         | 
| 33 43 | 
             
                end
         | 
| @@ -37,6 +47,12 @@ def run | |
| 37 47 | 
             
                opt.on('-v', '--verbose') do |o|
         | 
| 38 48 | 
             
                  options[:verbose] = true
         | 
| 39 49 | 
             
                end
         | 
| 50 | 
            +
                opt.on('--auth FILE', String, "(optional) enable basic authentication using a htpasswd FILE") do |o|
         | 
| 51 | 
            +
                  options[:auth] = o
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                opt.on('--realm REALM', String, "(optional) Use REALM for basic authentication (default: \"#{PrometheusExporter::DEFAULT_REALM}\")") do |o|
         | 
| 54 | 
            +
                  options[:realm] = o
         | 
| 55 | 
            +
                end
         | 
| 40 56 |  | 
| 41 57 | 
             
                opt.on('--unicorn-listen-address ADDRESS', String, '(optional) Address where unicorn listens on (unix or TCP address)') do |o|
         | 
| 42 58 | 
             
                  options[:unicorn_listen_address] = o
         | 
| @@ -47,6 +63,17 @@ def run | |
| 47 63 | 
             
                end
         | 
| 48 64 | 
             
              end.parse!
         | 
| 49 65 |  | 
| 66 | 
            +
              if options.has_key?(:realm) && !options.has_key?(:auth)
         | 
| 67 | 
            +
                STDERR.puts "[Warn] Providing REALM without AUTH has no effect"
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              if options.has_key?(:auth)
         | 
| 71 | 
            +
                unless File.exist?(options[:auth]) && File.readable?(options[:auth])
         | 
| 72 | 
            +
                  STDERR.puts "[Error] The AUTH file either doesn't exist or we don't have access to it"
         | 
| 73 | 
            +
                  exit 1
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 50 77 | 
             
              if custom_collector_filename
         | 
| 51 78 | 
             
                eval File.read(custom_collector_filename), nil, File.expand_path(custom_collector_filename)
         | 
| 52 79 | 
             
                found = false
         | 
| @@ -81,7 +108,7 @@ def run | |
| 81 108 |  | 
| 82 109 | 
             
              runner = PrometheusExporter::Server::Runner.new(options)
         | 
| 83 110 |  | 
| 84 | 
            -
              puts "#{Time.now} Starting prometheus exporter on  | 
| 111 | 
            +
              puts "#{Time.now} Starting prometheus exporter on #{runner.bind}:#{runner.port}"
         | 
| 85 112 | 
             
              runner.start
         | 
| 86 113 | 
             
              sleep
         | 
| 87 114 | 
             
            end
         | 
    
        data/lib/prometheus_exporter.rb
    CHANGED
    
    | @@ -7,8 +7,11 @@ require "thread" | |
| 7 7 | 
             
            module PrometheusExporter
         | 
| 8 8 | 
             
              # per: https://github.com/prometheus/prometheus/wiki/Default-port-allocations
         | 
| 9 9 | 
             
              DEFAULT_PORT = 9394
         | 
| 10 | 
            +
              DEFAULT_BIND_ADDRESS = 'localhost'
         | 
| 10 11 | 
             
              DEFAULT_PREFIX = 'ruby_'
         | 
| 12 | 
            +
              DEFAULT_LABEL = {}
         | 
| 11 13 | 
             
              DEFAULT_TIMEOUT = 2
         | 
| 14 | 
            +
              DEFAULT_REALM = 'Prometheus Exporter'
         | 
| 12 15 |  | 
| 13 16 | 
             
              class OjCompat
         | 
| 14 17 | 
             
                def self.parse(obj)
         | 
| @@ -19,6 +22,17 @@ module PrometheusExporter | |
| 19 22 | 
             
                end
         | 
| 20 23 | 
             
              end
         | 
| 21 24 |  | 
| 25 | 
            +
              def self.hostname
         | 
| 26 | 
            +
                @hostname ||=
         | 
| 27 | 
            +
                  begin
         | 
| 28 | 
            +
                    require 'socket'
         | 
| 29 | 
            +
                    Socket.gethostname
         | 
| 30 | 
            +
                  rescue => e
         | 
| 31 | 
            +
                    STDERR.puts "Unable to lookup hostname #{e}"
         | 
| 32 | 
            +
                    "unknown-host"
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 22 36 | 
             
              def self.detect_json_serializer(preferred)
         | 
| 23 37 | 
             
                if preferred.nil?
         | 
| 24 38 | 
             
                  preferred = :oj if has_oj?
         | 
| @@ -53,12 +53,21 @@ module PrometheusExporter | |
| 53 53 | 
             
                MAX_SOCKET_AGE = 25
         | 
| 54 54 | 
             
                MAX_QUEUE_SIZE = 10_000
         | 
| 55 55 |  | 
| 56 | 
            -
                def initialize( | 
| 56 | 
            +
                def initialize(
         | 
| 57 | 
            +
                  host: ENV.fetch('PROMETHEUS_EXPORTER_HOST', 'localhost'),
         | 
| 58 | 
            +
                  port: ENV.fetch('PROMETHEUS_EXPORTER_PORT', PrometheusExporter::DEFAULT_PORT),
         | 
| 59 | 
            +
                  max_queue_size: nil,
         | 
| 60 | 
            +
                  thread_sleep: 0.5,
         | 
| 61 | 
            +
                  json_serializer: nil,
         | 
| 62 | 
            +
                  custom_labels: nil
         | 
| 63 | 
            +
                )
         | 
| 57 64 | 
             
                  @metrics = []
         | 
| 58 65 |  | 
| 59 66 | 
             
                  @queue = Queue.new
         | 
| 67 | 
            +
             | 
| 60 68 | 
             
                  @socket = nil
         | 
| 61 69 | 
             
                  @socket_started = nil
         | 
| 70 | 
            +
                  @socket_pid = nil
         | 
| 62 71 |  | 
| 63 72 | 
             
                  max_queue_size ||= MAX_QUEUE_SIZE
         | 
| 64 73 | 
             
                  max_queue_size = max_queue_size.to_i
         | 
| @@ -100,7 +109,16 @@ module PrometheusExporter | |
| 100 109 | 
             
                end
         | 
| 101 110 |  | 
| 102 111 | 
             
                def send_json(obj)
         | 
| 103 | 
            -
                  payload = | 
| 112 | 
            +
                  payload =
         | 
| 113 | 
            +
                    if @custom_labels
         | 
| 114 | 
            +
                      if obj[:custom_labels]
         | 
| 115 | 
            +
                        obj.merge(custom_labels: @custom_labels.merge(obj[:custom_labels]))
         | 
| 116 | 
            +
                      else
         | 
| 117 | 
            +
                        obj.merge(custom_labels: @custom_labels)
         | 
| 118 | 
            +
                      end
         | 
| 119 | 
            +
                    else
         | 
| 120 | 
            +
                      obj
         | 
| 121 | 
            +
                    end
         | 
| 104 122 | 
             
                  send(@json_serializer.dump(payload))
         | 
| 105 123 | 
             
                end
         | 
| 106 124 |  | 
| @@ -184,12 +202,20 @@ module PrometheusExporter | |
| 184 202 | 
             
                end
         | 
| 185 203 |  | 
| 186 204 | 
             
                def close_socket_if_old!
         | 
| 187 | 
            -
                  if @socket && ((@socket_started + MAX_SOCKET_AGE) < Time.now.to_f)
         | 
| 205 | 
            +
                  if @socket_pid == Process.pid && @socket && @socket_started && ((@socket_started + MAX_SOCKET_AGE) < Time.now.to_f)
         | 
| 188 206 | 
             
                    close_socket!
         | 
| 189 207 | 
             
                  end
         | 
| 190 208 | 
             
                end
         | 
| 191 209 |  | 
| 192 210 | 
             
                def ensure_socket!
         | 
| 211 | 
            +
                  # if process was forked socket may be owned by parent
         | 
| 212 | 
            +
                  # leave it alone and reset
         | 
| 213 | 
            +
                  if @socket_pid != Process.pid
         | 
| 214 | 
            +
                    @socket = nil
         | 
| 215 | 
            +
                    @socket_started = nil
         | 
| 216 | 
            +
                    @socket_pid = nil
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
             | 
| 193 219 | 
             
                  close_socket_if_old!
         | 
| 194 220 | 
             
                  if !@socket
         | 
| 195 221 | 
             
                    @socket = TCPSocket.new @host, @port
         | 
| @@ -200,12 +226,14 @@ module PrometheusExporter | |
| 200 226 | 
             
                    @socket.write("Content-Type: application/octet-stream\r\n")
         | 
| 201 227 | 
             
                    @socket.write("\r\n")
         | 
| 202 228 | 
             
                    @socket_started = Time.now.to_f
         | 
| 229 | 
            +
                    @socket_pid = Process.pid
         | 
| 203 230 | 
             
                  end
         | 
| 204 231 |  | 
| 205 232 | 
             
                  nil
         | 
| 206 233 | 
             
                rescue
         | 
| 207 234 | 
             
                  @socket = nil
         | 
| 208 235 | 
             
                  @socket_started = nil
         | 
| 236 | 
            +
                  @socket_pid = nil
         | 
| 209 237 | 
             
                  raise
         | 
| 210 238 | 
             
                end
         | 
| 211 239 |  | 
| @@ -4,8 +4,10 @@ require_relative "client" | |
| 4 4 | 
             
            require_relative "instrumentation/process"
         | 
| 5 5 | 
             
            require_relative "instrumentation/method_profiler"
         | 
| 6 6 | 
             
            require_relative "instrumentation/sidekiq"
         | 
| 7 | 
            +
            require_relative "instrumentation/sidekiq_queue"
         | 
| 7 8 | 
             
            require_relative "instrumentation/delayed_job"
         | 
| 8 9 | 
             
            require_relative "instrumentation/puma"
         | 
| 9 10 | 
             
            require_relative "instrumentation/hutch"
         | 
| 10 11 | 
             
            require_relative "instrumentation/unicorn"
         | 
| 11 12 | 
             
            require_relative "instrumentation/active_record"
         | 
| 13 | 
            +
            require_relative "instrumentation/shoryuken"
         | 
| @@ -51,17 +51,6 @@ module PrometheusExporter::Instrumentation | |
| 51 51 | 
             
                def initialize(metric_labels, config_labels)
         | 
| 52 52 | 
             
                  @metric_labels = metric_labels
         | 
| 53 53 | 
             
                  @config_labels = config_labels
         | 
| 54 | 
            -
                  @hostname = nil
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                def hostname
         | 
| 58 | 
            -
                  @hostname ||=
         | 
| 59 | 
            -
                    begin
         | 
| 60 | 
            -
                      `hostname`.strip
         | 
| 61 | 
            -
                    rescue => e
         | 
| 62 | 
            -
                      STDERR.puts "Unable to lookup hostname #{e}"
         | 
| 63 | 
            -
                      "unknown-host"
         | 
| 64 | 
            -
                    end
         | 
| 65 54 | 
             
                end
         | 
| 66 55 |  | 
| 67 56 | 
             
                def collect
         | 
| @@ -80,14 +69,14 @@ module PrometheusExporter::Instrumentation | |
| 80 69 |  | 
| 81 70 | 
             
                    labels_from_config = pool.spec.config
         | 
| 82 71 | 
             
                      .select { |k, v| @config_labels.include? k }
         | 
| 83 | 
            -
                      .map { |k, v| [k.to_s.prepend("dbconfig_"), v] }
         | 
| 72 | 
            +
                      .map { |k, v| [k.to_s.dup.prepend("dbconfig_"), v] }
         | 
| 84 73 |  | 
| 85 74 | 
             
                    labels = @metric_labels.merge(pool_name: pool.spec.name).merge(Hash[labels_from_config])
         | 
| 86 75 |  | 
| 87 76 | 
             
                    metric = {
         | 
| 88 77 | 
             
                      pid: pid,
         | 
| 89 78 | 
             
                      type: "active_record",
         | 
| 90 | 
            -
                      hostname: hostname,
         | 
| 79 | 
            +
                      hostname: ::PrometheusExporter.hostname,
         | 
| 91 80 | 
             
                      metric_labels: labels
         | 
| 92 81 | 
             
                    }
         | 
| 93 82 | 
             
                    metric.merge!(pool.stat)
         | 
| @@ -3,6 +3,8 @@ | |
| 3 3 | 
             
            # collects stats from currently running process
         | 
| 4 4 | 
             
            module PrometheusExporter::Instrumentation
         | 
| 5 5 | 
             
              class Process
         | 
| 6 | 
            +
                @thread = nil if !defined?(@thread)
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
                def self.start(client: nil, type: "ruby", frequency: 30, labels: nil)
         | 
| 7 9 |  | 
| 8 10 | 
             
                  metric_labels =
         | 
| @@ -42,24 +44,13 @@ module PrometheusExporter::Instrumentation | |
| 42 44 |  | 
| 43 45 | 
             
                def initialize(metric_labels)
         | 
| 44 46 | 
             
                  @metric_labels = metric_labels
         | 
| 45 | 
            -
                  @hostname = nil
         | 
| 46 | 
            -
                end
         | 
| 47 | 
            -
             | 
| 48 | 
            -
                def hostname
         | 
| 49 | 
            -
                  @hostname ||=
         | 
| 50 | 
            -
                    begin
         | 
| 51 | 
            -
                      `hostname`.strip
         | 
| 52 | 
            -
                    rescue => e
         | 
| 53 | 
            -
                      STDERR.puts "Unable to lookup hostname #{e}"
         | 
| 54 | 
            -
                      "unknown-host"
         | 
| 55 | 
            -
                    end
         | 
| 56 47 | 
             
                end
         | 
| 57 48 |  | 
| 58 49 | 
             
                def collect
         | 
| 59 50 | 
             
                  metric = {}
         | 
| 60 51 | 
             
                  metric[:type] = "process"
         | 
| 61 52 | 
             
                  metric[:metric_labels] = @metric_labels
         | 
| 62 | 
            -
                  metric[:hostname] = hostname
         | 
| 53 | 
            +
                  metric[:hostname] = ::PrometheusExporter.hostname
         | 
| 63 54 | 
             
                  collect_gc_stats(metric)
         | 
| 64 55 | 
             
                  collect_v8_stats(metric)
         | 
| 65 56 | 
             
                  collect_process_stats(metric)
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module PrometheusExporter::Instrumentation
         | 
| 4 | 
            +
              class Shoryuken
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def initialize(client: nil)
         | 
| 7 | 
            +
                  @client = client || PrometheusExporter::Client.default
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def call(worker, queue, msg, body)
         | 
| 11 | 
            +
                  success = false
         | 
| 12 | 
            +
                  start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
         | 
| 13 | 
            +
                  result = yield
         | 
| 14 | 
            +
                  success = true
         | 
| 15 | 
            +
                  result
         | 
| 16 | 
            +
                rescue ::Shoryuken::Shutdown => e
         | 
| 17 | 
            +
                  shutdown = true
         | 
| 18 | 
            +
                  raise e
         | 
| 19 | 
            +
                ensure
         | 
| 20 | 
            +
                  duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
         | 
| 21 | 
            +
                  @client.send_json(
         | 
| 22 | 
            +
                      type: "shoryuken",
         | 
| 23 | 
            +
                      queue: queue,
         | 
| 24 | 
            +
                      name: worker.class.name,
         | 
| 25 | 
            +
                      success: success,
         | 
| 26 | 
            +
                      shutdown: shutdown,
         | 
| 27 | 
            +
                      duration: duration
         | 
| 28 | 
            +
                  )
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| @@ -1,6 +1,15 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require 'yaml'
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            module PrometheusExporter::Instrumentation
         | 
| 6 | 
            +
              JOB_WRAPPER_CLASS_NAME = 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'
         | 
| 7 | 
            +
              DELAYED_CLASS_NAMES = [
         | 
| 8 | 
            +
                'Sidekiq::Extensions::DelayedClass',
         | 
| 9 | 
            +
                'Sidekiq::Extensions::DelayedModel',
         | 
| 10 | 
            +
                'Sidekiq::Extensions::DelayedMailer',
         | 
| 11 | 
            +
              ]
         | 
| 12 | 
            +
             | 
| 4 13 | 
             
              class Sidekiq
         | 
| 5 14 | 
             
                def self.death_handler
         | 
| 6 15 | 
             
                  -> (job, ex) do
         | 
| @@ -32,15 +41,47 @@ module PrometheusExporter::Instrumentation | |
| 32 41 | 
             
                  raise e
         | 
| 33 42 | 
             
                ensure
         | 
| 34 43 | 
             
                  duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
         | 
| 35 | 
            -
                  class_name = worker.class.to_s == 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper' ?
         | 
| 36 | 
            -
                                 msg['wrapped'] : worker.class.to_s
         | 
| 37 44 | 
             
                  @client.send_json(
         | 
| 38 45 | 
             
                    type: "sidekiq",
         | 
| 39 | 
            -
                    name:  | 
| 46 | 
            +
                    name: get_name(worker, msg),
         | 
| 47 | 
            +
                    queue: queue,
         | 
| 40 48 | 
             
                    success: success,
         | 
| 41 49 | 
             
                    shutdown: shutdown,
         | 
| 42 50 | 
             
                    duration: duration
         | 
| 43 51 | 
             
                  )
         | 
| 44 52 | 
             
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                private
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def get_name(worker, msg)
         | 
| 57 | 
            +
                  class_name = worker.class.to_s
         | 
| 58 | 
            +
                  if class_name == JOB_WRAPPER_CLASS_NAME
         | 
| 59 | 
            +
                    get_job_wrapper_name(msg)
         | 
| 60 | 
            +
                  elsif DELAYED_CLASS_NAMES.include?(class_name)
         | 
| 61 | 
            +
                    get_delayed_name(msg, class_name)
         | 
| 62 | 
            +
                  else
         | 
| 63 | 
            +
                    class_name
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def get_job_wrapper_name(msg)
         | 
| 68 | 
            +
                  msg['wrapped']
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                def get_delayed_name(msg, class_name)
         | 
| 72 | 
            +
                  # fallback to class_name since we're relying on the internal implementation
         | 
| 73 | 
            +
                  # of the delayed extensions
         | 
| 74 | 
            +
                  # https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/extensions/class_methods.rb
         | 
| 75 | 
            +
                  begin
         | 
| 76 | 
            +
                    (target, method_name, _args) = YAML.load(msg['args'].first)
         | 
| 77 | 
            +
                    if target.class == Class
         | 
| 78 | 
            +
                      "#{target.name}##{method_name}"
         | 
| 79 | 
            +
                    else
         | 
| 80 | 
            +
                      "#{target.class.name}##{method_name}"
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                  rescue
         | 
| 83 | 
            +
                    class_name
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                end
         | 
| 45 86 | 
             
              end
         | 
| 46 87 | 
             
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module PrometheusExporter::Instrumentation
         | 
| 4 | 
            +
              class SidekiqQueue
         | 
| 5 | 
            +
                def self.start(client: nil, frequency: 30)
         | 
| 6 | 
            +
                  client ||= PrometheusExporter::Client.default
         | 
| 7 | 
            +
                  sidekiq_queue_collector = new
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  Thread.new do
         | 
| 10 | 
            +
                    loop do
         | 
| 11 | 
            +
                      begin
         | 
| 12 | 
            +
                        client.send_json(sidekiq_queue_collector.collect)
         | 
| 13 | 
            +
                      rescue StandardError => e
         | 
| 14 | 
            +
                        STDERR.puts("Prometheus Exporter Failed To Collect Sidekiq Queue metrics #{e}")
         | 
| 15 | 
            +
                      ensure
         | 
| 16 | 
            +
                        sleep frequency
         | 
| 17 | 
            +
                      end
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def collect
         | 
| 23 | 
            +
                  {
         | 
| 24 | 
            +
                    type: 'sidekiq_queue',
         | 
| 25 | 
            +
                    queues: collect_queue_stats
         | 
| 26 | 
            +
                  }
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def collect_queue_stats
         | 
| 30 | 
            +
                  hostname = Socket.gethostname
         | 
| 31 | 
            +
                  pid = ::Process.pid
         | 
| 32 | 
            +
                  ps = ::Sidekiq::ProcessSet.new
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  process = ps.find do |sp|
         | 
| 35 | 
            +
                    sp['hostname'] == hostname && sp['pid'] == pid
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  queues = process.nil? ? [] : process['queues']
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  ::Sidekiq::Queue.all.map do |queue|
         | 
| 41 | 
            +
                    next unless queues.include? queue.name
         | 
| 42 | 
            +
                    {
         | 
| 43 | 
            +
                      backlog_total: queue.size,
         | 
| 44 | 
            +
                      latency_seconds: queue.latency.to_i,
         | 
| 45 | 
            +
                      labels: { queue: queue.name }
         | 
| 46 | 
            +
                    }
         | 
| 47 | 
            +
                  end.compact
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         |