Instrumentation API

Reviewed By: cpojer

Differential Revision: D4074413

fbshipit-source-id: fd2dff17168f426680fd9dc8456f8b1ae3f55318
This commit is contained in:
Ovidiu Viorel Iepure 2016-11-03 07:41:55 -07:00 committed by Facebook Github Bot
parent cda4129dd3
commit 4d23c1f423
19 changed files with 335 additions and 458 deletions

View File

@ -10,11 +10,11 @@
require('../babelRegisterOnly')([/react-packager\/src/]); require('../babelRegisterOnly')([/react-packager\/src/]);
var debug = require('debug'); const debug = require('debug');
var Activity = require('./src/Activity'); const Logger = require('./src/Logger');
exports.createServer = createServer; exports.createServer = createServer;
exports.Activity = Activity; exports.Logger = Logger;
exports.getOrderedDependencyPaths = function(options, bundleOptions) { exports.getOrderedDependencyPaths = function(options, bundleOptions) {
var server = createNonPersistentServer(options); var server = createNonPersistentServer(options);
return server.getOrderedDependencyPaths(bundleOptions) return server.getOrderedDependencyPaths(bundleOptions)
@ -50,7 +50,7 @@ function createServer(options) {
} }
function createNonPersistentServer(options) { function createNonPersistentServer(options) {
Activity.disable(); Logger.disablePrinting();
// Don't start the filewatcher or the cache. // Don't start the filewatcher or the cache.
if (options.nonPersistent == null) { if (options.nonPersistent == null) {
options.nonPersistent = true; options.nonPersistent = true;

View File

@ -1,37 +0,0 @@
/**
* 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 Options = {
telemetric?: boolean,
silent?: boolean,
displayFields?: Array<string> | true,
};
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,
session: string,
startTimeStamp: [number, number],
};

View File

@ -1,101 +0,0 @@
/**
* 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.
*/
'use strict';
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;
});
describe('startEvent', () => {
it('writes the "START" phase of non-silent events to the console', () => {
const EVENT_NAME = 'EVENT_NAME';
const DATA = {someData: 42};
Activity.startEvent(EVENT_NAME, DATA, {displayFields: ['someData']});
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);
expect(consoleMsg).toContain('someData: 42');
});
it('does not write the "START" phase of silent events to the console', () => {
const EVENT_NAME = 'EVENT_NAME';
const DATA = {someData: 42};
Activity.startEvent(EVENT_NAME, DATA, {silent: true});
jest.runOnlyPendingTimers();
// eslint-disable-next-line no-console-disallow
expect(console.log.mock.calls.length).toBe(0);
});
});
describe('endEvent', () => {
it('writes the "END" phase of non-silent events to the console', () => {
const EVENT_NAME = 'EVENT_NAME';
const DATA = {someData: 42};
const eventID = Activity.startEvent(EVENT_NAME, DATA, {displayFields: ['someData']});
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);
expect(consoleMsg).toContain('someData: 42');
});
it('does not write the "END" phase of silent events to the console', () => {
const EVENT_NAME = 'EVENT_NAME';
const DATA = {someData: 42};
const eventID = Activity.startEvent(EVENT_NAME, DATA, {silent: true});
Activity.endEvent(eventID);
jest.runOnlyPendingTimers();
// eslint-disable-next-line no-console-disallow
expect(console.log.mock.calls.length).toBe(0);
});
it('throws when called with an invalid eventId', () => {
expect(() => Activity.endEvent(42)).toThrow();
});
it('throws when called with an expired eventId', () => {
const eid = Activity.startEvent('', '');
Activity.endEvent(eid);
expect(() => {
Activity.endEvent(eid);
}).toThrow();
});
});
});

View File

@ -1,42 +0,0 @@
/**
* 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

@ -1,125 +0,0 @@
/**
* 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, EventData, Options} from './Types';
const chalk = require('chalk');
const events = require('events');
const formatData = require('./formatData');
const normaliseEventData = require('./normaliseEventData');
const os = require('os');
let ENABLED = true;
let UUID = 1;
const session = `${os.hostname()}-${Date.now()}`;
const EVENT_INDEX: {[key: number]: Event} = Object.create(null);
const EVENT_EMITTER = new events.EventEmitter();
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,
options,
name,
session,
startTimeStamp: process.hrtime(),
};
logEvent(id, 'startEvent');
return id;
}
function endEvent(id: number): void {
const event = getEvent(id);
const delta = process.hrtime(event.startTimeStamp);
event.durationMs = Math.round((delta[0] * 1e9 + delta[1]) / 1e6);
logEvent(id, 'endEvent');
}
function getEvent(id: number): Event {
if (!EVENT_INDEX[id]) {
throw new Error(`Event(${id}) either ended or never started`);
}
return EVENT_INDEX[id];
}
function forgetEvent(id: number): void {
delete EVENT_INDEX[id];
}
function logEvent(id: number, phase: 'startEvent' | 'endEvent'): void {
const event = getEvent(id);
EVENT_EMITTER.emit(phase, id);
if (!ENABLED) {
return;
}
const {
name,
durationMs,
options,
} = event;
const logTimeStamp = new Date().toLocaleString();
const dataString = formatData(event);
const {telemetric, silent} = options;
switch (phase) {
case 'startEvent':
if (!silent) {
// eslint-disable-next-line no-console-disallow
console.log(chalk.dim(`[${logTimeStamp}] <START> ${name}${dataString}`));
}
break;
case 'endEvent':
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)`))
);
}
forgetEvent(id);
break;
default:
throw new Error(`Unexpected event phase "${phase}"!`);
}
}
function enable(): void {
ENABLED = true;
}
function disable(): void {
ENABLED = false;
}
module.exports = {
startEvent,
endEvent,
getEvent,
forgetEvent,
enable,
disable,
eventEmitter: EVENT_EMITTER,
};

View File

@ -1,41 +0,0 @@
/**
* 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

@ -24,7 +24,7 @@ jest
.mock('../Bundle') .mock('../Bundle')
.mock('../PrepackBundle') .mock('../PrepackBundle')
.mock('../HMRBundle') .mock('../HMRBundle')
.mock('../../Activity') .mock('../../Logger')
.mock('../../lib/declareOpts'); .mock('../../lib/declareOpts');
var Bundler = require('../'); var Bundler = require('../');

View File

@ -19,7 +19,6 @@ const Resolver = require('../Resolver');
const Bundle = require('./Bundle'); const Bundle = require('./Bundle');
const HMRBundle = require('./HMRBundle'); const HMRBundle = require('./HMRBundle');
const PrepackBundle = require('./PrepackBundle'); const PrepackBundle = require('./PrepackBundle');
const Activity = require('../Activity');
const ModuleTransport = require('../lib/ModuleTransport'); const ModuleTransport = require('../lib/ModuleTransport');
const declareOpts = require('../lib/declareOpts'); const declareOpts = require('../lib/declareOpts');
const imageSize = require('image-size'); const imageSize = require('image-size');
@ -29,6 +28,13 @@ const sizeOf = Promise.denodeify(imageSize);
const noop = () => {}; const noop = () => {};
const {
createActionStartEntry,
createActionEndEntry,
log,
print,
} = require('../Logger');
const validateOpts = declareOpts({ const validateOpts = declareOpts({
projectRoots: { projectRoots: {
type: 'array', type: 'array',
@ -364,16 +370,13 @@ class Bundler {
finalizeBundle = noop, finalizeBundle = noop,
onProgress = noop, onProgress = noop,
}) { }) {
const findEventId = Activity.startEvent( const transformingFilesLogEntry =
'Transforming modules', print(log(createActionStartEntry({
{ action_name: 'Transforming files',
entry_point: entryFile, entry_point: entryFile,
environment: dev ? 'dev' : 'prod', environment: dev ? 'dev' : 'prod',
}, })));
{
telemetric: true,
},
);
const modulesByName = Object.create(null); const modulesByName = Object.create(null);
if (!resolutionResponse) { if (!resolutionResponse) {
@ -392,7 +395,7 @@ class Bundler {
return Promise.resolve(resolutionResponse).then(response => { return Promise.resolve(resolutionResponse).then(response => {
bundle.setRamGroups(response.transformOptions.transform.ramGroups); bundle.setRamGroups(response.transformOptions.transform.ramGroups);
Activity.endEvent(findEventId); print(log(createActionEndEntry(transformingFilesLogEntry)));
onResolutionResponse(response); onResolutionResponse(response);
// get entry file complete path (`entryFile` is relative to roots) // get entry file complete path (`entryFile` is relative to roots)

View File

@ -8,8 +8,8 @@
*/ */
'use strict'; 'use strict';
const Activity = require('../Activity');
const Promise = require('promise'); const Promise = require('promise');
const declareOpts = require('../lib/declareOpts'); const declareOpts = require('../lib/declareOpts');
const os = require('os'); const os = require('os');
const util = require('util'); const util = require('util');
@ -112,21 +112,10 @@ class Transformer {
return Promise.reject(new Error('No transform module')); return Promise.reject(new Error('No transform module'));
} }
debug('transforming file', fileName); debug('transforming file', fileName);
const transformEventId = Activity.startEvent(
'Transforming file',
{
file_name: fileName,
},
{
telemetric: true,
silent: true,
},
);
return this return this
._transform(this._transformModulePath, fileName, code, options) ._transform(this._transformModulePath, fileName, code, options)
.then(result => { .then(result => {
debug('done transforming file', fileName); debug('done transforming file', fileName);
Activity.endEvent(transformEventId);
return result; return result;
}) })
.catch(error => { .catch(error => {

33
react-packager/src/Logger/Types.js vendored Normal file
View File

@ -0,0 +1,33 @@
/**
* 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 ActionLogEntryData = {
action_name: string,
};
export type ActionStartLogEntry = {
action_name?: string,
action_phase?: string,
log_entry_label: string,
log_session?: string,
start_timestamp?: [number, number],
};
export type LogEntry = {
action_name?: string,
action_phase?: string,
duration_ms?: number,
log_entry_label: string,
log_session?: string,
start_timestamp?: [number, number],
};

View File

@ -0,0 +1,65 @@
/**
* 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.
*
* eslint-disable no-console-disallow
*
*/
'use strict';
jest.disableAutomock();
const {
createEntry,
createActionStartEntry,
createActionEndEntry,
enablePrinting,
} = require('../');
describe('Logger', () => {
const originalConsoleLog = console.log;
beforeEach(() => {
console.log = jest.fn();
});
afterEach(() => {
console.log = originalConsoleLog;
enablePrinting();
});
it('creates simple log entries', () => {
const logEntry = createEntry('Test');
expect(logEntry).toEqual({
log_entry_label: 'Test',
log_session: jasmine.any(String),
});
});
it('creates action start log entries', () => {
const actionStartLogEntry = createActionStartEntry('Test');
expect(actionStartLogEntry).toEqual({
action_name: 'Test',
action_phase: 'start',
log_entry_label: 'Test',
log_session: jasmine.any(String),
start_timestamp: jasmine.any(Object),
});
});
it('creates action end log entries', () => {
const actionEndLogEntry = createActionEndEntry(createActionStartEntry('Test'));
expect(actionEndLogEntry).toEqual({
action_name: 'Test',
action_phase: 'end',
duration_ms: jasmine.any(Number),
log_entry_label: 'Test',
log_session: jasmine.any(String),
start_timestamp: jasmine.any(Object),
});
});
});

147
react-packager/src/Logger/index.js vendored Normal file
View File

@ -0,0 +1,147 @@
/**
* 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 {
ActionLogEntryData,
ActionStartLogEntry,
LogEntry,
} from './Types';
const {EventEmitter} = require('events');
const chalk = require('chalk');
const os = require('os');
let PRINT_LOG_ENTRIES = true;
const log_session = `${os.hostname()}-${Date.now()}`;
const eventEmitter = new EventEmitter();
function on(event: string, handler: (logEntry: LogEntry) => void): void {
eventEmitter.on(event, handler);
}
function createEntry(data: LogEntry | string): LogEntry {
const logEntry = typeof data === 'string' ? {log_entry_label: data} : data;
return {
...logEntry,
log_session,
};
}
function createActionStartEntry(data: ActionLogEntryData | string): LogEntry {
const logEntry = typeof data === 'string' ? {action_name: data} : data;
const {action_name} = logEntry;
return createEntry({
...logEntry,
action_name,
action_phase: 'start',
log_entry_label: action_name,
start_timestamp: process.hrtime(),
});
}
function createActionEndEntry(logEntry: ActionStartLogEntry): LogEntry {
const {
action_name,
action_phase,
start_timestamp,
} = logEntry;
if (action_phase !== 'start' || !Array.isArray(start_timestamp)) {
throw new Error('Action has not started or has already ended');
}
const timeDelta = process.hrtime(start_timestamp);
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
return createEntry({
...logEntry,
action_name,
action_phase: 'end',
duration_ms,
log_entry_label: action_name,
});
}
function log(logEntry: LogEntry): LogEntry {
eventEmitter.emit('log', logEntry);
return logEntry;
}
function print(
logEntry: LogEntry,
printFields?: Array<string> = [],
): LogEntry {
if (!PRINT_LOG_ENTRIES) {
return logEntry;
}
const {log_entry_label, action_phase, duration_ms} = logEntry;
const timeStamp = new Date().toLocaleString();
const logEntryDataList = [];
let logEntryString, logEntryDataString;
for (let i = 0, len = printFields.length; i < len; i++) {
const field = printFields[i];
const value = logEntry[field];
if (value === undefined) {
continue;
}
logEntryDataList.push(`${field}: ${value.toString()}`);
}
logEntryDataString = logEntryDataList.join(' | ');
if (logEntryDataString) {
logEntryDataString = ` ${logEntryDataString}`;
}
switch (action_phase) {
case 'start':
logEntryString = chalk.dim(`[${timeStamp}] <START> ${log_entry_label}${logEntryDataString}`);
break;
case 'end':
logEntryString = chalk.dim(`[${timeStamp}] <END> ${log_entry_label}${logEntryDataString}`) +
chalk.cyan(` (${+duration_ms}ms)`);
break;
default:
logEntryString = chalk.dim(`[${timeStamp}] ${log_entry_label}${logEntryDataString}`);
break;
}
// eslint-disable-next-line no-console-disallow
console.log(logEntryString);
return logEntry;
}
function enablePrinting(): void {
PRINT_LOG_ENTRIES = true;
}
function disablePrinting(): void {
PRINT_LOG_ENTRIES = false;
}
module.exports = {
on,
createEntry,
createActionStartEntry,
createActionEndEntry,
log,
print,
enablePrinting,
disablePrinting,
};

View File

@ -9,7 +9,6 @@
'use strict'; 'use strict';
const Activity = require('../Activity');
const DependencyGraph = require('../node-haste'); const DependencyGraph = require('../node-haste');
const declareOpts = require('../lib/declareOpts'); const declareOpts = require('../lib/declareOpts');
@ -89,7 +88,6 @@ class Resolver {
const opts = validateOpts(options); const opts = validateOpts(options);
this._depGraph = new DependencyGraph({ this._depGraph = new DependencyGraph({
activity: Activity,
roots: opts.projectRoots, roots: opts.projectRoots,
assetRoots_DEPRECATED: opts.assetRoots, assetRoots_DEPRECATED: opts.assetRoots,
assetExts: opts.assetExts, assetExts: opts.assetExts,

View File

@ -19,7 +19,7 @@ jest.setMock('worker-farm', function() { return () => {}; })
.mock('../../AssetServer') .mock('../../AssetServer')
.mock('../../lib/declareOpts') .mock('../../lib/declareOpts')
.mock('../../node-haste') .mock('../../node-haste')
.mock('../../Activity'); .mock('../../Logger');
let FileWatcher; let FileWatcher;

View File

@ -8,7 +8,6 @@
*/ */
'use strict'; 'use strict';
const Activity = require('../Activity');
const AssetServer = require('../AssetServer'); const AssetServer = require('../AssetServer');
const FileWatcher = require('../node-haste').FileWatcher; const FileWatcher = require('../node-haste').FileWatcher;
const getPlatformExtension = require('../node-haste').getPlatformExtension; const getPlatformExtension = require('../node-haste').getPlatformExtension;
@ -26,6 +25,13 @@ const url = require('url');
const debug = require('debug')('ReactNativePackager:Server'); const debug = require('debug')('ReactNativePackager:Server');
const {
createActionStartEntry,
createActionEndEntry,
log,
print,
} = require('../Logger');
function debounceAndBatch(fn, delay) { function debounceAndBatch(fn, delay) {
let timeout, args = []; let timeout, args = [];
return (value) => { return (value) => {
@ -496,15 +502,13 @@ class Server {
_processAssetsRequest(req, res) { _processAssetsRequest(req, res) {
const urlObj = url.parse(decodeURI(req.url), true); const urlObj = url.parse(decodeURI(req.url), true);
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
const assetEvent = Activity.startEvent(
'Processing asset request', const processingAssetRequestLogEntry =
{ print(log(createActionStartEntry({
action_name: 'Processing asset request',
asset: assetPath[1], asset: assetPath[1],
}, })), ['asset']);
{
displayFields: true,
},
);
this._assetServer.get(assetPath[1], urlObj.query.platform) this._assetServer.get(assetPath[1], urlObj.query.platform)
.then( .then(
data => { data => {
@ -518,7 +522,9 @@ class Server {
res.writeHead('404'); res.writeHead('404');
res.end('Asset not found'); res.end('Asset not found');
} }
).done(() => Activity.endEvent(assetEvent)); ).done(() => {
print(log(createActionEndEntry(processingAssetRequestLogEntry)), ['asset']);
});
} }
optionsHash(options) { optionsHash(options) {
@ -539,18 +545,15 @@ class Server {
const deps = bundleDeps.get(bundle); const deps = bundleDeps.get(bundle);
const {dependencyPairs, files, idToIndex, outdated} = deps; const {dependencyPairs, files, idToIndex, outdated} = deps;
if (outdated.size) { if (outdated.size) {
const updateExistingBundleEventId =
Activity.startEvent( const updatingExistingBundleLogEntry =
'Updating existing bundle', print(log(createActionStartEntry({
{ action_name: 'Updating existing bundle',
outdated_modules: outdated.size, outdated_modules: outdated.size,
}, })), ['outdated_modules']);
{
telemetric: true,
displayFields: true,
},
);
debug('Attempt to update existing bundle'); debug('Attempt to update existing bundle');
const changedModules = const changedModules =
Array.from(outdated, this.getModuleForPath, this); Array.from(outdated, this.getModuleForPath, this);
deps.outdated = new Set(); deps.outdated = new Set();
@ -604,8 +607,13 @@ class Server {
} }
bundle.invalidateSource(); bundle.invalidateSource();
print(
log(createActionEndEntry(updatingExistingBundleLogEntry)),
['outdated_modules'],
);
debug('Successfully updated existing bundle'); debug('Successfully updated existing bundle');
Activity.endEvent(updateExistingBundleEventId);
return bundle; return bundle;
}); });
}).catch(e => { }).catch(e => {
@ -652,17 +660,12 @@ class Server {
} }
const options = this._getOptionsFromUrl(req.url); const options = this._getOptionsFromUrl(req.url);
const startReqEventId = Activity.startEvent( const requestingBundleLogEntry =
'Requesting bundle', print(log(createActionStartEntry({
{ action_name: 'Requesting bundle',
url: req.url, bundle_url: req.url,
entry_point: options.entryFile, entry_point: options.entryFile,
}, })), ['bundle_url']);
{
telemetric: true,
displayFields: ['url'],
},
);
let consoleProgress = () => {}; let consoleProgress = () => {};
if (process.stdout.isTTY && !this._opts.silent) { if (process.stdout.isTTY && !this._opts.silent) {
@ -705,7 +708,7 @@ class Server {
mres.end(bundleSource); mres.end(bundleSource);
} }
debug('Finished response'); debug('Finished response');
Activity.endEvent(startReqEventId); print(log(createActionEndEntry(requestingBundleLogEntry)), ['bundle_url']);
} else if (requestType === 'map') { } else if (requestType === 'map') {
let sourceMap = p.getSourceMap({ let sourceMap = p.getSourceMap({
minify: options.minify, minify: options.minify,
@ -718,12 +721,12 @@ class Server {
mres.setHeader('Content-Type', 'application/json'); mres.setHeader('Content-Type', 'application/json');
mres.end(sourceMap); mres.end(sourceMap);
Activity.endEvent(startReqEventId); print(log(createActionEndEntry(requestingBundleLogEntry)), ['bundle_url']);
} else if (requestType === 'assets') { } else if (requestType === 'assets') {
const assetsList = JSON.stringify(p.getAssets()); const assetsList = JSON.stringify(p.getAssets());
mres.setHeader('Content-Type', 'application/json'); mres.setHeader('Content-Type', 'application/json');
mres.end(assetsList); mres.end(assetsList);
Activity.endEvent(startReqEventId); print(log(createActionEndEntry(requestingBundleLogEntry)), ['bundle_url']);
} }
}, },
error => this._handleError(mres, this.optionsHash(options), error) error => this._handleError(mres, this.optionsHash(options), error)
@ -735,13 +738,9 @@ class Server {
} }
_symbolicate(req, res) { _symbolicate(req, res) {
const startReqEventId = Activity.startEvent( const symbolicatingLogEntry =
'Symbolicating', print(log(createActionStartEntry('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;
@ -795,7 +794,7 @@ class Server {
res.end(JSON.stringify({error: error.message})); res.end(JSON.stringify({error: error.message}));
} }
).done(() => { ).done(() => {
Activity.endEvent(startReqEventId); print(log(createActionEndEntry(symbolicatingLogEntry)));
}); });
} }

View File

@ -10,7 +10,9 @@
jest.autoMockOff(); jest.autoMockOff();
jest.useRealTimers(); jest.useRealTimers();
jest.mock('fs'); jest
.mock('fs')
.mock('../../Logger');
// This is an ugly hack: // This is an ugly hack:
// * jest-haste-map uses `find` for fast file system crawling which won't work // * jest-haste-map uses `find` for fast file system crawling which won't work

View File

@ -27,6 +27,13 @@ interface FileWatcher {
on(event: 'all', handler: (type: string, filePath: string, rootPath: string, fstat: fs.Stats) => void): void, on(event: 'all', handler: (type: string, filePath: string, rootPath: string, fstat: fs.Stats) => void): void,
} }
const {
createActionStartEntry,
createActionEndEntry,
log,
print,
} = require('../Logger');
class Fastfs extends EventEmitter { class Fastfs extends EventEmitter {
_name: string; _name: string;
@ -62,18 +69,10 @@ class Fastfs extends EventEmitter {
return new File(root, true); return new File(root, true);
}); });
this._fastPaths = Object.create(null); this._fastPaths = Object.create(null);
this._activity = activity;
let fastfsActivity; const buildingInMemoryFSLogEntry =
if (activity) { print(log(createActionStartEntry('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);
if (root) { if (root) {
@ -88,9 +87,8 @@ class Fastfs extends EventEmitter {
} }
} }
}); });
if (activity) {
activity.endEvent(fastfsActivity); print(log(createActionEndEntry(buildingInMemoryFSLogEntry)));
}
if (this._fileWatcher) { if (this._fileWatcher) {
this._fileWatcher.on('all', this._processFileChange.bind(this)); this._fileWatcher.on('all', this._processFileChange.bind(this));

View File

@ -33,14 +33,15 @@ const util = require('util');
const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError'; const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError';
const defaultActivity = { const {
startEvent: () => {}, createActionStartEntry,
endEvent: () => {}, createActionEndEntry,
}; log,
print,
} = require('../Logger');
class DependencyGraph { class DependencyGraph {
constructor({ constructor({
activity,
roots, roots,
ignoreFilePath, ignoreFilePath,
fileWatcher, fileWatcher,
@ -65,7 +66,6 @@ class DependencyGraph {
resetCache, resetCache,
}) { }) {
this._opts = { this._opts = {
activity: activity || defaultActivity,
roots, roots,
ignoreFilePath: ignoreFilePath || (() => {}), ignoreFilePath: ignoreFilePath || (() => {}),
fileWatcher, fileWatcher,
@ -116,14 +116,8 @@ class DependencyGraph {
}); });
this._loading = haste.build().then(hasteMap => { this._loading = haste.build().then(hasteMap => {
const {activity} = this._opts; const initializingPackagerLogEntry =
const depGraphActivity = activity.startEvent( print(log(createActionStartEntry('Initializing Packager')));
'Initializing Packager',
null,
{
telemetric: true,
},
);
const hasteFSFiles = hasteMap.hasteFS.getAllFiles(); const hasteFSFiles = hasteMap.hasteFS.getAllFiles();
@ -134,7 +128,6 @@ class DependencyGraph {
hasteFSFiles, hasteFSFiles,
{ {
ignore: this._opts.ignoreFilePath, ignore: this._opts.ignoreFilePath,
activity: activity,
} }
); );
@ -181,17 +174,13 @@ class DependencyGraph {
} }
}); });
const hasteActivity = activity.startEvent( const buildingHasteMapLogEntry =
'Building Haste Map', print(log(createActionStartEntry('Building Haste Map')));
null,
{
telemetric: true,
},
);
return this._hasteMap.build().then( return this._hasteMap.build().then(
map => { map => {
activity.endEvent(hasteActivity); print(log(createActionEndEntry(buildingHasteMapLogEntry)));
activity.endEvent(depGraphActivity); print(log(createActionEndEntry(initializingPackagerLogEntry)));
return map; return map;
}, },
err => { err => {