scout_apm 2.0.0.pre3 → 2.0.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d49313e6479d1530b66fcc237fbe105646dd33d8
4
- data.tar.gz: bfc290ad58b83f3c3509cc81ffb14d9d08ab1237
3
+ metadata.gz: 550d7e521d6f822cdc1acdb002d8e770e42e37ae
4
+ data.tar.gz: fceea7f4780ab49b1a47f674f3d719a8a5e81b8e
5
5
  SHA512:
6
- metadata.gz: 420825d2d15e709e0d07209ff6d32c03fb51400123b31b880de67dd1c1890e4ab23421a05830d1821569db135aa4f30ab9e1b9b82e1112b713d0b09a797c8f5b
7
- data.tar.gz: 21d53aadf8fbb4584e5c5a75437f6b1433f28d7792c4715435b49f7f0170b619fc38246bae2e8b64b3e6acd546c57b7eead417d8ddafaafa1a34abd3c2f8f777
6
+ metadata.gz: cc456fb9984ee49cb6cefdf496cd8fb1854f7bd69c905e2775d3563adc902e8e105168948c13dd1105af06616ff63358db2ba8dc21393cf651f1066c49eaaa03
7
+ data.tar.gz: c8b63ebc3304cfff04b1769bbcb66740410fcdcb9b6a06dc58e155e88ae418b2ff0bd7ecedcd65570eb36fe675c5ce8340dff3f9b810b945c5bdd8da22682eac
data/.gitignore CHANGED
@@ -15,4 +15,4 @@ test/tmp/*
15
15
  .DS_Store
16
16
  test/tmp/*coverage/*
17
17
  coverage/*
18
- lib/allocations.bundle
18
+ lib/*.bundle
data/CHANGELOG.markdown CHANGED
@@ -10,6 +10,15 @@
10
10
  * Collect 95th percentiles
11
11
  * Remove unused & old references to Stackprof
12
12
 
13
+ # 1.6.2
14
+
15
+ * Use a more flexible approach to storing "Layaway Files" (the temporary data
16
+ files the agent uses).
17
+
18
+ # 1.6.1
19
+
20
+ * Remove old & unused references to Stackprof. Prevent interaction with intentional usage of Stackprof
21
+
13
22
  # 1.6.0
14
23
 
15
24
  * Dynamic algorithm for selecting when to collect traces. Now, we will collect a
data/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ All components of this product, except where noted, are Copyright (c) 2013-2016 Zimuth, Inc DBA Scout. All rights reserved.
2
+
3
+ Certain inventions disclosed in this file may be claimed within patents owned or patent applications filed by Zimuth, Inc. or third parties.
4
+
5
+ Subject to the terms of this notice, Zimuth grants you a nonexclusive, nontransferable license, without the right to sublicense, to (a) install and execute one copy of these files on any number of workstations owned or controlled by you and (b) distribute verbatim copies of these files to third parties. As a condition to the foregoing grant, you must provide this notice along with each copy you distribute and you must not remove, alter, or obscure this notice. All other use, reproduction, modification, distribution, or other exploitation of these files is strictly prohibited, except as may be set forth in a separate written license agreement between you and Zimuth. The terms of any such license agreement will control over this notice. The license stated above will be automatically terminated and revoked if you exceed its scope or violate any of the terms of this notice.
6
+
7
+ This License does not grant permission to use the trade names, trademarks, service marks, or product names of Zimuth, except as required for reasonable and customary use in describing the origin of this file and reproducing the content of this notice. You may not mark or brand this file with any trade name, trademarks, service marks, or product names other than the original brand (if any) provided by Zimuth.
8
+
9
+ Unless otherwise expressly agreed by Zimuth in a separate written license agreement, these files are provided AS IS, WITHOUT WARRANTY OF ANY KIND, including without any implied warranties of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, or NON-INFRINGEMENT. As a condition to your use of these files, you are solely responsible for such use. Zimuth will have no liability to you for direct, indirect, consequential, incidental, special, or punitive damages or for lost profits or data.
@@ -24,17 +24,35 @@ module ScoutApm
24
24
  report_to_server
25
25
  end
26
26
 
27
- MAX_AGE_TO_REPORT = (10 * 60) # ten minutes as seconds
28
-
29
27
  # In a running app, one process will get one period ready for delivery, the others will see 0.
30
28
  def report_to_server
31
- reporting_periods = layaway.periods_ready_for_delivery
32
- reporting_periods.reject! {|rp| rp.timestamp.age_in_seconds > MAX_AGE_TO_REPORT }
33
- reporting_periods.each do |rp|
34
- deliver_period(rp)
29
+ period_to_report = ScoutApm::StoreReportingPeriodTimestamp.minutes_ago(2)
30
+
31
+ logger.debug("Attempting to claim #{period_to_report.to_s}")
32
+
33
+ did_write = layaway.with_claim(period_to_report) do |rps|
34
+ logger.debug("Succeeded claiming #{period_to_report.to_s}")
35
+
36
+ begin
37
+ merged = rps.inject { |memo, rp| memo.merge(rp) }
38
+ logger.debug("Merged #{rps.length} reporting periods, delivering")
39
+ deliver_period(merged)
40
+ true
41
+ rescue => e
42
+ logger.debug("Error merging reporting periods #{e.message}")
43
+ logger.debug("Error merging reporting periods #{e.backtrace}")
44
+ false
45
+ end
46
+
47
+ end
48
+
49
+ if !did_write
50
+ logger.debug("Failed to obtain claim for #{period_to_report.to_s}")
35
51
  end
52
+
36
53
  end
37
54
 
55
+
38
56
  def deliver_period(reporting_period)
39
57
  metrics = reporting_period.metrics_payload
40
58
  slow_transactions = reporting_period.slow_transactions_payload
@@ -119,11 +119,13 @@ module ScoutApm
119
119
  logger.info "Starting monitoring for [#{environment.deploy_integration.name}]]."
120
120
  return environment.deploy_integration.install
121
121
  end
122
+
123
+ load_instruments if should_load_instruments?(options)
124
+
122
125
  return false unless preconditions_met?(options)
123
126
  @started = true
124
127
  logger.info "Starting monitoring for [#{environment.application_name}]. Framework [#{environment.framework}] App Server [#{environment.app_server}] Background Job Framework [#{environment.background_job_name}]."
125
128
 
126
- load_instruments if should_load_instruments?(options)
127
129
 
128
130
  [ ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
129
131
  ScoutApm::Instruments::Process::ProcessMemory.new(logger),
@@ -258,6 +260,8 @@ module ScoutApm
258
260
  # If we want to skip the app_server_check, then we must load it.
259
261
  def should_load_instruments?(options={})
260
262
  return true if options[:skip_app_server_check]
263
+ return true if config.value('instant')
264
+ return false if !apm_enabled?
261
265
  environment.app_server_integration.found? || !background_job_missing?
262
266
  end
263
267
 
@@ -55,8 +55,8 @@ module ScoutApm
55
55
 
56
56
  def value(key)
57
57
  @overlays.each do |overlay|
58
- if result = overlay.value(key)
59
- return result
58
+ if overlay.has_key?(key)
59
+ return overlay.value(key)
60
60
  end
61
61
  end
62
62
 
@@ -73,18 +73,31 @@ module ScoutApm
73
73
  'disabled_instruments' => [],
74
74
  'enable_background_jobs' => true,
75
75
  'ignore_traces' => [],
76
+ 'instant' => false, # false for now so code can live in main branch
76
77
  }.freeze
77
78
 
78
79
  def value(key)
79
80
  DEFAULTS[key]
80
81
  end
82
+
83
+ def has_key?(key)
84
+ DEFAULTS.has_key?(key)
85
+ end
81
86
  end
82
87
 
83
88
  class ConfigEnvironment
84
89
  def value(key)
85
- val = ENV['SCOUT_' + key.upcase]
90
+ val = ENV[key_to_env_key(key)]
86
91
  val.to_s.strip.length.zero? ? nil : val
87
92
  end
93
+
94
+ def has_key?(key)
95
+ ENV.has_key?(key_to_env_key(key))
96
+ end
97
+
98
+ def key_to_env_key(key)
99
+ 'SCOUT_' + key.upcase
100
+ end
88
101
  end
89
102
 
90
103
  # Attempts to load a configuration file, and parse it as YAML. If the file
@@ -105,17 +118,21 @@ module ScoutApm
105
118
  end
106
119
  end
107
120
 
121
+ def has_key?(key)
122
+ @settings.has_key?(key)
123
+ end
124
+
108
125
  private
109
126
 
110
127
  def load_file(file)
111
128
  if !File.exist?(@resolved_file_path)
112
- logger.info("Configuration file #{file} does not exist, skipping.")
129
+ logger.debug("Configuration file #{file} does not exist, skipping.")
113
130
  @file_loaded = false
114
131
  return
115
132
  end
116
133
 
117
134
  if !app_environment
118
- logger.info("Could not determine application environment, aborting configuration file load")
135
+ logger.debug("Could not determine application environment, aborting configuration file load")
119
136
  @file_loaded = false
120
137
  return
121
138
  end
@@ -131,10 +148,10 @@ module ScoutApm
131
148
  " check that there is a top level #{app_environment} key.")
132
149
  end
133
150
 
134
- logger.info("Loaded Configuration: #{@resolved_file_path}. Using environment: #{app_environment}")
151
+ logger.debug("Loaded Configuration: #{@resolved_file_path}. Using environment: #{app_environment}")
135
152
  @file_loaded = true
136
153
  rescue Exception => e # Explicit `Exception` handling to catch SyntaxError and anything else that ERB or YAML may throw
137
- logger.info("Failed loading configuration file: #{e.message}. ScoutAPM will continue starting with configuration from ENV and defaults")
154
+ logger.debug("Failed loading configuration file: #{e.message}. ScoutAPM will continue starting with configuration from ENV and defaults")
138
155
  @file_loaded = false
139
156
  end
140
157
  end
@@ -0,0 +1,35 @@
1
+ # A stand-in for Store. This one does not accumulate data, it just throws it away.
2
+ # Methods in here are the minimal set needed to fool calling objects
3
+ module ScoutApm
4
+ class FakeStore
5
+
6
+ def initialize
7
+ end
8
+
9
+ def current_timestamp
10
+ # why a time passed in here? So the histogram in tracked_request.record! doesn't accumulate data indefinitely.
11
+ StoreReportingPeriodTimestamp.new(Time.parse('2000-01-01 00:00:00'))
12
+ end
13
+
14
+ def track!(metrics, options={})
15
+ end
16
+
17
+ def track_one!(type, name, value, options={})
18
+ end
19
+
20
+ def track_slow_transaction!(slow_transaction)
21
+ end
22
+
23
+ def track_job!(job)
24
+ end
25
+
26
+ def track_slow_job!(job)
27
+ end
28
+
29
+ def write_to_layaway(layaway, force=false)
30
+ end
31
+
32
+ def add_sampler(sampler)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ <script language="JavaScript">
2
+ (function() {
3
+ /* instrument XMLHttpRequest */
4
+ var open = window.XMLHttpRequest.prototype.open;
5
+ var send = window.XMLHttpRequest.prototype.send;
6
+ function openReplacement(method, url, async, user, password) {
7
+ this._url = url;
8
+ return open.apply(this, arguments);
9
+ }
10
+ function sendReplacement(data) {
11
+ if (this.onload) {
12
+ this._onload = this.onload;
13
+ }
14
+ this.onload = onLoadReplacement;
15
+ return send.apply(this, arguments);
16
+ }
17
+ function onLoadReplacement() {
18
+ try {
19
+ traceText = this.getResponseHeader("X-scoutapminstant");
20
+ if(traceText){
21
+ //console.info("Got an AJAX instrumentation with "+traceText.length.toString()+" characters.")
22
+ setTimeout(function(){ window.scoutInstant("addTrace",traceText) },0);
23
+ }
24
+ } catch(e){
25
+ console.debug("Problem getting X-scoutapminstant header");
26
+ console.debug(e);
27
+ }
28
+ if (this._onload) {
29
+ return this._onload.apply(this, arguments);
30
+ }
31
+ }
32
+ window.XMLHttpRequest.prototype.open = openReplacement;
33
+ window.XMLHttpRequest.prototype.send = sendReplacement;
34
+ })();
35
+ </script>
@@ -0,0 +1,98 @@
1
+ module ScoutApm
2
+ module Instant
3
+
4
+ # an abstraction for manipulating the HTML we capture in the middleware
5
+ class Page
6
+ def initialize(html)
7
+ @html = html
8
+ @to_add_to_head = []
9
+ @to_add_to_body = []
10
+ end
11
+
12
+ def add_to_head(content)
13
+ @to_add_to_head << content
14
+ end
15
+
16
+ def add_to_body(content)
17
+ @to_add_to_body << content
18
+ end
19
+
20
+ def res
21
+ i = @html.index("</body>")
22
+ @html = @html.insert(i, @to_add_to_body.join("")) if i
23
+ i = @html.index("</head>")
24
+ @html = @html.insert(i, @to_add_to_head.join("")) if i
25
+ @html
26
+ end
27
+ end
28
+
29
+ class Util
30
+ # reads the literal contents of the file in assets/#{name}
31
+ # if any vars are supplied, do a simple string substitution of the vars for their values
32
+ def self.read_asset(name, vars = {})
33
+ contents = File.read(File.join(File.dirname(__FILE__), "assets", name))
34
+ if vars.any?
35
+ vars.each_pair{|k,v| contents.gsub!(k.to_s,v.to_s)}
36
+ end
37
+ contents
38
+ end
39
+ end
40
+
41
+ # Note that this middleware never even gets inserted unless Rails environment is development (See Railtie)
42
+ class Middleware
43
+ def initialize(app)
44
+ @app = app
45
+ end
46
+
47
+ def call(env)
48
+ status, headers, response = @app.call(env)
49
+
50
+ if ScoutApm::Agent.instance.config.value('instant')
51
+ if response.respond_to?(:body)
52
+ req = ScoutApm::RequestManager.lookup
53
+ slow_converter = LayerConverters::SlowRequestConverter.new(req)
54
+ trace = slow_converter.call
55
+ if trace
56
+ hash = ScoutApm::Serializers::PayloadSerializerToJson.rearrange_slow_transaction(trace)
57
+ hash.merge!(id:env['action_dispatch.request_id']) # TODO: this could be separated into a metadata section
58
+ payload = ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(hash)
59
+
60
+
61
+ if env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
62
+ # Add the payload as a header if it's an AJAX call
63
+ headers['X-scoutapminstant'] = payload
64
+ [status, headers, response]
65
+ else
66
+ # otherwise, attempt to add it inline in the page, along with the appropriate JS & CSS. Note, if page doesn't have a head or body,
67
+ #duration = (req.root_layer.total_call_time*1000).to_i
68
+ apm_host=ScoutApm::Agent.instance.config.value("direct_host")
69
+ page = ScoutApm::Instant::Page.new(response.body)
70
+ page.add_to_head(ScoutApm::Instant::Util.read_asset("xmlhttp_instrumentation.html")) # This monkey-patches XMLHttpRequest. It could possibly be part of the main scout_instant.js too. Putting it here so it runs as soon as possible.
71
+ page.add_to_head("<link href='#{apm_host}/instant/scout_instant.css?cachebust=#{Time.now.to_i}' media='all' rel='stylesheet' />")
72
+ page.add_to_body("<script src='#{apm_host}/instant/scout_instant.js?cachebust=#{Time.now.to_i}'></script>")
73
+ page.add_to_body("<script>var scoutInstantPageTrace=#{payload};window.scoutInstant=window.scoutInstant('#{apm_host}', scoutInstantPageTrace)</script>")
74
+
75
+
76
+ if response.is_a?(ActionDispatch::Response)
77
+ # preserve the ActionDispatch::Response when applicable
78
+ response.body=[page.res]
79
+ [status, headers, response]
80
+ else
81
+ # otherwise, just return an array
82
+ # TODO: this will break ActionCable repsponse
83
+ [status, headers, [page.res]]
84
+ end
85
+ end
86
+ else
87
+ [status, headers, response]
88
+ end
89
+ else
90
+ [status, headers, response]
91
+ end
92
+ else
93
+ [status, headers, response]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,53 +1,156 @@
1
- # Stores StoreReportingPeriod objects in a file before sending them to the server.
2
- # 1. A centralized store for multiple Agent processes. This way, only 1 checkin is sent to Scout rather than 1 per-process.
1
+ # Stores StoreReportingPeriod objects in a per-process file before sending them to the server.
2
+ # Coordinates a single process to collect up all individual files, merge them, then send.
3
+ #
4
+ # Each layaway file is named basedir/scout_#{timestamp}_#{pid}.data
5
+ # Where timestamp is in the format:
6
+ # And PID is the process id of the running process
7
+ #
3
8
  module ScoutApm
4
9
  class Layaway
5
- attr_accessor :file
10
+ # How old a file needs to be in Seconds before it gets reported.
11
+ REPORTING_AGE = 120
6
12
 
7
- def initialize
8
- @file = ScoutApm::LayawayFile.new
13
+ # How long to let a stale file sit before deleting it.
14
+ # Letting it sit a bit may be useful for debugging
15
+ STALE_AGE = 10 * 60
16
+
17
+ # A strftime format string for how we render timestamps in filenames.
18
+ # Must be sortable as an integer
19
+ TIME_FORMAT = "%Y%m%d%H%M"
20
+
21
+ def initialize(directory=nil)
22
+ @directory = directory
23
+ end
24
+
25
+ # Returns a Pathname object with the fully qualified directory where the layaway files can be placed.
26
+ # That directory must be writable by this process.
27
+ #
28
+ # Don't set this in initializer, since it relies on agent instance existing to figure out the value.
29
+ #
30
+ def directory
31
+ return @directory if @directory
32
+
33
+ data_file = ScoutApm::Agent.instance.config.value("data_file")
34
+ data_file = File.dirname(data_file) if data_file && !File.directory?
35
+
36
+ candidates = [
37
+ data_file,
38
+ "#{ScoutApm::Agent.instance.environment.root}/tmp",
39
+ "/tmp"
40
+ ].compact
41
+
42
+ found = candidates.detect { |dir| File.writable?(dir) }
43
+ ScoutApm::Agent.instance.logger.debug("Storing Layaway Files in #{found}")
44
+ @directory = Pathname.new(found)
45
+ end
46
+
47
+ def write_reporting_period(reporting_period)
48
+ filename = file_for(reporting_period.timestamp)
49
+ layaway_file = LayawayFile.new(filename)
50
+ layaway_file.write(reporting_period)
9
51
  end
10
52
 
11
- def add_reporting_period(time, reporting_period)
12
- file.read_and_write do |existing_data|
13
- existing_data ||= Hash.new
14
- ScoutApm::Agent.instance.logger.debug("AddReportingPeriod: Adding a reporting_period with timestamp: #{reporting_period.timestamp.to_s}, and #{reporting_period.request_count} requests")
53
+ # Claims a given timestamp (getting a lock on a particular filename),
54
+ # then yields ReportingPeriods collected up from all the files.
55
+ # If the yield returns truthy, delete the layaway files that made it up.
56
+ def with_claim(timestamp)
57
+ coordinator_file = glob_pattern(timestamp, :coordinator)
58
+
59
+
60
+ # This file gets deleted only by a process that successfully obtained a lock
61
+ f = File.open(coordinator_file, File::RDWR | File::CREAT)
62
+ begin
63
+ # Nonblocking, Exclusive lock.
64
+ if f.flock(File::LOCK_EX | File::LOCK_NB)
65
+
66
+ ScoutApm::Agent.instance.logger.debug("Obtained Reporting Lock")
15
67
 
16
- existing_data = existing_data.merge(time => reporting_period) {|key, old_val, new_val|
17
- old_req = old_val.request_count
18
- new_req = new_val.request_count
19
- ScoutApm::Agent.instance.logger.debug("Merging Two reporting periods (#{old_val.timestamp.to_s}, #{new_val.timestamp.to_s}): old req #{old_req}, new req #{new_req}")
68
+ files = all_files_for(timestamp).reject{|l| l.to_s == coordinator_file.to_s }
69
+ rps = files.map{ |layaway| LayawayFile.new(layaway).load }.compact
70
+ if rps.any?
71
+ yield rps
20
72
 
21
- old_val.merge(new_val)
22
- }
73
+ delete_files_for(timestamp) # also removes the coodinator_file
74
+ delete_stale_files(timestamp.to_time - STALE_AGE)
75
+ else
76
+ File.unlink(coordinator_file)
77
+ ScoutApm::Agent.instance.logger.debug("No layaway files to report")
78
+ end
23
79
 
24
- ScoutApm::Agent.instance.logger.debug("AddReportingPeriod: AfterMerge Timestamps: #{existing_data.keys.map(&:to_s).inspect}")
25
- existing_data
80
+ # Unlock the file when done!
81
+ f.flock(File::LOCK_UN | File::LOCK_NB)
82
+ f.close
83
+ true
84
+ else
85
+ # Didn't obtain lock, another process is reporting. Return false from this function, but otherwise no work
86
+ f.close
87
+ false
88
+ end
26
89
  end
27
90
  end
28
91
 
29
- REPORTING_INTERVAL = 60 # seconds
92
+ def delete_files_for(timestamp)
93
+ all_files_for(timestamp).each { |layaway| File.unlink(layaway) }
94
+ end
95
+
96
+ def delete_stale_files(older_than)
97
+ all_files_for(:all).
98
+ map { |filename| timestamp_from_filename(filename) }.
99
+ compact.
100
+ uniq.
101
+ select { |timestamp| timestamp.to_i < older_than.strftime(TIME_FORMAT).to_i }.
102
+ tap { |timestamps| ScoutApm::Agent.instance.logger.debug("Deleting stale layaway files with timestamps: #{timestamps.inspect}") }.
103
+ map { |timestamp| delete_files_for(timestamp) }
104
+ end
30
105
 
31
- # Returns an array of ReportingPeriod objects that are ready to be pushed to the server
32
- def periods_ready_for_delivery
33
- ready_for_delivery = []
34
- file.read_and_write do |existing_data|
35
- existing_data ||= {}
106
+ private
107
+
108
+ ##########################################
109
+ # Looking up files
110
+
111
+ def file_for(timestamp)
112
+ glob_pattern(timestamp)
113
+ end
36
114
 
37
- ScoutApm::Agent.instance.logger.debug("PeriodsReadyForDeliver: All Timestamps: #{existing_data.keys.map(&:to_s).inspect}")
115
+ def all_files_for(timestamp)
116
+ Dir[glob_pattern(timestamp, :all)]
117
+ end
38
118
 
39
- ready_for_delivery = existing_data.to_a.select {|time, rp| should_send?(rp) } # Select off the values we want. to_a is needed for compatibility with Ruby 1.8.7.
119
+ # Timestamp should be either :all or a Time-ish object that responds to strftime (StoreReportingPeriodTimestamp does)
120
+ # if timestamp == :all then find all timestamps, otherwise format it.
121
+ # if pid == :all, get the files for all
122
+ def glob_pattern(timestamp, pid=$$)
123
+ timestamp_pattern = format_timestamp(timestamp)
124
+ pid_pattern = format_pid(pid)
125
+ directory + "scout_#{timestamp_pattern}_#{pid_pattern}.data"
126
+ end
40
127
 
41
- # Rewrite anything not plucked out back to the file
42
- existing_data.reject {|k, v| ready_for_delivery.map(&:first).include?(k) }
128
+ def format_timestamp(timestamp)
129
+ if timestamp == :all
130
+ "*"
131
+ elsif timestamp.respond_to?(:strftime)
132
+ timestamp.strftime(TIME_FORMAT)
133
+ else
134
+ timestamp.to_s
43
135
  end
136
+ end
44
137
 
45
- return ready_for_delivery.map(&:last)
138
+ def format_pid(pid)
139
+ if pid == :all
140
+ "*"
141
+ else
142
+ pid.to_s
143
+ end
46
144
  end
47
145
 
48
- # We just want to send anything older than X
49
- def should_send?(reporting_period)
50
- reporting_period.timestamp.age_in_seconds > (REPORTING_INTERVAL * 2)
146
+ def timestamp_from_filename(filename)
147
+ match = filename.match(%r{scout_(.*)_.*\.data})
148
+ if match
149
+ match[1]
150
+ else
151
+ nil
152
+ end
51
153
  end
52
154
  end
53
155
  end
156
+
@@ -1,94 +1,36 @@
1
- # Logic for the serialized file access
1
+ # A single layaway file. See Layaway for the management of the group of files.
2
2
  module ScoutApm
3
3
  class LayawayFile
4
- def path
5
- return @path if @path
4
+ attr_reader :path
6
5
 
7
- candidates = [
8
- ScoutApm::Agent.instance.config.value("data_file"),
9
- "#{ScoutApm::Agent.instance.default_log_path}/scout_apm.db",
10
- "#{ScoutApm::Agent.instance.environment.root}/tmp/scout_apm.db"
11
- ]
12
-
13
- candidates.each do |candidate|
14
- next if candidate.nil?
15
-
16
- begin
17
- ScoutApm::Agent.instance.logger.debug("Checking Layaway File Location: #{candidate}")
18
- File.open(candidate, "w") { |f| } # Open & Close to check that we can
19
-
20
- # No exception, it is valid
21
- ScoutApm::Agent.instance.logger.info("Layaway File location found: #{candidate}")
22
- @path = candidate
23
- return @path
24
- rescue Exception
25
- ScoutApm::Agent.instance.logger.debug("Couldn't open layaway file for test write at #{candidate}")
26
- end
27
- end
28
-
29
- ScoutApm::Agent.instance.logger.error("No valid layaway file found, please set a location in the configuration key `data_file`")
30
- nil
6
+ def initialize(path)
7
+ @path = path
31
8
  end
32
9
 
33
- def dump(object)
34
- Marshal.dump(object)
35
- end
36
-
37
- def load(dump)
38
- if dump.size == 0
39
- ScoutApm::Agent.instance.logger.debug("No data in layaway file.")
40
- return nil
41
- end
42
- Marshal.load(dump)
10
+ def load
11
+ data = File.open(path, "r") { |f| read_raw(f) }
12
+ deserialize(data)
43
13
  rescue NameError, ArgumentError, TypeError => e
14
+ # Marshal error
44
15
  ScoutApm::Agent.instance.logger.info("Unable to load data from Layaway file, resetting.")
45
16
  ScoutApm::Agent.instance.logger.debug("#{e.message}, #{e.backtrace.join("\n\t")}")
46
17
  nil
47
18
  end
48
19
 
49
- def read_and_write
50
- File.open(path, File::RDWR | File::CREAT) do |f|
51
- f.flock(File::LOCK_EX)
52
- begin
53
- result = (yield get_data(f))
54
- f.rewind
55
- f.truncate(0)
56
- if result
57
- write(f, dump(result))
58
- end
59
- ensure
60
- f.flock(File::LOCK_UN)
61
- end
62
- end
63
- rescue Errno::ENOENT, Exception => e
64
- ScoutApm::Agent.instance.logger.error("Unable to access the layaway file [#{e.class} - #{e.message}]. " +
65
- "The user running the app must have read & write access. " +
66
- "Change the path by setting the `data_file` key in scout_apm.yml"
67
- )
68
- ScoutApm::Agent.instance.logger.debug(e.backtrace.join("\n\t"))
69
-
70
- # ensure the in-memory metric hash is cleared so data doesn't continue to accumulate.
71
- # ScoutApm::Agent.instance.store.metric_hash = {}
20
+ def write(data)
21
+ serialized_data = serialize(data)
22
+ File.open(path, "w") { |f| write_raw(f, serialized_data) }
72
23
  end
73
24
 
74
- def get_data(f)
75
- data = read_until_end(f)
76
- result = load(data)
77
- f.truncate(0)
78
- result
25
+ def serialize(data)
26
+ Marshal.dump(data)
79
27
  end
80
28
 
81
- def write(f, string)
82
- result = 0
83
- while (result < string.length)
84
- result += f.write_nonblock(string)
85
- end
86
- rescue Errno::EAGAIN, Errno::EINTR
87
- IO.select(nil, [f])
88
- retry
29
+ def deserialize(data)
30
+ Marshal.load(data)
89
31
  end
90
32
 
91
- def read_until_end(f)
33
+ def read_raw(f)
92
34
  contents = ""
93
35
  while true
94
36
  contents << f.read_nonblock(10_000)
@@ -99,5 +41,16 @@ module ScoutApm
99
41
  rescue EOFError
100
42
  contents
101
43
  end
44
+
45
+ def write_raw(f, data)
46
+ result = 0
47
+ while (result < data.length)
48
+ result += f.write_nonblock(data)
49
+ end
50
+ rescue Errno::EAGAIN, Errno::EINTR
51
+ IO.select(nil, [f])
52
+ retry
53
+ end
102
54
  end
103
55
  end
56
+
@@ -91,7 +91,6 @@ module ScoutApm
91
91
  end
92
92
 
93
93
  def capture_backtrace!
94
- ScoutApm::Agent.instance.logger.debug { "Capturing Backtrace for Layer [#{type}/#{name}]" }
95
94
  @backtrace = caller_array
96
95
  end
97
96
 
@@ -132,8 +131,7 @@ module ScoutApm
132
131
  if stop_time
133
132
  stop_time - start_time
134
133
  else
135
- # Shouldn't have called this yet. Return 0
136
- 0
134
+ Time.now - start_time
137
135
  end
138
136
  end
139
137
 
@@ -154,7 +152,11 @@ module ScoutApm
154
152
  # These are almost identical to the timing metrics.
155
153
 
156
154
  def total_allocations
157
- allocations = (@allocations_stop - @allocations_start)
155
+ if @allocations_stop > 0
156
+ allocations = (@allocations_stop - @allocations_start)
157
+ else
158
+ allocations = (ScoutApm::Instruments::Allocations.count - @allocations_start)
159
+ end
158
160
  allocations < 0 ? 0 : allocations
159
161
  end
160
162
 
@@ -20,7 +20,13 @@ module ScoutApm
20
20
 
21
21
  # Create a new TrackedRequest object for this thread
22
22
  def self.create
23
- Thread.current[:scout_request] = TrackedRequest.new
23
+ store = if ScoutApm::Agent.instance.apm_enabled?
24
+ ScoutApm::Agent.instance.store
25
+ else
26
+ ScoutApm::FakeStore.new
27
+ end
28
+
29
+ Thread.current[:scout_request] = TrackedRequest.new(store)
24
30
  end
25
31
  end
26
32
  end
@@ -13,17 +13,21 @@ module ScoutApm
13
13
  })
14
14
  end
15
15
 
16
- # Old style of metric serializing.
16
+ # For the old style of metric serializing.
17
17
  def rearrange_the_metrics(metrics)
18
18
  metrics.to_a.map do |meta, stats|
19
19
  stats.as_json.merge(:key => meta.as_json)
20
20
  end
21
21
  end
22
22
 
23
+ # takes an array of slow transactions
23
24
  def rearrange_the_slow_transactions(slow_transactions)
24
- slow_transactions.to_a.map do |slow_t|
25
- slow_t.as_json.merge(:metrics => rearrange_the_metrics(slow_t.metrics), :allocation_metrics => rearrange_the_metrics(slow_t.allocation_metrics))
26
- end
25
+ slow_transactions.to_a.map { |t| rearrange_slow_transaction(t) }
26
+ end
27
+
28
+ # takes just one slow transaction
29
+ def rearrange_slow_transaction(slow_t)
30
+ slow_t.as_json.merge(:metrics => rearrange_the_metrics(slow_t.metrics), :allocation_metrics => rearrange_the_metrics(slow_t.allocation_metrics))
27
31
  end
28
32
 
29
33
  def jsonify_hash(hash)
@@ -13,9 +13,7 @@ module ScoutApm
13
13
 
14
14
  def forking?
15
15
  return true unless (defined?(::Unicorn) && defined?(::Unicorn::Configurator))
16
- ObjectSpace.each_object(::Unicorn::Configurator).first[:preload_app].tap {|x|
17
- logger.info "Unicorn is forking? #{x}"
18
- }
16
+ ObjectSpace.each_object(::Unicorn::Configurator).first[:preload_app]
19
17
  rescue
20
18
  true
21
19
  end
@@ -23,13 +23,12 @@ module ScoutApm
23
23
  @metrics = metrics
24
24
  @allocation_metrics = allocation_metrics
25
25
  @context = context
26
- @time = time
26
+ @time = time || Time.now
27
27
  @prof = []
28
28
  @mem_delta = mem_delta
29
29
  @allocations = allocations
30
30
  @seconds_since_startup = (Time.now - ScoutApm::Agent.instance.process_start_time)
31
31
  @hostname = ScoutApm::Environment.instance.hostname
32
-
33
32
  @score = score
34
33
  ScoutApm::Agent.instance.logger.debug { "Slow Request [#{uri}] - Call Time: #{total_call_time} Mem Delta: #{mem_delta} Score: #{score}"}
35
34
  end
@@ -71,11 +71,10 @@ module ScoutApm
71
71
  reporting_periods.select { |time, rp| force || time.timestamp < current_timestamp.timestamp}.
72
72
  each { |time, rp|
73
73
  collect_samplers(rp)
74
- layaway.add_reporting_period(time, rp)
74
+ layaway.write_reporting_period(rp)
75
75
  reporting_periods.delete(time)
76
76
  }
77
77
  }
78
- ScoutApm::Agent.instance.logger.debug("Finished writing to layaway")
79
78
  end
80
79
 
81
80
  ######################################
@@ -108,8 +107,25 @@ module ScoutApm
108
107
  @timestamp = @raw_time.to_i - @raw_time.sec # The normalized time (integer) to compare by
109
108
  end
110
109
 
110
+ def self.minutes_ago(min, base_time=Time.now)
111
+ adjusted = base_time - (min * 60)
112
+ new(adjusted)
113
+ end
114
+
111
115
  def to_s
112
- Time.at(@timestamp).iso8601
116
+ strftime
117
+ end
118
+
119
+ def strftime(pattern=nil)
120
+ if pattern.nil?
121
+ to_time.iso8601
122
+ else
123
+ to_time.strftime(pattern)
124
+ end
125
+ end
126
+
127
+ def to_time
128
+ Time.at(@timestamp)
113
129
  end
114
130
 
115
131
  def eql?(o)
@@ -41,7 +41,8 @@ module ScoutApm
41
41
 
42
42
  BACKTRACE_THRESHOLD = 0.5 # the minimum threshold in seconds to record the backtrace for a metric.
43
43
 
44
- def initialize
44
+ def initialize(store)
45
+ @store = store #this is passed in so we can use a real store (normal operation) or fake store (instant mode only)
45
46
  @layers = []
46
47
  @call_set = Hash.new { |h, k| h[k] = CallSet.new }
47
48
  @annotations = {}
@@ -222,26 +223,26 @@ module ScoutApm
222
223
  # Update immediate and long-term histograms for both job and web requests
223
224
  if unique_name != :unknown
224
225
  ScoutApm::Agent.instance.request_histograms.add(unique_name, root_layer.total_call_time)
225
- ScoutApm::Agent.instance.request_histograms_by_time[ScoutApm::Agent.instance.store.current_timestamp].
226
+ ScoutApm::Agent.instance.request_histograms_by_time[@store.current_timestamp].
226
227
  add(unique_name, root_layer.total_call_time)
227
228
  end
228
229
 
229
230
  metrics = LayerConverters::MetricConverter.new(self).call
230
- ScoutApm::Agent.instance.store.track!(metrics)
231
+ @store.track!(metrics)
231
232
 
232
233
  error_metrics = LayerConverters::ErrorConverter.new(self).call
233
- ScoutApm::Agent.instance.store.track!(error_metrics)
234
+ @store.track!(error_metrics)
234
235
 
235
236
  allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
236
- ScoutApm::Agent.instance.store.track!(allocation_metrics)
237
+ @store.track!(allocation_metrics)
237
238
 
238
239
  if web?
239
240
  # Don't #call this - that's the job of the ScoredItemSet later.
240
241
  slow_converter = LayerConverters::SlowRequestConverter.new(self)
241
- ScoutApm::Agent.instance.store.track_slow_transaction!(slow_converter)
242
+ @store.track_slow_transaction!(slow_converter)
242
243
 
243
244
  queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call
244
- ScoutApm::Agent.instance.store.track!(queue_time_metrics)
245
+ @store.track!(queue_time_metrics)
245
246
 
246
247
  # If there's an instant_key, it means we need to report this right away
247
248
  if instant?
@@ -252,14 +253,14 @@ module ScoutApm
252
253
 
253
254
  if job?
254
255
  job_metrics = LayerConverters::JobConverter.new(self).call
255
- ScoutApm::Agent.instance.store.track_job!(job_metrics)
256
+ @store.track_job!(job_metrics)
256
257
 
257
258
  job_converter = LayerConverters::SlowJobConverter.new(self)
258
- ScoutApm::Agent.instance.store.track_slow_job!(job_converter)
259
+ @store.track_slow_job!(job_converter)
259
260
  end
260
261
 
261
262
  allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
262
- ScoutApm::Agent.instance.store.track!(allocation_metrics)
263
+ @store.track!(allocation_metrics)
263
264
 
264
265
  end
265
266
 
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "2.0.0.pre3"
2
+ VERSION = "2.0.0.pre4"
3
3
  end
4
4
 
data/lib/scout_apm.rb CHANGED
@@ -109,6 +109,7 @@ require 'scout_apm/bucket_name_splitter'
109
109
  require 'scout_apm/stack_item'
110
110
  require 'scout_apm/metric_set'
111
111
  require 'scout_apm/store'
112
+ require 'scout_apm/fake_store'
112
113
  require 'scout_apm/tracer'
113
114
  require 'scout_apm/context'
114
115
  require 'scout_apm/instant_reporting'
@@ -138,6 +139,8 @@ require 'scout_apm/serializers/deploy_serializer'
138
139
 
139
140
  require 'scout_apm/middleware'
140
141
 
142
+ require 'scout_apm/instant/middleware'
143
+
141
144
  if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR >= 3 && defined?(Rails::Railtie)
142
145
  module ScoutApm
143
146
  class Railtie < Rails::Railtie
@@ -152,6 +155,13 @@ if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR
152
155
 
153
156
  end
154
157
  end
158
+ class Railtie < Rails::Railtie
159
+ initializer "scout_apm.start" do |app|
160
+ if Rails.env.development?
161
+ app.middleware.use ScoutApm::Instant::Middleware
162
+ end
163
+ end
164
+ end
155
165
  end
156
166
  else
157
167
  ScoutApm::Agent.instance.start
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre3
4
+ version: 2.0.0.pre4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-06-16 00:00:00.000000000 Z
12
+ date: 2016-06-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rusage
@@ -106,6 +106,7 @@ files:
106
106
  - ".gitignore"
107
107
  - CHANGELOG.markdown
108
108
  - Gemfile
109
+ - LICENSE.md
109
110
  - README.markdown
110
111
  - Rakefile
111
112
  - data/cacert.pem
@@ -130,11 +131,14 @@ files:
130
131
  - lib/scout_apm/deploy_integrations/capistrano_3.cap
131
132
  - lib/scout_apm/deploy_integrations/capistrano_3.rb
132
133
  - lib/scout_apm/environment.rb
134
+ - lib/scout_apm/fake_store.rb
133
135
  - lib/scout_apm/framework_integrations/rails_2.rb
134
136
  - lib/scout_apm/framework_integrations/rails_3_or_4.rb
135
137
  - lib/scout_apm/framework_integrations/ruby.rb
136
138
  - lib/scout_apm/framework_integrations/sinatra.rb
137
139
  - lib/scout_apm/histogram.rb
140
+ - lib/scout_apm/instant/assets/xmlhttp_instrumentation.html
141
+ - lib/scout_apm/instant/middleware.rb
138
142
  - lib/scout_apm/instant_reporting.rb
139
143
  - lib/scout_apm/instruments/.DS_Store
140
144
  - lib/scout_apm/instruments/action_controller_rails_2.rb