APM Architecture, Concepts, and Workflow
APM tracing gives you a complete picture of how a single request moves through your system — which services it touched, how long each step took, and where errors or latency originated. Unlike metrics and logs, traces require changes to your application code. This page explains the core concepts, how tracing fits into the integration pipeline, and how to put it to work monitoring services, debugging incidents, and alerting on problems.
Overview
Tracing works by injecting a small instrumentation library into each service. The library records the start and end of every significant operation (a database query, an outbound HTTP call, a message queue publish) as a span, tags it with service name and context labels, and links related spans across service boundaries into a single trace.
Each instrumented service emits its spans to a local collection agent (kf-agent or the Datadog Agent — see Collection Agent), which forwards them over HTTPS to the Kloudfuse ingester. The ingester writes the spans to the Traces Store, where they become queryable in the APM views described in APM Traces and APM Services.
| The New Relic agent is the exception to this two-stage pattern. It sends spans directly to the Kloudfuse ingester using the New Relic collector protocol — bypassing kf-agent and the Datadog Agent entirely. No additional collection agent is required. See New Relic Trace Integration for configuration details. |
No trace data is visible in Kloudfuse until instrumentation is added. Metrics and logs can flow without touching application code; traces cannot.
Core Concepts
Trace
A trace is the complete journey of a single request across every service it touched. It begins at the entry point (frontend, API gateway, or similar), is assembled from individual spans that share the same trace ID, and enables end-to-end latency and performance analysis.
The trace forms a tree: the root span is the entry point (typically the first HTTP request), and child spans are nested operations within and across services. Kloudfuse displays a trace in the Traces Explorer’s trace detail as a flame graph and waterfall, showing the relative timing and nesting of every span.
Span
A span is a single timed operation within a service — a database query, an outbound HTTP call, a function call you instrumented manually. Every span records:
-
A name (for example,
http.request,db.query,kafka.produce) -
A start time and duration
-
A status (success or error)
-
Attributes — key-value pairs describing the operation (HTTP method, SQL statement, queue name)
-
A trace ID and parent span ID — linking the span to its place in the overall request, and to its parent (calling) or children (called) operations
Service
A service is a logically grouped, independently deployable unit of software that handles part of a transaction and exposes a network endpoint — for example, payment-service, auth-service, or inventory-db. Services generate spans, send traces, and are the primary unit of observability in APM Services. Exactly what distinguishes one service from another (so that, say, the same code running in two environments counts as two services) is configurable — see Service identity.
Relationships
-
One trace is made of many spans.
-
Spans form a DAG (directed acyclic graph) — a parent can have several children, but each span has exactly one parent (except the root).
-
Every span is assigned to exactly one service.
Context Propagation
For spans from different services to be linked into a single trace, the trace ID must be passed between services as each request crosses a service boundary. This is called context propagation.
Both Datadog and OTel SDKs handle context propagation automatically for common protocols:
-
HTTP — trace context is injected into request headers (
traceparentfor W3C,x-datadog-trace-idfor Datadog). -
gRPC — context is injected into gRPC metadata.
-
Message queues — context is embedded in message attributes (Kafka, RabbitMQ, SQS, depending on the library).
As long as all services in a call chain use compatible SDKs and propagation formats, spans assemble into a complete trace automatically.
Integration Components
Tracing involves three components: the SDK in your code, a collection agent, and the Kloudfuse ingester.
Instrumentation SDK
The SDK is a library added to your application. It intercepts calls to common frameworks and clients (HTTP servers, database drivers, message queue clients) and emits spans without requiring per-call code changes. This is called automatic instrumentation.
You can also add manual instrumentation to create custom spans for business logic that the automatic instrumentation does not cover.
Kloudfuse supports two SDK families:
-
OpenTelemetry SDKs — open-standard libraries available for all major languages. Spans are emitted over OTLP (HTTP or gRPC) to a collector.
-
Datadog SDKs (
dd-trace-*) — Datadog-compatible libraries. Spans are emitted over the Datadog trace protocol to the Datadog Agent.
Both families produce traces that appear identically in Kloudfuse APM. You can mix OTel and Datadog SDKs across services in the same environment — traces from both are correlated by trace ID.
Collection Agent
The collection agent runs in your cluster and receives spans from application pods. It buffers, compresses, and forwards spans to the Kloudfuse ingester. Applications send spans to the agent over the local network — no direct internet access from application pods is required.
Two agents support trace collection:
kf-agent (OTel-based)
kf-agent is a Kubernetes-native collector deployed as a Kubernetes Deployment.
It exposes OTLP endpoints for applications instrumented with OTel SDKs:
Because kf-agent runs as a Deployment (not a DaemonSet), all pods in the cluster share a single set of agent pods regardless of which node they run on.
This minimizes resource overhead for trace collection.
See OTel Kubernetes Integration for installation and configuration.
Datadog Agent
The Datadog Agent DaemonSet also accepts traces from applications instrumented with Datadog SDKs.
Application pods send traces to the agent on the host node using the DD_AGENT_HOST environment variable (which resolves to the node’s host IP).
The Datadog Agent trace listener runs on port 8126.
Applications using Datadog SDKs configure this via the DD_AGENT_HOST and DD_TRACE_AGENT_PORT environment variables.
See Datadog Kubernetes Integration for Datadog Agent setup.
Kloudfuse Ingester
The ingester receives spans from the collection agent over HTTPS and routes them to the Traces Store. Kloudfuse accepts spans in two formats:
-
OTLP HTTP Protobuf (
POST /ingester/v1/traces) — forkf-agentand OTel Collectors -
Datadog APM wire protocol — for Datadog Agent
Both formats produce traces queryable with the same APM views and TraceQL.
Choosing Between OTel and Datadog SDKs
| Consideration | OTel SDK | Datadog SDK |
|---|---|---|
Standard |
Open standard (CNCF); vendor-neutral |
Datadog proprietary; maps to OTel conventions internally |
Automatic instrumentation coverage |
Broad; most major frameworks covered |
Broad; especially strong for JVM and Python |
Agent |
|
Datadog Agent |
Manual instrumentation API |
OTel Tracer API (standardized) |
|
Mixed environments |
Compatible with Datadog via context propagation headers |
Compatible with OTel via W3C |
If you are migrating from Datadog to Kloudfuse, Datadog SDK instrumentation works without changes — just repoint the Datadog Agent at Kloudfuse.
If you are starting fresh, OTel SDKs are recommended for new services because they are vendor-neutral and avoid lock-in.
Language Instrumentation Guides
Step-by-step instrumentation for each supported language:
For migrating existing Datadog-instrumented services to OTel:
Tracing Outside Kubernetes
Docker
For Docker Compose environments, run kf-agent or the Datadog Agent as an additional service in your Compose file.
Application containers send traces to the agent container by service name.
See Docker Integration for Docker setup including the agent service definition.
AWS Lambda
The Datadog Lambda Extension Layer collects APM traces from Lambda functions. The extension runs inside the Lambda execution environment alongside your function code and forwards traces directly to Kloudfuse without a separate agent.
See Lambda Extension Layer for setup.
Verifying Traces Are Arriving
After deploying the agent and instrumenting at least one service:
-
Trigger a request through your application — for example, make an HTTP call to an instrumented endpoint.
-
In the Kloudfuse UI, click APM in the top navigation, then Explorer under Traces.
-
Set the time range to the last 15 minutes.
-
The trace for your request should appear in the Traces list. Click it to open the flame graph view; see Trace detail.
If no traces appear, check:
-
The agent pod is running and its logs show no connection errors to the Kloudfuse ingester.
-
The application is configured with the correct agent host and port.
-
The instrumentation library is loaded — for JVM agents, check that
-javaagent:is present in the JVM flags.
Using APM: Monitoring, Debugging, and Alerting
Once traces are flowing, the day-to-day work is monitoring service health, debugging the incidents that come up, and making sure you hear about the next one automatically. This section ties the individual APM screens together into that workflow. If you are looking for how to use one specific screen instead, see Services, Traces, or K-Lens.
Monitoring service health
Start at APM > Services > Service List (Service List). The table is sorted by request volume by default and gives you, per service, requests/s, p50/p99 latency, max latency, error %, and Apdex for the selected time range. Scan it for the two signals that matter most day to day: a service with a rising Error %, or one whose p99 Latency has drifted away from its usual value. If you don’t yet know what "usual" looks like for a service, widen the time range to a day or a week first — a service that’s always noisy at this percentile isn’t the same problem as one that just changed.
Use the facet panel on the left (span type, language, version, and any Infrastructure/Cloud/Additional labels) to narrow the table to a subset of services — for example, only the services in one Kubernetes namespace or one cloud account. See Filtering traces for the full list of filterable attributes (the same facets apply to Services, Databases, and Traces).
Database-backed calls (Postgres, Redis, and so on) get their own row in the Databases tab of the same page, so a slow query shows up as its own line item instead of being folded into the calling service’s numbers.
For a topology-level view of the same health data, switch to APM > Services > Service Map (Service Map). Nodes are sized by traffic and colored by error rate, so a problem service — or an unhealthy dependency several hops away from the one you actually own — is visually obvious even before you read a number.
Don’t wait to eyeball this table, though: put alerts on the services that matter most, and let Kloudfuse tell you when a number moves instead of checking on a schedule.
Debugging a specific problem
Once the Service List, Service Map, or an alert points you at a suspect service, click it to open the Service detail page. This is where you narrow from "this service is unhealthy" to "this is the specific call, deployment, or dependency causing it." Work through these tabs roughly in this order:
-
Check Deployments first if the problem started recently — it re-plots request rate, latency, and error rate broken out by
service_version, so a bad rollout is visible immediately as one version’s line diverging from the rest. This single check rules out (or confirms) the most common cause of a sudden regression before you look at anything else. -
Check Downstream and Upstream to see whether the problem originates in this service or is being caused by (or is causing trouble in) a service it calls or is called by. A latency increase that shows up in Downstream but not in Span Names usually means a dependency slowed down, not this service’s own code.
-
Check Infra and Runtime to rule out a resource cause — pod-level outliers, node pressure, or a memory/GC problem in the language runtime — before assuming the issue is in application logic.
-
Open Errors to see the specific error groups this service is producing, or Logs to see log lines correlated to the same service and time window without leaving the page.
From any of these tabs, switch to the Traces tab (or go directly to APM > Traces > Explorer) to find an individual slow or failing request. Filter by service_name and, if you already suspect it, span_type or an endpoint, then open a trace to see its flame graph and waterfall — this tells you exactly which span, on which host, took the time or returned the error, down to the stack trace and correlated logs.
When the traces list is too large to eyeball, or no single trace explains the pattern, reach for K-Lens instead of scrolling through dozens of traces one at a time. Rather than inspecting individual requests, K-Lens compares the slow or erroring population of spans against everything else and tells you which attribute — a pod, an availability zone, a database name, a service version, a Kubernetes node — is over-represented in the problem population. This turns "p99 is elevated" into "it’s these three pods in us-west1-a" in one step, without you having to guess which attribute to filter by first. See Using K-Lens as part of an investigation for a worked example of exactly this scenario.
Alerting on APM signals
Rather than waiting to notice a problem in the Service List, put an alert on it. The fastest path is from the service itself: on the Service detail page, click No APM alerts * (or * next to an existing alert count) to open the alert wizard already scoped to that service. You can also start from Alerts > New Alert and pick APM as the alert type — see Create an APM alert for the full field-by-field reference.
The wizard has four steps:
-
Choose a detection method — Threshold alerts fire when a metric crosses a fixed value you set; Anomaly alerts fire when a metric deviates from its own learned baseline. Start with Threshold for anything you can put a hard number on (error rate should never exceed 1%, an SLA-bound endpoint should never exceed 500ms p99). Reach for Anomaly when a service’s normal traffic and latency vary a lot by time of day or day of week — a fixed threshold would either miss real regressions during quiet periods or false-positive during expected peaks.
-
Pick a service or write a query — select the service (and, optionally, narrow it with an additional filter such as
span_type=webor a specific endpoint), then choose what to alert on: Requests/s, Errors/s, Error rate, p50/p75/p90/p95/p99 latency, or Average latency. Prefer a percentile (p90 or p99) over an average for latency alerts — an average hides the slow tail that’s usually what users actually experience. -
Set the alert condition — for a Threshold alert, the comparison (
is above,is below, and so on) and the value; for an Anomaly alert, the algorithm (Basic, Agile, Robust, or Agile + Robust — see Anomaly algorithm options) plus its sensitivity (Bound), which side of the baseline to watch (Band), and how much history it learns from. Either way, use the optional Warning and recovery thresholds, Evaluation frequency, and No data and error handling settings to tune how noisy the alert is before you rely on it. -
Add alert details — name the alert for the problem it represents (not the query, since that’s what whoever gets paged actually reads), choose the folder it’s stored in, and configure notification routing.
Service-level objectives (SLOs) complement threshold/anomaly alerts for services where you want to track compliance against a target over a rolling window (say, "99.9% of requests under 500ms over 30 days") rather than react to individual spikes. Configure one from the No SLO + control on the Service detail page; once configured, SLO burn appears in the SLOs tab. Use SLOs for the handful of services with an actual reliability commitment, and threshold/anomaly alerts for everything else — an SLO alert on every service just becomes noise.
Correlating an alert with the rest of your data
When an APM alert fires, don’t stop at the metric that tripped it — pivot to the underlying request. From the alert, or from the service it fired on, use the Logs tab to see log lines from the same service and time window, and the Traces tab to find the individual requests responsible. If the alert doesn’t point at an obvious single cause, use K-Lens the same way you would in a manual investigation: select the time window the alert covers on the heatmap and let it rank the attributes for you.
Because Kloudfuse correlates traces, logs, and infrastructure metrics by shared labels (service name, pod, trace ID), you can move between "the alert fired," "here is a failing request," and "here is the log line and pod it happened on" without re-entering filters by hand.