Class: ScoutApm::Layaway

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

Constant Summary collapse

STALE_AGE =

How long to let a stale file sit before deleting it. Letting it sit a bit may be useful for debugging

10 * 60
MAX_FILES_LIMIT =

Failsafe to prevent writing layaway files if for some reason they are not being cleaned up

5000
TIME_FORMAT =

A strftime format string for how we render timestamps in filenames. Must be sortable as an integer

"%Y%m%d%H%M"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, environment) ⇒ Layaway

Returns a new instance of Layaway.



24
25
26
27
# File 'lib/scout_apm/layaway.rb', line 24

def initialize(config, environment)
  @config = config
  @environment = environment
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



21
22
23
# File 'lib/scout_apm/layaway.rb', line 21

def config
  @config
end

#environmentObject (readonly)

Returns the value of attribute environment.



22
23
24
# File 'lib/scout_apm/layaway.rb', line 22

def environment
  @environment
end

Instance Method Details

#delete_files_for(timestamp) ⇒ Object



112
113
114
115
116
117
# File 'lib/scout_apm/layaway.rb', line 112

def delete_files_for(timestamp)
  all_files_for(timestamp).each { |layaway|
    ScoutApm::Agent.instance.logger.debug("Deleting layaway file: #{layaway}")
    File.unlink(layaway)
  }
end

#delete_stale_files(older_than) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
# File 'lib/scout_apm/layaway.rb', line 119

def delete_stale_files(older_than)
  all_files_for(:all).
    map { |filename| timestamp_from_filename(filename) }.
    compact.
    uniq.
    select { |timestamp| timestamp.to_i < older_than.strftime(TIME_FORMAT).to_i }.
      tap  { |timestamps| ScoutApm::Agent.instance.logger.debug("Deleting stale layaway files with timestamps: #{timestamps.inspect}") }.
    map    { |timestamp| delete_files_for(timestamp) }
rescue => e
  ScoutApm::Agent.instance.logger.debug("Problem deleting stale files: #{e.message}, #{e.backtrace.inspect}")
end

#directoryObject

Returns a Pathname object with the fully qualified directory where the layaway files can be placed. That directory must be writable by this process.

Don’t set this in initializer, since it relies on agent instance existing to figure out the value.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/scout_apm/layaway.rb', line 34

def directory
  return @directory if @directory

  data_file = config.value("data_file")
  data_file = File.dirname(data_file) if data_file && !File.directory?(data_file)

  candidates = [
    data_file,
    "#{environment.root}/tmp",
    "/tmp"
  ].compact

  found = candidates.detect { |dir| File.writable?(dir) }
  ScoutApm::Agent.instance.logger.debug("Storing Layaway Files in #{found}")
  @directory = Pathname.new(found)
end

#with_claim(timestamp) ⇒ Object

Claims a given timestamp (getting a lock on a particular filename), then yields ReportingPeriods collected up from all the files. If the yield returns truthy, delete the layaway files that made it up.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/scout_apm/layaway.rb', line 64

def with_claim(timestamp)
  coordinator_file = glob_pattern(timestamp, :coordinator)

  begin
    # This file gets deleted only by a process that successfully created and obtained the exclusive lock
    f = File.open(coordinator_file, File::RDWR | File::CREAT | File::EXCL | File::NONBLOCK)
  rescue Errno::EEXIST
    false
  end

  begin
    if f
      begin
        ScoutApm::Agent.instance.logger.debug("Obtained Reporting Lock")

        log_layaway_file_information

        files = all_files_for(timestamp).reject{|l| l.to_s == coordinator_file.to_s }
        rps = files.map{ |layaway| LayawayFile.new(layaway).load }.compact
        if rps.any?
          yield rps

          ScoutApm::Agent.instance.logger.debug("Deleting the now-reported layaway files for #{timestamp.to_s}")
          delete_files_for(timestamp) # also removes the coodinator_file

          ScoutApm::Agent.instance.logger.debug("Checking for any Stale layaway files")
          delete_stale_files(timestamp.to_time - STALE_AGE)
        else
          File.unlink(coordinator_file)
          ScoutApm::Agent.instance.logger.debug("No layaway files to report")
        end

        true
      rescue Exception => e
        ScoutApm::Agent.instance.logger.debug("Caught an exception in with_claim, with the coordination file locked: #{e.message}, #{e.backtrace.inspect}")
        raise
      ensure
        # Unlock the file when done!
        f.flock(File::LOCK_UN | File::LOCK_NB)
        f.close
      end
    else
      # Didn't obtain lock, another process is reporting. Return false from this function, but otherwise no work
      false
    end
  end
end

#write_reporting_period(reporting_period, files_limit = MAX_FILES_LIMIT) ⇒ Object



51
52
53
54
55
56
57
58
59
# File 'lib/scout_apm/layaway.rb', line 51

def write_reporting_period(reporting_period, files_limit = MAX_FILES_LIMIT)
  if at_layaway_file_limit?(files_limit)
    ScoutApm::Agent.instance.logger.error("Hit layaway file limit. Not writing to layaway file")
    return false
  end
  filename = file_for(reporting_period.timestamp)
  layaway_file = LayawayFile.new(filename)
  layaway_file.write(reporting_period)
end