class ScoutApm::BackgroundJobIntegrations::SidekiqMiddleware

We insert this middleware into the Sidekiq stack, to capture each job, and time them.

Constants

ACTIVE_JOB_KLASS
DELAYED_WRAPPER_KLASS
UNKNOWN_CLASS_PLACEHOLDER

Public Instance Methods

call(_worker, msg, queue) { || ... } click to toggle source
# File lib/scout_apm/background_job_integrations/sidekiq.rb, line 58
def call(_worker, msg, queue)
  req = ScoutApm::RequestManager.lookup
  req.annotate_request(:queue_latency => latency(msg))

  begin
    req.start_layer(ScoutApm::Layer.new('Queue', queue))
    started_queue = true
    req.start_layer(ScoutApm::Layer.new('Job', job_class(msg)))
    started_job = true

    yield
  rescue
    req.error!
    raise
  ensure
    req.stop_layer if started_job
    req.stop_layer if started_queue
  end
end
job_class(msg) click to toggle source

Capturing the class name is a little tricky, since we need to handle several cases:

  1. ActiveJob, with the class in the key 'wrapped'

  2. ActiveJob, but the 'wrapped' key is wrong (due to YAJL serializing weirdly), find it in args.job_class

  3. DelayedJob wrapper, deserializing using YAML into the real object, which can be introspected

  4. No wrapper, just sidekiq's class

# File lib/scout_apm/background_job_integrations/sidekiq.rb, line 88
def job_class(msg)
  job_class = msg.fetch('class', UNKNOWN_CLASS_PLACEHOLDER)

  if job_class == ACTIVE_JOB_KLASS && msg.key?('wrapped') && msg['wrapped'].is_a?(String)
    begin
      job_class = msg['wrapped'].to_s
    rescue
      ACTIVE_JOB_KLASS
    end
  elsif job_class == ACTIVE_JOB_KLASS && msg.try(:[], 'args').try(:[], 'job_class')
    begin
      job_class = msg['args']['job_class'].to_s
    rescue
      ACTIVE_JOB_KLASS
    end
  elsif job_class == DELAYED_WRAPPER_KLASS
    begin
      # Extract the info out of the wrapper
      yml = msg['args'].first
      deserialized_args = YAML.load(yml)
      klass, method, *rest = deserialized_args

      # If this is an instance of a class, get the class itself
      # Prevents instances from coming through named like "#<Foo:0x007ffd7a9dd8a0>"
      klass = klass.class unless klass.is_a? Module

      job_class = [klass, method].map(&:to_s).join(".")
    rescue
      DELAYED_WRAPPER_KLASS
    end
  end

  job_class
rescue
  UNKNOWN_CLASS_PLACEHOLDER
end
latency(msg, time = Time.now.to_f) click to toggle source
# File lib/scout_apm/background_job_integrations/sidekiq.rb, line 125
def latency(msg, time = Time.now.to_f)
  created_at = msg['enqueued_at'] || msg['created_at']
  if created_at
    (time - created_at)
  else
    0
  end
rescue
  0
end