DDTrace.Span (dd_trace_ex v0.1.0)

Copy Markdown View Source

One unit of traced work, in the shape Datadog receives it.

Almost every field on this struct is a field the agent is sent verbatim, which is what makes it worth asserting on directly — DDTrace.Test's matchers run against the values that go on the wire, with one exception: the trace-level markings below are written when the trace is complete and its chunk is on its way to the agent, which is after a span is captured. Assert on those against the chunk the agent receives.

Spans are opened by DDTrace.start_span/2 and closed by DDTrace.finish_span/2; this module is the shape they have in between.

Wire fields

  • trace_id — the 128-bit trace this span belongs to. Only its low 64 bits travel in the trace_id field; the upper half travels as the _dd.p.tid tag on the first span of the chunk.
  • span_id — this span's own 63-bit identifier.
  • parent_id — the enclosing span's identifier, 0 for a trace root.
  • name — the operation, e.g. "db.query"; the same across every instance of that operation.
  • resource — what the operation acted on, e.g. "SELECT * FROM orders"; defaults to the name.
  • service — the service the work is attributed to.
  • type — the Datadog span type, e.g. "web" or "sql".
  • start — when the span opened, in nanoseconds since the epoch.
  • duration — how long it took, in nanoseconds; nil until finished.
  • error1 when the span failed, 0 otherwise.
  • meta — string tags. A span closed by its parent carries dd_trace_ex.abandoned here.
  • metrics — numeric tags.

Trace-level markings

Three keys say something about the trace rather than about the span carrying them: _dd.p.tid, _dd.p.dm and the _sampling_priority_v1 metric. The agent reads them off the first span of each chunk, so that is where the tracer writes them — when the trace completes, not when the span opens, since a trace's sampling decision can change while it runs.

Everything else

Two fields exist only inside the tracer and never reach the agent: finished?, which is what makes closing a span twice harmless, and monotonic_start, the reading duration is measured against.

Timing

start comes from the system clock and duration from the monotonic clock, so a clock adjustment between the two can shorten neither. Both are overridden by the start_time: and finish_time: options, which backfill a span around work that was measured elsewhere: the duration is then exactly the difference between the two.

Summary

Types

A reading of both clocks: system time and monotonic time, in nanoseconds.

t()

Types

clocks()

@type clocks() :: {integer(), integer()}

A reading of both clocks: system time and monotonic time, in nanoseconds.

t()

@type t() :: %DDTrace.Span{
  duration: non_neg_integer() | nil,
  error: 0 | 1,
  finished?: boolean(),
  meta: %{optional(String.t()) => String.t()},
  metrics: %{optional(String.t()) => float()},
  monotonic_start: integer(),
  name: String.t(),
  parent_id: non_neg_integer(),
  resource: String.t(),
  service: String.t() | nil,
  span_id: non_neg_integer(),
  start: integer(),
  trace_id: non_neg_integer(),
  type: String.t() | nil
}