Skip to content
Draft
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
43 changes: 23 additions & 20 deletions src/profile-logic/call-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { ResourceType } from 'firefox-profiler/types';
import ExtensionIcon from '../../res/img/svg/extension.svg';
import { formatCallNodeNumber, formatPercent } from '../utils/format-numbers';
import { assertExhaustiveCheck, ensureExists } from '../utils/types';
import { checkBit } from '../utils/bitset';
import { type BitSet, checkBit, makeBitSet, setBit } from '../utils/bitset';
import * as ProfileData from './profile-data';
import type { CallTreeSummaryStrategy } from '../types/actions';
import type { CallNodeInfo, CallNodeInfoInverted } from './call-node-info';
Expand All @@ -43,7 +43,7 @@ import { getBottomBoxInfoForCallNode } from './bottom-box';
type CallNodeChildren = IndexIntoCallNodeTable[];

export type CallTreeTimingsNonInverted = {
callNodeHasChildren: Uint8Array;
callNodeHasChildren: BitSet;
self: Float64Array;
total: Float64Array;
rootTotalSummary: number; // sum of absolute values, this is used for computing percentages
Expand All @@ -61,7 +61,7 @@ export type CallTreeTimingsInverted = {
rootTotalSummary: number;
sortedRoots: IndexIntoFuncTable[];
totalPerRootFunc: Float64Array;
hasChildrenPerRootFunc: Uint8Array;
hasChildrenPerRootFunc: BitSet;
};

export type CallTreeTimingsFunctionList = {
Expand Down Expand Up @@ -119,7 +119,7 @@ export class CallTreeInternalNonInverted implements CallTreeInternal {
_callNodeInfo: CallNodeInfo;
_callNodeTable: CallNodeTable;
_callTreeTimings: CallTreeTimingsNonInverted;
_callNodeHasChildren: Uint8Array; // A table column matching the callNodeTable
_callNodeHasChildren: BitSet; // A table column matching the callNodeTable

constructor(
callNodeInfo: CallNodeInfo,
Expand Down Expand Up @@ -157,9 +157,12 @@ export class CallTreeInternalNonInverted implements CallTreeInternal {
childCallNodeIndex = this._callNodeTable.nextSibling[childCallNodeIndex]
) {
const childTotalSummary = this._callTreeTimings.total[childCallNodeIndex];
const childHasChildren = this._callNodeHasChildren[childCallNodeIndex];
const childHasChildren = checkBit(
this._callNodeHasChildren,
childCallNodeIndex
);

if (childTotalSummary !== 0 || childHasChildren !== 0) {
if (childTotalSummary !== 0 || childHasChildren) {
children.push(childCallNodeIndex);
}
}
Expand All @@ -172,7 +175,7 @@ export class CallTreeInternalNonInverted implements CallTreeInternal {
}

hasChildren(callNodeIndex: IndexIntoCallNodeTable): boolean {
return this._callNodeHasChildren[callNodeIndex] !== 0;
return checkBit(this._callNodeHasChildren, callNodeIndex);
}

getSelfAndTotal(callNodeIndex: IndexIntoCallNodeTable): SelfAndTotal {
Expand Down Expand Up @@ -242,7 +245,7 @@ class CallTreeInternalInverted implements CallTreeInternal {
_callNodeSelf: Float64Array;
_rootNodes: IndexIntoCallNodeTable[];
_totalPerRootFunc: Float64Array;
_hasChildrenPerRootFunc: Uint8Array;
_hasChildrenPerRootFunc: BitSet;
_totalAndHasChildrenPerNonRootNode: Map<
IndexIntoCallNodeTable,
TotalAndHasChildren
Expand All @@ -268,7 +271,7 @@ class CallTreeInternalInverted implements CallTreeInternal {

hasChildren(callNodeIndex: IndexIntoCallNodeTable): boolean {
if (this._callNodeInfo.isRoot(callNodeIndex)) {
return this._hasChildrenPerRootFunc[callNodeIndex] !== 0;
return checkBit(this._hasChildrenPerRootFunc, callNodeIndex);
}
return this._getTotalAndHasChildren(callNodeIndex).hasChildren;
}
Expand Down Expand Up @@ -790,8 +793,8 @@ export function computeCallTreeTimingsInverted(
const callNodeTableFuncCol = callNodeTable.func;
const callNodeTableDepthCol = callNodeTable.depth;
const totalPerRootFunc = new Float64Array(funcCount);
const hasChildrenPerRootFunc = new Uint8Array(funcCount);
const seenPerRootFunc = new Uint8Array(funcCount);
const hasChildrenPerRootFunc = makeBitSet(funcCount);
const seenPerRootFunc = makeBitSet(funcCount);
const sortedRoots = [];
for (let i = 0; i < callNodeSelf.length; i++) {
const self = callNodeSelf[i];
Expand All @@ -805,12 +808,12 @@ export function computeCallTreeTimingsInverted(
const func = callNodeTableFuncCol[i];

totalPerRootFunc[func] += self;
if (seenPerRootFunc[func] === 0) {
seenPerRootFunc[func] = 1;
if (!checkBit(seenPerRootFunc, func)) {
setBit(seenPerRootFunc, func);
sortedRoots.push(func);
}
if (callNodeTableDepthCol[i] !== 0) {
hasChildrenPerRootFunc[func] = 1;
setBit(hasChildrenPerRootFunc, func);
}
}
sortedRoots.sort(
Expand Down Expand Up @@ -861,7 +864,7 @@ export function computeCallTreeTimingsNonInverted(

// Compute the following variables:
const callNodeTotal = new Float64Array(callNodeTable.length);
const callNodeHasChildren = new Uint8Array(callNodeTable.length);
const callNodeHasChildren = makeBitSet(callNodeTable.length);

// We loop the call node table in reverse, so that we find the children
// before their parents, and the total is known at the time we reach a
Expand All @@ -874,7 +877,7 @@ export function computeCallTreeTimingsNonInverted(
// callNodeTotal[callNodeIndex] is the sum of our children's totals.
// Compute this node's total by adding this node's self.
const total = callNodeTotal[callNodeIndex] + callNodeSelf[callNodeIndex];
const hasChildren = callNodeHasChildren[callNodeIndex] !== 0;
const hasChildren = checkBit(callNodeHasChildren, callNodeIndex);
const hasTotalValue = total !== 0;

callNodeTotal[callNodeIndex] = total;
Expand All @@ -886,7 +889,7 @@ export function computeCallTreeTimingsNonInverted(
const prefixCallNode = callNodeTable.prefix[callNodeIndex];
if (prefixCallNode !== -1) {
callNodeTotal[prefixCallNode] += total;
callNodeHasChildren[prefixCallNode] = 1;
setBit(callNodeHasChildren, prefixCallNode);
}
}

Expand Down Expand Up @@ -940,7 +943,7 @@ function _computeFuncTotal(
// The set of "functions with potentially non-zero totals", stored as an array.
const seenFuncs = [];
// seenPerFunc[func] stores whether seenFuncs.includes(func).
const seenPerFunc = new Uint8Array(funcCount);
const seenPerFunc = makeBitSet(funcCount);

// We loop the call node table in reverse, so that we find the children
// before their parents, and the total is known at the time we reach a
Expand Down Expand Up @@ -979,8 +982,8 @@ function _computeFuncTotal(
funcTotal[func] += total;

// Add this func to the set of funcs with potentially non-zero totals.
if (seenPerFunc[func] === 0) {
seenPerFunc[func] = 1;
if (!checkBit(seenPerFunc, func)) {
setBit(seenPerFunc, func);
seenFuncs.push(func);
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/profile-logic/profile-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ function _computeCallNodeTableExtraColumns(
const innerWindowIDCol = new Float64Array(callNodeCount);
const inlinedIntoCol = new Int32Array(callNodeCount);

const haveFilled = new Uint8Array(callNodeCount);
const haveFilled = makeBitSet(callNodeCount);

for (let stackIndex = 0; stackIndex < stackCount; stackIndex++) {
const category = stackTableCategoryCol[stackIndex];
Expand All @@ -530,7 +530,7 @@ function _computeCallNodeTableExtraColumns(

const callNodeIndex = stackIndexToCallNodeIndex[stackIndex];

if (haveFilled[callNodeIndex] === 0) {
if (!checkBit(haveFilled, callNodeIndex)) {
funcCol[callNodeIndex] = frameTableFuncCol[frameIndex];

categoryCol[callNodeIndex] = category;
Expand All @@ -544,7 +544,7 @@ function _computeCallNodeTableExtraColumns(
innerWindowIDCol[callNodeIndex] = innerWindowID;
}

haveFilled[callNodeIndex] = 1;
setBit(haveFilled, callNodeIndex);
} else {
// Resolve category conflicts, by resetting a conflicting subcategory or
// category to the default category.
Expand Down
11 changes: 6 additions & 5 deletions src/profile-logic/symbolication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
import { PathSet } from '../utils/path';
import { StringTable } from '../utils/string-table';
import { updateRawThreadStacks } from './profile-data';
import { type BitSet, makeBitSet, setBit, checkBit } from '../utils/bitset';

// Contains functions to symbolicate a profile.

Expand Down Expand Up @@ -411,7 +412,7 @@ function finishSymbolicationForLib(
// - stack F with frame 5
function _computeStackTableWithAddedExpansionStacks(
stackTable: RawStackTable,
shouldStacksWithThisOldFrameBeRemoved: Uint8Array,
shouldStacksWithThisOldFrameBeRemoved: BitSet,
frameIndexToInlineExpansionFrames: Map<
IndexIntoFrameTable,
IndexIntoFrameTable[]
Expand All @@ -427,7 +428,7 @@ function _computeStackTableWithAddedExpansionStacks(
const oldPrefix = stackTable.prefix[stack];
const newPrefixOrMinusOne =
oldPrefix === null ? -1 : oldStackToNewStack[oldPrefix];
if (shouldStacksWithThisOldFrameBeRemoved[oldFrame] !== 0) {
if (checkBit(shouldStacksWithThisOldFrameBeRemoved, oldFrame)) {
// Don't add this stack node to the new stack table. Instead, make it
// so that this node's children use our prefix as their prefix.
oldStackToNewStack[stack] = newPrefixOrMinusOne;
Expand Down Expand Up @@ -470,7 +471,7 @@ export function applySymbolicationSteps(
} {
const oldFuncToNewFuncsMap: FuncToFuncsMap = new Map();
const frameCount = oldShared.frameTable.length;
const shouldStacksWithThisFrameBeRemoved = new Uint8Array(frameCount);
const shouldStacksWithThisFrameBeRemoved = makeBitSet(frameCount);
const frameIndexToInlineExpansionFrames = new Map<
IndexIntoFrameTable,
IndexIntoFrameTable[]
Expand Down Expand Up @@ -541,7 +542,7 @@ function _partiallyApplySymbolicationStep(
shared: RawProfileSharedData,
symbolicationStepInfo: SymbolicationStepInfo,
oldFuncToNewFuncsMap: FuncToFuncsMap,
shouldStacksWithThisFrameBeRemoved: Uint8Array,
shouldStacksWithThisFrameBeRemoved: BitSet,
frameIndexToInlineExpansionFrames: Map<
IndexIntoFrameTable,
IndexIntoFrameTable[]
Expand Down Expand Up @@ -585,7 +586,7 @@ function _partiallyApplySymbolicationStep(
for (const frameIndex of allFramesForThisLib) {
if (oldFrameTable.inlineDepth[frameIndex] > 0) {
inlinedFrames.push(frameIndex);
shouldStacksWithThisFrameBeRemoved[frameIndex] = 1;
setBit(shouldStacksWithThisFrameBeRemoved, frameIndex);
} else {
nonInlinedFrames.push(frameIndex);
}
Expand Down
27 changes: 13 additions & 14 deletions src/profile-logic/transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
translateFuncIndex,
translateResourceIndex,
} from './index-translation';
import { checkBit, makeBitSet, setBit } from 'firefox-profiler/utils/bitset';

/**
* This file contains the functions and logic for working with and applying transforms
Expand Down Expand Up @@ -926,9 +927,7 @@ export function dropFunction(
const { stackTable, frameTable } = thread;

// Go through each stack, and label it as containing the function or not.
// stackContainsFunc is a stackIndex => bool map, implemented as a U8 typed
// array for better performance. 0 means false, 1 means true.
const stackContainsFunc = new Uint8Array(stackTable.length);
const stackContainsFunc = makeBitSet(stackTable.length);
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
const prefix = stackTable.prefix[stackIndex];
const frameIndex = stackTable.frame[stackIndex];
Expand All @@ -937,15 +936,15 @@ export function dropFunction(
// This is the function we want to remove.
funcIndex === funcIndexToDrop ||
// The parent of this stack contained the function.
(prefix !== null && stackContainsFunc[prefix] === 1)
(prefix !== null && checkBit(stackContainsFunc, prefix))
) {
stackContainsFunc[stackIndex] = 1;
setBit(stackContainsFunc, stackIndex);
}
}

return updateThreadStacks(thread, stackTable, (stack) =>
// Drop the stacks that contain that function.
stack !== null && stackContainsFunc[stack] === 1 ? null : stack
stack !== null && checkBit(stackContainsFunc, stack) ? null : stack
);
}

Expand Down Expand Up @@ -1214,19 +1213,19 @@ export function collapseFunctionSubtree(
): Thread {
const { stackTable, frameTable } = thread;
const oldStackToNewStack = new Int32Array(stackTable.length);
const isInCollapsedSubtree = new Uint8Array(stackTable.length);
const isInCollapsedSubtree = makeBitSet(stackTable.length);

for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
const prefix = stackTable.prefix[stackIndex];
if (prefix !== null && isInCollapsedSubtree[prefix] !== 0) {
if (prefix !== null && checkBit(isInCollapsedSubtree, prefix)) {
oldStackToNewStack[stackIndex] = oldStackToNewStack[prefix];
isInCollapsedSubtree[stackIndex] = 1;
setBit(isInCollapsedSubtree, stackIndex);
} else {
oldStackToNewStack[stackIndex] = stackIndex;
const frameIndex = stackTable.frame[stackIndex];
const funcIndex = frameTable.func[frameIndex];
if (funcToCollapse === funcIndex) {
isInCollapsedSubtree[stackIndex] = 1;
setBit(isInCollapsedSubtree, stackIndex);
}
}
}
Expand Down Expand Up @@ -1740,24 +1739,24 @@ export function funcHasRecursiveCall(
funcToCheck: IndexIntoFuncTable
) {
// Set of stack indices that are funcToCheck or have a funcToCheck ancestor.
const ancestorOfCallNodeContainsFuncToCheck = new Uint8Array(
const ancestorOfCallNodeContainsFuncToCheck = makeBitSet(
callNodeTable.length
);

for (let i = 0; i < callNodeTable.length; i++) {
const prefix = callNodeTable.prefix[i];
const funcIndex = callNodeTable.func[i];
const recursivePrefix =
prefix !== -1 && ancestorOfCallNodeContainsFuncToCheck[prefix] !== 0;
prefix !== -1 && checkBit(ancestorOfCallNodeContainsFuncToCheck, prefix);

if (funcToCheck === funcIndex) {
if (recursivePrefix) {
// This function matches and so did one of its ancestors.
return true;
}
ancestorOfCallNodeContainsFuncToCheck[i] = 1;
setBit(ancestorOfCallNodeContainsFuncToCheck, i);
} else if (recursivePrefix) {
ancestorOfCallNodeContainsFuncToCheck[i] = 1;
setBit(ancestorOfCallNodeContainsFuncToCheck, i);
}
}
return false;
Expand Down
24 changes: 4 additions & 20 deletions src/test/store/__snapshots__/profile-view.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2069,16 +2069,8 @@ CallTree {
"_children": Array [],
"_displayDataByIndex": Map {},
"_internal": CallTreeInternalNonInverted {
"_callNodeHasChildren": Uint8Array [
1,
1,
0,
0,
0,
1,
0,
0,
0,
"_callNodeHasChildren": Int32Array [
35,
],
"_callNodeInfo": CallNodeInfoNonInverted {
"_cache": Map {},
Expand Down Expand Up @@ -2301,16 +2293,8 @@ CallTree {
],
},
"_callTreeTimings": Object {
"callNodeHasChildren": Uint8Array [
1,
1,
0,
0,
0,
1,
0,
0,
0,
"callNodeHasChildren": Int32Array [
35,
],
"rootTotalSummary": 2,
"self": Float64Array [
Expand Down
8 changes: 7 additions & 1 deletion src/test/unit/profile-tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getSampleIndexToCallNodeIndex,
} from '../../profile-logic/profile-data';
import { ResourceType } from 'firefox-profiler/types';
import { makeBitSet, setBit } from '../../utils/bitset';
import {
callTreeFromProfile,
functionListTreeFromProfile,
Expand Down Expand Up @@ -79,11 +80,16 @@ describe('unfiltered call tree', function () {
callNodeInfo.getCallNodeTable().length
)
);
const expectedHasChildren = makeBitSet(9);
// Nodes 0, 1, 2, 3, 5, 7 have children.
for (const i of [0, 1, 2, 3, 5, 7]) {
setBit(expectedHasChildren, i);
}
expect(callTreeTimings).toEqual({
type: 'NON_INVERTED',
timings: {
rootTotalSummary: 3,
callNodeHasChildren: new Uint8Array([1, 1, 1, 1, 0, 1, 0, 1, 0]),
callNodeHasChildren: expectedHasChildren,
self: new Float64Array([0, 0, 0, 0, 1, 0, 1, 0, 1]),
total: new Float64Array([3, 3, 2, 1, 1, 1, 1, 1, 1]),
},
Expand Down
Loading