This repository has been archived by the owner on Oct 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Performance timing recording and span conversion (#12)
- Loading branch information
1 parent
93d6a7a
commit 89561ae
Showing
12 changed files
with
1,223 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
packages/opencensus-web-instrumentation-perf/src/initial-load-root-span.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/** | ||
* Copyright 2019, OpenCensus Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
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'; | ||
import {GroupedPerfEntries} from './perf-recorder'; | ||
import {PerformanceLongTaskTiming, PerformanceNavigationTimingExtended} from './perf-types'; | ||
import {getResourceSpan} from './resource-span'; | ||
import {annotationsForPerfTimeFields} from './util'; | ||
|
||
/** | ||
* These are properties of PerformanceNavigationTiming that will be turned | ||
* into span annotations on the navigation span. | ||
*/ | ||
const NAVIGATION_TIMING_EVENTS = [ | ||
'domLoading', | ||
'domInteractive', | ||
'domContentLoaded', | ||
'domComplete', | ||
'loadEventStart', | ||
'loadEventEnd', | ||
'unloadEventStart', | ||
'unloadEventEnd', | ||
]; | ||
|
||
/** | ||
* Returns a root span for the initial page load along with child spans for | ||
* resource timings and long tasks that were part of the load. The root span | ||
* represents the full time between when the navigation was initiated in the | ||
* browser to when the `load` event fires. | ||
* @param tracer The tracer to associate the root span with | ||
* @param perfEntries Performance timing entries grouped by type. These should | ||
* include the entries up to soon after the `load` browser event fires. | ||
* @param navigationFetchSpanId This is the ID for the span that represents the | ||
* HTTP request to get the initial HTML. This should be sent back from the | ||
* server to enable linking the server and client side spans for the initial | ||
* HTML fetch. | ||
* @param traceId The trace ID that all spans returned should have. This should | ||
* also be specified by the server to enable linking between the server and | ||
* client spans for the initial HTML fetch. | ||
*/ | ||
export function getInitialLoadRootSpan( | ||
tracer: Tracer, perfEntries: GroupedPerfEntries, | ||
navigationFetchSpanId: string, traceId: string): RootSpan { | ||
const navTiming = perfEntries.navigationTiming; | ||
const navigationUrl = navTiming ? navTiming.name : location.href; | ||
const parsedNavigationUrl = parseUrl(navigationUrl); | ||
const navigationPath = parsedNavigationUrl.pathname; | ||
const root = new RootSpan(tracer, { | ||
name: `Nav.${navigationPath}`, | ||
spanContext: { | ||
traceId, | ||
// This becomes the parentSpanId field of the root span, and the actual | ||
// span ID for the root span gets assigned to a random number. | ||
spanId: '', | ||
}, | ||
kind: SpanKind.UNSPECIFIED, | ||
}); | ||
root.startPerfTime = 0; | ||
root.annotations = getNavigationAnnotations(perfEntries); | ||
root.attributes[ATTRIBUTE_HTTP_URL] = navigationUrl; | ||
root.attributes[ATTRIBUTE_HTTP_USER_AGENT] = navigator.userAgent; | ||
|
||
if (navTiming) { | ||
root.endPerfTime = navTiming.loadEventEnd; | ||
root.attributes[ATTRIBUTE_NAV_TYPE] = navTiming.type; | ||
const navFetchSpan = getNavigationFetchSpan( | ||
navTiming, navigationUrl, traceId, root.id, navigationFetchSpanId); | ||
root.spans.push(navFetchSpan); | ||
} | ||
|
||
const resourceSpans = perfEntries.resourceTimings.map( | ||
(resourceTiming) => getResourceSpan(resourceTiming, traceId, root.id)); | ||
const longTaskSpans = perfEntries.longTasks.map( | ||
(longTaskTiming) => getLongTaskSpan(longTaskTiming, traceId, root.id)); | ||
|
||
root.spans = root.spans.concat(resourceSpans, longTaskSpans); | ||
return root; | ||
} | ||
|
||
/** Returns a parent span for the HTTP request to retrieve the initial HTML. */ | ||
function getNavigationFetchSpan( | ||
navigationTiming: PerformanceNavigationTimingExtended, | ||
navigationName: string, traceId: string, parentSpanId: string, | ||
spanId: string): Span { | ||
const span = getResourceSpan(navigationTiming, traceId, parentSpanId, spanId); | ||
span.startPerfTime = navigationTiming.fetchStart; | ||
return span; | ||
} | ||
|
||
/** Formats a performance long task event as a span. */ | ||
function getLongTaskSpan( | ||
longTask: PerformanceLongTaskTiming, traceId: string, | ||
parentSpanId: string): Span { | ||
const span = new Span(); | ||
span.traceId = traceId; | ||
span.parentSpanId = parentSpanId; | ||
span.name = 'Long JS task'; | ||
span.startPerfTime = longTask.startTime; | ||
span.endPerfTime = longTask.startTime + longTask.duration; | ||
span.attributes[ATTRIBUTE_LONG_TASK_ATTRIBUTION] = | ||
JSON.stringify(longTask.attribution); | ||
return span; | ||
} | ||
|
||
/** Gets annotations for a navigation span including paint timings. */ | ||
function getNavigationAnnotations(perfEntries: GroupedPerfEntries): | ||
Annotation[] { | ||
const navigation = perfEntries.navigationTiming; | ||
if (!navigation) return []; | ||
|
||
const navAnnotations = | ||
annotationsForPerfTimeFields(navigation, NAVIGATION_TIMING_EVENTS); | ||
|
||
for (const paintTiming of perfEntries.paintTimings) { | ||
navAnnotations.push({ | ||
timestamp: paintTiming.startTime, | ||
description: paintTiming.name, | ||
attributes: {}, | ||
}); | ||
} | ||
return navAnnotations; | ||
} |
98 changes: 98 additions & 0 deletions
98
packages/opencensus-web-instrumentation-perf/src/perf-recorder.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/** | ||
* Copyright 2019, OpenCensus Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import {PerformanceLongTaskTiming, PerformanceNavigationTimingExtended, PerformanceObserverEntryList, PerformancePaintTiming, PerformanceResourceTimingExtended, WindowWithPerformanceObserver} from './perf-types'; | ||
|
||
/** Cast `window` to have PerformanceObserver. */ | ||
const windowWithPerfObserver = window as WindowWithPerformanceObserver; | ||
|
||
/** Store long task performance entries recorded with PerformanceObserver. */ | ||
const longTasks: PerformanceLongTaskTiming[] = []; | ||
|
||
/** How big to set the performance timing buffer so timings aren't lost. */ | ||
const RESOURCE_TIMING_BUFFER_SIZE = 2000; | ||
|
||
/** Represent all collected performance timings grouped by type. */ | ||
export interface GroupedPerfEntries { | ||
navigationTiming?: PerformanceNavigationTimingExtended; | ||
paintTimings: PerformancePaintTiming[]; | ||
resourceTimings: PerformanceResourceTimingExtended[]; | ||
longTasks: PerformanceLongTaskTiming[]; | ||
} | ||
|
||
/** | ||
* Begin recording performance timings. This starts tracking `longtask` timings | ||
* and also increases the resource timing buffer size. | ||
*/ | ||
export function recordPerfEntries() { | ||
if (!windowWithPerfObserver.performance) return; | ||
|
||
if (performance.setResourceTimingBufferSize) { | ||
performance.setResourceTimingBufferSize(RESOURCE_TIMING_BUFFER_SIZE); | ||
} | ||
|
||
if (windowWithPerfObserver.PerformanceObserver) { | ||
const longTaskObserver = | ||
new windowWithPerfObserver.PerformanceObserver(onLongTasks); | ||
longTaskObserver.observe({entryTypes: ['longtask']}); | ||
} | ||
} | ||
|
||
function onLongTasks(entryList: PerformanceObserverEntryList) { | ||
// These must be PerformanceLongTaskTiming objects because we only observe | ||
// 'longtask' above. | ||
longTasks.push(...(entryList.getEntries() as PerformanceLongTaskTiming[])); | ||
} | ||
|
||
/** Returns the recorded performance entries but does not clear them. */ | ||
export function getPerfEntries(): GroupedPerfEntries { | ||
if (!windowWithPerfObserver.performance) { | ||
return { | ||
resourceTimings: [], | ||
longTasks: [], | ||
paintTimings: [], | ||
}; | ||
} | ||
|
||
const perf = windowWithPerfObserver.performance; | ||
|
||
const entries: GroupedPerfEntries = { | ||
resourceTimings: perf.getEntriesByType('resource') as | ||
PerformanceResourceTimingExtended[], | ||
paintTimings: perf.getEntriesByType('paint') as PerformancePaintTiming[], | ||
longTasks: longTasks.slice(), | ||
}; | ||
|
||
const navEntries = perf.getEntriesByType('navigation'); | ||
if (navEntries.length) { | ||
entries.navigationTiming = | ||
navEntries[0] as PerformanceNavigationTimingExtended; | ||
} | ||
|
||
return entries; | ||
} | ||
|
||
/** Clears resource timings, marks, measures and stored long task timings. */ | ||
export function clearPerfEntries() { | ||
if (!windowWithPerfObserver.performance) return; | ||
longTasks.length = 0; | ||
windowWithPerfObserver.performance.clearResourceTimings(); | ||
windowWithPerfObserver.performance.clearMarks(); | ||
windowWithPerfObserver.performance.clearMeasures(); | ||
} | ||
|
||
/** Expose the resource timing buffer size for unit test. */ | ||
export const TEST_ONLY = {RESOURCE_TIMING_BUFFER_SIZE}; |
Oops, something went wrong.