From 5c6f122a715dcbade1f68c13923a5643dab4d0ee Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Wed, 29 Jan 2025 17:32:38 -0500 Subject: [PATCH 1/2] Add CallTree.getPreviewFilteredCtssSamples(). --- src/profile-logic/call-tree.js | 10 ++++++++++ src/selectors/per-thread/stack-sample.js | 1 + src/test/fixtures/utils.js | 1 + src/test/unit/profile-tree.test.js | 2 ++ 4 files changed, 14 insertions(+) diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index 32ad1e7e75..b96f07e4db 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -316,6 +316,7 @@ export class CallTree { _internal: CallTreeInternal; _callNodeInfo: CallNodeInfo; _thread: Thread; + _previewFilteredCtssSamples: SamplesLikeTable; _rootTotalSummary: number; _displayDataByIndex: Map; // _children is indexed by IndexIntoCallNodeTable. Since they are @@ -329,6 +330,7 @@ export class CallTree { thread: Thread, categories: CategoryList, callNodeInfo: CallNodeInfo, + previewFilteredCtssSamples: SamplesLikeTable, internal: CallTreeInternal, rootTotalSummary: number, isHighPrecision: boolean, @@ -338,6 +340,7 @@ export class CallTree { this._internal = internal; this._callNodeInfo = callNodeInfo; this._thread = thread; + this._previewFilteredCtssSamples = previewFilteredCtssSamples; this._rootTotalSummary = rootTotalSummary; this._displayDataByIndex = new Map(); this._children = []; @@ -346,6 +349,10 @@ export class CallTree { this._weightType = weightType; } + getPreviewFilteredCtssSamples(): SamplesLikeTable { + return this._previewFilteredCtssSamples; + } + getRoots() { return this._roots; } @@ -839,6 +846,7 @@ export function getCallTree( thread: Thread, callNodeInfo: CallNodeInfo, categories: CategoryList, + previewFilteredCtssSamples: SamplesLikeTable, callTreeTimings: CallTreeTimings, weightType: WeightType ): CallTree { @@ -850,6 +858,7 @@ export function getCallTree( thread, categories, callNodeInfo, + previewFilteredCtssSamples, new CallTreeInternalNonInverted(callNodeInfo, timings), timings.rootTotalSummary, Boolean(thread.isJsTracer), @@ -862,6 +871,7 @@ export function getCallTree( thread, categories, callNodeInfo, + previewFilteredCtssSamples, new CallTreeInternalInverted( ensureExists(callNodeInfo.asInverted()), timings diff --git a/src/selectors/per-thread/stack-sample.js b/src/selectors/per-thread/stack-sample.js index a05b5837d2..658d1859f7 100644 --- a/src/selectors/per-thread/stack-sample.js +++ b/src/selectors/per-thread/stack-sample.js @@ -326,6 +326,7 @@ export function getStackAndSampleSelectorsPerThread( threadSelectors.getFilteredThread, getCallNodeInfo, ProfileSelectors.getCategories, + threadSelectors.getPreviewFilteredCtssSamples, getCallTreeTimings, getWeightTypeForCallTree, CallTree.getCallTree diff --git a/src/test/fixtures/utils.js b/src/test/fixtures/utils.js index ad029bf197..eea4dbfb16 100644 --- a/src/test/fixtures/utils.js +++ b/src/test/fixtures/utils.js @@ -190,6 +190,7 @@ export function callTreeFromProfile( thread, callNodeInfo, ensureExists(profile.meta.categories), + thread.samples, callTreeTimings, 'samples' ); diff --git a/src/test/unit/profile-tree.test.js b/src/test/unit/profile-tree.test.js index 1d5a3e6773..ed10d7a22d 100644 --- a/src/test/unit/profile-tree.test.js +++ b/src/test/unit/profile-tree.test.js @@ -449,6 +449,7 @@ describe('inverted call tree', function () { thread, callNodeInfo, ensureExists(profile.meta.categories), + thread.samples, callTreeTimings, 'samples' ); @@ -491,6 +492,7 @@ describe('inverted call tree', function () { thread, invertedCallNodeInfo, ensureExists(profile.meta.categories), + thread.samples, invertedCallTreeTimings, 'samples' ); From 7ea21d4c789c91a0b2426c6718cc05055b494624 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 30 Jan 2025 11:51:25 -0500 Subject: [PATCH 2/2] Add prev/next buttons in the assembly view. --- src/actions/profile-view.js | 47 +++++-- src/app-logic/url-handling.js | 16 ++- .../app/AssemblyViewNativeSymbolNavigator.js | 117 +++++++++++++++++ src/components/app/BottomBox.css | 20 ++- src/components/app/BottomBox.js | 2 + src/components/shared/CallNodeContextMenu.js | 16 ++- src/components/stack-chart/index.js | 3 +- src/profile-logic/call-tree.js | 7 +- src/profile-logic/profile-data.js | 118 +++++++++++++----- src/reducers/url-state.js | 23 +++- src/selectors/url-state.js | 19 ++- src/test/store/bottom-box.test.js | 12 +- src/test/unit/profile-data.test.js | 9 +- src/test/url-handling.test.js | 4 + src/types/actions.js | 7 +- src/types/profile-derived.js | 1 + src/types/state.js | 3 +- 17 files changed, 354 insertions(+), 70 deletions(-) create mode 100644 src/components/app/AssemblyViewNativeSymbolNavigator.js diff --git a/src/actions/profile-view.js b/src/actions/profile-view.js index f0017e93da..38deab1ce2 100644 --- a/src/actions/profile-view.js +++ b/src/actions/profile-view.js @@ -1875,23 +1875,49 @@ export function changeTableViewOptions( }; } +function _findIndexOfMaxValue(arr: number[]): number { + if (arr.length === 0) { + return -1; + } + + let indexOfMaxValue = 0; + let maxValue = arr[0]; + for (let i = 1; i < arr.length; i++) { + const val = arr[i]; + if (val > maxValue) { + indexOfMaxValue = i; + maxValue = val; + } + } + return indexOfMaxValue; +} + export function updateBottomBoxContentsAndMaybeOpen( currentTab: TabSlug, - { libIndex, sourceFile, nativeSymbols }: BottomBoxInfo + bottomBoxInfo: BottomBoxInfo ): Action { - // TODO: If the set has more than one element, pick the native symbol with - // the highest total sample count - const nativeSymbol = nativeSymbols.length !== 0 ? nativeSymbols[0] : null; + const { + libIndex, + sourceFile, + nativeSymbols, + nativeSymbolWeightsAtOpeningTime, + } = bottomBoxInfo; + const haveSourceFile = sourceFile !== null; + const haveNativeSymbol = nativeSymbols.length !== 0; return { type: 'UPDATE_BOTTOM_BOX', libIndex, sourceFile, - nativeSymbol, + initialNativeSymbolEntryIndex: haveNativeSymbol + ? _findIndexOfMaxValue(nativeSymbolWeightsAtOpeningTime) + : null, allNativeSymbolsForInitiatingCallNode: nativeSymbols, + allNativeSymbolWeightsForInitiatingCallNode: + nativeSymbolWeightsAtOpeningTime, currentTab, - shouldOpenBottomBox: sourceFile !== null || nativeSymbol !== null, - shouldOpenAssemblyView: sourceFile === null && nativeSymbol !== null, + shouldOpenBottomBox: haveSourceFile || haveNativeSymbol, + shouldOpenAssemblyView: !haveSourceFile && haveNativeSymbol, }; } @@ -1901,6 +1927,13 @@ export function openAssemblyView(): Action { }; } +export function changeAssemblyViewNativeSymbolEntryIndex(entryIndex: number): Action { + return { + type: 'CHANGE_ASSEMBLY_VIEW_NATIVE_SYMBOL_ENTRY_INDEX', + entryIndex, + } +} + export function closeAssemblyView(): Action { return { type: 'CLOSE_ASSEMBLY_VIEW', diff --git a/src/app-logic/url-handling.js b/src/app-logic/url-handling.js index ab136b8966..55d93c70da 100644 --- a/src/app-logic/url-handling.js +++ b/src/app-logic/url-handling.js @@ -343,9 +343,16 @@ export function getQueryStringFromUrlState(urlState: UrlState): string { if (sourceView.sourceFile !== null) { query.sourceView = sourceView.sourceFile; } - if (assemblyView.isOpen && assemblyView.nativeSymbol !== null) { + if ( + assemblyView.isOpen && + assemblyView.currentNativeSymbolEntryIndex !== null + ) { + const { + currentNativeSymbolEntryIndex, + allNativeSymbolsForInitiatingCallNode, + } = assemblyView; query.assemblyView = stringifyAssemblyViewSymbol( - assemblyView.nativeSymbol + allNativeSymbolsForInitiatingCallNode[currentNativeSymbolEntryIndex] ); } } @@ -504,8 +511,9 @@ export function stateFromLocation( const assemblyView: AssemblyViewState = { isOpen: false, scrollGeneration: 0, - nativeSymbol: null, + currentNativeSymbolEntryIndex: null, allNativeSymbolsForInitiatingCallNode: [], + allNativeSymbolWeightsForInitiatingCallNode: [], }; const isBottomBoxOpenPerPanel = {}; tabSlugs.forEach((tabSlug) => (isBottomBoxOpenPerPanel[tabSlug] = false)); @@ -516,7 +524,7 @@ export function stateFromLocation( if (query.assemblyView) { const symbol = parseAssemblyViewSymbol(query.assemblyView); if (symbol !== null) { - assemblyView.nativeSymbol = symbol; + assemblyView.currentNativeSymbolEntryIndex = 0; assemblyView.allNativeSymbolsForInitiatingCallNode = [symbol]; assemblyView.isOpen = true; isBottomBoxOpenPerPanel[selectedTab] = true; diff --git a/src/components/app/AssemblyViewNativeSymbolNavigator.js b/src/components/app/AssemblyViewNativeSymbolNavigator.js new file mode 100644 index 0000000000..578a1ddbea --- /dev/null +++ b/src/components/app/AssemblyViewNativeSymbolNavigator.js @@ -0,0 +1,117 @@ +/* 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 from 'react'; +import classNames from 'classnames'; + +import { + getAssemblyViewCurrentNativeSymbolEntryIndex, + getAssemblyViewNativeSymbolEntryCount, +} from 'firefox-profiler/selectors/url-state'; +import { changeAssemblyViewNativeSymbolEntryIndex } from 'firefox-profiler/actions/profile-view'; +import explicitConnect from 'firefox-profiler/utils/connect'; + +import type { ConnectedProps } from 'firefox-profiler/utils/connect'; + +import { Localized } from '@fluent/react'; + +type StateProps = {| + +assemblyViewCurrentNativeSymbolEntryIndex: number | null, + +assemblyViewNativeSymbolEntryCount: number, +|}; + +type DispatchProps = {| + +changeAssemblyViewNativeSymbolEntryIndex: typeof changeAssemblyViewNativeSymbolEntryIndex, +|}; + +type Props = ConnectedProps<{||}, StateProps, DispatchProps>; + +class AssemblyViewNativeSymbolNavigatorImpl extends React.PureComponent { + _onPreviousClick = () => { + this._changeIndexBy(-1); + }; + + _onNextClick = () => { + this._changeIndexBy(1); + }; + + _changeIndexBy(delta: number) { + const { + assemblyViewCurrentNativeSymbolEntryIndex: index, + assemblyViewNativeSymbolEntryCount: count, + changeAssemblyViewNativeSymbolEntryIndex, + } = this.props; + const newIndex = index + delta; + if (newIndex >= 0 && newIndex < count) { + changeAssemblyViewNativeSymbolEntryIndex(newIndex); + } + } + + render() { + const { + assemblyViewCurrentNativeSymbolEntryIndex: index, + assemblyViewNativeSymbolEntryCount: count, + } = this.props; + + if (index === null || count <= 1) { + return null; + } + + return ( + <> +

+ {index !== null && count > 1 ? `${index + 1} of ${count}` : ''} +

+ + + + + + + + ); + } +} + +export const AssemblyViewNativeSymbolNavigator = explicitConnect< + {||}, + StateProps, + DispatchProps, +>({ + mapStateToProps: (state) => ({ + assemblyViewCurrentNativeSymbolEntryIndex: + getAssemblyViewCurrentNativeSymbolEntryIndex(state), + assemblyViewNativeSymbolEntryCount: + getAssemblyViewNativeSymbolEntryCount(state), + }), + mapDispatchToProps: { + changeAssemblyViewNativeSymbolEntryIndex, + }, + component: AssemblyViewNativeSymbolNavigatorImpl, +}); diff --git a/src/components/app/BottomBox.css b/src/components/app/BottomBox.css index fd619cdbd3..3b95dfba4a 100644 --- a/src/components/app/BottomBox.css +++ b/src/components/app/BottomBox.css @@ -46,7 +46,7 @@ line-height: 18px; } -.bottom-box-title { +.bottom-box-title, .bottom-box-title-trailer { overflow: hidden; margin: 0 8px; font: inherit; @@ -54,6 +54,10 @@ white-space: nowrap; } +.bottom-box-title-trailer { + margin: 0; +} + .bottom-box-header-trailing-buttons { display: flex; height: 100%; @@ -62,7 +66,9 @@ } .bottom-close-button, -.bottom-assembly-button { +.bottom-assembly-button, +.bottom-prev-button, +.bottom-next-button { width: 24px; height: 24px; flex-shrink: 0; @@ -71,6 +77,16 @@ background-size: 16px 16px; } +.bottom-prev-button, +.bottom-next-button { + font-size: 9px; + width: 16px; +} + +.bottom-next-button { + margin-right: 8px; +} + .bottom-close-button { background-image: url(/res/img/svg/close-dark.svg); } diff --git a/src/components/app/BottomBox.js b/src/components/app/BottomBox.js index 348ecd048c..b931145412 100644 --- a/src/components/app/BottomBox.js +++ b/src/components/app/BottomBox.js @@ -10,6 +10,7 @@ import classNames from 'classnames'; import { SourceView } from '../shared/SourceView'; import { AssemblyView } from '../shared/AssemblyView'; import { AssemblyViewToggleButton } from './AssemblyViewToggleButton'; +import { AssemblyViewNativeSymbolNavigator } from './AssemblyViewNativeSymbolNavigator'; import { CodeLoadingOverlay } from './CodeLoadingOverlay'; import { CodeErrorOverlay } from './CodeErrorOverlay'; import { @@ -187,6 +188,7 @@ class BottomBoxImpl extends React.PureComponent { // These trailing header buttons go into the bottom-box-bar of the last pane. const trailingHeaderButtons = (
+ {assemblyViewIsOpen ? : null}