Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs-user/guide-filtering-call-trees.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ Merging takes a call node and removes it from the call tree. Any self time for t

Focusing on a function or call node removes all of the ancestor call nodes—the children call nodes remain. If a stack does not contain that function or node, then it is removed. This effectively focuses on a subtree or a set of subtrees on the call tree.

### Focus on Function Self

Focus on function self is similar to focus on function, but more restrictive: it only keeps samples where the focused function is the innermost implementation-filtered frame. This helps you analyze where within a function the self time is being spent, by removing samples where the function is calling other code.

For example, if you focus-self on a JavaScript function with the JS implementation filter, you'll only see samples where that JS function has self time, and any native (C++) calls below it will be shown as descendants. This is particularly useful for:

- Finding which parts of a function are slow (by looking at the assembly or source lines)
- Understanding what engine internals are being called by a JS function (by switching implementation filter after focusing)
- Eliminating noise from code your function calls, to concentrate on the function's own execution

If the same function appears multiple times in a stack (recursion), only the innermost instance is kept as the root.

### Focus on Category

Focusing on the nodes that belong to the same category as the selected node, thereby merging all nodes that belong to another category.
Expand Down
22 changes: 22 additions & 0 deletions locales/en-US/app.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,21 @@ CallNodeContextMenu--transform-focus-function = Focus on function
.title = { CallNodeContextMenu--transform-focus-function-title }
CallNodeContextMenu--transform-focus-function-inverted = Focus on function (inverted)
.title = { CallNodeContextMenu--transform-focus-function-title }

## The translation for "self" in these strings should match the translation used
## in CallTree--samples-self and CallTree--bytes-self. Alternatively it can be
## translated as "self values" or "self time" (though "self time" is less desirable
## because this menu item is also shown in "bytes" mode).

CallNodeContextMenu--transform-focus-self-title =
Focusing on self is similar to focusing on a function, but only keeps samples
that contribute to the function’s self time. Samples in callees
are dropped, and the call tree is re-rooted to the focused function.
CallNodeContextMenu--transform-focus-self = Focus on self only
.title = { CallNodeContextMenu--transform-focus-self-title }

##

CallNodeContextMenu--transform-focus-subtree = Focus on subtree only
.title =
Focusing on a subtree will remove any sample that does not include that
Expand Down Expand Up @@ -1126,6 +1141,13 @@ TransformNavigator--focus-subtree = Focus Node: { $item }
# $item (String) - Name of the function that transform applied to.
TransformNavigator--focus-function = Focus: { $item }

# "Focus self" transform.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, worth adding a short note on "self" (no need for it to be as long as the other).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, how does this look?

# See: https://profiler.firefox.com/docs/#/./guide-filtering-call-trees?id=focus-on-function-self
# Also see the translation note above CallNodeContextMenu--transform-focus-self.
# Variables:
# $item (String) - Name of the function that transform applied to.
TransformNavigator--focus-self = Focus Self: { $item }

# "Focus category" transform. The word "Focus" has the meaning of an adjective here.
# See: https://profiler.firefox.com/docs/#/./guide-filtering-call-trees?id=focus-category
# Variables:
Expand Down
15 changes: 15 additions & 0 deletions res/img/svg/focus-self-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion src/app-logic/url-handling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import {
import { tabSlugs } from '../app-logic/tabs-handling';
import { StringTable } from 'firefox-profiler/utils/string-table';

export const CURRENT_URL_VERSION = 12;
export const CURRENT_URL_VERSION = 13;

/**
* This static piece of state might look like an anti-pattern, but it's a relatively
Expand Down Expand Up @@ -1193,6 +1193,9 @@ const _upgraders: {
// Remove the old sourceView parameter regardless of whether we found a match
delete query.sourceView;
},
[13]: (_) => {
// just added the focus-self transform
},
};

for (let destVersion = 1; destVersion <= CURRENT_URL_VERSION; destVersion++) {
Expand Down
4 changes: 4 additions & 0 deletions src/components/shared/CallNodeContextMenu.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
background-image: url(../../../res/img/svg/focus-icon.svg);
}

.callNodeContextMenuIconFocusSelf {
background-image: url(../../../res/img/svg/focus-self-icon.svg);
}

.callNodeContextMenuIconDrop {
background-image: url(../../../res/img/svg/drop-icon.svg);
}
Expand Down
17 changes: 17 additions & 0 deletions src/components/shared/CallNodeContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,13 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
funcIndex: selectedFunc,
});
break;
case 'focus-self':
addTransformToStack(threadsKey, {
type: 'focus-self',
funcIndex: selectedFunc,
implementation,
});
break;
case 'merge-call-node':
addTransformToStack(threadsKey, {
type: 'merge-call-node',
Expand Down Expand Up @@ -681,6 +688,16 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
content: 'Focus on subtree only',
})}

{this.renderTransformMenuItem({
l10nId: 'CallNodeContextMenu--transform-focus-self',
shortcut: 'S',
icon: 'FocusSelf',
onClick: this._handleClick,
transform: 'focus-self',
title: '',
content: 'Focus on self only',
})}

{hasCategory
? this.renderTransformMenuItem({
l10nId: 'CallNodeContextMenu--transform-focus-category',
Expand Down
74 changes: 74 additions & 0 deletions src/profile-logic/transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import type { StringTable } from 'firefox-profiler/utils/string-table';
const TRANSFORM_OBJ: { [key in TransformType]: true } = {
'focus-subtree': true,
'focus-function': true,
'focus-self': true,
'merge-call-node': true,
'merge-function': true,
'drop-function': true,
Expand Down Expand Up @@ -86,6 +87,9 @@ ALL_TRANSFORM_TYPES.forEach((transform: TransformType) => {
case 'focus-function':
shortKey = 'ff';
break;
case 'focus-self':
shortKey = 'ffs';
break;
case 'focus-category':
shortKey = 'fg';
break;
Expand Down Expand Up @@ -239,6 +243,20 @@ export function parseTransforms(transformString: string): TransformStack {
}
break;
}
case 'focus-self': {
// e.g. "ffs-js-325"
const [, implementation, funcIndexRaw] = tuple;
const funcIndex = parseInt(funcIndexRaw, 10);
if (isNaN(funcIndex) || funcIndex < 0) {
break;
}
transforms.push({
type: 'focus-self',
funcIndex,
implementation: toValidImplementationFilter(implementation),
});
break;
}
case 'focus-category': {
// e.g. "fg-3"
const [, categoryRaw] = tuple;
Expand Down Expand Up @@ -360,6 +378,8 @@ export function stringifyTransforms(transformStack: TransformStack): string {
return `${shortKey}-${transform.funcIndex}`;
case 'collapse-direct-recursion':
return `${shortKey}-${transform.implementation}-${transform.funcIndex}`;
case 'focus-self':
return `${shortKey}-${transform.implementation}-${transform.funcIndex}`;
case 'focus-subtree':
case 'merge-call-node': {
let string = [
Expand Down Expand Up @@ -444,6 +464,7 @@ export function getTransformLabelL10nIds(
funcIndex = transform.callNodePath[transform.callNodePath.length - 1];
break;
case 'focus-function':
case 'focus-self':
case 'merge-function':
case 'drop-function':
case 'collapse-direct-recursion':
Expand All @@ -462,6 +483,8 @@ export function getTransformLabelL10nIds(
return { l10nId: 'TransformNavigator--focus-subtree', item: funcName };
case 'focus-function':
return { l10nId: 'TransformNavigator--focus-function', item: funcName };
case 'focus-self':
return { l10nId: 'TransformNavigator--focus-self', item: funcName };
case 'merge-call-node':
return {
l10nId: 'TransformNavigator--merge-call-node',
Expand Down Expand Up @@ -511,6 +534,8 @@ export function applyTransformToCallNodePath(
);
case 'focus-function':
return _startCallNodePathWithFunction(transform.funcIndex, callNodePath);
case 'focus-self':
return _startCallNodePathWithFunction(transform.funcIndex, callNodePath);
case 'focus-category':
return _removeOtherCategoryFunctionsInNodePathWithFunction(
transform.category,
Expand Down Expand Up @@ -1443,6 +1468,53 @@ export function focusFunction(
});
}

export function focusSelf(
thread: Thread,
funcIndexToFocus: IndexIntoFuncTable,
implementation: ImplementationFilter
): Thread {
return timeCode('focusSelf', () => {
const { stackTable, frameTable } = thread;

const funcMatchesImplementation = FUNC_MATCHES[implementation];

const shouldKeepStack = new Uint8Array(stackTable.length);

const newPrefixCol = stackTable.prefix.slice();

for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
const frameIndex = stackTable.frame[stackIndex];
const funcIndex = frameTable.func[frameIndex];

if (funcIndex === funcIndexToFocus) {
shouldKeepStack[stackIndex] = 1;
newPrefixCol[stackIndex] = null;
} else {
const prefix = newPrefixCol[stackIndex];
if (
prefix !== null &&
shouldKeepStack[prefix] === 1 &&
!funcMatchesImplementation(thread, funcIndex)
) {
shouldKeepStack[stackIndex] = 1;
}
}
}

const newStackTable = {
...stackTable,
prefix: newPrefixCol,
};

return updateThreadStacks(thread, newStackTable, (oldStack) => {
if (oldStack === null || shouldKeepStack[oldStack] === 0) {
return null;
}
return oldStack;
});
});
}

export function focusCategory(thread: Thread, category: IndexIntoCategoryList) {
return timeCode('focusCategory', () => {
const { stackTable } = thread;
Expand Down Expand Up @@ -1859,6 +1931,8 @@ export function applyTransform(
return dropFunction(thread, transform.funcIndex);
case 'focus-function':
return focusFunction(thread, transform.funcIndex);
case 'focus-self':
return focusSelf(thread, transform.funcIndex, transform.implementation);
case 'focus-category':
return focusCategory(thread, transform.category);
case 'collapse-resource':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,27 @@ exports[`calltree/CallNodeContextMenu basic rendering renders a full context men
F
</kbd>
</div>
<div
aria-disabled="false"
class="react-contextmenu-item"
role="menuitem"
tabindex="-1"
>
<span
class="react-contextmenu-icon callNodeContextMenuIconFocusSelf"
/>
<div
class="react-contextmenu-item-content"
title="Focusing on self is similar to focusing on a function, but only keeps samples that contribute to the function’s self time. Samples in callees are dropped, and the call tree is re-rooted to the focused function."
>
Focus on self only
</div>
<kbd
class="callNodeContextMenuShortcut"
>
S
</kbd>
</div>
<div
aria-disabled="false"
class="react-contextmenu-item"
Expand Down
Loading