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

Add unit/integration testing for @opencensus/web-instrumentation-zone #104

Merged
merged 4 commits into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/opencensus-web-core/src/trace/model/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export class Tracing implements webTypes.Tracing {
return this.singletonInstance || (this.singletonInstance = new this());
}

constructor() {
// register the noop exporter to have an span event listener,
// helpful for testing.
this.tracer.registerSpanEventListener(this.exporter);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you do this in the tests rather than here in the production code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

/** Sets tracer and exporter config. */
start(config?: webTypes.Config): webTypes.Tracing {
this.tracer.start(config || {});
Expand Down
3 changes: 2 additions & 1 deletion packages/opencensus-web-core/test/test-tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Tracing', () => {
let newExporter: webTypes.Exporter;

beforeEach(() => {
tracing = new Tracing();
tracing = Tracing.instance;
tracer = tracing.tracer;
spyOn(tracer, 'registerSpanEventListener');
spyOn(tracer, 'unregisterSpanEventListener');
Expand All @@ -39,6 +39,7 @@ describe('Tracing', () => {

describe('default exporter', () => {
it('is a no-op exporter', () => {
tracing.unregisterExporter(tracing.exporter);
expect(tracing.exporter).toBe(NOOP_EXPORTER);
});
});
Expand Down
6 changes: 3 additions & 3 deletions packages/opencensus-web-instrumentation-zone/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ module.exports = (config) => {
browsers: ['ChromeHeadless'],
frameworks: ['jasmine'],
reporters: ['spec', 'coverage-istanbul'],
files: ['test/index.ts'],
preprocessors: {'test/index.ts': ['webpack']},
files: ['test/index.ts', 'node_modules/zone.js/dist/zone.js'],
preprocessors: { 'test/index.ts': ['webpack'] },
// Use webpack so that tree-shaking will remove all Node.js dependencies of
// the `@opencensus/core` library, since they are not actually used in this
// package's compiled JS code. Only the TypeScript interfaces from
// `@opecensus/core` are used.
webpack: webpackConfig,
webpackMiddleware: {noInfo: true},
webpackMiddleware: { noInfo: true },
coverageIstanbulReporter: {
reports: ['json', 'text-summary'],
dir: path.join(__dirname, 'coverage'),
Expand Down
2 changes: 2 additions & 0 deletions packages/opencensus-web-instrumentation-zone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"build:dev": "webpack --config ./webpack/dev-bundles.config.js",
"build": "webpack",
"start:webpack-server": "webpack-dev-server --config ./webpack/dev-bundles.config.js",
"test": "karma start --single-run",
"test:start": "karma start",
"codecov": "codecov -f coverage/*.json",
"clean": "rimraf build/*",
"check": "gts check",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ function setupExporter() {

export function startInteractionTracker() {
setupExporter();
return new InteractionTracker();
InteractionTracker.startTracking();
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ import {
declare const Zone: ZoneType & { prototype: Zone };

// Delay of 50 ms to reset currentEventTracingZone.
const RESET_TRACING_ZONE_DELAY = 50;
export const RESET_TRACING_ZONE_DELAY = 50;

export class InteractionTracker {
// Allows to track several events triggered by the same user interaction in the right Zone.
private currentEventTracingZone?: Zone;

// Used to reset the currentEventTracingZone when the interaction
// finishes before it is reset automatically.
private currentResetTracingZoneTimeout?: number;

// Map of interaction Ids to stopwatches.
private readonly interactions: {
[index: string]: OnPageInteractionStopwatch;
Expand All @@ -46,23 +50,26 @@ export class InteractionTracker {
[index: string]: number;
} = {};

constructor() {
private static singletonInstance: InteractionTracker;

private constructor() {
this.patchZoneRunTask();
this.patchZoneScheduleTask();
this.patchZoneCancelTask();
this.patchHistoryApi();
}

static startTracking(): InteractionTracker {
return this.singletonInstance || (this.singletonInstance = new this());
}

private patchZoneRunTask() {
const runTask = Zone.prototype.runTask;
Zone.prototype.runTask = (
task: AsyncTask,
applyThis: unknown,
applyArgs: unknown
) => {
console.warn('Running task');
console.log(task);

const interceptingElement = getTrackedElement(task);
const interactionName = resolveInteractionName(
interceptingElement,
Expand All @@ -87,7 +94,6 @@ export class InteractionTracker {
try {
return runTask.call(task.zone as {}, task, applyThis, applyArgs);
} finally {
console.log('Run task finished.');
if (
interceptingElement ||
(shouldCountTask(task) && isTrackedTask(task))
Expand Down Expand Up @@ -155,7 +161,7 @@ export class InteractionTracker {
tracing.tracer.startRootSpan(spanOptions, root => {
// As startRootSpan creates the zone and Zone.current corresponds to the
// new zone, we have to set the currentEventTracingZone with the Zone.current
// to capture the new zone, also, start the `OnPageInteraction` to capture the
// to capture the new zone, also, start the `OnPageInteraction` to capture the
// new root span.
this.currentEventTracingZone = Zone.current;
this.interactions[traceId] = startOnPageInteraction({
Expand All @@ -170,14 +176,19 @@ export class InteractionTracker {

// Timeout to reset currentEventTracingZone to allow the creation of a new
// zone for a new user interaction.
Zone.root.run(() =>
setTimeout(
() => (this.currentEventTracingZone = undefined),
Zone.root.run(() => {
// Store the timeout in case the interaction finishes before
// this callback runs.
this.currentResetTracingZoneTimeout = window.setTimeout(
() => this.resetCurrentTracingZone(),
RESET_TRACING_ZONE_DELAY
)
);
console.log('New zone:');
console.log(this.currentEventTracingZone);
);
});
}

private resetCurrentTracingZone() {
this.currentEventTracingZone = undefined;
this.currentResetTracingZoneTimeout = undefined;
}

/** Increments the count of outstanding tasks for a given interaction id. */
Expand Down Expand Up @@ -233,6 +244,14 @@ export class InteractionTracker {
this.interactionCompletionTimeouts[interactionId] = setTimeout(() => {
this.completeInteraction(interactionId);
delete this.interactionCompletionTimeouts[interactionId];
// In case the interaction finished beforeresetCurrentTracingZone is called,
// this in order to allow the creating of a new interaction.
if (this.currentResetTracingZoneTimeout) {
Zone.root.run(() => {
clearTimeout(this.currentResetTracingZoneTimeout);
this.resetCurrentTracingZone();
});
}
});
});
}
Expand Down Expand Up @@ -375,10 +394,15 @@ function resolveInteractionName(
function shouldCountTask(task: Task): boolean {
if (!task.data) return false;

// Don't count periodic tasks with a delay greater than 1 s.
if (task.data.isPeriodic && (task.data.delay && task.data.delay >= 1000)) {
return false;
}
// Don't count periodic tasks like setInterval as they will be repeatedly
// called. This will cause that the interaction never finishes, then would be
// imposible to measure the stability of the interaction.
// This case only applies for `setInterval` as we support `setTimeout`.
// TODO: ideally OpenCensus Web can manage this kind of tasks, so for example
// if a periodic task ends up doing some work in the future it will still
// be associated with that same older tracing zone. This is something we have to
// think of.
if (task.data.isPeriodic) return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you change this to ignore all periodic tasks? I would think that calls to setInterval could be pretty relevant to the work of user interactions.

One other thing that should be a TODO for later I think - if you have a periodic task with > 1 second of time, then I think it should be executed outside of the tracing zone. Otherwise, if it ends up doing some work in the future it will still be associated with that same older tracing zone.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found out that if we track periodic tasks, the interaction might not finish as they are called repeatedly, so we won't be able to determine the stability of the interaction.


// We're only interested in macroTasks and microTasks.
return task.type === 'macroTask' || task.type === 'microTask';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,20 @@ export class OnPageInteractionStopwatch {
return this.taskCount;
}

/** Stops the stopwatch and record the xhr response. */
/**
* Stops the stopwatch, fills root span attributes and ends the span.
* If has remaining tasks do not end the root span.
*/
stopAndRecord(): void {
if (this.hasRemainingTasks()) return;

const rootSpan = this.data.rootSpan;
rootSpan.addAttribute('EventType', this.data.eventType);
rootSpan.addAttribute('TargetElement', this.data.target.tagName);
rootSpan.addAttribute(ATTRIBUTE_HTTP_URL, this.data.startLocationHref);
rootSpan.addAttribute(ATTRIBUTE_HTTP_PATH, this.data.startLocationPath);
rootSpan.addAttribute(ATTRIBUTE_HTTP_USER_AGENT, navigator.userAgent);
rootSpan.end();
console.log('End of tracking. The interaction is stable.');
console.log('Time to stable: ' + this.data.rootSpan.duration + ' ms.');
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/opencensus-web-instrumentation-zone/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@

// This file is an entry point for the webpack test configuration, so this
// should import from all test files.
import './test-on-page-interaction';
import './test-interaction-tracker';
Loading