Class: ScoutApm::Layaway
- Inherits:
-
Object
- Object
- ScoutApm::Layaway
- 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
-
#context ⇒ Object
readonly
Returns the value of attribute context.
Instance Method Summary collapse
- #delete_files_for(timestamp) ⇒ Object
- #delete_stale_files(older_than) ⇒ Object
-
#directory ⇒ Object
Returns a Pathname object with the fully qualified directory where the layaway files can be placed.
-
#initialize(context) ⇒ Layaway
constructor
A new instance of Layaway.
- #logger ⇒ Object
-
#with_claim(timestamp) ⇒ Object
Claims a given timestamp by getting an exclusive lock on a timestamped coordinator file.
- #write_reporting_period(reporting_period, files_limit = MAX_FILES_LIMIT) ⇒ Object
Constructor Details
#initialize(context) ⇒ Layaway
Returns a new instance of Layaway.
22 23 24 |
# File 'lib/scout_apm/layaway.rb', line 22 def initialize(context) @context = context end |
Instance Attribute Details
#context ⇒ Object (readonly)
Returns the value of attribute context.
21 22 23 |
# File 'lib/scout_apm/layaway.rb', line 21 def context @context end |
Instance Method Details
#delete_files_for(timestamp) ⇒ Object
131 132 133 134 135 136 |
# File 'lib/scout_apm/layaway.rb', line 131 def delete_files_for() all_files_for().each { |layaway| logger.debug("Layaway: Deleting file: #{layaway}") File.unlink(layaway) } end |
#delete_stale_files(older_than) ⇒ Object
138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/scout_apm/layaway.rb', line 138 def delete_stale_files(older_than) all_files_for(:all). map { |filename| (filename) }. compact. uniq. select { || .to_i < older_than.strftime(TIME_FORMAT).to_i }. tap { || logger.debug("Layaway: Deleting stale files with timestamps: #{.inspect}") }. map { || delete_files_for() } rescue => e logger.debug("Layaway: Problem deleting stale files: #{e.}, #{e.backtrace.inspect}") end |
#directory ⇒ Object
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.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/scout_apm/layaway.rb', line 35 def directory return @directory if @directory data_file = context.config.value("data_file") data_file = File.dirname(data_file) if data_file && !File.directory?(data_file) candidates = [ data_file, "#{context.environment.root}/tmp", "/tmp" ].compact found = candidates.detect { |dir| File.writable?(dir) } logger.debug("Storing Layaway Files in #{found}") @directory = Pathname.new(found) end |
#logger ⇒ Object
26 27 28 |
# File 'lib/scout_apm/layaway.rb', line 26 def logger context.logger end |
#with_claim(timestamp) ⇒ Object
Claims a given timestamp by getting an exclusive lock on a timestamped coordinator file. The coordinator file never contains data, it’s just a syncronization mechanism.
Once the ‘claim’ is obtained:
* load and yield each ReportingPeriod from the layaway files.
* if there are reporting periods:
* yields any ReportingPeriods collected up from all the files.
* deletes all of the layaway files (including the coordinator) for the timestamp
* if not
* delete the coordinator
* remove any stale layaway files that may be hanging around.
* Finally unlock and ensure the coordinator file is cleared.
If a claim file can’t be obtained, return false without doing any work Another process is handling the reporting.
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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/scout_apm/layaway.rb', line 83 def with_claim() coordinator_file = glob_pattern(, :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 logger.debug("Obtained Reporting Lock") log_layaway_file_information files = all_files_for().reject{|l| l.to_s == coordinator_file.to_s } rps = files.map{ |layaway| LayawayFile.new(context, layaway).load }.compact if rps.any? yield rps logger.debug("Layaway: Deleting the now-reported files for #{.to_s}") delete_files_for() # also removes the coodinator_file else File.unlink(coordinator_file) logger.debug("Layaway: No files to report") end logger.debug("Layaway: Checking for any stale files") delete_stale_files(.to_time - STALE_AGE) true rescue Exception => e logger.debug("Layaway: Caught an exception in with_claim, with the coordination file locked: #{e.}, #{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
52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/scout_apm/layaway.rb', line 52 def write_reporting_period(reporting_period, files_limit = MAX_FILES_LIMIT) if at_layaway_file_limit?(files_limit) # This will happen constantly once we hit this case, so only log the first time @wrote_layaway_limit_error_message ||= logger.error("Layaway: Hit layaway file limit. Not writing to layaway file") return false end logger.debug("Layaway: wrote time period: #{reporting_period.}") filename = file_for(reporting_period.) layaway_file = LayawayFile.new(context, filename) layaway_file.write(reporting_period) rescue => e logger.debug("Layaway: error writing: #{e.}, #{e.backtrace.inspect}") raise e end |