Extending APM Capabilities with End-User Data: RUM Integration
Application Performance Monitoring (APM) agents instrument backend servers, databases, and microservices. However, server-side spans terminate at the API gateway or reverse proxy boundary, leaving engineers blind to client-side network stalls, JavaScript execution bottlenecks, Core Web Vitals regressions, and CDN edge cache delays.
Site Reliability Engineers bridge this observability divide by integrating Real User Monitoring (RUM) directly with APM distributed tracing pipelines. By propagating W3C traceparent headers from client browser navigation contexts into backend microservices, teams establish end-to-end trace graphs that correlate frontend user impact with server-side SQL queries. This guide explores RUM data pipelines, OpenTelemetry browser instrumentation, and diagnostic workflows.
1. Latency Decomposition and Ingestion Capacity Models
RUM measures the total user-perceived duration ((T_{\text{RUM}})) across distinct physical phases:
[T_{\text{RUM}} = T_{\text{network}} + T_{\text{server}} + T_{\text{response}} + T_{\text{browser}}]
While APM accurately measures internal server execution ((T_{\text{APM}} \approx T_{\text{server}})), the client and transit overhead ((T_{\text{network+client}})) is derived by computing:
[T_{\text{network+client}} \approx T_{\text{RUM}} - T_{\text{APM}}]
To budget telemetry pipeline costs and prevent collector saturation, model monthly event volume ((\text{MonthlyEvents})):
[\text{MonthlyEvents} = \text{DailyUsers} \times \text{SessionsPerUser} \times \text{EventsPerSession} \times \text{SamplingRate} \times 30]
Tail-sampling strategies allow teams to ingest (100%) of failed transactions while sampling healthy sessions at (5%) to control storage expenditures.
2. RUM vs APM Signal Coverage Matrix
Combining client-side RUM with server-side APM eliminates blind spots across modern web architectures:
| Architectural Layer | Telemetry Signal | Captured by APM? | Captured by RUM? | Primary Diagnostic Utility |
|---|---|---|---|---|
| Client Device / DOM | LCP, INP, CLS & Long Tasks | No | Yes | Measuring browser rendering & main-thread blocking |
| Client Network | DNS, TCP, TLS handshake timings | No | Yes | Exposing mobile ISP latency & slow CDN PoPs |
| Client Runtime | Unhandled JS exceptions & CORS errors | No | Yes | Catching broken SPA hydration & bundle crashes |
| API Gateway / Ingress | HTTP status codes & edge TTFB | Partial | Yes | Measuring request queue time & WAF drops |
| Backend Microservices | Distributed trace spans & RPC latencies | Yes | Linked via Trace ID | Isolating inter-service latency bottlenecks |
| Database & Cache | Slow SQL queries & Redis lock contention | Yes | Linked via Trace ID | Pinpointing root-cause resource exhaustion |
3. SRE Core Web Vitals and Frontend Threshold Matrix
Establish operational thresholds for client-side user experience and beacon reliability:
| Performance Signal | Healthy Baseline | Warning Investigation | Critical Incident Alert | Primary Ownership |
|---|---|---|---|---|
| Largest Contentful Paint (LCP) | (< 2.5\text{ s}) | (2.5\text{ s} - 4.0\text{ s}) | (> 4.0\text{ s}) | Frontend Platform |
| Interaction to Next Paint (INP) | (< 200\text{ ms}) | (200\text{ ms} - 500\text{ ms}) | (> 500\text{ ms}) | Web Application UI |
| Cumulative Layout Shift (CLS) | (< 0.10) | (0.10 - 0.25) | (> 0.25) | Frontend Engineering |
| Frontend API p95 Latency | (< 300\text{ ms}) | (300\text{ ms} - 750\text{ ms}) | (> 750\text{ ms}) | Backend Services |
| Client JavaScript Error Rate | (< 0.1%) | (0.1% - 1.0%) | (> 1.0%) | Frontend Platform |
| RUM Beacon Ingestion Failure | (< 1.0%) | (1.0% - 3.0%) | (> 3.0%) | Observability / SRE |
4. OpenTelemetry Browser SDK and Beacon Implementation
Capture Navigation Timing metrics and dispatch structured payloads using navigator.sendBeacon():
// Extract browser navigation timing metrics
const nav = performance.getEntriesByType("navigation")[0];
const rumPayload = {
service: "pingzo-frontend",
version: "2026.09.01",
url: window.location.pathname,
timings: {
dns_duration_ms: nav.domainLookupEnd - nav.domainLookupStart,
tcp_duration_ms: nav.connectEnd - nav.connectStart,
tls_duration_ms: nav.secureConnectionStart ? nav.connectEnd - nav.secureConnectionStart : 0,
ttfb_ms: nav.responseStart - nav.requestStart,
download_ms: nav.responseEnd - nav.responseStart,
dom_interactive_ms: nav.domInteractive
}
};
// Dispatch non-blocking beacon to RUM collector
navigator.sendBeacon(
"/api/rum/v1/events",
new Blob([JSON.stringify(rumPayload)], { type: "application/json" })
);
Configure OpenTelemetry in the browser to inject W3C traceparent headers into outgoing fetch calls:
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
const provider = new WebTracerProvider();
const exporter = new OTLPTraceExporter({
url: "https://telemetry.pingzoapp.com/v1/traces"
});
provider.addSpanProcessor(
new BatchSpanProcessor(exporter, {
maxQueueSize: 512,
scheduledDelayMillis: 5000,
maxExportBatchSize: 128
})
);
provider.register();
registerInstrumentations({
instrumentations: [
new FetchInstrumentation({
propagateTraceHeaderCorsUrls: [/.+/]
})
]
});
Validate collector endpoints and TLS negotiation states using CLI tools:
# Test RUM telemetry ingestion endpoint using HTTP/2
curl --http2 -sS -o /dev/null -w 'Status: %{http_code} | Total: %{time_total}s\n' \
-X POST "https://telemetry.pingzoapp.com/v1/traces" \
-H "Content-Type: application/json" \
-d '{"resourceSpans":[]}'
# Inspect collector TLS 1.3 certificate validity
openssl s_client -connect telemetry.pingzoapp.com:443 -servername telemetry.pingzoapp.com -tls1_3 </dev/null
[!NOTE] SRE Error Budget Alert: Translate frontend availability targets into allowable downtime allowances with our SLA Calculator. If client beacons report elevated connection latencies across regions, verify nameserver resolution using the DNS Lookup tool.
5. Troubleshooting RUM-to-APM Trace Correlation
Follow this structured runbook when client RUM sessions fail to link to backend APM traces:
- Verify browser beacon dispatch: Inspect browser DevTools Network tabs to ensure
/api/rumor/v1/tracesendpoints return HTTP200 OKor202 Accepted. - Audit CORS and CSP headers: Ensure that
Content-Security-Policydirectives allowconnect-srcto the telemetry domain, and verifyAccess-Control-Allow-Originheaders. - Inspect W3C
traceparentheaders: Confirm that outgoing API requests includetraceparent: 00-{traceId}-{spanId}-01and that reverse proxies do not strip trace headers. - Validate backend span adoption: Check that backend API gateways extract incoming trace context to create child spans rather than generating new root trace IDs.
- Audit trace sampling rules: Verify that backend APM collectors do not drop traces sampled by the frontend RUM SDK.
- Evaluate collector queue depth: Monitor collector metrics (
otelcol_exporter_enqueue_failed_spans) to identify queue overflows and ingestion throttling. - Check server-timing propagation: Ensure backend services emit
Server-Timing: app;dur=42.1headers so browser agents can record precise backend processing durations. - Correlate regressions with release tags: Filter RUM telemetry by
git_shaandrelease.versionto isolate frontend bundle regressions from backend infrastructure faults. - Validate recovery: Confirm that end-to-end trace graphs show connected client-to-database execution spans before closing the investigation.
Stop Finding Out About Outages from Angry Users
Get instant WhatsApp & Discord alerts the second your API, website, or server goes down. Setup in 30 seconds with 60-second checks.