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

Commit

Permalink
Support high-res performance times in ocagent exporter (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
draffensperger committed Jan 28, 2019
1 parent 03dba6b commit df824a8
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 21 deletions.
10 changes: 1 addition & 9 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,11 @@ jobs:
# NPM yet. We can't install the package directly from GitHub because NPM
# only supports that for packages in the root folder of the repo.
# See https://github.com/census-instrumentation/opencensus-node/pull/254
- run: (cd packages/opencensus-web-core && npm install || true)
- run: (cd packages/opencensus-web-exporter-ocagent && npm install || true)
- run: npm install || true
- run: find . -type f | grep '@opencensus/core' | xargs sed -i '/types="mocha"/d'

- run: npm install

# Remove the mocha type references in @opencensus/core. This has been
# fixed in the upstream package on GitHub, but has not been released on
# NPM yet. We can't install the package directly from GitHub because NPM
# only supports that for packages in the root folder of the repo.
# See https://github.com/census-instrumentation/opencensus-node/pull/254
- run: find . -type f | grep '@opencensus/core' | xargs sed -i '/types="mocha"/d'

- save_cache:
paths:
- node_modules
Expand Down
4 changes: 3 additions & 1 deletion packages/opencensus-web-core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions packages/opencensus-web-core/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"name": "@opencensus/web-core",
"version": "0.0.1",
"description":
"OpenCensus Web is a toolkit for collecting application performance and behavior data from client side web browser apps.",
"description": "OpenCensus Web is a toolkit for collecting application performance and behavior data from client side web browser apps.",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
"scripts": {
Expand Down
30 changes: 30 additions & 0 deletions packages/opencensus-web-core/src/common/time-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,33 @@ export function getPerfTimeOrigin(): number {
export function getDateForPerfTime(perfTime: DOMHighResTimeStamp): Date {
return new Date(getPerfTimeOrigin() + perfTime);
}

/**
* Converts a time in browser performance clock milliseconds into a nanosecond
* precision ISO date string.
*/
export function getIsoDateStrForPerfTime(perfTime: number): string {
// Break origin and perf times into whole and fractional parts.
const [originWhole, originFrac] = wholeAndFraction(getPerfTimeOrigin());
const [perfWhole, perfFrac] = wholeAndFraction(perfTime);

// Combine the fractional and whole parts separately to preserve precision.
const totalFrac = originFrac + perfFrac;
const [extraWholeMs, fracMs] = wholeAndFraction(totalFrac);
const wholeMs = originWhole + perfWhole + extraWholeMs;

// Use native Date class to get ISO string for the whole number milliseconds.
const dateStrWholeMs = new Date(wholeMs).toISOString();

// Append the fractional millisecond for the final 6 digits of the ISO date.
const dateStrWithoutZ = dateStrWholeMs.replace('Z', '');
const millisFracStr = fracMs.toFixed(6).substring(2);
return `${dateStrWithoutZ}${millisFracStr}Z`;
}

/** Splits a number into whole and fractional parts. */
function wholeAndFraction(num: number): [number, number] {
const whole = Math.floor(num);
const fraction = num - whole;
return [whole, fraction];
}
2 changes: 2 additions & 0 deletions packages/opencensus-web-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ export * from './trace/model/types';

// Re-export types this uses from @opencensus/core.
export {Annotation, Attributes, Link, MessageEvent, SpanContext, SpanEventListener, TraceState, Propagation, Exporter, TracerConfig, Config} from '@opencensus/core';

export * from './common/time-util';
17 changes: 16 additions & 1 deletion 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 {getDateForPerfTime, getPerfTimeOrigin} from '../src/common/time-util';
import {getDateForPerfTime, getIsoDateStrForPerfTime, getPerfTimeOrigin} from '../src/common/time-util';

describe('getPerfTimeOrigin', () => {
it('returns `performance.timeOrigin` if set', () => {
Expand All @@ -39,3 +39,18 @@ describe('getDateForPerfTime', () => {
expect(getDateForPerfTime(999.6).getTime()).toBe(1548000000999);
});
});

describe('getIsoDateStrForPerfTime', () => {
it('converts perf time to nanosecond-precise ISO date string', () => {
spyOnProperty(performance, 'timeOrigin').and.returnValue(1535683887001);
expect(getIsoDateStrForPerfTime(0.000001))
.toEqual('2018-08-31T02:51:27.001000001Z');
});

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

expect(getIsoDateStrForPerfTime(658867.8000000073))
.toEqual('2018-08-31T03:02:26.309385938Z');
});
});
4 changes: 3 additions & 1 deletion packages/opencensus-web-exporter-ocagent/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions packages/opencensus-web-exporter-ocagent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"access": "public"
},
"devDependencies": {
"@opencensus/core": "^0.0.8",
"@types/jasmine": "^3.3.4",
"@types/node": "^10.12.18",
"gts": "^0.9.0",
Expand All @@ -58,8 +59,9 @@
"ts-loader": "^5.1.0",
"typescript": "^3.1.6",
"webpack": "^4.18.0",
"webpack-cli": "^3.1.0",
"@opencensus/core": "0.0.8"
"webpack-cli": "^3.1.0"
},
"dependencies": {}
"dependencies": {
"@opencensus/web-core": "^0.0.1"
}
}
29 changes: 25 additions & 4 deletions packages/opencensus-web-exporter-ocagent/src/adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
*/

import * as coreTypes from '@opencensus/core';
import * as webCore from '@opencensus/web-core';
import * as apiTypes from './api-types';

/**
* Converts a RootSpan type from @opencensus/core to the Span JSON structure
* expected by the OpenCensus Agent's HTTP/JSON (grpc-gateway) API.
*/
export function adaptRootSpan(rootSpan: coreTypes.RootSpan): apiTypes.Span[] {
export function adaptRootSpan(rootSpan: coreTypes.RootSpan|
webCore.RootSpan): apiTypes.Span[] {
const adaptedSpans: apiTypes.Span[] = rootSpan.spans.map(adaptSpan);
adaptedSpans.unshift(adaptSpan(rootSpan));
return adaptedSpans;
Expand Down Expand Up @@ -156,7 +158,25 @@ function adaptLinks(links: coreTypes.Link[]): apiTypes.Links {
return {link: links.map(adaptLink)};
}

function adaptSpan(span: coreTypes.Span): apiTypes.Span {
/**
* Returns an ISO date string for a span high-resolution browser performance
* clock time when available (if the span was a `webCore.Span` instance) or
* otherwise using the more general `@opencensus/core` Date-typed times.
*/
function adaptSpanTime(perfTime: number|undefined, fallbackTime: Date) {
return perfTime === undefined ? fallbackTime.toISOString() :
webCore.getIsoDateStrForPerfTime(perfTime);
}

/** Interface to represent that a coreTypes.Span may be a webCore.Span */
interface MaybeWebSpan {
startPerfTime?: number;
endPerfTime?: number;
startTime: Date;
endTime: Date;
}

function adaptSpan(span: coreTypes.Span|webCore.Span): apiTypes.Span {
// The stackTrace and childSpanCount attributes are not currently supported by
// opencensus-web.
return {
Expand All @@ -166,8 +186,9 @@ function adaptSpan(span: coreTypes.Span): apiTypes.Span {
parentSpanId: hexToBase64(span.parentSpanId),
name: adaptString(span.name),
kind: adaptSpanKind(span.kind),
startTime: span.startTime.toISOString(),
endTime: span.endTime.toISOString(),
startTime:
adaptSpanTime((span as MaybeWebSpan).startPerfTime, span.startTime),
endTime: adaptSpanTime((span as MaybeWebSpan).endPerfTime, span.endTime),
attributes: adaptAttributes(span.attributes),
timeEvents: adaptTimeEvents(span.annotations, span.messageEvents),
links: adaptLinks(span.links),
Expand Down
53 changes: 53 additions & 0 deletions packages/opencensus-web-exporter-ocagent/test/test-adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import * as coreTypes from '@opencensus/core';
import * as webTypes from '@opencensus/web-core';
import {adaptRootSpan} from '../src/adapters';
import * as apiTypes from '../src/api-types';
import {MockRootSpan, MockSpan} from './mock-trace-types';
Expand Down Expand Up @@ -165,4 +166,56 @@ describe('Core to API Span adapters', () => {
];
expect(apiSpans).toEqual(expectedApiSpans);
});

it('adapts perf times of web spans as high-res timestamps', () => {
spyOnProperty(performance, 'timeOrigin').and.returnValue(1548000000000);
const tracer = new webTypes.Tracer();
const webSpan1 = new webTypes.Span();
webSpan1.id = '000000000000000a';
webSpan1.traceId = '00000000000000000000000000000001';
webSpan1.startPerfTime = 10.1;
webSpan1.endPerfTime = 20.113;
const webRootSpan = new webTypes.RootSpan(tracer);
webRootSpan.spans = [webSpan1];
webRootSpan.startPerfTime = 5.001;
webRootSpan.endPerfTime = 30.000001;
webRootSpan.id = '000000000000000b';
webRootSpan.traceId = '00000000000000000000000000000001';

const apiSpans = adaptRootSpan(webRootSpan);

const expectedApiSpans: apiTypes.Span[] = [
{
traceId: 'AAAAAAAAAAAAAAAAAAAAAQ==',
spanId: 'AAAAAAAAAAs=',
tracestate: {},
parentSpanId: '',
name: {value: 'unnamed'},
kind: apiTypes.SpanKind.UNSPECIFIED,
startTime: '2019-01-20T16:00:00.005001000Z',
endTime: '2019-01-20T16:00:00.030000001Z',
attributes: {attributeMap: {}},
timeEvents: {timeEvent: []},
links: {link: []},
status: {},
sameProcessAsParentSpan: true,
},
{
traceId: 'AAAAAAAAAAAAAAAAAAAAAQ==',
spanId: 'AAAAAAAAAAo=',
tracestate: {},
parentSpanId: '',
name: {value: 'unnamed'},
kind: apiTypes.SpanKind.UNSPECIFIED,
startTime: '2019-01-20T16:00:00.010100000Z',
endTime: '2019-01-20T16:00:00.020113000Z',
attributes: {attributeMap: {}},
timeEvents: {timeEvent: []},
links: {link: []},
status: {},
sameProcessAsParentSpan: true,
},
];
expect(apiSpans).toEqual(expectedApiSpans);
});
});

0 comments on commit df824a8

Please sign in to comment.