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.

Files changed (74) hide show
  1. data/CHANGELOG +2 -3
  2. data/README.rdoc +3 -3
  3. data/lib/new_relic/agent.rb +19 -7
  4. data/lib/new_relic/agent/agent.rb +83 -19
  5. data/lib/new_relic/agent/beacon_configuration.rb +8 -12
  6. data/lib/new_relic/agent/browser_monitoring.rb +8 -8
  7. data/lib/new_relic/agent/error_collector.rb +13 -13
  8. data/lib/new_relic/agent/instrumentation.rb +9 -0
  9. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +10 -2
  10. data/lib/new_relic/agent/instrumentation/metric_frame.rb +41 -35
  11. data/lib/new_relic/agent/instrumentation/metric_frame/pop.rb +92 -0
  12. data/lib/new_relic/agent/method_tracer.rb +0 -2
  13. data/lib/new_relic/agent/shim_agent.rb +2 -0
  14. data/lib/new_relic/agent/stats_engine/metric_stats.rb +89 -60
  15. data/lib/new_relic/agent/stats_engine/transactions.rb +1 -1
  16. data/lib/new_relic/agent/worker_loop.rb +1 -1
  17. data/lib/new_relic/collection_helper.rb +0 -2
  18. data/lib/new_relic/control/class_methods.rb +25 -12
  19. data/lib/new_relic/control/logging_methods.rb +30 -17
  20. data/lib/new_relic/data_serialization.rb +81 -0
  21. data/lib/new_relic/local_environment.rb +1 -1
  22. data/lib/new_relic/metric_data.rb +9 -5
  23. data/lib/new_relic/metric_spec.rb +7 -1
  24. data/lib/new_relic/rack/browser_monitoring.rb +1 -7
  25. data/lib/new_relic/stats.rb +4 -0
  26. data/lib/new_relic/transaction_analysis.rb +45 -88
  27. data/lib/new_relic/transaction_analysis/segment_summary.rb +47 -0
  28. data/lib/new_relic/transaction_sample.rb +15 -332
  29. data/lib/new_relic/transaction_sample/composite_segment.rb +27 -0
  30. data/lib/new_relic/transaction_sample/fake_segment.rb +9 -0
  31. data/lib/new_relic/transaction_sample/segment.rb +250 -0
  32. data/lib/new_relic/transaction_sample/summary_segment.rb +21 -0
  33. data/lib/new_relic/version.rb +3 -3
  34. data/newrelic.yml +3 -3
  35. data/newrelic_rpm.gemspec +27 -4
  36. data/test/active_record_fixtures.rb +31 -13
  37. data/test/new_relic/agent/agent/start_worker_thread_test.rb +1 -3
  38. data/test/new_relic/agent/agent_test.rb +73 -28
  39. data/test/new_relic/agent/agent_test_controller_test.rb +11 -10
  40. data/test/new_relic/agent/beacon_configuration_test.rb +37 -20
  41. data/test/new_relic/agent/browser_monitoring_test.rb +17 -28
  42. data/test/new_relic/agent/error_collector/notice_error_test.rb +9 -7
  43. data/test/new_relic/agent/error_collector_test.rb +6 -7
  44. data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +12 -5
  45. data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +195 -0
  46. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +60 -58
  47. data/test/new_relic/agent/instrumentation/queue_time_test.rb +14 -0
  48. data/test/new_relic/agent/instrumentation/rack_test.rb +35 -0
  49. data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +0 -1
  50. data/test/new_relic/agent/method_tracer_test.rb +8 -8
  51. data/test/new_relic/agent/sampler_test.rb +19 -0
  52. data/test/new_relic/agent/shim_agent_test.rb +20 -0
  53. data/test/new_relic/agent/stats_engine/metric_stats/harvest_test.rb +150 -0
  54. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +1 -0
  55. data/test/new_relic/agent/stats_engine/samplers_test.rb +4 -3
  56. data/test/new_relic/agent/{stats_engine/stats_engine_test.rb → stats_engine_test.rb} +8 -8
  57. data/test/new_relic/agent/transaction_sampler_test.rb +1 -1
  58. data/test/new_relic/agent/worker_loop_test.rb +2 -2
  59. data/test/new_relic/control/class_methods_test.rb +62 -0
  60. data/test/new_relic/control/logging_methods_test.rb +157 -0
  61. data/test/new_relic/control_test.rb +10 -10
  62. data/test/new_relic/data_serialization_test.rb +50 -0
  63. data/test/new_relic/local_environment_test.rb +13 -13
  64. data/test/new_relic/metric_data_test.rb +125 -0
  65. data/test/new_relic/metric_spec_test.rb +8 -0
  66. data/test/new_relic/transaction_analysis/segment_summary_test.rb +77 -0
  67. data/test/new_relic/transaction_analysis_test.rb +121 -0
  68. data/test/new_relic/transaction_sample/composite_segment_test.rb +35 -0
  69. data/test/new_relic/transaction_sample/fake_segment_test.rb +17 -0
  70. data/test/new_relic/transaction_sample/segment_test.rb +454 -0
  71. data/test/new_relic/transaction_sample/summary_segment_test.rb +31 -0
  72. data/test/new_relic/transaction_sample_test.rb +51 -0
  73. data/test/test_helper.rb +4 -14
  74. metadata +32 -7
@@ -102,7 +102,7 @@ module Agent
102
102
  #
103
103
  def end_transaction
104
104
  stack = scope_stack
105
-
105
+
106
106
  if stack && stack.empty?
107
107
  Thread::current[:newrelic_most_recent_transaction] = Thread::current[:newrelic_scope_name]
108
108
  Thread::current[:newrelic_scope_stack] = nil
@@ -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
- @local_env = NewRelic::LocalEnvironment.new
12
- if @local_env.framework == :test
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
- begin
18
- require "new_relic/control/frameworks/#{@local_env.framework}.rb"
19
- rescue LoadError
20
- end
21
- klass = NewRelic::Control::Frameworks.const_get(@local_env.framework.to_s.capitalize)
22
- klass.new @local_env
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("../../../..", __FILE__)
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
- # Control subclasses may override this, but it can be called multiple times.
33
- def setup_log
34
- @log_file = "#{log_path}/#{log_file_name}"
35
- @log = Logger.new(@log_file) rescue nil
36
-
37
- # change the format just for our logger
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
- def log.format_message(severity, timestamp, progname, msg)
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
- # set the log level as specified in the config file
44
-
45
- case fetch("log_level","info").downcase
46
- when "debug" then log.level = Logger::DEBUG
47
- when "info" then log.level = Logger::INFO
48
- when "warn" then log.level = Logger::WARN
49
- when "error" then log.level = Logger::ERROR
50
- when "fatal" then log.level = Logger::FATAL
51
- else log.level = Logger::INFO
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
+
@@ -247,7 +247,7 @@ module NewRelic
247
247
 
248
248
  def check_for_torquebox
249
249
  return unless defined?(::JRuby) &&
250
- ( Java::OrgTorqueboxRailsWebDeployers::RailsRackDeployer rescue nil)
250
+ ( org.torquebox::TorqueBox rescue nil)
251
251
  @dispatcher = :torquebox
252
252
  end
253
253
 
@@ -17,9 +17,7 @@ module NewRelic
17
17
  def original_spec
18
18
  @original_spec || @metric_spec
19
19
  end
20
- def metric_spec
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
- "#{metric_spec.name}(#{metric_spec.scope}): #{stats}" if metric_spec
39
- "#{metric_id}: #{stats}" if metric_spec.nil?
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
- (scope || '') == (o.scope || '')
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 << fragment) : (source = fragment)}
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>")
@@ -353,6 +353,10 @@ module NewRelic
353
353
  @call_count += value
354
354
  end
355
355
 
356
+ def inspect
357
+ "#<NewRelic::MethodTraceStats #{summary} >"
358
+ end
359
+
356
360
  end
357
361
 
358
362
  class ScopedMethodTraceStats < MethodTraceStats
@@ -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
- def database_time
5
- time_percentage(/^Database\/.*/)
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 average_time
33
- @total_time / @call_count
9
+ def render_time
10
+ time_percentage(/^View\/.*/)
34
11
  end
35
12
 
36
- def average_exclusive_time
37
- @exclusive_time / @call_count
38
- end
39
-
40
- def exclusive_time_percentage
41
- return 0 unless @exclusive_time && @sample.duration && @sample.duration > 0
42
- @exclusive_time / @sample.duration
43
- end
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
- def total_time_percentage
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
- def ui_name
51
- return @metric_name if @metric_name == 'Remainder'
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
- data.sort! do |x,y|
72
- y.exclusive_time <=> x.exclusive_time
73
- end
32
+ if limit && data.length > limit
33
+ data = data[0..limit - 1]
34
+ end
74
35
 
75
- if limit && data.length > limit
76
- data = data[0..limit - 1]
77
- end
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
- # add one last segment for the remaining time if any
80
- remainder = duration
81
- data.each do |segment|
82
- remainder -= segment.exclusive_time
83
- end
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
- if (remainder*1000).round > 0
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
- data
93
- end
94
-
95
- # return an array of sql statements executed by this transaction
96
- # each element in the array contains [sql, parent_segment_metric_name, duration]
97
- def sql_segments(show_non_sql_segments = true)
98
- segments = []
99
- each_segment do |segment|
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
- private
62
+ private
106
63
  def time_percentage(regex)
107
64
  total = 0
108
65
  each_segment do |segment|