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:
Ovidiu Viorel Iepure 2016-08-25 09:51:31 -07:00 committed by Facebook Github Bot 7
parent 053720b66f
commit ad7aa8434a
8 changed files with 178 additions and 74 deletions

25
react-packager/src/Activity/Types.js vendored Normal file
View File

@ -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,
};

View File

@ -13,14 +13,17 @@ jest.disableAutomock();
var Activity = require('../');
describe('Activity', () => {
// eslint-disable-next-line no-console-disallow
const origConsoleLog = console.log;
beforeEach(() => {
// eslint-disable-next-line no-console-disallow
console.log = jest.fn();
jest.runOnlyPendingTimers();
});
afterEach(() => {
// eslint-disable-next-line no-console-disallow
console.log = origConsoleLog;
});
@ -32,7 +35,9 @@ describe('Activity', () => {
Activity.startEvent(EVENT_NAME, DATA);
jest.runOnlyPendingTimers();
// eslint-disable-next-line no-console-disallow
expect(console.log.mock.calls.length).toBe(1);
// eslint-disable-next-line no-console-disallow
const consoleMsg = console.log.mock.calls[0][0];
expect(consoleMsg).toContain('START');
expect(consoleMsg).toContain(EVENT_NAME);
@ -49,7 +54,9 @@ describe('Activity', () => {
Activity.endEvent(eventID);
jest.runOnlyPendingTimers();
// eslint-disable-next-line no-console-disallow
expect(console.log.mock.calls.length).toBe(2);
// eslint-disable-next-line no-console-disallow
const consoleMsg = console.log.mock.calls[1][0];
expect(consoleMsg).toContain('END');
expect(consoleMsg).toContain(EVENT_NAME);

View File

@ -5,98 +5,122 @@
* 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';
import type {EventOptions} from './Types';
import type {Event} from './Types';
const chalk = require('chalk');
const events = require('events');
const _eventStarts = Object.create(null);
const _eventEmitter = new events.EventEmitter();
let ENABLED = true;
let UUID = 1;
let _uuid = 1;
let _enabled = true;
const EVENT_INDEX: {[key: number]: Event} = Object.create(null);
const EVENT_EMITTER = new events.EventEmitter();
function endEvent(eventId) {
const eventEndTime = Date.now();
if (!_eventStarts[eventId]) {
throw new Error('event(' + eventId + ') either ended or never started');
function startEvent(
name: string,
data: any = null,
options?: EventOptions = {telemetric: false},
): number {
if (name == null) {
throw new Error('No event name specified!');
}
_writeAction({
action: 'endEvent',
eventId: eventId,
tstamp: eventEndTime
});
}
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,
const id = UUID++;
EVENT_INDEX[id] = {
id,
startTimeStamp: Date.now(),
name,
data,
options,
};
_eventStarts[eventId] = action;
_writeAction(action);
return eventId;
logEvent(id, 'startEvent');
return id;
}
function disable() {
_enabled = false;
function endEvent(id: number): void {
getEvent(id).endTimeStamp = Date.now();
logEvent(id, 'endEvent');
}
function _writeAction(action) {
_eventEmitter.emit(action.action, action);
function getEvent(id: number): Event {
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;
}
const data = action.data ? ': ' + JSON.stringify(action.data) : '';
const fmtTime = new Date(action.tstamp).toLocaleTimeString();
const {
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':
console.log(chalk.dim(
'[' + fmtTime + '] ' +
'<START> ' + action.eventName +
data
));
// eslint-disable-next-line no-console-disallow
console.log(
chalk.dim(
'[' + new Date(startTimeStamp).toLocaleString() + '] ' +
'<START> ' + name + dataString
)
);
break;
case 'endEvent':
const startAction = _eventStarts[action.eventId];
const startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : '';
console.log(chalk.dim(
'[' + fmtTime + '] ' +
'<END> ' + startAction.eventName +
' (' + (action.tstamp - startAction.tstamp) + 'ms)' +
startData
));
delete _eventStarts[action.eventId];
// eslint-disable-next-line no-console-disallow
console.log(
chalk.dim('[' + new Date(endTimeStamp).toLocaleString() + '] ' + '<END> ' + name) +
chalk.dim(dataString) +
(telemetric ? chalk.reset.cyan(' (' + (duration) + 'ms)') : chalk.dim(' (' + (duration) + 'ms)'))
);
forgetEvent(id);
break;
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;
exports.startEvent = startEvent;
exports.disable = disable;
exports.eventEmitter = _eventEmitter;
function disable(): void {
ENABLED = false;
}
module.exports = {
startEvent,
endEvent,
getEvent,
forgetEvent,
enable,
disable,
eventEmitter: EVENT_EMITTER,
};

View File

@ -356,7 +356,13 @@ class Bundler {
onModuleTransformed = noop,
finalizeBundle = noop,
}) {
const findEventId = Activity.startEvent('find dependencies');
const findEventId = Activity.startEvent(
'Finding dependencies',
null,
{
telemetric: true,
},
);
const modulesByName = Object.create(null);
if (!resolutionResponse) {

View File

@ -485,7 +485,7 @@ class Server {
_processAssetsRequest(req, res) {
const urlObj = url.parse(req.url, true);
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)
.then(
data => res.end(this._rangeRequestMiddleware(req, res, data, assetPath)),
@ -610,7 +610,15 @@ class Server {
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);
debug('Getting bundle for request');
const building = this._useCachedOrUpdateOrCreateBundle(options);
@ -661,7 +669,13 @@ class Server {
}
_symbolicate(req, res) {
const startReqEventId = Activity.startEvent('symbolicate');
const startReqEventId = Activity.startEvent(
'Symbolicating',
null,
{
telemetric: true,
},
);
new Promise.resolve(req.rawBody).then(body => {
const stack = JSON.parse(body).stack;

View File

@ -1,4 +1,4 @@
/**
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
@ -60,6 +60,10 @@ class DeprecatedAssetMap {
if (activity) {
processAsset_DEPRECATEDActivity = activity.startEvent(
'Building (deprecated) Asset Map',
null,
{
telemetric: true,
},
);
}

View File

@ -46,7 +46,13 @@ class Fastfs extends EventEmitter {
let fastfsActivity;
const activity = this._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 => {
const root = this._getRoot(filePath);

View File

@ -1,4 +1,4 @@
/**
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
@ -91,8 +91,20 @@ class DependencyGraph {
}
const {activity} = this._opts;
const depGraphActivity = activity.startEvent('Building Dependency Graph');
const crawlActivity = activity.startEvent('Crawling File System');
const depGraphActivity = activity.startEvent(
'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);
this._crawling = crawl(allRoots, {
ignore: this._opts.ignoreFilePath,
@ -148,7 +160,13 @@ class DependencyGraph {
this._loading = Promise.all([
this._fastfs.build()
.then(() => {
const hasteActivity = activity.startEvent('Building Haste Map');
const hasteActivity = activity.startEvent(
'Building Haste Map',
null,
{
telemetric: true,
},
);
return this._hasteMap.build().then(map => {
activity.endEvent(hasteActivity);
return map;