Continuous profiling for every DataDog runtime - Java, Go, Python, Node, .NET, PHP, Ruby, Native.
Ingest, flame graphs, library categorization, endpoint breakdown, cross-env comparison, and trace
pivot. Shipped behind profiles.preview=true.
Their fleet: 571 services across 20 environments, mostly Java/Spring, with .NET, Node, PHP, Python in the long tail. DataDog Java agent versions span 1.31.2 through 1.60.3+, all comfortably above the 1.30 floor needed for endpoint binding.
Show JFL developers profile data alongside traces in the cluster they already trust - same UX guarantees they had on DataDog, without asking them to swap agents.
DataDog-agent-only ingest for Phase 1. No custom agent, no symbolization, no OTLP/Profiles yet. Match DataDog's developer workflow (flame, compare, endpoint, multi-runtime) with the 80% that ships clean.
Preview behind a flag. API path stable from day one. Warning: 299
header during preview, drops at GA. No URL churn for integrators when GA lands.
Two upload topologies, both validated end-to-end. JFL runs Pattern B in production, the DataDog agent sits between the JVM and CubeAPM and re-uploads. Same multipart wire format reaches the receiver either way.
Every UI surface JFL developers reach for first - picker, facets, flame, compare, endpoint drill - done at production scale.
The Profiles list pulls every recent capture across all services + envs, with a time
histogram across the top and faceted filtering on the left. The amber bar in the header
is the honest preview signal:
DataDog ingest preview · profiles.preview=true.
DataDog's affordance, faithful. Substring match, virtualized dropdown, Pinned + Recent sections, Cmd-K shortcut. Cross-env families surface together so devs spot the same feature across regions in one keystroke.
Every facet is sourced from the actual ingest data via a single /field_values
endpoint. No hard-coded enums - when a new runtime appears tomorrow, the rail picks
it up automatically. Counts update live as the time picker shifts.
The single most-used surface. View dropdown picks the lens (Full Stack / Only My Code / Libraries / Infrastructure). Color By switches the palette (Library category / Value heatmap / per-Function hash). Group By collapses frames (Method / Class / Package). Every combination is URL-bound so links into a specific lens always reproduce.
Top-50 sorts methods by self / total / share. Sandwich pivots on any single function and shows callers above + callees below - the classic perf-investigation move when you know what you're hunting for. Both ride on the same flame data; no extra fetch.
For Java services, the DataDog agent tags each CPU / wall / lock sample with the endpoint it ran inside. The right rail tallies them with verb chip + path + share, plus click-to-copy. When the agent didn't bind requests (old version, or tracing off), an honest empty state explains why, no blank panel, no fake data.
From any profile, "Compare To" surfaces three time-shifts (1 hour ago, Yesterday,
Last week) plus auto-discovered cross-env candidates - when the same service exists in
prod-na
and prod-eu,
the dropdown lists "Same in prod-eu" as a one-click pivot. Largest-mover ordering
keeps the diff readable on a 4-frame-deep flame.
Phase 1 ingests every runtime DataDog supports. Java is the rich case - six profile records per upload (cpu, alloc_space, lock_time, wall_time, gc_time, jfr-source). Others land correctly with their native fan-out. Library categorization is keyed per-runtime so "Only My Code" feels right whether the app is Spring or Go or Django.
Every Java / Python / Node / .NET / PHP upload carries the active trace ID on samples that ran inside a span. The Trace Inspect view picks up the pivot and lists profile records that overlap any span - or the whole trace. Tolerant of format drift between signal sources (decimal vs hex trace IDs), so prod profiles correlate even when encodings disagree.
Phase 1 is preview-flagged but not preview-quality. Test coverage matches the trace + log receivers we ship today. Real-DataDog fixtures drive the parsers. The receiver gates ingest behind a body-size cap, a decompression cap, and a configurable concurrency cap.
| Layer | Test | Threshold | Result |
|---|---|---|---|
| Ingest parser | jfr.Parse on real DD safe-mode capture | p99 < 200 ms / call | 3.19 ms · 60× under cap |
| Query (Flame) | Aggregate on 50K-sample profile | p95 < 1 s | 11 ms · 90× under cap |
| Storage | Round-trip write → query → byte equality | No drift | Byte-for-byte identical |
| Rate limit | Burst 1105 services × 60s cadence | 10× JFL prod rate | Cap holds at prod rate with ~12× headroom |
| Multi-tenant | Token / session auth on ingest + read | Cross-tenant isolation | Enforced at storage tenant axis |
| Self-dogfood | CubeAPM profiling CubeAPM (in-binary + agent sidecar) | Both topologies land | 5-record fan-out, every cycle |