diff --git a/src/profile-logic/profile-compacting.ts b/src/profile-logic/profile-compacting.ts index e575383286..1a5ce13aec 100644 --- a/src/profile-logic/profile-compacting.ts +++ b/src/profile-logic/profile-compacting.ts @@ -3,7 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { computeStringIndexMarkerFieldsByDataType } from './marker-schema'; -import { type BitSet, makeBitSet, setBit, checkBit } from '../utils/bitset'; +import { + type BitSet, + makeBitSet, + setBit, + clearBit, + checkBit, +} from '../utils/bitset'; import type { Profile, @@ -18,6 +24,7 @@ import type { NativeSymbolTable, Lib, SourceTable, + IndexIntoFrameTable, } from 'firefox-profiler/types'; import { assertExhaustiveCheck, @@ -78,13 +85,25 @@ const ColDesc = { class TableCompactionState { markBuffer: BitSet; oldIndexToNewIndexPlusOne: Int32Array; + oldIndexToCanonicalOldIndexPlusOne: Int32Array; + hasCanonicalRedirects: boolean = false; // whether oldIndexToCanonicalOldIndexPlusOne has any non-zero values newLength: number | null = null; constructor(itemCount: number) { this.markBuffer = makeBitSet(itemCount); + this.oldIndexToCanonicalOldIndexPlusOne = new Int32Array(itemCount); this.oldIndexToNewIndexPlusOne = new Int32Array(itemCount); } + redirectOldIndexToCanonicalOldIndex( + redirected: number, + canonical: number + ): void { + clearBit(this.markBuffer, redirected); + this.oldIndexToCanonicalOldIndexPlusOne[redirected] = canonical + 1; + this.hasCanonicalRedirects = true; + } + computeIndexTranslation(): void { let newLength = 0; for (let i = 0; i < this.oldIndexToNewIndexPlusOne.length; i++) { @@ -94,6 +113,20 @@ class TableCompactionState { } } this.newLength = newLength; + + if (this.hasCanonicalRedirects) { + // Patch redirected (deduped-away) rows so any reference to them + // resolves to their canonical row's new index. For tables that didn't + // dedup, oldIndexToCanonicalOldIndexPlusOne is all zeros and this loop is a no-op. + for (let i = 0; i < this.oldIndexToCanonicalOldIndexPlusOne.length; i++) { + const canonicalOldIndex = + this.oldIndexToCanonicalOldIndexPlusOne[i] - 1; + if (canonicalOldIndex !== -1) { + this.oldIndexToNewIndexPlusOne[i] = + this.oldIndexToNewIndexPlusOne[canonicalOldIndex]; + } + } + } } } @@ -190,39 +223,27 @@ export function computeCompactedProfile( _gatherReferencesInThread(thread, tcs, stringIndexMarkerFieldsByDataType); } - // The order of the _markTableAndComputeTranslation calls is important! + // The order of the _markTable calls is important! // We only want to mark data that is (transitively) used by thread data. // So, for example, we have to mark the funcTable before we mark the // sources, so that, by the time we look at the sources, we already know // which sources are (transitively) referenced. - _markTableAndComputeTranslation( - shared.stackTable, - tcs.stackTable, - stackTableDesc - ); - _markTableAndComputeTranslation( - shared.frameTable, - tcs.frameTable, - frameTableDesc - ); - _markTableAndComputeTranslation( - shared.funcTable, - tcs.funcTable, - funcTableDesc - ); - _markTableAndComputeTranslation( - shared.resourceTable, - tcs.resourceTable, - resourceTableDesc - ); - _markTableAndComputeTranslation( - shared.nativeSymbols, - tcs.nativeSymbols, - nativeSymbolsDesc - ); - _markTableAndComputeTranslation(shared.sources, tcs.sources, sourcesDesc); - tcs.stringArray.computeIndexTranslation(); + _markTable(shared.stackTable, tcs.stackTable, stackTableDesc); + _markTable(shared.frameTable, tcs.frameTable, frameTableDesc); + _markTable(shared.funcTable, tcs.funcTable, funcTableDesc); + _markTable(shared.resourceTable, tcs.resourceTable, resourceTableDesc); + _markTable(shared.nativeSymbols, tcs.nativeSymbols, nativeSymbolsDesc); + _markTable(shared.sources, tcs.sources, sourcesDesc); + tcs.libs.computeIndexTranslation(); + tcs.stringArray.computeIndexTranslation(); + tcs.sources.computeIndexTranslation(); + tcs.nativeSymbols.computeIndexTranslation(); + tcs.resourceTable.computeIndexTranslation(); + tcs.funcTable.computeIndexTranslation(); + _dedupFrameTable(shared.frameTable, tcs.frameTable); + tcs.frameTable.computeIndexTranslation(); + tcs.stackTable.computeIndexTranslation(); // Step 2: Create new tables for everything, skipping unreferenced entries. // The order of calls to _compactTable doesn't matter - we've already computed @@ -279,7 +300,7 @@ export function computeCompactedProfile( // --- Step 1: Marking --- -function _markTableAndComputeTranslation( +function _markTable( table: T, thisTableCompactionState: TableCompactionState, tableDesc: TableDescription @@ -327,8 +348,6 @@ function _markTableAndComputeTranslation( throw assertExhaustiveCheck(desc); } } - - thisTableCompactionState.computeIndexTranslation(); } function markColumn(col: Array, shouldMark: BitSet, markBuf: BitSet) { @@ -387,6 +406,111 @@ function markColumnWithNegOneableFields( } } +// Collapse identical rows in the frame table. Two rows are identical if every +// column has the same value. Duplicates often arise during profile processing +// because Firefox's gecko profile has a per-thread frame table, and +// process-profile.ts merges those into a single frame table without +// deduplicating (so that profile loading stays fast). Compacting runs in +// contexts where small profile size matters more than latency, so we dedupe +// here. +// +// We sort an array of marked frame indices using a comparator that walks the +// frame columns directly (no per-frame object is constructed). After sorting, +// duplicates are adjacent and a single linear pass picks one canonical frame +// per group, redirects the others to it, and clears their bits in markBuffer +// so they get skipped during the compact phase. +function _dedupFrameTable( + frameTable: FrameTable, + state: TableCompactionState +): void { + const markedFrames = new Array(); + const { markBuffer } = state; + for (let i = 0; i < frameTable.length; i++) { + if (checkBit(markBuffer, i)) { + markedFrames.push(i); + } + } + + if (markedFrames.length === 0) { + return; + } + + // Sort, so that we can deduplicate without creating hash strings. + markedFrames.sort((a, b) => _compareFrames(frameTable, a, b)); + + // Walk the sorted list. If we find matching subsequent frames, + // redirect the later frames to the first matching frame. + let prevFrame = markedFrames[0]; + for (let i = 1; i < markedFrames.length; i++) { + const frameIndex = markedFrames[i]; + if (_compareFrames(frameTable, frameIndex, prevFrame) === 0) { + state.redirectOldIndexToCanonicalOldIndex(frameIndex, prevFrame); + continue; + } + prevFrame = frameIndex; + } +} + +function _compareFrames(frameTable: FrameTable, a: number, b: number): number { + let d; + const funcCol = frameTable.func; + d = funcCol[a] - funcCol[b]; + if (d !== 0) { + return d; + } + const addressCol = frameTable.address; + d = addressCol[a] - addressCol[b]; + if (d !== 0) { + return d; + } + const inlineDepthCol = frameTable.inlineDepth; + d = inlineDepthCol[a] - inlineDepthCol[b]; + if (d !== 0) { + return d; + } + const categoryCol = frameTable.category; + d = _compareNullableNumber(categoryCol[a], categoryCol[b]); + if (d !== 0) { + return d; + } + const subcategoryCol = frameTable.subcategory; + d = _compareNullableNumber(subcategoryCol[a], subcategoryCol[b]); + if (d !== 0) { + return d; + } + const nativeSymbolCol = frameTable.nativeSymbol; + d = _compareNullableNumber(nativeSymbolCol[a], nativeSymbolCol[b]); + if (d !== 0) { + return d; + } + const innerWindowIDCol = frameTable.innerWindowID; + d = _compareNullableNumber(innerWindowIDCol[a], innerWindowIDCol[b]); + if (d !== 0) { + return d; + } + const lineCol = frameTable.line; + d = _compareNullableNumber(lineCol[a], lineCol[b]); + if (d !== 0) { + return d; + } + const columnCol = frameTable.column; + d = _compareNullableNumber(columnCol[a], columnCol[b]); + if (d !== 0) { + return d; + } + return 0; +} + +function _compareNullableNumber(a: number | null, b: number | null): number { + if (a === null) { + return b === null ? 0 : -1; + } + if (b === null) { + return 1; + } + return a - b; +} + function _gatherReferencesInThread( thread: RawThread, tcs: TableCompactionStates, diff --git a/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap b/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap index 71147b11fc..06af89ef93 100644 --- a/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap +++ b/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap @@ -116,38 +116,11 @@ Object { 54399, 54631, 17384, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, 15719, 28575, 30367, 18819, 16188, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, ], "category": Array [ 1, @@ -165,33 +138,6 @@ Object { 1, 1, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, ], "column": Array [ null, @@ -209,33 +155,6 @@ Object { null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, ], "func": Array [ 0, @@ -248,38 +167,11 @@ Object { 7, 8, 9, - 4, - 5, - 6, - 7, - 8, - 9, - 6, - 4, - 5, - 6, - 7, - 8, - 9, - 6, - 4, - 5, - 6, - 7, - 8, - 9, 6, 4, 10, 11, 12, - 4, - 5, - 6, - 7, - 8, - 9, - 6, ], "inlineDepth": Array [ 0, @@ -297,33 +189,6 @@ Object { 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, ], "innerWindowID": Array [ null, @@ -341,35 +206,8 @@ Object { null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, ], - "length": 42, + "length": 15, "line": Array [ null, null, @@ -386,33 +224,6 @@ Object { null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, ], "nativeSymbol": Array [ 0, @@ -425,38 +236,11 @@ Object { 10, 11, 9, - 5, - 2, - 3, - 10, - 11, - 9, - 3, - 5, - 2, - 3, - 10, - 11, - 9, - 3, - 5, - 2, - 3, - 10, - 11, - 9, 3, 5, 6, 7, 12, - 5, - 2, - 3, - 10, - 11, - 9, - 3, ], "subcategory": Array [ 0, @@ -474,33 +258,6 @@ Object { 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, ], }, "funcTable": Object { @@ -729,84 +486,84 @@ Object { 7, 8, 9, + 4, + 5, + 6, + 7, + 8, + 9, 10, - 11, - 12, - 13, - 14, - 15, - 16, - 12, + 6, + 7, + 8, + 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 11, + 12, 13, 14, - 15, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 19, - 20, - 21, - 22, - 23, - 19, - 20, - 21, - 22, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, ], "length": 88, "prefix": Array [ @@ -1415,38 +1172,11 @@ Object { 54399, 54631, 17384, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, 15719, 28575, 30367, 18819, 16188, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, ], "category": Array [ 1, @@ -1464,33 +1194,6 @@ Object { 1, 1, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, ], "column": Array [ null, @@ -1508,6 +1211,45 @@ Object { null, null, null, + ], + "func": Array [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 6, + 4, + 10, + 11, + 12, + ], + "inlineDepth": Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "innerWindowID": Array [ + null, + null, + null, null, null, null, @@ -1520,6 +1262,9 @@ Object { null, null, null, + ], + "length": 15, + "line": Array [ null, null, null, @@ -1536,226 +1281,22 @@ Object { null, null, ], - "func": Array [ + "nativeSymbol": Array [ 0, 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, 4, - 5, - 6, - 7, 8, - 9, - 6, - 4, 5, - 6, - 7, - 8, + 2, + 3, + 10, + 11, 9, - 6, - 4, + 3, 5, 6, 7, - 8, - 9, - 6, - 4, - 10, - 11, 12, - 4, - 5, - 6, - 7, - 8, - 9, - 6, - ], - "inlineDepth": Array [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ], - "innerWindowID": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "length": 42, - "line": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], - "nativeSymbol": Array [ - 0, - 1, - 4, - 8, - 5, - 2, - 3, - 10, - 11, - 9, - 5, - 2, - 3, - 10, - 11, - 9, - 3, - 5, - 2, - 3, - 10, - 11, - 9, - 3, - 5, - 2, - 3, - 10, - 11, - 9, - 3, - 5, - 6, - 7, - 12, - 5, - 2, - 3, - 10, - 11, - 9, - 3, ], "subcategory": Array [ 0, @@ -1773,33 +1314,6 @@ Object { 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, ], }, "funcTable": Object { @@ -2028,84 +1542,84 @@ Object { 7, 8, 9, + 4, + 5, + 6, + 7, + 8, + 9, 10, + 6, + 7, + 8, + 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, 11, 12, 13, 14, - 15, - 16, - 12, - 13, - 14, - 15, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 19, - 20, - 21, - 22, - 23, - 19, - 20, - 21, - 22, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, ], "length": 88, "prefix": Array [ @@ -2702,32 +2216,12 @@ Object { "pages": Array [], "profilerOverhead": Array [], "shared": Object { - "frameTable": Object { - "address": Array [ - 24915, - 16067, - 38027, - 11180, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, + "frameTable": Object { + "address": Array [ + 24915, + 16067, + 38027, + 11180, 28563, 15795, 15667, @@ -2739,13 +2233,6 @@ Object { 30367, 18819, 16188, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, ], "category": Array [ 1, @@ -2763,33 +2250,6 @@ Object { 1, 1, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, ], "column": Array [ null, @@ -2807,33 +2267,6 @@ Object { null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, ], "func": Array [ 0, @@ -2846,38 +2279,11 @@ Object { 7, 8, 9, - 4, - 5, - 6, - 7, - 8, - 9, - 6, - 4, - 5, - 6, - 7, - 8, - 9, - 6, - 4, - 5, - 6, - 7, - 8, - 9, 6, 4, 10, 11, 12, - 4, - 5, - 6, - 7, - 8, - 9, - 6, ], "inlineDepth": Array [ 0, @@ -2895,33 +2301,6 @@ Object { 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, ], "innerWindowID": Array [ null, @@ -2939,35 +2318,8 @@ Object { null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, ], - "length": 42, + "length": 15, "line": Array [ null, null, @@ -2984,33 +2336,6 @@ Object { null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, ], "nativeSymbol": Array [ 0, @@ -3023,38 +2348,11 @@ Object { 10, 11, 9, - 5, - 2, - 3, - 10, - 11, - 9, - 3, - 5, - 2, - 3, - 10, - 11, - 9, - 3, - 5, - 2, - 3, - 10, - 11, - 9, 3, 5, 6, 7, 12, - 5, - 2, - 3, - 10, - 11, - 9, - 3, ], "subcategory": Array [ 0, @@ -3072,33 +2370,6 @@ Object { 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, ], }, "funcTable": Object { @@ -3327,84 +2598,84 @@ Object { 7, 8, 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, 10, + 6, + 7, + 8, + 9, 11, 12, 13, 14, - 15, - 16, - 12, - 13, - 14, - 15, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 19, - 20, - 21, - 22, - 23, - 19, - 20, - 21, - 22, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, ], "length": 88, "prefix": Array [ @@ -4013,38 +3284,11 @@ Object { 54399, 54631, 17384, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, 15719, 28575, 30367, 18819, 16188, - 28563, - 15795, - 15667, - 54399, - 54631, - 17384, - 15719, ], "category": Array [ 1, @@ -4062,33 +3306,6 @@ Object { 1, 1, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, ], "column": Array [ null, @@ -4106,33 +3323,6 @@ Object { null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, ], "func": Array [ 0, @@ -4141,71 +3331,17 @@ Object { 3, 4, 5, - 6, - 7, - 8, - 9, - 4, - 5, - 6, - 7, - 8, - 9, - 6, - 4, - 5, - 6, - 7, - 8, - 9, - 6, - 4, - 5, - 6, - 7, - 8, - 9, - 6, - 4, - 10, - 11, - 12, - 4, - 5, - 6, - 7, - 8, - 9, - 6, - ], - "inlineDepth": Array [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 6, + 7, + 8, + 9, + 6, + 4, + 10, + 11, + 12, + ], + "inlineDepth": Array [ 0, 0, 0, @@ -4238,35 +3374,8 @@ Object { null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, ], - "length": 42, + "length": 15, "line": Array [ null, null, @@ -4283,33 +3392,6 @@ Object { null, null, null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, ], "nativeSymbol": Array [ 0, @@ -4322,38 +3404,11 @@ Object { 10, 11, 9, - 5, - 2, - 3, - 10, - 11, - 9, - 3, - 5, - 2, - 3, - 10, - 11, - 9, - 3, - 5, - 2, - 3, - 10, - 11, - 9, 3, 5, 6, 7, 12, - 5, - 2, - 3, - 10, - 11, - 9, - 3, ], "subcategory": Array [ 0, @@ -4371,33 +3426,6 @@ Object { 0, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, ], }, "funcTable": Object { @@ -4626,84 +3654,84 @@ Object { 7, 8, 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, 10, + 6, + 7, + 8, + 9, 11, 12, 13, 14, - 15, - 16, - 12, - 13, - 14, - 15, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 19, - 20, - 21, - 22, - 23, - 19, - 20, - 21, - 22, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 30, - 26, - 27, - 28, - 29, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, - 41, - 37, - 38, - 39, - 40, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, + 10, + 6, + 7, + 8, + 9, ], "length": 88, "prefix": Array [ diff --git a/src/test/unit/profile-compacting.test.ts b/src/test/unit/profile-compacting.test.ts new file mode 100644 index 0000000000..03a23f54ef --- /dev/null +++ b/src/test/unit/profile-compacting.test.ts @@ -0,0 +1,83 @@ +/* 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/. */ + +import { computeCompactedProfile } from '../../profile-logic/profile-compacting'; +import { getProfileFromTextSamples } from '../fixtures/profiles/processed-profile'; +import { callTreeFromProfile, formatTree } from '../fixtures/utils'; + +describe('computeCompactedProfile frame deduplication', function () { + it('collapses identical rows in the frame table into a single row', function () { + // Start from a profile that has different B and C frames. + const { + profile, + funcNamesDictPerThread: [funcNamesDict], + } = getProfileFromTextSamples(` + A A + B C + `); + + const { shared } = profile; + const { frameTable } = shared; + + const { B, C } = funcNamesDict; + expect(frameTable.length).toBe(3); + const frameC = frameTable.func.indexOf(C); + + // Make frameC a duplicate of frameB. + frameTable.func[frameC] = B; + + const { profile: compacted } = computeCompactedProfile(profile); + + // The unused func C should have been removed. + expect(compacted.shared.funcTable.length).toBe(2); + + // The duplicate frame should have been collapsed. + expect(compacted.shared.frameTable.length).toBe(2); + + // The duplicate stacks should have been collapsed. + expect(compacted.shared.frameTable.length).toBe(2); + + const callTree = callTreeFromProfile(profile, /* threadIndex */ 0); + const formattedTree = formatTree(callTree); + expect(formattedTree).toEqual([ + '- A (total: 2, self: —)', + ' - B (total: 2, self: 2)', + ]); + }); + + it('keeps frames distinct when they differ in any column', function () { + // Start from a profile that has different B and C frames. + const { + profile, + funcNamesDictPerThread: [funcNamesDict], + } = getProfileFromTextSamples(` + A A + B[line:100] C[line:123] + `); + + const { shared } = profile; + const { frameTable } = shared; + + const { B, C } = funcNamesDict; + expect(frameTable.length).toBe(3); + const frameB = frameTable.func.indexOf(B); + const frameC = frameTable.func.indexOf(C); + + // Make frameC almost a duplicate of frameB. It still has a different line though. + frameTable.func[frameC] = frameTable.func[frameB]; + + const { profile: compacted1 } = computeCompactedProfile(profile); + + // Differing line means we keep both frames. + expect(compacted1.shared.frameTable.length).toBe(3); + + // Now make the lines match, too. + frameTable.line[frameC] = frameTable.line[frameB]; + + const { profile: compacted2 } = computeCompactedProfile(profile); + + // Lines now match, so deduplication should kick in. + expect(compacted2.shared.frameTable.length).toBe(2); + }); +});