Add details to Activity events

Summary: Update Activity API to allow adding more details to events for telemetry purposes.

Reviewed By: davidaurelio

Differential Revision: D3982691

fbshipit-source-id: 07f3ed5d1ec4eddbbdeb00feb02ea75e1168705e
This commit is contained in:
Ovidiu Viorel Iepure 2016-10-12 10:35:57 -07:00 committed by Facebook Github Bot
parent 308ab1001e
commit dc0f7875c8
8 changed files with 137 additions and 35 deletions

View File

@ -11,16 +11,26 @@
*/
'use strict';
export type EventOptions = {
export type Options = {
telemetric?: boolean,
silent?: boolean,
displayFields?: Array<string> | true,
};
export type Event = {
id: number,
startTimeStamp: [number, number],
durationMs?: number,
name: string,
data?: any,
options: EventOptions,
type EventFieldDescriptor = {
type: 'int' | 'normal',
value: number | string | boolean,
};
export type NormalisedEventData = {[key: string]: EventFieldDescriptor};
export type EventData = {[key: string]: number | string | boolean};
export type Event = {
data: NormalisedEventData,
durationMs?: number,
id: number,
name: string,
options: Options,
startTimeStamp: [number, number],
};

View File

@ -32,7 +32,7 @@ describe('Activity', () => {
const EVENT_NAME = 'EVENT_NAME';
const DATA = {someData: 42};
Activity.startEvent(EVENT_NAME, DATA);
Activity.startEvent(EVENT_NAME, DATA, {displayFields: ['someData']});
jest.runOnlyPendingTimers();
// eslint-disable-next-line no-console-disallow
@ -41,7 +41,7 @@ describe('Activity', () => {
const consoleMsg = console.log.mock.calls[0][0];
expect(consoleMsg).toContain('START');
expect(consoleMsg).toContain(EVENT_NAME);
expect(consoleMsg).toContain(JSON.stringify(DATA));
expect(consoleMsg).toContain('someData: 42');
});
it('does not write the "START" phase of silent events to the console', () => {
@ -61,7 +61,7 @@ describe('Activity', () => {
const EVENT_NAME = 'EVENT_NAME';
const DATA = {someData: 42};
const eventID = Activity.startEvent(EVENT_NAME, DATA);
const eventID = Activity.startEvent(EVENT_NAME, DATA, {displayFields: ['someData']});
Activity.endEvent(eventID);
jest.runOnlyPendingTimers();
@ -71,7 +71,7 @@ describe('Activity', () => {
const consoleMsg = console.log.mock.calls[1][0];
expect(consoleMsg).toContain('END');
expect(consoleMsg).toContain(EVENT_NAME);
expect(consoleMsg).toContain(JSON.stringify(DATA));
expect(consoleMsg).toContain('someData: 42');
});
it('does not write the "END" phase of silent events to the console', () => {

View File

@ -0,0 +1,42 @@
/**
* 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';
import type {Event} from './Types';
function getDataString(event: Event): string {
const {options, data} = event;
const {displayFields} = options;
if (!Object.keys(data).length ||
!(Array.isArray(displayFields) || displayFields === true)) {
return '';
}
const fields = Array.isArray(displayFields) ? displayFields : Object.keys(data);
const dataList = fields.map(field => {
if (data[field] === undefined) {
throw new Error(`"${field}" is not defined for event ""${event.name}"!`);
}
return `${field}: ${data[field].value.toString()}`;
});
let dataString = dataList.join(' | ');
if (dataString) {
dataString = ` ${dataString} `;
}
return dataString;
}
module.exports = getDataString;

View File

@ -11,11 +11,12 @@
*/
'use strict';
import type {EventOptions} from './Types';
import type {Event} from './Types';
import type {Event, EventData, Options} from './Types';
const chalk = require('chalk');
const events = require('events');
const formatData = require('./formatData');
const normaliseEventData = require('./normaliseEventData');
let ENABLED = true;
let UUID = 1;
@ -23,24 +24,21 @@ let UUID = 1;
const EVENT_INDEX: {[key: number]: Event} = Object.create(null);
const EVENT_EMITTER = new events.EventEmitter();
function startEvent(
name: string,
data: any = null,
options?: EventOptions = {},
): number {
function startEvent(name: string, data: EventData = {}, options: Options = {}): number {
if (name == null) {
throw new Error('No event name specified!');
}
const id = UUID++;
EVENT_INDEX[id] = {
data: normaliseEventData(data),
id,
startTimeStamp: process.hrtime(),
name,
data,
options,
name,
startTimeStamp: process.hrtime(),
};
logEvent(id, 'startEvent');
return id;
}
@ -74,12 +72,11 @@ function logEvent(id: number, phase: 'startEvent' | 'endEvent'): void {
const {
name,
durationMs,
data,
options,
} = event;
const logTimeStamp = new Date().toLocaleString();
const dataString = data ? ': ' + JSON.stringify(data) : '';
const dataString = formatData(event);
const {telemetric, silent} = options;
switch (phase) {
@ -94,15 +91,15 @@ function logEvent(id: number, phase: 'startEvent' | 'endEvent'): void {
if (!silent) {
// eslint-disable-next-line no-console-disallow
console.log(
chalk.dim(`[${logTimeStamp}] <END> ${name}${dataString} `) +
(telemetric ? chalk.reset.cyan(`(${+durationMs}ms)`) : chalk.dim(`(${+durationMs}ms)`))
chalk.dim(`[${logTimeStamp}] <END> ${name}${dataString}`) +
(telemetric ? chalk.reset.cyan(` (${+durationMs}ms)`) : chalk.dim(` (${+durationMs}ms)`))
);
}
forgetEvent(id);
break;
default:
throw new Error('Unexpected scheduled event type: ' + name);
throw new Error(`Unexpected event phase "${phase}"!`);
}
}

View File

@ -0,0 +1,41 @@
/**
* 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';
import type {EventData, NormalisedEventData} from './Types';
function normaliseEventData(eventData: EventData): NormalisedEventData {
if (!eventData) {
return {};
}
const normalisedEventData = {};
Object.keys(eventData).forEach(field => {
const value = eventData[field];
let type;
if (typeof value === 'string' || typeof value === 'boolean') {
type = 'normal';
} else if (typeof value === 'number') {
type = 'int';
} else {
throw new Error(`Disallowed value for event field "${field}""!`);
}
normalisedEventData[field] = {type, value};
});
return normalisedEventData;
}
module.exports = normaliseEventData;

View File

@ -366,11 +366,12 @@ class Bundler {
}) {
const findEventId = Activity.startEvent(
'Transforming modules',
null,
{
entry_point: entryFile,
environment: dev ? 'dev' : 'prod',
},
{
telemetric: true,
entryPoint: entryFile,
details: dev ? 'dev' : 'prod',
},
);
const modulesByName = Object.create(null);

View File

@ -114,7 +114,9 @@ class Transformer {
debug('transforming file', fileName);
const transformEventId = Activity.startEvent(
'Transforming file',
fileName,
{
file_name: fileName,
},
{
telemetric: true,
silent: true,

View File

@ -499,7 +499,15 @@ class Server {
_processAssetsRequest(req, res) {
const urlObj = url.parse(decodeURI(req.url), true);
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
const assetEvent = Activity.startEvent('Processing asset request', {asset: assetPath[1]});
const assetEvent = Activity.startEvent(
'Processing asset request',
{
asset: assetPath[1],
},
{
displayFields: true,
},
);
this._assetServer.get(assetPath[1], urlObj.query.platform)
.then(
data => {
@ -538,10 +546,11 @@ class Server {
Activity.startEvent(
'Updating existing bundle',
{
outdatedModules: outdated.size,
outdated_modules: outdated.size,
},
{
telemetric: true,
displayFields: true,
},
);
debug('Attempt to update existing bundle');
@ -650,11 +659,11 @@ class Server {
'Requesting bundle',
{
url: req.url,
entry_point: options.entryFile,
},
{
telemetric: true,
entryPoint: options.entryFile,
details: req.url,
displayFields: ['url'],
},
);