mirror of https://github.com/status-im/metro.git
Improvements to the Activity API
Summary: Revised the Activity implementation - added flow annotations - fixed some lint warnings - added event `options`, specifically a `telemetric` option which indicates that events tagged in this manner are relevant for telemetry. The duration of these events is now highlighted in the Activity log Reviewed By: bestander, kentaromiura Differential Revision: D3770753 fbshipit-source-id: 6976535fd3bf5269c6263d825d657ab0805ecaad
This commit is contained in:
parent
053720b66f
commit
ad7aa8434a
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
export type EventOptions = {
|
||||||
|
telemetric: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Event = {
|
||||||
|
id: number,
|
||||||
|
startTimeStamp: number,
|
||||||
|
endTimeStamp?: number,
|
||||||
|
name: string,
|
||||||
|
data?: any,
|
||||||
|
options: EventOptions,
|
||||||
|
};
|
|
@ -13,14 +13,17 @@ jest.disableAutomock();
|
||||||
var Activity = require('../');
|
var Activity = require('../');
|
||||||
|
|
||||||
describe('Activity', () => {
|
describe('Activity', () => {
|
||||||
|
// eslint-disable-next-line no-console-disallow
|
||||||
const origConsoleLog = console.log;
|
const origConsoleLog = console.log;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
// eslint-disable-next-line no-console-disallow
|
||||||
console.log = jest.fn();
|
console.log = jest.fn();
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
// eslint-disable-next-line no-console-disallow
|
||||||
console.log = origConsoleLog;
|
console.log = origConsoleLog;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,7 +35,9 @@ describe('Activity', () => {
|
||||||
Activity.startEvent(EVENT_NAME, DATA);
|
Activity.startEvent(EVENT_NAME, DATA);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console-disallow
|
||||||
expect(console.log.mock.calls.length).toBe(1);
|
expect(console.log.mock.calls.length).toBe(1);
|
||||||
|
// eslint-disable-next-line no-console-disallow
|
||||||
const consoleMsg = console.log.mock.calls[0][0];
|
const consoleMsg = console.log.mock.calls[0][0];
|
||||||
expect(consoleMsg).toContain('START');
|
expect(consoleMsg).toContain('START');
|
||||||
expect(consoleMsg).toContain(EVENT_NAME);
|
expect(consoleMsg).toContain(EVENT_NAME);
|
||||||
|
@ -49,7 +54,9 @@ describe('Activity', () => {
|
||||||
Activity.endEvent(eventID);
|
Activity.endEvent(eventID);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console-disallow
|
||||||
expect(console.log.mock.calls.length).toBe(2);
|
expect(console.log.mock.calls.length).toBe(2);
|
||||||
|
// eslint-disable-next-line no-console-disallow
|
||||||
const consoleMsg = console.log.mock.calls[1][0];
|
const consoleMsg = console.log.mock.calls[1][0];
|
||||||
expect(consoleMsg).toContain('END');
|
expect(consoleMsg).toContain('END');
|
||||||
expect(consoleMsg).toContain(EVENT_NAME);
|
expect(consoleMsg).toContain(EVENT_NAME);
|
||||||
|
|
|
@ -5,98 +5,122 @@
|
||||||
* This source code is licensed under the BSD-style license found in the
|
* This source code is licensed under the BSD-style license found in the
|
||||||
* LICENSE file in the root directory of this source tree. An additional grant
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import type {EventOptions} from './Types';
|
||||||
|
import type {Event} from './Types';
|
||||||
|
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const events = require('events');
|
const events = require('events');
|
||||||
|
|
||||||
const _eventStarts = Object.create(null);
|
let ENABLED = true;
|
||||||
const _eventEmitter = new events.EventEmitter();
|
let UUID = 1;
|
||||||
|
|
||||||
let _uuid = 1;
|
const EVENT_INDEX: {[key: number]: Event} = Object.create(null);
|
||||||
let _enabled = true;
|
const EVENT_EMITTER = new events.EventEmitter();
|
||||||
|
|
||||||
function endEvent(eventId) {
|
function startEvent(
|
||||||
const eventEndTime = Date.now();
|
name: string,
|
||||||
if (!_eventStarts[eventId]) {
|
data: any = null,
|
||||||
throw new Error('event(' + eventId + ') either ended or never started');
|
options?: EventOptions = {telemetric: false},
|
||||||
|
): number {
|
||||||
|
if (name == null) {
|
||||||
|
throw new Error('No event name specified!');
|
||||||
}
|
}
|
||||||
|
|
||||||
_writeAction({
|
const id = UUID++;
|
||||||
action: 'endEvent',
|
EVENT_INDEX[id] = {
|
||||||
eventId: eventId,
|
id,
|
||||||
tstamp: eventEndTime
|
startTimeStamp: Date.now(),
|
||||||
});
|
name,
|
||||||
}
|
data,
|
||||||
|
options,
|
||||||
function startEvent(eventName, data) {
|
|
||||||
const eventStartTime = Date.now();
|
|
||||||
|
|
||||||
if (eventName == null) {
|
|
||||||
throw new Error('No event name specified');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data == null) {
|
|
||||||
data = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventId = _uuid++;
|
|
||||||
const action = {
|
|
||||||
action: 'startEvent',
|
|
||||||
data: data,
|
|
||||||
eventId: eventId,
|
|
||||||
eventName: eventName,
|
|
||||||
tstamp: eventStartTime,
|
|
||||||
};
|
};
|
||||||
_eventStarts[eventId] = action;
|
logEvent(id, 'startEvent');
|
||||||
_writeAction(action);
|
return id;
|
||||||
|
|
||||||
return eventId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function disable() {
|
function endEvent(id: number): void {
|
||||||
_enabled = false;
|
getEvent(id).endTimeStamp = Date.now();
|
||||||
|
logEvent(id, 'endEvent');
|
||||||
}
|
}
|
||||||
|
|
||||||
function _writeAction(action) {
|
function getEvent(id: number): Event {
|
||||||
_eventEmitter.emit(action.action, action);
|
if (!EVENT_INDEX[id]) {
|
||||||
|
throw new Error(`Event(${id}) either ended or never started`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!_enabled) {
|
return EVENT_INDEX[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
function forgetEvent(id: number): void {
|
||||||
|
delete EVENT_INDEX[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
function logEvent(id: number, phase: 'startEvent' | 'endEvent'): void {
|
||||||
|
const event = EVENT_INDEX[id];
|
||||||
|
EVENT_EMITTER.emit(phase, id);
|
||||||
|
|
||||||
|
if (!ENABLED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = action.data ? ': ' + JSON.stringify(action.data) : '';
|
const {
|
||||||
const fmtTime = new Date(action.tstamp).toLocaleTimeString();
|
startTimeStamp,
|
||||||
|
endTimeStamp,
|
||||||
|
name,
|
||||||
|
data,
|
||||||
|
options,
|
||||||
|
} = event;
|
||||||
|
|
||||||
switch (action.action) {
|
const duration = +endTimeStamp - startTimeStamp;
|
||||||
|
const dataString = data ? ': ' + JSON.stringify(data) : '';
|
||||||
|
const {telemetric} = options;
|
||||||
|
|
||||||
|
switch (phase) {
|
||||||
case 'startEvent':
|
case 'startEvent':
|
||||||
console.log(chalk.dim(
|
// eslint-disable-next-line no-console-disallow
|
||||||
'[' + fmtTime + '] ' +
|
console.log(
|
||||||
'<START> ' + action.eventName +
|
chalk.dim(
|
||||||
data
|
'[' + new Date(startTimeStamp).toLocaleString() + '] ' +
|
||||||
));
|
'<START> ' + name + dataString
|
||||||
|
)
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'endEvent':
|
case 'endEvent':
|
||||||
const startAction = _eventStarts[action.eventId];
|
// eslint-disable-next-line no-console-disallow
|
||||||
const startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : '';
|
console.log(
|
||||||
console.log(chalk.dim(
|
chalk.dim('[' + new Date(endTimeStamp).toLocaleString() + '] ' + '<END> ' + name) +
|
||||||
'[' + fmtTime + '] ' +
|
chalk.dim(dataString) +
|
||||||
'<END> ' + startAction.eventName +
|
(telemetric ? chalk.reset.cyan(' (' + (duration) + 'ms)') : chalk.dim(' (' + (duration) + 'ms)'))
|
||||||
' (' + (action.tstamp - startAction.tstamp) + 'ms)' +
|
);
|
||||||
startData
|
forgetEvent(id);
|
||||||
));
|
|
||||||
delete _eventStarts[action.eventId];
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error('Unexpected scheduled action type: ' + action.action);
|
throw new Error('Unexpected scheduled event type: ' + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enable(): void {
|
||||||
|
ENABLED = true;
|
||||||
|
}
|
||||||
|
|
||||||
exports.endEvent = endEvent;
|
function disable(): void {
|
||||||
exports.startEvent = startEvent;
|
ENABLED = false;
|
||||||
exports.disable = disable;
|
}
|
||||||
exports.eventEmitter = _eventEmitter;
|
|
||||||
|
module.exports = {
|
||||||
|
startEvent,
|
||||||
|
endEvent,
|
||||||
|
getEvent,
|
||||||
|
forgetEvent,
|
||||||
|
enable,
|
||||||
|
disable,
|
||||||
|
eventEmitter: EVENT_EMITTER,
|
||||||
|
};
|
||||||
|
|
|
@ -356,7 +356,13 @@ class Bundler {
|
||||||
onModuleTransformed = noop,
|
onModuleTransformed = noop,
|
||||||
finalizeBundle = noop,
|
finalizeBundle = noop,
|
||||||
}) {
|
}) {
|
||||||
const findEventId = Activity.startEvent('find dependencies');
|
const findEventId = Activity.startEvent(
|
||||||
|
'Finding dependencies',
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
telemetric: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
const modulesByName = Object.create(null);
|
const modulesByName = Object.create(null);
|
||||||
|
|
||||||
if (!resolutionResponse) {
|
if (!resolutionResponse) {
|
||||||
|
|
|
@ -485,7 +485,7 @@ class Server {
|
||||||
_processAssetsRequest(req, res) {
|
_processAssetsRequest(req, res) {
|
||||||
const urlObj = url.parse(req.url, true);
|
const urlObj = url.parse(req.url, true);
|
||||||
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
|
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
|
||||||
const assetEvent = Activity.startEvent(`processing asset request ${assetPath[1]}`);
|
const assetEvent = Activity.startEvent('Processing asset request', {asset: assetPath[1]});
|
||||||
this._assetServer.get(assetPath[1], urlObj.query.platform)
|
this._assetServer.get(assetPath[1], urlObj.query.platform)
|
||||||
.then(
|
.then(
|
||||||
data => res.end(this._rangeRequestMiddleware(req, res, data, assetPath)),
|
data => res.end(this._rangeRequestMiddleware(req, res, data, assetPath)),
|
||||||
|
@ -610,7 +610,15 @@ class Server {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const startReqEventId = Activity.startEvent('request:' + req.url);
|
const startReqEventId = Activity.startEvent(
|
||||||
|
'Requesting bundle',
|
||||||
|
{
|
||||||
|
url: req.url,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
telemetric: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
const options = this._getOptionsFromUrl(req.url);
|
const options = this._getOptionsFromUrl(req.url);
|
||||||
debug('Getting bundle for request');
|
debug('Getting bundle for request');
|
||||||
const building = this._useCachedOrUpdateOrCreateBundle(options);
|
const building = this._useCachedOrUpdateOrCreateBundle(options);
|
||||||
|
@ -661,7 +669,13 @@ class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
_symbolicate(req, res) {
|
_symbolicate(req, res) {
|
||||||
const startReqEventId = Activity.startEvent('symbolicate');
|
const startReqEventId = Activity.startEvent(
|
||||||
|
'Symbolicating',
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
telemetric: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
new Promise.resolve(req.rawBody).then(body => {
|
new Promise.resolve(req.rawBody).then(body => {
|
||||||
const stack = JSON.parse(body).stack;
|
const stack = JSON.parse(body).stack;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2015-present, Facebook, Inc.
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -60,6 +60,10 @@ class DeprecatedAssetMap {
|
||||||
if (activity) {
|
if (activity) {
|
||||||
processAsset_DEPRECATEDActivity = activity.startEvent(
|
processAsset_DEPRECATEDActivity = activity.startEvent(
|
||||||
'Building (deprecated) Asset Map',
|
'Building (deprecated) Asset Map',
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
telemetric: true,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,13 @@ class Fastfs extends EventEmitter {
|
||||||
let fastfsActivity;
|
let fastfsActivity;
|
||||||
const activity = this._activity;
|
const activity = this._activity;
|
||||||
if (activity) {
|
if (activity) {
|
||||||
fastfsActivity = activity.startEvent('Building in-memory fs for ' + this._name);
|
fastfsActivity = activity.startEvent(
|
||||||
|
'Building in-memory fs for ' + this._name,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
telemetric: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
files.forEach(filePath => {
|
files.forEach(filePath => {
|
||||||
const root = this._getRoot(filePath);
|
const root = this._getRoot(filePath);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2015-present, Facebook, Inc.
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -91,8 +91,20 @@ class DependencyGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
const {activity} = this._opts;
|
const {activity} = this._opts;
|
||||||
const depGraphActivity = activity.startEvent('Building Dependency Graph');
|
const depGraphActivity = activity.startEvent(
|
||||||
const crawlActivity = activity.startEvent('Crawling File System');
|
'Building Dependency Graph',
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
telemetric: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const crawlActivity = activity.startEvent(
|
||||||
|
'Crawling File System',
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
telemetric: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED);
|
const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED);
|
||||||
this._crawling = crawl(allRoots, {
|
this._crawling = crawl(allRoots, {
|
||||||
ignore: this._opts.ignoreFilePath,
|
ignore: this._opts.ignoreFilePath,
|
||||||
|
@ -148,7 +160,13 @@ class DependencyGraph {
|
||||||
this._loading = Promise.all([
|
this._loading = Promise.all([
|
||||||
this._fastfs.build()
|
this._fastfs.build()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const hasteActivity = activity.startEvent('Building Haste Map');
|
const hasteActivity = activity.startEvent(
|
||||||
|
'Building Haste Map',
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
telemetric: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
return this._hasteMap.build().then(map => {
|
return this._hasteMap.build().then(map => {
|
||||||
activity.endEvent(hasteActivity);
|
activity.endEvent(hasteActivity);
|
||||||
return map;
|
return map;
|
||||||
|
|
Loading…
Reference in New Issue