Skip to content

Commit

Permalink
feat(instrumentation-document-load): performance paint timing events (o…
Browse files Browse the repository at this point in the history
…pen-telemetry#484)

* feat(instrumentation-document-load): performance paint timing events

* chore: fixed lint
  • Loading branch information
kkruk-sumo authored Jun 8, 2021
1 parent 2a30f62 commit 4a0dab7
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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.
*/

export enum EventNames {
FIRST_PAINT = 'firstPaint',
FIRST_CONTENTFUL_PAINT = 'firstContentfulPaint',
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
addSpanNetworkEvents,
hasKey,
PerformanceEntries,
PerformanceLegacy,
PerformanceTimingNames as PTN,
} from '@opentelemetry/web';
import {
Expand All @@ -37,6 +36,10 @@ import {
import { AttributeNames } from './enums/AttributeNames';
import { VERSION } from './version';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import {
addSpanPerformancePaintEvents,
getPerformanceNavigationEntries,
} from './utils';

/**
* This class represents a document load plugin
Expand Down Expand Up @@ -90,7 +93,7 @@ export class DocumentLoadInstrumentation extends InstrumentationBase<unknown> {
const metaElement = [...document.getElementsByTagName('meta')].find(
e => e.getAttribute('name') === TRACE_PARENT_HEADER
);
const entries = this._getEntries();
const entries = getPerformanceNavigationEntries();
const traceparent = (metaElement && metaElement.content) || '';
context.with(propagation.extract(ROOT_CONTEXT, { traceparent }), () => {
const rootSpan = this._startSpan(
Expand Down Expand Up @@ -137,6 +140,8 @@ export class DocumentLoadInstrumentation extends InstrumentationBase<unknown> {
addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_START, entries);
addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_END, entries);

addSpanPerformancePaintEvents(rootSpan);

this._endSpan(rootSpan, PTN.LOAD_EVENT_END, entries);
});
}
Expand All @@ -163,44 +168,6 @@ export class DocumentLoadInstrumentation extends InstrumentationBase<unknown> {
}
}

/**
* gets performance entries of navigation
*/
private _getEntries() {
const entries: PerformanceEntries = {};
const performanceNavigationTiming = (
otperformance as unknown as Performance
).getEntriesByType?.('navigation')[0] as PerformanceEntries;

if (performanceNavigationTiming) {
const keys = Object.values(PTN);
keys.forEach((key: string) => {
if (hasKey(performanceNavigationTiming, key)) {
const value = performanceNavigationTiming[key];
if (typeof value === 'number') {
entries[key] = value;
}
}
});
} else {
// // fallback to previous version
const perf: typeof otperformance & PerformanceLegacy = otperformance;
const performanceTiming = perf.timing;
if (performanceTiming) {
const keys = Object.values(PTN);
keys.forEach((key: string) => {
if (hasKey(performanceTiming, key)) {
const value = performanceTiming[key];
if (typeof value === 'number') {
entries[key] = value;
}
}
});
}
}
return entries;
}

/**
* Creates and ends a span with network information about resource added as timed events
* @param resource
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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 { Span } from '@opentelemetry/api';
import { otperformance } from '@opentelemetry/core';
import {
hasKey,
PerformanceEntries,
PerformanceLegacy,
PerformanceTimingNames as PTN,
} from '@opentelemetry/web';
import { EventNames } from './enums/EventNames';

export const getPerformanceNavigationEntries = (): PerformanceEntries => {
const entries: PerformanceEntries = {};
const performanceNavigationTiming = (
otperformance as unknown as Performance
).getEntriesByType?.('navigation')[0] as PerformanceEntries;

if (performanceNavigationTiming) {
const keys = Object.values(PTN);
keys.forEach((key: string) => {
if (hasKey(performanceNavigationTiming, key)) {
const value = performanceNavigationTiming[key];
if (typeof value === 'number') {
entries[key] = value;
}
}
});
} else {
// // fallback to previous version
const perf: typeof otperformance & PerformanceLegacy = otperformance;
const performanceTiming = perf.timing;
if (performanceTiming) {
const keys = Object.values(PTN);
keys.forEach((key: string) => {
if (hasKey(performanceTiming, key)) {
const value = performanceTiming[key];
if (typeof value === 'number') {
entries[key] = value;
}
}
});
}
}

return entries;
};

const performancePaintNames = {
'first-paint': EventNames.FIRST_PAINT,
'first-contentful-paint': EventNames.FIRST_CONTENTFUL_PAINT,
};

export const addSpanPerformancePaintEvents = (span: Span) => {
const performancePaintTiming = (
otperformance as unknown as Performance
).getEntriesByType?.('paint');
if (performancePaintTiming) {
performancePaintTiming.forEach(({ name, startTime }) => {
if (hasKey(performancePaintNames, name)) {
span.addEvent(performancePaintNames[name], startTime);
}
});
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import * as assert from 'assert';
import * as sinon from 'sinon';
import { DocumentLoadInstrumentation } from '../src';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import { EventNames } from '../src/enums/EventNames';

const exporter = new InMemorySpanExporter();
const provider = new BasicTracerProvider();
Expand Down Expand Up @@ -182,6 +183,25 @@ const entriesFallback = {
loadEventEnd: 1571078170394,
} as any;

const paintEntries: PerformanceEntryList = [
{
duration: 0,
entryType: 'paint',
name: 'first-paint',
startTime: 7.480000003241003,
toJSON() {},
},
{
duration: 0,
entryType: 'paint',
name: 'first-contentful-paint',
startTime: 8.480000003241003,
toJSON() {},
},
];

performance.getEntriesByType;

const userAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36';

Expand Down Expand Up @@ -246,6 +266,7 @@ describe('DocumentLoad Instrumentation', () => {
spyEntries = sandbox.stub(window.performance, 'getEntriesByType');
spyEntries.withArgs('navigation').returns([entries]);
spyEntries.withArgs('resource').returns([]);
spyEntries.withArgs('paint').returns([]);
});
afterEach(() => {
spyEntries.restore();
Expand All @@ -254,7 +275,7 @@ describe('DocumentLoad Instrumentation', () => {
plugin.enable();
setTimeout(() => {
assert.strictEqual(window.document.readyState, 'complete');
assert.strictEqual(spyEntries.callCount, 2);
assert.strictEqual(spyEntries.callCount, 3);
done();
});
});
Expand All @@ -270,6 +291,7 @@ describe('DocumentLoad Instrumentation', () => {
spyEntries = sandbox.stub(window.performance, 'getEntriesByType');
spyEntries.withArgs('navigation').returns([entries]);
spyEntries.withArgs('resource').returns([]);
spyEntries.withArgs('paint').returns([]);
});
afterEach(() => {
spyEntries.restore();
Expand All @@ -293,7 +315,7 @@ describe('DocumentLoad Instrumentation', () => {
})
);
setTimeout(() => {
assert.strictEqual(spyEntries.callCount, 2);
assert.strictEqual(spyEntries.callCount, 3);
done();
});
});
Expand All @@ -305,6 +327,7 @@ describe('DocumentLoad Instrumentation', () => {
spyEntries = sandbox.stub(window.performance, 'getEntriesByType');
spyEntries.withArgs('navigation').returns([entries]);
spyEntries.withArgs('resource').returns([]);
spyEntries.withArgs('paint').returns(paintEntries);
});
afterEach(() => {
spyEntries.restore();
Expand All @@ -328,6 +351,12 @@ describe('DocumentLoad Instrumentation', () => {
assert.strictEqual(fetchSpan.name, 'documentLoad');
ensureNetworkEventsExists(rsEvents);

assert.strictEqual(fsEvents[9].name, EventNames.FIRST_PAINT);
assert.strictEqual(
fsEvents[10].name,
EventNames.FIRST_CONTENTFUL_PAINT
);

assert.strictEqual(fsEvents[0].name, PTN.FETCH_START);
assert.strictEqual(fsEvents[1].name, PTN.UNLOAD_EVENT_START);
assert.strictEqual(fsEvents[2].name, PTN.UNLOAD_EVENT_END);
Expand All @@ -342,7 +371,7 @@ describe('DocumentLoad Instrumentation', () => {
assert.strictEqual(fsEvents[8].name, PTN.LOAD_EVENT_END);

assert.strictEqual(rsEvents.length, 9);
assert.strictEqual(fsEvents.length, 9);
assert.strictEqual(fsEvents.length, 11);
assert.strictEqual(exporter.getFinishedSpans().length, 2);
done();
});
Expand Down Expand Up @@ -401,6 +430,7 @@ describe('DocumentLoad Instrumentation', () => {
spyEntries = sandbox.stub(window.performance, 'getEntriesByType');
spyEntries.withArgs('navigation').returns([entries]);
spyEntries.withArgs('resource').returns(resources);
spyEntries.withArgs('paint').returns([]);
});
afterEach(() => {
spyEntries.restore();
Expand Down Expand Up @@ -438,6 +468,7 @@ describe('DocumentLoad Instrumentation', () => {
spyEntries = sandbox.stub(window.performance, 'getEntriesByType');
spyEntries.withArgs('navigation').returns([entries]);
spyEntries.withArgs('resource').returns(resourcesNoSecureConnectionStart);
spyEntries.withArgs('paint').returns([]);
});
afterEach(() => {
spyEntries.restore();
Expand Down Expand Up @@ -479,6 +510,7 @@ describe('DocumentLoad Instrumentation', () => {
spyEntries = sandbox.stub(window.performance, 'getEntriesByType');
spyEntries.withArgs('navigation').returns([entriesWithoutLoadEventEnd]);
spyEntries.withArgs('resource').returns([]);
spyEntries.withArgs('paint').returns([]);
});
afterEach(() => {
spyEntries.restore();
Expand Down Expand Up @@ -603,6 +635,8 @@ describe('DocumentLoad Instrumentation', () => {
.withArgs('navigation')
.returns([navEntriesWithNegativeFetch])
.withArgs('resource')
.returns([])
.withArgs('paint')
.returns([]);

sandbox.stub(window.performance, 'timing').get(() => {
Expand Down

0 comments on commit 4a0dab7

Please sign in to comment.