newrelic_rpm 3.5.2.17 → 3.5.3.24

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 (134) hide show
  1. data/.travis.yml +3 -1
  2. data/CHANGELOG +39 -23
  3. data/GUIDELINES_FOR_CONTRIBUTING.md +23 -2
  4. data/Rakefile +32 -0
  5. data/lib/new_relic/agent.rb +1 -0
  6. data/lib/new_relic/agent/agent.rb +45 -6
  7. data/lib/new_relic/agent/browser_monitoring.rb +36 -20
  8. data/lib/new_relic/agent/busy_calculator.rb +12 -4
  9. data/lib/new_relic/agent/configuration/defaults.rb +6 -1
  10. data/lib/new_relic/agent/configuration/environment_source.rb +14 -0
  11. data/lib/new_relic/agent/instrumentation/sinatra.rb +14 -10
  12. data/lib/new_relic/agent/new_relic_service.rb +192 -34
  13. data/lib/new_relic/agent/pipe_channel_manager.rb +1 -2
  14. data/lib/new_relic/agent/pipe_service.rb +5 -1
  15. data/lib/new_relic/agent/samplers/memory_sampler.rb +1 -1
  16. data/lib/new_relic/agent/sql_sampler.rb +29 -10
  17. data/lib/new_relic/agent/stats_engine/metric_stats.rb +40 -0
  18. data/lib/new_relic/agent/stats_engine/samplers.rb +1 -2
  19. data/lib/new_relic/agent/thread.rb +27 -0
  20. data/lib/new_relic/agent/thread_profiler.rb +295 -0
  21. data/lib/new_relic/agent/worker_loop.rb +29 -15
  22. data/lib/new_relic/control/frameworks/rails.rb +4 -4
  23. data/lib/new_relic/control/frameworks/rails3.rb +1 -1
  24. data/lib/new_relic/helper.rb +3 -0
  25. data/lib/new_relic/metric_data.rb +10 -2
  26. data/lib/new_relic/noticed_error.rb +5 -0
  27. data/lib/new_relic/transaction_sample.rb +23 -13
  28. data/lib/new_relic/transaction_sample/segment.rb +13 -15
  29. data/lib/new_relic/version.rb +1 -1
  30. data/lib/tasks/tests.rake +5 -11
  31. data/test/multiverse/.gitignore +10 -0
  32. data/test/multiverse/README.md +90 -0
  33. data/test/multiverse/Rakefile +17 -0
  34. data/test/multiverse/lib/multiverse/color.rb +13 -0
  35. data/test/multiverse/lib/multiverse/envfile.rb +66 -0
  36. data/test/multiverse/lib/multiverse/environment.rb +16 -0
  37. data/test/multiverse/lib/multiverse/output_collector.rb +29 -0
  38. data/test/multiverse/lib/multiverse/runner.rb +44 -0
  39. data/test/multiverse/lib/multiverse/suite.rb +162 -0
  40. data/test/multiverse/script/run_one +3 -0
  41. data/test/multiverse/script/runner +9 -0
  42. data/test/multiverse/suites/active_record/Envfile +13 -0
  43. data/test/multiverse/suites/active_record/ar_method_aliasing.rb +94 -0
  44. data/test/multiverse/suites/active_record/config/newrelic.yml +22 -0
  45. data/test/multiverse/suites/active_record/encoding_test.rb +26 -0
  46. data/test/multiverse/suites/agent_only/Envfile +3 -0
  47. data/test/multiverse/suites/agent_only/config/newrelic.yml +22 -0
  48. data/test/multiverse/suites/agent_only/http_response_code_test.rb +53 -0
  49. data/test/multiverse/suites/agent_only/marshaling_test.rb +109 -0
  50. data/test/multiverse/suites/agent_only/method_visibility_test.rb +98 -0
  51. data/test/multiverse/suites/agent_only/pipe_manager_test.rb +33 -0
  52. data/test/multiverse/suites/agent_only/service_timeout_test.rb +29 -0
  53. data/test/multiverse/suites/agent_only/test_trace_method_with_punctuation.rb +30 -0
  54. data/test/multiverse/suites/agent_only/test_trace_transaction_with_punctuation.rb +32 -0
  55. data/test/multiverse/suites/agent_only/thread_profiling_test.rb +80 -0
  56. data/test/multiverse/suites/datamapper/Envfile +8 -0
  57. data/test/multiverse/suites/datamapper/config/newrelic.yml +22 -0
  58. data/test/multiverse/suites/datamapper/encoding_test.rb +36 -0
  59. data/test/multiverse/suites/monitor_mode_false/Envfile +2 -0
  60. data/test/multiverse/suites/monitor_mode_false/config/newrelic.yml +25 -0
  61. data/test/multiverse/suites/monitor_mode_false/no_dns_resolv.rb +29 -0
  62. data/test/multiverse/suites/no_load/Envfile +2 -0
  63. data/test/multiverse/suites/no_load/config/newrelic.yml +23 -0
  64. data/test/multiverse/suites/no_load/start_up_test.rb +14 -0
  65. data/test/multiverse/suites/rails_3_error_tracing/Envfile +15 -0
  66. data/test/multiverse/suites/rails_3_error_tracing/config/newrelic.yml +165 -0
  67. data/test/multiverse/suites/rails_3_error_tracing/error_tracing_test.rb +236 -0
  68. data/test/multiverse/suites/rails_3_gc/Envfile +8 -0
  69. data/test/multiverse/suites/rails_3_gc/config/newrelic.yml +167 -0
  70. data/test/multiverse/suites/rails_3_gc/instrumentation_test.rb +92 -0
  71. data/test/multiverse/suites/rails_3_queue_time/Envfile +15 -0
  72. data/test/multiverse/suites/rails_3_queue_time/config/newrelic.yml +165 -0
  73. data/test/multiverse/suites/rails_3_queue_time/queue_time_test.rb +75 -0
  74. data/test/multiverse/suites/rails_3_views/.gitignore +3 -0
  75. data/test/multiverse/suites/rails_3_views/Envfile +16 -0
  76. data/test/multiverse/suites/rails_3_views/app/views/foos/_foo.html.haml +1 -0
  77. data/test/multiverse/suites/rails_3_views/app/views/test/_a_partial.html.erb +1 -0
  78. data/test/multiverse/suites/rails_3_views/app/views/test/_mid_partial.html.erb +1 -0
  79. data/test/multiverse/suites/rails_3_views/app/views/test/_top_partial.html.erb +3 -0
  80. data/test/multiverse/suites/rails_3_views/app/views/test/deep_partial.html.erb +3 -0
  81. data/test/multiverse/suites/rails_3_views/app/views/test/haml_view.html.haml +6 -0
  82. data/test/multiverse/suites/rails_3_views/app/views/test/index.html.erb +4 -0
  83. data/test/multiverse/suites/rails_3_views/config/newrelic.yml +164 -0
  84. data/test/multiverse/suites/rails_3_views/view_instrumentation_test.rb +245 -0
  85. data/test/multiverse/suites/resque/Envfile +21 -0
  86. data/test/multiverse/suites/resque/config/newrelic.yml +22 -0
  87. data/test/multiverse/suites/resque/dump.rdb +0 -0
  88. data/test/multiverse/suites/resque/instrumentation_test.rb +73 -0
  89. data/test/multiverse/suites/rum_auto_instrumentation/Envfile +4 -0
  90. data/test/multiverse/suites/rum_auto_instrumentation/config/newrelic.yml +24 -0
  91. data/test/multiverse/suites/rum_auto_instrumentation/problem_response.html +422 -0
  92. data/test/multiverse/suites/rum_auto_instrumentation/responses/worst_case_small.html +5000 -0
  93. data/test/multiverse/suites/rum_auto_instrumentation/sanity_test.rb +115 -0
  94. data/test/multiverse/suites/sinatra/Envfile +13 -0
  95. data/test/multiverse/suites/sinatra/config/newrelic.yml +24 -0
  96. data/test/multiverse/suites/sinatra/sinatra_metric_explosion_test.rb +76 -0
  97. data/test/multiverse/suites/sinatra/sinatra_routes_test.rb +46 -0
  98. data/test/multiverse/test/multiverse_test.rb +55 -0
  99. data/test/multiverse/test/suite_examples/one/a/Envfile +3 -0
  100. data/test/multiverse/test/suite_examples/one/a/a_test.rb +11 -0
  101. data/test/multiverse/test/suite_examples/one/a/config/newrelic.yml +24 -0
  102. data/test/multiverse/test/suite_examples/one/b/Envfile +3 -0
  103. data/test/multiverse/test/suite_examples/one/b/b_test.rb +11 -0
  104. data/test/multiverse/test/suite_examples/one/b/config/newrelic.yml +24 -0
  105. data/test/multiverse/test/suite_examples/three/a/Envfile +2 -0
  106. data/test/multiverse/test/suite_examples/three/a/fail_test.rb +6 -0
  107. data/test/multiverse/test/suite_examples/three/b/Envfile +2 -0
  108. data/test/multiverse/test/suite_examples/three/b/win_test.rb +6 -0
  109. data/test/multiverse/test/suite_examples/two/a/Envfile +1 -0
  110. data/test/multiverse/test/suite_examples/two/a/fail_test.rb +6 -0
  111. data/test/new_relic/agent/agent_test.rb +54 -2
  112. data/test/new_relic/agent/agent_test_controller.rb +1 -1
  113. data/test/new_relic/agent/agent_test_controller_test.rb +35 -5
  114. data/test/new_relic/agent/browser_monitoring_test.rb +8 -8
  115. data/test/new_relic/agent/configuration/environment_source_test.rb +16 -0
  116. data/test/new_relic/agent/method_tracer_test.rb +6 -6
  117. data/test/new_relic/agent/new_relic_service_test.rb +137 -20
  118. data/test/new_relic/agent/sql_sampler_test.rb +26 -0
  119. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +1 -1
  120. data/test/new_relic/agent/stats_engine_test.rb +1 -0
  121. data/test/new_relic/agent/thread_profiler_test.rb +536 -0
  122. data/test/new_relic/agent/thread_test.rb +76 -0
  123. data/test/new_relic/agent/threaded_test.rb +65 -0
  124. data/test/new_relic/agent/transaction_sampler_test.rb +16 -13
  125. data/test/new_relic/agent/worker_loop_test.rb +20 -0
  126. data/test/new_relic/fake_collector.rb +103 -31
  127. data/test/new_relic/fake_service.rb +7 -1
  128. data/test/new_relic/metric_data_test.rb +45 -16
  129. data/test/new_relic/noticed_error_test.rb +14 -0
  130. data/test/new_relic/transaction_sample/segment_test.rb +23 -4
  131. data/test/new_relic/transaction_sample_test.rb +39 -0
  132. data/ui/views/layouts/newrelic_default.rhtml +1 -0
  133. data/ui/views/newrelic/threads.rhtml +2 -10
  134. metadata +88 -2
@@ -1,5 +1,10 @@
1
+ require 'zlib'
2
+ require 'base64'
3
+ require 'digest/md5'
4
+
1
5
  require 'new_relic/agent'
2
6
  require 'new_relic/control'
7
+
3
8
  module NewRelic
4
9
  module Agent
5
10
 
@@ -218,21 +223,35 @@ module NewRelic
218
223
  Agent.config[:'slow_sql.explain_enabled']
219
224
  end
220
225
 
221
- def to_json(*a)
222
- [@path, @url, @sql_id, @sql, @database_metric_name, @call_count, @total_call_time, @min_call_time, @max_call_time, @params].to_json(*a)
226
+ def to_collector_array(marshaller)
227
+ params = if marshaller.respond_to?(:encode_compress)
228
+ marshaller.encode_compress(@params)
229
+ else
230
+ @params
231
+ end
232
+
233
+ [ @path, @url, @sql_id, @sql, @database_metric_name, @call_count,
234
+ Helper.time_to_millis(@total_call_time),
235
+ Helper.time_to_millis(@min_call_time),
236
+ Helper.time_to_millis(@max_call_time),
237
+ params ]
223
238
  end
224
239
 
225
240
  private
226
241
 
227
- def consistent_hash(string)
228
- if NewRelic::LanguageSupport.using_version?('1.9.2')
229
- # String#hash is salted differently on every VM start in 1.9
230
- require 'digest/md5'
231
- Digest::MD5.hexdigest(string).hex
242
+ def compress(data)
243
+ if NewRelic::Agent::NewRelicService::JsonMarshaller.is_supported?
244
+ require 'json'
245
+ Base64.encode64(Zlib::Deflate.deflate(JSON.dump(data), Zlib::DEFAULT_COMPRESSION))
232
246
  else
233
- string.hash
234
- end.modulo(2**31-1)
235
- # modulo ensures sql_id fits in an INT(11)
247
+ data
248
+ end
249
+ end
250
+
251
+ def consistent_hash(string)
252
+ # need to hash the same way in every process
253
+ Digest::MD5.hexdigest(string).hex \
254
+ .modulo(2**31-1) # ensure sql_id fits in an INT(11)
236
255
  end
237
256
  end
238
257
  end
@@ -63,6 +63,19 @@ module NewRelic
63
63
 
64
64
  backtrace = t.backtrace.map { |b| "\t#{b}" }.join("\n")
65
65
  "\t#{t}\n#{backtrace}"
66
+
67
+ rescue Exception => e
68
+ # JRuby 1.7.0 has a nasty habit of raising a
69
+ # java.lang.NullPointerException when we iterate through threads
70
+ # asking for backtraces. This line allows us to swallow java
71
+ # exceptions without referencing their classes (since they don't
72
+ # exist in MRI). It also prevents us from swallowing signals or
73
+ # other nasty things that can happen when you rescue Exception.
74
+ NewRelic::Control.instance.log.warn(
75
+ "Error collecting thread backtraces: #{e.class.name}: #{e.message}")
76
+ NewRelic::Control.instance.log.debug( e.backtrace.join("\n") )
77
+
78
+ raise e if e.class.ancestors.include? Exception
66
79
  end
67
80
  end
68
81
 
@@ -106,6 +119,33 @@ module NewRelic
106
119
  stats_hash[NewRelic::MetricSpec.new(metric_name, scope_name)]
107
120
  end
108
121
 
122
+
123
+ # Helper method for timing supportability metrics
124
+ def record_supportability_metrics_timed(metrics)
125
+ start_time = Time.now
126
+ yield
127
+ end_time = Time.now
128
+ duration = (end_time - start_time).to_f
129
+ ensure
130
+ record_supportability_metrics(duration, metrics) do |value, metric|
131
+ metric.record_data_point(value)
132
+ end
133
+ end
134
+
135
+ # Helper for recording a straight value into the count
136
+ def record_supportability_metrics_count(value, *metrics)
137
+ record_supportability_metrics(value, *metrics) do |value, metric|
138
+ metric.call_count = value
139
+ end
140
+ end
141
+
142
+ # Helper method for recording supportability metrics consistently
143
+ def record_supportability_metrics(value, *metrics)
144
+ metrics.each do |metric|
145
+ yield(value, get_stats_no_scope("Supportability/#{metric}"))
146
+ end
147
+ end
148
+
109
149
  # This module was extracted from the harvest method and should
110
150
  # be refactored
111
151
  module Harvest
@@ -27,7 +27,7 @@ module Agent
27
27
  # start up a thread that will periodically poll for metric samples
28
28
  return if periodic_samplers.empty?
29
29
 
30
- @sampler_thread = Thread.new do
30
+ @sampler_thread = NewRelic::Agent::Thread.new('Sampler Tasks') do
31
31
  loop do
32
32
  now = Time.now
33
33
  begin
@@ -40,7 +40,6 @@ module Agent
40
40
  end
41
41
  end
42
42
  end
43
- @sampler_thread['newrelic_label'] = 'Sampler Tasks'
44
43
  end
45
44
 
46
45
  private
@@ -0,0 +1,27 @@
1
+ module NewRelic
2
+ module Agent
3
+
4
+ class Thread < ::Thread
5
+ def initialize(label)
6
+ NewRelic::Agent.logger.debug("Creating New Relic thread: #{label}")
7
+ self[:newrelic_label] = label
8
+ super
9
+ end
10
+
11
+ def self.bucket_thread(thread, profile_agent_code)
12
+ if thread.key?(:newrelic_label)
13
+ return profile_agent_code ? :agent : :ignore
14
+ elsif !thread[:newrelic_metric_frame].nil?
15
+ thread[:newrelic_metric_frame].request.nil? ? :background : :request
16
+ else
17
+ :other
18
+ end
19
+ end
20
+
21
+ def self.scrub_backtrace(thread, profile_agent_code)
22
+ return thread.backtrace if profile_agent_code
23
+ thread.backtrace.select {|t| t !~ /\/newrelic_rpm-\d/ }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,295 @@
1
+ require 'new_relic/agent/thread'
2
+ require 'new_relic/agent/worker_loop'
3
+
4
+ module NewRelic
5
+ module Agent
6
+
7
+ class ThreadProfiler
8
+
9
+ attr_reader :profile
10
+
11
+ def self.is_supported?
12
+ RUBY_VERSION >= "1.9.2"
13
+ end
14
+
15
+ def start(profile_id, duration, interval, profile_agent_code)
16
+ if !ThreadProfiler.is_supported?
17
+ log.debug("Not starting thread profile as it isn't supported on this environment")
18
+ @profile = nil
19
+ else
20
+ log.debug("Starting thread profile. profile_id=#{profile_id}, duration=#{duration}")
21
+ @profile = ThreadProfile.new(profile_id, duration, interval, profile_agent_code)
22
+ @profile.run
23
+ end
24
+ end
25
+
26
+ def stop(report_data)
27
+ @profile.stop unless @profile.nil?
28
+ @profile = nil if !report_data
29
+ end
30
+
31
+ def harvest
32
+ profile = @profile
33
+ @profile = nil
34
+ profile
35
+ end
36
+
37
+ def respond_to_commands(commands, &notify_results)
38
+ return if commands.empty? || commands.first.size < 2
39
+
40
+ # Doesn't deal with multiple commands in the return set as
41
+ # we currently only have start/stop of thread profiling
42
+ command_id = commands.first[0]
43
+ command = commands.first[1]
44
+
45
+ name = command["name"]
46
+ arguments = command["arguments"]
47
+
48
+ case name
49
+ when "start_profiler"
50
+ start_unless_running_and_notify(command_id, arguments, &notify_results)
51
+
52
+ when "stop_profiler"
53
+ stop_and_notify(command_id, arguments, &notify_results)
54
+ end
55
+ end
56
+
57
+ def running?
58
+ !@profile.nil?
59
+ end
60
+
61
+ def finished?
62
+ @profile && @profile.finished?
63
+ end
64
+
65
+ private
66
+
67
+ def start_unless_running_and_notify(command_id, arguments)
68
+ profile_id = arguments.fetch("profile_id", -1)
69
+ duration = arguments.fetch("duration", 120)
70
+ interval = arguments.fetch("sample_period", 0.1)
71
+ profile_agent_code = arguments.fetch("profile_agent_code", true)
72
+
73
+ if running?
74
+ msg = "Profile already in progress. Ignoring agent command to start another."
75
+ log.debug(msg)
76
+ yield(command_id, msg) if block_given?
77
+ else
78
+ start(profile_id, duration, interval, profile_agent_code)
79
+ yield(command_id) if block_given?
80
+ end
81
+ end
82
+
83
+ def stop_and_notify(command_id, arguments)
84
+ report_data = arguments.fetch("report_data", true)
85
+ stop(report_data)
86
+ yield(command_id) if block_given?
87
+ end
88
+
89
+ def log
90
+ NewRelic::Agent.logger
91
+ end
92
+ end
93
+
94
+ class ThreadProfile
95
+
96
+ attr_reader :profile_id,
97
+ :traces,
98
+ :profile_agent_code, :interval,
99
+ :poll_count, :sample_count,
100
+ :start_time, :stop_time
101
+
102
+ def initialize(profile_id, duration, interval, profile_agent_code)
103
+ @profile_id = profile_id
104
+ @profile_agent_code = profile_agent_code
105
+
106
+ @worker_loop = NewRelic::Agent::WorkerLoop.new(:duration => duration)
107
+ @interval = interval
108
+ @finished = false
109
+
110
+ @traces = {
111
+ :agent => [],
112
+ :background => [],
113
+ :other => [],
114
+ :request => []
115
+ }
116
+ @flattened_nodes = []
117
+
118
+ @poll_count = 0
119
+ @sample_count = 0
120
+ end
121
+
122
+ def run
123
+ Thread.new('Thread Profiler') do
124
+ @start_time = now_in_millis
125
+
126
+ @worker_loop.run(@interval) do
127
+ NewRelic::Agent.instance.stats_engine.
128
+ record_supportability_metrics_timed("ThreadProfiler/PollingTime") do
129
+
130
+ @poll_count += 1
131
+ Thread.list.each do |t|
132
+ @sample_count += 1
133
+
134
+ bucket = Thread.bucket_thread(t, @profile_agent_code)
135
+ backtrace = Thread.scrub_backtrace(t, @profile_agent_code)
136
+ aggregate(backtrace, @traces[bucket]) unless bucket == :ignore
137
+ end
138
+ end
139
+ end
140
+
141
+ mark_done
142
+ log.debug("Finished thread profile. Will send with next harvest.")
143
+ end
144
+ end
145
+
146
+ def stop
147
+ @worker_loop.stop
148
+ mark_done
149
+ log.debug("Stopping thread profile.")
150
+ end
151
+
152
+ def aggregate(trace, trees=@traces[:request], parent=nil)
153
+ return nil if trace.nil? || trace.empty?
154
+ node = Node.new(trace.last)
155
+ existing = trees.find {|n| n == node}
156
+
157
+ if existing.nil?
158
+ existing = node
159
+ @flattened_nodes << node
160
+ end
161
+
162
+ if parent
163
+ parent.add_child(node)
164
+ else
165
+ trees << node unless trees.include? node
166
+ end
167
+
168
+ existing.runnable_count += 1
169
+ aggregate(trace[0..-2], existing.children, existing)
170
+
171
+ existing
172
+ end
173
+
174
+ def prune!(count_to_keep)
175
+ @flattened_nodes.sort!(&:order_for_pruning)
176
+
177
+ NewRelic::Agent.instance.stats_engine.
178
+ record_supportability_metrics_count(@flattened_nodes.size, "ThreadProfiler/NodeCount")
179
+
180
+ mark_for_pruning(@flattened_nodes, count_to_keep)
181
+
182
+ traces.each { |_, nodes| Node.prune!(nodes) }
183
+ end
184
+
185
+ THREAD_PROFILER_NODES = 20_000
186
+
187
+ def to_compressed_array
188
+ prune!(THREAD_PROFILER_NODES)
189
+
190
+ traces = {
191
+ "OTHER" => @traces[:other].map{|t| t.to_array },
192
+ "REQUEST" => @traces[:request].map{|t| t.to_array },
193
+ "AGENT" => @traces[:agent].map{|t| t.to_array },
194
+ "BACKGROUND" => @traces[:background].map{|t| t.to_array }
195
+ }
196
+
197
+ [[@profile_id,
198
+ @start_time.to_f, @stop_time.to_f,
199
+ @poll_count,
200
+ ThreadProfile.compress(JSON.dump(traces)),
201
+ @sample_count, 0]]
202
+ end
203
+
204
+ def now_in_millis
205
+ Time.now.to_f * 1_000
206
+ end
207
+
208
+ def finished?
209
+ @finished
210
+ end
211
+
212
+ def mark_done
213
+ @finished = true
214
+ @stop_time = now_in_millis
215
+ end
216
+
217
+ def mark_for_pruning(nodes, count_to_keep)
218
+ to_prune = nodes[count_to_keep..-1] || []
219
+ to_prune.each { |n| n.to_prune = true }
220
+ end
221
+
222
+ def self.flattened_nodes(nodes)
223
+ nodes.map { |n| [n, flattened_nodes(n.children)] }.flatten
224
+ end
225
+
226
+ def self.compress(json)
227
+ compressed = Base64.encode64(Zlib::Deflate.deflate(json, Zlib::DEFAULT_COMPRESSION))
228
+ end
229
+
230
+ def self.parse_backtrace(trace)
231
+ trace.map do |line|
232
+ line =~ /(.*)\:(\d+)\:in `(.*)'/
233
+ { :method => $3, :line_no => $2.to_i, :file => $1 }
234
+ end
235
+ end
236
+
237
+ class Node
238
+ attr_reader :file, :method, :line_no, :children
239
+ attr_accessor :runnable_count, :to_prune, :depth
240
+
241
+ def initialize(line, parent=nil)
242
+ line =~ /(.*)\:(\d+)\:in `(.*)'/
243
+ @file = $1
244
+ @method = $3
245
+ @line_no = $2.to_i
246
+ @children = []
247
+ @runnable_count = 0
248
+ @to_prune = false
249
+ @depth = 0
250
+
251
+ parent.add_child(self) if parent
252
+ end
253
+
254
+ def ==(other)
255
+ @file == other.file &&
256
+ @method == other.method &&
257
+ @line_no == other.line_no
258
+ end
259
+
260
+ def total_count
261
+ @runnable_count
262
+ end
263
+
264
+ # Descending order on count, ascending on depth of nodes
265
+ def order_for_pruning(y)
266
+ [-runnable_count, depth] <=> [-y.runnable_count, y.depth]
267
+ end
268
+
269
+ def to_array
270
+ [[@file, @method, @line_no],
271
+ @runnable_count, 0,
272
+ @children.map {|c| c.to_array}]
273
+ end
274
+
275
+ def add_child(child)
276
+ child.depth = @depth + 1
277
+ @children << child unless @children.include? child
278
+ end
279
+
280
+ def prune!
281
+ Node.prune!(@children)
282
+ end
283
+
284
+ def self.prune!(kids)
285
+ kids.delete_if { |child| child.to_prune }
286
+ kids.each { |child| child.prune! }
287
+ end
288
+ end
289
+
290
+ def log
291
+ NewRelic::Agent.logger
292
+ end
293
+ end
294
+ end
295
+ end