Skip to content

Continuous Profiler and Error Tracking

Tracing tells you where something is slow or failing — which service, which span — while the Continuous Profiler and Error Tracking tell you why, down to the exact function burning CPU or the deduplicated root cause behind a pile of repeated stack traces.

Continuous Profiler: code-level visibility, always on

Section titled “Continuous Profiler: code-level visibility, always on”

The Continuous Profiler runs constantly, in production, at low overhead, capturing CPU time, wall-time, and allocations at the code level — individual functions and call stacks, not just service-level aggregates. Because it’s always on rather than something you turn on to investigate one incident after the fact, the data is already there by the time you need it.

The profiler ties its samples back to the specific service, endpoint, and even the individual trace they occurred in. That link is what makes it useful for debugging a slow request: instead of guessing which part of checkout-web is expensive, you jump from a slow trace directly into a flame graph of exactly which function was running, and for how long, during that request:

# ddtrace's profiler starts alongside the tracer — no separate instrumentation needed
from ddtrace.profiling import Profiler
prof = Profiler()
prof.start()

With both the tracer and profiler running, a slow span in a trace can point you straight at the function-level flame graph for that exact window of execution — tracing narrows down which span, the profiler narrows down which line of code.

Error Tracking: turning repeated stack traces into one issue

Section titled “Error Tracking: turning repeated stack traces into one issue”

Error Tracking takes error events surfaced from traces and logs and automatically groups and deduplicates similar errors into a single issue, instead of leaving you to manually notice that the same NullPointerException has fired 482 times across a week of traces. Each issue carries a lifecycle you manage explicitly — typically new, triaged, and resolved — so a recurring error becomes one thing your team tracks to closure rather than hundreds of individual, unrelated-looking events:

{
"issue": {
"fingerprint": "[email protected]_discount",
"status": "new",
"occurrences": 482,
"first_seen": "2026-07-01T10:03:00Z",
"last_seen": "2026-07-08T09:12:00Z",
"example_trace_ids": ["6141ee2f9a4d4b2e8f3c1a90", "9af23cd1b8e7f4a6c2d5e901"]
}
}

Notice the example_trace_ids field — Error Tracking keeps a link back to actual example traces for the issue, so you can jump from “this issue has fired 482 times” straight into one concrete occurrence to see the full request context around it.

Put the two together and they cover the gap tracing alone leaves open:

  • Tracing — which service and span is slow or failing.
  • Continuous Profiler — which function, at the code level, is consuming the CPU/wall-time/allocations during that slow span.
  • Error Tracking — which deduplicated issue a failing span actually belongs to, and where that issue sits in its new/triaged/resolved lifecycle.

Neither tool replaces tracing — they both start from a trace or span and go one level deeper than “which service” can take you.

flowchart LR
  Trace[Slow or failing trace] --> Where[Tracing: which service, which span]
  Where --> Profiler[Continuous Profiler: which function, CPU/wall-time/allocations]
  Where --> ErrorTracking[Error Tracking: deduplicated issue, new/triaged/resolved]
  Profiler --> WhySlow[Why it is slow]
  ErrorTracking --> WhyFailing[Why it is failing]
From where to why
What does the Continuous Profiler add on top of what tracing already shows you?
What does Error Tracking do with 482 occurrences of the same underlying error across many traces?
Why is the Continuous Profiler described as "always-on" rather than something you enable during an incident?
In one sentence, how do tracing and these two tools divide responsibility?