Class: Datadog::Context

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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(options = {})
  @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 = options.fetch(:max_length, DEFAULT_MAX_LENGTH)
  reset(options)
end

Instance Attribute Details

#max_lengthObject (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_spanObject



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_spanObject

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_spanObject

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_ifArray<Span>

Delete any span matching the condition. This is thread safe.

Returns:

  • (Array<Span>)

    deleted spans



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.

Returns:

  • (Boolean)


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_countObject

@@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_cloneObject

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

#getArray<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.

Returns:

  • (Array<Array<Span>, Boolean>)

    finished trace and sampled flag



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

#originObject



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.

Returns:

  • (Boolean)


172
173
174
175
176
# File 'lib/ddtrace/context.rb', line 172

def sampled?
  @mutex.synchronize do
    return @sampled
  end
end

#sampling_priorityObject



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_idObject



45
46
47
48
49
# File 'lib/ddtrace/context.rb', line 45

def span_id
  @mutex.synchronize do
    @parent_span_id
  end
end

#to_sObject

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_idObject



39
40
41
42
43
# File 'lib/ddtrace/context.rb', line 39

def trace_id
  @mutex.synchronize do
    @parent_trace_id
  end
end