Class: Datadog::Context
- Inherits:
-
Object
- Object
- Datadog::Context
- Includes:
- Utils::Forking
- Defined in:
- lib/ddtrace/context.rb
Overview
\Context is used to keep track of a hierarchy of spans for the current execution flow. During each logical execution, the same \Context is used to represent a single logical trace, even if the trace is built asynchronously.
A single code execution may use multiple \Context if part of the execution must not be related to the current tracing. As example, a delayed job may compose a standalone trace instead of being related to the same trace that generates the job itself. On the other hand, if it's part of the same \Context, it will be related to the original trace.
This data structure is thread-safe. rubocop:disable Metrics/ClassLength
Constant Summary collapse
- DEFAULT_MAX_LENGTH =
100k spans is about a 100Mb footprint
100_000
Instance Attribute Summary collapse
-
#max_length ⇒ Object
readonly
Returns the value of attribute max_length.
Instance Method Summary collapse
-
#add_span(span) ⇒ Object
Add a span to the context trace list, keeping it as the last active span.
-
#annotate_for_flush!(span) ⇒ Object
Set tags to root span required for flush.
- #attach_origin(span) ⇒ Object
- #attach_sampling_priority(span) ⇒ Object
-
#close_span(span) ⇒ Object
Mark a span as a finished, increasing the internal counter to prevent cycles inside _trace list.
- #current_root_span ⇒ Object
-
#current_span ⇒ Object
Return the last active span that corresponds to the last inserted item in the trace list.
-
#current_span_and_root_span ⇒ Object
Same as calling #current_span and #current_root_span, but works atomically thus preventing races when we need to retrieve both.
-
#delete_span_if ⇒ Array<Span>
Delete any span matching the condition.
-
#finished? ⇒ Boolean
Returns if the trace for the current Context is finished or not.
-
#finished_span_count ⇒ Object
@@return [Numeric] numbers of finished spans.
-
#fork_clone ⇒ Object
Generates equivalent context for forked processes.
-
#get ⇒ Array<Array<Span>, Boolean>
Returns both the trace list generated in the current context and if the context is sampled or not.
-
#initialize(options = {}) ⇒ Context
constructor
Initialize a new thread-safe \Context.
- #origin ⇒ Object
- #origin=(origin) ⇒ Object
-
#sampled? ⇒ Boolean
Returns true if the context is sampled, that is, if it should be kept and sent to the trace agent.
- #sampling_priority ⇒ Object
- #sampling_priority=(priority) ⇒ Object
- #span_id ⇒ Object
-
#to_s ⇒ Object
Return a string representation of the context.
- #trace_id ⇒ Object
Methods included from Utils::Forking
#after_fork!, extended, #fork_pid, #forked?, included, #update_fork_pid!
Constructor Details
#initialize(options = {}) ⇒ Context
Initialize a new thread-safe \Context.
31 32 33 34 35 36 37 |
# File 'lib/ddtrace/context.rb', line 31 def initialize( = {}) @mutex = Mutex.new # max_length is the amount of spans above which, for a given trace, # the context will simply drop and ignore spans, avoiding high memory usage. @max_length = .fetch(:max_length, DEFAULT_MAX_LENGTH) reset() end |
Instance Attribute Details
#max_length ⇒ Object (readonly)
Returns the value of attribute max_length.
28 29 30 |
# File 'lib/ddtrace/context.rb', line 28 def max_length @max_length end |
Instance Method Details
#add_span(span) ⇒ Object
Add a span to the context trace list, keeping it as the last active span.
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 |
# File 'lib/ddtrace/context.rb', line 100 def add_span(span) @mutex.synchronize do # If hitting the hard limit, just drop spans. This is really a rare case # as it means despite the soft limit, the hard limit is reached, so the trace # by default has 10000 spans, all of which belong to unfinished parts of a # larger trace. This is a catch-all to reduce global memory usage. if @max_length > 0 && @trace.length >= @max_length # Detach the span from any context, it's being dropped and ignored. span.context = nil Datadog.logger.debug("context full, ignoring span #{span.name}") # If overflow has already occurred, don't send this metric. # Prevents metrics spam if buffer repeatedly overflows for the same trace. unless @overflow Datadog.health_metrics.error_context_overflow(1, tags: ["max_length:#{@max_length}"]) @overflow = true end return end set_current_span(span) @current_root_span = span if @trace.empty? @trace << span span.context = self end end |
#annotate_for_flush!(span) ⇒ Object
Set tags to root span required for flush
237 238 239 240 |
# File 'lib/ddtrace/context.rb', line 237 def annotate_for_flush!(span) attach_sampling_priority(span) if @sampled && @sampling_priority attach_origin(span) if @origin end |
#attach_origin(span) ⇒ Object
249 250 251 252 253 254 |
# File 'lib/ddtrace/context.rb', line 249 def attach_origin(span) span.set_tag( Ext::DistributedTracing::ORIGIN_KEY, @origin ) end |
#attach_sampling_priority(span) ⇒ Object
242 243 244 245 246 247 |
# File 'lib/ddtrace/context.rb', line 242 def attach_sampling_priority(span) span.set_metric( Ext::DistributedTracing::SAMPLING_PRIORITY_KEY, @sampling_priority ) end |
#close_span(span) ⇒ Object
Mark a span as a finished, increasing the internal counter to prevent cycles inside _trace list.
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 |
# File 'lib/ddtrace/context.rb', line 129 def close_span(span) @mutex.synchronize do @finished_spans += 1 # Current span is only meaningful for linear tree-like traces, # in other cases, this is just broken and one should rely # on per-instrumentation code to retrieve handle parent/child relations. set_current_span(span.parent) return if span.tracer.nil? if span.parent.nil? && !all_spans_finished? if Datadog.configuration.diagnostics.debug opened_spans = @trace.length - @finished_spans Datadog.logger.debug("root span #{span.name} closed but has #{opened_spans} unfinished spans:") end @trace.reject(&:finished?).group_by(&:name).each do |unfinished_span_name, unfinished_spans| Datadog.logger.debug("unfinished span: #{unfinished_spans.first}") if Datadog.configuration.diagnostics.debug Datadog.health_metrics.error_unfinished_spans( unfinished_spans.length, tags: ["name:#{unfinished_span_name}"] ) end end end end |
#current_root_span ⇒ Object
85 86 87 88 89 |
# File 'lib/ddtrace/context.rb', line 85 def current_root_span @mutex.synchronize do @current_root_span end end |
#current_span ⇒ Object
Return the last active span that corresponds to the last inserted item in the trace list. This cannot be considered as the current active span in asynchronous environments, because some spans can be closed earlier while child spans still need to finish their traced execution.
79 80 81 82 83 |
# File 'lib/ddtrace/context.rb', line 79 def current_span @mutex.synchronize do @current_span end end |
#current_span_and_root_span ⇒ Object
Same as calling #current_span and #current_root_span, but works atomically thus preventing races when we need to retrieve both
93 94 95 96 97 |
# File 'lib/ddtrace/context.rb', line 93 def current_span_and_root_span @mutex.synchronize do [@current_span, @current_root_span] end end |
#delete_span_if ⇒ Array<Span>
Delete any span matching the condition. This is thread safe.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/ddtrace/context.rb', line 212 def delete_span_if @mutex.synchronize do [].tap do |deleted_spans| @trace.delete_if do |span| finished = span.finished? next unless yield span deleted_spans << span # We need to detach the span from the context, else, some code # finishing it afterwards would mess up with the number of # finished_spans and possibly cause other side effects. span.context = nil # Acknowledge there's one span less to finish, if needed. # It's very important to keep this balanced. @finished_spans -= 1 if finished true end end end end |
#finished? ⇒ Boolean
Returns if the trace for the current Context is finished or not. A \Context is considered finished if all spans in this context are finished.
157 158 159 160 161 |
# File 'lib/ddtrace/context.rb', line 157 def finished? @mutex.synchronize do return all_spans_finished? end end |
#finished_span_count ⇒ Object
@@return [Numeric] numbers of finished spans
164 165 166 167 168 |
# File 'lib/ddtrace/context.rb', line 164 def finished_span_count @mutex.synchronize do @finished_spans end end |
#fork_clone ⇒ Object
Generates equivalent context for forked processes.
When Context from parent process is forked, child process should have a Context belonging to the same trace but not have the parent process spans.
269 270 271 272 273 274 275 276 277 |
# File 'lib/ddtrace/context.rb', line 269 def fork_clone self.class.new( trace_id: trace_id, span_id: span_id, sampled: sampled?, sampling_priority: sampling_priority, origin: origin ) end |
#get ⇒ Array<Array<Span>, Boolean>
Returns both the trace list generated in the current context and if the context is sampled or not.
It returns +[nil,@sampled]+ if the \Context is not finished.
If a trace is returned, the \Context will be reset so that it can be re-used immediately.
This operation is thread-safe.
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/ddtrace/context.rb', line 190 def get @mutex.synchronize do trace = @trace sampled = @sampled # still return sampled attribute, even if context is not finished return nil, sampled unless all_spans_finished? # Root span is finished at this point, we can configure it annotate_for_flush!(@current_root_span) # Allow caller to modify trace before context is reset yield(trace) if block_given? reset [trace, sampled] end end |
#origin ⇒ Object
63 64 65 66 67 |
# File 'lib/ddtrace/context.rb', line 63 def origin @mutex.synchronize do @origin end end |
#origin=(origin) ⇒ Object
69 70 71 72 73 |
# File 'lib/ddtrace/context.rb', line 69 def origin=(origin) @mutex.synchronize do @origin = origin end end |
#sampled? ⇒ Boolean
Returns true if the context is sampled, that is, if it should be kept and sent to the trace agent.
172 173 174 175 176 |
# File 'lib/ddtrace/context.rb', line 172 def sampled? @mutex.synchronize do return @sampled end end |
#sampling_priority ⇒ Object
51 52 53 54 55 |
# File 'lib/ddtrace/context.rb', line 51 def sampling_priority @mutex.synchronize do @sampling_priority end end |
#sampling_priority=(priority) ⇒ Object
57 58 59 60 61 |
# File 'lib/ddtrace/context.rb', line 57 def sampling_priority=(priority) @mutex.synchronize do @sampling_priority = priority end end |
#span_id ⇒ Object
45 46 47 48 49 |
# File 'lib/ddtrace/context.rb', line 45 def span_id @mutex.synchronize do @parent_span_id end end |
#to_s ⇒ Object
Return a string representation of the context.
257 258 259 260 261 262 |
# File 'lib/ddtrace/context.rb', line 257 def to_s @mutex.synchronize do # rubocop:disable Layout/LineLength "Context(trace.length:#{@trace.length},sampled:#{@sampled},finished_spans:#{@finished_spans},current_span:#{@current_span})" end end |
#trace_id ⇒ Object
39 40 41 42 43 |
# File 'lib/ddtrace/context.rb', line 39 def trace_id @mutex.synchronize do @parent_trace_id end end |