Skip to content

Commit 3936e7c

Browse files
committed
fix ttfb, fb, fco, measurements, connection attributes
1 parent 1695674 commit 3936e7c

File tree

7 files changed

+190
-68
lines changed

7 files changed

+190
-68
lines changed

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ window._testBaseTimestamp = performance.timeOrigin / 1000;
55

66
Sentry.init({
77
dsn: 'https://public@dsn.ingest.sentry.io/1337',
8-
integrations: [
9-
Sentry.browserTracingIntegration({ idleTimeout: 4000 }),
10-
Sentry.spanStreamingIntegration(),
11-
],
8+
integrations: [Sentry.browserTracingIntegration({ idleTimeout: 4000 }), Sentry.spanStreamingIntegration()],
129
tracesSampleRate: 1,
1310
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window._testBaseTimestamp = performance.timeOrigin / 1000;
5+
6+
Sentry.init({
7+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
8+
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
9+
traceLifecycle: 'stream',
10+
tracesSampleRate: 1,
11+
debug: true,
12+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../utils/fixtures';
3+
import { hidePage, shouldSkipTracingTest, testingCdnBundle } from '../../../../utils/helpers';
4+
import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils';
5+
6+
sentryTest.beforeEach(async ({ page }) => {
7+
if (shouldSkipTracingTest() || testingCdnBundle()) {
8+
sentryTest.skip();
9+
}
10+
11+
await page.setViewportSize({ width: 800, height: 1200 });
12+
});
13+
14+
sentryTest(
15+
'captures TTFB and TTFB request time as attributes on the streamed pageload span',
16+
async ({ getLocalTestUrl, page }) => {
17+
const url = await getLocalTestUrl({ testDir: __dirname });
18+
19+
const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload');
20+
21+
await page.goto(url);
22+
await hidePage(page);
23+
24+
const pageloadSpan = await pageloadSpanPromise;
25+
26+
// If responseStart === 0, TTFB is not reported.
27+
// This seems to happen somewhat randomly, so we handle it.
28+
const responseStart = await page.evaluate("performance.getEntriesByType('navigation')[0].responseStart;");
29+
if (responseStart !== 0) {
30+
expect(pageloadSpan.attributes?.['browser.web_vital.ttfb.value']?.type).toMatch(/^(double)|(integer)$/);
31+
expect(pageloadSpan.attributes?.['browser.web_vital.ttfb.value']?.value).toBeGreaterThan(0);
32+
}
33+
34+
expect(pageloadSpan.attributes?.['browser.web_vital.ttfb.request_time']?.type).toMatch(/^(double)|(integer)$/);
35+
},
36+
);

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

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { getBrowserPerformanceAPI, isMeasurementValue, msToSec, startAndEndSpan
2727
import { getActivationStart } from './web-vitals/lib/getActivationStart';
2828
import { getNavigationEntry } from './web-vitals/lib/getNavigationEntry';
2929
import { getVisibilityWatcher } from './web-vitals/lib/getVisibilityWatcher';
30-
30+
import { debug } from '@sentry/core';
3131
interface NavigatorNetworkInformation {
3232
readonly connection?: NetworkInformation;
3333
}
@@ -336,6 +336,11 @@ interface AddPerformanceEntriesOptions {
336336
* Default: []
337337
*/
338338
ignorePerformanceApiSpans: Array<string | RegExp>;
339+
340+
/**
341+
* Whether span streaming is enabled.
342+
*/
343+
spanStreamingEnabled?: boolean;
339344
}
340345

341346
/** Add performance related spans to a transaction */
@@ -347,6 +352,14 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
347352
return;
348353
}
349354

355+
const {
356+
spanStreamingEnabled,
357+
ignorePerformanceApiSpans,
358+
ignoreResourceSpans,
359+
recordClsOnPageloadSpan,
360+
recordLcpOnPageloadSpan,
361+
} = options;
362+
350363
const timeOrigin = msToSec(origin);
351364

352365
const performanceEntries = performance.getEntries();
@@ -375,7 +388,7 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
375388
case 'mark':
376389
case 'paint':
377390
case 'measure': {
378-
_addMeasureSpans(span, entry, startTime, duration, timeOrigin, options.ignorePerformanceApiSpans);
391+
_addMeasureSpans(span, entry, startTime, duration, timeOrigin, ignorePerformanceApiSpans);
379392

380393
// capture web vitals
381394
const firstHidden = getVisibilityWatcher();
@@ -398,7 +411,7 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
398411
startTime,
399412
duration,
400413
timeOrigin,
401-
options.ignoreResourceSpans,
414+
ignoreResourceSpans,
402415
);
403416
break;
404417
}
@@ -408,25 +421,51 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
408421

409422
_performanceCursor = Math.max(performanceEntries.length - 1, 0);
410423

411-
_trackNavigator(span);
424+
_trackNavigator(span, spanStreamingEnabled);
412425

413426
// Measurements are only available for pageload transactions
414427
if (op === 'pageload') {
415428
_addTtfbRequestTimeToMeasurements(_measurements);
416429

417-
// If CLS standalone spans are enabled, don't record CLS as a measurement
418-
if (!options.recordClsOnPageloadSpan) {
419-
delete _measurements.cls;
420-
}
430+
if (spanStreamingEnabled) {
431+
const setAttr = (shortWebVitalName: string, value: number, customAttrName?: string) => {
432+
const attrKey = customAttrName ?? `browser.web_vital.${shortWebVitalName}.value`;
433+
span.setAttribute(attrKey, value);
434+
debug.log('Setting web vital attribute', { [attrKey]: value }, 'on pageload span');
435+
};
436+
// for streamed pageload spans, we add the web vital measurements as attributes.
437+
// We omit LCP, CLS and INP because they're tracked separately as spans
438+
if (_measurements['ttfb']) {
439+
setAttr('ttfb', _measurements['ttfb'].value);
440+
}
441+
if (_measurements['ttfb.requestTime']) {
442+
setAttr('ttfb.requestTime', _measurements['ttfb.requestTime'].value, 'browser.web_vital.ttfb.request_time');
443+
}
444+
if (_measurements['fp']) {
445+
setAttr('fp', _measurements['fp'].value);
446+
}
447+
if (_measurements['fcp']) {
448+
setAttr('fcp', _measurements['fcp'].value);
449+
}
450+
} else {
451+
// TODO (V11): Remove this else branch once we remove v1 standalone spans and transactions
421452

422-
// If LCP standalone spans are enabled, don't record LCP as a measurement
423-
if (!options.recordLcpOnPageloadSpan) {
424-
delete _measurements.lcp;
425-
}
453+
// If CLS standalone spans are enabled, don't record CLS as a measurement
454+
if (!recordClsOnPageloadSpan) {
455+
delete _measurements.cls;
456+
}
426457

427-
Object.entries(_measurements).forEach(([measurementName, measurement]) => {
428-
setMeasurement(measurementName, measurement.value, measurement.unit);
429-
});
458+
// If LCP standalone spans are enabled, don't record LCP as a measurement
459+
if (!recordLcpOnPageloadSpan) {
460+
delete _measurements.lcp;
461+
}
462+
463+
Object.entries(_measurements).forEach(([measurementName, measurement]) => {
464+
setMeasurement(measurementName, measurement.value, measurement.unit);
465+
});
466+
467+
_setWebVitalAttributes(span, options);
468+
}
430469

431470
// Set timeOrigin which denotes the timestamp which to base the LCP/FCP/FP/TTFB measurements on
432471
span.setAttribute('performance.timeOrigin', timeOrigin);
@@ -438,8 +477,6 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
438477
// This is user action is called "activation" and the time between navigation and activation is stored in
439478
// the `activationStart` attribute of the "navigation" PerformanceEntry.
440479
span.setAttribute('performance.activationStart', getActivationStart());
441-
442-
_setWebVitalAttributes(span, options);
443480
}
444481

445482
_lcpEntry = undefined;
@@ -734,8 +771,9 @@ export function _addResourceSpans(
734771

735772
/**
736773
* Capture the information of the user agent.
774+
* TODO v11: Remove non-span-streaming attributes and measurements once we removed transactions
737775
*/
738-
function _trackNavigator(span: Span): void {
776+
function _trackNavigator(span: Span, spanStreamingEnabled: boolean | undefined): void {
739777
const navigator = WINDOW.navigator as null | (Navigator & NavigatorNetworkInformation & NavigatorDeviceMemory);
740778
if (!navigator) {
741779
return;
@@ -745,24 +783,37 @@ function _trackNavigator(span: Span): void {
745783
const connection = navigator.connection;
746784
if (connection) {
747785
if (connection.effectiveType) {
748-
span.setAttribute('effectiveConnectionType', connection.effectiveType);
786+
span.setAttribute(
787+
spanStreamingEnabled ? 'network.connection.effective_type' : 'effectiveConnectionType',
788+
connection.effectiveType,
789+
);
749790
}
750791

751792
if (connection.type) {
752-
span.setAttribute('connectionType', connection.type);
793+
span.setAttribute(spanStreamingEnabled ? 'network.connection.type' : 'connectionType', connection.type);
753794
}
754795

755796
if (isMeasurementValue(connection.rtt)) {
756797
_measurements['connection.rtt'] = { value: connection.rtt, unit: 'millisecond' };
798+
if (spanStreamingEnabled) {
799+
span.setAttribute('network.connection.rtt', connection.rtt);
800+
}
757801
}
758802
}
759803

760804
if (isMeasurementValue(navigator.deviceMemory)) {
761-
span.setAttribute('deviceMemory', `${navigator.deviceMemory} GB`);
805+
if (spanStreamingEnabled) {
806+
span.setAttribute('device.memory.estimated_capacity', navigator.deviceMemory);
807+
} else {
808+
span.setAttribute('deviceMemory', `${navigator.deviceMemory} GB`);
809+
}
762810
}
763811

764812
if (isMeasurementValue(navigator.hardwareConcurrency)) {
765-
span.setAttribute('hardwareConcurrency', String(navigator.hardwareConcurrency));
813+
span.setAttribute(
814+
spanStreamingEnabled ? 'device.processor_count' : 'hardwareConcurrency',
815+
String(navigator.hardwareConcurrency),
816+
);
766817
}
767818
}
768819

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ import { addClsInstrumentationHandler, addInpInstrumentationHandler, addLcpInstr
2222
import type { WebVitalReportEvent } from './utils';
2323
import { getBrowserPerformanceAPI, listenForWebVitalReportEvents, msToSec, supportsWebVital } from './utils';
2424
import type { PerformanceEventTiming } from './instrument';
25+
2526
interface WebVitalSpanOptions {
2627
name: string;
2728
op: string;
2829
origin: string;
2930
metricName: 'lcp' | 'cls' | 'inp';
3031
value: number;
31-
unit: string;
3232
attributes?: SpanAttributes;
3333
parentSpan?: Span;
3434
reportEvent?: WebVitalReportEvent;
@@ -147,7 +147,6 @@ export function _sendLcpSpan(
147147
origin: 'auto.http.browser.lcp',
148148
metricName: 'lcp',
149149
value: lcpValue,
150-
unit: 'millisecond',
151150
attributes,
152151
parentSpan: pageloadSpan,
153152
reportEvent,
@@ -210,7 +209,6 @@ export function _sendClsSpan(
210209
origin: 'auto.http.browser.cls',
211210
metricName: 'cls',
212211
value: clsValue,
213-
unit: '',
214212
attributes,
215213
parentSpan: pageloadSpan,
216214
reportEvent,
@@ -279,7 +277,6 @@ export function _sendInpSpan(inpValue: number, entry: PerformanceEventTiming): v
279277
origin: 'auto.http.browser.inp',
280278
metricName: 'inp',
281279
value: inpValue,
282-
unit: 'millisecond',
283280
attributes: {
284281
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry.duration,
285282
'sentry.transaction': routeName,

0 commit comments

Comments
 (0)