Skip to content

Commit 301d3dc

Browse files
committed
Use Int32Array for the stackTable.prefix column.
The `prefix` column of both `RawStackTable` and `StackTable` is now an `Int32Array`, with `-1` (instead of `null`) indicating that a stack node is a root. The wire format changes accordingly, so the processed profile version is bumped to 63 with an upgrader that converts the column on load.
1 parent d116be9 commit 301d3dc

27 files changed

Lines changed: 562 additions & 449 deletions

docs-developer/CHANGELOG-formats.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ Note that this is not an exhaustive list. Processed profile format upgraders can
66

77
## Processed profile format
88

9+
### Version 66
10+
11+
The `prefix` column of `profile.shared.stackTable` now uses `-1` instead of `null`
12+
to indicate "this stack node is a root". In-memory it is now an `Int32Array`;
13+
on the JSON wire, the column is just an array of numbers (where `-1` marks a
14+
root).
15+
916
### Version 65
1017

1118
The stack table's `frame` column (stored at `profile.shared.stackTable.frame`) can now optionally be stored as an `Int32Array`, for profiles loaded from [JsonSlabs](https://github.com/mstange/json-slabs/) files (.jslb, .jslb.gz). Regular JS / JSON arrays are still accepted.

src/app-logic/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const GECKO_PROFILE_VERSION = 34;
1212
// The current version of the "processed" profile format.
1313
// Please don't forget to update the processed profile format changelog in
1414
// `docs-developer/CHANGELOG-formats.md`.
15-
export const PROCESSED_PROFILE_VERSION = 65;
15+
export const PROCESSED_PROFILE_VERSION = 66;
1616

1717
// The following are the margin sizes for the left and right of the timeline. Independent
1818
// components need to share these values.

src/app-logic/url-handling.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,8 +1649,9 @@ function getStackIndexFromVersion3JSCallNodePath(
16491649
oldCallNodePath: CallNodePath
16501650
): IndexIntoStackTable | null {
16511651
const { stackTable, funcTable, frameTable } = shared;
1652-
const stackIndexDepth: Map<IndexIntoStackTable | null, number> = new Map();
1653-
stackIndexDepth.set(null, -1);
1652+
const stackIndexDepth: Map<IndexIntoStackTable, number> = new Map();
1653+
// Map key -1 represents the virtual root above all stack indexes.
1654+
stackIndexDepth.set(-1, -1);
16541655

16551656
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
16561657
const prefix = stackTable.prefix[stackIndex];
@@ -1659,9 +1660,8 @@ function getStackIndexFromVersion3JSCallNodePath(
16591660
const isJS = funcTable.isJS[funcIndex];
16601661
// We know that at this point stack table is sorted and the following
16611662
// condition holds:
1662-
// assert(prefixStack === null || prefixStack < stackIndex);
1663-
const doesPrefixMatchCallNodePath =
1664-
prefix === null || stackIndexDepth.has(prefix);
1663+
// assert(prefixStack === -1 || prefixStack < stackIndex);
1664+
const doesPrefixMatchCallNodePath = stackIndexDepth.has(prefix);
16651665

16661666
if (!doesPrefixMatchCallNodePath) {
16671667
continue;
@@ -1696,8 +1696,8 @@ function getVersion4JSCallNodePathFromStackIndex(
16961696
): CallNodePath {
16971697
const { funcTable, stackTable, frameTable } = shared;
16981698
const callNodePath = [];
1699-
let nextStackIndex: IndexIntoStackTable | null = stackIndex;
1700-
while (nextStackIndex !== null) {
1699+
let nextStackIndex: IndexIntoStackTable = stackIndex;
1700+
while (nextStackIndex !== -1) {
17011701
const frameIndex: IndexIntoFrameTable = stackTable.frame[nextStackIndex];
17021702
const funcIndex = frameTable.func[frameIndex];
17031703
if (funcTable.isJS[funcIndex] || funcTable.relevantForJS[funcIndex]) {

src/profile-logic/address-timings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export function getStackAddressInfo(
127127
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
128128
const prefixStack = stackTable.prefix[stackIndex];
129129
const prefixAddressSet: IndexIntoAddressSetTable | -1 =
130-
prefixStack !== null ? stackIndexToAddressSetIndex[prefixStack] : -1;
130+
prefixStack !== -1 ? stackIndexToAddressSetIndex[prefixStack] : -1;
131131

132132
const frame = stackTable.frame[stackIndex];
133133
const nativeSymbolOfThisStack = frameTable.nativeSymbol[frame];

src/profile-logic/data-structures.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,14 @@ export function getRawStackTableBuilder(): RawStackTableBuilder {
7070
export function getRawStackTableBuilderWithExistingContents(
7171
existing: RawStackTable
7272
): RawStackTableBuilder {
73+
const prefix = new Array<IndexIntoStackTable | null>(existing.length);
74+
for (let i = 0; i < existing.length; i++) {
75+
const p = existing.prefix[i];
76+
prefix[i] = p === -1 ? null : p;
77+
}
7378
return {
7479
frame: [...existing.frame],
75-
prefix: [...existing.prefix],
80+
prefix,
7681
length: existing.length,
7782
};
7883
}
@@ -81,9 +86,14 @@ export function finishRawStackTableBuilder(
8186
builder: RawStackTableBuilder
8287
): RawStackTable {
8388
const { frame, prefix, length } = builder;
89+
const prefixCol = new Int32Array(length);
90+
for (let i = 0; i < length; i++) {
91+
const p = prefix[i];
92+
prefixCol[i] = p === null ? -1 : p;
93+
}
8494
return {
8595
frame: new Int32Array(frame),
86-
prefix,
96+
prefix: prefixCol,
8797
length,
8898
};
8999
}

src/profile-logic/import/chrome.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
import type {
66
Profile,
77
RawThread,
8-
RawStackTable,
98
IndexIntoStackTable,
109
MixedObject,
1110
} from 'firefox-profiler/types';
1211

13-
import { getEmptyProfile, getEmptyThread } from '../data-structures';
12+
import {
13+
getEmptyProfile,
14+
getEmptyThread,
15+
type RawStackTableBuilder,
16+
} from '../data-structures';
1417
import type { StringTable } from '../../utils/string-table';
1518
import { ensureExists, coerce } from '../../utils/types';
1619
import {
@@ -868,7 +871,7 @@ function getImageSize(
868871
* For sanity, check that stacks are ordered where the prefix stack
869872
* always preceeds the current stack index in the StackTable.
870873
*/
871-
function assertStackOrdering(stackTable: RawStackTable) {
874+
function assertStackOrdering(stackTable: RawStackTableBuilder) {
872875
const visitedStacks = new Set<number | null>([null]);
873876
for (let i = 0; i < stackTable.length; i++) {
874877
if (!visitedStacks.has(stackTable.prefix[i])) {

src/profile-logic/insert-stack-labels.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import type {
66
IndexIntoFrameTable,
7-
IndexIntoStackTable,
87
RawStackTable,
98
IndexIntoFuncTable,
109
Profile,
@@ -199,7 +198,7 @@ export function insertStackLabels(
199198
for (let stackIndex = 0; stackIndex < oldStackTable.length; stackIndex++) {
200199
const parentStackIndex = oldStackTable.prefix[stackIndex];
201200
const inheritedLabelFrameIndex =
202-
parentStackIndex !== null
201+
parentStackIndex !== -1
203202
? inheritedLabelFrameIndexAtStack[parentStackIndex]
204203
: null;
205204
const frameIndex = oldStackTable.frame[stackIndex];
@@ -218,7 +217,7 @@ export function insertStackLabels(
218217
) {
219218
labelFrameIndexToInsertAtStack[stackIndex] = null;
220219
inheritedLabelFrameIndexAtStack[stackIndex] = null;
221-
} else if (parentStackIndex === null) {
220+
} else if (parentStackIndex === -1) {
222221
labelFrameIndexToInsertAtStack[stackIndex] = rootLabelFrameIndex;
223222
inheritedLabelFrameIndexAtStack[stackIndex] = rootLabelFrameIndex;
224223
stacksToInsertCount++;
@@ -230,7 +229,7 @@ export function insertStackLabels(
230229

231230
// Now compute the new stack table.
232231
const newStackCount = oldStackTable.length + stacksToInsertCount;
233-
const newPrefixCol = new Array<IndexIntoStackTable | null>(newStackCount);
232+
const newPrefixCol = new Int32Array(newStackCount);
234233
const newFrameCol = new Array<IndexIntoFrameTable>(newStackCount);
235234
const oldStackToNewStackPlusOne = new Int32Array(oldStackTable.length);
236235
let nextNewStackIndex = 0;
@@ -243,7 +242,7 @@ export function insertStackLabels(
243242
labelFrameIndexToInsertAtStack[oldStackIndex];
244243
const oldPrefix = oldStackTable.prefix[oldStackIndex];
245244
let newPrefix =
246-
oldPrefix !== null ? oldStackToNewStackPlusOne[oldPrefix] - 1 : null;
245+
oldPrefix !== -1 ? oldStackToNewStackPlusOne[oldPrefix] - 1 : -1;
247246
const frameIndex = oldStackTable.frame[oldStackIndex];
248247
if (labelFrameIndexToInsert !== null) {
249248
const insertedStackIndex = nextNewStackIndex++;

src/profile-logic/line-timings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function getStackLineInfo(
5757
for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) {
5858
const prefixStack = stackTable.prefix[stackIndex];
5959
const prefixLineSet: IndexIntoLineSetTable | -1 =
60-
prefixStack !== null ? stackIndexToLineSetIndex[prefixStack] : -1;
60+
prefixStack !== -1 ? stackIndexToLineSetIndex[prefixStack] : -1;
6161

6262
const frame = stackTable.frame[stackIndex];
6363
const func = frameTable.func[frame];

src/profile-logic/merge-compare.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1132,7 +1132,7 @@ function mergeStackTables(
11321132
);
11331133
const prefix = stackTable.prefix[i];
11341134
const newPrefix =
1135-
prefix === null ? null : oldStackToNewStackPlusOne[prefix] - 1;
1135+
prefix === -1 ? null : oldStackToNewStackPlusOne[prefix] - 1;
11361136

11371137
newStackTable.frame.push(frameIndex);
11381138
newStackTable.prefix.push(newPrefix);

src/profile-logic/processed-profile-versioning.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,6 @@ export function attemptToUpgradeProcessedProfileThroughMutation(
8080
? meta.preprocessedProfileVersion
8181
: UNANNOTATED_VERSION;
8282

83-
if (profileVersion === PROCESSED_PROFILE_VERSION) {
84-
return profile;
85-
}
86-
8783
if (profileVersion > PROCESSED_PROFILE_VERSION) {
8884
throw new Error(
8985
`Unable to parse a processed profile of version ${profileVersion}, most likely profiler.firefox.com needs to be refreshed. ` +
@@ -103,12 +99,24 @@ export function attemptToUpgradeProcessedProfileThroughMutation(
10399
}
104100
}
105101

102+
_normalizeAfterUpgrade(profile);
103+
106104
const upgradedProfile = profile as Profile;
107105
upgradedProfile.meta.preprocessedProfileVersion = PROCESSED_PROFILE_VERSION;
108106

109107
return upgradedProfile;
110108
}
111109

110+
// Fixups for things that don't survive JSON roundtripping at the current
111+
// profile version, such as typed array columns being parsed as regular
112+
// arrays. Idempotent; safe to run on an already-normalized profile.
113+
function _normalizeAfterUpgrade(profile: any): void {
114+
const stackTable = profile.shared?.stackTable ?? null;
115+
if (stackTable && !(stackTable.prefix instanceof Int32Array)) {
116+
stackTable.prefix = new Int32Array(stackTable.prefix);
117+
}
118+
}
119+
112120
function _archFromAbi(abi: string): string {
113121
if (abi === 'x86_64-gcc3') {
114122
return 'x86_64';
@@ -3260,6 +3268,22 @@ const _upgraders: {
32603268
// `IndexIntoFrameTable[]` to `IndexIntoFrameTable[] | Int32Array<ArrayBuffer>`.
32613269
// All valid v64 profiles are valid v65 profiles, so no upgrader is needed.
32623270
},
3271+
[66]: (profile: any) => {
3272+
// The stackTable.prefix column is now stored as an Int32Array with -1
3273+
// meaning "root", instead of an Array<number | null> with null meaning
3274+
// "root". Convert any null entries to -1 and turn the column into an
3275+
// Int32Array.
3276+
const stackTable = profile.shared?.stackTable ?? null;
3277+
if (stackTable && Array.isArray(stackTable.prefix)) {
3278+
const oldPrefix = stackTable.prefix;
3279+
const newPrefix = new Int32Array(oldPrefix.length);
3280+
for (let i = 0; i < oldPrefix.length; i++) {
3281+
const p = oldPrefix[i];
3282+
newPrefix[i] = p === null ? -1 : p;
3283+
}
3284+
stackTable.prefix = newPrefix;
3285+
}
3286+
},
32633287
// If you add a new upgrader here, please document the change in
32643288
// `docs-developer/CHANGELOG-formats.md`.
32653289
};

0 commit comments

Comments
 (0)