Skip to content

Commit

Permalink
when web worker perf.getEntries() is stubbed, use fallback method
Browse files Browse the repository at this point in the history
  • Loading branch information
sbma44 authored and jfirebaugh committed Jun 4, 2018
1 parent 45b0be0 commit 179214d
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 15 deletions.
10 changes: 7 additions & 3 deletions src/source/geojson_worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { getJSON } from '../util/ajax';

import perf from '../util/performance';
import performance from '../util/performance';
import rewind from 'geojson-rewind';
import GeoJSONWrapper from './geojson_wrapper';
import vtpbf from 'vt-pbf';
Expand Down Expand Up @@ -152,6 +152,10 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
const params = this._pendingLoadDataParams;
delete this._pendingCallback;
delete this._pendingLoadDataParams;

const perf = (params && params.request && params.request.collectResourceTiming) ?
new performance.Performance(params.request) : false;

this.loadGeoJSON(params, (err, data) => {
if (err || !data) {
return callback(err);
Expand All @@ -171,8 +175,8 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
this.loaded = {};

const result = {};
if (params.request && params.request.collectResourceTiming) {
const resourceTimingData = perf.getEntriesByName(params.request.url);
if (perf) {
const resourceTimingData = perf.finish();
// it's necessary to eval the result of getEntriesByName() here via parse/stringify
// late evaluation in the main thread causes TypeError: illegal invocation
if (resourceTimingData) {
Expand Down
10 changes: 7 additions & 3 deletions src/source/vector_tile_worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import vt from '@mapbox/vector-tile';
import Protobuf from 'pbf';
import WorkerTile from './worker_tile';
import { extend } from '../util/util';
import perf from '../util/performance';
import performance from '../util/performance';

import type {
WorkerSource,
Expand Down Expand Up @@ -102,6 +102,9 @@ class VectorTileWorkerSource implements WorkerSource {
if (!this.loading)
this.loading = {};

const perf = (params && params.request && params.request.collectResourceTiming) ?
new performance.Performance(params.request) : false;

const workerTile = this.loading[uid] = new WorkerTile(params);
workerTile.abort = this.loadVectorData(params, (err, response) => {
delete this.loading[uid];
Expand All @@ -114,9 +117,10 @@ class VectorTileWorkerSource implements WorkerSource {
const cacheControl = {};
if (response.expires) cacheControl.expires = response.expires;
if (response.cacheControl) cacheControl.cacheControl = response.cacheControl;

const resourceTiming = {};
if (params.request && params.request.collectResourceTiming) {
const resourceTimingData = perf.getEntriesByName(params.request.url);
if (perf) {
const resourceTimingData = perf.finish();
// it's necessary to eval the result of getEntriesByName() here via parse/stringify
// late evaluation in the main thread causes TypeError: illegal invocation
if (resourceTimingData)
Expand Down
89 changes: 80 additions & 9 deletions src/util/performance.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,85 @@
// @flow

// Wraps performance.getEntriesByName to facilitate testing
import type {RequestParameters} from '../util/ajax';

// Wraps performance to facilitate testing
// Not incorporated into browser.js because the latter is poisonous when used outside the main thread
const exported = {
getEntriesByName: (url: string) => {
if ((typeof performance !== 'undefined') && performance && performance.getEntriesByName)
return performance.getEntriesByName(url);
else
return false;
}
const performanceExists = typeof performance !== 'undefined';
const wrapper = {};

wrapper.getEntriesByName = (url: string) => {
if (performanceExists && performance && performance.getEntriesByName)
return performance.getEntriesByName(url);
else
return false;
};

wrapper.mark = (name: string) => {
if (performanceExists && performance && performance.mark)
return performance.mark(name);
else
return false;
};

wrapper.measure = (name: string, startMark: string, endMark: string) => {
if (performanceExists && performance && performance.measure)
return performance.measure(name, startMark, endMark);
else
return false;
};

wrapper.clearMarks = (name: string) => {
if (performanceExists && performance && performance.clearMarks)
return performance.clearMarks(name);
else
return false;
};

wrapper.clearMeasures = (name: string) => {
if (performanceExists && performance && performance.clearMeasures)
return performance.clearMeasures(name);
else
return false;
};

export default exported;
/**
* Safe wrapper for the performance resource timing API in web workers with graceful degradation
*
* @param {RequestParameters} request
* @private
*/
class Performance {
_marks: {start: string, end: string, measure: string};

constructor (request: RequestParameters) {
this._marks = {
start: [request.url, 'start'].join('#'),
end: [request.url, 'end'].join('#'),
measure: request.url.toString()
};

wrapper.mark(this._marks.start);
}

finish() {
wrapper.mark(this._marks.end);
let resourceTimingData = wrapper.getEntriesByName(this._marks.measure);

// fallback if web worker implementation of perf.getEntriesByName returns empty
if (resourceTimingData.length === 0) {
wrapper.measure(this._marks.measure, this._marks.start, this._marks.end);
resourceTimingData = wrapper.getEntriesByName(this._marks.measure);

// cleanup
wrapper.clearMarks(this._marks.start);
wrapper.clearMarks(this._marks.end);
wrapper.clearMeasures(this._marks.measure);
}

return resourceTimingData;
}
}

wrapper.Performance = Performance;

export default wrapper;
32 changes: 32 additions & 0 deletions test/unit/source/geojson_worker_source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,38 @@ test('resourceTiming', (t) => {
});
});

t.test('loadData - url (resourceTiming fallback method)', (t) => {
const sampleMarks = [100, 350];
const marks = {};
const measures = {};
t.stub(perf, 'getEntriesByName').callsFake((name) => { return measures[name] || []; });
t.stub(perf, 'mark').callsFake((name) => {
marks[name] = sampleMarks.shift();
return null;
});
t.stub(perf, 'measure').callsFake((name, start, end) => {
measures[name] = measures[name] || [];
measures[name].push({
duration: marks[end] - marks[start],
entryType: 'measure',
name: name,
startTime: marks[start]
});
return null;
});
t.stub(perf, 'clearMarks').callsFake(() => { return null; });
t.stub(perf, 'clearMeasures').callsFake(() => { return null; });

const layerIndex = new StyleLayerIndex(layers);
const source = new GeoJSONWorkerSource(null, layerIndex, (params, callback) => { return callback(null, geoJson); });

source.loadData({ source: 'testSource', request: { url: 'http://localhost/nonexistent', collectResourceTiming: true } }, (err, result) => {
t.equal(err, null);
t.deepEquals(result.resourceTiming.testSource, [{"duration": 250, "entryType": "measure", "name": "http://localhost/nonexistent", "startTime": 100 }], 'got expected resource timing');
t.end();
});
});

t.test('loadData - data', (t) => {
const layerIndex = new StyleLayerIndex(layers);
const source = new GeoJSONWorkerSource(null, layerIndex);
Expand Down
54 changes: 54 additions & 0 deletions test/unit/source/vector_tile_worker_source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,57 @@ test('VectorTileWorkerSource provides resource timing information', (t) => {
t.end();
});
});

test('VectorTileWorkerSource provides resource timing information (fallback method)', (t) => {
const rawTileData = fs.readFileSync(path.join(__dirname, '/../../fixtures/mbsv5-6-18-23.vector.pbf'));

function loadVectorData(params, callback) {
return callback(null, {
vectorTile: new vt.VectorTile(new Protobuf(rawTileData)),
rawData: rawTileData,
cacheControl: null,
expires: null
});
}

const layerIndex = new StyleLayerIndex([{
id: 'test',
source: 'source',
'source-layer': 'test',
type: 'fill'
}]);

const source = new VectorTileWorkerSource(null, layerIndex, loadVectorData);

const sampleMarks = [100, 350];
const marks = {};
const measures = {};
t.stub(perf, 'getEntriesByName').callsFake((name) => { return measures[name] || []; });
t.stub(perf, 'mark').callsFake((name) => {
marks[name] = sampleMarks.shift();
return null;
});
t.stub(perf, 'measure').callsFake((name, start, end) => {
measures[name] = measures[name] || [];
measures[name].push({
duration: marks[end] - marks[start],
entryType: 'measure',
name: name,
startTime: marks[start]
});
return null;
});
t.stub(perf, 'clearMarks').callsFake(() => { return null; });
t.stub(perf, 'clearMeasures').callsFake(() => { return null; });

source.loadTile({
source: 'source',
uid: 0,
tileID: { overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0} },
request: { url: 'http://localhost:2900/faketile.pbf', collectResourceTiming: true }
}, (err, res) => {
t.false(err);
t.deepEquals(res.resourceTiming[0], {"duration": 250, "entryType": "measure", "name": "http://localhost:2900/faketile.pbf", "startTime": 100 }, 'resourceTiming resp is expected');
t.end();
});
});

0 comments on commit 179214d

Please sign in to comment.