Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 89561ae

Browse files
Performance timing recording and span conversion (#12)
1 parent 93d6a7a commit 89561ae

12 files changed

Lines changed: 1223 additions & 7 deletions

File tree

packages/opencensus-web-core/src/trace/model/attribute-keys.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ export const ATTRIBUTE_HTTP_RESP_ENCODED_BODY_SIZE =
6868
export const ATTRIBUTE_HTTP_RESP_DECODED_BODY_SIZE =
6969
`${HTTP_PREFIX}resp_decoded_body_size`;
7070

71+
/** Attribute prefix for spans that represent navigations in the browser. */
7172
const NAVIGATION_PREFIX = 'nav.';
72-
7373
/**
7474
* The type of browser navigation. See
7575
* https://www.w3.org/TR/navigation-timing-2/#sec-performance-navigation-types
@@ -81,3 +81,15 @@ export const ATTRIBUTE_NAV_TYPE = `${NAVIGATION_PREFIX}type`;
8181
*/
8282
export const ATTRIBUTE_NAV_REDIRECT_COUNT =
8383
`${NAVIGATION_PREFIX}redirect_count`;
84+
85+
/**
86+
* Attribute prefix for spans that are for "long tasks" (long JS event loops).
87+
* See https://www.w3.org/TR/longtasks/
88+
*/
89+
export const LONG_TASK_PREFIX = 'long_task.';
90+
/**
91+
* A JSON string of the `attribution` field of a long task timing. This gives
92+
* a little additional information about what on the page may have caused the
93+
* long task.
94+
*/
95+
export const ATTRIBUTE_LONG_TASK_ATTRIBUTION = `${LONG_TASK_PREFIX}attribution`;

packages/opencensus-web-core/src/trace/model/span.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@
1515
*/
1616

1717
import * as coreTypes from '@opencensus/core';
18-
1918
import {LOGGER} from '../../common/console-logger';
2019
import {getDateForPerfTime} from '../../common/time-util';
2120
import {randomSpanId} from '../../internal/util';
22-
2321
import {MessageEvent, SpanKind} from './types';
2422

2523
/** Default span name if none is specified. */
2624
const DEFAULT_SPAN_NAME = 'unnamed';
2725

2826
/** A span represents a single operation within a trace. */
2927
export class Span implements coreTypes.Span {
30-
id = randomSpanId();
28+
constructor(
29+
/** The ID of this span. Defaults to a random span ID. */
30+
public id = randomSpanId()) {}
3131

3232
/** If the parent span is in another process. */
3333
remoteParent = false;

packages/opencensus-web-core/test/test-span.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ describe('Span', () => {
2828
expect(span.id).toMatch('^[a-z0-9]{16}$');
2929
});
3030

31+
it('allows initializing id in constructor', () => {
32+
const span = new Span('000000000000000b');
33+
expect(span.id).toBe('000000000000000b');
34+
});
35+
3136
it('calculates time fields based on startPerfTime/endPerfTime', () => {
3237
expect(span.ended).toBe(false);
3338

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/**
2+
* Copyright 2019, OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {Annotation, ATTRIBUTE_HTTP_URL, ATTRIBUTE_HTTP_USER_AGENT, ATTRIBUTE_LONG_TASK_ATTRIBUTION, ATTRIBUTE_NAV_TYPE, parseUrl, RootSpan, Span, SpanKind, Tracer} from '@opencensus/web-core';
18+
import {GroupedPerfEntries} from './perf-recorder';
19+
import {PerformanceLongTaskTiming, PerformanceNavigationTimingExtended} from './perf-types';
20+
import {getResourceSpan} from './resource-span';
21+
import {annotationsForPerfTimeFields} from './util';
22+
23+
/**
24+
* These are properties of PerformanceNavigationTiming that will be turned
25+
* into span annotations on the navigation span.
26+
*/
27+
const NAVIGATION_TIMING_EVENTS = [
28+
'domLoading',
29+
'domInteractive',
30+
'domContentLoaded',
31+
'domComplete',
32+
'loadEventStart',
33+
'loadEventEnd',
34+
'unloadEventStart',
35+
'unloadEventEnd',
36+
];
37+
38+
/**
39+
* Returns a root span for the initial page load along with child spans for
40+
* resource timings and long tasks that were part of the load. The root span
41+
* represents the full time between when the navigation was initiated in the
42+
* browser to when the `load` event fires.
43+
* @param tracer The tracer to associate the root span with
44+
* @param perfEntries Performance timing entries grouped by type. These should
45+
* include the entries up to soon after the `load` browser event fires.
46+
* @param navigationFetchSpanId This is the ID for the span that represents the
47+
* HTTP request to get the initial HTML. This should be sent back from the
48+
* server to enable linking the server and client side spans for the initial
49+
* HTML fetch.
50+
* @param traceId The trace ID that all spans returned should have. This should
51+
* also be specified by the server to enable linking between the server and
52+
* client spans for the initial HTML fetch.
53+
*/
54+
export function getInitialLoadRootSpan(
55+
tracer: Tracer, perfEntries: GroupedPerfEntries,
56+
navigationFetchSpanId: string, traceId: string): RootSpan {
57+
const navTiming = perfEntries.navigationTiming;
58+
const navigationUrl = navTiming ? navTiming.name : location.href;
59+
const parsedNavigationUrl = parseUrl(navigationUrl);
60+
const navigationPath = parsedNavigationUrl.pathname;
61+
const root = new RootSpan(tracer, {
62+
name: `Nav.${navigationPath}`,
63+
spanContext: {
64+
traceId,
65+
// This becomes the parentSpanId field of the root span, and the actual
66+
// span ID for the root span gets assigned to a random number.
67+
spanId: '',
68+
},
69+
kind: SpanKind.UNSPECIFIED,
70+
});
71+
root.startPerfTime = 0;
72+
root.annotations = getNavigationAnnotations(perfEntries);
73+
root.attributes[ATTRIBUTE_HTTP_URL] = navigationUrl;
74+
root.attributes[ATTRIBUTE_HTTP_USER_AGENT] = navigator.userAgent;
75+
76+
if (navTiming) {
77+
root.endPerfTime = navTiming.loadEventEnd;
78+
root.attributes[ATTRIBUTE_NAV_TYPE] = navTiming.type;
79+
const navFetchSpan = getNavigationFetchSpan(
80+
navTiming, navigationUrl, traceId, root.id, navigationFetchSpanId);
81+
root.spans.push(navFetchSpan);
82+
}
83+
84+
const resourceSpans = perfEntries.resourceTimings.map(
85+
(resourceTiming) => getResourceSpan(resourceTiming, traceId, root.id));
86+
const longTaskSpans = perfEntries.longTasks.map(
87+
(longTaskTiming) => getLongTaskSpan(longTaskTiming, traceId, root.id));
88+
89+
root.spans = root.spans.concat(resourceSpans, longTaskSpans);
90+
return root;
91+
}
92+
93+
/** Returns a parent span for the HTTP request to retrieve the initial HTML. */
94+
function getNavigationFetchSpan(
95+
navigationTiming: PerformanceNavigationTimingExtended,
96+
navigationName: string, traceId: string, parentSpanId: string,
97+
spanId: string): Span {
98+
const span = getResourceSpan(navigationTiming, traceId, parentSpanId, spanId);
99+
span.startPerfTime = navigationTiming.fetchStart;
100+
return span;
101+
}
102+
103+
/** Formats a performance long task event as a span. */
104+
function getLongTaskSpan(
105+
longTask: PerformanceLongTaskTiming, traceId: string,
106+
parentSpanId: string): Span {
107+
const span = new Span();
108+
span.traceId = traceId;
109+
span.parentSpanId = parentSpanId;
110+
span.name = 'Long JS task';
111+
span.startPerfTime = longTask.startTime;
112+
span.endPerfTime = longTask.startTime + longTask.duration;
113+
span.attributes[ATTRIBUTE_LONG_TASK_ATTRIBUTION] =
114+
JSON.stringify(longTask.attribution);
115+
return span;
116+
}
117+
118+
/** Gets annotations for a navigation span including paint timings. */
119+
function getNavigationAnnotations(perfEntries: GroupedPerfEntries):
120+
Annotation[] {
121+
const navigation = perfEntries.navigationTiming;
122+
if (!navigation) return [];
123+
124+
const navAnnotations =
125+
annotationsForPerfTimeFields(navigation, NAVIGATION_TIMING_EVENTS);
126+
127+
for (const paintTiming of perfEntries.paintTimings) {
128+
navAnnotations.push({
129+
timestamp: paintTiming.startTime,
130+
description: paintTiming.name,
131+
attributes: {},
132+
});
133+
}
134+
return navAnnotations;
135+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Copyright 2019, OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {PerformanceLongTaskTiming, PerformanceNavigationTimingExtended, PerformanceObserverEntryList, PerformancePaintTiming, PerformanceResourceTimingExtended, WindowWithPerformanceObserver} from './perf-types';
18+
19+
/** Cast `window` to have PerformanceObserver. */
20+
const windowWithPerfObserver = window as WindowWithPerformanceObserver;
21+
22+
/** Store long task performance entries recorded with PerformanceObserver. */
23+
const longTasks: PerformanceLongTaskTiming[] = [];
24+
25+
/** How big to set the performance timing buffer so timings aren't lost. */
26+
const RESOURCE_TIMING_BUFFER_SIZE = 2000;
27+
28+
/** Represent all collected performance timings grouped by type. */
29+
export interface GroupedPerfEntries {
30+
navigationTiming?: PerformanceNavigationTimingExtended;
31+
paintTimings: PerformancePaintTiming[];
32+
resourceTimings: PerformanceResourceTimingExtended[];
33+
longTasks: PerformanceLongTaskTiming[];
34+
}
35+
36+
/**
37+
* Begin recording performance timings. This starts tracking `longtask` timings
38+
* and also increases the resource timing buffer size.
39+
*/
40+
export function recordPerfEntries() {
41+
if (!windowWithPerfObserver.performance) return;
42+
43+
if (performance.setResourceTimingBufferSize) {
44+
performance.setResourceTimingBufferSize(RESOURCE_TIMING_BUFFER_SIZE);
45+
}
46+
47+
if (windowWithPerfObserver.PerformanceObserver) {
48+
const longTaskObserver =
49+
new windowWithPerfObserver.PerformanceObserver(onLongTasks);
50+
longTaskObserver.observe({entryTypes: ['longtask']});
51+
}
52+
}
53+
54+
function onLongTasks(entryList: PerformanceObserverEntryList) {
55+
// These must be PerformanceLongTaskTiming objects because we only observe
56+
// 'longtask' above.
57+
longTasks.push(...(entryList.getEntries() as PerformanceLongTaskTiming[]));
58+
}
59+
60+
/** Returns the recorded performance entries but does not clear them. */
61+
export function getPerfEntries(): GroupedPerfEntries {
62+
if (!windowWithPerfObserver.performance) {
63+
return {
64+
resourceTimings: [],
65+
longTasks: [],
66+
paintTimings: [],
67+
};
68+
}
69+
70+
const perf = windowWithPerfObserver.performance;
71+
72+
const entries: GroupedPerfEntries = {
73+
resourceTimings: perf.getEntriesByType('resource') as
74+
PerformanceResourceTimingExtended[],
75+
paintTimings: perf.getEntriesByType('paint') as PerformancePaintTiming[],
76+
longTasks: longTasks.slice(),
77+
};
78+
79+
const navEntries = perf.getEntriesByType('navigation');
80+
if (navEntries.length) {
81+
entries.navigationTiming =
82+
navEntries[0] as PerformanceNavigationTimingExtended;
83+
}
84+
85+
return entries;
86+
}
87+
88+
/** Clears resource timings, marks, measures and stored long task timings. */
89+
export function clearPerfEntries() {
90+
if (!windowWithPerfObserver.performance) return;
91+
longTasks.length = 0;
92+
windowWithPerfObserver.performance.clearResourceTimings();
93+
windowWithPerfObserver.performance.clearMarks();
94+
windowWithPerfObserver.performance.clearMeasures();
95+
}
96+
97+
/** Expose the resource timing buffer size for unit test. */
98+
export const TEST_ONLY = {RESOURCE_TIMING_BUFFER_SIZE};

0 commit comments

Comments
 (0)