class Airbrake::PerformanceNotifier

PerformanceNotifier aggregates performance data and periodically sends it 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, self.class.name)
  @sync_sender = SyncSender.new(:put)
  @schedule_flush = nil
  @filter_chain = FilterChain.new

  @payload = {}.extend(MonitorMixin)
  @has_payload = @payload.new_cond
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 41
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 50
def close
  @payload.synchronize do
    @schedule_flush.kill if @schedule_flush
    @sync_sender.close
    @async_sender.close
  end
end
delete_filter(filter_class) click to toggle source

@see Airbrake.delete_performance_filter

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

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

# File lib/airbrake-ruby/performance_notifier.rb, line 27
def notify(metric)
  @payload.synchronize do
    send_metric(metric, sync: false)
  end
end
notify_sync(metric) click to toggle source

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

# File lib/airbrake-ruby/performance_notifier.rb, line 36
def notify_sync(metric)
  send_metric(metric, sync: true).value
end

Private Instance Methods

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

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

  if metric.timing && metric.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 60
def schedule_flush
  @schedule_flush ||= Thread.new do
    loop do
      @payload.synchronize do
        @last_flush_time ||= MonotonicTime.time_in_s

        while (MonotonicTime.time_in_s - @last_flush_time) < @flush_period
          @has_payload.wait(@flush_period)
        end

        if @payload.none?
          @last_flush_time = nil
          next
        end

        send(@async_sender, @payload, Airbrake::Promise.new)
        @payload.clear
      end
    end
  end
end
send(sender, payload, promise) click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 129
def send(sender, payload, promise)
  raise "payload cannot be empty. Race?" if payload.none?

  with_grouped_payload(payload) do |metric_hash, destination|
    url = URI.join(
      @config.apm_host,
      "api/v5/projects/#{@config.project_id}/#{destination}",
    )

    logger.debug do
      "#{LOG_LABEL} #{self.class.name}##{__method__}: #{metric_hash}"
    end
    sender.send(metric_hash, promise, url)
  end

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

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

  update_payload(metric)
  if sync || @flush_period == 0
    send(@sync_sender, @payload, promise)
  else
    @has_payload.signal
    schedule_flush
  end
end
serialize_metrics(metrics) click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 161
def serialize_metrics(metrics)
  metrics.map do |metric, stats|
    metric_hash = metric.to_h.merge!(stats[:total].to_h)

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

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

  @payload[metric][:total].increment_ms(metric.timing)

  metric.groups.each do |name, ms|
    @payload[metric][name] ||= Airbrake::Stat.new
    @payload[metric][name].increment_ms(ms)
  end
end
with_grouped_payload(raw_payload) { |payload, destination| ... } click to toggle source
# File lib/airbrake-ruby/performance_notifier.rb, line 147
def with_grouped_payload(raw_payload)
  grouped_payload = raw_payload.group_by do |metric, _stats|
    [metric.cargo, metric.destination]
  end

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

    yield(payload, destination)
  end
end