newrelic_rpm 2.12.3 → 2.13.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of newrelic_rpm might be problematic. Click here for more details.
- data/CHANGELOG +24 -2
- data/README.rdoc +172 -0
- data/bin/newrelic +13 -0
- data/bin/newrelic_cmd +2 -1
- data/install.rb +8 -45
- data/lib/new_relic/agent.rb +43 -30
- data/lib/new_relic/agent/agent.rb +699 -631
- data/lib/new_relic/agent/busy_calculator.rb +81 -81
- data/lib/new_relic/agent/error_collector.rb +9 -6
- data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +2 -2
- data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +10 -5
- data/lib/new_relic/agent/instrumentation/data_mapper.rb +13 -45
- data/lib/new_relic/agent/instrumentation/memcache.rb +15 -4
- data/lib/new_relic/agent/instrumentation/metric_frame.rb +37 -29
- data/lib/new_relic/agent/instrumentation/rack.rb +20 -34
- data/lib/new_relic/agent/method_tracer.rb +9 -9
- data/lib/new_relic/agent/samplers/delayed_job_lock_sampler.rb +31 -31
- data/lib/new_relic/agent/stats_engine/metric_stats.rb +5 -5
- data/lib/new_relic/agent/stats_engine/samplers.rb +3 -0
- data/lib/new_relic/agent/transaction_sampler.rb +31 -15
- data/lib/new_relic/agent/worker_loop.rb +29 -28
- data/lib/new_relic/collection_helper.rb +4 -2
- data/lib/new_relic/command.rb +85 -0
- data/lib/new_relic/commands/deployments.rb +74 -114
- data/lib/new_relic/commands/install.rb +81 -0
- data/lib/new_relic/control.rb +54 -379
- data/lib/new_relic/control/configuration.rb +149 -0
- data/lib/new_relic/control/{external.rb → frameworks/external.rb} +2 -2
- data/lib/new_relic/control/{merb.rb → frameworks/merb.rb} +1 -1
- data/lib/new_relic/control/frameworks/rails.rb +126 -0
- data/lib/new_relic/control/{rails3.rb → frameworks/rails3.rb} +11 -10
- data/lib/new_relic/control/{ruby.rb → frameworks/ruby.rb} +1 -1
- data/lib/new_relic/control/{sinatra.rb → frameworks/sinatra.rb} +2 -2
- data/lib/new_relic/control/instrumentation.rb +84 -0
- data/lib/new_relic/control/logging_methods.rb +74 -0
- data/lib/new_relic/control/profiling.rb +24 -0
- data/lib/new_relic/control/server_methods.rb +88 -0
- data/lib/new_relic/local_environment.rb +3 -3
- data/lib/new_relic/metric_parser.rb +13 -2
- data/lib/new_relic/metric_parser/active_record.rb +4 -1
- data/lib/new_relic/metric_parser/apdex.rb +53 -0
- data/lib/new_relic/metric_parser/controller.rb +13 -5
- data/lib/new_relic/metric_parser/mem_cache.rb +1 -1
- data/lib/new_relic/metric_parser/other_transaction.rb +21 -0
- data/lib/new_relic/metric_spec.rb +3 -3
- data/lib/new_relic/noticed_error.rb +1 -1
- data/lib/new_relic/rack/developer_mode.rb +257 -0
- data/lib/new_relic/rack/metric_app.rb +56 -50
- data/lib/new_relic/rack/mongrel_rpm.ru +7 -6
- data/lib/new_relic/rack/newrelic.yml +4 -3
- data/lib/new_relic/rack_app.rb +2 -1
- data/lib/new_relic/recipes.rb +7 -7
- data/lib/new_relic/stats.rb +6 -14
- data/lib/new_relic/timer_lib.rb +27 -0
- data/lib/new_relic/transaction_analysis.rb +2 -7
- data/lib/new_relic/transaction_sample.rb +17 -85
- data/lib/new_relic/url_rule.rb +14 -0
- data/lib/new_relic/version.rb +3 -3
- data/lib/newrelic_rpm.rb +5 -9
- data/newrelic.yml +26 -9
- data/newrelic_rpm.gemspec +67 -32
- data/test/config/newrelic.yml +5 -0
- data/test/config/test_control.rb +6 -8
- data/test/new_relic/agent/active_record_instrumentation_test.rb +5 -6
- data/test/new_relic/agent/agent_controller_test.rb +18 -4
- data/test/new_relic/agent/agent_test_controller.rb +1 -6
- data/test/new_relic/agent/busy_calculator_test.rb +2 -0
- data/test/new_relic/agent/collection_helper_test.rb +6 -6
- data/test/new_relic/agent/error_collector_test.rb +13 -21
- data/test/new_relic/agent/metric_data_test.rb +3 -6
- data/test/new_relic/agent/rpm_agent_test.rb +121 -117
- data/test/new_relic/agent/task_instrumentation_test.rb +128 -133
- data/test/new_relic/agent/transaction_sample_test.rb +176 -170
- data/test/new_relic/agent/worker_loop_test.rb +24 -18
- data/test/new_relic/control_test.rb +13 -3
- data/test/new_relic/deployments_api_test.rb +7 -7
- data/test/new_relic/environment_test.rb +1 -1
- data/test/new_relic/metric_parser_test.rb +58 -4
- data/test/new_relic/rack/episodes_test.rb +317 -0
- data/test/new_relic/stats_test.rb +3 -2
- data/test/test_contexts.rb +28 -0
- data/test/test_helper.rb +24 -5
- data/ui/helpers/{newrelic_helper.rb → developer_mode_helper.rb} +63 -23
- data/ui/views/layouts/newrelic_default.rhtml +6 -6
- data/ui/views/newrelic/_explain_plans.rhtml +4 -4
- data/ui/views/newrelic/_sample.rhtml +18 -17
- data/ui/views/newrelic/_segment.rhtml +1 -0
- data/ui/views/newrelic/_segment_row.rhtml +8 -8
- data/ui/views/newrelic/_show_sample_detail.rhtml +1 -1
- data/ui/views/newrelic/_show_sample_sql.rhtml +2 -2
- data/ui/views/newrelic/_sql_row.rhtml +8 -3
- data/ui/views/newrelic/_stack_trace.rhtml +9 -24
- data/ui/views/newrelic/_table.rhtml +1 -1
- data/ui/views/newrelic/explain_sql.rhtml +3 -2
- data/ui/views/newrelic/{images → file/images}/arrow-close.png +0 -0
- data/ui/views/newrelic/{images → file/images}/arrow-open.png +0 -0
- data/ui/views/newrelic/{images → file/images}/blue_bar.gif +0 -0
- data/ui/views/newrelic/{images → file/images}/file_icon.png +0 -0
- data/ui/views/newrelic/{images → file/images}/gray_bar.gif +0 -0
- data/ui/views/newrelic/{images → file/images}/new-relic-rpm-desktop.gif +0 -0
- data/ui/views/newrelic/{images → file/images}/new_relic_rpm_desktop.gif +0 -0
- data/ui/views/newrelic/{images → file/images}/textmate.png +0 -0
- data/ui/views/newrelic/file/javascript/jquery-1.4.2.js +6240 -0
- data/ui/views/newrelic/file/javascript/transaction_sample.js +120 -0
- data/ui/views/newrelic/{stylesheets → file/stylesheets}/style.css +0 -0
- data/ui/views/newrelic/index.rhtml +7 -5
- data/ui/views/newrelic/sample_not_found.rhtml +1 -1
- data/ui/views/newrelic/show_sample.rhtml +5 -6
- metadata +92 -34
- data/README.md +0 -138
- data/lib/new_relic/commands/new_relic_commands.rb +0 -30
- data/lib/new_relic/control/rails.rb +0 -151
- data/test/new_relic/agent/mock_ar_connection.rb +0 -40
- data/test/ui/newrelic_controller_test.rb +0 -14
- data/test/ui/newrelic_helper_test.rb +0 -53
- data/ui/controllers/newrelic_controller.rb +0 -220
- data/ui/views/newrelic/javascript/prototype-scriptaculous.js +0 -7288
- data/ui/views/newrelic/javascript/transaction_sample.js +0 -107
@@ -0,0 +1,24 @@
|
|
1
|
+
module NewRelic
|
2
|
+
class Control
|
3
|
+
module Profiling
|
4
|
+
|
5
|
+
# A flag used in dev mode to indicate if profiling is available
|
6
|
+
def profiling?
|
7
|
+
@profiling
|
8
|
+
end
|
9
|
+
|
10
|
+
def profiling_available?
|
11
|
+
@profiling_available ||=
|
12
|
+
begin
|
13
|
+
require 'ruby-prof'
|
14
|
+
true
|
15
|
+
rescue LoadError; end
|
16
|
+
end
|
17
|
+
# Set the flag for capturing profiles in dev mode. If RubyProf is not
|
18
|
+
# loaded a true value is ignored.
|
19
|
+
def profiling=(val)
|
20
|
+
@profiling = profiling_available? && val && defined?(RubyProf)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module NewRelic
|
2
|
+
class Control
|
3
|
+
|
4
|
+
# Structs holding info for the remote server and proxy server
|
5
|
+
class Server < Struct.new :name, :port, :ip #:nodoc:
|
6
|
+
def to_s; "#{name}:#{port}"; end
|
7
|
+
end
|
8
|
+
|
9
|
+
ProxyServer = Struct.new :name, :port, :user, :password #:nodoc:
|
10
|
+
|
11
|
+
module ServerMethods
|
12
|
+
|
13
|
+
def server
|
14
|
+
@remote_server ||= server_from_host(nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
def api_server
|
18
|
+
api_host = self['api_host'] || 'rpm.newrelic.com'
|
19
|
+
@api_server ||=
|
20
|
+
NewRelic::Control::Server.new \
|
21
|
+
api_host,
|
22
|
+
(self['api_port'] || self['port'] || (use_ssl? ? 443 : 80)).to_i,
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def proxy_server
|
27
|
+
@proxy_server ||=
|
28
|
+
NewRelic::Control::ProxyServer.new self['proxy_host'], self['proxy_port'], self['proxy_user'], self['proxy_pass']
|
29
|
+
end
|
30
|
+
|
31
|
+
def server_from_host(hostname=nil)
|
32
|
+
host = hostname || self['host'] || 'collector.newrelic.com'
|
33
|
+
|
34
|
+
# if the host is not an IP address, turn it into one
|
35
|
+
NewRelic::Control::Server.new host, (self['port'] || (use_ssl? ? 443 : 80)).to_i, convert_to_ip_address(host)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Look up the ip address of the host using the pure ruby lookup
|
39
|
+
# to prevent blocking. If that fails, fall back to the regular
|
40
|
+
# IPSocket library. Return nil if we can't find the host ip
|
41
|
+
# address and don't have a good default.
|
42
|
+
def convert_to_ip_address(host)
|
43
|
+
# here we leave it as a host name since the cert verification
|
44
|
+
# needs it in host form
|
45
|
+
return host if verify_certificate?
|
46
|
+
return nil if host.nil? || host.downcase == "localhost"
|
47
|
+
# Fall back to known ip address in the common case
|
48
|
+
ip_address = '65.74.177.195' if host.downcase == 'collector.newrelic.com'
|
49
|
+
begin
|
50
|
+
ip_address = Resolv.getaddress(host)
|
51
|
+
log.info "Resolved #{host} to #{ip_address}"
|
52
|
+
rescue => e
|
53
|
+
log.warn "DNS Error caching IP address: #{e}"
|
54
|
+
log.debug e.backtrace.join("\n ")
|
55
|
+
ip_address = IPSocket::getaddress host rescue ip_address
|
56
|
+
end
|
57
|
+
ip_address
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return the Net::HTTP with proxy configuration given the NewRelic::Control::Server object.
|
61
|
+
# Default is the collector but for api calls you need to pass api_server
|
62
|
+
#
|
63
|
+
# Experimental support for SSL verification:
|
64
|
+
# swap 'VERIFY_NONE' for 'VERIFY_PEER' line to try it out
|
65
|
+
# If verification fails, uncomment the 'http.ca_file' line
|
66
|
+
# and it will use the included certificate.
|
67
|
+
def http_connection(host = nil)
|
68
|
+
host ||= server
|
69
|
+
# Proxy returns regular HTTP if @proxy_host is nil (the default)
|
70
|
+
http_class = Net::HTTP::Proxy(proxy_server.name, proxy_server.port,
|
71
|
+
proxy_server.user, proxy_server.password)
|
72
|
+
http = http_class.new(host.ip || host.name, host.port)
|
73
|
+
log.debug("Http Connection opened to #{host.ip||host.name}:#{host.port}")
|
74
|
+
if use_ssl?
|
75
|
+
http.use_ssl = true
|
76
|
+
if verify_certificate?
|
77
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
78
|
+
http.ca_file = File.join(File.dirname(__FILE__), '..', '..', 'cert', 'cacert.pem')
|
79
|
+
else
|
80
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
81
|
+
end
|
82
|
+
end
|
83
|
+
http
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
@@ -152,10 +152,10 @@ module NewRelic
|
|
152
152
|
@unicorn
|
153
153
|
end
|
154
154
|
|
155
|
-
# Obsolete method for DelayedJob instrumentation support
|
156
|
-
#
|
155
|
+
# Obsolete method for DelayedJob instrumentation support. Now all DJ instrumentation
|
156
|
+
# is bundled in the newrelic_rpm gem and nobody should be invoking this method.
|
157
157
|
def delayed_worker=(worker)
|
158
|
-
$stderr.puts "WARNING:
|
158
|
+
$stderr.puts "WARNING: obsolete call to delayed_worker=(worker). Please remove custom DJ instrumentation."
|
159
159
|
end
|
160
160
|
|
161
161
|
private
|
@@ -67,7 +67,7 @@ module NewRelic
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def apdex_metric_path
|
70
|
-
|
70
|
+
%Q[Apdex/#{segments[1..-1].join('/')}]
|
71
71
|
end
|
72
72
|
|
73
73
|
# A short name for legends in the graphs
|
@@ -81,6 +81,8 @@ module NewRelic
|
|
81
81
|
nil
|
82
82
|
end
|
83
83
|
|
84
|
+
# Category is a UI description of the general
|
85
|
+
# category of metrics for this metric.
|
84
86
|
def category
|
85
87
|
segments[0]
|
86
88
|
end
|
@@ -112,7 +114,11 @@ module NewRelic
|
|
112
114
|
def url
|
113
115
|
''
|
114
116
|
end
|
115
|
-
|
117
|
+
# Return the list of dispatcher metrics that correspond to this metric. That is,
|
118
|
+
# the summary metrics which should also be recorded when this metric is recorded.
|
119
|
+
def summary_metrics
|
120
|
+
[]
|
121
|
+
end
|
116
122
|
# returns a hash of params for url_for(), giving you a drilldown URL to an RPM page for this metric
|
117
123
|
# define in subclasses - TB 2009-12-18
|
118
124
|
# def drilldown_url(metric_id); end
|
@@ -121,5 +127,10 @@ module NewRelic
|
|
121
127
|
@name = name
|
122
128
|
end
|
123
129
|
|
130
|
+
# These would be reflected properly by method missing; consider
|
131
|
+
# this an optimization
|
132
|
+
def is_controller?; false; end
|
133
|
+
def is_transaction?; false; end
|
134
|
+
|
124
135
|
end
|
125
136
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class NewRelic::MetricParser::Apdex < NewRelic::MetricParser
|
2
|
+
|
3
|
+
CLIENT = 'Client'
|
4
|
+
|
5
|
+
# Convenience method for creating the appropriate client
|
6
|
+
# metric name.
|
7
|
+
def self.client_metric(apdex_t)
|
8
|
+
"Apdex/#{CLIENT}/#{apdex_t}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_client?
|
12
|
+
segments[1] == CLIENT
|
13
|
+
end
|
14
|
+
def is_summary?
|
15
|
+
segments.size == 1
|
16
|
+
end
|
17
|
+
|
18
|
+
# Apdex/Client/N
|
19
|
+
def apdex_t
|
20
|
+
is_client? && segments[2].to_f
|
21
|
+
end
|
22
|
+
|
23
|
+
def developer_name
|
24
|
+
case
|
25
|
+
when is_client? then "Apdex Client (#{apdex_t})"
|
26
|
+
when is_summary? then "Apdex"
|
27
|
+
else "Apdex #{segments[1..-1].join("/")}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def short_name
|
32
|
+
# standard controller actions
|
33
|
+
if segments.length > 1
|
34
|
+
url
|
35
|
+
else
|
36
|
+
'All Frontend Urls'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def url
|
41
|
+
'/' + segments[1..-1].join('/')
|
42
|
+
end
|
43
|
+
|
44
|
+
# this is used to match transaction traces to controller actions.
|
45
|
+
# TT's don't have a preceding slash :P
|
46
|
+
def tt_path
|
47
|
+
segments[1..-1].join('/')
|
48
|
+
end
|
49
|
+
|
50
|
+
def call_rate_suffix
|
51
|
+
'rpm'
|
52
|
+
end
|
53
|
+
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
class NewRelic::MetricParser::Controller < NewRelic::MetricParser
|
2
2
|
|
3
|
-
def is_controller
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
def is_controller?; true; end
|
4
|
+
def is_transaction?; true; end
|
5
|
+
|
7
6
|
# If the controller name segments look like a file path, convert it to the controller
|
8
|
-
# class name. If it begins with a capital letter, assume it's already a class name
|
7
|
+
# class name. If it begins with a capital letter, assume it's already a class name.
|
8
|
+
# We only expect a lower case letter with Rails, so we'll be able to use camelize for
|
9
|
+
# that.
|
9
10
|
def controller_name
|
10
11
|
path = segments[1..-2].join('/')
|
11
12
|
path < 'a' ? path : path.camelize+"Controller"
|
@@ -23,6 +24,9 @@ class NewRelic::MetricParser::Controller < NewRelic::MetricParser
|
|
23
24
|
"#{controller_name}##{action_name}"
|
24
25
|
end
|
25
26
|
|
27
|
+
def is_web_transaction?
|
28
|
+
true
|
29
|
+
end
|
26
30
|
# return the cpu measuring equivalent. It may be nil since this metric was not
|
27
31
|
# present in earlier versions of the agent.
|
28
32
|
def cpu_metric
|
@@ -51,4 +55,8 @@ class NewRelic::MetricParser::Controller < NewRelic::MetricParser
|
|
51
55
|
def call_rate_suffix
|
52
56
|
'rpm'
|
53
57
|
end
|
58
|
+
|
59
|
+
def summary_metrics
|
60
|
+
%w[HttpDispatcher]
|
61
|
+
end
|
54
62
|
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
# OtherTransaction metrics must have at least three segments: /OtherTransaction/<task>/*
|
2
|
+
# Task is "Background", "Resque", "DelayedJob" etc.
|
2
3
|
|
3
4
|
class NewRelic::MetricParser::OtherTransaction < NewRelic::MetricParser
|
5
|
+
|
6
|
+
def is_transaction?
|
7
|
+
true
|
8
|
+
end
|
4
9
|
def task
|
5
10
|
segments[1]
|
6
11
|
end
|
@@ -9,7 +14,23 @@ class NewRelic::MetricParser::OtherTransaction < NewRelic::MetricParser
|
|
9
14
|
segments[2..-1].join(NewRelic::MetricParser::SEPARATOR)
|
10
15
|
end
|
11
16
|
|
17
|
+
def short_name
|
18
|
+
developer_name
|
19
|
+
end
|
20
|
+
|
12
21
|
def drilldown_url(metric_id)
|
13
22
|
{:controller => '/v2/background_tasks', :action => 'index', :task => task, :anchor => "id=#{metric_id}"}
|
14
23
|
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
segments[2..-1].join "/"
|
27
|
+
end
|
28
|
+
|
29
|
+
def summary_metrics
|
30
|
+
if segments.size > 2
|
31
|
+
%W[OtherTransaction/#{task}/all OtherTransaction/all]
|
32
|
+
else
|
33
|
+
[]
|
34
|
+
end
|
35
|
+
end
|
15
36
|
end
|
@@ -4,7 +4,7 @@ class NewRelic::MetricSpec
|
|
4
4
|
attr_accessor :name
|
5
5
|
attr_accessor :scope
|
6
6
|
|
7
|
-
MAX_LENGTH =
|
7
|
+
MAX_LENGTH = 255
|
8
8
|
# Need a "zero-arg" constructor so it can be instantiated from java (using
|
9
9
|
# jruby) for sending responses to ruby agents from the java collector.
|
10
10
|
#
|
@@ -14,8 +14,8 @@ class NewRelic::MetricSpec
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def truncate!
|
17
|
-
self.name = name[0...
|
18
|
-
self.scope = scope[0...
|
17
|
+
self.name = name[0...MAX_LENGTH] if name && name.size > MAX_LENGTH
|
18
|
+
self.scope = scope[0...MAX_LENGTH] if scope && scope.size > MAX_LENGTH
|
19
19
|
end
|
20
20
|
|
21
21
|
def ==(o)
|
@@ -7,7 +7,7 @@ class NewRelic::NoticedError
|
|
7
7
|
self.path = path
|
8
8
|
self.params = NewRelic::NoticedError.normalize_params(data)
|
9
9
|
|
10
|
-
self.exception_class = exception ? exception.class.name : '
|
10
|
+
self.exception_class = exception.is_a?(Exception) ? exception.class.name : 'Error'
|
11
11
|
|
12
12
|
if exception.respond_to?('original_exception')
|
13
13
|
self.message = exception.original_exception.message.to_s
|
@@ -0,0 +1,257 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/request'
|
3
|
+
require 'rack/response'
|
4
|
+
require 'rack/file'
|
5
|
+
|
6
|
+
module NewRelic::Rack
|
7
|
+
class DeveloperMode
|
8
|
+
|
9
|
+
VIEW_PATH = File.expand_path('../../../../ui/views/', __FILE__)
|
10
|
+
HELPER_PATH = File.expand_path('../../../../ui/helpers/', __FILE__)
|
11
|
+
require File.join(HELPER_PATH, 'developer_mode_helper.rb')
|
12
|
+
|
13
|
+
include NewRelic::DeveloperModeHelper
|
14
|
+
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
return @app.call(env) unless /^\/newrelic/ =~ Rack::Request.new(env).path_info
|
21
|
+
dup._call(env)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def _call(env)
|
27
|
+
@req = Rack::Request.new(env)
|
28
|
+
@rendered = false
|
29
|
+
case @req.path_info
|
30
|
+
when /profile/
|
31
|
+
profile
|
32
|
+
when /file/
|
33
|
+
Rack::File.new(VIEW_PATH).call(env)
|
34
|
+
when /index/
|
35
|
+
index
|
36
|
+
when /threads/
|
37
|
+
threads
|
38
|
+
when /reset/
|
39
|
+
reset
|
40
|
+
when /show_sample_detail/
|
41
|
+
show_sample_data
|
42
|
+
when /show_sample_summary/
|
43
|
+
show_sample_data
|
44
|
+
when /show_sample_sql/
|
45
|
+
show_sample_data
|
46
|
+
when /explain_sql/
|
47
|
+
explain_sql
|
48
|
+
when /show_source/
|
49
|
+
show_source
|
50
|
+
when /^\/newrelic\/?$/
|
51
|
+
index
|
52
|
+
else
|
53
|
+
@app.call(env)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def index
|
60
|
+
get_samples
|
61
|
+
render(:index)
|
62
|
+
end
|
63
|
+
|
64
|
+
def reset
|
65
|
+
NewRelic::Agent.instance.transaction_sampler.reset!
|
66
|
+
Rack::Response.new{|r| r.redirect('/newrelic/')}.finish
|
67
|
+
end
|
68
|
+
|
69
|
+
def explain_sql
|
70
|
+
get_segment
|
71
|
+
|
72
|
+
return render(:sample_not_found) unless @sample
|
73
|
+
|
74
|
+
@sql = @segment[:sql]
|
75
|
+
@trace = @segment[:backtrace]
|
76
|
+
|
77
|
+
if NewRelic::Agent.agent.record_sql == :obfuscated
|
78
|
+
@obfuscated_sql = @segment.obfuscated_sql
|
79
|
+
end
|
80
|
+
|
81
|
+
explanations = @segment.explain_sql
|
82
|
+
if explanations
|
83
|
+
@explanation = explanations.first
|
84
|
+
if !@explanation.blank?
|
85
|
+
first_row = @explanation.first
|
86
|
+
# Show the standard headers if it looks like a mysql explain plan
|
87
|
+
# Otherwise show blank headers
|
88
|
+
if first_row.length < NewRelic::MYSQL_EXPLAIN_COLUMNS.length
|
89
|
+
@row_headers = nil
|
90
|
+
else
|
91
|
+
@row_headers = NewRelic::MYSQL_EXPLAIN_COLUMNS
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
render(:explain_sql)
|
96
|
+
end
|
97
|
+
|
98
|
+
def profile
|
99
|
+
NewRelic::Control.instance.profiling = params['start'] == 'true'
|
100
|
+
index
|
101
|
+
end
|
102
|
+
|
103
|
+
def threads
|
104
|
+
render(:threads)
|
105
|
+
end
|
106
|
+
|
107
|
+
def render(view, layout=true)
|
108
|
+
add_rack_array = true
|
109
|
+
if view.is_a? Hash
|
110
|
+
layout = false
|
111
|
+
if view[:object]
|
112
|
+
object = view[:object]
|
113
|
+
end
|
114
|
+
|
115
|
+
if view[:collection]
|
116
|
+
return view[:collection].map do |object|
|
117
|
+
render({:partial => view[:partial], :object => object})
|
118
|
+
end.join(' ')
|
119
|
+
end
|
120
|
+
|
121
|
+
if view[:partial]
|
122
|
+
add_rack_array = false
|
123
|
+
view = "_#{view[:partial]}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
binding = Proc.new {}
|
127
|
+
if layout
|
128
|
+
body = render_with_layout(view) do
|
129
|
+
render_without_layout(view, binding)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
body = render_without_layout(view, binding)
|
133
|
+
end
|
134
|
+
if add_rack_array
|
135
|
+
Rack::Response.new(body).finish
|
136
|
+
else
|
137
|
+
body
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# You have to call this with a block - the contents returned from
|
142
|
+
# that block are interpolated into the layout
|
143
|
+
def render_with_layout(view)
|
144
|
+
body = ERB.new(File.read(File.join(VIEW_PATH, 'layouts/newrelic_default.rhtml')))
|
145
|
+
body.result(Proc.new {})
|
146
|
+
end
|
147
|
+
|
148
|
+
# you have to pass a binding to this (a proc) so that ERB can have
|
149
|
+
# access to helper functions and local variables
|
150
|
+
def render_without_layout(view, binding)
|
151
|
+
ERB.new(File.read(File.join(VIEW_PATH, 'newrelic', view.to_s + '.rhtml')), nil, nil, 'frobnitz').result(binding)
|
152
|
+
end
|
153
|
+
|
154
|
+
def content_tag(tag, contents, opts={})
|
155
|
+
opt_values = opts.map {|k, v| "#{k}=\"#{v}\"" }.join(' ')
|
156
|
+
"<#{tag} #{opt_values}>#{contents}</#{tag}>"
|
157
|
+
end
|
158
|
+
|
159
|
+
def sample
|
160
|
+
@sample || @samples[0]
|
161
|
+
end
|
162
|
+
|
163
|
+
def params
|
164
|
+
@req.params
|
165
|
+
end
|
166
|
+
|
167
|
+
def segment
|
168
|
+
@segment
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# show the selected source file with the highlighted selected line
|
173
|
+
def show_source
|
174
|
+
@filename = params['file']
|
175
|
+
line_number = params['line'].to_i
|
176
|
+
|
177
|
+
if !File.readable?(@filename)
|
178
|
+
@source="<p>Unable to read #{@filename}.</p>"
|
179
|
+
return
|
180
|
+
end
|
181
|
+
begin
|
182
|
+
file = File.new(@filename, 'r')
|
183
|
+
rescue => e
|
184
|
+
@source="<p>Unable to access the source file #{@filename} (#{e.message}).</p>"
|
185
|
+
return
|
186
|
+
end
|
187
|
+
@source = ""
|
188
|
+
|
189
|
+
@source << "<pre>"
|
190
|
+
file.each_line do |line|
|
191
|
+
# place an anchor 6 lines above the selected line (if the line # < 6)
|
192
|
+
if file.lineno == line_number - 6
|
193
|
+
@source << "</pre><pre id = 'selected_line'>"
|
194
|
+
@source << line.rstrip
|
195
|
+
@source << "</pre><pre>"
|
196
|
+
|
197
|
+
# highlight the selected line
|
198
|
+
elsif file.lineno == line_number
|
199
|
+
@source << "</pre><pre class = 'selected_source_line'>"
|
200
|
+
@source << line.rstrip
|
201
|
+
@source << "</pre><pre>"
|
202
|
+
else
|
203
|
+
@source << line
|
204
|
+
end
|
205
|
+
end
|
206
|
+
render(:show_source)
|
207
|
+
end
|
208
|
+
|
209
|
+
def show_sample_data
|
210
|
+
get_sample
|
211
|
+
|
212
|
+
return render(:sample_not_found) unless @sample
|
213
|
+
|
214
|
+
@request_params = @sample.params['request_params'] || {}
|
215
|
+
@custom_params = @sample.params['custom_params'] || {}
|
216
|
+
|
217
|
+
controller_metric = @sample.root_segment.called_segments.first.metric_name
|
218
|
+
|
219
|
+
metric_parser = NewRelic::MetricParser.for_metric_named controller_metric
|
220
|
+
@sample_controller_name = metric_parser.controller_name
|
221
|
+
@sample_action_name = metric_parser.action_name
|
222
|
+
|
223
|
+
render(:show_sample)
|
224
|
+
end
|
225
|
+
|
226
|
+
def get_samples
|
227
|
+
@samples = NewRelic::Agent.instance.transaction_sampler.samples.select do |sample|
|
228
|
+
sample.params[:path] != nil
|
229
|
+
end
|
230
|
+
|
231
|
+
return @samples = @samples.sort{|x,y| y.omit_segments_with('(Rails/Application Code Loading)|(Database/.*/.+ Columns)').duration <=>
|
232
|
+
x.omit_segments_with('(Rails/Application Code Loading)|(Database/.*/.+ Columns)').duration} if params['h']
|
233
|
+
return @samples = @samples.sort{|x,y| x.params[:uri] <=> y.params[:uri]} if params['u']
|
234
|
+
@samples = @samples.reverse
|
235
|
+
end
|
236
|
+
|
237
|
+
def get_sample
|
238
|
+
get_samples
|
239
|
+
id = params['id']
|
240
|
+
sample_id = id.to_i
|
241
|
+
@samples.each do |s|
|
242
|
+
if s.sample_id == sample_id
|
243
|
+
@sample = stripped_sample(s)
|
244
|
+
return
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def get_segment
|
250
|
+
get_sample
|
251
|
+
return unless @sample
|
252
|
+
|
253
|
+
segment_id = params['segment'].to_i
|
254
|
+
@segment = @sample.find_segment(segment_id)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|