[DevTools] Add initial APIs for logging instrumentation events under feature flag (#22276)

This commit is contained in:
Juan
2021-09-14 11:10:24 -04:00
committed by GitHub
parent 9a4cfc4005
commit b43d3a26ab
13 changed files with 228 additions and 104 deletions
+5
View File
@@ -18,6 +18,7 @@ import {
localStorageRemoveItem,
localStorageSetItem,
} from 'react-devtools-shared/src/storage';
import {registerEventLogger} from 'react-devtools-shared/src/Logger';
import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
import {__DEBUG__} from 'react-devtools-shared/src/constants';
@@ -87,6 +88,10 @@ function createPanelIfReactLoaded() {
const tabId = chrome.devtools.inspectedWindow.tabId;
registerEventLogger((event: LogEvent) => {
// TODO: hook up event logging
});
function initBridgeAndStore() {
const port = chrome.runtime.connect({
name: '' + tabId,
+47
View File
@@ -0,0 +1,47 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
*/
import {enableLogger} from 'react-devtools-feature-flags';
type LoadHookNamesEvent = {|
+name: 'loadHookNames',
+displayName: string | null,
+numberOfHooks: number | null,
+durationMs: number,
+resolution: 'success' | 'error' | 'timeout' | 'unknown',
|};
// prettier-ignore
export type LogEvent =
| LoadHookNamesEvent;
export type LogFunction = LogEvent => void;
let loggers: Array<LogFunction> = [];
export const logEvent: LogFunction =
enableLogger === true
? function logEvent(event: LogEvent): void {
loggers.forEach(log => {
log(event);
});
}
: function logEvent() {};
export const registerEventLogger =
enableLogger === true
? function registerEventLogger(eventLogger: LogFunction): () => void {
if (enableLogger) {
loggers.push(eventLogger);
return function unregisterEventLogger() {
loggers = loggers.filter(logger => logger !== eventLogger);
};
}
return () => {};
}
: function registerEventLogger() {};
@@ -14,6 +14,9 @@ const supportsUserTiming =
typeof performance.mark === 'function' &&
typeof performance.clearMarks === 'function';
const supportsPerformanceNow =
typeof performance !== 'undefined' && typeof performance.now === 'function';
function mark(markName: string): void {
if (supportsUserTiming) {
performance.mark(markName + '-start');
@@ -27,42 +30,78 @@ function measure(markName: string): void {
}
}
export async function withAsyncPerformanceMark<TReturn>(
function now(): number {
if (supportsPerformanceNow) {
return performance.now();
}
return Date.now();
}
export async function withAsyncPerfMeasurements<TReturn>(
markName: string,
callback: () => Promise<TReturn>,
onComplete?: number => void,
): Promise<TReturn> {
const start = now();
if (__PERFORMANCE_PROFILE__) {
mark(markName);
const result = await callback();
measure(markName);
return result;
}
return callback();
const result = await callback();
if (__PERFORMANCE_PROFILE__) {
measure(markName);
}
if (onComplete != null) {
const duration = now() - start;
onComplete(duration);
}
return result;
}
export function withSyncPerformanceMark<TReturn>(
export function withSyncPerfMeasurements<TReturn>(
markName: string,
callback: () => TReturn,
onComplete?: number => void,
): TReturn {
const start = now();
if (__PERFORMANCE_PROFILE__) {
mark(markName);
const result = callback();
measure(markName);
return result;
}
return callback();
const result = callback();
if (__PERFORMANCE_PROFILE__) {
measure(markName);
}
if (onComplete != null) {
const duration = now() - start;
onComplete(duration);
}
return result;
}
export function withCallbackPerformanceMark<TReturn>(
export function withCallbackPerfMeasurements<TReturn>(
markName: string,
callback: (done: () => void) => TReturn,
onComplete?: number => void,
): TReturn {
const start = now();
if (__PERFORMANCE_PROFILE__) {
mark(markName);
const done = () => {
measure(markName);
};
return callback(done);
}
return callback(() => {});
const done = () => {
if (__PERFORMANCE_PROFILE__) {
measure(markName);
}
if (onComplete != null) {
const duration = now() - start;
onComplete(duration);
}
};
return callback(done);
}
@@ -16,6 +16,7 @@
export const enableProfilerChangedHookIndices = true;
export const isInternalFacebookBuild = true;
export const enableNamedHooksFeature = false;
export const enableLogger = false;
export const consoleManagedByDevToolsDuringStrictMode = false;
/************************************************************************
@@ -16,6 +16,7 @@
export const enableProfilerChangedHookIndices = false;
export const isInternalFacebookBuild = false;
export const enableNamedHooksFeature = false;
export const enableLogger = false;
export const consoleManagedByDevToolsDuringStrictMode = false;
/************************************************************************
@@ -16,4 +16,5 @@
export const enableProfilerChangedHookIndices = false;
export const isInternalFacebookBuild = false;
export const enableNamedHooksFeature = true;
export const enableLogger = false;
export const consoleManagedByDevToolsDuringStrictMode = true;
@@ -16,6 +16,7 @@
export const enableProfilerChangedHookIndices = true;
export const isInternalFacebookBuild = true;
export const enableNamedHooksFeature = true;
export const enableLogger = false;
export const consoleManagedByDevToolsDuringStrictMode = true;
/************************************************************************
@@ -16,6 +16,7 @@
export const enableProfilerChangedHookIndices = true;
export const isInternalFacebookBuild = false;
export const enableNamedHooksFeature = true;
export const enableLogger = false;
export const consoleManagedByDevToolsDuringStrictMode = true;
/************************************************************************
+78 -50
View File
@@ -18,6 +18,8 @@ import type {
} from 'react-devtools-shared/src/types';
import type {HookSource} from 'react-debug-tools/src/ReactDebugHooks';
import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
import {withCallbackPerfMeasurements} from './PerformanceLoggingUtils';
import {logEvent} from './Logger';
const TIMEOUT = 30000;
@@ -92,6 +94,11 @@ export function loadHookNames(
},
};
let timeoutID;
let didTimeout = false;
let resolution = 'unknown';
let resolvedHookNames: HookNames | null = null;
const wake = () => {
if (timeoutID) {
clearTimeout(timeoutID);
@@ -103,71 +110,92 @@ export function loadHookNames(
callbacks.clear();
};
const handleLoadComplete = (durationMs: number): void => {
// Log duration for parsing hook names
logEvent({
name: 'loadHookNames',
displayName: element.displayName,
numberOfHooks: resolvedHookNames?.size ?? null,
durationMs,
resolution,
});
};
const newRecord: Record<HookNames> = (record = {
status: Pending,
value: wakeable,
});
let didTimeout = false;
withCallbackPerfMeasurements(
'loadHookNames',
done => {
loadHookNamesFunction(hooksTree, fetchFileWithCaching).then(
function onSuccess(hookNames) {
if (didTimeout) {
return;
}
loadHookNamesFunction(hooksTree, fetchFileWithCaching).then(
function onSuccess(hookNames) {
if (didTimeout) {
return;
}
if (__DEBUG__) {
console.log('[hookNamesCache] onSuccess() hookNames:', hookNames);
}
if (__DEBUG__) {
console.log('[hookNamesCache] onSuccess() hookNames:', hookNames);
}
if (hookNames) {
const resolvedRecord = ((newRecord: any): ResolvedRecord<HookNames>);
resolvedRecord.status = Resolved;
resolvedRecord.value = hookNames;
} else {
const notFoundRecord = ((newRecord: any): RejectedRecord);
notFoundRecord.status = Rejected;
notFoundRecord.value = null;
}
if (hookNames) {
const resolvedRecord = ((newRecord: any): ResolvedRecord<HookNames>);
resolvedRecord.status = Resolved;
resolvedRecord.value = hookNames;
} else {
const notFoundRecord = ((newRecord: any): RejectedRecord);
notFoundRecord.status = Rejected;
notFoundRecord.value = null;
}
resolution = 'success';
resolvedHookNames = hookNames;
done();
wake();
},
function onError(error) {
if (didTimeout) {
return;
}
wake();
},
function onError(error) {
if (didTimeout) {
return;
}
if (__DEBUG__) {
console.log('[hookNamesCache] onError()');
}
if (__DEBUG__) {
console.log('[hookNamesCache] onError()');
}
console.error(error);
console.error(error);
const thrownRecord = ((newRecord: any): RejectedRecord);
thrownRecord.status = Rejected;
thrownRecord.value = null;
const thrownRecord = ((newRecord: any): RejectedRecord);
thrownRecord.status = Rejected;
thrownRecord.value = null;
resolution = 'error';
done();
wake();
},
);
wake();
// Eventually timeout and stop trying to load names.
timeoutID = setTimeout(function onTimeout() {
if (__DEBUG__) {
console.log('[hookNamesCache] onTimeout()');
}
timeoutID = null;
didTimeout = true;
const timedoutRecord = ((newRecord: any): RejectedRecord);
timedoutRecord.status = Rejected;
timedoutRecord.value = null;
resolution = 'timeout';
done();
wake();
}, TIMEOUT);
},
handleLoadComplete,
);
// Eventually timeout and stop trying to load names.
let timeoutID = setTimeout(function onTimeout() {
if (__DEBUG__) {
console.log('[hookNamesCache] onTimeout()');
}
timeoutID = null;
didTimeout = true;
const timedoutRecord = ((newRecord: any): RejectedRecord);
timedoutRecord.status = Rejected;
timedoutRecord.value = null;
wake();
}, TIMEOUT);
map.set(element, record);
}
+5 -5
View File
@@ -7,7 +7,7 @@
* @flow
*/
import {withSyncPerformanceMark} from 'react-devtools-shared/src/PerformanceMarks';
import {withSyncPerfMeasurements} from 'react-devtools-shared/src/PerformanceLoggingUtils';
import traverse, {NodePath, Node} from '@babel/traverse';
import {File} from '@babel/types';
@@ -132,7 +132,7 @@ export function getHookName(
originalSourceLineNumber: number,
originalSourceColumnNumber: number,
): string | null {
const hooksFromAST = withSyncPerformanceMark(
const hooksFromAST = withSyncPerfMeasurements(
'getPotentialHookDeclarationsFromAST(originalSourceAST)',
() => getPotentialHookDeclarationsFromAST(originalSourceAST),
);
@@ -176,7 +176,7 @@ export function getHookName(
// nodesAssociatedWithReactHookASTNode could directly be used to obtain the hook variable name
// depending on the type of potentialReactHookASTNode
try {
const nodesAssociatedWithReactHookASTNode = withSyncPerformanceMark(
const nodesAssociatedWithReactHookASTNode = withSyncPerfMeasurements(
'getFilteredHookASTNodes()',
() =>
getFilteredHookASTNodes(
@@ -186,7 +186,7 @@ export function getHookName(
),
);
const name = withSyncPerformanceMark('getHookNameFromNode()', () =>
const name = withSyncPerfMeasurements('getHookNameFromNode()', () =>
getHookNameFromNode(
hook,
nodesAssociatedWithReactHookASTNode,
@@ -297,7 +297,7 @@ function getHookVariableName(
function getPotentialHookDeclarationsFromAST(sourceAST: File): NodePath[] {
const potentialHooksFound: NodePath[] = [];
withSyncPerformanceMark('traverse(sourceAST)', () =>
withSyncPerfMeasurements('traverse(sourceAST)', () =>
traverse(sourceAST, {
enter(path) {
if (path.isVariableDeclarator() && isPotentialHookDeclaration(path)) {
@@ -12,7 +12,7 @@ import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
import type {HookNames} from 'react-devtools-shared/src/types';
import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
import {withAsyncPerformanceMark} from 'react-devtools-shared/src/PerformanceMarks';
import {withAsyncPerfMeasurements} from 'react-devtools-shared/src/PerformanceLoggingUtils';
import WorkerizedParseSourceAndMetadata from './parseSourceAndMetadata.worker';
import typeof * as ParseSourceAndMetadataModule from './parseSourceAndMetadata';
import {flattenHooksList, loadSourceAndMetadata} from './loadSourceAndMetadata';
@@ -37,7 +37,7 @@ export async function parseHookNames(
hooksTree: HooksTree,
fetchFileWithCaching: FetchFileWithCaching | null,
): Promise<HookNames | null> {
return withAsyncPerformanceMark('parseHookNames', async () => {
return withAsyncPerfMeasurements('parseHookNames', async () => {
const hooksList = flattenHooksList(hooksTree);
if (hooksList.length === 0) {
// This component tree contains no named hooks.
@@ -49,10 +49,10 @@ import {__DEBUG__} from 'react-devtools-shared/src/constants';
import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
import {sourceMapIncludesSource} from '../SourceMapUtils';
import {
withAsyncPerformanceMark,
withCallbackPerformanceMark,
withSyncPerformanceMark,
} from 'react-devtools-shared/src/PerformanceMarks';
withAsyncPerfMeasurements,
withCallbackPerfMeasurements,
withSyncPerfMeasurements,
} from 'react-devtools-shared/src/PerformanceLoggingUtils';
import type {
HooksNode,
@@ -98,17 +98,17 @@ export async function loadSourceAndMetadata(
hooksList: HooksList,
fetchFileWithCaching: FetchFileWithCaching | null,
): Promise<LocationKeyToHookSourceAndMetadata> {
return withAsyncPerformanceMark('loadSourceAndMetadata()', async () => {
const locationKeyToHookSourceAndMetadata = withSyncPerformanceMark(
return withAsyncPerfMeasurements('loadSourceAndMetadata()', async () => {
const locationKeyToHookSourceAndMetadata = withSyncPerfMeasurements(
'initializeHookSourceAndMetadata',
() => initializeHookSourceAndMetadata(hooksList),
);
await withAsyncPerformanceMark('loadSourceFiles()', () =>
await withAsyncPerfMeasurements('loadSourceFiles()', () =>
loadSourceFiles(locationKeyToHookSourceAndMetadata, fetchFileWithCaching),
);
await withAsyncPerformanceMark('extractAndLoadSourceMapJSON()', () =>
await withAsyncPerfMeasurements('extractAndLoadSourceMapJSON()', () =>
extractAndLoadSourceMapJSON(locationKeyToHookSourceAndMetadata),
);
@@ -157,7 +157,7 @@ function extractAndLoadSourceMapJSON(
// TODO (named hooks) If this RegExp search is slow, we could try breaking it up
// first using an indexOf(' sourceMappingURL=') to find the start of the comment
// (probably at the end of the file) and then running the RegExp on the remaining substring.
let sourceMappingURLMatch = withSyncPerformanceMark(
let sourceMappingURLMatch = withSyncPerfMeasurements(
'sourceMapRegex.exec(runtimeSourceCode)',
() => sourceMapRegex.exec(runtimeSourceCode),
);
@@ -185,12 +185,12 @@ function extractAndLoadSourceMapJSON(
const trimmed = ((sourceMappingURL.match(
/base64,([a-zA-Z0-9+\/=]+)/,
): any): Array<string>)[1];
const decoded = withSyncPerformanceMark(
const decoded = withSyncPerfMeasurements(
'decodeBase64String()',
() => decodeBase64String(trimmed),
);
const sourceMapJSON = withSyncPerformanceMark(
const sourceMapJSON = withSyncPerfMeasurements(
'JSON.parse(decoded)',
() => JSON.parse(decoded),
);
@@ -227,7 +227,7 @@ function extractAndLoadSourceMapJSON(
}
// If the first source map we found wasn't a match, check for more.
sourceMappingURLMatch = withSyncPerformanceMark(
sourceMappingURLMatch = withSyncPerfMeasurements(
'sourceMapRegex.exec(runtimeSourceCode)',
() => sourceMapRegex.exec(runtimeSourceCode),
);
@@ -266,7 +266,7 @@ function extractAndLoadSourceMapJSON(
dedupedFetchPromises.get(url) ||
fetchFile(url).then(
sourceMapContents => {
const sourceMapJSON = withSyncPerformanceMark(
const sourceMapJSON = withSyncPerfMeasurements(
'JSON.parse(sourceMapContents)',
() => JSON.parse(sourceMapContents),
);
@@ -316,7 +316,7 @@ function fetchFile(
url: string,
markName?: string = 'fetchFile',
): Promise<string> {
return withCallbackPerformanceMark(`${markName}("${url}")`, done => {
return withCallbackPerfMeasurements(`${markName}("${url}")`, done => {
return new Promise((resolve, reject) => {
fetch(url, FETCH_OPTIONS).then(
response => {
@@ -376,7 +376,7 @@ export function hasNamedHooks(hooksTree: HooksTree): boolean {
export function flattenHooksList(hooksTree: HooksTree): HooksList {
const hooksList: HooksList = [];
withSyncPerformanceMark('flattenHooksList()', () => {
withSyncPerfMeasurements('flattenHooksList()', () => {
flattenHooksListImpl(hooksTree, hooksList);
});
@@ -472,7 +472,7 @@ function loadSourceFiles(
// If a helper function has been injected to fetch with caching,
// use it to fetch the (already loaded) source file.
fetchFileFunction = url => {
return withAsyncPerformanceMark(
return withAsyncPerfMeasurements(
`fetchFileWithCaching("${url}")`,
() => {
return ((fetchFileWithCaching: any): FetchFileWithCaching)(url);
@@ -19,9 +19,9 @@ import {__DEBUG__} from 'react-devtools-shared/src/constants';
import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
import {SourceMapMetadataConsumer} from '../SourceMapMetadataConsumer';
import {
withAsyncPerformanceMark,
withSyncPerformanceMark,
} from 'react-devtools-shared/src/PerformanceMarks';
withAsyncPerfMeasurements,
withSyncPerfMeasurements,
} from 'react-devtools-shared/src/PerformanceLoggingUtils';
import type {
HooksList,
@@ -106,27 +106,27 @@ export async function parseSourceAndMetadata(
hooksList: HooksList,
locationKeyToHookSourceAndMetadata: LocationKeyToHookSourceAndMetadata,
): Promise<HookNames | null> {
return withAsyncPerformanceMark('parseSourceAndMetadata()', async () => {
const locationKeyToHookParsedMetadata = withSyncPerformanceMark(
return withAsyncPerfMeasurements('parseSourceAndMetadata()', async () => {
const locationKeyToHookParsedMetadata = withSyncPerfMeasurements(
'initializeHookParsedMetadata',
() => initializeHookParsedMetadata(locationKeyToHookSourceAndMetadata),
);
withSyncPerformanceMark('parseSourceMaps', () =>
withSyncPerfMeasurements('parseSourceMaps', () =>
parseSourceMaps(
locationKeyToHookSourceAndMetadata,
locationKeyToHookParsedMetadata,
),
);
withSyncPerformanceMark('parseSourceAST()', () =>
withSyncPerfMeasurements('parseSourceAST()', () =>
parseSourceAST(
locationKeyToHookSourceAndMetadata,
locationKeyToHookParsedMetadata,
),
);
return withSyncPerformanceMark('findHookNames()', () =>
return withSyncPerfMeasurements('findHookNames()', () =>
findHookNames(hooksList, locationKeyToHookParsedMetadata),
);
});
@@ -174,7 +174,7 @@ function findHookNames(
let name;
const {metadataConsumer} = hookParsedMetadata;
if (metadataConsumer != null) {
name = withSyncPerformanceMark('metadataConsumer.hookNameFor()', () =>
name = withSyncPerfMeasurements('metadataConsumer.hookNameFor()', () =>
metadataConsumer.hookNameFor({
line: originalSourceLineNumber,
column: originalSourceColumnNumber,
@@ -184,7 +184,7 @@ function findHookNames(
}
if (name == null) {
name = withSyncPerformanceMark('getHookName()', () =>
name = withSyncPerfMeasurements('getHookName()', () =>
getHookName(
hook,
hookParsedMetadata.originalSourceAST,
@@ -303,7 +303,7 @@ function parseSourceAST(
// Now that the source map has been loaded,
// extract the original source for later.
// TODO (named hooks) Refactor this read, github.com/facebook/react/pull/22181
const {column, line, source} = withSyncPerformanceMark(
const {column, line, source} = withSyncPerfMeasurements(
'sourceConsumer.originalPositionFor()',
() =>
sourceConsumer.originalPositionFor({
@@ -329,7 +329,7 @@ function parseSourceAST(
// It can be relative if the source map specifies it that way,
// but we use it as a cache key across different source maps and there can be collisions.
originalSourceURL = (source: string);
originalSourceCode = withSyncPerformanceMark(
originalSourceCode = withSyncPerfMeasurements(
'sourceConsumer.sourceContentFor()',
() => (sourceConsumer.sourceContentFor(source, true): string),
);
@@ -401,7 +401,7 @@ function parseSourceAST(
// TODO (named hooks) This is probably where we should check max source length,
// rather than in loadSourceAndMetatada -> loadSourceFiles().
const originalSourceAST = withSyncPerformanceMark(
const originalSourceAST = withSyncPerfMeasurements(
'[@babel/parser] parse(originalSourceCode)',
() =>
parse(originalSourceCode, {
@@ -441,11 +441,11 @@ function parseSourceMaps(
const sourceMapJSON = hookSourceAndMetadata.sourceMapJSON;
if (sourceMapJSON != null) {
hookParsedMetadata.metadataConsumer = withSyncPerformanceMark(
hookParsedMetadata.metadataConsumer = withSyncPerfMeasurements(
'new SourceMapMetadataConsumer(sourceMapJSON)',
() => new SourceMapMetadataConsumer(sourceMapJSON),
);
hookParsedMetadata.sourceConsumer = withSyncPerformanceMark(
hookParsedMetadata.sourceConsumer = withSyncPerfMeasurements(
'new SourceMapConsumer(sourceMapJSON)',
() => new SourceMapConsumer(sourceMapJSON),
);