Skip to content

Commit 41eeee6

Browse files
committed
ref(browser): Add protocol metadata to resource spans
- Relocated `extractNetworkProtocol` util to `browser-utils`. - Modified `browserMetrics` test to assert the new metadata. Signed-off-by: Kaung Zin Hein <83657429+Zen-cronic@users.noreply.github.com>
1 parent a67d3ca commit 41eeee6

File tree

7 files changed

+114
-85
lines changed

7 files changed

+114
-85
lines changed

packages/browser-utils/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export {
1717
registerInpInteractionListener,
1818
} from './metrics/browserMetrics';
1919

20+
export { extractNetworkProtocol } from './metrics/utils';
21+
2022
export { addClickKeypressInstrumentationHandler } from './instrument/dom';
2123

2224
export { addHistoryInstrumentationHandler } from './instrument/history';

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ import {
2020
addPerformanceInstrumentationHandler,
2121
addTtfbInstrumentationHandler,
2222
} from './instrument';
23-
import { getBrowserPerformanceAPI, isMeasurementValue, msToSec, startAndEndSpan } from './utils';
23+
import {
24+
extractNetworkProtocol,
25+
getBrowserPerformanceAPI,
26+
isMeasurementValue,
27+
msToSec,
28+
startAndEndSpan,
29+
} from './utils';
2430
import { getActivationStart } from './web-vitals/lib/getActivationStart';
2531
import { getNavigationEntry } from './web-vitals/lib/getNavigationEntry';
2632
import { getVisibilityWatcher } from './web-vitals/lib/getVisibilityWatcher';
@@ -596,6 +602,10 @@ export function _addResourceSpans(
596602

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

605+
const { name, version } = extractNetworkProtocol(entry.nextHopProtocol);
606+
attributes['network.protocol.name'] = name;
607+
attributes['network.protocol.version'] = version;
608+
599609
const startTimestamp = timeOrigin + startTime;
600610
const endTimestamp = startTimestamp + duration;
601611

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,34 @@ export function getBrowserPerformanceAPI(): Performance | undefined {
137137
export function msToSec(time: number): number {
138138
return time / 1000;
139139
}
140+
141+
/**
142+
* Converts ALPN protocol ids to name and version.
143+
*
144+
* (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids)
145+
* @param nextHopProtocol PerformanceResourceTiming.nextHopProtocol
146+
*/
147+
export function extractNetworkProtocol(nextHopProtocol: string): { name: string; version: string } {
148+
let name = 'unknown';
149+
let version = 'unknown';
150+
let _name = '';
151+
for (const char of nextHopProtocol) {
152+
// http/1.1 etc.
153+
if (char === '/') {
154+
[name, version] = nextHopProtocol.split('/') as [string, string];
155+
break;
156+
}
157+
// h2, h3 etc.
158+
if (!isNaN(Number(char))) {
159+
name = _name === 'h' ? 'http' : _name;
160+
version = nextHopProtocol.split(_name)[1] as string;
161+
break;
162+
}
163+
_name += char;
164+
}
165+
if (_name === nextHopProtocol) {
166+
// webrtc, ftp, etc.
167+
name = _name;
168+
}
169+
return { name, version };
170+
}

packages/browser-utils/test/browser/browserMetrics.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ describe('_addResourceSpans', () => {
131131
encodedBodySize: 256,
132132
decodedBodySize: 256,
133133
renderBlockingStatus: 'non-blocking',
134+
nextHopProtocol: 'http/1.1',
134135
});
135136
_addResourceSpans(span, entry, resourceEntryName, 123, 456, 100);
136137

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

@@ -169,6 +171,7 @@ describe('_addResourceSpans', () => {
169171
encodedBodySize: 456,
170172
decodedBodySize: 593,
171173
renderBlockingStatus: 'non-blocking',
174+
nextHopProtocol: 'http/1.1',
172175
});
173176

174177
const timeOrigin = 100;
@@ -195,6 +198,8 @@ describe('_addResourceSpans', () => {
195198
['url.scheme']: 'https',
196199
['server.address']: 'example.com',
197200
['url.same_origin']: true,
201+
['network.protocol.name']: 'http',
202+
['network.protocol.version']: '1.1',
198203
},
199204
}),
200205
);
@@ -233,6 +238,7 @@ describe('_addResourceSpans', () => {
233238
const { initiatorType, op } = table[i]!;
234239
const entry = mockPerformanceResourceTiming({
235240
initiatorType,
241+
nextHopProtocol: 'http/1.1',
236242
});
237243
_addResourceSpans(span, entry, 'https://example.com/assets/to/me', 123, 234, 465);
238244

@@ -254,6 +260,7 @@ describe('_addResourceSpans', () => {
254260
encodedBodySize: 0,
255261
decodedBodySize: 0,
256262
renderBlockingStatus: 'non-blocking',
263+
nextHopProtocol: 'h2',
257264
});
258265

259266
_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);
@@ -271,6 +278,8 @@ describe('_addResourceSpans', () => {
271278
['url.scheme']: 'https',
272279
['server.address']: 'example.com',
273280
['url.same_origin']: true,
281+
['network.protocol.name']: 'http',
282+
['network.protocol.version']: '2',
274283
},
275284
}),
276285
);
@@ -288,6 +297,7 @@ describe('_addResourceSpans', () => {
288297
transferSize: 2147483647,
289298
encodedBodySize: 2147483647,
290299
decodedBodySize: 2147483647,
300+
nextHopProtocol: 'h3',
291301
});
292302

293303
_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);
@@ -301,6 +311,8 @@ describe('_addResourceSpans', () => {
301311
'server.address': 'example.com',
302312
'url.same_origin': true,
303313
'url.scheme': 'https',
314+
['network.protocol.name']: 'http',
315+
['network.protocol.version']: '3',
304316
},
305317
description: '/assets/to/css',
306318
timestamp: 468,
@@ -325,6 +337,7 @@ describe('_addResourceSpans', () => {
325337
transferSize: null,
326338
encodedBodySize: null,
327339
decodedBodySize: null,
340+
nextHopProtocol: 'h3',
328341
} as unknown as PerformanceResourceTiming;
329342

330343
_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);
@@ -338,6 +351,8 @@ describe('_addResourceSpans', () => {
338351
'server.address': 'example.com',
339352
'url.same_origin': true,
340353
'url.scheme': 'https',
354+
['network.protocol.name']: 'http',
355+
['network.protocol.version']: '3',
341356
},
342357
description: '/assets/to/css',
343358
timestamp: 468,
@@ -365,6 +380,7 @@ describe('_addResourceSpans', () => {
365380
encodedBodySize: 0,
366381
decodedBodySize: 0,
367382
deliveryType,
383+
nextHopProtocol: 'h3',
368384
});
369385

370386
_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);

packages/browser-utils/test/browser/utils.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { SentrySpan, getCurrentScope, getIsolationScope, setCurrentClient, spanToJSON } from '@sentry/core';
2-
import { startAndEndSpan } from '../../src/metrics/utils';
2+
import { extractNetworkProtocol, startAndEndSpan } from '../../src/metrics/utils';
33
import { TestClient, getDefaultClientOptions } from '../utils/TestClient';
44

55
describe('startAndEndSpan()', () => {
@@ -54,3 +54,54 @@ describe('startAndEndSpan()', () => {
5454
expect(spanToJSON(parentSpan).start_timestamp).toEqual(123);
5555
});
5656
});
57+
58+
interface ProtocolInfo {
59+
name: string;
60+
version: string;
61+
}
62+
63+
describe('HTTPTimings', () => {
64+
test('Extracting version from ALPN protocol', () => {
65+
const nextHopToNetworkVersion: Record<string, ProtocolInfo> = {
66+
'http/0.9': { name: 'http', version: '0.9' },
67+
'http/1.0': { name: 'http', version: '1.0' },
68+
'http/1.1': { name: 'http', version: '1.1' },
69+
'spdy/1': { name: 'spdy', version: '1' },
70+
'spdy/2': { name: 'spdy', version: '2' },
71+
'spdy/3': { name: 'spdy', version: '3' },
72+
'stun.turn': { name: 'stun.turn', version: 'unknown' },
73+
'stun.nat-discovery': { name: 'stun.nat-discovery', version: 'unknown' },
74+
h2: { name: 'http', version: '2' },
75+
h2c: { name: 'http', version: '2c' },
76+
webrtc: { name: 'webrtc', version: 'unknown' },
77+
'c-webrtc': { name: 'c-webrtc', version: 'unknown' },
78+
ftp: { name: 'ftp', version: 'unknown' },
79+
imap: { name: 'imap', version: 'unknown' },
80+
pop3: { name: 'pop', version: '3' },
81+
managesieve: { name: 'managesieve', version: 'unknown' },
82+
coap: { name: 'coap', version: 'unknown' },
83+
'xmpp-client': { name: 'xmpp-client', version: 'unknown' },
84+
'xmpp-server': { name: 'xmpp-server', version: 'unknown' },
85+
'acme-tls/1': { name: 'acme-tls', version: '1' },
86+
mqtt: { name: 'mqtt', version: 'unknown' },
87+
dot: { name: 'dot', version: 'unknown' },
88+
'ntske/1': { name: 'ntske', version: '1' },
89+
sunrpc: { name: 'sunrpc', version: 'unknown' },
90+
h3: { name: 'http', version: '3' },
91+
smb: { name: 'smb', version: 'unknown' },
92+
irc: { name: 'irc', version: 'unknown' },
93+
nntp: { name: 'nntp', version: 'unknown' },
94+
nnsp: { name: 'nnsp', version: 'unknown' },
95+
doq: { name: 'doq', version: 'unknown' },
96+
'sip/2': { name: 'sip', version: '2' },
97+
'tds/8.0': { name: 'tds', version: '8.0' },
98+
dicom: { name: 'dicom', version: 'unknown' },
99+
};
100+
101+
const protocols = Object.keys(nextHopToNetworkVersion);
102+
for (const protocol of protocols) {
103+
const expected = nextHopToNetworkVersion[protocol]!;
104+
expect(extractNetworkProtocol(protocol)).toMatchObject(expected);
105+
}
106+
});
107+
});

packages/browser/src/tracing/request.ts

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
SENTRY_XHR_DATA_KEY,
33
addPerformanceInstrumentationHandler,
44
addXhrInstrumentationHandler,
5+
extractNetworkProtocol,
56
} from '@sentry-internal/browser-utils';
67
import type { Client, HandlerDataXhr, SentryWrappedXMLHttpRequest, Span } from '@sentry/core';
78
import {
@@ -228,37 +229,6 @@ function addHTTPTimings(span: Span): void {
228229
});
229230
}
230231

231-
/**
232-
* Converts ALPN protocol ids to name and version.
233-
*
234-
* (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids)
235-
* @param nextHopProtocol PerformanceResourceTiming.nextHopProtocol
236-
*/
237-
export function extractNetworkProtocol(nextHopProtocol: string): { name: string; version: string } {
238-
let name = 'unknown';
239-
let version = 'unknown';
240-
let _name = '';
241-
for (const char of nextHopProtocol) {
242-
// http/1.1 etc.
243-
if (char === '/') {
244-
[name, version] = nextHopProtocol.split('/') as [string, string];
245-
break;
246-
}
247-
// h2, h3 etc.
248-
if (!isNaN(Number(char))) {
249-
name = _name === 'h' ? 'http' : _name;
250-
version = nextHopProtocol.split(_name)[1] as string;
251-
break;
252-
}
253-
_name += char;
254-
}
255-
if (_name === nextHopProtocol) {
256-
// webrtc, ftp, etc.
257-
name = _name;
258-
}
259-
return { name, version };
260-
}
261-
262232
function getAbsoluteTime(time: number = 0): number {
263233
return ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000;
264234
}

packages/browser/test/tracing/request.test.ts

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as browserUtils from '@sentry-internal/browser-utils';
55
import * as utils from '@sentry/core';
66
import type { Client } from '@sentry/core';
77

8-
import { extractNetworkProtocol, instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/tracing/request';
8+
import { instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/tracing/request';
99

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

67-
interface ProtocolInfo {
68-
name: string;
69-
version: string;
70-
}
71-
72-
describe('HTTPTimings', () => {
73-
test('Extracting version from ALPN protocol', () => {
74-
const nextHopToNetworkVersion: Record<string, ProtocolInfo> = {
75-
'http/0.9': { name: 'http', version: '0.9' },
76-
'http/1.0': { name: 'http', version: '1.0' },
77-
'http/1.1': { name: 'http', version: '1.1' },
78-
'spdy/1': { name: 'spdy', version: '1' },
79-
'spdy/2': { name: 'spdy', version: '2' },
80-
'spdy/3': { name: 'spdy', version: '3' },
81-
'stun.turn': { name: 'stun.turn', version: 'unknown' },
82-
'stun.nat-discovery': { name: 'stun.nat-discovery', version: 'unknown' },
83-
h2: { name: 'http', version: '2' },
84-
h2c: { name: 'http', version: '2c' },
85-
webrtc: { name: 'webrtc', version: 'unknown' },
86-
'c-webrtc': { name: 'c-webrtc', version: 'unknown' },
87-
ftp: { name: 'ftp', version: 'unknown' },
88-
imap: { name: 'imap', version: 'unknown' },
89-
pop3: { name: 'pop', version: '3' },
90-
managesieve: { name: 'managesieve', version: 'unknown' },
91-
coap: { name: 'coap', version: 'unknown' },
92-
'xmpp-client': { name: 'xmpp-client', version: 'unknown' },
93-
'xmpp-server': { name: 'xmpp-server', version: 'unknown' },
94-
'acme-tls/1': { name: 'acme-tls', version: '1' },
95-
mqtt: { name: 'mqtt', version: 'unknown' },
96-
dot: { name: 'dot', version: 'unknown' },
97-
'ntske/1': { name: 'ntske', version: '1' },
98-
sunrpc: { name: 'sunrpc', version: 'unknown' },
99-
h3: { name: 'http', version: '3' },
100-
smb: { name: 'smb', version: 'unknown' },
101-
irc: { name: 'irc', version: 'unknown' },
102-
nntp: { name: 'nntp', version: 'unknown' },
103-
nnsp: { name: 'nnsp', version: 'unknown' },
104-
doq: { name: 'doq', version: 'unknown' },
105-
'sip/2': { name: 'sip', version: '2' },
106-
'tds/8.0': { name: 'tds', version: '8.0' },
107-
dicom: { name: 'dicom', version: 'unknown' },
108-
};
109-
110-
const protocols = Object.keys(nextHopToNetworkVersion);
111-
for (const protocol of protocols) {
112-
const expected = nextHopToNetworkVersion[protocol]!;
113-
expect(extractNetworkProtocol(protocol)).toMatchObject(expected);
114-
}
115-
});
116-
});
117-
11867
describe('shouldAttachHeaders', () => {
11968
describe('should prefer `tracePropagationTargets` over defaults', () => {
12069
it('should return `true` if the url matches the new tracePropagationTargets', () => {

0 commit comments

Comments
 (0)