mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[Flight] Use a heuristic to extract a useful description of I/O from the Promise value (#33662)
It's useful to be able to distinguish between different invocations of common helper libraries (like fetch) without having to click through each one. This adds a heuristic to extract a useful description of I/O from the Promise value. We try to find things like getUser(id) -> User where User.id is the id or fetch(url) -> Response where Response.url is the url. For urls we use the filename (or hostname if there is none) as the short name if it can fit. The full url is in the tooltip. <img width="845" alt="Screenshot 2025-06-27 at 7 58 20 PM" src="https://github.com/user-attachments/assets/95f10c08-13a8-449e-97e8-52f0083a65dc" />
This commit is contained in:
parent
508f7aa78f
commit
94fce500bc
|
|
@ -300,6 +300,125 @@ function getIOColor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getIODescription(value: any): string {
|
||||||
|
if (!__DEV__) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'object':
|
||||||
|
// Test the object for a bunch of common property names that are useful identifiers.
|
||||||
|
// While we only have the return value here, it should ideally be a name that
|
||||||
|
// describes the arguments requested.
|
||||||
|
if (value === null) {
|
||||||
|
return '';
|
||||||
|
} else if (value instanceof Error) {
|
||||||
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||||
|
return String(value.message);
|
||||||
|
} else if (typeof value.url === 'string') {
|
||||||
|
return value.url;
|
||||||
|
} else if (typeof value.command === 'string') {
|
||||||
|
return value.command;
|
||||||
|
} else if (
|
||||||
|
typeof value.request === 'object' &&
|
||||||
|
typeof value.request.url === 'string'
|
||||||
|
) {
|
||||||
|
return value.request.url;
|
||||||
|
} else if (
|
||||||
|
typeof value.response === 'object' &&
|
||||||
|
typeof value.response.url === 'string'
|
||||||
|
) {
|
||||||
|
return value.response.url;
|
||||||
|
} else if (
|
||||||
|
typeof value.id === 'string' ||
|
||||||
|
typeof value.id === 'number' ||
|
||||||
|
typeof value.id === 'bigint'
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||||
|
return String(value.id);
|
||||||
|
} else if (typeof value.name === 'string') {
|
||||||
|
return value.name;
|
||||||
|
} else {
|
||||||
|
const str = value.toString();
|
||||||
|
if (str.startWith('[object ') || str.length < 5 || str.length > 500) {
|
||||||
|
// This is probably not a useful description.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
case 'string':
|
||||||
|
if (value.length < 5 || value.length > 500) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
case 'number':
|
||||||
|
case 'bigint':
|
||||||
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||||
|
return String(value);
|
||||||
|
default:
|
||||||
|
// Not useful descriptors.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
} catch (x) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIOLongName(
|
||||||
|
ioInfo: ReactIOInfo,
|
||||||
|
description: string,
|
||||||
|
env: void | string,
|
||||||
|
rootEnv: string,
|
||||||
|
): string {
|
||||||
|
const name = ioInfo.name;
|
||||||
|
const longName = description === '' ? name : name + ' (' + description + ')';
|
||||||
|
const isPrimaryEnv = env === rootEnv;
|
||||||
|
return isPrimaryEnv || env === undefined
|
||||||
|
? longName
|
||||||
|
: longName + ' [' + env + ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIOShortName(
|
||||||
|
ioInfo: ReactIOInfo,
|
||||||
|
description: string,
|
||||||
|
env: void | string,
|
||||||
|
rootEnv: string,
|
||||||
|
): string {
|
||||||
|
const name = ioInfo.name;
|
||||||
|
const isPrimaryEnv = env === rootEnv;
|
||||||
|
const envSuffix = isPrimaryEnv || env === undefined ? '' : ' [' + env + ']';
|
||||||
|
let desc = '';
|
||||||
|
const descMaxLength = 30 - name.length - envSuffix.length;
|
||||||
|
if (descMaxLength > 1) {
|
||||||
|
const l = description.length;
|
||||||
|
if (l > 0 && l <= descMaxLength) {
|
||||||
|
// We can fit the full description
|
||||||
|
desc = ' (' + description + ')';
|
||||||
|
} else if (
|
||||||
|
description.startsWith('http://') ||
|
||||||
|
description.startsWith('https://') ||
|
||||||
|
description.startsWith('/')
|
||||||
|
) {
|
||||||
|
// Looks like a URL. Let's see if we can extract something shorter.
|
||||||
|
// We don't have to do a full parse so let's try something cheaper.
|
||||||
|
let queryIdx = description.indexOf('?');
|
||||||
|
if (queryIdx === -1) {
|
||||||
|
queryIdx = description.length;
|
||||||
|
}
|
||||||
|
if (description.charCodeAt(queryIdx - 1) === 47 /* "/" */) {
|
||||||
|
// Ends with slash. Look before that.
|
||||||
|
queryIdx--;
|
||||||
|
}
|
||||||
|
const slashIdx = description.lastIndexOf('/', queryIdx - 1);
|
||||||
|
if (queryIdx - slashIdx < descMaxLength) {
|
||||||
|
// This may now be either the file name or the host.
|
||||||
|
desc = ' (' + description.slice(slashIdx + 1, queryIdx) + ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name + desc + envSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
export function logComponentAwaitAborted(
|
export function logComponentAwaitAborted(
|
||||||
asyncInfo: ReactAsyncInfo,
|
asyncInfo: ReactAsyncInfo,
|
||||||
trackIdx: number,
|
trackIdx: number,
|
||||||
|
|
@ -308,17 +427,16 @@ export function logComponentAwaitAborted(
|
||||||
rootEnv: string,
|
rootEnv: string,
|
||||||
): void {
|
): void {
|
||||||
if (supportsUserTiming && endTime > 0) {
|
if (supportsUserTiming && endTime > 0) {
|
||||||
const env = asyncInfo.env;
|
|
||||||
const name = asyncInfo.awaited.name;
|
|
||||||
const isPrimaryEnv = env === rootEnv;
|
|
||||||
const entryName =
|
const entryName =
|
||||||
'await ' +
|
'await ' + getIOShortName(asyncInfo.awaited, '', asyncInfo.env, rootEnv);
|
||||||
(isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']');
|
|
||||||
const debugTask = asyncInfo.debugTask || asyncInfo.awaited.debugTask;
|
const debugTask = asyncInfo.debugTask || asyncInfo.awaited.debugTask;
|
||||||
if (__DEV__ && debugTask) {
|
if (__DEV__ && debugTask) {
|
||||||
const properties = [
|
const properties = [
|
||||||
['Aborted', 'The stream was aborted before this Promise resolved.'],
|
['Aborted', 'The stream was aborted before this Promise resolved.'],
|
||||||
];
|
];
|
||||||
|
const tooltipText =
|
||||||
|
getIOLongName(asyncInfo.awaited, '', asyncInfo.env, rootEnv) +
|
||||||
|
' Aborted';
|
||||||
debugTask.run(
|
debugTask.run(
|
||||||
// $FlowFixMe[method-unbinding]
|
// $FlowFixMe[method-unbinding]
|
||||||
performance.measure.bind(performance, entryName, {
|
performance.measure.bind(performance, entryName, {
|
||||||
|
|
@ -330,7 +448,7 @@ export function logComponentAwaitAborted(
|
||||||
track: trackNames[trackIdx],
|
track: trackNames[trackIdx],
|
||||||
trackGroup: COMPONENTS_TRACK,
|
trackGroup: COMPONENTS_TRACK,
|
||||||
properties,
|
properties,
|
||||||
tooltipText: entryName + ' Aborted',
|
tooltipText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
@ -357,12 +475,10 @@ export function logComponentAwaitErrored(
|
||||||
error: mixed,
|
error: mixed,
|
||||||
): void {
|
): void {
|
||||||
if (supportsUserTiming && endTime > 0) {
|
if (supportsUserTiming && endTime > 0) {
|
||||||
const env = asyncInfo.env;
|
const description = getIODescription(error);
|
||||||
const name = asyncInfo.awaited.name;
|
|
||||||
const isPrimaryEnv = env === rootEnv;
|
|
||||||
const entryName =
|
const entryName =
|
||||||
'await ' +
|
'await ' +
|
||||||
(isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']');
|
getIOShortName(asyncInfo.awaited, description, asyncInfo.env, rootEnv);
|
||||||
const debugTask = asyncInfo.debugTask || asyncInfo.awaited.debugTask;
|
const debugTask = asyncInfo.debugTask || asyncInfo.awaited.debugTask;
|
||||||
if (__DEV__ && debugTask) {
|
if (__DEV__ && debugTask) {
|
||||||
const message =
|
const message =
|
||||||
|
|
@ -374,6 +490,9 @@ export function logComponentAwaitErrored(
|
||||||
: // eslint-disable-next-line react-internal/safe-string-coercion
|
: // eslint-disable-next-line react-internal/safe-string-coercion
|
||||||
String(error);
|
String(error);
|
||||||
const properties = [['Rejected', message]];
|
const properties = [['Rejected', message]];
|
||||||
|
const tooltipText =
|
||||||
|
getIOLongName(asyncInfo.awaited, description, asyncInfo.env, rootEnv) +
|
||||||
|
' Rejected';
|
||||||
debugTask.run(
|
debugTask.run(
|
||||||
// $FlowFixMe[method-unbinding]
|
// $FlowFixMe[method-unbinding]
|
||||||
performance.measure.bind(performance, entryName, {
|
performance.measure.bind(performance, entryName, {
|
||||||
|
|
@ -385,7 +504,7 @@ export function logComponentAwaitErrored(
|
||||||
track: trackNames[trackIdx],
|
track: trackNames[trackIdx],
|
||||||
trackGroup: COMPONENTS_TRACK,
|
trackGroup: COMPONENTS_TRACK,
|
||||||
properties,
|
properties,
|
||||||
tooltipText: entryName + ' Rejected',
|
tooltipText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
@ -412,13 +531,15 @@ export function logComponentAwait(
|
||||||
value: mixed,
|
value: mixed,
|
||||||
): void {
|
): void {
|
||||||
if (supportsUserTiming && endTime > 0) {
|
if (supportsUserTiming && endTime > 0) {
|
||||||
const env = asyncInfo.env;
|
const description = getIODescription(value);
|
||||||
const name = asyncInfo.awaited.name;
|
const name = getIOShortName(
|
||||||
const isPrimaryEnv = env === rootEnv;
|
asyncInfo.awaited,
|
||||||
|
description,
|
||||||
|
asyncInfo.env,
|
||||||
|
rootEnv,
|
||||||
|
);
|
||||||
|
const entryName = 'await ' + name;
|
||||||
const color = getIOColor(name);
|
const color = getIOColor(name);
|
||||||
const entryName =
|
|
||||||
'await ' +
|
|
||||||
(isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']');
|
|
||||||
const debugTask = asyncInfo.debugTask || asyncInfo.awaited.debugTask;
|
const debugTask = asyncInfo.debugTask || asyncInfo.awaited.debugTask;
|
||||||
if (__DEV__ && debugTask) {
|
if (__DEV__ && debugTask) {
|
||||||
const properties: Array<[string, string]> = [];
|
const properties: Array<[string, string]> = [];
|
||||||
|
|
@ -427,6 +548,12 @@ export function logComponentAwait(
|
||||||
} else if (value !== undefined) {
|
} else if (value !== undefined) {
|
||||||
addValueToProperties('Resolved', value, properties, 0, '');
|
addValueToProperties('Resolved', value, properties, 0, '');
|
||||||
}
|
}
|
||||||
|
const tooltipText = getIOLongName(
|
||||||
|
asyncInfo.awaited,
|
||||||
|
description,
|
||||||
|
asyncInfo.env,
|
||||||
|
rootEnv,
|
||||||
|
);
|
||||||
debugTask.run(
|
debugTask.run(
|
||||||
// $FlowFixMe[method-unbinding]
|
// $FlowFixMe[method-unbinding]
|
||||||
performance.measure.bind(performance, entryName, {
|
performance.measure.bind(performance, entryName, {
|
||||||
|
|
@ -438,6 +565,7 @@ export function logComponentAwait(
|
||||||
track: trackNames[trackIdx],
|
track: trackNames[trackIdx],
|
||||||
trackGroup: COMPONENTS_TRACK,
|
trackGroup: COMPONENTS_TRACK,
|
||||||
properties,
|
properties,
|
||||||
|
tooltipText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
@ -463,11 +591,8 @@ export function logIOInfoErrored(
|
||||||
const startTime = ioInfo.start;
|
const startTime = ioInfo.start;
|
||||||
const endTime = ioInfo.end;
|
const endTime = ioInfo.end;
|
||||||
if (supportsUserTiming && endTime >= 0) {
|
if (supportsUserTiming && endTime >= 0) {
|
||||||
const name = ioInfo.name;
|
const description = getIODescription(error);
|
||||||
const env = ioInfo.env;
|
const entryName = getIOShortName(ioInfo, description, ioInfo.env, rootEnv);
|
||||||
const isPrimaryEnv = env === rootEnv;
|
|
||||||
const entryName =
|
|
||||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
|
||||||
const debugTask = ioInfo.debugTask;
|
const debugTask = ioInfo.debugTask;
|
||||||
if (__DEV__ && debugTask) {
|
if (__DEV__ && debugTask) {
|
||||||
const message =
|
const message =
|
||||||
|
|
@ -479,6 +604,8 @@ export function logIOInfoErrored(
|
||||||
: // eslint-disable-next-line react-internal/safe-string-coercion
|
: // eslint-disable-next-line react-internal/safe-string-coercion
|
||||||
String(error);
|
String(error);
|
||||||
const properties = [['Rejected', message]];
|
const properties = [['Rejected', message]];
|
||||||
|
const tooltipText =
|
||||||
|
getIOLongName(ioInfo, description, ioInfo.env, rootEnv) + ' Rejected';
|
||||||
debugTask.run(
|
debugTask.run(
|
||||||
// $FlowFixMe[method-unbinding]
|
// $FlowFixMe[method-unbinding]
|
||||||
performance.measure.bind(performance, entryName, {
|
performance.measure.bind(performance, entryName, {
|
||||||
|
|
@ -489,7 +616,7 @@ export function logIOInfoErrored(
|
||||||
color: 'error',
|
color: 'error',
|
||||||
track: IO_TRACK,
|
track: IO_TRACK,
|
||||||
properties,
|
properties,
|
||||||
tooltipText: entryName + ' Rejected',
|
tooltipText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
@ -515,13 +642,10 @@ export function logIOInfo(
|
||||||
const startTime = ioInfo.start;
|
const startTime = ioInfo.start;
|
||||||
const endTime = ioInfo.end;
|
const endTime = ioInfo.end;
|
||||||
if (supportsUserTiming && endTime >= 0) {
|
if (supportsUserTiming && endTime >= 0) {
|
||||||
const name = ioInfo.name;
|
const description = getIODescription(value);
|
||||||
const env = ioInfo.env;
|
const entryName = getIOShortName(ioInfo, description, ioInfo.env, rootEnv);
|
||||||
const isPrimaryEnv = env === rootEnv;
|
const color = getIOColor(entryName);
|
||||||
const entryName =
|
|
||||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
|
||||||
const debugTask = ioInfo.debugTask;
|
const debugTask = ioInfo.debugTask;
|
||||||
const color = getIOColor(name);
|
|
||||||
if (__DEV__ && debugTask) {
|
if (__DEV__ && debugTask) {
|
||||||
const properties: Array<[string, string]> = [];
|
const properties: Array<[string, string]> = [];
|
||||||
if (typeof value === 'object' && value !== null) {
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
|
@ -529,6 +653,12 @@ export function logIOInfo(
|
||||||
} else if (value !== undefined) {
|
} else if (value !== undefined) {
|
||||||
addValueToProperties('Resolved', value, properties, 0, '');
|
addValueToProperties('Resolved', value, properties, 0, '');
|
||||||
}
|
}
|
||||||
|
const tooltipText = getIOLongName(
|
||||||
|
ioInfo,
|
||||||
|
description,
|
||||||
|
ioInfo.env,
|
||||||
|
rootEnv,
|
||||||
|
);
|
||||||
debugTask.run(
|
debugTask.run(
|
||||||
// $FlowFixMe[method-unbinding]
|
// $FlowFixMe[method-unbinding]
|
||||||
performance.measure.bind(performance, entryName, {
|
performance.measure.bind(performance, entryName, {
|
||||||
|
|
@ -539,6 +669,7 @@ export function logIOInfo(
|
||||||
color: color,
|
color: color,
|
||||||
track: IO_TRACK,
|
track: IO_TRACK,
|
||||||
properties,
|
properties,
|
||||||
|
tooltipText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user