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

Commit

Permalink
Remove clock sync and initial load trace ID assignment code (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
draffensperger committed Mar 26, 2019
1 parent 27cb768 commit f6c8afa
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 213 deletions.
30 changes: 10 additions & 20 deletions packages/opencensus-web-all/src/export-initial-load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
* limitations under the License.
*/

import {adjustPerfTimeOrigin, tracing} from '@opencensus/web-core';
import {tracing} from '@opencensus/web-core';
import {OCAgentExporter} from '@opencensus/web-exporter-ocagent';
import {clearPerfEntries, getInitialLoadRootSpan, getPerfEntries} from '@opencensus/web-instrumentation-perf';

import {OpenCensusWebConfig, WindowWithOcwGlobals} from './types';
import {WindowWithOcwGlobals} from './types';

const windowWithOcwGlobals = window as WindowWithOcwGlobals;

Expand All @@ -34,42 +34,32 @@ const TRACE_ENDPOINT = '/v1/trace';

/**
* Waits until after the document `load` event fires, and then uses the
* `window.ocwConfig` settings to configure an OpenCensus agent exporter and
* `window.ocwAgent` setting to configure an OpenCensus agent exporter and
* export the spans for the initial page load.
*/
export function exportRootSpanAfterLoadEvent() {
const config = windowWithOcwGlobals.ocwConfig;
if (!config || !config.agent || !config.sampled) {
if (!windowWithOcwGlobals.ocwAgent) {
console.log('Not configured to export page load spans.');
return;
}

tracing.registerExporter(
new OCAgentExporter({agentEndpoint: `${config.agent}${TRACE_ENDPOINT}`}));
tracing.registerExporter(new OCAgentExporter(
{agentEndpoint: `${windowWithOcwGlobals.ocwAgent}${TRACE_ENDPOINT}`}));

if (document.readyState === 'complete') {
exportInitialLoadSpans(config);
exportInitialLoadSpans();
} else {
window.addEventListener('load', () => {
exportInitialLoadSpans(config);
exportInitialLoadSpans();
});
}
}

function exportInitialLoadSpans(config: OpenCensusWebConfig) {
function exportInitialLoadSpans() {
setTimeout(() => {
const perfEntries = getPerfEntries();

// Adjust the performance time origin with server time if we have it.
if (perfEntries.navigationTiming && config.reqStartTime &&
config.reqDuration) {
adjustPerfTimeOrigin(
config.reqStartTime, config.reqDuration,
perfEntries.navigationTiming);
}

const root = getInitialLoadRootSpan(
tracing.tracer, perfEntries, config.spanId, config.traceId);
const root = getInitialLoadRootSpan(tracing.tracer, perfEntries);

clearPerfEntries();
// Notify that the span has ended to trigger export.
Expand Down
54 changes: 24 additions & 30 deletions packages/opencensus-web-all/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,35 @@

import {PerformanceLongTaskTiming, WindowWithLongTasks} from '@opencensus/web-instrumentation-perf';

/** OpenCensus Web configuration that can be set on `window`. */
export declare interface OpenCensusWebConfig {
/**
* Whether the initial load root span should be sampled for trace. This is
* only a hint and should not be used to enforce sampling since clients could
* simply ignore this field.
*/
sampled?: boolean;
/** Trace ID for the initial load spans. */
traceId?: string;
/** Span ID for the initial load fetch client span. */
spanId?: string;
/** Type for `window` object with variables OpenCensus Web interacts with. */
export declare interface WindowWithOcwGlobals extends WindowWithLongTasks {
/**
* Start time of server fetch request for initial navigation HTML in server
* time epoch milliseconds. This is used to correct for clock skew between
* client and server before exporting spans.
* HTTP root URL of the agent endpoint to write traces to.
* Example 'https://my-oc-agent-deployment.com:55678'
*/
reqStartTime?: number;
ocwAgent?: string;
/**
* Duration of the server fetch request for initial HTML in server
* milliseconds. This is also used to correct for clock skew.
* For the initial page load, web browsers do not send any custom headers,
* which means that the server will not receive trace context headers.
* However, we still want the server side request for the initial page load to
* be recorded as a child span of the client side web timing span. So we can
* have servers programmatically set a `traceparent` header to give their
* request span a parent span ID. That simulated trace context header can then
* be sent back to the client as a global variable to use for setting its
* trace ID and request load client span ID, as well as for making a sampling
* decision.
* This header value is in the format of
* [version]-[trace ID in hex]-[span ID in hex]-[trace flags]
* For example:
* 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
* See https://www.w3.org/TR/trace-context/ for details.
*/
reqDuration?: number;
traceparent?: string;
/**
* HTTP root URL of the agent endpoint to write traces to.
* Example 'https://my-oc-agent-deployment.com:55678'
* List to collect long task timings as they are observed. This is on the
* window so that the code to instrument the long tasks and the code that
* exports it can be in different JS bundles. This enables deferring loading
* the export code until it is needed.
*/
agent?: string;
}

/**
* Type for the `window` object with the variables OpenCensus Web interacts
* with.
*/
export declare interface WindowWithOcwGlobals extends WindowWithLongTasks {
ocwLt?: PerformanceLongTaskTiming[];
ocwConfig?: OpenCensusWebConfig;
}
22 changes: 7 additions & 15 deletions packages/opencensus-web-all/test/test-export-initial-load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,35 @@
*/

import {exportRootSpanAfterLoadEvent} from '../src/export-initial-load';
import {OpenCensusWebConfig, WindowWithOcwGlobals} from '../src/types';
import {WindowWithOcwGlobals} from '../src/types';

const windowWithOcwGlobals = window as WindowWithOcwGlobals;

describe('exportRootSpanAfterLoadEvent', () => {
let realOcwConfig: OpenCensusWebConfig|undefined;
let realOcwAgent: string|undefined;
beforeEach(() => {
jasmine.clock().install();
spyOn(XMLHttpRequest.prototype, 'open');
spyOn(XMLHttpRequest.prototype, 'send');
spyOn(XMLHttpRequest.prototype, 'setRequestHeader');
realOcwConfig = windowWithOcwGlobals.ocwConfig;
realOcwAgent = windowWithOcwGlobals.ocwAgent;
});
afterEach(() => {
jasmine.clock().uninstall();
windowWithOcwGlobals.ocwConfig = realOcwConfig;
windowWithOcwGlobals.ocwAgent = realOcwAgent;
});

it('exports spans to agent if sampled and agent configured', () => {
windowWithOcwGlobals.ocwConfig = {sampled: true, agent: 'http://agent'};
it('exports spans to agent if agent is configured', () => {
windowWithOcwGlobals.ocwAgent = 'http://agent';
exportRootSpanAfterLoadEvent();
jasmine.clock().tick(300000);
expect(XMLHttpRequest.prototype.open)
.toHaveBeenCalledWith('POST', 'http://agent/v1/trace');
expect(XMLHttpRequest.prototype.send).toHaveBeenCalled();
});

it('does not export if not sampled', () => {
windowWithOcwGlobals.ocwConfig = {agent: 'http://agent'};
exportRootSpanAfterLoadEvent();
jasmine.clock().tick(300000);
expect(XMLHttpRequest.prototype.open).not.toHaveBeenCalled();
expect(XMLHttpRequest.prototype.send).not.toHaveBeenCalled();
});

it('does not export if agent not configured', () => {
windowWithOcwGlobals.ocwConfig = {sampled: true};
windowWithOcwGlobals.ocwAgent = undefined;
exportRootSpanAfterLoadEvent();
jasmine.clock().tick(300000);
expect(XMLHttpRequest.prototype.open).not.toHaveBeenCalled();
Expand Down
56 changes: 1 addition & 55 deletions packages/opencensus-web-core/src/common/time-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,62 +14,10 @@
* limitations under the License.
*/

/** Polyfill value of `performance.timeOrigin` for browser that lack support. */
let perfOriginPolyfill = 0;
/** If non-zero, the approximate peformance clock origin in server time. */
let perfOriginInServerTime = 0;

/**
* Returns the origin of the browser performance clock in epoch millis.
* This can be adjusted to align more closely with the server's clock using the
* `adjustPerfTimeOrigin` function.
*/
/** Returns the origin of the browser performance clock in epoch millis. */
export function getPerfTimeOrigin(): number {
return perfOriginInServerTime ? perfOriginInServerTime :
getClientPerfTimeOrigin();
}

/**
* Adjusts the performance clock time origin based on server clock times for the
* start and duration of the navigation fetch (the initial HTML load request).
*
* This adjusts the client clock such that the network time is evenly spread on
* both sides of the request, so that the server's span will be positioned right
* in the middle of the client's span. This enables visualizing the server and
* client spans no the same timeline even if they have clock skew.
*
* @param serverNavFetchStartTime The server's measurement of the request start
* in epoch milliseconds from the server clock. This would be sent back to
* the client in a <script> in the rendered HTML.
* @param serverNavFetchDuration The server's measurement of the request
* duration in milliseconds. This would also be sent to the client.
* @param perfNavTiming The performance navigation timing, which can be
* retrieved by `performance.getEntriesByType('navigation')[0]`, provided
* that the browser supports it.
*/
export function adjustPerfTimeOrigin(
serverNavFetchStartTime: number, serverNavFetchDuration: number,
perfNavTiming: PerformanceNavigationTiming) {
const clientStart = perfNavTiming.requestStart;
const clientEnd = perfNavTiming.responseStart;
const clientNavFetchDuration = clientEnd - clientStart;

// Server time is more than client time, which we don't expect, so don't try
// to adjust the time origin.
if (serverNavFetchDuration > clientNavFetchDuration) return;

const networkTime = clientNavFetchDuration - serverNavFetchDuration;
const halfNetworkTime = networkTime / 2;
const clientStartInServerTime = serverNavFetchStartTime - halfNetworkTime;
perfOriginInServerTime = clientStartInServerTime - clientStart;
}

/** Helper function used for testing. */
function clearAdjustedPerfTime() {
perfOriginInServerTime = 0;
}

function getClientPerfTimeOrigin() {
if (performance.timeOrigin) return performance.timeOrigin;
if (!perfOriginPolyfill) {
perfOriginPolyfill = Date.now() - performance.now();
Expand Down Expand Up @@ -111,5 +59,3 @@ function wholeAndFraction(num: number): [number, number] {
const fraction = num - whole;
return [whole, fraction];
}

export const TEST_ONLY = {clearAdjustedPerfTime};
98 changes: 5 additions & 93 deletions packages/opencensus-web-core/test/test-time-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import {adjustPerfTimeOrigin, getDateForPerfTime, getIsoDateStrForPerfTime, getPerfTimeOrigin, TEST_ONLY} from '../src/common/time-util';
import {getDateForPerfTime, getIsoDateStrForPerfTime, getPerfTimeOrigin} from '../src/common/time-util';
import {mockGetterOrValue, restoreGetterOrValue} from './util';

describe('time utils', () => {
Expand All @@ -29,112 +29,23 @@ describe('time utils', () => {
describe('getPerfTimeOrigin', () => {
it('returns `performance.timeOrigin` if set', () => {
mockGetterOrValue(performance, 'timeOrigin', 1548000000000);

expect(getPerfTimeOrigin()).toBe(1548000000000);
});

it('calculates via polyfill if `performance.timeOrigin` unset', () => {
mockGetterOrValue(performance, 'timeOrigin', undefined);
spyOn(Date, 'now').and.returnValue(1548000009999);
spyOn(performance, 'now').and.returnValue(9999);
});
});

describe('adjustPerfTimeOrigin', () => {
function createNavTiming({requestStart, responseStart}:
{requestStart: number, responseStart: number}):
PerformanceNavigationTiming {
return {
// This are used by the time adjustment function below.
requestStart, // Client start time in performance clock millis.
responseStart, // Client end time in performance clock millis.
// These are needed to satisfy the interface.
connectEnd: 0,
connectStart: 0,
decodedBodySize: 0,
domComplete: 0,
domContentLoadedEventEnd: 0,
domContentLoadedEventStart: 0,
domInteractive: 0,
domainLookupEnd: 0,
domainLookupStart: 0,
duration: 0,
encodedBodySize: 0,
entryType: '',
fetchStart: 0,
initiatorType: '',
loadEventEnd: 0,
loadEventStart: 0,
name: '',
nextHopProtocol: '',
redirectCount: 0,
redirectEnd: 0,
redirectStart: 0,
responseEnd: 0,
secureConnectionStart: 0,
startTime: 0,
toJSON: () => ({}),
transferSize: 0,
type: 'navigate',
unloadEventEnd: 0,
unloadEventStart: 0,
workerStart: 0,
};
}

const CLIENT_TIME_ORIGIN = 1548000000000;
beforeEach(() => {
mockGetterOrValue(performance, 'timeOrigin', CLIENT_TIME_ORIGIN);
});
afterEach(() => {
TEST_ONLY.clearAdjustedPerfTime();
});

it('keeps client time origin if server time longer than client', () => {
// Client nav fetch duration is 5ms
const perfNavTiming =
createNavTiming({requestStart: 10.1, responseStart: 15.1});

// Server nav fetch duration is 10ms
adjustPerfTimeOrigin(
1548000001000.2, /* serverNavFetchDuration */ 10, perfNavTiming);

expect(getPerfTimeOrigin()).toBe(CLIENT_TIME_ORIGIN);
});

it('adjusts origin to center server span in client span', () => {
const clientNavFetchStartInPerfTime = 10;
const clientNavFetchEndInPerfTime = 18;
const perfNavTiming = createNavTiming({
requestStart: clientNavFetchStartInPerfTime,
responseStart: clientNavFetchEndInPerfTime,
});
const serverNavFetchStartEpochMillis = 1500000001000; // Epoch millis.
const serverNavFetchDuration = 6; // Duration millis

adjustPerfTimeOrigin(
serverNavFetchStartEpochMillis, serverNavFetchDuration,
perfNavTiming);

// Calculations to make the expectation clearer:
const clientNavFetchDuration =
clientNavFetchEndInPerfTime - clientNavFetchStartInPerfTime;
expect(clientNavFetchDuration).toBe(8); // Duration millis
const networkTime = clientNavFetchDuration - serverNavFetchDuration;
expect(networkTime).toBe(2); // Duration millis
const clientNavStartInEpochMillis =
serverNavFetchStartEpochMillis - networkTime / 2;
expect(clientNavStartInEpochMillis).toBe(1500000000999);
const perfOriginInEpochMillis =
clientNavStartInEpochMillis - clientNavFetchStartInPerfTime;
expect(perfOriginInEpochMillis).toBe(1500000000989);

expect(getPerfTimeOrigin()).toBe(perfOriginInEpochMillis);
expect(getPerfTimeOrigin()).toBe(1548000000000);
});
});

describe('getDateForPerfTime', () => {
it('calculates date for perf time based on time origin', () => {
mockGetterOrValue(performance, 'timeOrigin', 1548000000000);

expect(getDateForPerfTime(999.6).getTime()).toBe(1548000000999);
});
});
Expand All @@ -148,6 +59,7 @@ describe('time utils', () => {

it('accurately combines milliseconds from origin and perf times', () => {
mockGetterOrValue(performance, 'timeOrigin', 1535683887441.586);

expect(getIsoDateStrForPerfTime(658867.8000000073))
.toEqual('2018-08-31T03:02:26.309385938Z');
});
Expand Down

0 comments on commit f6c8afa

Please sign in to comment.