Skip to content

Commit 45f3698

Browse files
logaretmclaude
andcommitted
fix(browser): Only emit LCP, CLS, INP as streamed spans; disable standalone spans when streaming
TTFB, FCP, and FP should remain as attributes on the pageload span rather than separate streamed spans. Also ensures standalone CLS/LCP spans are disabled when span streaming is enabled to prevent duplicate spans. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dce9fc2 commit 45f3698

File tree

7 files changed

+8
-390
lines changed

7 files changed

+8
-390
lines changed

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-streamed-spans/init.js

Lines changed: 0 additions & 11 deletions
This file was deleted.

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-streamed-spans/template.html

Lines changed: 0 additions & 10 deletions
This file was deleted.

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-streamed-spans/test.ts

Lines changed: 0 additions & 84 deletions
This file was deleted.

packages/browser-utils/src/index.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export {
44
addTtfbInstrumentationHandler,
55
addLcpInstrumentationHandler,
66
addInpInstrumentationHandler,
7-
addFcpInstrumentationHandler,
87
} from './metrics/instrument';
98

109
export {
@@ -21,14 +20,7 @@ export { startTrackingElementTiming } from './metrics/elementTiming';
2120

2221
export { extractNetworkProtocol } from './metrics/utils';
2322

24-
export {
25-
trackClsAsSpan,
26-
trackFcpAsSpan,
27-
trackFpAsSpan,
28-
trackInpAsSpan,
29-
trackLcpAsSpan,
30-
trackTtfbAsSpan,
31-
} from './metrics/webVitalSpans';
23+
export { trackClsAsSpan, trackInpAsSpan, trackLcpAsSpan } from './metrics/webVitalSpans';
3224

3325
export { addClickKeypressInstrumentationHandler } from './instrument/dom';
3426

packages/browser-utils/src/metrics/webVitalSpans.ts

Lines changed: 1 addition & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,8 @@ import { DEBUG_BUILD } from '../debug-build';
1919
import { WINDOW } from '../types';
2020
import { INP_ENTRY_MAP } from './inp';
2121
import type { InstrumentationHandlerCallback } from './instrument';
22-
import {
23-
addClsInstrumentationHandler,
24-
addFcpInstrumentationHandler,
25-
addInpInstrumentationHandler,
26-
addLcpInstrumentationHandler,
27-
addPerformanceInstrumentationHandler,
28-
addTtfbInstrumentationHandler,
29-
} from './instrument';
22+
import { addClsInstrumentationHandler, addInpInstrumentationHandler, addLcpInstrumentationHandler } from './instrument';
3023
import { listenForWebVitalReportEvents, msToSec, supportsWebVital } from './utils';
31-
import { getVisibilityWatcher } from './web-vitals/lib/getVisibilityWatcher';
3224

3325
interface WebVitalSpanOptions {
3426
name: string;
@@ -266,112 +258,3 @@ export function _sendInpSpan(
266258
endTime: startTime + msToSec(entry.duration),
267259
});
268260
}
269-
270-
/**
271-
* Tracks TTFB as a streamed span.
272-
*/
273-
export function trackTtfbAsSpan(client: Client): void {
274-
addTtfbInstrumentationHandler(({ metric }) => {
275-
_sendTtfbSpan(metric.value, client);
276-
});
277-
}
278-
279-
/**
280-
* Exported only for testing.
281-
*/
282-
export function _sendTtfbSpan(ttfbValue: number, _client: Client): void {
283-
DEBUG_BUILD && debug.log(`Sending TTFB span (${ttfbValue})`);
284-
285-
const timeOrigin = msToSec(browserPerformanceTimeOrigin() || 0);
286-
287-
const attributes: SpanAttributes = {};
288-
289-
// Try to get request_time from navigation timing
290-
try {
291-
const navEntry = WINDOW.performance?.getEntriesByType?.('navigation')[0] as PerformanceNavigationTiming | undefined;
292-
if (navEntry) {
293-
attributes['browser.web_vital.ttfb.request_time'] = navEntry.responseStart - navEntry.requestStart;
294-
}
295-
} catch {
296-
// ignore
297-
}
298-
299-
_emitWebVitalSpan({
300-
name: 'TTFB',
301-
op: 'ui.webvital.ttfb',
302-
origin: 'auto.http.browser.ttfb',
303-
metricName: 'ttfb',
304-
value: ttfbValue,
305-
unit: 'millisecond',
306-
attributes,
307-
startTime: timeOrigin,
308-
endTime: timeOrigin + msToSec(ttfbValue),
309-
});
310-
}
311-
312-
/**
313-
* Tracks FCP as a streamed span.
314-
*/
315-
export function trackFcpAsSpan(_client: Client): void {
316-
addFcpInstrumentationHandler(({ metric }) => {
317-
_sendFcpSpan(metric.value);
318-
});
319-
}
320-
321-
/**
322-
* Exported only for testing.
323-
*/
324-
export function _sendFcpSpan(fcpValue: number): void {
325-
DEBUG_BUILD && debug.log(`Sending FCP span (${fcpValue})`);
326-
327-
const timeOrigin = msToSec(browserPerformanceTimeOrigin() || 0);
328-
329-
_emitWebVitalSpan({
330-
name: 'FCP',
331-
op: 'ui.webvital.fcp',
332-
origin: 'auto.http.browser.fcp',
333-
metricName: 'fcp',
334-
value: fcpValue,
335-
unit: 'millisecond',
336-
startTime: timeOrigin,
337-
endTime: timeOrigin + msToSec(fcpValue),
338-
});
339-
}
340-
341-
/**
342-
* Tracks FP (First Paint) as a streamed span.
343-
*/
344-
export function trackFpAsSpan(_client: Client): void {
345-
const visibilityWatcher = getVisibilityWatcher();
346-
347-
addPerformanceInstrumentationHandler('paint', ({ entries }) => {
348-
for (const entry of entries) {
349-
if (entry.name === 'first-paint') {
350-
if (entry.startTime < visibilityWatcher.firstHiddenTime) {
351-
_sendFpSpan(entry.startTime);
352-
}
353-
break;
354-
}
355-
}
356-
});
357-
}
358-
359-
/**
360-
* Exported only for testing.
361-
*/
362-
export function _sendFpSpan(fpStartTime: number): void {
363-
DEBUG_BUILD && debug.log(`Sending FP span (${fpStartTime})`);
364-
365-
const timeOrigin = msToSec(browserPerformanceTimeOrigin() || 0);
366-
367-
_emitWebVitalSpan({
368-
name: 'FP',
369-
op: 'ui.webvital.fp',
370-
origin: 'auto.http.browser.fp',
371-
metricName: 'fp',
372-
value: fpStartTime,
373-
unit: 'millisecond',
374-
startTime: timeOrigin,
375-
endTime: timeOrigin + msToSec(fpStartTime),
376-
});
377-
}

0 commit comments

Comments
 (0)