class ScoutApm::Reporting

Attributes

context[R]

Public Class Methods

new(context) click to toggle source
# File lib/scout_apm/reporting.rb, line 6
def initialize(context)
  @context = context
end

Public Instance Methods

add_metric_ids(metrics) click to toggle source

Before reporting, lookup metric_id for each MetricMeta. This speeds up reporting on the server-side.

# File lib/scout_apm/reporting.rb, line 136
def add_metric_ids(metrics)
  metrics.each do |meta,stats|
    if metric_id = metric_lookup[meta]
      meta.metric_id = metric_id
    end
  end
end
deliver_period(reporting_period,metadata) click to toggle source
# File lib/scout_apm/reporting.rb, line 79
def deliver_period(reporting_period,metadata)
  metrics = reporting_period.metrics_payload
  slow_transactions = reporting_period.slow_transactions_payload
  jobs = reporting_period.jobs
  slow_jobs = reporting_period.slow_jobs_payload
  histograms = reporting_period.histograms
  db_query_metrics = reporting_period.db_query_metrics_payload
  external_service_metrics = reporting_period.external_service_metrics_payload
  traces = (slow_transactions.map(&:span_trace) + slow_jobs.map(&:span_trace)).compact

  log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)

  payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, external_service_metrics, traces)
  logger.debug("Sending payload w/ Headers: #{headers.inspect}")

  reporter.report(payload, headers)
rescue => e
  logger.warn "Error on checkin"
  logger.info e.message
  logger.debug e.backtrace
end
headers() click to toggle source

TODO: Move this into PayloadSerializer? XXX: Remove non-json report format entirely

# File lib/scout_apm/reporting.rb, line 126
def headers
  if ScoutApm::Agent.instance.context.config.value("report_format") == 'json'
    headers = {'Content-Type' => 'application/json'}
  else
    headers = {}
  end
end
log_deliver(metrics, slow_transactions, metadata, jobs_traces, histograms) click to toggle source
# File lib/scout_apm/reporting.rb, line 101
def log_deliver(metrics, slow_transactions, metadata, jobs_traces, histograms)
  total_request_count = metrics.
    select { |meta,stats| meta.metric_name =~ /\AController/ }.
    inject(0) {|sum, (_, stat)| sum + stat.call_count }

  memory = metrics.
    find {|meta,stats| meta.metric_name =~ /\AMemory/ }
  process_log_str = if memory
                      "Recorded from #{memory.last.call_count} processes"
                    else
                      "Recorded across (unknown) processes"
                    end

  time_clause       = "[#{Time.parse(metadata[:agent_time]).strftime("%H:%M")}]"
  metrics_clause    = "#{metrics.length} Metrics for #{total_request_count} requests"
  slow_trans_clause = "#{slow_transactions.length} Slow Transaction Traces"
  job_clause        = "#{jobs_traces.length} Job Traces"
  histogram_clause  = "#{histograms.length} Histograms"

  logger.info "#{time_clause} Delivering #{metrics_clause} and #{slow_trans_clause} and #{job_clause}, #{process_log_str}."
  logger.debug("\n\nMetrics: #{metrics.pretty_inspect}\nSlowTrans: #{slow_transactions.pretty_inspect}\nMetadata: #{metadata.inspect.pretty_inspect}\n\n")
end
logger() click to toggle source
# File lib/scout_apm/reporting.rb, line 10
def logger
  context.logger
end
metadata(reporting_period) click to toggle source
# File lib/scout_apm/reporting.rb, line 68
def metadata(reporting_period)
  {
    :app_root      => context.environment.root.to_s,
    :unique_id     => ScoutApm::Utils::UniqueId.simple,
    :agent_version => ScoutApm::VERSION,
    :agent_time    => reporting_period.timestamp.to_s,
    :agent_pid     => Process.pid,
    :platform      => "ruby",
  }
end
process_metrics() click to toggle source

The data moves through a treadmill of reporting, coordinating several Rails processes by using an external file.

  • During the minute it is being recorded by the instruments, it gets recorded into the ram of each process (in the Store class).

  • The minute after, each process writes its own metrics to a shared LayawayFile

  • The minute after that, the first process to wake up pushes the combined data to the server, and wipes it. Next processes don't have anything to do.

At any given point, there is data in each of those steps, moving its way through the process

# File lib/scout_apm/reporting.rb, line 26
def process_metrics
  # Do any per-minute work necessary for the store
  context.store.tick!

  # Write the previous minute's data to the shared-across-process layaway file.
  context.store.write_to_layaway(context.layaway)

  # Attempt to send 2 minutes ago's data up to the server.  This
  # only acctually occurs if this process is the first to wake up this
  # minute.
  report_to_server
end
report_to_server() click to toggle source

In a running app, one process will get the period ready for delivery, the others will see 0.

# File lib/scout_apm/reporting.rb, line 40
def report_to_server
  period_to_report = ScoutApm::StoreReportingPeriodTimestamp.minutes_ago(2)

  logger.debug("Attempting to claim #{period_to_report.to_s}")

  did_write = context.layaway.with_claim(period_to_report) do |rps|
    logger.debug("Succeeded claiming #{period_to_report.to_s}")

    begin
      merged = rps.inject { |memo, rp| memo.merge(rp) }
      logger.debug("Merged #{rps.length} reporting periods, delivering")
      metadata = metadata(merged)
      deliver_period(merged,metadata)
      context.extensions.run_periodic_callbacks(merged, metadata)
      true
    rescue => e
      logger.debug("Error merging reporting periods #{e.message}")
      logger.debug("Error merging reporting periods #{e.backtrace}")
      false
    end

  end

  if !did_write
    logger.debug("Failed to obtain claim for #{period_to_report.to_s}")
  end
end
reporter() click to toggle source
# File lib/scout_apm/reporting.rb, line 14
def reporter
  @reporter ||= ScoutApm::Reporter.new(context, :checkin)
end