newrelic_rpm 3.0.1 → 3.1.0.beta1
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 +2 -3
- data/README.rdoc +3 -3
- data/lib/new_relic/agent.rb +19 -7
- data/lib/new_relic/agent/agent.rb +83 -19
- data/lib/new_relic/agent/beacon_configuration.rb +8 -12
- data/lib/new_relic/agent/browser_monitoring.rb +8 -8
- data/lib/new_relic/agent/error_collector.rb +13 -13
- data/lib/new_relic/agent/instrumentation.rb +9 -0
- data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +10 -2
- data/lib/new_relic/agent/instrumentation/metric_frame.rb +41 -35
- data/lib/new_relic/agent/instrumentation/metric_frame/pop.rb +92 -0
- data/lib/new_relic/agent/method_tracer.rb +0 -2
- data/lib/new_relic/agent/shim_agent.rb +2 -0
- data/lib/new_relic/agent/stats_engine/metric_stats.rb +89 -60
- data/lib/new_relic/agent/stats_engine/transactions.rb +1 -1
- data/lib/new_relic/agent/worker_loop.rb +1 -1
- data/lib/new_relic/collection_helper.rb +0 -2
- data/lib/new_relic/control/class_methods.rb +25 -12
- data/lib/new_relic/control/logging_methods.rb +30 -17
- data/lib/new_relic/data_serialization.rb +81 -0
- data/lib/new_relic/local_environment.rb +1 -1
- data/lib/new_relic/metric_data.rb +9 -5
- data/lib/new_relic/metric_spec.rb +7 -1
- data/lib/new_relic/rack/browser_monitoring.rb +1 -7
- data/lib/new_relic/stats.rb +4 -0
- data/lib/new_relic/transaction_analysis.rb +45 -88
- data/lib/new_relic/transaction_analysis/segment_summary.rb +47 -0
- data/lib/new_relic/transaction_sample.rb +15 -332
- data/lib/new_relic/transaction_sample/composite_segment.rb +27 -0
- data/lib/new_relic/transaction_sample/fake_segment.rb +9 -0
- data/lib/new_relic/transaction_sample/segment.rb +250 -0
- data/lib/new_relic/transaction_sample/summary_segment.rb +21 -0
- data/lib/new_relic/version.rb +3 -3
- data/newrelic.yml +3 -3
- data/newrelic_rpm.gemspec +27 -4
- data/test/active_record_fixtures.rb +31 -13
- data/test/new_relic/agent/agent/start_worker_thread_test.rb +1 -3
- data/test/new_relic/agent/agent_test.rb +73 -28
- data/test/new_relic/agent/agent_test_controller_test.rb +11 -10
- data/test/new_relic/agent/beacon_configuration_test.rb +37 -20
- data/test/new_relic/agent/browser_monitoring_test.rb +17 -28
- data/test/new_relic/agent/error_collector/notice_error_test.rb +9 -7
- data/test/new_relic/agent/error_collector_test.rb +6 -7
- data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +12 -5
- data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +195 -0
- data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +60 -58
- data/test/new_relic/agent/instrumentation/queue_time_test.rb +14 -0
- data/test/new_relic/agent/instrumentation/rack_test.rb +35 -0
- data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +0 -1
- data/test/new_relic/agent/method_tracer_test.rb +8 -8
- data/test/new_relic/agent/sampler_test.rb +19 -0
- data/test/new_relic/agent/shim_agent_test.rb +20 -0
- data/test/new_relic/agent/stats_engine/metric_stats/harvest_test.rb +150 -0
- data/test/new_relic/agent/stats_engine/metric_stats_test.rb +1 -0
- data/test/new_relic/agent/stats_engine/samplers_test.rb +4 -3
- data/test/new_relic/agent/{stats_engine/stats_engine_test.rb → stats_engine_test.rb} +8 -8
- data/test/new_relic/agent/transaction_sampler_test.rb +1 -1
- data/test/new_relic/agent/worker_loop_test.rb +2 -2
- data/test/new_relic/control/class_methods_test.rb +62 -0
- data/test/new_relic/control/logging_methods_test.rb +157 -0
- data/test/new_relic/control_test.rb +10 -10
- data/test/new_relic/data_serialization_test.rb +50 -0
- data/test/new_relic/local_environment_test.rb +13 -13
- data/test/new_relic/metric_data_test.rb +125 -0
- data/test/new_relic/metric_spec_test.rb +8 -0
- data/test/new_relic/transaction_analysis/segment_summary_test.rb +77 -0
- data/test/new_relic/transaction_analysis_test.rb +121 -0
- data/test/new_relic/transaction_sample/composite_segment_test.rb +35 -0
- data/test/new_relic/transaction_sample/fake_segment_test.rb +17 -0
- data/test/new_relic/transaction_sample/segment_test.rb +454 -0
- data/test/new_relic/transaction_sample/summary_segment_test.rb +31 -0
- data/test/new_relic/transaction_sample_test.rb +51 -0
- data/test/test_helper.rb +4 -14
- metadata +32 -7
@@ -25,7 +25,7 @@ module NewRelic
|
|
25
25
|
# that runs this worker loop. This will run the task immediately.
|
26
26
|
def run(period=nil, &block)
|
27
27
|
@period = period if period
|
28
|
-
@next_invocation_time = (Time.now + period)
|
28
|
+
@next_invocation_time = (Time.now + @period)
|
29
29
|
@task = block
|
30
30
|
while keep_running do
|
31
31
|
now = Time.now
|
@@ -32,10 +32,8 @@ module NewRelic
|
|
32
32
|
if backtrace
|
33
33
|
# this is for 1.9.1, where strings no longer have Enumerable
|
34
34
|
backtrace = backtrace.split("\n") if String === backtrace
|
35
|
-
# strip newrelic from the trace
|
36
35
|
backtrace = backtrace.reject {|line| line =~ /new_?relic/ }
|
37
36
|
# rename methods back to their original state
|
38
|
-
# GJV - 4/6/10 - adding .to_s call since we were seeing line as a Fixnum in some cases
|
39
37
|
backtrace = backtrace.collect {|line| line.to_s.gsub(/_without_(newrelic|trace)/, "")}
|
40
38
|
end
|
41
39
|
backtrace
|
@@ -6,26 +6,39 @@ module NewRelic
|
|
6
6
|
@instance ||= new_instance
|
7
7
|
end
|
8
8
|
|
9
|
+
def local_env
|
10
|
+
@local_env ||= NewRelic::LocalEnvironment.new
|
11
|
+
end
|
12
|
+
|
9
13
|
# Create the concrete class for environment specific behavior:
|
10
14
|
def new_instance
|
11
|
-
|
12
|
-
|
13
|
-
config = File.expand_path("../../../../test/config/newrelic.yml", __FILE__)
|
14
|
-
require "config/test_control"
|
15
|
-
NewRelic::Control::Frameworks::Test.new @local_env, config
|
15
|
+
if local_env.framework == :test
|
16
|
+
load_test_framework
|
16
17
|
else
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
load_framework_class(local_env.framework).new(local_env)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# nb this does not 'load test' the framework, it loads the 'test framework'
|
23
|
+
def load_test_framework
|
24
|
+
config = File.expand_path(File.join('..','..','..','..', "test","config","newrelic.yml"), __FILE__)
|
25
|
+
require "config/test_control"
|
26
|
+
NewRelic::Control::Frameworks::Test.new(local_env, config)
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_framework_class(framework)
|
30
|
+
begin
|
31
|
+
require "new_relic/control/frameworks/#{framework}"
|
32
|
+
rescue LoadError
|
33
|
+
# maybe it is already loaded by some external system
|
34
|
+
# i.e. rpm_contrib or user extensions?
|
23
35
|
end
|
36
|
+
NewRelic::Control::Frameworks.const_get(framework.to_s.capitalize)
|
24
37
|
end
|
25
38
|
|
26
39
|
# The root directory for the plugin or gem
|
27
40
|
def newrelic_root
|
28
|
-
File.expand_path("
|
41
|
+
File.expand_path(File.join("..", "..", "..", ".."), __FILE__)
|
29
42
|
end
|
30
43
|
end
|
31
44
|
extend ClassMethods
|
@@ -29,27 +29,40 @@ module NewRelic
|
|
29
29
|
@settings && agent_enabled?
|
30
30
|
end
|
31
31
|
|
32
|
-
#
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
# set the log level as specified in the config file
|
33
|
+
def set_log_level!(logger)
|
34
|
+
case fetch("log_level","info").downcase
|
35
|
+
when "debug" then logger.level = Logger::DEBUG
|
36
|
+
when "info" then logger.level = Logger::INFO
|
37
|
+
when "warn" then logger.level = Logger::WARN
|
38
|
+
when "error" then logger.level = Logger::ERROR
|
39
|
+
when "fatal" then logger.level = Logger::FATAL
|
40
|
+
else logger.level = Logger::INFO
|
41
|
+
end
|
42
|
+
logger
|
43
|
+
end
|
38
44
|
|
39
|
-
|
45
|
+
# change the format just for our logger
|
46
|
+
def set_log_format!(logger)
|
47
|
+
def logger.format_message(severity, timestamp, progname, msg)
|
40
48
|
"[#{timestamp.strftime("%m/%d/%y %H:%M:%S %z")} #{Socket.gethostname} (#{$$})] #{severity} : #{msg}\n"
|
41
49
|
end
|
50
|
+
logger
|
51
|
+
end
|
42
52
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
53
|
+
# Create the logger using the configuration variables
|
54
|
+
#
|
55
|
+
# Control subclasses may override this, but it can be called multiple times.
|
56
|
+
def setup_log
|
57
|
+
@log_file = "#{log_path}/#{log_file_name}"
|
58
|
+
@log = Logger.new(@log_file) rescue nil
|
59
|
+
if @log
|
60
|
+
set_log_format!(@log)
|
61
|
+
set_log_level!(@log)
|
52
62
|
end
|
63
|
+
# note this is not the variable from above - it is the `log`
|
64
|
+
# method, which returns a default logger if none is setup
|
65
|
+
# above
|
53
66
|
log
|
54
67
|
end
|
55
68
|
|
@@ -58,7 +71,7 @@ module NewRelic
|
|
58
71
|
end
|
59
72
|
|
60
73
|
def log_path
|
61
|
-
return if @log_path
|
74
|
+
return @log_path if @log_path
|
62
75
|
@log_path = File.expand_path(fetch('log_file_path', 'log/'))
|
63
76
|
if !File.directory?(@log_path) && ! (Dir.mkdir(@log_path) rescue nil)
|
64
77
|
log!("Error creating New Relic log directory '#{@log_path}'", :error)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
module NewRelic
|
3
|
+
class DataSerialization
|
4
|
+
module ClassMethods
|
5
|
+
def should_send_data?
|
6
|
+
# TODO get configuration from main control
|
7
|
+
(File.size(file_path) >= max_size)
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_from_file
|
11
|
+
create_file_if_needed
|
12
|
+
with_locked_store('r+') do |f|
|
13
|
+
get_data_from_file(f)
|
14
|
+
end
|
15
|
+
rescue(EOFError) => e
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def dump_to_file
|
20
|
+
create_file_if_needed
|
21
|
+
with_locked_store('r+') do |f|
|
22
|
+
result = (yield get_data_from_file(f))
|
23
|
+
f.rewind
|
24
|
+
f.write(dump(result))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def with_locked_store(mode)
|
31
|
+
File.open(file_path, mode) do |f|
|
32
|
+
f.flock(File::LOCK_EX)
|
33
|
+
begin
|
34
|
+
yield(f)
|
35
|
+
ensure
|
36
|
+
f.flock(File::LOCK_UN)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
rescue Exception => e
|
40
|
+
puts e.inspect
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_data_from_file(f)
|
44
|
+
data = f.read
|
45
|
+
result = load(data)
|
46
|
+
f.truncate(0)
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def max_size
|
51
|
+
100_000
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_file_if_needed
|
55
|
+
FileUtils.touch(file_path) unless File.exists?(file_path)
|
56
|
+
end
|
57
|
+
|
58
|
+
def dump(object)
|
59
|
+
Marshal.dump(object)
|
60
|
+
end
|
61
|
+
|
62
|
+
def load(dump)
|
63
|
+
Marshal.load(dump)
|
64
|
+
rescue ArgumentError => e
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def truncate_file
|
69
|
+
create_file_if_needed
|
70
|
+
File.truncate(file_path, 0)
|
71
|
+
end
|
72
|
+
|
73
|
+
def file_path
|
74
|
+
# TODO get configuration from main control
|
75
|
+
'./log/newrelic_agent_store.db'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
extend ClassMethods
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -17,9 +17,7 @@ module NewRelic
|
|
17
17
|
def original_spec
|
18
18
|
@original_spec || @metric_spec
|
19
19
|
end
|
20
|
-
|
21
|
-
@metric_spec
|
22
|
-
end
|
20
|
+
|
23
21
|
def metric_spec= new_spec
|
24
22
|
@original_spec = @metric_spec if @metric_spec
|
25
23
|
@metric_spec = new_spec
|
@@ -35,8 +33,14 @@ module NewRelic
|
|
35
33
|
end
|
36
34
|
|
37
35
|
def to_s
|
38
|
-
|
39
|
-
|
36
|
+
if metric_spec
|
37
|
+
"#{metric_spec.name}(#{metric_spec.scope}): #{stats}"
|
38
|
+
else
|
39
|
+
"#{metric_id}: #{stats}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
def inspect
|
43
|
+
"#<MetricData metric_spec:#{metric_spec.inspect}, stats:#{stats.inspect}, metric_id:#{metric_id.inspect}>"
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
@@ -26,7 +26,7 @@ class NewRelic::MetricSpec
|
|
26
26
|
self.class == o.class &&
|
27
27
|
name.eql?(o.name) &&
|
28
28
|
# coerce scope to a string and compare
|
29
|
-
|
29
|
+
scope.to_s == o.scope.to_s
|
30
30
|
end
|
31
31
|
|
32
32
|
def hash
|
@@ -37,6 +37,7 @@ class NewRelic::MetricSpec
|
|
37
37
|
# return a new metric spec if the given regex
|
38
38
|
# matches the name or scope.
|
39
39
|
def sub(pattern, replacement, apply_to_scope = true)
|
40
|
+
NewRelic::Control.instance.log.warn("The sub method on metric specs is deprecated") rescue nil
|
40
41
|
return nil if name !~ pattern &&
|
41
42
|
(!apply_to_scope || scope.nil? || scope !~ pattern)
|
42
43
|
new_name = name.sub(pattern, replacement)[0...MAX_LENGTH]
|
@@ -51,9 +52,14 @@ class NewRelic::MetricSpec
|
|
51
52
|
end
|
52
53
|
|
53
54
|
def to_s
|
55
|
+
return name if scope.empty?
|
54
56
|
"#{name}:#{scope}"
|
55
57
|
end
|
56
58
|
|
59
|
+
def inspect
|
60
|
+
"#<NewRelic::MetricSpec '#{name}':'#{scope}'>"
|
61
|
+
end
|
62
|
+
|
57
63
|
def to_json(*a)
|
58
64
|
{'name' => name,
|
59
65
|
'scope' => scope}.to_json(*a)
|
@@ -9,12 +9,6 @@ module NewRelic::Rack
|
|
9
9
|
|
10
10
|
# method required by Rack interface
|
11
11
|
def call(env)
|
12
|
-
|
13
|
-
# clear out the thread locals we use in case this is a static request
|
14
|
-
Thread.current[:newrelic_most_recent_transaction] = nil
|
15
|
-
Thread.current[:newrelic_start_time] = Time.now
|
16
|
-
Thread.current[:newrelic_queue_time] = 0
|
17
|
-
|
18
12
|
result = @app.call(env) # [status, headers, response]
|
19
13
|
|
20
14
|
if (NewRelic::Agent.browser_timing_header != "") && should_instrument?(result[0], result[1])
|
@@ -36,7 +30,7 @@ module NewRelic::Rack
|
|
36
30
|
|
37
31
|
def autoinstrument_source(response, headers)
|
38
32
|
source = nil
|
39
|
-
response.each {|fragment| (source) ? (source <<
|
33
|
+
response.each {|fragment| (source) ? (source << f) : (source = fragment)}
|
40
34
|
|
41
35
|
body_start = source.index("<body")
|
42
36
|
body_close = source.rindex("</body>")
|
data/lib/new_relic/stats.rb
CHANGED
@@ -1,108 +1,65 @@
|
|
1
|
+
require 'new_relic/transaction_analysis/segment_summary'
|
1
2
|
# Add these methods to TransactionSample that enable performance analysis in the user interface.
|
2
3
|
module NewRelic
|
3
|
-
module TransactionAnalysis
|
4
|
-
|
5
|
-
|
6
|
-
end
|
7
|
-
|
8
|
-
def render_time
|
9
|
-
time_percentage(/^View\/.*/)
|
10
|
-
end
|
11
|
-
|
12
|
-
# summarizes performance data for all calls to segments
|
13
|
-
# with the same metric_name
|
14
|
-
class SegmentSummary
|
15
|
-
attr_accessor :metric_name, :total_time, :exclusive_time, :call_count
|
16
|
-
def initialize(metric_name, sample)
|
17
|
-
@metric_name = metric_name
|
18
|
-
@total_time, @exclusive_time, @call_count = 0,0,0
|
19
|
-
@sample = sample
|
20
|
-
end
|
21
|
-
|
22
|
-
def <<(segment)
|
23
|
-
if metric_name != segment.metric_name
|
24
|
-
raise ArgumentError, "Metric Name Mismatch: #{segment.metric_name} != #{metric_name}"
|
25
|
-
end
|
26
|
-
|
27
|
-
@total_time += segment.duration
|
28
|
-
@exclusive_time += segment.exclusive_duration
|
29
|
-
@call_count += 1
|
4
|
+
module TransactionAnalysis
|
5
|
+
def database_time
|
6
|
+
time_percentage(/^Database\/.*/)
|
30
7
|
end
|
31
8
|
|
32
|
-
def
|
33
|
-
|
9
|
+
def render_time
|
10
|
+
time_percentage(/^View\/.*/)
|
34
11
|
end
|
35
12
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
13
|
+
# return the data that breaks down the performance of the transaction
|
14
|
+
# as an array of SegmentSummary objects. If a limit is specified, then
|
15
|
+
# limit the data set to the top n
|
16
|
+
def breakdown_data(limit = nil)
|
17
|
+
metric_hash = {}
|
18
|
+
each_segment do |segment|
|
19
|
+
unless segment == root_segment
|
20
|
+
metric_name = segment.metric_name
|
21
|
+
metric_hash[metric_name] ||= SegmentSummary.new(metric_name, self)
|
22
|
+
metric_hash[metric_name] << segment
|
23
|
+
end
|
24
|
+
end
|
44
25
|
|
45
|
-
|
46
|
-
return 0 unless @total_time && @sample.duration && @sample.duration > 0
|
47
|
-
@total_time / @sample.duration
|
48
|
-
end
|
26
|
+
data = metric_hash.values
|
49
27
|
|
50
|
-
|
51
|
-
|
52
|
-
NewRelic::MetricParser::MetricParser.parse(@metric_name).developer_name
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# return the data that breaks down the performance of the transaction
|
57
|
-
# as an array of SegmentSummary objects. If a limit is specified, then
|
58
|
-
# limit the data set to the top n
|
59
|
-
def breakdown_data(limit = nil)
|
60
|
-
metric_hash = {}
|
61
|
-
each_segment do |segment|
|
62
|
-
unless segment == root_segment
|
63
|
-
metric_name = segment.metric_name
|
64
|
-
metric_hash[metric_name] ||= SegmentSummary.new(metric_name, self)
|
65
|
-
metric_hash[metric_name] << segment
|
28
|
+
data.sort! do |x,y|
|
29
|
+
y.exclusive_time <=> x.exclusive_time
|
66
30
|
end
|
67
|
-
end
|
68
|
-
|
69
|
-
data = metric_hash.values
|
70
31
|
|
71
|
-
|
72
|
-
|
73
|
-
|
32
|
+
if limit && data.length > limit
|
33
|
+
data = data[0..limit - 1]
|
34
|
+
end
|
74
35
|
|
75
|
-
|
76
|
-
|
77
|
-
|
36
|
+
# add one last segment for the remaining time if any
|
37
|
+
remainder = duration
|
38
|
+
data.each do |segment|
|
39
|
+
remainder -= segment.exclusive_time
|
40
|
+
end
|
78
41
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
42
|
+
if (remainder*1000).round > 0
|
43
|
+
remainder_summary = SegmentSummary.new('Remainder', self)
|
44
|
+
remainder_summary.total_time = remainder_summary.exclusive_time = remainder
|
45
|
+
remainder_summary.call_count = 1
|
46
|
+
data << remainder_summary
|
47
|
+
end
|
84
48
|
|
85
|
-
|
86
|
-
remainder_summary = SegmentSummary.new('Remainder', self)
|
87
|
-
remainder_summary.total_time = remainder_summary.exclusive_time = remainder
|
88
|
-
remainder_summary.call_count = 1
|
89
|
-
data << remainder_summary
|
49
|
+
data
|
90
50
|
end
|
91
51
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
segments << segment if segment[:sql] || segment[:sql_obfuscated] || (show_non_sql_segments && segment[:key])
|
52
|
+
# return an array of sql statements executed by this transaction
|
53
|
+
# each element in the array contains [sql, parent_segment_metric_name, duration]
|
54
|
+
def sql_segments(show_non_sql_segments = true)
|
55
|
+
segments = []
|
56
|
+
each_segment do |segment|
|
57
|
+
segments << segment if segment[:sql] || segment[:sql_obfuscated] || (show_non_sql_segments && segment[:key])
|
58
|
+
end
|
59
|
+
segments
|
101
60
|
end
|
102
|
-
segments
|
103
|
-
end
|
104
61
|
|
105
|
-
|
62
|
+
private
|
106
63
|
def time_percentage(regex)
|
107
64
|
total = 0
|
108
65
|
each_segment do |segment|
|