Class: ScoutApm::Layaway

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

Overview

Stores metrics in a file before sending them to the server. Two uses:

  1. A centralized store for multiple Agent processes. This way, only 1 checkin is sent to Scout rather than 1 per-process.

  2. Bundling up reports from multiple timeslices to make updates more efficent server-side.

Data is stored in a Hash, where the keys are Time.to_i on the minute. The value is a Hash => Hash, :slow_transactions => Array. When depositing data, the new data is either merged with an existing time or placed in a new key.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeLayaway

Returns a new instance of Layaway.



9
10
11
# File 'lib/scout_apm/layaway.rb', line 9

def initialize
  @file = ScoutApm::LayawayFile.new
end

Instance Attribute Details

#fileObject

Returns the value of attribute file.



8
9
10
# File 'lib/scout_apm/layaway.rb', line 8

def file
  @file
end

Instance Method Details

#deposit_and_deliverObject



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/scout_apm/layaway.rb', line 13

def deposit_and_deliver
  new_metrics = ScoutApm::Agent.instance.store.metric_hash
  log_deposited_metrics(new_metrics)
  log_deposited_slow_transactions(ScoutApm::Agent.instance.store.slow_transactions)
  to_deliver = {}
  file.read_and_write do |old_data|
    old_data ||= Hash.new
    # merge data
    # if (1) there's data in the file and (2) there isn't any data yet for the current minute, this means we've
    # collected all metrics for the previous slots and we're ready to deliver.
    #
    # Example w/2 processes:
    #
    # 12:00:34 ---
    # Process 1: old_data.any? => false, so deposits.
    # Process 2: old_data_any? => true and old_data[12:00].nil? => false, so deposits.
    #
    # 12:01:34 ---
    # Process 1: old_data.any? => true and old_data[12:01].nil? => true, so delivers metrics.
    # Process 2: old_data.any? => true and old_data[12:01].nil? => false, so deposits.
    if old_data.any? and old_data[slot].nil?
      to_deliver = old_data
      old_data = Hash.new
    elsif old_data.any?
      ScoutApm::Agent.instance.logger.debug "Not yet time to deliver payload for slot [#{Time.at(old_data.keys.sort.last).strftime("%m/%d/%y %H:%M:%S %z")}]"
    else
      ScoutApm::Agent.instance.logger.debug "There is no data in the layaway file to deliver."
    end
    old_data[slot]=ScoutApm::Agent.instance.store.merge_data_and_clear(old_data[slot] || {:metrics => {}, :slow_transactions => []})
    log_saved_data(old_data,new_metrics)
    old_data
  end
  to_deliver.any? ? validate_data(to_deliver) : {}
end

#log_deposited_metrics(new_metrics) ⇒ Object



74
75
76
77
78
79
80
81
82
# File 'lib/scout_apm/layaway.rb', line 74

def log_deposited_metrics(new_metrics)
  controller_count = 0
  new_metrics.each do |meta,stats|
    if meta.metric_name =~ /\AController/
      controller_count += stats.call_count
    end
  end
  ScoutApm::Agent.instance.logger.debug "Depositing #{controller_count} requests into #{Time.at(slot).strftime("%m/%d/%y %H:%M:%S %z")} slot."
end

#log_deposited_slow_transactions(new_slow_transactions) ⇒ Object



84
85
86
# File 'lib/scout_apm/layaway.rb', line 84

def log_deposited_slow_transactions(new_slow_transactions)
  ScoutApm::Agent.instance.logger.debug "Depositing #{new_slow_transactions.size} slow transactions into #{Time.at(slot).strftime("%m/%d/%y %H:%M:%S %z")} slot."
end

#log_saved_data(old_data, new_metrics) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/scout_apm/layaway.rb', line 88

def log_saved_data(old_data,new_metrics)
  ScoutApm::Agent.instance.logger.debug "Saving the following #{old_data.size} time slots locally:"
  old_data.each do |k,v|
    controller_count = 0
    new_metrics.each do |meta,stats|
      if meta.metric_name =~ /\AController/
        controller_count += stats.call_count
      end
    end
    ScoutApm::Agent.instance.logger.debug "#{Time.at(k).strftime("%m/%d/%y %H:%M:%S %z")} => #{controller_count} requests and #{v[:slow_transactions].size} slow transactions"
  end
end

#slotObject

Data is stored under timestamp-keys (without the second).



68
69
70
71
72
# File 'lib/scout_apm/layaway.rb', line 68

def slot
  t = Time.now
  t -= t.sec
  t.to_i
end

#validate_data(data) ⇒ Object

Ensures the data we’re sending to the server isn’t stale. This can occur if the agent is collecting data, and app server goes down w/data in the local storage. When it is restarted later data will remain in local storage but it won’t be for the current reporting interval.

If the data is stale, an empty Hash is returned. Otherwise, the data from the most recent slot is returned.



53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/scout_apm/layaway.rb', line 53

def validate_data(data)
  data = data.to_a.sort
  now = Time.now
  if (most_recent = data.first.first) < now.to_i - 2*60
    ScoutApm::Agent.instance.logger.debug "Local Storage is stale (#{Time.at(most_recent).strftime("%m/%d/%y %H:%M:%S %z")}). Not sending data."
    {}
  else
    data.first.last
  end
rescue
  ScoutApm::Agent.instance.logger.debug $!.message
  ScoutApm::Agent.instance.logger.debug $!.backtrace
end