diff --git a/locales/en-US/app.ftl b/locales/en-US/app.ftl
index 1e01f06651..f9155a7ad7 100644
--- a/locales/en-US/app.ftl
+++ b/locales/en-US/app.ftl
@@ -397,6 +397,16 @@ MarkerContextMenu--select-the-receiver-thread =
MarkerContextMenu--select-the-sender-thread =
Select the sender thread “{ $threadName }”
+## MarkerFiltersContextMenu
+## This is the menu when filter icon is clicked in Marker Chart and Marker Table
+## panels.
+
+# This string is used on the marker filters menu item when clicked on the filter icon.
+# Variables:
+# $filter (String) - Search string that will be used to filter the markers.
+MarkerFiltersContextMenu--drop-samples-outside-of-markers-matching =
+ Drop samples outside of markers matching “{ $filter }”
+
## MarkerSettings
## This is used in all panels related to markers.
@@ -404,6 +414,9 @@ MarkerSettings--panel-search =
.label = Filter Markers:
.title = Only display markers that match a certain name
+MarkerSettings--marker-filters =
+ .title = Marker Filters
+
## MarkerSidebar
## This is the sidebar component that is used in Marker Table panel.
@@ -972,6 +985,11 @@ TransformNavigator--collapse-direct-recursion-only = Collapse direct recursion o
# $item (String) - Name of the function that transform applied to.
TransformNavigator--collapse-function-subtree = Collapse subtree: { $item }
+# "Drop samples outside of markers matching ..." transform.
+# Variables:
+# $item (String) - Search filter of the markers that transform will apply to.
+TransformNavigator--drop-samples-outside-of-markers-matching = Drop samples outside of markers matching: “{ $item }”
+
## "Bottom box" - a view which contains the source view and the assembly view,
## at the bottom of the profiler UI
##
diff --git a/res/img/svg/filter.svg b/res/img/svg/filter.svg
new file mode 100644
index 0000000000..94f1f25c45
--- /dev/null
+++ b/res/img/svg/filter.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/src/components/shared/CallNodeContextMenu.js b/src/components/shared/CallNodeContextMenu.js
index 8bfba53344..2b747b40d0 100644
--- a/src/components/shared/CallNodeContextMenu.js
+++ b/src/components/shared/CallNodeContextMenu.js
@@ -363,6 +363,10 @@ class CallNodeContextMenuImpl extends React.PureComponent {
});
break;
}
+ case 'filter-samples':
+ throw new Error(
+ "Filter samples transform can't be applied from the call node context menu."
+ );
default:
assertExhaustiveCheck(type);
}
diff --git a/src/components/shared/MarkerFiltersContextMenu.js b/src/components/shared/MarkerFiltersContextMenu.js
new file mode 100644
index 0000000000..0bfce82d91
--- /dev/null
+++ b/src/components/shared/MarkerFiltersContextMenu.js
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// @flow
+import React, { PureComponent } from 'react';
+import { MenuItem } from '@firefox-devtools/react-contextmenu';
+import { Localized } from '@fluent/react';
+
+import { ContextMenu } from './ContextMenu';
+import explicitConnect from 'firefox-profiler/utils/connect';
+import {
+ getMarkersSearchString,
+ getSelectedThreadsKey,
+} from 'firefox-profiler/selectors/url-state';
+import { addTransformToStack } from 'firefox-profiler/actions/profile-view';
+
+import type { ThreadsKey } from 'firefox-profiler/types';
+import type { ConnectedProps } from 'firefox-profiler/utils/connect';
+
+type OwnProps = {|
+ +onShow: () => void,
+ +onHide: () => void,
+|};
+
+type StateProps = {|
+ +searchString: string,
+ +threadsKey: ThreadsKey,
+|};
+
+type DispatchProps = {|
+ +addTransformToStack: typeof addTransformToStack,
+|};
+
+type Props = ConnectedProps;
+
+class MarkerFiltersContextMenuImpl extends PureComponent {
+ filterSamplesByMarker = () => {
+ const { searchString, threadsKey, addTransformToStack } = this.props;
+ addTransformToStack(threadsKey, {
+ type: 'filter-samples',
+ filterType: 'marker-search',
+ filter: searchString,
+ });
+ };
+
+ render() {
+ const { searchString, onShow, onHide } = this.props;
+ return (
+
+ );
+ }
+}
+
+export const MarkerFiltersContextMenu = explicitConnect<
+ OwnProps,
+ StateProps,
+ DispatchProps
+>({
+ mapStateToProps: (state) => ({
+ searchString: getMarkersSearchString(state),
+ threadsKey: getSelectedThreadsKey(state),
+ }),
+ mapDispatchToProps: {
+ addTransformToStack,
+ },
+ component: MarkerFiltersContextMenuImpl,
+});
diff --git a/src/components/shared/MarkerSettings.css b/src/components/shared/MarkerSettings.css
index 43c3bbf944..d0da118501 100644
--- a/src/components/shared/MarkerSettings.css
+++ b/src/components/shared/MarkerSettings.css
@@ -9,3 +9,31 @@
padding: 0;
line-height: 25px;
}
+
+.filterMarkersButton {
+ position: relative;
+ width: 24px;
+ height: 24px;
+ flex: none;
+ padding-right: 30px;
+ margin: 0 4px;
+ background-image: url(firefox-profiler-res/img/svg/filter.svg);
+ background-position: 4px center;
+ background-repeat: no-repeat;
+}
+
+/* This is the dropdown arrow on the right of the button. */
+.filterMarkersButton::after {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ border-top: 6px solid;
+ border-right: 4px solid transparent;
+ border-bottom: 0 solid transparent;
+ border-left: 4px solid transparent;
+ margin-top: 7px;
+ margin-right: 4px;
+ margin-left: 4px;
+ color: var(--grey-90);
+ content: '';
+}
diff --git a/src/components/shared/MarkerSettings.js b/src/components/shared/MarkerSettings.js
index 50296f5cef..b745983c89 100644
--- a/src/components/shared/MarkerSettings.js
+++ b/src/components/shared/MarkerSettings.js
@@ -6,12 +6,15 @@
import React, { PureComponent } from 'react';
import { Localized } from '@fluent/react';
+import classNames from 'classnames';
+import { showMenu } from '@firefox-devtools/react-contextmenu';
import explicitConnect from 'firefox-profiler/utils/connect';
import { changeMarkersSearchString } from 'firefox-profiler/actions/profile-view';
import { getMarkersSearchString } from 'firefox-profiler/selectors/url-state';
import { PanelSearch } from './PanelSearch';
import { StackImplementationSetting } from 'firefox-profiler/components/shared/StackImplementationSetting';
+import { MarkerFiltersContextMenu } from './MarkerFiltersContextMenu';
import type { ConnectedProps } from 'firefox-profiler/utils/connect';
@@ -28,13 +31,65 @@ type DispatchProps = {|
type Props = ConnectedProps<{||}, StateProps, DispatchProps>;
-class MarkerSettingsImpl extends PureComponent {
+type State = {|
+ +isMarkerFiltersMenuVisible: boolean,
+ // react-contextmenu library automatically hides the menu on mousedown even
+ // if it's already visible. That's why we need to handle the mousedown event
+ // as well and check if the menu is visible or not before it hides it.
+ // Otherwise, if we check this in onClick event, the state will always be
+ // `false` since the library already hid it on mousedown.
+ +isFilterMenuVisibleOnMouseDown: boolean,
+|};
+
+class MarkerSettingsImpl extends PureComponent {
+ state = {
+ isMarkerFiltersMenuVisible: false,
+ isFilterMenuVisibleOnMouseDown: false,
+ };
+
_onSearch = (value: string) => {
this.props.changeMarkersSearchString(value);
};
+ _onClickToggleFilterButton = (event: SyntheticMouseEvent) => {
+ const { isFilterMenuVisibleOnMouseDown } = this.state;
+ if (isFilterMenuVisibleOnMouseDown) {
+ // Do nothing as we would like to hide the menu if the menu was already visible on mouse down.
+ return;
+ }
+
+ const rect = event.currentTarget.getBoundingClientRect();
+ // FIXME: Currently we assume that the context menu is 250px wide, but ideally
+ // we should get the real width. It's not so easy though, because the context
+ // menu is not rendered yet.
+ const isRightAligned = rect.right > window.innerWidth - 250;
+
+ showMenu({
+ data: null,
+ id: 'MarkerFiltersContextMenu',
+ position: { x: isRightAligned ? rect.right : rect.left, y: rect.bottom },
+ target: event.target,
+ });
+ };
+
+ _onShowFiltersContextMenu = () => {
+ this.setState({ isMarkerFiltersMenuVisible: true });
+ };
+
+ _onHideFiltersContextMenu = () => {
+ this.setState({ isMarkerFiltersMenuVisible: false });
+ };
+
+ _onMouseDownToggleFilterButton = () => {
+ this.setState((state) => ({
+ isFilterMenuVisibleOnMouseDown: state.isMarkerFiltersMenuVisible,
+ }));
+ };
+
render() {
const { searchString } = this.props;
+ const { isMarkerFiltersMenuVisible } = this.state;
+
return (
@@ -52,6 +107,27 @@ class MarkerSettingsImpl extends PureComponent {
onSearch={this._onSearch}
/>
+
+
+
+
);
}
diff --git a/src/profile-logic/transforms.js b/src/profile-logic/transforms.js
index 62bd2f7a16..e4bf11ac00 100644
--- a/src/profile-logic/transforms.js
+++ b/src/profile-logic/transforms.js
@@ -17,12 +17,14 @@ import {
import { timeCode } from '../utils/time-code';
import { assertExhaustiveCheck, convertToTransformType } from '../utils/flow';
import { CallTree } from '../profile-logic/call-tree';
+import { getSearchFilteredMarkerIndexes } from '../profile-logic/marker-data';
import {
shallowCloneFrameTable,
shallowCloneFuncTable,
getEmptyStackTable,
} from './data-structures';
import { getFunctionName } from './function-info';
+import { splitSearchString, stringsToRegExp } from '../utils/string';
import type {
Thread,
@@ -40,6 +42,13 @@ import type {
TransformType,
TransformStack,
ProfileMeta,
+ StartEndRange,
+ FilterSamplesType,
+ Marker,
+ MarkerIndex,
+ MarkerSchemaByName,
+ CategoryList,
+ Milliseconds,
} from 'firefox-profiler/types';
/**
@@ -61,6 +70,7 @@ const SHORT_KEY_TO_TRANSFORM: { [string]: TransformType } = {};
'collapse-direct-recursion',
'collapse-recursion',
'collapse-function-subtree',
+ 'filter-samples',
].forEach((transform: TransformType) => {
// This is kind of an awkward switch, but it ensures we've exhaustively checked that
// we have a mapping for every transform.
@@ -96,6 +106,9 @@ const SHORT_KEY_TO_TRANSFORM: { [string]: TransformType } = {};
case 'collapse-function-subtree':
shortKey = 'cfs';
break;
+ case 'filter-samples':
+ shortKey = 'fs';
+ break;
default: {
throw assertExhaustiveCheck(transform);
}
@@ -268,6 +281,20 @@ export function parseTransforms(transformString: string): TransformStack {
break;
}
+ case 'filter-samples': {
+ // e.g. "fs-m-BackboneJS-TodoMVC.Adding100Items-async"
+ const [, shortFilterType, ...filter] = tuple;
+ // Filter string may include "-" characters, so we need to join them back.
+ const filterString = filter.join('-');
+ const filterType = convertToFullFilterType(shortFilterType);
+
+ transforms.push({
+ type: 'filter-samples',
+ filterType,
+ filter: filterString,
+ });
+ break;
+ }
default:
throw assertExhaustiveCheck(type);
}
@@ -275,6 +302,30 @@ export function parseTransforms(transformString: string): TransformStack {
return transforms;
}
+/**
+ * Convert the shortened filter type into the full filter type.
+ */
+function convertToFullFilterType(shortFilterType: string): FilterSamplesType {
+ switch (shortFilterType) {
+ case 'm':
+ return 'marker-search';
+ default:
+ throw new Error('Unknown filter type.');
+ }
+}
+
+/**
+ * Convert the full filter type into the shortened filter type.
+ */
+function convertToShortFilterType(filterType: FilterSamplesType): string {
+ switch (filterType) {
+ case 'marker-search':
+ return 'm';
+ default:
+ throw assertExhaustiveCheck(filterType);
+ }
+}
+
/**
* Each transform in the stack is separated by a "~".
*/
@@ -317,6 +368,10 @@ export function stringifyTransforms(transformStack: TransformStack): string {
}
return string;
}
+ case 'filter-samples':
+ return `${shortKey}-${convertToShortFilterType(
+ transform.filterType
+ )}-${transform.filter}`;
default:
throw assertExhaustiveCheck(transform);
}
@@ -364,6 +419,19 @@ export function getTransformLabelL10nIds(
};
}
+ if (transform.type === 'filter-samples') {
+ switch (transform.filterType) {
+ case 'marker-search':
+ return {
+ l10nId:
+ 'TransformNavigator--drop-samples-outside-of-markers-matching',
+ item: transform.filter,
+ };
+ default:
+ throw assertExhaustiveCheck(transform.filterType);
+ }
+ }
+
// Lookup function name.
let funcIndex;
switch (transform.type) {
@@ -474,6 +542,15 @@ export function applyTransformToCallNodePath(
transform.funcIndex,
callNodePath
);
+ case 'filter-samples':
+ // There's nothing to update in the call node path. But this call node path
+ // could disappear if we filtered out all the samples with this path.
+ // This is also the case for drop-function transform. We need to have a
+ // generic mechanism for: if the selected call node (after the transformation
+ // has been applied to the call path) is not present in the call tree, run
+ // some generic code that finds a close-by call node which is present.
+ // See: https://github.com/firefox-devtools/profiler/issues/4618
+ return callNodePath;
default:
throw assertExhaustiveCheck(transform);
}
@@ -1674,10 +1751,128 @@ export function funcHasRecursiveCall(
return false;
}
+function _findRangesByMarkerFilter(
+ getMarker: (MarkerIndex) => Marker,
+ markerIndexes: MarkerIndex[],
+ markerSchemaByName: MarkerSchemaByName,
+ categoryList: CategoryList,
+ filter: string
+): StartEndRange[] {
+ const ranges = [];
+
+ const searchRegExp = stringsToRegExp(splitSearchString(filter));
+ const searchFilteredMarkerIndexes = getSearchFilteredMarkerIndexes(
+ getMarker,
+ markerIndexes,
+ markerSchemaByName,
+ searchRegExp,
+ categoryList
+ );
+
+ for (const markerIndex of searchFilteredMarkerIndexes) {
+ const { start, end } = getMarker(markerIndex);
+
+ if (start === null || end === null) {
+ // This is not an interval marker, so we can't use it as a range.
+ continue;
+ }
+
+ ranges.push({ start: start, end: end });
+ }
+ return ranges;
+}
+
+/**
+ * Find the sample ranges to filter depending on the filter type, then go
+ * through all the samples and remove the ones that are outside of the ranges.
+ */
+export function filterSamples(
+ thread: Thread,
+ getMarker: (MarkerIndex) => Marker,
+ markerIndexes: MarkerIndex[],
+ markerSchemaByName: MarkerSchemaByName,
+ categoryList: CategoryList,
+ filterType: FilterSamplesType,
+ filter: string
+): Thread {
+ return timeCode('filterSamples', () => {
+ // Find the ranges to filter.
+ let ranges: StartEndRange[];
+ switch (filterType) {
+ case 'marker-search':
+ ranges = _findRangesByMarkerFilter(
+ getMarker,
+ markerIndexes,
+ markerSchemaByName,
+ categoryList,
+ filter
+ );
+ break;
+ default:
+ throw assertExhaustiveCheck(filterType);
+ }
+
+ // Now let's go through all the samples and remove the ones that are outside
+ // of the ranges.
+ const { samples, jsAllocations, nativeAllocations } = thread;
+
+ function filterTable<
+ Table: {
+ stack: Array,
+ time: Milliseconds[],
+ length: number,
+ }
+ >(table: Table): Table {
+ const newTable = {
+ ...table,
+ stack: table.stack.slice(),
+ };
+
+ for (let tableIndex = 0; tableIndex < newTable.length; tableIndex++) {
+ const sampleTime = newTable.time[tableIndex];
+
+ let sampleInRange = false;
+ for (const { start, end } of ranges) {
+ if (sampleTime >= start && sampleTime <= end) {
+ sampleInRange = true;
+ break;
+ }
+ }
+
+ if (!sampleInRange) {
+ newTable.stack[tableIndex] = null;
+ }
+ }
+
+ return newTable;
+ }
+
+ const newThread = {
+ ...thread,
+ samples: filterTable(samples),
+ };
+
+ if (jsAllocations) {
+ // Filter the JS allocations if there are any.
+ newThread.jsAllocations = filterTable(jsAllocations);
+ }
+ if (nativeAllocations) {
+ // Filter the native allocations if there are any.
+ newThread.nativeAllocations = filterTable(nativeAllocations);
+ }
+
+ return newThread;
+ });
+}
+
export function applyTransform(
thread: Thread,
transform: Transform,
- defaultCategory: IndexIntoCategoryList
+ defaultCategory: IndexIntoCategoryList,
+ getMarker: (MarkerIndex) => Marker,
+ markerIndexes: MarkerIndex[],
+ markerSchemaByName: MarkerSchemaByName,
+ categoryList: CategoryList
): Thread {
switch (transform.type) {
case 'focus-subtree':
@@ -1727,6 +1922,16 @@ export function applyTransform(
transform.funcIndex,
defaultCategory
);
+ case 'filter-samples':
+ return filterSamples(
+ thread,
+ getMarker,
+ markerIndexes,
+ markerSchemaByName,
+ categoryList,
+ transform.filterType,
+ transform.filter
+ );
default:
throw assertExhaustiveCheck(transform);
}
diff --git a/src/selectors/per-thread/index.js b/src/selectors/per-thread/index.js
index 4881cc481c..5554e0f494 100644
--- a/src/selectors/per-thread/index.js
+++ b/src/selectors/per-thread/index.js
@@ -7,7 +7,8 @@ import memoize from 'memoize-immutable';
import * as UrlState from '../url-state';
import * as ProfileData from '../../profile-logic/profile-data';
import {
- getThreadSelectorsPerThread,
+ getThreadSelectorsWithMarkersPerThread,
+ getBasicThreadSelectorsPerThread,
type ThreadSelectorsPerThread,
} from './thread';
import {
@@ -152,12 +153,26 @@ function _buildThreadSelectors(
threadIndexes: Set,
threadsKey: ThreadsKey = ProfileData.getThreadsKey(threadIndexes)
) {
- // We define the thread selectors in 3 steps to ensure clarity in the
+ // We define the thread selectors in 5 steps to ensure clarity in the
// separate files.
- // 1. The basic selectors.
- let selectors = getThreadSelectorsPerThread(threadIndexes, threadsKey);
- // 2. Stack, sample and marker selectors that need the previous basic
- // selectors for their own definition.
+ // 1. The basic thread selectors.
+ let selectors = getBasicThreadSelectorsPerThread(threadIndexes, threadsKey);
+ // 2. The marker selectors.
+ selectors = {
+ ...selectors,
+ ...getMarkerSelectorsPerThread(selectors, threadIndexes, threadsKey),
+ };
+ // 3. The thread selectors that need marker selectors.
+ selectors = {
+ ...selectors,
+ ...getThreadSelectorsWithMarkersPerThread(
+ selectors,
+ threadIndexes,
+ threadsKey
+ ),
+ };
+ // 4. Stack, sample selectors that need the previous selectors for their
+ // own definition.
selectors = {
...selectors,
...getStackAndSampleSelectorsPerThread(
@@ -165,9 +180,8 @@ function _buildThreadSelectors(
threadIndexes,
threadsKey
),
- ...getMarkerSelectorsPerThread(selectors, threadIndexes, threadsKey),
};
- // 3. Other selectors that need selectors from different files to be defined.
+ // 5. Other selectors that need selectors from different files to be defined.
selectors = {
...selectors,
...getComposedSelectorsPerThread(selectors),
diff --git a/src/selectors/per-thread/markers.js b/src/selectors/per-thread/markers.js
index 96b5760e20..3b0228b6e9 100644
--- a/src/selectors/per-thread/markers.js
+++ b/src/selectors/per-thread/markers.js
@@ -13,7 +13,7 @@ import * as ProfileSelectors from '../profile';
import { getRightClickedMarkerInfo } from '../right-clicked-marker';
import { getLabelGetter } from '../../profile-logic/marker-schema';
-import type { ThreadSelectorsPerThread } from './thread';
+import type { BasicThreadSelectorsPerThread } from './thread';
import type {
RawMarkerTable,
ThreadIndex,
@@ -43,7 +43,7 @@ export type MarkerSelectorsPerThread = $ReturnType<
* Create the selectors for a thread that have to do with either markers.
*/
export function getMarkerSelectorsPerThread(
- threadSelectors: ThreadSelectorsPerThread,
+ threadSelectors: BasicThreadSelectorsPerThread,
threadIndexes: Set,
threadsKey: ThreadsKey
) {
@@ -610,6 +610,7 @@ export function getMarkerSelectorsPerThread(
getTimelineJankMarkerIndexes,
getDerivedMarkerInfo,
getMarkerIndexToRawMarkerIndexes,
+ getFullMarkerList,
getFullMarkerListIndexes,
getNetworkMarkerIndexes,
getSearchFilteredNetworkMarkerIndexes,
diff --git a/src/selectors/per-thread/stack-sample.js b/src/selectors/per-thread/stack-sample.js
index 41554ee576..674eefd6cd 100644
--- a/src/selectors/per-thread/stack-sample.js
+++ b/src/selectors/per-thread/stack-sample.js
@@ -44,6 +44,7 @@ import type {
} from 'firefox-profiler/types';
import type { ThreadSelectorsPerThread } from './thread';
+import type { MarkerSelectorsPerThread } from './markers';
/**
* Infer the return type from the getStackAndSampleSelectorsPerThread function. This
@@ -54,11 +55,16 @@ export type StackAndSampleSelectorsPerThread = $ReturnType<
typeof getStackAndSampleSelectorsPerThread
>;
+type ThreadAndMarkerSelectorsPerThread = {|
+ ...ThreadSelectorsPerThread,
+ ...MarkerSelectorsPerThread,
+|};
+
/**
* Create the selectors for a thread that have to do with either stacks or samples.
*/
export function getStackAndSampleSelectorsPerThread(
- threadSelectors: ThreadSelectorsPerThread,
+ threadSelectors: ThreadAndMarkerSelectorsPerThread,
threadIndexes: Set,
threadsKey: ThreadsKey
) {
diff --git a/src/selectors/per-thread/thread.js b/src/selectors/per-thread/thread.js
index 0d4da59e77..b0ecded1c1 100644
--- a/src/selectors/per-thread/thread.js
+++ b/src/selectors/per-thread/thread.js
@@ -44,24 +44,30 @@ import type {
import type { UniqueStringArray } from '../../utils/unique-string-array';
import type { TransformLabeL10nIds } from 'firefox-profiler/profile-logic/transforms';
+import type { MarkerSelectorsPerThread } from './markers';
import { mergeThreads } from '../../profile-logic/merge-compare';
import { defaultThreadViewOptions } from '../../reducers/profile-view';
/**
- * Infer the return type from the getThreadSelectorsPerThread function. This
- * is done that so that the local type definition with `Selector` is the canonical
- * definition for the type of the selector.
+ * Infer the return type from the getBasicThreadSelectorsPerThread and
+ * getThreadSelectorsWithMarkersPerThread functions. This is done that so that
+ * the local type definition with `Selector` is the canonical definition for
+ * the type of the selector.
*/
-export type ThreadSelectorsPerThread = $ReturnType<
- typeof getThreadSelectorsPerThread
+export type BasicThreadSelectorsPerThread = $ReturnType<
+ typeof getBasicThreadSelectorsPerThread
>;
+export type ThreadSelectorsPerThread = {|
+ ...BasicThreadSelectorsPerThread,
+ ...$ReturnType,
+|};
/**
* Create the selectors for a thread that have to do with an entire thread. This includes
* the general filtering pipeline for threads.
*/
-export function getThreadSelectorsPerThread(
+export function getBasicThreadSelectorsPerThread(
threadIndexes: Set,
threadsKey: ThreadsKey
) {
@@ -172,74 +178,6 @@ export function getThreadSelectorsPerThread(
}
);
- // It becomes very expensive to apply each transform over and over again as they
- // typically take around 100ms to run per transform on a fast machine. Memoize
- // memoize each step individually so that they transform stack can be pushed and
- // popped frequently and easily.
- const _applyTransformMemoized = memoize(Transforms.applyTransform, {
- cache: new MixedTupleMap(),
- });
-
- const getTransformStack: Selector = (state) =>
- UrlState.getTransformStack(state, threadsKey);
-
- const getRangeAndTransformFilteredThread: Selector = createSelector(
- getRangeFilteredThread,
- getTransformStack,
- ProfileSelectors.getDefaultCategory,
- (startingThread, transforms, defaultCategory) => {
- return transforms.reduce(
- // Apply the reducer using an arrow function to ensure correct memoization.
- (thread, transform) =>
- _applyTransformMemoized(thread, transform, defaultCategory),
- startingThread
- );
- }
- );
-
- const _getImplementationFilteredThread: Selector = createSelector(
- getRangeAndTransformFilteredThread,
- UrlState.getImplementationFilter,
- ProfileSelectors.getDefaultCategory,
- ProfileData.filterThreadByImplementation
- );
-
- const _getImplementationAndSearchFilteredThread: Selector =
- createSelector(
- _getImplementationFilteredThread,
- UrlState.getSearchStrings,
- (thread, searchStrings) => {
- return ProfileData.filterThreadToSearchStrings(thread, searchStrings);
- }
- );
-
- const getFilteredThread: Selector = createSelector(
- _getImplementationAndSearchFilteredThread,
- UrlState.getInvertCallstack,
- ProfileSelectors.getDefaultCategory,
- (thread, shouldInvertCallstack, defaultCategory) => {
- return shouldInvertCallstack
- ? ProfileData.invertCallstack(thread, defaultCategory)
- : thread;
- }
- );
-
- const getPreviewFilteredThread: Selector = createSelector(
- getFilteredThread,
- ProfileSelectors.getPreviewSelection,
- (thread, previewSelection): Thread => {
- if (!previewSelection.hasSelection) {
- return thread;
- }
- const { selectionStart, selectionEnd } = previewSelection;
- return ProfileData.filterThreadSamplesToRange(
- thread,
- selectionStart,
- selectionEnd
- );
- }
- );
-
/**
* The CallTreeSummaryStrategy determines how the call tree summarizes the
* the current thread. By default, this is done by timing, but other
@@ -295,20 +233,6 @@ export function getThreadSelectorsPerThread(
CallTree.extractSamplesLikeTable
);
- const getFilteredSamplesForCallTree: Selector =
- createSelector(
- getFilteredThread,
- getCallTreeSummaryStrategy,
- CallTree.extractSamplesLikeTable
- );
-
- const getPreviewFilteredSamplesForCallTree: Selector =
- createSelector(
- getPreviewFilteredThread,
- getCallTreeSummaryStrategy,
- CallTree.extractSamplesLikeTable
- );
-
/**
* This selector returns the offset to add to a sampleIndex when accessing the
* base thread, if your thread is a range filtered thread (all but the base
@@ -328,29 +252,6 @@ export function getThreadSelectorsPerThread(
}
);
- /**
- * This selector returns the offset to add to a sampleIndex when accessing the
- * base thread, if your thread is the preview filtered thread.
- */
- const getSampleIndexOffsetFromPreviewRange: Selector = createSelector(
- getFilteredSamplesForCallTree,
- ProfileSelectors.getPreviewSelection,
- getSampleIndexOffsetFromCommittedRange,
- (samples, previewSelection, sampleIndexFromCommittedRange) => {
- if (!previewSelection.hasSelection) {
- return sampleIndexFromCommittedRange;
- }
-
- const [beginSampleIndex] = ProfileData.getSampleIndexRangeForSelection(
- samples,
- previewSelection.selectionStart,
- previewSelection.selectionEnd
- );
-
- return sampleIndexFromCommittedRange + beginSampleIndex;
- }
- );
-
const getFriendlyThreadName: Selector = createSelector(
ProfileSelectors.getThreads,
getThread,
@@ -363,27 +264,6 @@ export function getThreadSelectorsPerThread(
ProfileData.getThreadProcessDetails
);
- const getTransformLabelL10nIds: Selector =
- createSelector(
- ProfileSelectors.getMeta,
- getRangeAndTransformFilteredThread,
- getFriendlyThreadName,
- getTransformStack,
- Transforms.getTransformLabelL10nIds
- );
-
- const getLocalizedTransformLabels: Selector = createSelector(
- getTransformLabelL10nIds,
- (transformL10nIds) =>
- transformL10nIds.map((transform) => (
-
- ))
- );
-
const getViewOptions: Selector = (state) =>
ProfileSelectors.getProfileViewOptions(state).perThread[threadsKey] ||
defaultThreadViewOptions;
@@ -478,20 +358,11 @@ export function getThreadSelectorsPerThread(
getNativeAllocations,
getJsAllocations,
getThreadRange,
- getFilteredThread,
getRangeFilteredThread,
- getRangeAndTransformFilteredThread,
- getPreviewFilteredThread,
getUnfilteredSamplesForCallTree,
- getFilteredSamplesForCallTree,
- getPreviewFilteredSamplesForCallTree,
getSampleIndexOffsetFromCommittedRange,
- getSampleIndexOffsetFromPreviewRange,
getFriendlyThreadName,
getThreadProcessDetails,
- getTransformLabelL10nIds,
- getLocalizedTransformLabels,
- getTransformStack,
getViewOptions,
getJsTracerTable,
getExpensiveJsTracerTiming,
@@ -507,3 +378,172 @@ export function getThreadSelectorsPerThread(
getCallTreeSummaryStrategy,
};
}
+
+type BasicThreadAndMarkerSelectorsPerThread = {|
+ ...BasicThreadSelectorsPerThread,
+ ...MarkerSelectorsPerThread,
+|};
+
+export function getThreadSelectorsWithMarkersPerThread(
+ threadSelectors: BasicThreadAndMarkerSelectorsPerThread,
+ threadIndexes: Set,
+ threadsKey: ThreadsKey
+) {
+ // It becomes very expensive to apply each transform over and over again as they
+ // typically take around 100ms to run per transform on a fast machine. Memoize
+ // memoize each step individually so that they transform stack can be pushed and
+ // popped frequently and easily.
+ const _applyTransformMemoized = memoize(Transforms.applyTransform, {
+ cache: new MixedTupleMap(),
+ });
+
+ const getTransformStack: Selector = (state) =>
+ UrlState.getTransformStack(state, threadsKey);
+
+ const getRangeAndTransformFilteredThread: Selector = createSelector(
+ threadSelectors.getRangeFilteredThread,
+ getTransformStack,
+ ProfileSelectors.getDefaultCategory,
+ threadSelectors.getMarkerGetter,
+ threadSelectors.getFullMarkerListIndexes,
+ ProfileSelectors.getMarkerSchemaByName,
+ ProfileSelectors.getCategories,
+ (
+ startingThread,
+ transforms,
+ defaultCategory,
+ markerGetter,
+ markerIndexes,
+ markerSchemaByName,
+ categories
+ ) => {
+ return transforms.reduce(
+ // Apply the reducer using an arrow function to ensure correct memoization.
+ (thread, transform) =>
+ _applyTransformMemoized(
+ thread,
+ transform,
+ defaultCategory,
+ markerGetter,
+ markerIndexes,
+ markerSchemaByName,
+ categories
+ ),
+ startingThread
+ );
+ }
+ );
+
+ const _getImplementationFilteredThread: Selector = createSelector(
+ getRangeAndTransformFilteredThread,
+ UrlState.getImplementationFilter,
+ ProfileSelectors.getDefaultCategory,
+ ProfileData.filterThreadByImplementation
+ );
+
+ const _getImplementationAndSearchFilteredThread: Selector =
+ createSelector(
+ _getImplementationFilteredThread,
+ UrlState.getSearchStrings,
+ (thread, searchStrings) => {
+ return ProfileData.filterThreadToSearchStrings(thread, searchStrings);
+ }
+ );
+
+ const getFilteredThread: Selector = createSelector(
+ _getImplementationAndSearchFilteredThread,
+ UrlState.getInvertCallstack,
+ ProfileSelectors.getDefaultCategory,
+ (thread, shouldInvertCallstack, defaultCategory) => {
+ return shouldInvertCallstack
+ ? ProfileData.invertCallstack(thread, defaultCategory)
+ : thread;
+ }
+ );
+
+ const getPreviewFilteredThread: Selector = createSelector(
+ getFilteredThread,
+ ProfileSelectors.getPreviewSelection,
+ (thread, previewSelection): Thread => {
+ if (!previewSelection.hasSelection) {
+ return thread;
+ }
+ const { selectionStart, selectionEnd } = previewSelection;
+ return ProfileData.filterThreadSamplesToRange(
+ thread,
+ selectionStart,
+ selectionEnd
+ );
+ }
+ );
+
+ const getFilteredSamplesForCallTree: Selector =
+ createSelector(
+ getFilteredThread,
+ threadSelectors.getCallTreeSummaryStrategy,
+ CallTree.extractSamplesLikeTable
+ );
+
+ const getPreviewFilteredSamplesForCallTree: Selector =
+ createSelector(
+ getPreviewFilteredThread,
+ threadSelectors.getCallTreeSummaryStrategy,
+ CallTree.extractSamplesLikeTable
+ );
+
+ /**
+ * This selector returns the offset to add to a sampleIndex when accessing the
+ * base thread, if your thread is the preview filtered thread.
+ */
+ const getSampleIndexOffsetFromPreviewRange: Selector = createSelector(
+ getFilteredSamplesForCallTree,
+ ProfileSelectors.getPreviewSelection,
+ threadSelectors.getSampleIndexOffsetFromCommittedRange,
+ (samples, previewSelection, sampleIndexFromCommittedRange) => {
+ if (!previewSelection.hasSelection) {
+ return sampleIndexFromCommittedRange;
+ }
+
+ const [beginSampleIndex] = ProfileData.getSampleIndexRangeForSelection(
+ samples,
+ previewSelection.selectionStart,
+ previewSelection.selectionEnd
+ );
+
+ return sampleIndexFromCommittedRange + beginSampleIndex;
+ }
+ );
+
+ const getTransformLabelL10nIds: Selector =
+ createSelector(
+ ProfileSelectors.getMeta,
+ getRangeAndTransformFilteredThread,
+ threadSelectors.getFriendlyThreadName,
+ getTransformStack,
+ Transforms.getTransformLabelL10nIds
+ );
+
+ const getLocalizedTransformLabels: Selector = createSelector(
+ getTransformLabelL10nIds,
+ (transformL10nIds) =>
+ transformL10nIds.map((transform) => (
+
+ ))
+ );
+
+ return {
+ getTransformStack,
+ getRangeAndTransformFilteredThread,
+ getFilteredThread,
+ getPreviewFilteredThread,
+ getFilteredSamplesForCallTree,
+ getPreviewFilteredSamplesForCallTree,
+ getSampleIndexOffsetFromPreviewRange,
+ getTransformLabelL10nIds,
+ getLocalizedTransformLabels,
+ };
+}
diff --git a/src/test/components/__snapshots__/MarkerChart.test.js.snap b/src/test/components/__snapshots__/MarkerChart.test.js.snap
index 199cb5b4af..33bfed573f 100644
--- a/src/test/components/__snapshots__/MarkerChart.test.js.snap
+++ b/src/test/components/__snapshots__/MarkerChart.test.js.snap
@@ -446,6 +446,31 @@ exports[`MarkerChart renders the normal marker chart and matches the snapshot 1`
Did you know you can use the comma (,) to search using several terms?
+
+
+
+
+
+