Class: ScoutApm::Reporting

Inherits:
Object
  • Object
show all
Defined in:
lib/scout_apm/reporting.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context) ⇒ Reporting

Returns a new instance of Reporting.



6
7
8
# File 'lib/scout_apm/reporting.rb', line 6

def initialize(context)
  @context = context
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



4
5
6
# File 'lib/scout_apm/reporting.rb', line 4

def context
  @context
end

Instance Method Details

#add_metric_ids(metrics) ⇒ Object

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



135
136
137
138
139
140
141
# File 'lib/scout_apm/reporting.rb', line 135

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) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/scout_apm/reporting.rb', line 79

def deliver_period(reporting_period,)
  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
  traces = (slow_transactions.map(&:span_trace) + slow_jobs.map(&:span_trace)).compact

  log_deliver(metrics, slow_transactions, , slow_jobs, histograms)

  payload = ScoutApm::Serializers::PayloadSerializer.serialize(, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_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

#headersObject

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



125
126
127
128
129
130
131
# File 'lib/scout_apm/reporting.rb', line 125

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) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/scout_apm/reporting.rb', line 100

def log_deliver(metrics, slow_transactions, , 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([: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: #{.inspect.pretty_inspect}\n\n")
end

#loggerObject



10
11
12
# File 'lib/scout_apm/reporting.rb', line 10

def logger
  context.logger
end

#metadata(reporting_period) ⇒ Object



68
69
70
71
72
73
74
75
76
77
# File 'lib/scout_apm/reporting.rb', line 68

def (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_metricsObject

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



26
27
28
29
30
31
32
33
34
35
36
37
# 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_serverObject

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



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 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")
       = (merged)
      deliver_period(merged,)
      context.extensions.run_periodic_callbacks(merged, )
      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

#reporterObject



14
15
16
# File 'lib/scout_apm/reporting.rb', line 14

def reporter
  @reporter ||= ScoutApm::Reporter.new(context, :checkin)
end