lib/honeybadger/plugins/sidekiq.rb in honeybadger-5.8.0 vs lib/honeybadger/plugins/sidekiq.rb in honeybadger-5.11.0

- old
+ new

@@ -1,5 +1,6 @@ +require 'honeybadger/instrumentation_helper' require 'honeybadger/plugin' require 'honeybadger/ruby' module Honeybadger module Plugins @@ -9,11 +10,62 @@ Honeybadger.clear! yield end end - Plugin.register do + class ServerMiddlewareInstrumentation + include Honeybadger::InstrumentationHelper + + def call(worker, msg, queue, &block) + if msg["wrapped"] + context = { + jid: msg["jid"], + worker: msg["wrapped"], + queue: queue + } + else + context = { + jid: msg["jid"], + worker: msg["class"], + queue: queue + } + end + + begin + duration = Honeybadger.instrumentation.monotonic_timer { block.call }[0] + status = 'success' + rescue Exception => e + status = 'failure' + raise + ensure + context.merge!(duration: duration, status: status) + Honeybadger.event('perform.sidekiq', context) + + metric_source 'sidekiq' + histogram 'perform', { bins: [30, 60, 120, 300, 1800, 3600, 21_600] }.merge(context.slice(:worker, :queue, :duration)) + end + end + end + + class ClientMiddlewareInstrumentation + include Honeybadger::InstrumentationHelper + + def call(worker, msg, queue, _redis) + context = { + worker: msg["wrapped"] || msg["class"], + queue: queue + } + + Honeybadger.event('enqueue.sidekiq', context) + + yield + end + end + + Plugin.register :sidekiq do + leader_checker = nil + requirement { defined?(::Sidekiq) } execution do ::Sidekiq.configure_server do |sidekiq| sidekiq.server_middleware do |chain| @@ -67,9 +119,84 @@ opts[:action] = 'perform' if opts[:component] end Honeybadger.notify(ex, opts) } + end + end + + if config.load_plugin_insights?(:sidekiq) + require "sidekiq" + require "sidekiq/api" + require "sidekiq/component" + + class SidekiqClusterCollectionChecker + include ::Sidekiq::Component + def initialize(config) + @config = config + end + + def collect? + return true unless defined?(::Sidekiq::Enterprise) + leader? + end + end + + ::Sidekiq.configure_server do |config| + config.server_middleware { |chain| chain.add(ServerMiddlewareInstrumentation) } + config.client_middleware { |chain| chain.add(ClientMiddlewareInstrumentation) } + config.on(:startup) do + leader_checker = SidekiqClusterCollectionChecker.new(config) + end + end + + ::Sidekiq.configure_client do |config| + config.client_middleware { |chain| chain.add(ClientMiddlewareInstrumentation) } + end + end + end + + collect do + if config.cluster_collection?(:sidekiq) && (leader_checker.nil? || leader_checker.collect?) + metric_source 'sidekiq' + + stats = ::Sidekiq::Stats.new + + gauge 'active_workers', ->{ stats.workers_size } + gauge 'active_processes', ->{ stats.processes_size } + gauge 'jobs_processed', ->{ stats.processed } + gauge 'jobs_failed', ->{ stats.failed } + gauge 'jobs_scheduled', ->{ stats.scheduled_size } + gauge 'jobs_enqueued', ->{ stats.enqueued } + gauge 'jobs_dead', ->{ stats.dead_size } + gauge 'jobs_retry', ->{ stats.retry_size } + + ::Sidekiq::Queue.all.each do |queue| + gauge 'queue_latency', { queue: queue.name }, ->{ (queue.latency * 1000).ceil } + gauge 'queue_depth', { queue: queue.name }, ->{ queue.size } + end + + Hash.new(0).tap do |busy_counts| + ::Sidekiq::Workers.new.each do |_pid, _tid, work| + payload = work.respond_to?(:payload) ? work.payload : work["payload"] + payload = JSON.parse(payload) if payload.is_a?(String) + busy_counts[payload["queue"]] += 1 + end + end.each do |queue_name, busy_count| + gauge 'queue_busy', { queue: queue_name }, ->{ busy_count } + end + + processes = ::Sidekiq::ProcessSet.new.to_enum(:each).to_a + gauge 'capacity', ->{ processes.map { |process| process["concurrency"] }.sum } + + process_utilizations = processes.map do |process| + next unless process["concurrency"].to_f > 0 + process["busy"] / process["concurrency"].to_f + end.compact + + if process_utilizations.any? + utilization = process_utilizations.sum / process_utilizations.length.to_f + gauge 'utilization', ->{ utilization } end end end end end