wd_newrelic_rpm 3.5.5 → 3.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGELOG +60 -0
  3. data/Rakefile +14 -18
  4. data/gem-public_cert.pem +20 -0
  5. data/lib/new_relic/agent.rb +3 -0
  6. data/lib/new_relic/agent/agent.rb +86 -97
  7. data/lib/new_relic/agent/agent_logger.rb +9 -1
  8. data/lib/new_relic/agent/busy_calculator.rb +5 -0
  9. data/lib/new_relic/agent/configuration/defaults.rb +3 -3
  10. data/lib/new_relic/agent/configuration/manager.rb +12 -0
  11. data/lib/new_relic/agent/configuration/mask_defaults.rb +1 -0
  12. data/lib/new_relic/agent/configuration/yaml_source.rb +5 -1
  13. data/lib/new_relic/agent/cross_process_monitoring.rb +164 -20
  14. data/lib/new_relic/agent/error_collector.rb +13 -2
  15. data/lib/new_relic/agent/event_listener.rb +39 -0
  16. data/lib/new_relic/agent/instrumentation/browser_monitoring_timings.rb +18 -8
  17. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +1 -1
  18. data/lib/new_relic/agent/instrumentation/sinatra.rb +8 -1
  19. data/lib/new_relic/agent/new_relic_service.rb +90 -10
  20. data/lib/new_relic/agent/pipe_service.rb +9 -0
  21. data/lib/new_relic/agent/sql_sampler.rb +10 -3
  22. data/lib/new_relic/agent/stats_engine/transactions.rb +1 -0
  23. data/lib/new_relic/agent/thread_profiler.rb +20 -7
  24. data/lib/new_relic/agent/worker_loop.rb +2 -1
  25. data/lib/new_relic/coerce.rb +37 -0
  26. data/lib/new_relic/commands/deployments.rb +1 -1
  27. data/lib/new_relic/control/frameworks/rails.rb +29 -5
  28. data/lib/new_relic/control/frameworks/rails3.rb +2 -11
  29. data/lib/new_relic/control/instance_methods.rb +11 -7
  30. data/lib/new_relic/control/server_methods.rb +5 -37
  31. data/lib/new_relic/latest_changes.rb +31 -0
  32. data/lib/new_relic/local_environment.rb +1 -1
  33. data/lib/new_relic/metric_data.rb +13 -2
  34. data/lib/new_relic/noticed_error.rb +8 -1
  35. data/lib/new_relic/rack/agent_hooks.rb +20 -0
  36. data/lib/new_relic/rack/error_collector.rb +11 -1
  37. data/lib/new_relic/recipes.rb +32 -10
  38. data/lib/new_relic/transaction_sample.rb +12 -3
  39. data/lib/new_relic/transaction_sample/segment.rb +6 -3
  40. data/lib/new_relic/version.rb +10 -15
  41. data/newrelic.yml +12 -19
  42. data/newrelic_rpm.gemspec +22 -464
  43. data/test/multiverse/.gitignore +1 -0
  44. data/test/multiverse/lib/multiverse/environment.rb +1 -1
  45. data/test/multiverse/lib/multiverse/suite.rb +2 -0
  46. data/test/multiverse/suites/active_record/Envfile +3 -3
  47. data/test/multiverse/suites/active_record/ar_method_aliasing.rb +1 -1
  48. data/test/multiverse/suites/active_record/config/newrelic.yml +2 -2
  49. data/test/multiverse/suites/agent_only/Envfile +2 -1
  50. data/test/multiverse/suites/agent_only/config/newrelic.yml +3 -1
  51. data/test/multiverse/suites/agent_only/cross_process_test.rb +56 -0
  52. data/test/multiverse/suites/{logging → agent_only}/logging_test.rb +42 -22
  53. data/test/multiverse/suites/agent_only/no_dns_resolv.rb +17 -0
  54. data/test/multiverse/suites/{rum_auto_instrumentation/sanity_test.rb → agent_only/rum_instrumentation_test.rb} +25 -46
  55. data/test/multiverse/suites/agent_only/service_timeout_test.rb +6 -3
  56. data/test/multiverse/suites/agent_only/ssl_test.rb +22 -0
  57. data/test/multiverse/suites/{no_load → agent_only}/start_up_test.rb +9 -2
  58. data/test/multiverse/suites/agent_only/testing_app.rb +17 -0
  59. data/test/multiverse/suites/agent_only/thread_profiling_test.rb +6 -5
  60. data/test/multiverse/suites/datamapper/config/newrelic.yml +1 -1
  61. data/test/multiverse/suites/{rails_3_queue_time → rails}/Envfile +3 -0
  62. data/test/multiverse/suites/rails/app.rb +49 -0
  63. data/test/multiverse/suites/{rails_3_views → rails}/app/views/foos/_foo.html.haml +0 -0
  64. data/test/multiverse/suites/{rails_3_views/app/views/test → rails/app/views/views}/_a_partial.html.erb +0 -0
  65. data/test/multiverse/suites/{rails_3_views/app/views/test → rails/app/views/views}/_mid_partial.html.erb +0 -0
  66. data/test/multiverse/suites/{rails_3_views/app/views/test → rails/app/views/views}/_top_partial.html.erb +0 -0
  67. data/test/multiverse/suites/{rails_3_views/app/views/test → rails/app/views/views}/deep_partial.html.erb +0 -0
  68. data/test/multiverse/suites/{rails_3_views/app/views/test → rails/app/views/views}/haml_view.html.haml +0 -0
  69. data/test/multiverse/suites/{rails_3_views/app/views/test → rails/app/views/views}/index.html.erb +0 -0
  70. data/test/multiverse/suites/rails/config/newrelic.yml +32 -0
  71. data/test/multiverse/suites/{rails_3_error_tracing → rails}/error_tracing_test.rb +51 -88
  72. data/test/multiverse/suites/rails/gc_instrumentation_test.rb +79 -0
  73. data/test/multiverse/suites/{rails_3_queue_time → rails}/queue_time_test.rb +3 -23
  74. data/test/multiverse/suites/{rails_3_views → rails}/view_instrumentation_test.rb +21 -61
  75. data/test/multiverse/suites/resque/Envfile +7 -4
  76. data/test/multiverse/suites/resque/Rakefile +8 -0
  77. data/test/multiverse/suites/resque/config/newrelic.yml +1 -1
  78. data/test/multiverse/suites/resque/instrumentation_test.rb +118 -41
  79. data/test/multiverse/suites/resque/resque_setup.rb +15 -0
  80. data/test/multiverse/suites/sinatra/config/newrelic.yml +1 -2
  81. data/test/multiverse/suites/sinatra/sinatra_error_tracing_test.rb +38 -0
  82. data/test/multiverse/suites/sinatra/sinatra_test.rb +17 -0
  83. data/test/multiverse/test/suite_examples/one/a/config/newrelic.yml +1 -1
  84. data/test/multiverse/test/suite_examples/one/b/config/newrelic.yml +1 -1
  85. data/test/new_relic/agent/agent/connect_test.rb +24 -100
  86. data/test/new_relic/agent/agent/start_worker_thread_test.rb +3 -3
  87. data/test/new_relic/agent/agent_test.rb +126 -31
  88. data/test/new_relic/agent/browser_monitoring_test.rb +1 -1
  89. data/test/new_relic/agent/busy_calculator_test.rb +8 -0
  90. data/test/new_relic/agent/configuration/manager_test.rb +28 -0
  91. data/test/new_relic/agent/configuration/yaml_source_test.rb +12 -2
  92. data/test/new_relic/agent/cross_process_monitoring_test.rb +144 -31
  93. data/test/new_relic/agent/error_collector_test.rb +16 -0
  94. data/test/new_relic/agent/event_listener_test.rb +46 -0
  95. data/test/new_relic/agent/instrumentation/browser_monitoring_timings_test.rb +57 -30
  96. data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +1 -0
  97. data/test/new_relic/agent/new_relic_service_test.rb +95 -2
  98. data/test/new_relic/agent/pipe_channel_manager_test.rb +3 -3
  99. data/test/new_relic/agent/pipe_service_test.rb +21 -1
  100. data/test/new_relic/agent/rpm_agent_test.rb +1 -1
  101. data/test/new_relic/agent/sql_sampler_test.rb +20 -0
  102. data/test/new_relic/agent/thread_profiler_test.rb +53 -8
  103. data/test/new_relic/agent/worker_loop_test.rb +19 -16
  104. data/test/new_relic/agent_test.rb +1 -2
  105. data/test/new_relic/coerce_test.rb +65 -0
  106. data/test/new_relic/command/deployments_test.rb +1 -1
  107. data/test/new_relic/control_test.rb +23 -44
  108. data/test/new_relic/fake_collector.rb +34 -6
  109. data/test/new_relic/local_environment_test.rb +1 -1
  110. data/test/new_relic/metric_data_test.rb +29 -0
  111. data/test/new_relic/noticed_error_test.rb +8 -0
  112. data/test/new_relic/rack/agent_hooks_test.rb +30 -0
  113. data/test/new_relic/rack/error_collector_test.rb +16 -0
  114. data/test/new_relic/transaction_sample/segment_test.rb +7 -0
  115. data/test/new_relic/transaction_sample_test.rb +36 -8
  116. data/test/new_relic/version_number_test.rb +6 -30
  117. data/test/script/ci.sh +6 -5
  118. data/test/test_contexts.rb +2 -1
  119. data/test/test_helper.rb +23 -6
  120. data/ui/helpers/google_pie_chart.rb +1 -0
  121. metadata +68 -67
  122. data/newrelic_rpm.gemspec.erb +0 -54
  123. data/test/fixtures/gemspec_no_build.rb +0 -442
  124. data/test/fixtures/gemspec_with_build.rb +0 -442
  125. data/test/fixtures/gemspec_with_build_and_stage.rb +0 -442
  126. data/test/multiverse/suites/logging/Envfile +0 -4
  127. data/test/multiverse/suites/logging/config/newrelic.yml +0 -22
  128. data/test/multiverse/suites/monitor_mode_false/Envfile +0 -2
  129. data/test/multiverse/suites/monitor_mode_false/config/newrelic.yml +0 -25
  130. data/test/multiverse/suites/monitor_mode_false/no_dns_resolv.rb +0 -29
  131. data/test/multiverse/suites/no_load/Envfile +0 -2
  132. data/test/multiverse/suites/no_load/config/newrelic.yml +0 -22
  133. data/test/multiverse/suites/rails_3_error_tracing/Envfile +0 -15
  134. data/test/multiverse/suites/rails_3_error_tracing/config/newrelic.yml +0 -165
  135. data/test/multiverse/suites/rails_3_gc/Envfile +0 -8
  136. data/test/multiverse/suites/rails_3_gc/config/newrelic.yml +0 -167
  137. data/test/multiverse/suites/rails_3_gc/instrumentation_test.rb +0 -92
  138. data/test/multiverse/suites/rails_3_queue_time/config/newrelic.yml +0 -165
  139. data/test/multiverse/suites/rails_3_views/.gitignore +0 -3
  140. data/test/multiverse/suites/rails_3_views/Envfile +0 -16
  141. data/test/multiverse/suites/rails_3_views/config/newrelic.yml +0 -164
  142. data/test/multiverse/suites/resque/dump.rdb +0 -0
  143. data/test/multiverse/suites/rum_auto_instrumentation/Envfile +0 -4
  144. data/test/multiverse/suites/rum_auto_instrumentation/config/newrelic.yml +0 -24
  145. data/test/multiverse/suites/rum_auto_instrumentation/responses/worst_case_small.html +0 -5000
  146. data/test/new_relic/fake_service.rb +0 -53
@@ -32,6 +32,10 @@ module NewRelic
32
32
  @log.debug(format_messages(msgs))
33
33
  end
34
34
 
35
+ def is_startup_logger?
36
+ false
37
+ end
38
+
35
39
  # Allows for passing exceptions in explicitly, which format with backtrace
36
40
  def format_messages(msgs)
37
41
  msgs.map do |msg|
@@ -120,12 +124,16 @@ module NewRelic
120
124
  end
121
125
  end
122
126
 
123
- # BBase class for startup logging and testing in multiverse
127
+ # Base class for startup logging and testing in multiverse
124
128
  class MemoryLogger
125
129
  def initialize
126
130
  @messages = []
127
131
  end
128
132
 
133
+ def is_startup_logger?
134
+ true
135
+ end
136
+
129
137
  attr_accessor :messages, :level
130
138
 
131
139
  def fatal(*msgs)
@@ -31,10 +31,15 @@ module NewRelic
31
31
  # instance variable accumulator. this is harvested when we send
32
32
  # data to the server
33
33
  def dispatcher_finish(end_time = nil)
34
+ # If #dispatcher_start hasn't been called at least once, abort early
35
+ return unless Thread.current[:busy_entries]
36
+
34
37
  end_time ||= time_now
35
38
  callers = Thread.current[:busy_entries] -= 1
39
+
36
40
  # Ignore nested calls
37
41
  return if callers > 0
42
+
38
43
  @lock.synchronize do
39
44
  if @entrypoint_stack.empty?
40
45
  ::NewRelic::Agent.logger.warn("Stack underflow tracking dispatcher entry and exit!\n #{caller.join(" \n")}")
@@ -30,7 +30,7 @@ module NewRelic
30
30
  when 4
31
31
  :rails4
32
32
  else
33
- Agent.logger.error "Detected unsupported Rails version #{Rails::VERSION::STRING}"
33
+ ::NewRelic::Agent::Agent.logger.error "Detected unsupported Rails version #{Rails::VERSION::STRING}"
34
34
  end
35
35
  when defined?(::Sinatra) && defined?(::Sinatra::Base) then :sinatra
36
36
  when defined?(::NewRelic::IA) then :external
@@ -55,8 +55,8 @@ module NewRelic
55
55
  :api_host => 'rpm.newrelic.com',
56
56
  :port => Proc.new { self[:ssl] ? 443 : 80 },
57
57
  :api_port => Proc.new { self[:port] },
58
- :ssl => false,
59
- :verify_certificate => false,
58
+ :ssl => true,
59
+ :verify_certificate => true,
60
60
  :sync_startup => false,
61
61
  :send_data_on_exit => true,
62
62
  :post_size_limit => 2 * 1024 * 1024, # 2 megs
@@ -20,10 +20,14 @@ module NewRelic
20
20
  end
21
21
 
22
22
  def apply_config(source, level=0)
23
+ was_finished = finished_configuring?
24
+
23
25
  invoke_callbacks(:add, source)
24
26
  @config_stack.insert(level, source.freeze)
25
27
  reset_cache
26
28
  log_config(:add, source)
29
+
30
+ notify_finished_configuring if !was_finished && finished_configuring?
27
31
  end
28
32
 
29
33
  def remove_config(source=nil)
@@ -86,6 +90,14 @@ module NewRelic
86
90
  end
87
91
  end
88
92
 
93
+ def notify_finished_configuring
94
+ NewRelic::Agent.instance.events.notify(:finished_configuring)
95
+ end
96
+
97
+ def finished_configuring?
98
+ @config_stack.any? {|s| s.is_a?(ServerSource)}
99
+ end
100
+
89
101
  def flattened
90
102
  @config_stack.reverse.inject({}) do |flat,layer|
91
103
  thawed_layer = layer.dup
@@ -4,6 +4,7 @@ module NewRelic
4
4
  MASK_DEFAULTS = {
5
5
  :'thread_profiler' => Proc.new { !NewRelic::Agent::ThreadProfiler.is_supported? },
6
6
  :'thread_profiler.enabled' => Proc.new { !NewRelic::Agent::ThreadProfiler.is_supported? },
7
+ :verify_certificate => Proc.new{ true }
7
8
  }
8
9
  end
9
10
  end
@@ -25,7 +25,11 @@ module NewRelic
25
25
  license_key = ''
26
26
 
27
27
  erb = ERB.new(file).result(binding)
28
- config = merge!(YAML.load(erb)[env] || {})
28
+ confighash = YAML.load(erb)
29
+ ::NewRelic::Agent.logger.error("Config (#{path}) doesn't include a '#{env}' environment!") unless
30
+ confighash.key?(env)
31
+
32
+ config = merge!(confighash[env] || {})
29
33
  rescue ScriptError, StandardError => e
30
34
  ::NewRelic::Agent.logger.error("Unable to read configuration file #{path}: #{e}")
31
35
  end
@@ -1,43 +1,187 @@
1
+ require 'new_relic/rack/agent_hooks'
2
+ require 'new_relic/agent/thread'
3
+
1
4
  module NewRelic
2
5
  module Agent
3
- module CrossProcessMonitoring
6
+ class CrossProcessMonitor
7
+
8
+ def initialize(events = nil)
9
+ # When we're starting up for real in the agent, we get passed the events
10
+ # Other spots can pull from the agent, during startup the agent doesn't exist yet!
11
+ events ||= Agent.instance.events
12
+ @trusted_ids = []
13
+
14
+ events.subscribe(:finished_configuring) do
15
+ finish_setup(Agent.config)
16
+ register_event_listeners
17
+ end
18
+ end
19
+
20
+ def finish_setup(config)
21
+ @cross_process_id = config[:cross_process_id]
22
+ @encoding_key = config[:encoding_key]
23
+ @encoding_bytes = get_bytes(@encoding_key) unless @encoding_key.nil?
24
+ @trusted_ids = config[:trusted_account_ids] || []
25
+ end
26
+
27
+ # Expected sequence of events:
28
+ # :before_call will save our cross process request id to the thread
29
+ # :start_transaction will get called when a transaction starts up
30
+ # :after_call will write our response headers/metrics and clean up the thread
31
+ def register_event_listeners
32
+ NewRelic::Agent.logger.debug("Wiring up Cross Process monitoring to events after finished configuring")
33
+
34
+ events = Agent.instance.events
35
+ events.subscribe(:before_call) do |env|
36
+ save_client_cross_process_id(env)
37
+ end
38
+
39
+ events.subscribe(:start_transaction) do |name|
40
+ set_transaction_custom_parameters
41
+ end
42
+
43
+ events.subscribe(:after_call) do |env, (status_code, headers, body)|
44
+ insert_response_header(env, headers)
45
+ end
46
+
47
+ events.subscribe(:notice_error) do |_, options|
48
+ set_error_custom_parameters(options)
49
+ end
50
+ end
51
+
52
+ # Because we aren't in the right spot when our transaction actually
53
+ # starts, hold client_cross_process_id we get thread local until then.
54
+ THREAD_ID_KEY = :newrelic_client_cross_process_id
55
+
56
+ def save_client_cross_process_id(request_headers)
57
+ if should_process_request(request_headers)
58
+ NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = decoded_id(request_headers)
59
+ end
60
+ end
4
61
 
5
- module_function
62
+ def clear_client_cross_process_id
63
+ NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = nil
64
+ end
6
65
 
7
- def insert_response_header(request, response)
8
- if Agent.config[:'cross_process.enabled'] &&
9
- NewRelic::Agent.instance.cross_process_id && (id = id_from_request(request))
66
+ def client_cross_process_id
67
+ NewRelic::Agent::AgentThread.current[THREAD_ID_KEY]
68
+ end
10
69
 
11
- content_length = -1
70
+ def insert_response_header(request_headers, response_headers)
71
+ unless client_cross_process_id.nil?
12
72
  timings = NewRelic::Agent::BrowserMonitoring.timings
73
+ content_length = content_length_from_request(request_headers)
74
+
75
+ set_response_headers(response_headers, timings, content_length)
76
+ set_metrics(client_cross_process_id, timings)
77
+
78
+ clear_client_cross_process_id
79
+ end
80
+ end
81
+
82
+ def should_process_request(request_headers)
83
+ return Agent.config[:'cross_process.enabled'] &&
84
+ @cross_process_id &&
85
+ trusts?(request_headers)
86
+ end
87
+
88
+ # Expects an ID of format "12#345", and will only accept that!
89
+ def trusts?(request)
90
+ id = decoded_id(request)
91
+ split_id = id.match(/(\d+)#\d+/)
92
+ return false if split_id.nil?
13
93
 
14
- # FIXME the transaction name might not be properly encoded. use a json generator
15
- payload = %[["#{NewRelic::Agent.instance.cross_process_id}","#{timings.transaction_name}",#{timings.queue_time_in_millis},#{timings.app_time_in_millis},#{content_length}] ]
16
- payload = obfuscate_with_key(payload, NewRelic::Agent.instance.cross_process_encoding_bytes)
94
+ @trusted_ids.include?(split_id.captures.first.to_i)
95
+ end
96
+
97
+ def set_response_headers(response_headers, timings, content_length)
98
+ response_headers['X-NewRelic-App-Data'] = build_payload(timings, content_length)
99
+ end
100
+
101
+ def build_payload(timings, content_length)
102
+
103
+ # FIXME The transaction name might not be properly encoded. use a json generator
104
+ # For now we just handle quote characters by dropping them
105
+ transaction_name = timings.transaction_name.gsub(/["']/, "")
106
+
107
+ payload = %[["#{@cross_process_id}","#{transaction_name}",#{timings.queue_time_in_seconds},#{timings.app_time_in_seconds},#{content_length}] ]
108
+ payload = obfuscate_with_key(payload)
109
+ end
110
+
111
+ def set_transaction_custom_parameters
112
+ # We expect to get the before call to set the id (if we have it) before
113
+ # this, and then write our custom parameter when the transaction starts
114
+ NewRelic::Agent.add_custom_parameters(:client_cross_process_id => client_cross_process_id) unless client_cross_process_id.nil?
115
+ end
116
+
117
+ def set_error_custom_parameters(options)
118
+ options[:client_cross_process_id] = client_cross_process_id unless client_cross_process_id.nil?
119
+ end
120
+
121
+ def set_metrics(id, timings)
122
+ metric = NewRelic::Agent.instance.stats_engine.get_stats_no_scope("ClientApplication/#{id}/all")
123
+ metric.record_data_point(timings.app_time_in_seconds)
124
+ end
125
+
126
+ def obfuscate_with_key(text)
127
+ Base64.encode64(encode_with_key(text)).chomp
128
+ end
17
129
 
18
- response['X-NewRelic-App-Data'] = payload
19
- #FIXME generate ClientApplication metric. id must be decoded first
20
- # String metricName = MessageFormat.format("ClientApplication/{0}/all", id);
130
+ def decode_with_key(text)
131
+ encode_with_key(Base64.decode64(text))
132
+ end
133
+
134
+ NEWRELIC_ID_HEADER_KEYS = %w{X-NewRelic-ID HTTP_X_NEWRELIC_ID X_NEWRELIC_ID}
135
+ CONTENT_LENGTH_HEADER_KEYS = %w{Content-Length HTTP_CONTENT_LENGTH CONTENT_LENGTH}
136
+
137
+ def decoded_id(request)
138
+ encoded_id = from_headers(request, NEWRELIC_ID_HEADER_KEYS)
139
+ return "" if encoded_id.nil?
140
+
141
+ decode_with_key(encoded_id)
142
+ end
143
+
144
+ def content_length_from_request(request)
145
+ from_headers(request, CONTENT_LENGTH_HEADER_KEYS) || -1
146
+ end
147
+
148
+
149
+
150
+ private
151
+
152
+ # Ruby 1.8.6 doesn't support the bytes method on strings.
153
+ def get_bytes(value)
154
+ return [] if value.nil?
155
+
156
+ bytes = []
157
+ value.each_byte do |b|
158
+ bytes << b
21
159
  end
160
+ bytes
22
161
  end
23
162
 
24
- def obfuscate_with_key(text, key_bytes)
25
- obfuscated = ""
163
+ def encode_with_key(text)
164
+ key_bytes = @encoding_bytes
165
+
166
+ encoded = ""
26
167
  index = 0
27
168
  text.each_byte{|byte|
28
- obfuscated.concat((byte ^ key_bytes[index % key_bytes.length].to_i))
169
+ encoded.concat((byte ^ key_bytes[index % key_bytes.length].to_i))
29
170
  index+=1
30
171
  }
31
-
32
- [obfuscated].pack("m0").gsub("\n", '')
172
+ encoded
33
173
  end
34
174
 
35
- def id_from_request(request)
36
- %w{X-NewRelic-ID HTTP_X_NEWRELIC_ID X_NEWRELIC_ID}.each do |header|
37
- return request.env[header] if request.env.has_key?(header)
175
+ def from_headers(request, try_keys)
176
+ # For lookups, upcase all our keys on both sides just to be safe
177
+ upcased_keys = try_keys.map{|k| k.upcase}
178
+ upcased_keys.each do |header|
179
+ found_key = request.keys.find { |k| k.upcase == header }
180
+ return request[found_key] unless found_key.nil?
38
181
  end
39
182
  nil
40
183
  end
184
+
41
185
  end
42
186
  end
43
187
  end
@@ -21,6 +21,8 @@ module NewRelic
21
21
  # Returns a new error collector
22
22
  def initialize
23
23
  @errors = []
24
+ @seen_error_ids = []
25
+
24
26
  # lookup of exception class names to ignore. Hash for fast access
25
27
  @ignore = {}
26
28
  @capture_source = Agent.config[:'error_collector.capture_source']
@@ -92,7 +94,11 @@ module NewRelic
92
94
  end
93
95
 
94
96
  # Increments a statistic that tracks total error rate
95
- def increment_error_count!
97
+ # Be sure not to double-count same exception. This clears per harvest.
98
+ def increment_error_count!(exception)
99
+ return if @seen_error_ids.include?(exception.object_id)
100
+ @seen_error_ids << exception.object_id
101
+
96
102
  NewRelic::Agent.get_stats("Errors/all").increment_count
97
103
  end
98
104
 
@@ -102,7 +108,7 @@ module NewRelic
102
108
  def should_exit_notice_error?(exception)
103
109
  if enabled?
104
110
  if !error_is_ignored?(exception)
105
- increment_error_count!
111
+ increment_error_count!(exception)
106
112
  return exception.nil? # exit early if the exception is nil
107
113
  end
108
114
  end
@@ -211,6 +217,7 @@ module NewRelic
211
217
 
212
218
  include NoticeError
213
219
 
220
+
214
221
  # Notice the error with the given available options:
215
222
  #
216
223
  # * <tt>:uri</tt> => The request path, minus any request params or query string.
@@ -223,6 +230,7 @@ module NewRelic
223
230
  # If exception is nil, the error count is bumped and no traced error is recorded
224
231
  def notice_error(exception, options={})
225
232
  return if should_exit_notice_error?(exception)
233
+ NewRelic::Agent.instance.events.notify(:notice_error, exception, options)
226
234
  action_path = fetch_from_options(options, :metric, (NewRelic::Agent.instance.stats_engine.scope_name || ''))
227
235
  exception_options = error_params_from_options(options).merge(exception_info(exception))
228
236
  add_to_error_queue(NewRelic::NoticedError.new(action_path, exception_options, exception))
@@ -238,6 +246,9 @@ module NewRelic
238
246
  errors = @errors
239
247
  @errors = []
240
248
 
249
+ # Only expect to re-see errors on same request, so clear on harvest
250
+ @seen_error_ids = []
251
+
241
252
  if unsent_errors && !unsent_errors.empty?
242
253
  errors = unsent_errors + errors
243
254
  end
@@ -0,0 +1,39 @@
1
+ module NewRelic::Agent
2
+ # Basic mechanism for the agent instance to provide agent-wide eventing.
3
+ # It is intended to keep different pieces of the app decoupled from each other.
4
+ #
5
+ # While an EventListener could be used elsewhere, it's strongly expected
6
+ # your eventing needs should be met by the agent's instance.
7
+ class EventListener
8
+
9
+ attr_accessor :runaway_threshold
10
+
11
+ def initialize
12
+ @events = {}
13
+ @runaway_threshold = 100
14
+ end
15
+
16
+ def subscribe(event, &handler)
17
+ @events[event] ||= []
18
+ @events[event] << handler
19
+ check_for_runaway_subscriptions(event)
20
+ end
21
+
22
+ def check_for_runaway_subscriptions(event)
23
+ count = @events[event].size
24
+ NewRelic::Agent.logger.debug("Run-away event subscription on #{event}? Subscribed #{count}") if count > @runaway_threshold
25
+ end
26
+
27
+ def notify(event, *args)
28
+ return unless @events.has_key?(event)
29
+
30
+ @events[event].each do |handler|
31
+ begin
32
+ handler.call(*args)
33
+ rescue => err
34
+ NewRelic::Agent.logger.debug("Failure during notify for #{@event}", err)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -4,37 +4,47 @@ module NewRelic
4
4
  class BrowserMonitoringTimings
5
5
 
6
6
  def initialize(queue_time_in_seconds, transaction)
7
+ @now = Time.now.to_i
7
8
  if transaction.nil?
8
9
  @start_time_in_seconds = 0.0
9
10
  else
10
11
  @transaction_name = transaction.transaction_name
11
- @start_time_in_seconds = transaction.start_time
12
+ @start_time_in_seconds = transaction.start_time.to_i
12
13
  end
13
14
 
14
- @queue_time_in_seconds = queue_time_in_seconds
15
+ @queue_time_in_seconds = clamp_to_positive(queue_time_in_seconds)
15
16
  end
16
17
 
17
- attr_reader :transaction_name
18
+ attr_reader :transaction_name,
19
+ :start_time_in_seconds, :queue_time_in_seconds
18
20
 
19
21
  def start_time_in_millis
20
22
  convert_to_milliseconds(@start_time_in_seconds)
21
23
  end
22
24
 
23
25
  def queue_time_in_millis
24
- convert_to_milliseconds(@queue_time_in_seconds)
26
+ convert_to_milliseconds(queue_time_in_seconds)
25
27
  end
26
28
 
27
29
  def app_time_in_millis
28
- convert_to_milliseconds(Time.now - @start_time_in_seconds)
30
+ convert_to_milliseconds(app_time_in_seconds)
31
+ end
32
+
33
+ def app_time_in_seconds
34
+ @now - @start_time_in_seconds
29
35
  end
30
36
 
31
37
  private
32
38
 
33
- def convert_to_milliseconds(value)
34
- value = (value.to_f * 1000.0).round
35
- return 0.0 if value < 0
39
+ def convert_to_milliseconds(value_in_seconds)
40
+ clamp_to_positive((value_in_seconds.to_f * 1000.0).round)
41
+ end
42
+
43
+ def clamp_to_positive(value)
44
+ return 0.0 if value.nil? || value < 0
36
45
  value
37
46
  end
47
+
38
48
  end
39
49
  end
40
50
  end