Skip to content

Commit

Permalink
[DevTools] RFC: Add initial APIs for logging instrumentation events u…
Browse files Browse the repository at this point in the history
…nder feature flag
  • Loading branch information
Juan Tejada committed Sep 13, 2021
1 parent 33226fa commit 0ef97f4
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 57 deletions.
5 changes: 5 additions & 0 deletions packages/react-devtools-extensions/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
Expand Down
43 changes: 43 additions & 0 deletions packages/react-devtools-shared/src/Logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* 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';

export type LogEvent = {|
+name: 'parseHookNames',
+displayName: string | null,
+numberOfHooks: number | null,
+durationMs: number,
+resolution: 'success' | 'error' | 'timeout',
|};

export type LogFunction = LogEvent => void;

let loggers = [];
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() {};
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -27,6 +30,13 @@ function measure(markName: string): void {
}
}

function now(): number {
if (supportsPerformanceNow) {
return performance.now();
}
return Date.now();
}

export async function withAsyncPerformanceMark<TReturn>(
markName: string,
callback: () => Promise<TReturn>,
Expand Down Expand Up @@ -66,3 +76,32 @@ export function withCallbackPerformanceMark<TReturn>(
}
return callback(() => {});
}

export async function measureAsyncDuration<TReturn>(
callback: () => Promise<TReturn>,
): Promise<[number, TReturn]> {
const start = now();
const result = await callback();
const duration = now() - start;
return [duration, result];
}

export function measureSyncDuration<TReturn>(
callback: () => TReturn,
): [number, TReturn] {
const start = now();
const result = callback();
const duration = now() - start;
return [duration, result];
}

export function measureCallbackDuration<TReturn>(
callback: (done: () => number) => TReturn,
): TReturn {
const start = now();
const done = () => {
const duration = now() - start;
return duration;
};
return callback(done);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
export const enableProfilerChangedHookIndices = true;
export const isInternalFacebookBuild = true;
export const enableNamedHooksFeature = false;
export const enableLogger = false;

export const consoleManagedByDevToolsDuringStrictMode = false;

/************************************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
export const enableProfilerChangedHookIndices = false;
export const isInternalFacebookBuild = false;
export const enableNamedHooksFeature = false;
export const enableLogger = false;

export const consoleManagedByDevToolsDuringStrictMode = false;

/************************************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@
export const enableProfilerChangedHookIndices = false;
export const isInternalFacebookBuild = false;
export const enableNamedHooksFeature = true;
export const enableLogger = false;

export const consoleManagedByDevToolsDuringStrictMode = true;
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
export const enableProfilerChangedHookIndices = true;
export const isInternalFacebookBuild = true;
export const enableNamedHooksFeature = true;
export const enableLogger = false;

export const consoleManagedByDevToolsDuringStrictMode = true;

/************************************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
export const enableProfilerChangedHookIndices = true;
export const isInternalFacebookBuild = false;
export const enableNamedHooksFeature = true;
export const enableLogger = false;

export const consoleManagedByDevToolsDuringStrictMode = true;

/************************************************************************
Expand Down
124 changes: 71 additions & 53 deletions packages/react-devtools-shared/src/hookNamesCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {measureCallbackDuration} from './PerformanceLoggingUtils';
import {logEvent} from './Logger';

const TIMEOUT = 30000;

Expand Down Expand Up @@ -92,7 +94,13 @@ export function loadHookNames(
},
};

const wake = () => {
let timeoutID;

const wake = (
resolution: 'success' | 'error' | 'timeout',
hookNames: HookNames | null,
durationMs: number,
) => {
if (timeoutID) {
clearTimeout(timeoutID);
timeoutID = null;
Expand All @@ -101,6 +109,15 @@ export function loadHookNames(
// This assumes they won't throw.
callbacks.forEach(callback => callback());
callbacks.clear();

// Log duration for parsing hook names
logEvent({
name: 'parseHookNames',
displayName: element.displayName,
numberOfHooks: hookNames?.size ?? null,
durationMs,
resolution,
});
};

const newRecord: Record<HookNames> = (record = {
Expand All @@ -110,64 +127,65 @@ export function loadHookNames(

let didTimeout = false;

loadHookNamesFunction(hooksTree, fetchFileWithCaching).then(
function onSuccess(hookNames) {
if (didTimeout) {
return;
}

measureCallbackDuration(done => {
loadHookNamesFunction(hooksTree, fetchFileWithCaching).then(
function onSuccess(hookNames) {
if (didTimeout) {
return;
}

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

wake('success', hookNames, done());
},
function onError(error) {
if (didTimeout) {
return;
}

if (__DEBUG__) {
console.log('[hookNamesCache] onError()');
}

console.error(error);

const thrownRecord = ((newRecord: any): RejectedRecord);
thrownRecord.status = Rejected;
thrownRecord.value = null;

wake('error', null, done());
},
);

// Eventually timeout and stop trying to load names.
timeoutID = setTimeout(function onTimeout() {
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;
console.log('[hookNamesCache] onTimeout()');
}

wake();
},
function onError(error) {
if (didTimeout) {
return;
}

if (__DEBUG__) {
console.log('[hookNamesCache] onError()');
}

console.error(error);

const thrownRecord = ((newRecord: any): RejectedRecord);
thrownRecord.status = Rejected;
thrownRecord.value = null;

wake();
},
);

// Eventually timeout and stop trying to load names.
let timeoutID = setTimeout(function onTimeout() {
if (__DEBUG__) {
console.log('[hookNamesCache] onTimeout()');
}

timeoutID = null;

didTimeout = true;
timeoutID = null;

const timedoutRecord = ((newRecord: any): RejectedRecord);
timedoutRecord.status = Rejected;
timedoutRecord.value = null;
didTimeout = true;

wake();
}, TIMEOUT);
const timedoutRecord = ((newRecord: any): RejectedRecord);
timedoutRecord.status = Rejected;
timedoutRecord.value = null;

wake('timeout', null, done());
}, TIMEOUT);
});
map.set(element, record);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/react-devtools-shared/src/hooks/astUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @flow
*/

import {withSyncPerformanceMark} from 'react-devtools-shared/src/PerformanceMarks';
import {withSyncPerformanceMark} from 'react-devtools-shared/src/PerformanceLoggingUtils';
import traverse, {NodePath, Node} from '@babel/traverse';
import {File} from '@babel/types';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {withAsyncPerformanceMark} from 'react-devtools-shared/src/PerformanceLoggingUtils';
import WorkerizedParseSourceAndMetadata from './parseSourceAndMetadata.worker';
import typeof * as ParseSourceAndMetadataModule from './parseSourceAndMetadata';
import {flattenHooksList, loadSourceAndMetadata} from './loadSourceAndMetadata';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
withAsyncPerformanceMark,
withCallbackPerformanceMark,
withSyncPerformanceMark,
} from 'react-devtools-shared/src/PerformanceMarks';
} from 'react-devtools-shared/src/PerformanceLoggingUtils';

import type {
HooksNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {SourceMapMetadataConsumer} from '../SourceMapMetadataConsumer';
import {
withAsyncPerformanceMark,
withSyncPerformanceMark,
} from 'react-devtools-shared/src/PerformanceMarks';
} from 'react-devtools-shared/src/PerformanceLoggingUtils';

import type {
HooksList,
Expand Down

0 comments on commit 0ef97f4

Please sign in to comment.