-
Notifications
You must be signed in to change notification settings - Fork 47k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Scheduling Profiler: De-emphasize React internal frames (#22588)
This commit adds code to all React bundles to explicitly register the beginning and ending of the module. This is done by creating Error objects (which capture the file name, line number, and column number) and passing them explicitly to a DevTools hook (when present). Next, as the Scheduling Profiler logs metadata to the User Timing API, it prints these module ranges along with other metadata (like Lane values and profiler version number). Lastly, the Scheduling Profiler UI compares stack frames to these ranges when drawing the flame graph and dims or de-emphasizes frames that fall within an internal module. The net effect of this is that user code (and 3rd party code) stands out clearly in the flame graph while React internal modules are dimmed. Internal module ranges are completely optional. Older profiling samples, or ones recorded without the React DevTools extension installed, will simply not dim the internal frames.
- Loading branch information
Brian Vaughn
authored
Oct 21, 2021
1 parent
f6abf4b
commit 4ba2057
Showing
21 changed files
with
543 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
...-devtools-scheduling-profiler/src/content-views/utils/__tests__/__modules__/module-one.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** | ||
* 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 | ||
*/ | ||
|
||
export const outerErrorA = new Error(); | ||
|
||
export const moduleStartError = new Error(); | ||
export const innerError = new Error(); | ||
export const moduleStopError = new Error(); | ||
|
||
export const outerErrorB = new Error(); |
18 changes: 18 additions & 0 deletions
18
...-devtools-scheduling-profiler/src/content-views/utils/__tests__/__modules__/module-two.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
* 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 | ||
*/ | ||
|
||
export const moduleAStartError = new Error(); | ||
export const innerErrorA = new Error(); | ||
export const moduleAStopError = new Error(); | ||
|
||
export const outerError = new Error(); | ||
|
||
export const moduleBStartError = new Error(); | ||
export const innerErrorB = new Error(); | ||
export const moduleBStopError = new Error(); |
79 changes: 79 additions & 0 deletions
79
...eact-devtools-scheduling-profiler/src/content-views/utils/__tests__/moduleFilters-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/** | ||
* 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 | ||
*/ | ||
|
||
import {isInternalModule} from '../moduleFilters'; | ||
|
||
describe('isInternalModule', () => { | ||
let map; | ||
|
||
function createFlamechartStackFrame(scriptUrl, locationLine, locationColumn) { | ||
return { | ||
name: 'test', | ||
timestamp: 0, | ||
duration: 1, | ||
scriptUrl, | ||
locationLine, | ||
locationColumn, | ||
}; | ||
} | ||
|
||
function createStackFrame(fileName, lineNumber, columnNumber) { | ||
return { | ||
columnNumber: columnNumber, | ||
lineNumber: lineNumber, | ||
fileName: fileName, | ||
functionName: 'test', | ||
source: ` at test (${fileName}:${lineNumber}:${columnNumber})`, | ||
}; | ||
} | ||
|
||
beforeEach(() => { | ||
map = new Map(); | ||
map.set('foo', [ | ||
[createStackFrame('foo', 10, 0), createStackFrame('foo', 15, 100)], | ||
]); | ||
map.set('bar', [ | ||
[createStackFrame('bar', 10, 0), createStackFrame('bar', 15, 100)], | ||
[createStackFrame('bar', 20, 0), createStackFrame('bar', 25, 100)], | ||
]); | ||
}); | ||
|
||
it('should properly identify stack frames within the provided module ranges', () => { | ||
expect( | ||
isInternalModule(map, createFlamechartStackFrame('foo', 10, 0)), | ||
).toBe(true); | ||
expect( | ||
isInternalModule(map, createFlamechartStackFrame('foo', 12, 35)), | ||
).toBe(true); | ||
expect( | ||
isInternalModule(map, createFlamechartStackFrame('foo', 15, 100)), | ||
).toBe(true); | ||
expect( | ||
isInternalModule(map, createFlamechartStackFrame('bar', 12, 0)), | ||
).toBe(true); | ||
expect( | ||
isInternalModule(map, createFlamechartStackFrame('bar', 22, 125)), | ||
).toBe(true); | ||
}); | ||
|
||
it('should properly identify stack frames outside of the provided module ranges', () => { | ||
expect(isInternalModule(map, createFlamechartStackFrame('foo', 9, 0))).toBe( | ||
false, | ||
); | ||
expect( | ||
isInternalModule(map, createFlamechartStackFrame('foo', 15, 101)), | ||
).toBe(false); | ||
expect( | ||
isInternalModule(map, createFlamechartStackFrame('bar', 17, 0)), | ||
).toBe(false); | ||
expect( | ||
isInternalModule(map, createFlamechartStackFrame('baz', 12, 0)), | ||
).toBe(false); | ||
}); | ||
}); |
69 changes: 69 additions & 0 deletions
69
packages/react-devtools-scheduling-profiler/src/content-views/utils/moduleFilters.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/** | ||
* 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 | ||
*/ | ||
|
||
import type { | ||
FlamechartStackFrame, | ||
InternalModuleSourceToRanges, | ||
} from '../../types'; | ||
|
||
import { | ||
CHROME_WEBSTORE_EXTENSION_ID, | ||
INTERNAL_EXTENSION_ID, | ||
LOCAL_EXTENSION_ID, | ||
} from 'react-devtools-shared/src/constants'; | ||
|
||
export function isInternalModule( | ||
internalModuleSourceToRanges: InternalModuleSourceToRanges, | ||
flamechartStackFrame: FlamechartStackFrame, | ||
): boolean { | ||
const {locationColumn, locationLine, scriptUrl} = flamechartStackFrame; | ||
|
||
if (scriptUrl == null || locationColumn == null || locationLine == null) { | ||
// This could indicate a browser-internal API like performance.mark(). | ||
return false; | ||
} | ||
|
||
// Internal modules are only registered if DevTools was running when the profile was captured, | ||
// but DevTools should also hide its own frames to avoid over-emphasizing them. | ||
if ( | ||
// Handle webpack-internal:// sources | ||
scriptUrl.includes('/react-devtools') || | ||
scriptUrl.includes('/react_devtools') || | ||
// Filter out known extension IDs | ||
scriptUrl.includes(CHROME_WEBSTORE_EXTENSION_ID) || | ||
scriptUrl.includes(INTERNAL_EXTENSION_ID) || | ||
scriptUrl.includes(LOCAL_EXTENSION_ID) | ||
// Unfortunately this won't get everything, like relatively loaded chunks or Web Worker files. | ||
) { | ||
return true; | ||
} | ||
|
||
// Filter out React internal packages. | ||
const ranges = internalModuleSourceToRanges.get(scriptUrl); | ||
if (ranges != null) { | ||
for (let i = 0; i < ranges.length; i++) { | ||
const [startStackFrame, stopStackFrame] = ranges[i]; | ||
|
||
const isAfterStart = | ||
locationLine > startStackFrame.lineNumber || | ||
(locationLine === startStackFrame.lineNumber && | ||
locationColumn >= startStackFrame.columnNumber); | ||
const isBeforeStop = | ||
locationLine < stopStackFrame.lineNumber || | ||
(locationLine === stopStackFrame.lineNumber && | ||
locationColumn <= stopStackFrame.columnNumber); | ||
|
||
if (isAfterStart && isBeforeStop) { | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} |
Oops, something went wrong.