Skip to content
Merged
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
14 changes: 14 additions & 0 deletions res/drop-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/components/calltree/ProfileCallTreeContextMenu.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
background: url(../../../res/focus-icon.svg) center center no-repeat;
}

.profileCallTreeContextMenuIconDrop {
background: url(../../../res/drop-icon.svg) center center no-repeat;
}

.profileCallTreeContextMenuLabel {
color: #555;
font-weight: bold;
Expand Down
23 changes: 11 additions & 12 deletions src/components/calltree/ProfileCallTreeContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ class ProfileCallTreeContextMenu extends PureComponent<Props> {
break;
case 'merge-call-node':
case 'merge-function':
case 'merge-subtree':
case 'focus-subtree':
case 'focus-function':
case 'collapse-resource':
case 'collapse-direct-recursion':
case 'drop-function':
this.addTransformToStack(type);
break;
default:
Expand Down Expand Up @@ -146,14 +146,6 @@ class ProfileCallTreeContextMenu extends PureComponent<Props> {
funcIndex: selectedFunc,
});
break;
case 'merge-subtree':
addTransformToStack(threadIndex, {
type: 'merge-subtree',
callNodePath: selectedCallNodePath,
implementation,
inverted,
});
break;
case 'merge-call-node':
addTransformToStack(threadIndex, {
type: 'merge-call-node',
Expand All @@ -167,6 +159,12 @@ class ProfileCallTreeContextMenu extends PureComponent<Props> {
funcIndex: selectedFunc,
});
break;
case 'drop-function':
addTransformToStack(threadIndex, {
type: 'drop-function',
funcIndex: selectedFunc,
});
break;
case 'collapse-resource': {
const { funcTable } = thread;
const resourceIndex = funcTable.resource[selectedFunc];
Expand Down Expand Up @@ -268,9 +266,6 @@ class ProfileCallTreeContextMenu extends PureComponent<Props> {
<span className="profileCallTreeContextMenuIcon profileCallTreeContextMenuIconMerge" />
Merge function into caller across the entire tree
</MenuItem>
{/* <MenuItem onClick={this.handleClick} data={{ type: 'mergeSubtree' }}>
Merge subtree into calling function
</MenuItem> */}
<MenuItem onClick={this.handleClick} data={{ type: 'focus-subtree' }}>
<span className="profileCallTreeContextMenuIcon profileCallTreeContextMenuIconFocus" />
Focus on subtree
Expand Down Expand Up @@ -302,6 +297,10 @@ class ProfileCallTreeContextMenu extends PureComponent<Props> {
Collapse direct recursion
</MenuItem>
: null}
<MenuItem onClick={this.handleClick} data={{ type: 'drop-function' }}>
<span className="profileCallTreeContextMenuIcon profileCallTreeContextMenuIconDrop" />
Drop samples with this function
</MenuItem>
<div className="react-contextmenu-separator" />
<MenuItem
onClick={this.handleClick}
Expand Down
78 changes: 61 additions & 17 deletions src/profile-logic/transforms.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ import type { Transform, TransformStack } from '../types/transforms';
const TRANSFORM_TO_SHORT_KEY = {
'focus-subtree': 'f',
'focus-function': 'ff',
'merge-subtree': 'ms',
'merge-call-node': 'mcn',
'merge-function': 'mf',
'drop-function': 'df',
'collapse-resource': 'cr',
'collapse-direct-recursion': 'rec',
};

const SHORT_KEY_TO_TRANSFORM = {
f: 'focus-subtree',
ff: 'focus-function',
ms: 'merge-subtree',
mcn: 'merge-call-node',
mf: 'merge-function',
df: 'drop-function',
cr: 'collapse-resource',
rec: 'collapse-direct-recursion',
};
Expand Down Expand Up @@ -108,6 +108,7 @@ export function parseTransforms(stringValue: string = ''): TransformStack {
break;
}
case 'merge-function':
case 'drop-function':
case 'focus-function': {
// e.g. "mf-325"
const [, funcIndexRaw] = tuple;
Expand All @@ -127,15 +128,20 @@ export function parseTransforms(stringValue: string = ''): TransformStack {
funcIndex,
});
break;
case 'drop-function':
transforms.push({
type: 'drop-function',
funcIndex,
});
break;
default:
throw new Error('Unmatched transform.');
}
}
break;
}
case 'focus-subtree':
case 'merge-call-node':
case 'merge-subtree': {
case 'merge-call-node': {
// e.g. "f-js-xFFpUMl-i" or "f-cpp-0KV4KV5KV61KV7KV8K"
const [
,
Expand Down Expand Up @@ -163,14 +169,6 @@ export function parseTransforms(stringValue: string = ''): TransformStack {
callNodePath,
});
break;
case 'merge-subtree':
transforms.push({
type: 'merge-subtree',
implementation,
callNodePath,
inverted,
});
break;
default:
throw new Error('Unmatched transform.');
}
Expand All @@ -193,6 +191,7 @@ export function stringifyTransforms(transforms: TransformStack = []): string {
const shortKey = TRANSFORM_TO_SHORT_KEY[transform.type];
switch (transform.type) {
case 'merge-function':
case 'drop-function':
return `${shortKey}-${transform.funcIndex}`;
case 'focus-function': {
let string = `${shortKey}-${transform.funcIndex}`;
Expand All @@ -206,8 +205,7 @@ export function stringifyTransforms(transforms: TransformStack = []): string {
case 'collapse-direct-recursion':
return `${shortKey}-${transform.implementation}-${transform.funcIndex}`;
case 'focus-subtree':
case 'merge-call-node':
case 'merge-subtree': {
case 'merge-call-node': {
let string = [
shortKey,
transform.implementation,
Expand Down Expand Up @@ -252,12 +250,12 @@ export function getTransformLabels(
let funcIndex;
switch (transform.type) {
case 'focus-subtree':
case 'merge-subtree':
case 'merge-call-node':
funcIndex = transform.callNodePath[transform.callNodePath.length - 1];
break;
case 'focus-function':
case 'merge-function':
case 'drop-function':
case 'collapse-direct-recursion':
funcIndex = transform.funcIndex;
break;
Expand All @@ -272,12 +270,12 @@ export function getTransformLabels(
return `Focus Node: ${funcName}`;
case 'focus-function':
return `Focus: ${funcName}`;
case 'merge-subtree':
return `Merge Subtree: ${funcName}`;
case 'merge-call-node':
return `Merge Node: ${funcName}`;
case 'merge-function':
return `Merge: ${funcName}`;
case 'drop-function':
return `Drop: ${funcName}`;
case 'collapse-direct-recursion':
return `Collapse recursion: ${funcName}`;
default:
Expand Down Expand Up @@ -305,6 +303,8 @@ export function applyTransformToCallNodePath(
return _mergeNodeInCallNodePath(transform.callNodePath, callNodePath);
case 'merge-function':
return _mergeFunctionInCallNodePath(transform.funcIndex, callNodePath);
case 'drop-function':
return _dropFunctionInCallNodePath(transform.funcIndex, callNodePath);
case 'collapse-resource':
return _collapseResourceInCallNodePath(
transform.resourceIndex,
Expand Down Expand Up @@ -357,6 +357,14 @@ function _mergeFunctionInCallNodePath(
return callNodePath.filter(nodeFunc => nodeFunc !== funcIndex);
}

function _dropFunctionInCallNodePath(
funcIndex: IndexIntoFuncTable,
callNodePath: CallNodePath
): CallNodePath {
// If the CallNodePath contains the function, return an empty path.
return callNodePath.includes(funcIndex) ? [] : callNodePath;
}

function _collapseResourceInCallNodePath(
resourceIndex: IndexIntoResourceTable,
collapsedFuncIndex: IndexIntoFuncTable,
Expand Down Expand Up @@ -572,6 +580,42 @@ export function mergeFunction(
});
}

/**
* Drop any samples that contain the given function.
*/
export function dropFunction(
thread: Thread,
funcIndexToDrop: IndexIntoFuncTable
) {
const { stackTable, frameTable, samples } = thread;

// Go through each stack, and label it as containing the function or not.
const stackContainsFunc: Array<void | true> = [];
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
const prefix = stackTable.prefix[stackIndex];
const frameIndex = stackTable.frame[stackIndex];
const funcIndex = frameTable.func[frameIndex];
if (
// This is the function we want to remove.
funcIndex === funcIndexToDrop ||
// The parent of this stack contained the function.
(prefix !== null && stackContainsFunc[prefix])
) {
stackContainsFunc[stackIndex] = true;
}
}

// Regenerate the stacks for the samples table.
const stack = samples.stack.map(
stack => (stack !== null && stackContainsFunc[stack] ? null : stack)
);

// Return the thread with the replaced samples.
return Object.assign({}, thread, {
samples: Object.assign({}, samples, { stack }),
});
}

export function collapseResource(
thread: Thread,
resourceIndexToCollapse: IndexIntoResourceTable,
Expand Down
5 changes: 2 additions & 3 deletions src/reducers/profile-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,6 @@ export const selectorsForThread = (
transform.callNodePath,
transform.implementation
);
case 'merge-subtree':
// TODO - Implement this transform.
return thread;
case 'merge-call-node':
return Transforms.mergeCallNode(
thread,
Expand All @@ -524,6 +521,8 @@ export const selectorsForThread = (
);
case 'merge-function':
return Transforms.mergeFunction(thread, transform.funcIndex);
case 'drop-function':
return Transforms.dropFunction(thread, transform.funcIndex);
case 'focus-function':
return Transforms.focusFunction(thread, transform.funcIndex);
case 'collapse-resource':
Expand Down
46 changes: 46 additions & 0 deletions src/test/store/transforms.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,52 @@ describe('"merge-function" transform', function() {
});
});

describe('"drop-function" transform', function() {
describe('on a call tree', function() {
const { profile, funcNames } = getProfileFromTextSamples(`
A A A A
B B C B
C C E
D
`);
const threadIndex = 0;
const C = funcNames.indexOf('C');

const { dispatch, getState } = storeWithProfile(profile);
const originalCallTree = selectedThreadSelectors.getCallTree(getState());

function formatTreeIntoArray(tree) {
return formatTree(tree).split('\n').filter(string => string);
}

it('starts as an unfiltered call tree', function() {
expect(formatTreeIntoArray(originalCallTree)).toEqual([
'- A (total: 4, self:—)',
' - B (total: 3, self:—)',
' - C (total: 2, self:1)',
' - D (total: 1, self:1)',
' - E (total: 1, self:1)',
' - C (total: 1, self:1)',
]);
});

it('function C can be merged into callers', function() {
dispatch(
addTransformToStack(threadIndex, {
type: 'drop-function',
funcIndex: C,
})
);
const callTree = selectedThreadSelectors.getCallTree(getState());
expect(formatTreeIntoArray(callTree)).toEqual([
'- A (total: 1, self:—)',
' - B (total: 1, self:—)',
' - E (total: 1, self:1)',
]);
});
});
});

describe('"focus-function" transform', function() {
describe('on a call tree', function() {
/**
Expand Down
12 changes: 5 additions & 7 deletions src/test/url-handling.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ describe('url upgrading', function() {

describe('URL serialization of the transform stack', function() {
const transformString =
'f-combined-012~ms-js-123~mcn-combined-234~f-js-345-i~mf-6~ff-7~cr-combined-8-9~rec-combined-10';
'f-combined-012~mcn-combined-234~f-js-345-i~mf-6~ff-7~cr-combined-8-9~rec-combined-10~df-11';
const { getState } = _getStoreWithURL({
search: '?transforms=' + transformString,
});
Expand All @@ -254,12 +254,6 @@ describe('URL serialization of the transform stack', function() {
implementation: 'combined',
inverted: false,
},
{
type: 'merge-subtree',
callNodePath: [1, 2, 3],
implementation: 'js',
inverted: false,
},
{
type: 'merge-call-node',
callNodePath: [2, 3, 4],
Expand Down Expand Up @@ -290,6 +284,10 @@ describe('URL serialization of the transform stack', function() {
funcIndex: 10,
implementation: 'combined',
},
{
type: 'drop-function',
funcIndex: 11,
},
]);
});

Expand Down
Loading