Skip to content

Commit

Permalink
ref(browser): Add protocol metadata to resource spans
Browse files Browse the repository at this point in the history
- Relocated `extractNetworkProtocol` util to `browser-utils`.
- Modified `browserMetrics` test to assert the new metadata.

Signed-off-by: Kaung Zin Hein <[email protected]>
  • Loading branch information
Zen-cronic committed Jan 26, 2025
1 parent a67d3ca commit 41eeee6
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 85 deletions.
2 changes: 2 additions & 0 deletions packages/browser-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export {
registerInpInteractionListener,
} from './metrics/browserMetrics';

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

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

export { addHistoryInstrumentationHandler } from './instrument/history';
Expand Down
12 changes: 11 additions & 1 deletion packages/browser-utils/src/metrics/browserMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ import {
addPerformanceInstrumentationHandler,
addTtfbInstrumentationHandler,
} from './instrument';
import { getBrowserPerformanceAPI, isMeasurementValue, msToSec, startAndEndSpan } from './utils';
import {
extractNetworkProtocol,
getBrowserPerformanceAPI,
isMeasurementValue,
msToSec,
startAndEndSpan,
} from './utils';
import { getActivationStart } from './web-vitals/lib/getActivationStart';
import { getNavigationEntry } from './web-vitals/lib/getNavigationEntry';
import { getVisibilityWatcher } from './web-vitals/lib/getVisibilityWatcher';
Expand Down Expand Up @@ -596,6 +602,10 @@ export function _addResourceSpans(

attributes['url.same_origin'] = resourceUrl.includes(WINDOW.location.origin);

const { name, version } = extractNetworkProtocol(entry.nextHopProtocol);
attributes['network.protocol.name'] = name;
attributes['network.protocol.version'] = version;

const startTimestamp = timeOrigin + startTime;
const endTimestamp = startTimestamp + duration;

Expand Down
31 changes: 31 additions & 0 deletions packages/browser-utils/src/metrics/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,34 @@ export function getBrowserPerformanceAPI(): Performance | undefined {
export function msToSec(time: number): number {
return time / 1000;
}

/**
* Converts ALPN protocol ids to name and version.
*
* (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids)
* @param nextHopProtocol PerformanceResourceTiming.nextHopProtocol
*/
export function extractNetworkProtocol(nextHopProtocol: string): { name: string; version: string } {
let name = 'unknown';
let version = 'unknown';
let _name = '';
for (const char of nextHopProtocol) {
// http/1.1 etc.
if (char === '/') {
[name, version] = nextHopProtocol.split('/') as [string, string];
break;
}
// h2, h3 etc.
if (!isNaN(Number(char))) {
name = _name === 'h' ? 'http' : _name;
version = nextHopProtocol.split(_name)[1] as string;
break;
}
_name += char;
}
if (_name === nextHopProtocol) {
// webrtc, ftp, etc.
name = _name;
}
return { name, version };
}
16 changes: 16 additions & 0 deletions packages/browser-utils/test/browser/browserMetrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ describe('_addResourceSpans', () => {
encodedBodySize: 256,
decodedBodySize: 256,
renderBlockingStatus: 'non-blocking',
nextHopProtocol: 'http/1.1',
});
_addResourceSpans(span, entry, resourceEntryName, 123, 456, 100);

Expand All @@ -150,6 +151,7 @@ describe('_addResourceSpans', () => {
encodedBodySize: 256,
decodedBodySize: 256,
renderBlockingStatus: 'non-blocking',
nextHopProtocol: 'http/1.1',
});
_addResourceSpans(span, entry, 'https://example.com/assets/to/me', 123, 456, 100);

Expand All @@ -169,6 +171,7 @@ describe('_addResourceSpans', () => {
encodedBodySize: 456,
decodedBodySize: 593,
renderBlockingStatus: 'non-blocking',
nextHopProtocol: 'http/1.1',
});

const timeOrigin = 100;
Expand All @@ -195,6 +198,8 @@ describe('_addResourceSpans', () => {
['url.scheme']: 'https',
['server.address']: 'example.com',
['url.same_origin']: true,
['network.protocol.name']: 'http',
['network.protocol.version']: '1.1',
},
}),
);
Expand Down Expand Up @@ -233,6 +238,7 @@ describe('_addResourceSpans', () => {
const { initiatorType, op } = table[i]!;
const entry = mockPerformanceResourceTiming({
initiatorType,
nextHopProtocol: 'http/1.1',
});
_addResourceSpans(span, entry, 'https://example.com/assets/to/me', 123, 234, 465);

Expand All @@ -254,6 +260,7 @@ describe('_addResourceSpans', () => {
encodedBodySize: 0,
decodedBodySize: 0,
renderBlockingStatus: 'non-blocking',
nextHopProtocol: 'h2',
});

_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);
Expand All @@ -271,6 +278,8 @@ describe('_addResourceSpans', () => {
['url.scheme']: 'https',
['server.address']: 'example.com',
['url.same_origin']: true,
['network.protocol.name']: 'http',
['network.protocol.version']: '2',
},
}),
);
Expand All @@ -288,6 +297,7 @@ describe('_addResourceSpans', () => {
transferSize: 2147483647,
encodedBodySize: 2147483647,
decodedBodySize: 2147483647,
nextHopProtocol: 'h3',
});

_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);
Expand All @@ -301,6 +311,8 @@ describe('_addResourceSpans', () => {
'server.address': 'example.com',
'url.same_origin': true,
'url.scheme': 'https',
['network.protocol.name']: 'http',
['network.protocol.version']: '3',
},
description: '/assets/to/css',
timestamp: 468,
Expand All @@ -325,6 +337,7 @@ describe('_addResourceSpans', () => {
transferSize: null,
encodedBodySize: null,
decodedBodySize: null,
nextHopProtocol: 'h3',
} as unknown as PerformanceResourceTiming;

_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);
Expand All @@ -338,6 +351,8 @@ describe('_addResourceSpans', () => {
'server.address': 'example.com',
'url.same_origin': true,
'url.scheme': 'https',
['network.protocol.name']: 'http',
['network.protocol.version']: '3',
},
description: '/assets/to/css',
timestamp: 468,
Expand Down Expand Up @@ -365,6 +380,7 @@ describe('_addResourceSpans', () => {
encodedBodySize: 0,
decodedBodySize: 0,
deliveryType,
nextHopProtocol: 'h3',
});

_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);
Expand Down
53 changes: 52 additions & 1 deletion packages/browser-utils/test/browser/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SentrySpan, getCurrentScope, getIsolationScope, setCurrentClient, spanToJSON } from '@sentry/core';
import { startAndEndSpan } from '../../src/metrics/utils';
import { extractNetworkProtocol, startAndEndSpan } from '../../src/metrics/utils';
import { TestClient, getDefaultClientOptions } from '../utils/TestClient';

describe('startAndEndSpan()', () => {
Expand Down Expand Up @@ -54,3 +54,54 @@ describe('startAndEndSpan()', () => {
expect(spanToJSON(parentSpan).start_timestamp).toEqual(123);
});
});

interface ProtocolInfo {
name: string;
version: string;
}

describe('HTTPTimings', () => {
test('Extracting version from ALPN protocol', () => {
const nextHopToNetworkVersion: Record<string, ProtocolInfo> = {
'http/0.9': { name: 'http', version: '0.9' },
'http/1.0': { name: 'http', version: '1.0' },
'http/1.1': { name: 'http', version: '1.1' },
'spdy/1': { name: 'spdy', version: '1' },
'spdy/2': { name: 'spdy', version: '2' },
'spdy/3': { name: 'spdy', version: '3' },
'stun.turn': { name: 'stun.turn', version: 'unknown' },
'stun.nat-discovery': { name: 'stun.nat-discovery', version: 'unknown' },
h2: { name: 'http', version: '2' },
h2c: { name: 'http', version: '2c' },
webrtc: { name: 'webrtc', version: 'unknown' },
'c-webrtc': { name: 'c-webrtc', version: 'unknown' },
ftp: { name: 'ftp', version: 'unknown' },
imap: { name: 'imap', version: 'unknown' },
pop3: { name: 'pop', version: '3' },
managesieve: { name: 'managesieve', version: 'unknown' },
coap: { name: 'coap', version: 'unknown' },
'xmpp-client': { name: 'xmpp-client', version: 'unknown' },
'xmpp-server': { name: 'xmpp-server', version: 'unknown' },
'acme-tls/1': { name: 'acme-tls', version: '1' },
mqtt: { name: 'mqtt', version: 'unknown' },
dot: { name: 'dot', version: 'unknown' },
'ntske/1': { name: 'ntske', version: '1' },
sunrpc: { name: 'sunrpc', version: 'unknown' },
h3: { name: 'http', version: '3' },
smb: { name: 'smb', version: 'unknown' },
irc: { name: 'irc', version: 'unknown' },
nntp: { name: 'nntp', version: 'unknown' },
nnsp: { name: 'nnsp', version: 'unknown' },
doq: { name: 'doq', version: 'unknown' },
'sip/2': { name: 'sip', version: '2' },
'tds/8.0': { name: 'tds', version: '8.0' },
dicom: { name: 'dicom', version: 'unknown' },
};

const protocols = Object.keys(nextHopToNetworkVersion);
for (const protocol of protocols) {
const expected = nextHopToNetworkVersion[protocol]!;
expect(extractNetworkProtocol(protocol)).toMatchObject(expected);
}
});
});
32 changes: 1 addition & 31 deletions packages/browser/src/tracing/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
SENTRY_XHR_DATA_KEY,
addPerformanceInstrumentationHandler,
addXhrInstrumentationHandler,
extractNetworkProtocol,
} from '@sentry-internal/browser-utils';
import type { Client, HandlerDataXhr, SentryWrappedXMLHttpRequest, Span } from '@sentry/core';
import {
Expand Down Expand Up @@ -228,37 +229,6 @@ function addHTTPTimings(span: Span): void {
});
}

/**
* Converts ALPN protocol ids to name and version.
*
* (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids)
* @param nextHopProtocol PerformanceResourceTiming.nextHopProtocol
*/
export function extractNetworkProtocol(nextHopProtocol: string): { name: string; version: string } {
let name = 'unknown';
let version = 'unknown';
let _name = '';
for (const char of nextHopProtocol) {
// http/1.1 etc.
if (char === '/') {
[name, version] = nextHopProtocol.split('/') as [string, string];
break;
}
// h2, h3 etc.
if (!isNaN(Number(char))) {
name = _name === 'h' ? 'http' : _name;
version = nextHopProtocol.split(_name)[1] as string;
break;
}
_name += char;
}
if (_name === nextHopProtocol) {
// webrtc, ftp, etc.
name = _name;
}
return { name, version };
}

function getAbsoluteTime(time: number = 0): number {
return ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000;
}
Expand Down
53 changes: 1 addition & 52 deletions packages/browser/test/tracing/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as browserUtils from '@sentry-internal/browser-utils';
import * as utils from '@sentry/core';
import type { Client } from '@sentry/core';

import { extractNetworkProtocol, instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/tracing/request';
import { instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/tracing/request';

beforeAll(() => {
// @ts-expect-error need to override global Request because it's not in the vi environment (even with an
Expand Down Expand Up @@ -64,57 +64,6 @@ describe('instrumentOutgoingRequests', () => {
});
});

interface ProtocolInfo {
name: string;
version: string;
}

describe('HTTPTimings', () => {
test('Extracting version from ALPN protocol', () => {
const nextHopToNetworkVersion: Record<string, ProtocolInfo> = {
'http/0.9': { name: 'http', version: '0.9' },
'http/1.0': { name: 'http', version: '1.0' },
'http/1.1': { name: 'http', version: '1.1' },
'spdy/1': { name: 'spdy', version: '1' },
'spdy/2': { name: 'spdy', version: '2' },
'spdy/3': { name: 'spdy', version: '3' },
'stun.turn': { name: 'stun.turn', version: 'unknown' },
'stun.nat-discovery': { name: 'stun.nat-discovery', version: 'unknown' },
h2: { name: 'http', version: '2' },
h2c: { name: 'http', version: '2c' },
webrtc: { name: 'webrtc', version: 'unknown' },
'c-webrtc': { name: 'c-webrtc', version: 'unknown' },
ftp: { name: 'ftp', version: 'unknown' },
imap: { name: 'imap', version: 'unknown' },
pop3: { name: 'pop', version: '3' },
managesieve: { name: 'managesieve', version: 'unknown' },
coap: { name: 'coap', version: 'unknown' },
'xmpp-client': { name: 'xmpp-client', version: 'unknown' },
'xmpp-server': { name: 'xmpp-server', version: 'unknown' },
'acme-tls/1': { name: 'acme-tls', version: '1' },
mqtt: { name: 'mqtt', version: 'unknown' },
dot: { name: 'dot', version: 'unknown' },
'ntske/1': { name: 'ntske', version: '1' },
sunrpc: { name: 'sunrpc', version: 'unknown' },
h3: { name: 'http', version: '3' },
smb: { name: 'smb', version: 'unknown' },
irc: { name: 'irc', version: 'unknown' },
nntp: { name: 'nntp', version: 'unknown' },
nnsp: { name: 'nnsp', version: 'unknown' },
doq: { name: 'doq', version: 'unknown' },
'sip/2': { name: 'sip', version: '2' },
'tds/8.0': { name: 'tds', version: '8.0' },
dicom: { name: 'dicom', version: 'unknown' },
};

const protocols = Object.keys(nextHopToNetworkVersion);
for (const protocol of protocols) {
const expected = nextHopToNetworkVersion[protocol]!;
expect(extractNetworkProtocol(protocol)).toMatchObject(expected);
}
});
});

describe('shouldAttachHeaders', () => {
describe('should prefer `tracePropagationTargets` over defaults', () => {
it('should return `true` if the url matches the new tracePropagationTargets', () => {
Expand Down

0 comments on commit 41eeee6

Please sign in to comment.