newrelic_rpm 3.8.0.218 → 3.8.1.221

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +32 -0
  3. data/README.md +4 -7
  4. data/Rakefile +3 -0
  5. data/lib/new_relic/agent.rb +3 -7
  6. data/lib/new_relic/agent/agent.rb +4 -14
  7. data/lib/new_relic/agent/agent_logger.rb +19 -11
  8. data/lib/new_relic/agent/autostart.rb +1 -1
  9. data/lib/new_relic/agent/configuration/default_source.rb +25 -12
  10. data/lib/new_relic/agent/configuration/manager.rb +14 -7
  11. data/lib/new_relic/agent/configuration/yaml_source.rb +39 -8
  12. data/lib/new_relic/agent/cross_app_monitor.rb +9 -7
  13. data/lib/new_relic/agent/cross_app_tracing.rb +6 -6
  14. data/lib/new_relic/agent/datastores/mongo.rb +6 -7
  15. data/lib/new_relic/agent/datastores/mongo/metric_translator.rb +32 -13
  16. data/lib/new_relic/agent/datastores/mongo/statement_formatter.rb +4 -3
  17. data/lib/new_relic/agent/error_collector.rb +2 -2
  18. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +10 -69
  19. data/lib/new_relic/agent/instrumentation/action_view_subscriber.rb +5 -7
  20. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +2 -2
  21. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +77 -93
  22. data/lib/new_relic/agent/instrumentation/evented_subscriber.rb +1 -1
  23. data/lib/new_relic/agent/instrumentation/mongo.rb +26 -42
  24. data/lib/new_relic/agent/instrumentation/rubyprof.rb +1 -1
  25. data/lib/new_relic/agent/instrumentation/sinatra.rb +4 -1
  26. data/lib/new_relic/agent/javascript_instrumentor.rb +15 -6
  27. data/lib/new_relic/agent/method_tracer.rb +41 -92
  28. data/lib/new_relic/agent/request_sampler.rb +0 -1
  29. data/lib/new_relic/agent/rules_engine.rb +36 -12
  30. data/lib/new_relic/agent/shim_agent.rb +0 -1
  31. data/lib/new_relic/agent/sql_sampler.rb +8 -15
  32. data/lib/new_relic/agent/stats_engine.rb +2 -6
  33. data/lib/new_relic/agent/stats_engine/metric_stats.rb +8 -2
  34. data/lib/new_relic/agent/stats_engine/stats_hash.rb +1 -1
  35. data/lib/new_relic/agent/supported_versions.rb +1 -1
  36. data/lib/new_relic/agent/traced_method_stack.rb +87 -0
  37. data/lib/new_relic/agent/transaction.rb +277 -107
  38. data/lib/new_relic/agent/transaction_sample_builder.rb +2 -2
  39. data/lib/new_relic/agent/transaction_sampler.rb +18 -27
  40. data/lib/new_relic/agent/transaction_state.rb +15 -40
  41. data/lib/new_relic/control/instance_methods.rb +8 -4
  42. data/lib/new_relic/recipes.rb +3 -3
  43. data/lib/new_relic/transaction_sample.rb +3 -7
  44. data/lib/new_relic/version.rb +1 -1
  45. data/lib/sequel/extensions/newrelic_instrumentation.rb +3 -3
  46. data/newrelic_rpm.gemspec +15 -9
  47. data/test/agent_helper.rb +71 -36
  48. data/test/environments/norails/Gemfile +2 -0
  49. data/test/environments/rails21/Gemfile +2 -0
  50. data/test/environments/rails22/Gemfile +2 -0
  51. data/test/environments/rails23/Gemfile +2 -0
  52. data/test/environments/rails30/Gemfile +2 -0
  53. data/test/environments/rails31/Gemfile +2 -0
  54. data/test/environments/rails32/Gemfile +2 -0
  55. data/test/environments/rails40/Gemfile +2 -0
  56. data/test/environments/rails41/Gemfile +1 -0
  57. data/test/helpers/mongo_metric_builder.rb +1 -1
  58. data/test/multiverse/suites/agent_only/audit_log_test.rb +2 -2
  59. data/test/multiverse/suites/agent_only/cross_application_tracing_test.rb +9 -1
  60. data/test/multiverse/suites/agent_only/encoding_handling_test.rb +1 -1
  61. data/test/multiverse/suites/agent_only/logging_test.rb +2 -2
  62. data/test/multiverse/suites/agent_only/marshaling_test.rb +8 -9
  63. data/test/multiverse/suites/agent_only/rum_instrumentation_test.rb +14 -1
  64. data/test/multiverse/suites/agent_only/set_transaction_name_test.rb +30 -13
  65. data/test/multiverse/suites/agent_only/thread_profiling_test.rb +9 -8
  66. data/test/multiverse/suites/agent_only/transaction_ignoring_test.rb +43 -0
  67. data/test/multiverse/suites/config_file_loading/config_file_loading_test.rb +77 -5
  68. data/test/multiverse/suites/excon/excon_test.rb +1 -1
  69. data/test/multiverse/suites/mongo/Envfile +4 -7
  70. data/test/multiverse/suites/mongo/helpers/mongo_operation_tests.rb +55 -16
  71. data/test/multiverse/suites/mongo/helpers/mongo_server.rb +6 -4
  72. data/test/multiverse/suites/rails/bad_instrumentation_test.rb +2 -2
  73. data/test/multiverse/suites/rails/error_tracing_test.rb +3 -1
  74. data/test/multiverse/suites/rails/request_statistics_test.rb +14 -14
  75. data/test/multiverse/suites/rails/transaction_ignoring_test.rb +45 -0
  76. data/test/multiverse/suites/sequel/sequel_instrumentation_test.rb +1 -1
  77. data/test/new_relic/agent/agent/connect_test.rb +4 -4
  78. data/test/new_relic/agent/agent_logger_test.rb +32 -0
  79. data/test/new_relic/agent/configuration/manager_test.rb +12 -4
  80. data/test/new_relic/agent/configuration/yaml_source_test.rb +2 -2
  81. data/test/new_relic/agent/cross_app_monitor_test.rb +6 -2
  82. data/test/new_relic/agent/cross_app_tracing_test.rb +5 -5
  83. data/test/new_relic/agent/datastores/mongo/statement_formatter_test.rb +7 -6
  84. data/test/new_relic/agent/error_collector_test.rb +1 -2
  85. data/test/new_relic/agent/instrumentation/action_controller_subscriber_test.rb +24 -11
  86. data/test/new_relic/agent/instrumentation/action_view_subscriber_test.rb +2 -2
  87. data/test/new_relic/agent/instrumentation/active_record_subscriber_test.rb +12 -17
  88. data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +43 -32
  89. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +3 -4
  90. data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +36 -20
  91. data/test/new_relic/agent/javascript_instrumentor_test.rb +1 -2
  92. data/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb +15 -26
  93. data/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb +66 -103
  94. data/test/new_relic/agent/method_tracer_test.rb +2 -2
  95. data/test/new_relic/agent/mock_scope_listener.rb +3 -6
  96. data/test/new_relic/agent/pipe_channel_manager_test.rb +4 -4
  97. data/test/new_relic/agent/rules_engine_test.rb +13 -0
  98. data/test/new_relic/agent/sql_sampler_test.rb +8 -10
  99. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +18 -0
  100. data/test/new_relic/agent/stats_engine_test.rb +0 -173
  101. data/test/new_relic/agent/threading/agent_thread_test.rb +27 -26
  102. data/test/new_relic/agent/traced_method_stack_test.rb +139 -0
  103. data/test/new_relic/agent/transaction_sample_builder_test.rb +2 -12
  104. data/test/new_relic/agent/transaction_sampler_test.rb +98 -107
  105. data/test/new_relic/agent/transaction_state_test.rb +52 -61
  106. data/test/new_relic/agent/transaction_test.rb +209 -140
  107. data/test/new_relic/agent_test.rb +3 -2
  108. data/test/new_relic/control_test.rb +10 -9
  109. data/test/new_relic/fake_collector.rb +34 -2
  110. data/test/new_relic/http_client_test_cases.rb +0 -5
  111. data/test/new_relic/license_test.rb +4 -2
  112. data/test/new_relic/local_environment_test.rb +14 -28
  113. data/test/new_relic/multiverse_helpers.rb +2 -2
  114. data/test/new_relic/rack/developer_mode_test.rb +4 -5
  115. data/test/new_relic/transaction_ignoring_test_cases.rb +104 -0
  116. data/test/new_relic/transaction_sample_test.rb +14 -7
  117. data/test/performance/lib/performance/instrumentation/gc_stats.rb +6 -3
  118. data/test/performance/suites/transaction_tracing.rb +4 -1
  119. data/test/test_helper.rb +31 -60
  120. data/ui/views/newrelic/show_sample.rhtml +1 -1
  121. metadata +46 -101
  122. metadata.gz.sig +0 -0
  123. data/lib/new_relic/agent/stats_engine/transactions.rb +0 -114
  124. data/lib/new_relic/agent/transaction/pop.rb +0 -52
  125. data/test/new_relic/agent/transaction/pop_test.rb +0 -79
@@ -102,7 +102,6 @@ class NewRelic::Agent::RequestSampler
102
102
  end
103
103
 
104
104
  NewRelic::Agent.config.register_callback(:'analytics_events.enabled') do |enabled|
105
- NewRelic::Agent.logger.info "%sabling the Request Sampler." % [ enabled ? 'En' : 'Dis' ]
106
105
  @enabled = enabled
107
106
  end
108
107
  end
@@ -8,9 +8,7 @@ module NewRelic
8
8
  include Enumerable
9
9
  extend Forwardable
10
10
 
11
- def_delegators :@rules, :size, :<<, :inspect, :each
12
-
13
- attr_accessor :rules
11
+ def_delegators :@rules, :size, :inspect, :each, :clear
14
12
 
15
13
  def self.from_specs(specs)
16
14
  rules = (specs || []).map { |spec| Rule.new(spec) }
@@ -18,19 +16,36 @@ module NewRelic
18
16
  end
19
17
 
20
18
  def initialize(rules=[])
21
- @rules = rules
19
+ @rules = rules.sort
20
+ end
21
+
22
+ def << rule
23
+ @rules << rule
24
+ @rules.sort!
25
+ @rules
22
26
  end
23
27
 
24
28
  def rename(original_string)
25
- @rules.sort.inject(original_string) do |string,rule|
29
+ @rules.inject(original_string) do |string,rule|
26
30
  if rule.each_segment
27
- result, matched = rule.map_to_list(string.split('/'))
28
- result = result.join('/')
31
+ segments = string.split('/')
32
+
33
+ # If string looks like '/foo/bar', we want to
34
+ # ignore the empty string preceding the first slash.
35
+ add_preceding_slash = false
36
+ if segments[0] == ''
37
+ segments.shift
38
+ add_preceding_slash = true
39
+ end
40
+
41
+ result, matched = rule.map_to_list(segments)
42
+ result = result.join('/') if !result.nil?
43
+ result = '/'+result if add_preceding_slash
29
44
  else
30
45
  result, matched = rule.apply(string)
31
46
  end
32
47
 
33
- break result if matched && rule.terminate_chain
48
+ break result if (matched && rule.terminate_chain) || result.nil?
34
49
  result
35
50
  end
36
51
  end
@@ -47,7 +62,7 @@ module NewRelic
47
62
  raise ArgumentError.new('must specify replacement when ignore is false')
48
63
  end
49
64
 
50
- @match_expression = Regexp.new(options['match_expression'])
65
+ @match_expression = Regexp.new(options['match_expression'], Regexp::IGNORECASE)
51
66
  @replacement = options['replacement']
52
67
  @ignore = options['ignore'] || false
53
68
  @eval_order = options['eval_order'] || 0
@@ -57,9 +72,18 @@ module NewRelic
57
72
  end
58
73
 
59
74
  def apply(string)
60
- method = @replace_all ? :gsub : :sub
61
- result = string.send(method, @match_expression, @replacement)
62
- [result, result != string]
75
+ if @ignore
76
+ if string.match @match_expression
77
+ [nil, true]
78
+ else
79
+ [string, false]
80
+ end
81
+ else
82
+ method = @replace_all ? :gsub : :sub
83
+ result = string.send(method, @match_expression, @replacement)
84
+ match_found = ($~ != nil)
85
+ [result, match_found]
86
+ end
63
87
  end
64
88
 
65
89
  def map_to_list(list)
@@ -14,7 +14,6 @@ module NewRelic
14
14
  def initialize
15
15
  super
16
16
  @stats_engine.extend NewRelic::Agent::StatsEngine::Shim
17
- @stats_engine.extend NewRelic::Agent::StatsEngine::Transactions::Shim
18
17
  @transaction_sampler.extend NewRelic::Agent::TransactionSampler::Shim
19
18
  @sql_sampler.extend NewRelic::Agent::SqlSampler::Shim
20
19
  @error_collector.extend NewRelic::Agent::ErrorCollector::Shim
@@ -11,14 +11,12 @@ require 'new_relic/control'
11
11
 
12
12
  module NewRelic
13
13
  module Agent
14
-
15
14
  class SqlSampler
16
15
 
17
16
  # Module defining methods stubbed out when the agent is disabled
18
- module Shim #:nodoc:
19
- def notice_scope_empty(*args); end
20
- def notice_first_scope_push(*args); end
21
- def notice_transaction(*args); end
17
+ module Shim
18
+ def on_start_transaction(*args); end
19
+ def on_finishing_transaction(*args); end
22
20
  end
23
21
 
24
22
  attr_reader :disabled
@@ -43,23 +41,18 @@ module NewRelic
43
41
  Agent.config[:'transaction_tracer.enabled']
44
42
  end
45
43
 
46
- def notice_transaction(uri=nil, params={})
44
+ def on_start_transaction(start_time, uri=nil, params={})
45
+ TransactionState.get.sql_sampler_transaction_data = TransactionSqlData.new
46
+
47
47
  if NewRelic::Agent.instance.transaction_sampler.builder
48
48
  guid = NewRelic::Agent.instance.transaction_sampler.builder.sample.guid
49
49
  end
50
+
50
51
  if Agent.config[:'slow_sql.enabled'] && transaction_data
51
52
  transaction_data.set_transaction_info(uri, params, guid)
52
53
  end
53
54
  end
54
55
 
55
- def notice_first_scope_push(time)
56
- create_transaction_data
57
- end
58
-
59
- def create_transaction_data
60
- TransactionState.get.sql_sampler_transaction_data = TransactionSqlData.new
61
- end
62
-
63
56
  def transaction_data
64
57
  TransactionState.get.sql_sampler_transaction_data
65
58
  end
@@ -69,7 +62,7 @@ module NewRelic
69
62
  end
70
63
 
71
64
  # This is called when we are done with the transaction.
72
- def notice_scope_empty(name, time=Time.now)
65
+ def on_finishing_transaction(name, time=Time.now)
73
66
  data = transaction_data
74
67
  data.set_transaction_name(name)
75
68
  clear_transaction_data
@@ -4,7 +4,6 @@
4
4
 
5
5
  require 'new_relic/agent/stats_engine/metric_stats'
6
6
  require 'new_relic/agent/stats_engine/samplers'
7
- require 'new_relic/agent/stats_engine/transactions'
8
7
  require 'new_relic/agent/stats_engine/gc_profiler'
9
8
  require 'new_relic/agent/stats_engine/stats_hash'
10
9
 
@@ -14,15 +13,12 @@ module NewRelic
14
13
  class StatsEngine
15
14
  include MetricStats
16
15
  include Samplers
17
- include Transactions
18
16
 
19
17
  attr_accessor :metric_rules
20
18
 
21
19
  def initialize
22
- # Makes the unit tests happy
23
- NewRelic::Agent::TransactionState.get.clear_stats_scope_stack
24
- @stats_lock = Mutex.new
25
- @stats_hash = StatsHash.new
20
+ @stats_lock = Mutex.new
21
+ @stats_hash = StatsHash.new
26
22
  @metric_rules = RulesEngine.new
27
23
  end
28
24
 
@@ -148,8 +148,10 @@ module NewRelic
148
148
  renamed_stats = NewRelic::Agent::StatsHash.new(stats_hash.started_at)
149
149
  stats_hash.each do |spec, stats|
150
150
  new_name = rules_engine.rename(spec.name)
151
- new_spec = NewRelic::MetricSpec.new(new_name, spec.scope)
152
- renamed_stats[new_spec].merge!(stats)
151
+ unless new_name.nil?
152
+ new_spec = NewRelic::MetricSpec.new(new_name, spec.scope)
153
+ renamed_stats[new_spec].merge!(stats)
154
+ end
153
155
  end
154
156
  renamed_stats
155
157
  end
@@ -189,6 +191,10 @@ module NewRelic
189
191
  def in_transaction?
190
192
  !!transaction_stats_hash
191
193
  end
194
+
195
+ def transaction_stats_hash
196
+ Transaction.current && Transaction.current.stats_hash
197
+ end
192
198
  end
193
199
  end
194
200
  end
@@ -49,7 +49,7 @@ module NewRelic
49
49
  stats = nil
50
50
  begin
51
51
  stats = self[metric_spec]
52
- rescue => e
52
+ rescue NoMethodError => e
53
53
  # This only happen in the case of a corrupted default_proc
54
54
  # Side-step it manually, notice the issue, and carry on....
55
55
  NewRelic::Agent.instance.error_collector. \
@@ -140,7 +140,7 @@ module NewRelic
140
140
  :mongo =>
141
141
  {
142
142
  :type => :database,
143
- :supported => ["~>1.8.0", "~>1.9.0"],
143
+ :supported => ["~>1.8.0", "~>1.9.0", "~>1.10.0"],
144
144
  :url => "https://rubygems.org/gems/mongo",
145
145
  :feed => "https://rubygems.org/gems/mongo/versions.atom"
146
146
  },
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ module NewRelic
6
+ module Agent
7
+ class TracedMethodFrame
8
+ attr_reader :deduct_call_time_from_parent, :tag
9
+ attr_accessor :name, :start_time, :children_time, :type
10
+ def initialize(tag, start_time, deduct_call_time)
11
+ @tag = tag
12
+ @start_time = start_time
13
+ @deduct_call_time_from_parent = deduct_call_time
14
+ @children_time = 0
15
+ end
16
+ end
17
+
18
+ # TracedMethodStack is responsible for tracking the push and pop of methods
19
+ # that we are tracing, notifying the transaction sampler, and calculating
20
+ # exclusive time when a method is complete. This is allowed whether a
21
+ # transaction is in progress not.
22
+ class TracedMethodStack
23
+ def initialize
24
+ @stack = []
25
+ end
26
+
27
+ def self.push_frame(tag, time = Time.now.to_f, deduct_call_time_from_parent = true)
28
+ stack = NewRelic::Agent::TransactionState.get.traced_method_stack
29
+ stack.push_frame(tag, time, deduct_call_time_from_parent)
30
+ end
31
+
32
+ def self.pop_frame(expected_frame, name, time=Time.now.to_f)
33
+ stack = NewRelic::Agent::TransactionState.get.traced_method_stack
34
+ stack.pop_frame(expected_frame, name, time)
35
+ end
36
+
37
+ # Pushes a frame onto the transaction stack - this generates a
38
+ # TransactionSample::Segment at the end of transaction execution.
39
+ #
40
+ # The generated segment won't be named until pop_frame is called.
41
+ #
42
+ # +tag+ should be a Symbol, and is only for debugging purposes to
43
+ # identify this frame if the stack gets corrupted.
44
+ def push_frame(tag, time = Time.now.to_f, deduct_call_time_from_parent = true)
45
+ transaction_sampler.notice_push_frame(time) if sampler_enabled?
46
+ frame = TracedMethodFrame.new(tag, time, deduct_call_time_from_parent)
47
+ @stack.push frame
48
+ frame
49
+ end
50
+
51
+ # Pops a frame off the transaction stack - this updates the transaction
52
+ # sampler that we've finished execution of a traced method.
53
+ #
54
+ # +expected_frame+ should be TracedMethodFrame from the corresponding
55
+ # push_frame call.
56
+ #
57
+ # +name+ will be applied to the generated transaction trace segment.
58
+ def pop_frame(expected_frame, name, time=Time.now.to_f)
59
+ frame = @stack.pop
60
+ fail "unbalanced pop from blame stack, got #{frame ? frame.tag : 'nil'}, expected #{expected_frame ? expected_frame.tag : 'nil'}" if frame != expected_frame
61
+
62
+ if !@stack.empty?
63
+ if frame.deduct_call_time_from_parent
64
+ @stack.last.children_time += (time - frame.start_time)
65
+ else
66
+ @stack.last.children_time += frame.children_time
67
+ end
68
+ end
69
+ transaction_sampler.notice_pop_frame(name, time) if sampler_enabled?
70
+ frame.name = name
71
+ frame
72
+ end
73
+
74
+ def sampler_enabled?
75
+ Agent.config[:'transaction_tracer.enabled'] || Agent.config[:developer_mode]
76
+ end
77
+
78
+ def transaction_sampler
79
+ Agent.instance.transaction_sampler
80
+ end
81
+
82
+ def empty?
83
+ @stack.empty?
84
+ end
85
+ end
86
+ end
87
+ end
@@ -2,8 +2,8 @@
2
2
  # This file is distributed under New Relic's license terms.
3
3
  # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
4
 
5
- require 'new_relic/agent/transaction/pop'
6
5
  require 'new_relic/agent/transaction_timings'
6
+ require 'new_relic/agent/instrumentation/queue_time'
7
7
 
8
8
  module NewRelic
9
9
  module Agent
@@ -12,21 +12,25 @@ module NewRelic
12
12
  #
13
13
  # @api public
14
14
  class Transaction
15
- # helper module refactored out of the `pop` method
16
- include Pop
15
+
16
+ # for nested transactions
17
+ SUBTRANSACTION_PREFIX = 'Nested'.freeze
17
18
 
18
19
  attr_accessor :start_time # A Time instance for the start time, never nil
19
20
  attr_accessor :apdex_start # A Time instance used for calculating the apdex score, which
20
21
  # might end up being @start, or it might be further upstream if
21
22
  # we can find a request header for the queue entry time
22
- attr_accessor(:type, :exceptions, :filtered_params, :force_flag,
23
- :jruby_cpu_start, :process_cpu_start, :database_metric_name)
23
+ attr_accessor :type, :exceptions, :filtered_params,
24
+ :jruby_cpu_start, :process_cpu_start
25
+
26
+ attr_reader :database_metric_name
24
27
 
25
- attr_reader :name
26
28
  attr_reader :guid
27
29
  attr_reader :stats_hash
28
30
  attr_reader :gc_start_snapshot
29
31
 
32
+ attr_reader :name_from_child
33
+
30
34
  # Populated with the trace sample once this transaction is completed.
31
35
  attr_reader :transaction_trace
32
36
 
@@ -37,32 +41,115 @@ module NewRelic
37
41
 
38
42
  # Return the currently active transaction, or nil.
39
43
  def self.current
40
- self.stack.last
44
+ TransactionState.get.current_transaction
41
45
  end
42
46
 
43
- def self.parent
44
- self.stack[-2]
47
+ def self.set_default_transaction_name(name, options = {})
48
+ txn = current
49
+ name = make_transaction_name(name, options[:category])
50
+
51
+ if txn.frame_stack.empty?
52
+ txn.default_name = name
53
+ txn.type = options[:category] if options[:category]
54
+ else
55
+ txn.frame_stack.last.name = name
56
+ txn.frame_stack.last.type = options[:category] if options[:category]
57
+ end
45
58
  end
46
59
 
47
- def self.start(transaction_type, options={})
48
- txn = Transaction.new(transaction_type, options)
49
- txn.start(transaction_type)
50
- self.stack.push(txn)
51
- return txn
60
+ def self.set_overriding_transaction_name(name, options = {})
61
+ txn = current
62
+ return unless txn
63
+
64
+ name = make_transaction_name(name, options[:category])
65
+
66
+ if txn.frame_stack.empty?
67
+ txn.default_name = name
68
+ txn.name_from_api = name
69
+ txn.type = options[:category] if options[:category]
70
+ else
71
+ txn.frame_stack.last.name = name
72
+ txn.frame_stack.last.type = options[:category] if options[:category]
73
+
74
+ # Parent transaction also takes this name, but only if they
75
+ # are both/neither web transactions.
76
+ child_is_web_type = transaction_type_is_web?(txn.frame_stack.last.type)
77
+ txn_is_web_type = transaction_type_is_web?(txn.type)
78
+
79
+ if (child_is_web_type == txn_is_web_type)
80
+ txn.name_from_api = name
81
+ end
82
+ end
52
83
  end
53
84
 
54
- def self.stop(metric_name=nil, end_time=Time.now)
55
- txn = self.stack.last
56
- txn.stop(metric_name, end_time) if txn
57
- return self.stack.pop
85
+ def self.make_transaction_name(name, category=nil)
86
+ namer = Instrumentation::ControllerInstrumentation::TransactionNamer
87
+ "#{namer.category_name(category)}/#{name}"
58
88
  end
59
89
 
60
- def self.stack
61
- TransactionState.get.current_transaction_stack
90
+ def self.start(transaction_type, options)
91
+ transaction_type ||= :controller
92
+ txn = current
93
+
94
+ if txn
95
+ _, nested_frame = NewRelic::Agent::MethodTracer::TraceExecutionScoped.trace_execution_scoped_header({:deduct_call_time_from_parent => true}, Time.now.to_f)
96
+ nested_frame.name = options[:transaction_name]
97
+ nested_frame.type = transaction_type
98
+ txn.frame_stack << nested_frame
99
+ else
100
+ txn = Transaction.new(transaction_type, options)
101
+ TransactionState.get.current_transaction = txn
102
+ txn.start()
103
+ end
104
+
105
+ txn
106
+ end
107
+
108
+ def self.stop(end_time=Time.now, opts={})
109
+ txn = current
110
+
111
+ if txn.frame_stack.empty?
112
+ txn.stop(end_time, opts)
113
+ TransactionState.get.current_transaction = nil
114
+ else
115
+ nested_frame = txn.frame_stack.pop
116
+
117
+ # Parent transaction inherits the name of the first child
118
+ # to complete, if they are both/neither web transactions.
119
+ nested_is_web_type = transaction_type_is_web?(nested_frame.type)
120
+ txn_is_web_type = transaction_type_is_web?(txn.type)
121
+
122
+ if (nested_is_web_type == txn_is_web_type)
123
+ # first child to finish wins
124
+ txn.name_from_child ||= nested_frame.name
125
+ end
126
+
127
+ NewRelic::Agent::MethodTracer::TraceExecutionScoped.trace_execution_scoped_footer(
128
+ nested_frame.start_time.to_f,
129
+ nested_transaction_name(nested_frame.name),
130
+ [],
131
+ nested_frame,
132
+ {:metric => true},
133
+ end_time.to_f)
134
+ end
135
+
136
+ :transaction_stopped
62
137
  end
63
138
 
64
139
  def self.in_transaction?
65
- !self.stack.empty?
140
+ !TransactionState.get.current_transaction.nil?
141
+ end
142
+
143
+ def self.nested_transaction_name(name)
144
+ if name =~ /^Controller\//
145
+ "#{SUBTRANSACTION_PREFIX}/#{name}"
146
+ else
147
+ name
148
+ end
149
+ end
150
+
151
+ def root?
152
+ true
66
153
  end
67
154
 
68
155
  # This is the name of the model currently assigned to database
@@ -79,8 +166,8 @@ module NewRelic
79
166
  NewRelic::Agent.instance
80
167
  end
81
168
 
82
- def self.freeze_name
83
- self.current && self.current.freeze_name
169
+ def self.freeze_name_and_execute_if_not_ignored
170
+ self.current && self.current.freeze_name_and_execute_if_not_ignored { yield if block_given? }
84
171
  end
85
172
 
86
173
  @@java_classes_loaded = false
@@ -91,77 +178,116 @@ module NewRelic
91
178
  java_import 'java.lang.management.ManagementFactory'
92
179
  java_import 'com.sun.management.OperatingSystemMXBean'
93
180
  @@java_classes_loaded = true
94
- rescue => e
181
+ rescue
95
182
  end
96
183
  end
97
184
 
98
- attr_reader :depth
185
+ attr_reader :frame_stack
186
+
187
+ def initialize(type, options)
188
+ @frame_stack = []
189
+
190
+ @default_name = Helper.correctly_encoded(options[:transaction_name])
191
+ @name_from_child = nil
192
+ @name_from_api = nil
193
+ @frozen_name = nil
99
194
 
100
- def initialize(type=:controller, options={})
101
195
  @type = type
102
196
  @start_time = Time.now
103
- @apdex_start = @start_time
197
+ @apdex_start = options[:apdex_start_time] || @start_time
104
198
  @jruby_cpu_start = jruby_cpu_time
105
199
  @process_cpu_start = process_cpu
106
200
  @gc_start_snapshot = NewRelic::Agent::StatsEngine::GCProfiler.take_snapshot
107
201
  @filtered_params = options[:filtered_params] || {}
108
- @force_flag = options[:force]
109
202
  @request = options[:request]
110
203
  @exceptions = {}
111
204
  @stats_hash = StatsHash.new
112
205
  @guid = generate_guid
113
- TransactionState.get.transaction = self
206
+ @ignore_this_transaction = false
207
+ TransactionState.get.most_recent_transaction = self
114
208
  end
115
209
 
116
210
  def noticed_error_ids
117
211
  @noticed_error_ids ||= []
118
212
  end
119
213
 
120
- def name=(name)
121
- if !@name_frozen
122
- @name = name
123
- else
124
- NewRelic::Agent.logger.warn("Attempted to rename transaction to '#{name}' after transaction name was already frozen as '#{@name}'.")
125
- end
214
+ def default_name=(name)
215
+ @default_name = Helper.correctly_encoded(name)
126
216
  end
127
217
 
128
- def name_set?
129
- @name && @name != NewRelic::Agent::UNKNOWN_METRIC
218
+ def name_from_child=(name)
219
+ @name_from_child = Helper.correctly_encoded(name)
130
220
  end
131
221
 
132
- def freeze_name
133
- return if name_frozen?
134
- @name = NewRelic::Agent.instance.transaction_rules.rename(@name)
135
- @name_frozen = true
222
+ def name_from_api=(name)
223
+ if @frozen_name
224
+ NewRelic::Agent.logger.warn("Attempted to rename transaction to '#{name}' after transaction name was already frozen as '#{@frozen_name}'.")
225
+ end
226
+
227
+ @name_from_api = Helper.correctly_encoded(name)
136
228
  end
137
229
 
138
- def name_frozen?
139
- @name_frozen
230
+ def best_name
231
+ return @frozen_name if @frozen_name
232
+ return @name_from_api if @name_from_api
233
+
234
+ if @name_from_child
235
+ return @name_from_child
236
+ elsif !@frame_stack.empty?
237
+ return @frame_stack.last.name
238
+ end
239
+
240
+ return @default_name if @default_name
241
+
242
+ NewRelic::Agent::UNKNOWN_METRIC
140
243
  end
141
244
 
142
- def parent
143
- has_parent? && self.class.stack[-2]
245
+ def name_set?
246
+ (@name_from_api || @name_from_child || @default_name) ? true : false
144
247
  end
145
248
 
146
- def root?
147
- self.class.stack.size == 1
249
+ def freeze_name_and_execute_if_not_ignored
250
+ if !name_frozen?
251
+ name = NewRelic::Agent.instance.transaction_rules.rename(best_name)
252
+ @name_frozen = true
253
+
254
+ if name.nil?
255
+ @ignore_this_transaction = true
256
+ @frozen_name = best_name
257
+ else
258
+ @frozen_name = name
259
+ end
260
+ end
261
+
262
+ if block_given? && !@ignore_this_transaction
263
+ yield
264
+ end
148
265
  end
149
266
 
150
- def has_parent?
151
- self.class.stack.size > 1
267
+ def name_frozen?
268
+ @frozen_name ? true : false
269
+ end
270
+
271
+ def ignored?
272
+ @ignore_this_transaction
152
273
  end
153
274
 
154
275
  # Indicate that we are entering a measured controller action or task.
155
276
  # Make sure you unwind every push with a pop call.
156
- def start(transaction_type)
157
- @name = NewRelic::Agent::UNKNOWN_METRIC
277
+ def start()
278
+ return if !NewRelic::Agent.is_execution_traced?
158
279
 
159
- transaction_sampler.notice_first_scope_push(start_time)
160
- sql_sampler.notice_first_scope_push(start_time)
280
+ transaction_sampler.on_start_transaction(start_time, uri, filtered_params)
281
+ sql_sampler.on_start_transaction(start_time, uri, filtered_params)
282
+ NewRelic::Agent.instance.events.notify(:start_transaction)
283
+ NewRelic::Agent::BusyCalculator.dispatcher_start(start_time)
161
284
 
162
- agent.stats_engine.start_transaction
163
- transaction_sampler.notice_transaction(uri, filtered_params)
164
- sql_sampler.notice_transaction(uri, filtered_params)
285
+ @trace_options = {
286
+ :metric => true,
287
+ :scoped_metric => false,
288
+ :deduct_call_time_from_parent => true
289
+ }
290
+ _, @expected_scope = NewRelic::Agent::MethodTracer::TraceExecutionScoped.trace_execution_scoped_header(@trace_options, start_time.to_f)
165
291
  end
166
292
 
167
293
  # Indicate that you don't want to keep the currently saved transaction
@@ -185,35 +311,55 @@ module NewRelic
185
311
  transaction_sampler.ignore_transaction
186
312
  end
187
313
 
314
+ def summary_metrics
315
+ metric_parser = NewRelic::MetricParser::MetricParser.for_metric_named(@frozen_name)
316
+ metric_parser.summary_metrics
317
+ end
318
+
319
+ def stop(end_time, opts)
320
+ return if !NewRelic::Agent.is_execution_traced?
321
+ freeze_name_and_execute_if_not_ignored
322
+
323
+ name = @frozen_name
324
+ metrics = summary_metrics
325
+
326
+ if @name_from_child
327
+ name = Transaction.nested_transaction_name(@default_name)
328
+ metrics << @frozen_name
329
+ @trace_options[:scoped_metric] = true
330
+ end
331
+
332
+ NewRelic::Agent::MethodTracer::TraceExecutionScoped.trace_execution_scoped_footer(
333
+ start_time.to_f,
334
+ name,
335
+ metrics,
336
+ @expected_scope,
337
+ @trace_options,
338
+ end_time.to_f)
188
339
 
189
- # Unwind one stack level. It knows if it's back at the outermost caller and
190
- # does the appropriate wrapup of the context.
191
- def stop(fallback_name=::NewRelic::Agent::UNKNOWN_METRIC, end_time=Time.now)
192
- @name = fallback_name unless name_set? || name_frozen?
193
- freeze_name
194
340
  log_underflow if @type.nil?
341
+ NewRelic::Agent::BusyCalculator.dispatcher_finish(end_time)
342
+
343
+ unless @ignore_this_transaction
344
+ # these record metrics so need to be done before merging stats
195
345
 
196
- # these record metrics so need to be done before merging stats
197
- if self.root?
198
346
  # this one records metrics and wants to happen
199
347
  # before the transaction sampler is finished
200
- if traced?
201
- record_transaction_cpu
202
- gc_stop_snapshot = NewRelic::Agent::StatsEngine::GCProfiler.take_snapshot
203
- gc_delta = NewRelic::Agent::StatsEngine::GCProfiler.record_delta(
204
- gc_start_snapshot, gc_stop_snapshot)
205
- end
206
- @transaction_trace = transaction_sampler.notice_scope_empty(self, Time.now, gc_delta)
207
- sql_sampler.notice_scope_empty(@name)
208
- end
348
+ record_transaction_cpu
349
+ gc_stop_snapshot = NewRelic::Agent::StatsEngine::GCProfiler.take_snapshot
350
+ gc_delta = NewRelic::Agent::StatsEngine::GCProfiler.record_delta(
351
+ gc_start_snapshot, gc_stop_snapshot)
352
+ @transaction_trace = transaction_sampler.on_finishing_transaction(self, Time.now, gc_delta)
353
+ sql_sampler.on_finishing_transaction(@frozen_name)
354
+
355
+ record_apdex(end_time, opts[:exception_encountered]) unless opts[:ignore_apdex]
356
+ NewRelic::Agent::Instrumentation::QueueTime.record_frontend_metrics(apdex_start, start_time) if queue_time > 0.0
357
+ NewRelic::Agent::TransactionState.get.request_ignore_enduser = true if opts[:ignore_enduser]
209
358
 
210
- record_exceptions
211
- merge_stats_hash
359
+ record_exceptions
360
+ merge_stats_hash
212
361
 
213
- # these tear everything down so need to be done after merging stats
214
- if self.root?
215
362
  send_transaction_finished_event(start_time, end_time)
216
- agent.stats_engine.end_transaction
217
363
  end
218
364
  end
219
365
 
@@ -221,7 +367,7 @@ module NewRelic
221
367
  # values and sampler can't be successfully modified from this event.
222
368
  def send_transaction_finished_event(start_time, end_time)
223
369
  payload = {
224
- :name => @name,
370
+ :name => @frozen_name,
225
371
  :type => @type,
226
372
  :start_timestamp => start_time.to_f,
227
373
  :duration => end_time.to_f - start_time.to_f,
@@ -248,14 +394,18 @@ module NewRelic
248
394
  end
249
395
  end
250
396
 
397
+ def log_underflow
398
+ NewRelic::Agent.logger.error "Underflow in transaction: #{caller.join("\n ")}"
399
+ end
400
+
251
401
  def merge_stats_hash
252
- stats_hash.resolve_scopes!(@name)
402
+ stats_hash.resolve_scopes!(best_name)
253
403
  NewRelic::Agent.instance.stats_engine.merge!(stats_hash)
254
404
  end
255
405
 
256
406
  def record_exceptions
257
407
  @exceptions.each do |exception, options|
258
- options[:metric] = @name
408
+ options[:metric] = best_name
259
409
  agent.error_collector.notice_error(exception, options)
260
410
  end
261
411
  end
@@ -281,10 +431,10 @@ module NewRelic
281
431
  end
282
432
 
283
433
  def self.extract_request_options(options)
284
- request = options.delete(:request)
285
- if request
286
- options[:referer] = referer_from_request(request)
287
- options[:uri] = uri_from_request(request)
434
+ req = options.delete(:request)
435
+ if req
436
+ options[:referer] = referer_from_request(req)
437
+ options[:uri] = uri_from_request(req)
288
438
  end
289
439
  options
290
440
  end
@@ -292,12 +442,12 @@ module NewRelic
292
442
  # If we aren't currently in a transaction, but found the remains of one
293
443
  # just finished in the TransactionState, use those custom params!
294
444
  def self.extract_finished_transaction_options(options)
295
- finished_txn = NewRelic::Agent::TransactionState.get.transaction
445
+ finished_txn = NewRelic::Agent::TransactionState.get.most_recent_transaction
296
446
  if finished_txn
297
447
  custom_params = options.fetch(:custom_params, {})
298
448
  custom_params.merge!(finished_txn.custom_parameters)
299
449
  options = options.merge(:custom_params => custom_params)
300
- options[:metric] = finished_txn.name
450
+ options[:metric] = finished_txn.best_name
301
451
  end
302
452
  options
303
453
  end
@@ -333,17 +483,18 @@ module NewRelic
333
483
  def record_apdex(end_time=Time.now, is_error=nil)
334
484
  return unless recording_web_transaction? && NewRelic::Agent.is_execution_traced?
335
485
 
336
- freeze_name
337
- action_duration = end_time - start_time
338
- total_duration = end_time - apdex_start
339
- is_error = is_error.nil? ? !exceptions.empty? : is_error
486
+ freeze_name_and_execute_if_not_ignored do
487
+ action_duration = end_time - start_time
488
+ total_duration = end_time - apdex_start
489
+ is_error = is_error.nil? ? !exceptions.empty? : is_error
340
490
 
341
- apdex_bucket_global = self.class.apdex_bucket(total_duration, is_error, apdex_t)
342
- apdex_bucket_txn = self.class.apdex_bucket(action_duration, is_error, apdex_t)
491
+ apdex_bucket_global = self.class.apdex_bucket(total_duration, is_error, apdex_t)
492
+ apdex_bucket_txn = self.class.apdex_bucket(action_duration, is_error, apdex_t)
343
493
 
344
- @stats_hash.record(APDEX_METRIC_SPEC, apdex_bucket_global, apdex_t)
345
- txn_apdex_metric = NewRelic::MetricSpec.new(@name.gsub(/^[^\/]+\//, 'Apdex/'))
346
- @stats_hash.record(txn_apdex_metric, apdex_bucket_txn, apdex_t)
494
+ @stats_hash.record(APDEX_METRIC_SPEC, apdex_bucket_global, apdex_t)
495
+ txn_apdex_metric = NewRelic::MetricSpec.new(@frozen_name.gsub(/^[^\/]+\//, 'Apdex/'))
496
+ @stats_hash.record(txn_apdex_metric, apdex_bucket_txn, apdex_t)
497
+ end
347
498
  end
348
499
 
349
500
  def apdex_t
@@ -352,7 +503,7 @@ module NewRelic
352
503
 
353
504
  def transaction_specific_apdex_t
354
505
  key = :web_transactions_apdex
355
- Agent.config[key] && Agent.config[key][self.name]
506
+ Agent.config[key] && Agent.config[key][best_name]
356
507
  end
357
508
 
358
509
  # Yield to a block that is run with a database metric name context. This means
@@ -412,22 +563,22 @@ module NewRelic
412
563
 
413
564
  # Make a safe attempt to get the referer from a request object, generally successful when
414
565
  # it's a Rack request.
415
- def self.referer_from_request(request)
416
- if request && request.respond_to?(:referer)
417
- request.referer.to_s.split('?').first
566
+ def self.referer_from_request(req)
567
+ if req && req.respond_to?(:referer)
568
+ req.referer.to_s.split('?').first
418
569
  end
419
570
  end
420
571
 
421
572
  # Make a safe attempt to get the URI, without the host and query string.
422
- def self.uri_from_request(request)
573
+ def self.uri_from_request(req)
423
574
  approximate_uri = case
424
- when request.respond_to?(:fullpath) then request.fullpath
425
- when request.respond_to?(:path) then request.path
426
- when request.respond_to?(:request_uri) then request.request_uri
427
- when request.respond_to?(:uri) then request.uri
428
- when request.respond_to?(:url) then request.url
575
+ when req.respond_to?(:fullpath ) then req.fullpath
576
+ when req.respond_to?(:path ) then req.path
577
+ when req.respond_to?(:request_uri) then req.request_uri
578
+ when req.respond_to?(:uri ) then req.uri
579
+ when req.respond_to?(:url ) then req.url
429
580
  end
430
- return approximate_uri[%r{^(https?://.*?)?(/[^?]*)}, 2] || '/' if approximate_uri # '
581
+ return approximate_uri[%r{^(https?://.*?)?(/[^?]*)}, 2] || '/' if approximate_uri
431
582
  end
432
583
 
433
584
 
@@ -449,6 +600,25 @@ module NewRelic
449
600
  end
450
601
  end
451
602
 
603
+ def cpu_burn
604
+ normal_cpu_burn || jruby_cpu_burn
605
+ end
606
+
607
+ def normal_cpu_burn
608
+ return unless @process_cpu_start
609
+ process_cpu - @process_cpu_start
610
+ end
611
+
612
+ def jruby_cpu_burn
613
+ return unless @jruby_cpu_start
614
+ jruby_cpu_time - @jruby_cpu_start
615
+ end
616
+
617
+ def record_transaction_cpu
618
+ burn = cpu_burn
619
+ transaction_sampler.notice_transaction_cpu_time(burn) if burn
620
+ end
621
+
452
622
  private
453
623
 
454
624
  def process_cpu
@@ -457,7 +627,7 @@ module NewRelic
457
627
  p.stime + p.utime
458
628
  end
459
629
 
460
- def jruby_cpu_time # :nodoc:
630
+ def jruby_cpu_time
461
631
  return nil unless @@java_classes_loaded
462
632
  threadMBean = ManagementFactory.getThreadMXBean()
463
633
  java_utime = threadMBean.getCurrentThreadUserTime() # ns