newrelic_rpm 3.9.4.245 → 3.9.5.251

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +57 -0
  3. data/Guardfile +1 -0
  4. data/lib/new_relic/agent/agent.rb +3 -3
  5. data/lib/new_relic/agent/audit_logger.rb +5 -2
  6. data/lib/new_relic/agent/configuration/default_source.rb +11 -5
  7. data/lib/new_relic/agent/error_collector.rb +14 -1
  8. data/lib/new_relic/agent/hostname.rb +22 -1
  9. data/lib/new_relic/agent/instrumentation/middleware_tracing.rb +8 -2
  10. data/lib/new_relic/agent/instrumentation/queue_time.rb +9 -6
  11. data/lib/new_relic/agent/method_tracer.rb +51 -172
  12. data/lib/new_relic/agent/method_tracer_helpers.rb +90 -0
  13. data/lib/new_relic/agent/new_relic_service.rb +33 -11
  14. data/lib/new_relic/agent/new_relic_service/encoders.rb +9 -5
  15. data/lib/new_relic/agent/request_sampler.rb +20 -12
  16. data/lib/new_relic/agent/rules_engine.rb +31 -78
  17. data/lib/new_relic/agent/rules_engine/replacement_rule.rb +76 -0
  18. data/lib/new_relic/agent/rules_engine/segment_terms_rule.rb +48 -0
  19. data/lib/new_relic/agent/samplers/cpu_sampler.rb +1 -1
  20. data/lib/new_relic/agent/sql_sampler.rb +39 -10
  21. data/lib/new_relic/agent/stats_engine/metric_stats.rb +1 -0
  22. data/lib/new_relic/agent/stats_engine/stats_hash.rb +7 -13
  23. data/lib/new_relic/agent/system_info.rb +96 -10
  24. data/lib/new_relic/agent/threading/agent_thread.rb +4 -1
  25. data/lib/new_relic/agent/threading/backtrace_node.rb +67 -57
  26. data/lib/new_relic/agent/threading/thread_profile.rb +30 -15
  27. data/lib/new_relic/agent/transaction.rb +11 -4
  28. data/lib/new_relic/environment_report.rb +21 -20
  29. data/lib/new_relic/version.rb +1 -1
  30. data/test/agent_helper.rb +12 -0
  31. data/test/fixtures/cross_agent_tests/README.md +1 -0
  32. data/test/fixtures/cross_agent_tests/proc_cpuinfo/1pack_1core_1logical.txt +3 -0
  33. data/test/fixtures/cross_agent_tests/proc_cpuinfo/1pack_1core_2logical.txt +14 -0
  34. data/test/fixtures/cross_agent_tests/proc_cpuinfo/1pack_2core_2logical.txt +14 -0
  35. data/test/fixtures/cross_agent_tests/proc_cpuinfo/1pack_4core_4logical.txt +28 -0
  36. data/test/fixtures/{proc_cpuinfo.txt → cross_agent_tests/proc_cpuinfo/2pack_12core_24logical.txt} +0 -0
  37. data/test/fixtures/cross_agent_tests/proc_cpuinfo/2pack_20core_40logical.txt +999 -0
  38. data/test/fixtures/cross_agent_tests/proc_cpuinfo/2pack_2core_2logical.txt +51 -0
  39. data/test/fixtures/cross_agent_tests/proc_cpuinfo/2pack_2core_4logical.txt +28 -0
  40. data/test/fixtures/cross_agent_tests/proc_cpuinfo/2pack_4core_4logical.txt +28 -0
  41. data/test/fixtures/cross_agent_tests/proc_cpuinfo/4pack_4core_4logical.txt +103 -0
  42. data/test/fixtures/cross_agent_tests/proc_cpuinfo/8pack_8core_8logical.txt +199 -0
  43. data/test/fixtures/cross_agent_tests/proc_cpuinfo/README.md +24 -0
  44. data/test/fixtures/cross_agent_tests/proc_cpuinfo/Xpack_Xcore_2logical.txt +43 -0
  45. data/test/fixtures/cross_agent_tests/transaction_segment_terms.json +101 -0
  46. data/test/multiverse/lib/multiverse/suite.rb +1 -1
  47. data/test/multiverse/suites/agent_only/agent_run_id_handling_test.rb +40 -0
  48. data/test/multiverse/suites/agent_only/labels_test.rb +9 -14
  49. data/test/multiverse/suites/agent_only/marshaling_test.rb +4 -6
  50. data/test/multiverse/suites/agent_only/rename_rule_test.rb +41 -4
  51. data/test/multiverse/suites/agent_only/set_transaction_name_test.rb +11 -3
  52. data/test/multiverse/suites/config_file_loading/config_file_loading_test.rb +8 -8
  53. data/test/multiverse/suites/rack/example_app.rb +20 -0
  54. data/test/multiverse/suites/rack/http_response_code_test.rb +51 -0
  55. data/test/multiverse/suites/sidekiq/Envfile +13 -6
  56. data/test/multiverse/suites/sidekiq/sidekiq_server.rb +4 -3
  57. data/test/new_relic/agent/audit_logger_test.rb +27 -0
  58. data/test/new_relic/agent/error_collector_test.rb +26 -5
  59. data/test/new_relic/agent/hostname_test.rb +66 -14
  60. data/test/new_relic/agent/instrumentation/action_controller_subscriber_test.rb +8 -12
  61. data/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb +7 -45
  62. data/test/new_relic/agent/method_tracer_test.rb +52 -1
  63. data/test/new_relic/agent/new_relic_service_test.rb +76 -0
  64. data/test/new_relic/agent/request_sampler_test.rb +7 -0
  65. data/test/new_relic/agent/rules_engine_test.rb +87 -56
  66. data/test/new_relic/agent/sql_sampler_test.rb +50 -14
  67. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +2 -2
  68. data/test/new_relic/agent/stats_engine/samplers_test.rb +1 -1
  69. data/test/new_relic/agent/{stats_hash_test.rb → stats_engine/stats_hash_test.rb} +1 -38
  70. data/test/new_relic/agent/system_info_test.rb +45 -0
  71. data/test/new_relic/agent/threading/agent_thread_test.rb +30 -0
  72. data/test/new_relic/agent/threading/backtrace_node_test.rb +27 -44
  73. data/test/new_relic/agent/threading/thread_profile_test.rb +35 -14
  74. data/test/new_relic/agent/transaction_test.rb +13 -10
  75. data/test/new_relic/environment_report_test.rb +7 -6
  76. data/test/new_relic/fake_collector.rb +10 -6
  77. data/test/new_relic/multiverse_helpers.rb +4 -11
  78. data/test/new_relic/rack/agent_hooks_test.rb +1 -1
  79. data/test/performance/lib/performance/baseline_compare_reporter.rb +24 -7
  80. data/test/performance/lib/performance/result.rb +3 -1
  81. data/test/performance/lib/performance/runner.rb +10 -0
  82. data/test/performance/lib/performance/timer.rb +6 -10
  83. data/test/performance/script/runner +18 -1
  84. data/test/performance/suites/queue_time.rb +21 -0
  85. data/test/performance/suites/stats_hash.rb +34 -0
  86. data/test/performance/suites/thread_profiling.rb +26 -0
  87. metadata +25 -4
  88. metadata.gz.sig +0 -0
@@ -0,0 +1,48 @@
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 RulesEngine
8
+ class SegmentTermsRule
9
+ SEGMENT_PLACEHOLDER = '*'.freeze
10
+ ADJACENT_PLACEHOLDERS_REGEX = %r{((?:^|/)\*)(?:/\*)*}.freeze
11
+ ADJACENT_PLACEHOLDERS_REPLACEMENT = '\1'.freeze
12
+
13
+ attr_reader :prefix, :terms
14
+
15
+ def initialize(options)
16
+ @prefix = options['prefix']
17
+ @terms = options['terms']
18
+ @trim_range = (@prefix.size..-1)
19
+ end
20
+
21
+ def terminal?
22
+ true
23
+ end
24
+
25
+ def matches?(string)
26
+ string.start_with?(@prefix)
27
+ end
28
+
29
+ def apply(string)
30
+ rest = string[@trim_range]
31
+ leading_slash = rest.slice!(LEADING_SLASH_REGEX)
32
+
33
+ segments = rest.split(SEGMENT_SEPARATOR)
34
+ segments.map! { |s| @terms.include?(s) ? s : SEGMENT_PLACEHOLDER }
35
+ transformed_suffix = collapse_adjacent_placeholder_segments(segments)
36
+
37
+ "#{@prefix}#{leading_slash}#{transformed_suffix}"
38
+ end
39
+
40
+ def collapse_adjacent_placeholder_segments(segments)
41
+ joined = segments.join(SEGMENT_SEPARATOR)
42
+ joined.gsub!(ADJACENT_PLACEHOLDERS_REGEX, ADJACENT_PLACEHOLDERS_REPLACEMENT)
43
+ joined
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -13,7 +13,7 @@ module NewRelic
13
13
  named :cpu
14
14
 
15
15
  def initialize
16
- @processor_count = NewRelic::Agent::SystemInfo.processor_count
16
+ @processor_count = NewRelic::Agent::SystemInfo.num_logical_processors
17
17
  if @processor_count.nil?
18
18
  NewRelic::Agent.logger.warn("Failed to determine processor count, assuming 1")
19
19
  @processor_count = 1
@@ -39,6 +39,8 @@ module NewRelic
39
39
  # this is for unit tests only
40
40
  attr_reader :sql_traces
41
41
 
42
+ MAX_SAMPLES = 10
43
+
42
44
  def initialize
43
45
  @sql_traces = {}
44
46
 
@@ -82,27 +84,55 @@ module NewRelic
82
84
  data.set_transaction_name(name)
83
85
  if data.sql_data.size > 0
84
86
  @samples_lock.synchronize do
85
- ::NewRelic::Agent.logger.debug "Harvesting #{data.sql_data.size} slow transaction sql statement(s)"
86
- harvest_slow_sql data
87
+ ::NewRelic::Agent.logger.debug "Examining #{data.sql_data.size} slow transaction sql statement(s)"
88
+ save_slow_sql data
87
89
  end
88
90
  end
89
91
  end
90
92
 
91
93
  # this should always be called under the @samples_lock
92
- def harvest_slow_sql(transaction_sql_data)
94
+ def save_slow_sql(transaction_sql_data)
95
+ path = transaction_sql_data.path
96
+ uri = transaction_sql_data.uri
97
+
93
98
  transaction_sql_data.sql_data.each do |sql_item|
94
99
  normalized_sql = sql_item.normalize
95
100
  sql_trace = @sql_traces[normalized_sql]
96
101
  if sql_trace
97
- sql_trace.aggregate(sql_item, transaction_sql_data.path,
98
- transaction_sql_data.uri)
102
+ sql_trace.aggregate(sql_item, path, uri)
99
103
  else
100
- @sql_traces[normalized_sql] = SqlTrace.new(normalized_sql,
101
- sql_item, transaction_sql_data.path, transaction_sql_data.uri)
104
+ if has_room?
105
+ sql_trace = SqlTrace.new(normalized_sql, sql_item, path, uri)
106
+ elsif should_add_trace?(sql_item)
107
+ remove_shortest_trace
108
+ sql_trace = SqlTrace.new(normalized_sql, sql_item, path, uri)
109
+ end
110
+
111
+ if sql_trace
112
+ @sql_traces[normalized_sql] = sql_trace
113
+ end
102
114
  end
103
115
  end
104
116
  end
105
117
 
118
+ # this should always be called under the @samples_lock
119
+ def should_add_trace?(sql_item)
120
+ @sql_traces.any? do |(_, existing_trace)|
121
+ existing_trace.max_call_time < sql_item.duration
122
+ end
123
+ end
124
+
125
+ # this should always be called under the @samples_lock
126
+ def has_room?
127
+ @sql_traces.size < MAX_SAMPLES
128
+ end
129
+
130
+ # this should always be called under the @samples_lock
131
+ def remove_shortest_trace
132
+ shortest_key, _ = @sql_traces.min_by { |(_, trace)| trace.max_call_time }
133
+ @sql_traces.delete(shortest_key)
134
+ end
135
+
106
136
  # Records an SQL query, potentially creating a new slow SQL trace, or
107
137
  # aggregating the query into an existing slow SQL trace.
108
138
  #
@@ -150,12 +180,11 @@ module NewRelic
150
180
  def harvest!
151
181
  return [] unless enabled?
152
182
 
153
- result = []
183
+ slowest = []
154
184
  @samples_lock.synchronize do
155
- result = @sql_traces.values
185
+ slowest = @sql_traces.values
156
186
  @sql_traces = {}
157
187
  end
158
- slowest = result.sort{|a,b| b.max_call_time <=> a.max_call_time}[0,10]
159
188
  slowest.each {|trace| trace.prepare_to_send }
160
189
  slowest
161
190
  end
@@ -183,6 +183,7 @@ module NewRelic
183
183
  def merge!(other_stats_hash)
184
184
  with_stats_lock do
185
185
  @stats_hash.merge!(other_stats_hash)
186
+ @stats_hash
186
187
  end
187
188
  end
188
189
 
@@ -10,6 +10,11 @@
10
10
  # Missing keys will be automatically created as empty NewRelic::Agent::Stats
11
11
  # instances, so use has_key? explicitly to check for key existence.
12
12
  #
13
+ # Note that instances of this class are intended to be append-only with respect
14
+ # to new metrics. That is, you should not attempt to *remove* an entry after it
15
+ # has been added, only update it (create a new instance if you need to start
16
+ # over with a blank slate).
17
+ #
13
18
  # This class makes no provisions for safe usage from multiple threads, such
14
19
  # measures should be externally provided.
15
20
 
@@ -18,6 +23,7 @@ require 'new_relic/agent/internal_agent_error'
18
23
  module NewRelic
19
24
  module Agent
20
25
  class StatsHash < ::Hash
26
+
21
27
  attr_accessor :started_at, :harvested_at
22
28
 
23
29
  def initialize(started_at=Time.now)
@@ -68,24 +74,12 @@ module NewRelic
68
74
  end
69
75
  end
70
76
 
71
- class StatsMergerError < NewRelic::Agent::InternalAgentError
72
- def initialize(key, destination, source, original_exception)
73
- super("Failure when merging stats '#{key}'. In Hash: #{destination.inspect_full}. Merging: #{source.inspect_full}. Original exception: #{original_exception.class} #{original_exception.message}")
74
- set_backtrace(original_exception.backtrace)
75
- end
76
- end
77
-
78
77
  def merge!(other)
79
78
  if other.is_a?(StatsHash) && other.started_at < @started_at
80
79
  @started_at = other.started_at
81
80
  end
82
81
  other.each do |key, val|
83
- begin
84
- merge_or_insert(key, val)
85
- rescue => err
86
- NewRelic::Agent.instance.error_collector. \
87
- notice_agent_error(StatsMergerError.new(key, self.fetch(key, nil), val, err))
88
- end
82
+ merge_or_insert(key, val)
89
83
  end
90
84
  self
91
85
  end
@@ -16,20 +16,106 @@ module NewRelic
16
16
  RbConfig::CONFIG['target_os']
17
17
  end
18
18
 
19
- def self.processor_count
20
- case ruby_os_identifier
21
- when /darwin/, /freebsd/
22
- `sysctl -n hw.ncpu`.to_i
23
- when /linux/
24
- cpuinfo = proc_try_read('/proc/cpuinfo')
25
- return unless cpuinfo
26
- processors = cpuinfo.split("\n").select {|line| line =~ /^processor\s*:/ }.size
27
- processors == 0 ? nil : processors
19
+ def self.clear_processor_info
20
+ @processor_info = nil
21
+ end
22
+
23
+ def self.get_processor_info
24
+ if @processor_info.nil?
25
+ case ruby_os_identifier
26
+
27
+ when /darwin/, /freebsd/
28
+ @processor_info = {
29
+ :num_physical_packages => `sysctl -n hw.packages`.to_i,
30
+ :num_physical_cores => `sysctl -n hw.physicalcpu_max`.to_i,
31
+ :num_logical_processors => `sysctl -n hw.logicalcpu_max`.to_i
32
+ }
33
+ # in case those don't work, try backup values
34
+ if @processor_info[:num_physical_cores] <= 0
35
+ @processor_info[:num_physical_cores] = `sysctl -n hw.physicalcpu`.to_i
36
+ end
37
+ if @processor_info[:num_logical_processors] <= 0
38
+ @processor_info[:num_logical_processors] = `sysctl -n hw.logicalcpu`.to_i
39
+ end
40
+ if @processor_info[:num_logical_processors] <= 0
41
+ @processor_info[:num_logical_processors] = `sysctl -n hw.ncpu`.to_i
42
+ end
43
+ if @processor_info[:num_logical_processors] <= 0
44
+ @processor_info[:num_logical_processors] = `sysctl -n hw.availcpu`.to_i
45
+ end
46
+ if @processor_info[:num_logical_processors] <= 0
47
+ @processor_info[:num_logical_processors] = `sysctl -n hw.activecpu`.to_i
48
+ end
49
+
50
+ when /linux/
51
+ cpuinfo = proc_try_read('/proc/cpuinfo')
52
+ @processor_info = cpuinfo ? parse_cpuinfo(cpuinfo) : {}
53
+ end
54
+
55
+ # give nils for obviously wrong values
56
+ @processor_info.keys.each do |key|
57
+ @processor_info[key] = nil if @processor_info[key] <= 0
58
+ end
28
59
  end
60
+
61
+ @processor_info
29
62
  rescue
30
- nil
63
+ {}
31
64
  end
32
65
 
66
+ def self.parse_cpuinfo(cpuinfo)
67
+ # Build a hash of the form
68
+ # { [phys_id, core_id] => num_logical_processors_on_this_core }
69
+ cores = Hash.new(0)
70
+ phys_id = core_id = nil
71
+
72
+ total_processors = 0
73
+
74
+ cpuinfo.split("\n").map(&:strip).each do |line|
75
+ case line
76
+ when /^processor\s*:/
77
+ cores[[phys_id, core_id]] += 1 if phys_id && core_id
78
+ phys_id = core_id = nil # reset these values
79
+ total_processors += 1
80
+ when /^physical id\s*:(.*)/
81
+ phys_id = $1.strip.to_i
82
+ when /^core id\s*:(.*)/
83
+ core_id = $1.strip.to_i
84
+ end
85
+ end
86
+ cores[[phys_id, core_id]] += 1 if phys_id && core_id
87
+
88
+ num_physical_packages = cores.keys.map(&:first).uniq.size
89
+ num_physical_cores = cores.size
90
+ num_logical_processors = cores.values.reduce(0,:+)
91
+
92
+ if num_physical_cores == 0
93
+ num_logical_processors = total_processors
94
+
95
+ if total_processors == 1
96
+ # Some older, single-core processors might not list ids,
97
+ # so we'll just mark them all 1.
98
+ num_physical_packages = 1
99
+ num_physical_cores = 1
100
+ else
101
+ # We have no way of knowing how many packages or cores
102
+ # we have, even though we know how many processors there are.
103
+ num_physical_packages = nil
104
+ num_physical_cores = nil
105
+ end
106
+ end
107
+
108
+ {
109
+ :num_physical_packages => num_physical_packages,
110
+ :num_physical_cores => num_physical_cores,
111
+ :num_logical_processors => num_logical_processors
112
+ }
113
+ end
114
+
115
+ def self.num_physical_packages ; get_processor_info[:num_physical_packages ] end
116
+ def self.num_physical_cores ; get_processor_info[:num_physical_cores ] end
117
+ def self.num_logical_processors; get_processor_info[:num_logical_processors] end
118
+
33
119
  def self.processor_arch
34
120
  RbConfig::CONFIG['target_cpu']
35
121
  end
@@ -13,7 +13,10 @@ module NewRelic
13
13
  begin
14
14
  blk.call
15
15
  rescue => e
16
- ::NewRelic::Agent.logger.debug("Thread #{label} exited with error", e)
16
+ ::NewRelic::Agent.logger.error("Thread #{label} exited with error", e)
17
+ rescue Exception => e
18
+ ::NewRelic::Agent.logger.error("Thread #{label} exited with exception. Re-raising in case of interupt.", e)
19
+ raise
17
20
  ensure
18
21
  ::NewRelic::Agent.logger.debug("Exiting New Relic thread: #{label}")
19
22
  end
@@ -5,103 +5,113 @@
5
5
  module NewRelic
6
6
  module Agent
7
7
  module Threading
8
+ MAX_THREAD_PROFILE_DEPTH = 500
8
9
 
9
- class BacktraceNode
10
- attr_reader :file, :method, :line_no, :children, :raw_line
11
- attr_accessor :runnable_count, :depth
12
-
13
- def initialize(line)
14
- if line
15
- @raw_line = line
16
- @root = false
17
- else
18
- @root = true
19
- end
10
+ class BacktraceBase
11
+ attr_reader :children
20
12
 
13
+ def initialize
21
14
  @children = []
22
- @runnable_count = 0
23
- @depth = 0
15
+ @depth = 0
24
16
  end
25
17
 
26
- def root?
27
- @root
18
+ def add_child_unless_present(child)
19
+ child.depth = @depth + 1
20
+ @children << child unless @children.include? child
28
21
  end
29
22
 
30
- def empty?
31
- root? && @children.empty?
23
+ def add_child(child)
24
+ child.depth = @depth + 1
25
+ @children << child
32
26
  end
33
27
 
34
- def find(raw_line)
28
+ def find_child(raw_line)
35
29
  @children.find { |child| child.raw_line == raw_line }
36
30
  end
31
+ end
32
+
33
+
34
+ class BacktraceRoot < BacktraceBase
35
+ attr_reader :flattened
36
+
37
+ def initialize
38
+ super
39
+ @flattened = []
40
+ end
37
41
 
38
42
  def ==(other)
39
- (
40
- (root? && other.root? || @raw_line == other.raw_line) &&
41
- (
42
- @depth == other.depth &&
43
- @runnable_count == other.runnable_count
44
- )
45
- )
43
+ true # all roots are at the same depth and have no raw_line
46
44
  end
47
45
 
48
- def total_count
49
- @runnable_count
46
+ def as_array
47
+ @children.map { |c| c.as_array }.compact
50
48
  end
51
49
 
52
50
  def aggregate(backtrace)
53
51
  current = self
54
52
 
53
+ depth = 0
55
54
  backtrace.reverse_each do |frame|
56
- existing_node = current.find(frame)
55
+ break if depth >= MAX_THREAD_PROFILE_DEPTH
56
+
57
+ existing_node = current.find_child(frame)
57
58
  if existing_node
58
59
  node = existing_node
59
60
  else
60
61
  node = Threading::BacktraceNode.new(frame)
61
- current.add_child_unless_present(node)
62
+ current.add_child(node)
63
+ @flattened << node
62
64
  end
63
65
 
64
66
  node.runnable_count += 1
65
67
  current = node
68
+ depth += 1
66
69
  end
67
70
  end
68
71
 
69
- # Descending order on count, ascending on depth of nodes
70
- def <=>(other)
71
- [-runnable_count, depth] <=> [-other.runnable_count, other.depth]
72
+ def dump_string
73
+ result = "#<BacktraceRoot:#{object_id}>"
74
+ child_results = @children.map { |c| c.dump_string(2) }.join("\n")
75
+ result << "\n" unless child_results.empty?
76
+ result << child_results
77
+ end
78
+ end
79
+
80
+
81
+ class BacktraceNode < BacktraceBase
82
+ attr_reader :file, :method, :line_no, :raw_line, :as_array
83
+ attr_accessor :runnable_count, :depth
84
+
85
+ def initialize(line)
86
+ super()
87
+ @raw_line = line
88
+ @children = []
89
+ @runnable_count = 0
90
+ end
91
+
92
+ def ==(other)
93
+ (
94
+ @raw_line == other.raw_line &&
95
+ @depth == other.depth &&
96
+ @runnable_count == other.runnable_count
97
+ )
72
98
  end
73
99
 
74
- def flatten
75
- initial = self.root? ? [] : [self]
76
- @children.inject(initial) { |all, child| all.concat(child.flatten) }
100
+ def mark_for_array_conversion
101
+ @as_array = []
77
102
  end
78
103
 
79
104
  include NewRelic::Coerce
80
105
 
81
- def to_array
82
- child_arrays = @children.map { |c| c.to_array }
83
- return child_arrays if root?
84
- file, method, line = parse_backtrace_frame(@raw_line)
85
- [
86
- [
87
- string(file),
88
- string(method),
89
- int(line)
90
- ],
91
- int(@runnable_count),
92
- 0,
93
- child_arrays
94
- ]
95
- end
106
+ def complete_array_conversion
107
+ child_arrays = @children.map { |c| c.as_array }.compact
96
108
 
97
- def add_child_unless_present(child)
98
- child.depth = @depth + 1
99
- @children << child unless @children.include? child
100
- end
109
+ file, method, line = parse_backtrace_frame(@raw_line)
101
110
 
102
- def prune!(targets)
103
- @children.delete_if { |child| targets.include?(child) }
104
- @children.each { |child| child.prune!(targets) }
111
+ @as_array << [string(file), string(method), int(line)]
112
+ @as_array << int(@runnable_count)
113
+ @as_array << 0
114
+ @as_array << child_arrays
105
115
  end
106
116
 
107
117
  def dump_string(indent=0)