-
Notifications
You must be signed in to change notification settings - Fork 604
/
Copy pathabstract_segment.rb
264 lines (220 loc) · 8.28 KB
/
abstract_segment.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
require 'new_relic/agent/range_extensions'
require 'new_relic/agent/guid_generator'
module NewRelic
module Agent
class Transaction
class AbstractSegment
# This class is the base class for all segments. It is reponsible for
# timing, naming, and defining lifecycle callbacks. One of the more
# complex responsibilites of this class is computing exclusive duration.
# One of the reasons for this complexity is that exclusive time will be
# computed using time ranges or by recording an aggregate value for
# a segments children time. The reason for this is that computing
# exclusive duration using time ranges is expensive and it's only
# necessary if a segment's children run concurrently, or a segment ends
# after its parent. We will use the optimized exclusive duration
# calculation in all other cases.
#
attr_reader :start_time, :end_time, :duration, :exclusive_duration, :guid
attr_accessor :name, :parent, :children_time, :transaction, :transaction_name
attr_writer :record_metrics, :record_scoped_metric, :record_on_finish
attr_reader :noticed_error
def initialize name=nil, start_time=nil
@name = name
@transaction_name = nil
@transaction = nil
@guid = NewRelic::Agent::GuidGenerator.generate_guid
@parent = nil
@params = nil
@start_time = start_time if start_time
@end_time = nil
@duration = 0.0
@exclusive_duration = 0.0
@children_time = 0.0
@children_time_ranges = nil
@active_children = 0
@range_recorded = false
@concurrent_children = false
@record_metrics = true
@record_scoped_metric = true
@record_on_finish = false
@noticed_error = nil
end
def start
@start_time ||= Time.now
return unless transaction
parent.child_start self if parent
end
def finish
@end_time = Time.now
@duration = end_time.to_f - start_time.to_f
return unless transaction
run_complete_callbacks
finalize if record_on_finish?
rescue => e
NewRelic::Agent.logger.error "Exception finishing segment: #{name}", e
end
def finished?
!!@end_time
end
def record_metrics?
@record_metrics
end
def record_scoped_metric?
@record_scoped_metric
end
def record_on_finish?
@record_on_finish
end
def finalize
force_finish unless finished?
record_exclusive_duration
record_metrics if record_metrics?
end
def params
@params ||= {}
end
def params?
!!@params
end
def time_range
@start_time.to_f .. @end_time.to_f
end
def children_time_ranges
@children_time_ranges ||= []
end
def children_time_ranges?
!!@children_time_ranges
end
def concurrent_children?
@concurrent_children
end
INSPECT_IGNORE = [:@transaction, :@transaction_state].freeze
def inspect
ivars = (instance_variables - INSPECT_IGNORE).inject([]) do |memo, var_name|
memo << "#{var_name}=#{instance_variable_get(var_name).inspect}"
end
sprintf('#<%s:0x%x %s>', self.class.name, object_id, ivars.join(', '))
end
# callback for subclasses to override
def transaction_assigned
end
def set_noticed_error noticed_error
if @noticed_error
NewRelic::Agent.logger.debug \
"Segment: #{name} overwriting previously noticed " \
"error: #{@noticed_error.inspect} with: #{noticed_error.inspect}"
end
@noticed_error = noticed_error
end
def notice_error exception, options={}
if Agent.config[:high_security]
NewRelic::Agent.logger.debug \
"Segment: #{name} ignores notice_error for " \
"error: #{exception.inspect} because :high_security is enabled"
else
NewRelic::Agent.instance.error_collector.notice_segment_error self, exception, options
end
end
def noticed_error_attributes
return unless @noticed_error
@noticed_error.attributes_from_notice_error
end
protected
attr_writer :range_recorded
def range_recorded?
@range_recorded
end
def child_start segment
@active_children += 1
@concurrent_children = @concurrent_children || @active_children > 1
transaction.async = true if @concurrent_children
end
def child_complete segment
@active_children -= 1
record_child_time segment
if finished?
transaction.async = true
parent.descendant_complete(self, segment) if parent
end
end
# When a child segment completes after its parent, we need to propagate
# the information about the descendant further up the tree so that
# ancestors can properly account for exclusive time. Once we've reached
# an ancestor whose end time is greater than or equal to the descendant's
# we can stop the propagation. We pass along the direct child so we can
# make any corrections needed for exclusive time calculation.
def descendant_complete child, descendant
RangeExtensions.merge_or_append descendant.time_range,
children_time_ranges
# If this child's time was previously added to this segment's
# aggregate children time, we need to re-record it using a time range
# for proper exclusive time calculation
unless child.range_recorded?
self.children_time -= child.duration
record_child_time_as_range child
end
if parent && finished? && descendant.end_time >= end_time
parent.descendant_complete self, descendant
end
end
private
def force_finish
finish
NewRelic::Agent.logger.warn "Segment: #{name} was unfinished at " \
"the end of transaction. Timing information for this segment's" \
"parent #{parent.name} in #{transaction.best_name} may be inaccurate."
end
def run_complete_callbacks
segment_complete
parent.child_complete self if parent
transaction.segment_complete self
end
def record_metrics
raise NotImplementedError, "Subclasses must implement record_metrics"
end
# callback for subclasses to override
def segment_complete
end
def record_child_time child
if concurrent_children? || finished? && end_time < child.end_time
record_child_time_as_range child
else
record_child_time_as_number child
end
end
def record_child_time_as_range child
RangeExtensions.merge_or_append child.time_range,
children_time_ranges
child.range_recorded = true
end
def record_child_time_as_number child
self.children_time += child.duration
end
def record_exclusive_duration
overlapping_duration = if children_time_ranges?
RangeExtensions.compute_overlap time_range, children_time_ranges
else
0.0
end
@exclusive_duration = duration - children_time - overlapping_duration
transaction.total_time += @exclusive_duration
params[:exclusive_duration_millis] = @exclusive_duration * 1000 if transaction.async?
end
def metric_cache
transaction.metrics
end
def transaction_state
@transaction_state ||= if @transaction
transaction.state
else
Tracer.state
end
end
end
end
end
end