newrelic_rpm 2.8.0

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 (107) hide show
  1. data/LICENSE +37 -0
  2. data/README +93 -0
  3. data/Rakefile +38 -0
  4. data/install.rb +37 -0
  5. data/lib/new_relic/agent.rb +26 -0
  6. data/lib/new_relic/agent/agent.rb +762 -0
  7. data/lib/new_relic/agent/chained_call.rb +13 -0
  8. data/lib/new_relic/agent/collection_helper.rb +81 -0
  9. data/lib/new_relic/agent/error_collector.rb +105 -0
  10. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +95 -0
  11. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +151 -0
  12. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  13. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +105 -0
  14. data/lib/new_relic/agent/instrumentation/memcache.rb +18 -0
  15. data/lib/new_relic/agent/instrumentation/merb/controller.rb +17 -0
  16. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +15 -0
  17. data/lib/new_relic/agent/instrumentation/merb/errors.rb +6 -0
  18. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +35 -0
  19. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  20. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +30 -0
  21. data/lib/new_relic/agent/instrumentation/rails/errors.rb +23 -0
  22. data/lib/new_relic/agent/instrumentation/rails/rails.rb +6 -0
  23. data/lib/new_relic/agent/method_tracer.rb +171 -0
  24. data/lib/new_relic/agent/patch_const_missing.rb +31 -0
  25. data/lib/new_relic/agent/samplers/cpu.rb +29 -0
  26. data/lib/new_relic/agent/samplers/memory.rb +55 -0
  27. data/lib/new_relic/agent/samplers/mongrel.rb +26 -0
  28. data/lib/new_relic/agent/stats_engine.rb +241 -0
  29. data/lib/new_relic/agent/synchronize.rb +40 -0
  30. data/lib/new_relic/agent/transaction_sampler.rb +281 -0
  31. data/lib/new_relic/agent/worker_loop.rb +128 -0
  32. data/lib/new_relic/api/deployments.rb +92 -0
  33. data/lib/new_relic/config.rb +194 -0
  34. data/lib/new_relic/config/merb.rb +35 -0
  35. data/lib/new_relic/config/rails.rb +113 -0
  36. data/lib/new_relic/config/ruby.rb +9 -0
  37. data/lib/new_relic/local_environment.rb +108 -0
  38. data/lib/new_relic/merbtasks.rb +6 -0
  39. data/lib/new_relic/metric_data.rb +26 -0
  40. data/lib/new_relic/metric_spec.rb +39 -0
  41. data/lib/new_relic/metrics.rb +7 -0
  42. data/lib/new_relic/noticed_error.rb +21 -0
  43. data/lib/new_relic/shim_agent.rb +95 -0
  44. data/lib/new_relic/stats.rb +359 -0
  45. data/lib/new_relic/transaction_analysis.rb +122 -0
  46. data/lib/new_relic/transaction_sample.rb +499 -0
  47. data/lib/new_relic/version.rb +111 -0
  48. data/lib/new_relic_api.rb +275 -0
  49. data/lib/newrelic_rpm.rb +27 -0
  50. data/lib/tasks/agent_tests.rake +14 -0
  51. data/lib/tasks/all.rb +4 -0
  52. data/lib/tasks/install.rake +7 -0
  53. data/newrelic.yml +137 -0
  54. data/recipes/newrelic.rb +46 -0
  55. data/test/config/newrelic.yml +26 -0
  56. data/test/config/test_config.rb +9 -0
  57. data/test/new_relic/agent/mock_ar_connection.rb +40 -0
  58. data/test/new_relic/agent/mock_scope_listener.rb +23 -0
  59. data/test/new_relic/agent/model_fixture.rb +17 -0
  60. data/test/new_relic/agent/tc_active_record.rb +91 -0
  61. data/test/new_relic/agent/tc_agent.rb +112 -0
  62. data/test/new_relic/agent/tc_collection_helper.rb +104 -0
  63. data/test/new_relic/agent/tc_controller.rb +98 -0
  64. data/test/new_relic/agent/tc_dispatcher_instrumentation.rb +52 -0
  65. data/test/new_relic/agent/tc_error_collector.rb +127 -0
  66. data/test/new_relic/agent/tc_method_tracer.rb +306 -0
  67. data/test/new_relic/agent/tc_stats_engine.rb +218 -0
  68. data/test/new_relic/agent/tc_synchronize.rb +37 -0
  69. data/test/new_relic/agent/tc_transaction_sample.rb +175 -0
  70. data/test/new_relic/agent/tc_transaction_sample_builder.rb +200 -0
  71. data/test/new_relic/agent/tc_transaction_sampler.rb +305 -0
  72. data/test/new_relic/agent/tc_worker_loop.rb +101 -0
  73. data/test/new_relic/agent/testable_agent.rb +13 -0
  74. data/test/new_relic/tc_config.rb +36 -0
  75. data/test/new_relic/tc_deployments_api.rb +37 -0
  76. data/test/new_relic/tc_environment.rb +94 -0
  77. data/test/new_relic/tc_metric_spec.rb +150 -0
  78. data/test/new_relic/tc_shim_agent.rb +9 -0
  79. data/test/new_relic/tc_stats.rb +141 -0
  80. data/test/test_helper.rb +39 -0
  81. data/test/ui/tc_newrelic_helper.rb +44 -0
  82. data/ui/controllers/newrelic_controller.rb +200 -0
  83. data/ui/helpers/google_pie_chart.rb +55 -0
  84. data/ui/helpers/newrelic_helper.rb +286 -0
  85. data/ui/views/layouts/newrelic_default.rhtml +49 -0
  86. data/ui/views/newrelic/_explain_plans.rhtml +27 -0
  87. data/ui/views/newrelic/_sample.rhtml +12 -0
  88. data/ui/views/newrelic/_segment.rhtml +28 -0
  89. data/ui/views/newrelic/_segment_row.rhtml +14 -0
  90. data/ui/views/newrelic/_show_sample_detail.rhtml +22 -0
  91. data/ui/views/newrelic/_show_sample_sql.rhtml +19 -0
  92. data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
  93. data/ui/views/newrelic/_sql_row.rhtml +11 -0
  94. data/ui/views/newrelic/_stack_trace.rhtml +30 -0
  95. data/ui/views/newrelic/_table.rhtml +12 -0
  96. data/ui/views/newrelic/explain_sql.rhtml +45 -0
  97. data/ui/views/newrelic/images/arrow-close.png +0 -0
  98. data/ui/views/newrelic/images/arrow-open.png +0 -0
  99. data/ui/views/newrelic/images/blue_bar.gif +0 -0
  100. data/ui/views/newrelic/images/gray_bar.gif +0 -0
  101. data/ui/views/newrelic/index.rhtml +37 -0
  102. data/ui/views/newrelic/javascript/transaction_sample.js +107 -0
  103. data/ui/views/newrelic/sample_not_found.rhtml +2 -0
  104. data/ui/views/newrelic/show_sample.rhtml +62 -0
  105. data/ui/views/newrelic/show_source.rhtml +3 -0
  106. data/ui/views/newrelic/stylesheets/style.css +394 -0
  107. metadata +180 -0
@@ -0,0 +1,31 @@
1
+ # This class is for debugging purposes only.
2
+ #
3
+ class Module
4
+ @@newrelic_agent_thread = nil
5
+ def new_relic_const_missing(*args)
6
+ if Thread.current == @@newrelic_agent_thread
7
+ msg = "Agent background thread shouldn't be calling const_missing (#{args.inspect}) \n"
8
+ msg << caller[0..4].join(" \n")
9
+ NewRelic::Config.instance.log.warn msg
10
+ end
11
+ original_const_missing(*args)
12
+ end
13
+
14
+ def newrelic_enable_warning
15
+ Module.class_eval do
16
+ if !defined?(original_const_missing)
17
+ alias_method :original_const_missing, :const_missing
18
+ alias_method :const_missing, :new_relic_const_missing
19
+ end
20
+ end
21
+ end
22
+ def newrelic_disable_warning
23
+ Module.class_eval do
24
+ alias_method :const_missing, :original_const_missing if defined?(original_const_missing)
25
+ end
26
+ end
27
+
28
+ def newrelic_set_agent_thread(thread)
29
+ @@newrelic_agent_thread = thread
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ module NewRelic::Agent
2
+ class CPUSampler
3
+ def initialize
4
+
5
+ agent = NewRelic::Agent.instance
6
+
7
+ agent.stats_engine.add_sampled_metric("CPU/User Time") do | stats |
8
+ t = Process.times
9
+ @last_utime ||= t.utime
10
+
11
+ utime = t.utime
12
+ stats.record_data_point(utime - @last_utime) if (utime - @last_utime) >= 0
13
+ @last_utime = utime
14
+ end
15
+
16
+ agent.stats_engine.add_sampled_metric("CPU/System Time") do | stats |
17
+ t = Process.times
18
+ @last_stime ||= t.stime
19
+
20
+ stime = t.stime
21
+ stats.record_data_point(stime - @last_stime) if (stime - @last_stime) >= 0
22
+ @last_stime = stime
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ # CPU sampling like this doesn't work for jruby
29
+ NewRelic::Agent::CPUSampler.new unless defined? Java
@@ -0,0 +1,55 @@
1
+ module NewRelic::Agent
2
+ class MemorySampler
3
+ def initialize
4
+ if RUBY_PLATFORM =~ /java/
5
+ platform = %x[uname -s].downcase
6
+ else
7
+ platform = RUBY_PLATFORM.downcase
8
+ end
9
+
10
+ # macos, linux, solaris
11
+ if platform =~ /darwin|linux/
12
+ @ps = "ps -o rsz"
13
+ elsif platform =~ /freebsd/
14
+ @ps = "ps -o rss"
15
+ elsif platform =~ /solaris/
16
+ @ps = "/usr/bin/ps -o rss -p"
17
+ end
18
+ if !@ps
19
+ raise "Unsupported platform for getting memory: #{platform}"
20
+ end
21
+
22
+ if @ps
23
+ @broken = false
24
+
25
+ agent = NewRelic::Agent.instance
26
+ agent.stats_engine.add_sampled_metric("Memory/Physical") do |stats|
27
+ if !@broken
28
+ begin
29
+ process = $$
30
+ memory = `#{@ps} #{process}`.split("\n")[1].to_f / 1024
31
+
32
+ # if for some reason the ps command doesn't work on the resident os,
33
+ # then don't execute it any more.
34
+ if memory >= 0
35
+ stats.record_data_point memory
36
+ else
37
+ NewRelic::Agent.instance.log.error "Error attempting to determine resident memory for pid #{process} (got result of #{memory}, this process = #{$$}). Disabling this metric."
38
+ NewRelic::Agent.instance.log.error "Faulty command: `#{@ps}`"
39
+ @broken = true
40
+ end
41
+ rescue Exception => e
42
+ if e.is_a? Errno::ENOMEM
43
+ NewRelic::Agent.instance.log.error "Got OOM trying to determine process memory usage"
44
+ else
45
+ raise e
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ NewRelic::Agent::MemorySampler.new
@@ -0,0 +1,26 @@
1
+ # NewRelic Instrumentation for Mongrel - tracks the queue length of the mongrel server
2
+ if defined? Mongrel::HttpServer
3
+
4
+ agent = NewRelic::Agent.instance
5
+ mongrel = nil;
6
+ ObjectSpace.each_object(Mongrel::HttpServer) do |mongrel_instance|
7
+ # should only be one mongrel instance in the vm
8
+ if mongrel
9
+ agent.log.info("Discovered multiple mongrel instances in one Ruby VM. "+
10
+ "This is unexpected and might affect the Accuracy of the Mongrel Request Queue metric.")
11
+ end
12
+
13
+ mongrel = mongrel_instance
14
+ end
15
+
16
+
17
+ if mongrel
18
+ agent.stats_engine.add_sampled_metric("Mongrel/Queue Length") do |stats|
19
+ qsize = mongrel.workers.list.length
20
+ qsize -= 1 if NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.is_busy?
21
+ qsize = 0 if qsize < 0
22
+ stats.record_data_point qsize
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,241 @@
1
+
2
+ module NewRelic::Agent
3
+ class StatsEngine
4
+ POLL_PERIOD = 10
5
+
6
+ attr_accessor :log
7
+
8
+ ScopeStackElement = Struct.new(:name, :children_time, :deduct_call_time_from_parent)
9
+
10
+ class SampledItem
11
+ def initialize(stats, &callback)
12
+ @stats = stats
13
+ @callback = callback
14
+ end
15
+
16
+ def poll
17
+ @callback.call @stats
18
+ end
19
+ end
20
+
21
+ def initialize(log = Logger.new(STDERR))
22
+ @stats_hash = {}
23
+ @sampled_items = []
24
+ @scope_stack_listener = nil
25
+ @log = log
26
+
27
+ # Makes the unit tests happy
28
+ Thread::current[:newrelic_scope_stack] = nil
29
+
30
+ spawn_sampler_thread
31
+ end
32
+
33
+ def spawn_sampler_thread
34
+
35
+ return if !@sampler_process.nil? && @sampler_process == $$
36
+
37
+ # start up a thread that will periodically poll for metric samples
38
+ @sampler_thread = Thread.new do
39
+ while true do
40
+ begin
41
+ sleep POLL_PERIOD
42
+ @sampled_items.each do |sampled_item|
43
+ begin
44
+ sampled_item.poll
45
+ rescue => e
46
+ log.error e
47
+ @sampled_items.delete sampled_item
48
+ log.error "Removing #{sampled_item} from list"
49
+ log.debug e.backtrace.to_s
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ @sampler_process = $$
57
+ end
58
+
59
+ def add_scope_stack_listener(l)
60
+ fail "Can't add a scope listener midflight in a transaction" if scope_stack.any?
61
+ @scope_stack_listener = l
62
+ end
63
+
64
+ def remove_scope_stack_listener(l)
65
+ fail "Unknown stack listener trying to be removed" if @scope_stack_listener != l
66
+ @scope_stack_listener = nil
67
+ end
68
+
69
+ def push_scope(metric, time = Time.now.to_f, deduct_call_time_from_parent = true)
70
+
71
+ stack = (Thread::current[:newrelic_scope_stack] ||= [])
72
+
73
+ if @scope_stack_listener
74
+ @scope_stack_listener.notice_first_scope_push(time) if stack.empty?
75
+ @scope_stack_listener.notice_push_scope metric, time
76
+ end
77
+
78
+ scope = ScopeStackElement.new(metric, 0, deduct_call_time_from_parent)
79
+ stack.push scope
80
+
81
+ scope
82
+ end
83
+
84
+ def pop_scope(expected_scope, duration, time=Time.now.to_f)
85
+
86
+ stack = Thread::current[:newrelic_scope_stack]
87
+
88
+ scope = stack.pop
89
+
90
+ fail "unbalanced pop from blame stack: #{scope.name} != #{expected_scope.name}" if scope != expected_scope
91
+
92
+ stack.last.children_time += duration unless (stack.empty? || !scope.deduct_call_time_from_parent)
93
+
94
+ if !scope.deduct_call_time_from_parent && !stack.empty?
95
+ stack.last.children_time += scope.children_time
96
+ end
97
+
98
+ if @scope_stack_listener
99
+ @scope_stack_listener.notice_pop_scope(scope.name, time)
100
+ @scope_stack_listener.notice_scope_empty(time) if stack.empty?
101
+ end
102
+
103
+ scope
104
+ end
105
+
106
+ def peek_scope
107
+ scope_stack.last
108
+ end
109
+
110
+ def add_sampled_metric(metric_name, &sampler_callback)
111
+ stats = get_stats(metric_name, false)
112
+ @sampled_items << SampledItem.new(stats, &sampler_callback)
113
+ end
114
+
115
+ # set the name of the transaction for the current thread, which will be used
116
+ # to define the scope of all traced methods called on this thread until the
117
+ # scope stack is empty.
118
+ #
119
+ # currently the transaction name is the name of the controller action that
120
+ # is invoked via the dispatcher, but conceivably we could use other transaction
121
+ # names in the future if the traced application does more than service http request
122
+ # via controller actions
123
+ def transaction_name=(transaction)
124
+ Thread::current[:newrelic_transaction_name] = transaction
125
+ end
126
+
127
+ def transaction_name
128
+ Thread::current[:newrelic_transaction_name]
129
+ end
130
+
131
+
132
+ def lookup_stat(metric_name)
133
+ return @stats_hash[metric_name]
134
+ end
135
+ def metrics
136
+ return @stats_hash.keys
137
+ end
138
+
139
+ def get_stats_no_scope(metric_name)
140
+ stats = @stats_hash[metric_name]
141
+ if stats.nil?
142
+ stats = NewRelic::MethodTraceStats.new
143
+ @stats_hash[metric_name] = stats
144
+ end
145
+ stats
146
+ end
147
+
148
+ def get_stats(metric_name, use_scope = true)
149
+ stats = @stats_hash[metric_name]
150
+ if stats.nil?
151
+ stats = NewRelic::MethodTraceStats.new
152
+ @stats_hash[metric_name] = stats
153
+ end
154
+
155
+ if use_scope && transaction_name
156
+ spec = NewRelic::MetricSpec.new metric_name, transaction_name
157
+
158
+ scoped_stats = @stats_hash[spec]
159
+ if scoped_stats.nil?
160
+ scoped_stats = NewRelic::ScopedMethodTraceStats.new stats
161
+ @stats_hash[spec] = scoped_stats
162
+ end
163
+
164
+ stats = scoped_stats
165
+ end
166
+ return stats
167
+ end
168
+
169
+ # Note: this is not synchronized. There is still some risk in this and
170
+ # we will revisit later to see if we can make this more robust without
171
+ # sacrificing efficiency.
172
+ def harvest_timeslice_data(previous_timeslice_data, metric_ids)
173
+ timeslice_data = {}
174
+ @stats_hash.keys.each do |metric_spec|
175
+
176
+
177
+ # get a copy of the stats collected since the last harvest, and clear
178
+ # the stats inside our hash table for the next time slice.
179
+ stats = @stats_hash[metric_spec]
180
+
181
+ # we have an optimization for unscoped metrics
182
+ if !(metric_spec.is_a? NewRelic::MetricSpec)
183
+ metric_spec = NewRelic::MetricSpec.new metric_spec
184
+ end
185
+
186
+ if stats.nil?
187
+ raise "Nil stats for #{metric_spec.name} (#{metric_spec.scope})"
188
+ end
189
+
190
+ stats_copy = stats.clone
191
+ stats.reset
192
+
193
+ # if the previous timeslice data has not been reported (due to an error of some sort)
194
+ # then we need to merge this timeslice with the previously accumulated - but not sent
195
+ # data
196
+ previous_metric_data = previous_timeslice_data[metric_spec]
197
+ stats_copy.merge! previous_metric_data.stats unless previous_metric_data.nil?
198
+
199
+ stats_copy.round!
200
+
201
+ # don't bother collecting and reporting stats that have zero-values for this timeslice.
202
+ # significant performance boost and storage savings.
203
+ unless stats_copy.call_count == 0 && stats_copy.total_call_time == 0.0 && stats_copy.total_exclusive_time == 0.0
204
+
205
+ metric_spec_for_transport = (metric_ids[metric_spec].nil?) ? metric_spec : nil
206
+
207
+ metric_data = NewRelic::MetricData.new(metric_spec_for_transport, stats_copy, metric_ids[metric_spec])
208
+
209
+ timeslice_data[metric_spec] = metric_data
210
+ end
211
+ end
212
+
213
+ timeslice_data
214
+ end
215
+
216
+
217
+ def start_transaction
218
+ Thread::current[:newrelic_scope_stack] = []
219
+ end
220
+
221
+
222
+ # Try to clean up gracefully, otherwise we leave things hanging around on thread locals
223
+ #
224
+ def end_transaction
225
+ stack = Thread::current[:newrelic_scope_stack]
226
+
227
+ if stack
228
+ @scope_stack_listener.notice_scope_empty(Time.now) if @scope_stack_listener && !stack.empty?
229
+ Thread::current[:newrelic_scope_stack] = nil
230
+ end
231
+
232
+ Thread::current[:newrelic_transaction_name] = nil
233
+ end
234
+
235
+ private
236
+
237
+ def scope_stack
238
+ Thread::current[:newrelic_scope_stack] ||= []
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,40 @@
1
+
2
+ module NewRelic::Agent
3
+
4
+ module Synchronize
5
+ def synchronize_sync
6
+ @_local_sync ||= Sync.new
7
+
8
+ @_local_sync.synchronize(:EX) do
9
+ yield
10
+ end
11
+ end
12
+
13
+
14
+ def synchronize_mutex
15
+ @_local_mutex ||= Mutex.new
16
+
17
+ @_local_mutex.synchronize do
18
+ yield
19
+ end
20
+ end
21
+
22
+
23
+ def synchronize_thread
24
+ old_val = Thread.critical
25
+
26
+ Thread.critical = true
27
+
28
+ begin
29
+ yield
30
+ ensure
31
+ Thread.critical = old_val
32
+ end
33
+ end
34
+
35
+
36
+ alias synchronize synchronize_mutex
37
+ alias synchronize_quick synchronize_mutex
38
+ alias synchronized_long synchronize_mutex
39
+ end
40
+ end
@@ -0,0 +1,281 @@
1
+
2
+ module NewRelic::Agent
3
+
4
+
5
+ class TransactionSampler
6
+ include Synchronize
7
+
8
+ BUILDER_KEY = :transaction_sample_builder
9
+ @@capture_params = true
10
+
11
+ def self.capture_params
12
+ @@capture_params
13
+ end
14
+ def self.capture_params=(params)
15
+ @@capture_params = params
16
+ end
17
+ attr_accessor :stack_trace_threshold
18
+
19
+ def initialize(agent)
20
+ @samples = []
21
+
22
+ @max_samples = 100
23
+ @stack_trace_threshold = 100000.0
24
+ agent.stats_engine.add_scope_stack_listener self
25
+
26
+ agent.set_sql_obfuscator(:replace) do |sql|
27
+ default_sql_obfuscator(sql)
28
+ end
29
+ end
30
+
31
+ def disable
32
+ NewRelic::Agent.instance.stats_engine.remove_scope_stack_listener self
33
+ end
34
+
35
+
36
+ def default_sql_obfuscator(sql)
37
+ # puts "obfuscate: #{sql}"
38
+
39
+ # remove escaped strings
40
+ sql = sql.gsub("''", "?")
41
+
42
+ # replace all string literals
43
+ sql = sql.gsub(/'[^']*'/, "?")
44
+
45
+ # replace all number literals
46
+ sql = sql.gsub(/\d+/, "?")
47
+
48
+ # puts "result: #{sql}"
49
+
50
+ sql
51
+ end
52
+
53
+
54
+ def notice_first_scope_push(time)
55
+ if Thread::current[:record_tt] == false
56
+ Thread::current[BUILDER_KEY] = nil
57
+ else
58
+ Thread::current[BUILDER_KEY] = TransactionSampleBuilder.new(time)
59
+ end
60
+ end
61
+
62
+ def notice_push_scope(scope, time=Time.now.to_f)
63
+
64
+ return unless builder
65
+
66
+ builder.trace_entry(scope, time)
67
+
68
+ # in developer mode, capture the stack trace with the segment.
69
+ # this is cpu and memory expensive and therefore should not be
70
+ # turned on in production mode
71
+ if NewRelic::Config.instance.developer_mode?
72
+ segment = builder.current_segment
73
+ if segment
74
+ # Strip stack frames off the top that match /new_relic/agent/
75
+ trace = caller
76
+ while trace.first =~/\/lib\/new_relic\/agent\//
77
+ trace.shift
78
+ end
79
+
80
+ trace = trace[0..39] if trace.length > 40
81
+ segment[:backtrace] = trace
82
+ end
83
+ end
84
+ end
85
+
86
+ def scope_depth
87
+ return 0 unless builder
88
+
89
+ builder.scope_depth
90
+ end
91
+
92
+ def notice_pop_scope(scope, time = Time.now.to_f)
93
+ return unless builder
94
+ builder.trace_exit(scope, time)
95
+ end
96
+
97
+ # This is called when we are done with the transaction. We've
98
+ # unwound the stack to the top level.
99
+ def notice_scope_empty(time=Time.now.to_f)
100
+
101
+ last_builder = builder
102
+ return unless last_builder
103
+
104
+ last_builder.finish_trace(time)
105
+ reset_builder
106
+
107
+ synchronize do
108
+ sample = last_builder.sample
109
+
110
+ # ensure we don't collect more than a specified number of samples in memory
111
+ @samples << sample if NewRelic::Config.instance.developer_mode? && sample.params[:path] != nil
112
+ @samples.shift while @samples.length > @max_samples
113
+
114
+ if @slowest_sample.nil? || @slowest_sample.duration < sample.duration
115
+ @slowest_sample = sample
116
+ end
117
+ end
118
+ end
119
+
120
+ def notice_transaction(path, request, params)
121
+ return unless builder
122
+
123
+ builder.set_transaction_info(path, request, params)
124
+ end
125
+
126
+ def notice_transaction_cpu_time(cpu_time)
127
+ return unless builder
128
+
129
+ builder.set_transaction_cpu_time(cpu_time)
130
+ end
131
+
132
+
133
+ # some statements (particularly INSERTS with large BLOBS
134
+ # may be very large; we should trim them to a maximum usable length
135
+ # config is the driver configuration for the connection
136
+ MAX_SQL_LENGTH = 16384
137
+ def notice_sql(sql, config, duration)
138
+ return unless builder
139
+ if Thread::current[:record_sql].nil? || Thread::current[:record_sql]
140
+ segment = builder.current_segment
141
+ if segment
142
+ current_sql = segment[:sql]
143
+ sql = current_sql + ";\n" + sql if current_sql
144
+
145
+ if sql.length > (MAX_SQL_LENGTH - 4)
146
+ sql = sql[0..MAX_SQL_LENGTH-4] + '...'
147
+ end
148
+
149
+ segment[:sql] = sql
150
+ segment[:connection_config] = config
151
+ segment[:backtrace] = caller.join("\n") if duration >= @stack_trace_threshold
152
+ end
153
+ end
154
+ end
155
+
156
+ # get the set of collected samples, merging into previous samples,
157
+ # and clear the collected sample list.
158
+
159
+ def harvest_slowest_sample(previous_slowest = nil)
160
+ synchronize do
161
+ slowest = @slowest_sample
162
+ @slowest_sample = nil
163
+
164
+ return nil unless slowest
165
+
166
+ if previous_slowest.nil? || previous_slowest.duration < slowest.duration
167
+ slowest
168
+ else
169
+ previous_slowest
170
+ end
171
+ end
172
+ end
173
+
174
+ # get the list of samples without clearing the list.
175
+ def get_samples
176
+ synchronize do
177
+ return @samples.clone
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def builder
184
+ Thread::current[BUILDER_KEY]
185
+ end
186
+ def reset_builder
187
+ Thread::current[BUILDER_KEY] = nil
188
+ end
189
+
190
+ end
191
+
192
+ # a builder is created with every sampled transaction, to dynamically
193
+ # generate the sampled data. It is a thread-local object, and is not
194
+ # accessed by any other thread so no need for synchronization.
195
+ class TransactionSampleBuilder
196
+ attr_reader :current_segment
197
+
198
+ include CollectionHelper
199
+
200
+ def initialize(time=Time.now.to_f)
201
+ @sample = NewRelic::TransactionSample.new(time)
202
+ @sample_start = time
203
+ @current_segment = @sample.root_segment
204
+ end
205
+
206
+ def trace_entry(metric_name, time)
207
+ segment = @sample.create_segment(time - @sample_start, metric_name)
208
+ @current_segment.add_called_segment(segment)
209
+ @current_segment = segment
210
+ end
211
+
212
+ def trace_exit(metric_name, time)
213
+ if metric_name != @current_segment.metric_name
214
+ fail "unbalanced entry/exit: #{metric_name} != #{@current_segment.metric_name}"
215
+ end
216
+
217
+ @current_segment.end_trace(time - @sample_start)
218
+ @current_segment = @current_segment.parent_segment
219
+ end
220
+
221
+ def finish_trace(time)
222
+ # This should never get called twice, but in a rare case that we can't reproduce in house it does.
223
+ # log forensics and return gracefully
224
+ if @sample.frozen?
225
+ log = NewRelic::Config.instance.log
226
+
227
+ log.warn "Unexpected double-freeze of Transaction Trace Object."
228
+ log.info "Please send this diagnostic data to New Relic"
229
+ log.info @sample.to_s
230
+ return
231
+ end
232
+
233
+ @sample.root_segment.end_trace(time - @sample_start)
234
+ @sample.params[:custom_params] = normalize_params(NewRelic::Agent.instance.custom_params)
235
+ @sample.freeze
236
+ @current_segment = nil
237
+ end
238
+
239
+ def scope_depth
240
+ depth = -1 # have to account for the root
241
+ current = @current_segment
242
+
243
+ while(current)
244
+ depth += 1
245
+ current = current.parent_segment
246
+ end
247
+
248
+ depth
249
+ end
250
+
251
+ def freeze
252
+ @sample.freeze unless sample.frozen?
253
+ end
254
+
255
+
256
+ def set_transaction_info(path, request, params)
257
+ @sample.params[:path] = path
258
+
259
+ if TransactionSampler.capture_params
260
+ params = normalize_params params
261
+
262
+ @sample.params[:request_params].merge!(params)
263
+ @sample.params[:request_params].delete :controller
264
+ @sample.params[:request_params].delete :action
265
+ end
266
+
267
+ @sample.params[:uri] = request.path if request
268
+ end
269
+
270
+ def set_transaction_cpu_time(cpu_time)
271
+ @sample.params[:cpu_time] = cpu_time
272
+ end
273
+
274
+
275
+ def sample
276
+ fail "Not finished building" unless @sample.frozen?
277
+ @sample
278
+ end
279
+
280
+ end
281
+ end