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 +4 -4
- data/.gitignore +1 -1
- data/CHANGELOG.markdown +9 -0
- data/LICENSE.md +9 -0
- data/lib/scout_apm/agent/reporting.rb +24 -6
- data/lib/scout_apm/agent.rb +5 -1
- data/lib/scout_apm/config.rb +24 -7
- data/lib/scout_apm/fake_store.rb +35 -0
- data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +35 -0
- data/lib/scout_apm/instant/middleware.rb +98 -0
- data/lib/scout_apm/layaway.rb +134 -31
- data/lib/scout_apm/layaway_file.rb +27 -74
- data/lib/scout_apm/layer.rb +6 -4
- data/lib/scout_apm/request_manager.rb +7 -1
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +8 -4
- data/lib/scout_apm/server_integrations/unicorn.rb +1 -3
- data/lib/scout_apm/slow_transaction.rb +1 -2
- data/lib/scout_apm/store.rb +19 -3
- data/lib/scout_apm/tracked_request.rb +11 -10
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +10 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 550d7e521d6f822cdc1acdb002d8e770e42e37ae
|
4
|
+
data.tar.gz: fceea7f4780ab49b1a47f674f3d719a8a5e81b8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc456fb9984ee49cb6cefdf496cd8fb1854f7bd69c905e2775d3563adc902e8e105168948c13dd1105af06616ff63358db2ba8dc21393cf651f1066c49eaaa03
|
7
|
+
data.tar.gz: c8b63ebc3304cfff04b1769bbcb66740410fcdcb9b6a06dc58e155e88ae418b2ff0bd7ecedcd65570eb36fe675c5ce8340dff3f9b810b945c5bdd8da22682eac
|
data/.gitignore
CHANGED
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
data/lib/scout_apm/agent.rb
CHANGED
@@ -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
|
|
data/lib/scout_apm/config.rb
CHANGED
@@ -55,8 +55,8 @@ module ScoutApm
|
|
55
55
|
|
56
56
|
def value(key)
|
57
57
|
@overlays.each do |overlay|
|
58
|
-
if
|
59
|
-
return
|
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[
|
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.
|
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.
|
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.
|
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.
|
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
|
data/lib/scout_apm/layaway.rb
CHANGED
@@ -1,53 +1,156 @@
|
|
1
|
-
# Stores StoreReportingPeriod objects in a file before sending them to the server.
|
2
|
-
#
|
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
|
-
|
10
|
+
# How old a file needs to be in Seconds before it gets reported.
|
11
|
+
REPORTING_AGE = 120
|
6
12
|
|
7
|
-
|
8
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
106
|
+
private
|
107
|
+
|
108
|
+
##########################################
|
109
|
+
# Looking up files
|
110
|
+
|
111
|
+
def file_for(timestamp)
|
112
|
+
glob_pattern(timestamp)
|
113
|
+
end
|
36
114
|
|
37
|
-
|
115
|
+
def all_files_for(timestamp)
|
116
|
+
Dir[glob_pattern(timestamp, :all)]
|
117
|
+
end
|
38
118
|
|
39
|
-
|
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
|
-
|
42
|
-
|
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
|
-
|
138
|
+
def format_pid(pid)
|
139
|
+
if pid == :all
|
140
|
+
"*"
|
141
|
+
else
|
142
|
+
pid.to_s
|
143
|
+
end
|
46
144
|
end
|
47
145
|
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
#
|
1
|
+
# A single layaway file. See Layaway for the management of the group of files.
|
2
2
|
module ScoutApm
|
3
3
|
class LayawayFile
|
4
|
-
|
5
|
-
return @path if @path
|
4
|
+
attr_reader :path
|
6
5
|
|
7
|
-
|
8
|
-
|
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
|
34
|
-
|
35
|
-
|
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
|
50
|
-
|
51
|
-
|
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
|
75
|
-
data
|
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
|
82
|
-
|
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
|
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
|
+
|
data/lib/scout_apm/layer.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
25
|
-
|
26
|
-
|
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]
|
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
|
data/lib/scout_apm/store.rb
CHANGED
@@ -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.
|
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
|
-
|
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[
|
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
|
-
|
231
|
+
@store.track!(metrics)
|
231
232
|
|
232
233
|
error_metrics = LayerConverters::ErrorConverter.new(self).call
|
233
|
-
|
234
|
+
@store.track!(error_metrics)
|
234
235
|
|
235
236
|
allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
|
236
|
-
|
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
|
-
|
242
|
+
@store.track_slow_transaction!(slow_converter)
|
242
243
|
|
243
244
|
queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call
|
244
|
-
|
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
|
-
|
256
|
+
@store.track_job!(job_metrics)
|
256
257
|
|
257
258
|
job_converter = LayerConverters::SlowJobConverter.new(self)
|
258
|
-
|
259
|
+
@store.track_slow_job!(job_converter)
|
259
260
|
end
|
260
261
|
|
261
262
|
allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
|
262
|
-
|
263
|
+
@store.track!(allocation_metrics)
|
263
264
|
|
264
265
|
end
|
265
266
|
|
data/lib/scout_apm/version.rb
CHANGED
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.
|
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-
|
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
|