class Airbrake::PerformanceNotifier

QueryNotifier aggregates information about SQL queries and periodically sends collected data to Airbrake.

@api public @since v3.2.0 rubocop:disable Metrics/ClassLength

Public Class Methods

new() click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 12
def initialize
  @config = Airbrake::Config.instance
  @flush_period = Airbrake::Config.instance.performance_stats_flush_period
  @async_sender = AsyncSender.new(:put)
  @sync_sender = SyncSender.new(:put)
  @payload = {}
  @schedule_flush = nil
  @mutex = Mutex.new
  @filter_chain = FilterChain.new
  @waiting = false
end

Public Instance Methods

add_filter(filter = nil, &block) click to toggle source

@see Airbrake.add_performance_filter

# File lib/airbrake-ruby/performance_notifier.rb, line 39
def add_filter(filter = nil, &block)
  @filter_chain.add_filter(block_given? ? block : filter)
end
close() click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 48
def close
  @mutex.synchronize do
    @schedule_flush.kill if @schedule_flush
    @async_sender.close
    logger.debug("#{LOG_LABEL} performance notifier closed")
  end
end
delete_filter(filter_class) click to toggle source

@see Airbrake.delete_performance_filter

# File lib/airbrake-ruby/performance_notifier.rb, line 44
def delete_filter(filter_class)
  @filter_chain.delete_filter(filter_class)
end
notify(resource) click to toggle source

@param [Hash] resource @see Airbrake.notify_query @see Airbrake.notify_request

# File lib/airbrake-ruby/performance_notifier.rb, line 27
def notify(resource)
  send_resource(resource, sync: false)
end
notify_sync(resource) click to toggle source

@param [Hash] resource @since v4.10.0 @see Airbrake.notify_queue_sync

# File lib/airbrake-ruby/performance_notifier.rb, line 34
def notify_sync(resource)
  send_resource(resource, sync: true).value
end

Private Instance Methods

check_configuration(resource) click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 141
def check_configuration(resource)
  promise = @config.check_configuration
  return promise if promise.rejected?

  promise = @config.check_performance_options(resource)
  return promise if promise.rejected?

  if resource.timing && resource.timing == 0
    return Promise.new.reject(':timing cannot be zero')
  end

  Promise.new
end
schedule_flush() click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 86
def schedule_flush
  return if @payload.empty?

  if @schedule_flush && @schedule_flush.status == 'sleep' && @waiting
    begin
      @schedule_flush.run
    rescue ThreadError => exception
      logger.error("#{LOG_LABEL}: error occurred while flushing: #{exception}")
    end
  end

  @schedule_flush ||= spawn_timer
end
send(sender, payload, promise) click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 155
def send(sender, payload, promise)
  signature = "#{self.class.name}##{__method__}"
  raise "#{signature}: payload (#{payload}) cannot be empty. Race?" if payload.none?

  logger.debug { "#{LOG_LABEL} #{signature}: #{payload}" }

  with_grouped_payload(payload) do |resource_hash, destination|
    url = URI.join(
      @config.host,
      "api/v5/projects/#{@config.project_id}/#{destination}",
    )
    sender.send(resource_hash, promise, url)
  end

  promise
end
send_resource(resource, sync:) click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 122
def send_resource(resource, sync:)
  promise = check_configuration(resource)
  return promise if promise.rejected?

  @filter_chain.refine(resource)
  if resource.ignored?
    return Promise.new.reject("#{resource.class} was ignored by a filter")
  end

  @mutex.synchronize do
    update_payload(resource)
    if sync || @flush_period == 0
      send(@sync_sender, @payload, promise)
    else
      schedule_flush
    end
  end
end
serialize_resources(resources) click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 186
def serialize_resources(resources)
  resources.map do |resource, stats|
    resource_hash = resource.to_h.merge!(stats[:total].to_h)

    if resource.groups.any?
      group_stats = stats.reject { |name, _stat| name == :total }
      resource_hash['groups'] = group_stats.merge(group_stats) do |_name, stat|
        stat.to_h
      end
    end

    resource_hash
  end
end
spawn_timer() click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 100
def spawn_timer
  Thread.new do
    loop do
      if @payload.none?
        @waiting = true
        Thread.stop
        @waiting = false
      end

      sleep(@flush_period)

      payload = nil
      @mutex.synchronize do
        payload = @payload
        @payload = {}
      end

      send(@async_sender, payload, Airbrake::Promise.new)
    end
  end
end
update_payload(resource) click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 58
def update_payload(resource)
  if (total_stat = @payload[resource])
    @payload.key(total_stat).merge(resource)
  else
    @payload[resource] = { total: Airbrake::Stat.new }
  end

  update_total(resource, @payload[resource][:total])

  resource.groups.each do |name, ms|
    @payload[resource][name] ||= Airbrake::Stat.new
    @payload[resource][name].increment_ms(ms)
  end
end
update_total(resource, total) click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 73
def update_total(resource, total)
  if resource.timing
    total.increment_ms(resource.timing)
  else
    loc = caller_locations(6..6).first
    Kernel.warn(
      "#{loc.path}:#{loc.lineno}: warning: :start_time and :end_time are " \
      "deprecated. Use :timing & :time instead",
    )
    total.increment(resource.start_time, resource.end_time)
  end
end
with_grouped_payload(raw_payload) { |payload, destination| ... } click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 172
def with_grouped_payload(raw_payload)
  grouped_payload = raw_payload.group_by do |resource, _stats|
    [resource.cargo, resource.destination]
  end

  grouped_payload.each do |(cargo, destination), resources|
    payload = {}
    payload[cargo] = serialize_resources(resources)
    payload['environment'] = @config.environment if @config.environment

    yield(payload, destination)
  end
end