From 90c3b95ff725d932172cd8adadf0c67325756acc Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Sun, 5 Jul 2026 11:28:58 -0400 Subject: [PATCH 1/7] Add raw/derived split for the allocation table types. We already have this split for the SamplesTable, but not yet for the other tables. Having it for all tables means that SamplesLikeTable can always be one of the derived tables. The goal is to make the derived SamplesTable have a Float64Array for the time column, and for the raw tables to allow both number[] and Float64Array for their time or timeDeltas columns. This will be easier once SamplesLikeTable is always a derived table. --- src/profile-logic/data-structures.ts | 12 ++--- src/profile-logic/import/dhat.ts | 6 +-- src/profile-logic/process-profile.ts | 20 +++---- src/profile-logic/profile-data.ts | 50 +++++++++++++++-- src/selectors/per-thread/thread.tsx | 22 +++++++- .../fixtures/profiles/processed-profile.ts | 18 +++---- src/test/fixtures/utils.ts | 16 +++++- src/types/profile-derived.ts | 53 ++++++++++++++++++- src/types/profile.ts | 22 ++++---- 9 files changed, 174 insertions(+), 45 deletions(-) diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index 476745de1a..76ca32a6d5 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -15,9 +15,9 @@ import type { RawStackTable, FuncTable, RawMarkerTable, - JsAllocationsTable, - UnbalancedNativeAllocationsTable, - BalancedNativeAllocationsTable, + RawJsAllocationsTable, + RawUnbalancedNativeAllocationsTable, + RawBalancedNativeAllocationsTable, ResourceTable, NativeSymbolTable, Profile, @@ -274,7 +274,7 @@ export function getEmptyRawMarkerTable(): RawMarkerTable { }; } -export function getEmptyJsAllocationsTable(): JsAllocationsTable { +export function getEmptyRawJsAllocationsTable(): RawJsAllocationsTable { // Important! // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not @@ -296,7 +296,7 @@ export function getEmptyJsAllocationsTable(): JsAllocationsTable { * The native allocation tables come in two varieties. Get one of the members of the * union. */ -export function getEmptyUnbalancedNativeAllocationsTable(): UnbalancedNativeAllocationsTable { +export function getEmptyRawUnbalancedNativeAllocationsTable(): RawUnbalancedNativeAllocationsTable { // Important! // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not @@ -314,7 +314,7 @@ export function getEmptyUnbalancedNativeAllocationsTable(): UnbalancedNativeAllo * The native allocation tables come in two varieties. Get one of the members of the * union. */ -export function getEmptyBalancedNativeAllocationsTable(): BalancedNativeAllocationsTable { +export function getEmptyRawBalancedNativeAllocationsTable(): RawBalancedNativeAllocationsTable { // Important! // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not diff --git a/src/profile-logic/import/dhat.ts b/src/profile-logic/import/dhat.ts index 6f883c2e29..c04e25d808 100644 --- a/src/profile-logic/import/dhat.ts +++ b/src/profile-logic/import/dhat.ts @@ -12,7 +12,7 @@ import type { import { getEmptyProfile, getEmptyThread, - getEmptyUnbalancedNativeAllocationsTable, + getEmptyRawUnbalancedNativeAllocationsTable, } from 'firefox-profiler/profile-logic/data-structures'; import { GlobalDataCollector } from 'firefox-profiler/profile-logic/global-data-collector'; @@ -154,7 +154,7 @@ type WriteCount = number; /** * The dhat convertor converts to the processed profile format, rather than to the Gecko - * format, as it needs the UnbalancedNativeAllocationsTable type, which is unavailable + * format, as it needs the RawUnbalancedNativeAllocationsTable type, which is unavailable * in the Gecko format. In the Gecko format, that data comes in the form of markers, which * would be awkard to target. */ @@ -187,7 +187,7 @@ export function attemptToConvertDhat(json: unknown): Profile | null { const frameTable = globalDataCollector.getFrameTable(); const stringTable = globalDataCollector.getStringTable(); - const allocationsTable = getEmptyUnbalancedNativeAllocationsTable(); + const allocationsTable = getEmptyRawUnbalancedNativeAllocationsTable(); // dhat profiles do no support categories. Fill the category and subcategory information // with 0s. diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index 2f38fcb4a8..31dfde4ce8 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -14,8 +14,8 @@ import { AddressLocator } from './address-locator'; import { getEmptyExtensions, getEmptyRawMarkerTable, - getEmptyJsAllocationsTable, - getEmptyUnbalancedNativeAllocationsTable, + getEmptyRawJsAllocationsTable, + getEmptyRawUnbalancedNativeAllocationsTable, type RawStackTableBuilder, } from './data-structures'; import { immutableUpdate, ensureExists } from '../utils/types'; @@ -61,9 +61,9 @@ import type { IndexIntoFuncTable, IndexIntoStringTable, JsTracerTable, - JsAllocationsTable, + RawJsAllocationsTable, ProfilerOverhead, - NativeAllocationsTable, + RawNativeAllocationsTable, Milliseconds, Microseconds, Address, @@ -631,8 +631,8 @@ function _convertPayloadStackToIndex( * Process the markers. * Convert stacks to causes. * Process GC markers. - * Extract JS allocations into the JsAllocationsTable. - * Extract Native allocations into the NativeAllocationsTable. + * Extract JS allocations into the RawJsAllocationsTable. + * Extract Native allocations into the RawNativeAllocationsTable. */ function _processMarkers( geckoMarkers: GeckoMarkerStruct, @@ -642,13 +642,13 @@ function _processMarkers( stackIndexOffset: IndexIntoStackTable ): { markers: RawMarkerTable; - jsAllocations: JsAllocationsTable | null; - nativeAllocations: NativeAllocationsTable | null; + jsAllocations: RawJsAllocationsTable | null; + nativeAllocations: RawNativeAllocationsTable | null; } { const markers = getEmptyRawMarkerTable(); - const jsAllocations = getEmptyJsAllocationsTable(); + const jsAllocations = getEmptyRawJsAllocationsTable(); const inProgressNativeAllocations = - getEmptyUnbalancedNativeAllocationsTable(); + getEmptyRawUnbalancedNativeAllocationsTable(); const memoryAddress: number[] = []; const threadId: number[] = []; diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 865efbd41f..43c2d35bab 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -73,6 +73,9 @@ import type { CounterIndex, RawCounterSamplesTable, CounterSamplesTable, + RawJsAllocationsTable, + JsAllocationsTable, + RawNativeAllocationsTable, NativeAllocationsTable, InnerWindowID, BalancedNativeAllocationsTable, @@ -2776,6 +2779,47 @@ export function computeSamplesTableFromRawSamplesTable( }; } +export function computeJsAllocationsTableFromRawJsAllocationsTable( + raw: RawJsAllocationsTable +): JsAllocationsTable { + return { + time: raw.time, + className: raw.className, + typeName: raw.typeName, + coarseType: raw.coarseType, + weight: raw.weight, + weightType: raw.weightType, + inNursery: raw.inNursery, + stack: raw.stack, + length: raw.length, + }; +} + +export function computeNativeAllocationsTableFromRawNativeAllocationsTable( + raw: RawNativeAllocationsTable +): NativeAllocationsTable { + if ('memoryAddress' in raw) { + return { + time: raw.time, + weight: raw.weight, + weightType: raw.weightType, + stack: raw.stack, + argumentValues: raw.argumentValues, + memoryAddress: raw.memoryAddress, + threadId: raw.threadId, + length: raw.length, + }; + } + return { + time: raw.time, + weight: raw.weight, + weightType: raw.weightType, + stack: raw.stack, + argumentValues: raw.argumentValues, + length: raw.length, + }; +} + /** * Create the derived Thread. */ @@ -2790,7 +2834,9 @@ export function createThreadFromDerivedTables( stringTable: StringTable, sources: SourceTable, tracedValuesBuffer: ArrayBuffer | undefined, - sourceLocationTable: SourceLocationTable + sourceLocationTable: SourceLocationTable, + jsAllocations: JsAllocationsTable | undefined, + nativeAllocations: NativeAllocationsTable | undefined ): Thread { const { processType, @@ -2807,8 +2853,6 @@ export function createThreadFromDerivedTables( isJsTracer, pid, tid, - jsAllocations, - nativeAllocations, markers, jsTracer, isPrivateBrowsing, diff --git a/src/selectors/per-thread/thread.tsx b/src/selectors/per-thread/thread.tsx index 54966a55c7..ce13fd664a 100644 --- a/src/selectors/per-thread/thread.tsx +++ b/src/selectors/per-thread/thread.tsx @@ -27,7 +27,9 @@ import type { JsTracerTable, RawSamplesTable, SamplesTable, + RawNativeAllocationsTable, NativeAllocationsTable, + RawJsAllocationsTable, JsAllocationsTable, SamplesLikeTable, Selector, @@ -143,11 +145,25 @@ export function getBasicThreadSelectorsPerThread( ) : null ); - const getNativeAllocations: Selector = ( + const getRawNativeAllocations: Selector = ( state ) => getRawThread(state).nativeAllocations; - const getJsAllocations: Selector = (state) => + const getRawJsAllocations: Selector = (state) => getRawThread(state).jsAllocations; + const getNativeAllocations: Selector = + createSelector(getRawNativeAllocations, (raw) => + raw + ? ProfileData.computeNativeAllocationsTableFromRawNativeAllocationsTable( + raw + ) + : undefined + ); + const getJsAllocations: Selector = + createSelector(getRawJsAllocations, (raw) => + raw + ? ProfileData.computeJsAllocationsTableFromRawJsAllocationsTable(raw) + : undefined + ); const getThreadRange: Selector = (state) => // This function is already memoized in profile-data.js, so we don't need to // memoize it here with `createSelector`. @@ -195,6 +211,8 @@ export function getBasicThreadSelectorsPerThread( getTracedValuesBuffer, (state: State) => ProfileSelectors.getRawProfileSharedData(state).sourceLocationTable, + getJsAllocations, + getNativeAllocations, ProfileData.createThreadFromDerivedTables ); diff --git a/src/test/fixtures/profiles/processed-profile.ts b/src/test/fixtures/profiles/processed-profile.ts index fea127bc5c..8056ab88ef 100644 --- a/src/test/fixtures/profiles/processed-profile.ts +++ b/src/test/fixtures/profiles/processed-profile.ts @@ -5,9 +5,9 @@ import { getEmptyProfile, getEmptyThread, getEmptyJsTracerTable, - getEmptyJsAllocationsTable, - getEmptyUnbalancedNativeAllocationsTable, - getEmptyBalancedNativeAllocationsTable, + getEmptyRawJsAllocationsTable, + getEmptyRawUnbalancedNativeAllocationsTable, + getEmptyRawBalancedNativeAllocationsTable, getRawStackTableBuilderWithExistingContents, finishRawStackTableBuilder, } from '../../../profile-logic/data-structures'; @@ -1628,8 +1628,8 @@ export function getProfileWithJsAllocations() { I[lib:libI.so] `); - // Now add a JsAllocationsTable. - const jsAllocations = getEmptyJsAllocationsTable(); + // Now add a RawJsAllocationsTable. + const jsAllocations = getEmptyRawJsAllocationsTable(); profile.threads[0].jsAllocations = jsAllocations; // The stack table is built sequentially, so we can assume that the stack indexes @@ -1708,8 +1708,8 @@ export function getProfileWithUnbalancedNativeAllocations() { ` ); - // Now add a NativeAllocationsTable. - const nativeAllocations = getEmptyUnbalancedNativeAllocationsTable(); + // Now add a RawNativeAllocationsTable. + const nativeAllocations = getEmptyRawUnbalancedNativeAllocationsTable(); profile.threads[0].nativeAllocations = nativeAllocations; // The stack table is built sequentially, so we can assume that the stack indexes @@ -1776,8 +1776,8 @@ export function getProfileWithBalancedNativeAllocations() { ` ); - // Now add a NativeAllocationsTable. - const nativeAllocations = getEmptyBalancedNativeAllocationsTable(); + // Now add a RawNativeAllocationsTable. + const nativeAllocations = getEmptyRawBalancedNativeAllocationsTable(); const [thread] = profile.threads; thread.nativeAllocations = nativeAllocations; const threadId = thread.tid; diff --git a/src/test/fixtures/utils.ts b/src/test/fixtures/utils.ts index d91e76e8fa..77e120fc72 100644 --- a/src/test/fixtures/utils.ts +++ b/src/test/fixtures/utils.ts @@ -19,6 +19,8 @@ import { createThreadFromDerivedTables, computeStackTableFromRawStackTable, computeSamplesTableFromRawSamplesTable, + computeJsAllocationsTableFromRawJsAllocationsTable, + computeNativeAllocationsTableFromRawNativeAllocationsTable, } from 'firefox-profiler/profile-logic/profile-data'; import { getProfileWithDicts } from './profiles/processed-profile'; import { StringTable } from '../../utils/string-table'; @@ -162,6 +164,16 @@ export function computeThreadFromRawThread( const tracedValuesBuffer = rawThread.tracedValuesBuffer ? base64StringToBytes(rawThread.tracedValuesBuffer) : undefined; + const jsAllocations = rawThread.jsAllocations + ? computeJsAllocationsTableFromRawJsAllocationsTable( + rawThread.jsAllocations + ) + : undefined; + const nativeAllocations = rawThread.nativeAllocations + ? computeNativeAllocationsTableFromRawNativeAllocationsTable( + rawThread.nativeAllocations + ) + : undefined; return createThreadFromDerivedTables( rawThread, samples, @@ -173,7 +185,9 @@ export function computeThreadFromRawThread( stringTable, shared.sources, tracedValuesBuffer, - shared.sourceLocationTable + shared.sourceLocationTable, + jsAllocations, + nativeAllocations ); } diff --git a/src/types/profile-derived.ts b/src/types/profile-derived.ts index 6568b3b209..e05e0d59e1 100644 --- a/src/types/profile-derived.ts +++ b/src/types/profile-derived.ts @@ -19,8 +19,6 @@ import type { Tid, ProcessType, PausedRange, - JsAllocationsTable, - NativeAllocationsTable, RawMarkerTable, FrameTable, FuncTable, @@ -185,6 +183,57 @@ export type CounterSamplesTable = { length: number; }; +/** + * The `JsAllocationsTable` type of the derived thread. + * + * Currently identical in shape to the `RawJsAllocationsTable`. Splitting the + * derived type off lets us widen columns (e.g. store `time` as a typed array) + * without affecting the raw type. + */ +export type JsAllocationsTable = { + time: Milliseconds[]; + className: string[]; + typeName: string[]; + coarseType: string[]; + weight: Bytes[]; + weightType: 'bytes'; + inNursery: boolean[]; + stack: Array; + length: number; +}; + +/** + * The `UnbalancedNativeAllocationsTable` type of the derived thread. + * + * Currently identical in shape to the `RawUnbalancedNativeAllocationsTable`. + */ +export type UnbalancedNativeAllocationsTable = { + time: Milliseconds[]; + weight: Bytes[]; + weightType: 'bytes'; + stack: Array; + argumentValues?: Array; + length: number; +}; + +/** + * The `BalancedNativeAllocationsTable` type of the derived thread. + * + * Currently identical in shape to the `RawBalancedNativeAllocationsTable`. + */ +export type BalancedNativeAllocationsTable = + UnbalancedNativeAllocationsTable & { + memoryAddress: number[]; + threadId: number[]; + }; + +/** + * The `NativeAllocationsTable` type of the derived thread. + */ +export type NativeAllocationsTable = + | UnbalancedNativeAllocationsTable + | BalancedNativeAllocationsTable; + export type Counter = { name: string; category: string; diff --git a/src/types/profile.ts b/src/types/profile.ts index 8b470516e2..63c2ccff11 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -151,8 +151,10 @@ export type RawSamplesTable = { /** * JS allocations are recorded as a marker payload, but in profile processing they * are moved to the Thread. This allows them to be part of the stack processing pipeline. + * + * There is also a derived `JsAllocationsTable` type, see profile-derived.js. */ -export type JsAllocationsTable = { +export type RawJsAllocationsTable = { time: Milliseconds[]; className: string[]; typeName: string[]; // Currently only 'JSObject' @@ -170,7 +172,7 @@ export type JsAllocationsTable = { * This variant is the original version of the table, before the memory address * and threadId were added. */ -export type UnbalancedNativeAllocationsTable = { +export type RawUnbalancedNativeAllocationsTable = { time: Milliseconds[]; // "weight" is used here rather than "bytes", so that this type can be // used as a SamplesLikeTable. @@ -184,8 +186,8 @@ export type UnbalancedNativeAllocationsTable = { /** * The memory address and thread ID were added later. */ -export type BalancedNativeAllocationsTable = - UnbalancedNativeAllocationsTable & { +export type RawBalancedNativeAllocationsTable = + RawUnbalancedNativeAllocationsTable & { memoryAddress: number[]; threadId: number[]; }; @@ -195,10 +197,12 @@ export type BalancedNativeAllocationsTable = * are moved to the Thread. This allows them to be part of the stack processing pipeline. * Currently they include native allocations and deallocations. However, both * of them are sampled independently, so they will be unbalanced if summed togther. + * + * There is also a derived `NativeAllocationsTable` type, see profile-derived.js. */ -export type NativeAllocationsTable = - | UnbalancedNativeAllocationsTable - | BalancedNativeAllocationsTable; +export type RawNativeAllocationsTable = + | RawUnbalancedNativeAllocationsTable + | RawBalancedNativeAllocationsTable; /** * Markers represent arbitrary events that happen within the browser. They have a @@ -764,8 +768,8 @@ export type RawThread = { pid: Pid; tid: Tid; samples: RawSamplesTable; - jsAllocations?: JsAllocationsTable; - nativeAllocations?: NativeAllocationsTable; + jsAllocations?: RawJsAllocationsTable; + nativeAllocations?: RawNativeAllocationsTable; markers: RawMarkerTable; jsTracer?: JsTracerTable; // If present and true, this thread was launched for a private browsing session only. From 39e16900cf64097fa5b726c9553bb9c1d6c541d9 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Fri, 3 Jul 2026 11:47:35 -0400 Subject: [PATCH 2/7] Store the derived samples table's `time` column as a Float64Array. The following derived tables now use `Float64Array` for their `time` column: - `SamplesTable` (derived samples) - `CounterSamplesTable` - `JsAllocationsTable` and `UnbalancedNativeAllocationsTable` (derived allocations, introduced by the previous commit) - `SamplesLikeTable` The raw tables still use `number[]`, as before, so the profile format is unchanged. --- src/profile-logic/combined-cpu.ts | 8 ++--- src/profile-logic/merge-compare.ts | 5 ++- src/profile-logic/profile-data.ts | 36 ++++++++++++------- src/profile-logic/transforms.ts | 3 +- src/profile-query/cpu-activity.ts | 2 +- .../components/EmptyThreadIndicator.test.tsx | 2 +- .../fixtures/profiles/processed-profile.ts | 6 ++-- .../__snapshots__/profile-view.test.ts.snap | 12 +++---- src/test/unit/activity-slice-tree.test.ts | 4 +-- src/test/unit/combined-cpu.test.ts | 10 +++--- src/test/unit/process-profile.test.ts | 10 ++++-- .../unit/profile-query/cpu-activity.test.ts | 6 ++-- src/types/profile-derived.ts | 20 +++++------ src/utils/number-series.ts | 10 ++++-- src/utils/slice-tree.ts | 10 +++--- 15 files changed, 80 insertions(+), 64 deletions(-) diff --git a/src/profile-logic/combined-cpu.ts b/src/profile-logic/combined-cpu.ts index a7a7efa163..6966e779f3 100644 --- a/src/profile-logic/combined-cpu.ts +++ b/src/profile-logic/combined-cpu.ts @@ -2,14 +2,14 @@ * 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 type { SamplesTable } from 'firefox-profiler/types'; +import type { Milliseconds, SamplesTable } from 'firefox-profiler/types'; import { bisectionLeft } from '../utils/bisect'; /** * Represents CPU usage over time for a single thread. */ export type CpuRatioTimeSeries = { - time: number[]; + time: Float64Array; cpuRatio: Float64Array; maxCpuRatio: number; length: number; @@ -87,7 +87,7 @@ export function combineCPUDataFromThreads( } const cursors = new Array(threadsWithCPU.length).fill(0); - const combinedTime: number[] = []; + const combinedTime: Milliseconds[] = []; const combinedCPURatio: number[] = []; let combinedMaxCpuRatio = 0; @@ -126,7 +126,7 @@ export function combineCPUDataFromThreads( } return { - time: combinedTime, + time: Float64Array.from(combinedTime), cpuRatio: Float64Array.from(combinedCPURatio), maxCpuRatio: combinedMaxCpuRatio, length: combinedTime.length, diff --git a/src/profile-logic/merge-compare.ts b/src/profile-logic/merge-compare.ts index 3e6f600dcf..a13e993e8e 100644 --- a/src/profile-logic/merge-compare.ts +++ b/src/profile-logic/merge-compare.ts @@ -1352,9 +1352,8 @@ function combineSamplesForMerging(threads: RawThread[]): RawSamplesTable { const samplesPerThread: RawSamplesTable[] = threads.map( (thread) => thread.samples ); - const sampleTimesPerThread: Milliseconds[][] = samplesPerThread.map( - computeTimeColumnForRawSamplesTable - ); + const sampleTimesPerThread: Float64Array[] = + samplesPerThread.map(computeTimeColumnForRawSamplesTable); // This is the array that holds the latest processed sample index for each // thread's samplesTable. const nextSampleIndexPerThread: number[] = Array( diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 43c2d35bab..e071318d8b 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -1939,9 +1939,12 @@ function _computeStackMatchesFromFuncMatches( export function computeTimeColumnForRawSamplesTable( samples: RawSamplesTable | RawCounterSamplesTable -): number[] { +): Float64Array { const { time, timeDeltas } = samples; - return time ?? numberSeriesFromDeltas(ensureExists(timeDeltas)); + if (time !== undefined) { + return Float64Array.from(time); + } + return numberSeriesFromDeltas(ensureExists(timeDeltas)); } export function computeSampleCategoriesAndSubcategories( @@ -2014,7 +2017,10 @@ export function hasUsefulSamples( * This function takes both a SamplesTable and can be used on CounterSamplesTable. */ export function getSampleIndexRangeForSelection( - times: { time: Milliseconds[]; length: number }, + times: { + time: Milliseconds[] | Float64Array; + length: number; + }, rangeStart: number, rangeEnd: number ): [IndexIntoSamplesTable, IndexIntoSamplesTable] { @@ -2022,7 +2028,7 @@ export function getSampleIndexRangeForSelection( } export function getIndexRangeForSelection( - times: Milliseconds[], + times: Milliseconds[] | Float64Array, rangeStart: number, rangeEnd: number ): [IndexIntoSamplesTable, IndexIntoSamplesTable] { @@ -2037,7 +2043,10 @@ export function getIndexRangeForSelection( * sure that some charts will not be cut off at the edges when zoomed in to a range. */ export function getInclusiveSampleIndexRangeForSelection( - table: { time: Milliseconds[]; length: number }, + table: { + time: Milliseconds[] | Float64Array; + length: number; + }, rangeStart: number, rangeEnd: number ): [IndexIntoSamplesTable, IndexIntoSamplesTable] { @@ -2045,7 +2054,7 @@ export function getInclusiveSampleIndexRangeForSelection( } export function getInclusiveIndexRangeForSelection( - times: Milliseconds[], + times: Milliseconds[] | Float64Array, rangeStart: number, rangeEnd: number ): [IndexIntoSamplesTable, IndexIntoSamplesTable] { @@ -2257,7 +2266,7 @@ export function filterRawThreadSamplesToRange( ); const newSamples: RawSamplesTable = { length: endSampleIndex - beginSampleIndex, - time: sampleTimes.slice(beginSampleIndex, endSampleIndex), + time: Array.from(sampleTimes.slice(beginSampleIndex, endSampleIndex)), weight: samples.weight ? samples.weight.slice(beginSampleIndex, endSampleIndex) : null, @@ -2392,7 +2401,7 @@ export function filterCounterSamplesToRange( newCounter.samples = { length: endSampleIndex - beginSampleIndex, - time: timeColumn.slice(beginSampleIndex, endSampleIndex), + time: Array.from(timeColumn.slice(beginSampleIndex, endSampleIndex)), count: samples.count.slice(beginSampleIndex, endSampleIndex), number: samples.number ? samples.number.slice(beginSampleIndex, endSampleIndex) @@ -2783,7 +2792,7 @@ export function computeJsAllocationsTableFromRawJsAllocationsTable( raw: RawJsAllocationsTable ): JsAllocationsTable { return { - time: raw.time, + time: Float64Array.from(raw.time), className: raw.className, typeName: raw.typeName, coarseType: raw.coarseType, @@ -2798,9 +2807,10 @@ export function computeJsAllocationsTableFromRawJsAllocationsTable( export function computeNativeAllocationsTableFromRawNativeAllocationsTable( raw: RawNativeAllocationsTable ): NativeAllocationsTable { + const time = Float64Array.from(raw.time); if ('memoryAddress' in raw) { return { - time: raw.time, + time, weight: raw.weight, weightType: raw.weightType, stack: raw.stack, @@ -2811,7 +2821,7 @@ export function computeNativeAllocationsTableFromRawNativeAllocationsTable( }; } return { - time: raw.time, + time, weight: raw.weight, weightType: raw.weightType, stack: raw.stack, @@ -2941,11 +2951,11 @@ export function updateThreadStacksByGeneratingNewStackColumns( newStackTable: StackTable, computeMappedStackColumn: ( oldStack: Array, - sampleTime: Array + sampleTime: Float64Array ) => Array, computeMappedSyncBacktraceStackColumn: ( oldStack: Array, - sampleTime: Array + sampleTime: Float64Array ) => Array, computeMappedMarkerDataColumn: ( markerData: Array diff --git a/src/profile-logic/transforms.ts b/src/profile-logic/transforms.ts index 0dd6f2b7ff..57a96ed9d6 100644 --- a/src/profile-logic/transforms.ts +++ b/src/profile-logic/transforms.ts @@ -45,7 +45,6 @@ import type { MarkerIndex, MarkerSchemaByName, CategoryList, - Milliseconds, ProfileIndexTranslationMaps, } from 'firefox-profiler/types'; import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info'; @@ -1876,7 +1875,7 @@ export function filterSamples( function computeFilteredStackColumn( originalStackColumn: Array, - timeColumn: Milliseconds[] + timeColumn: Float64Array ): Array { const newStackColumn = originalStackColumn.slice(); let sampleIndex = 0; diff --git a/src/profile-query/cpu-activity.ts b/src/profile-query/cpu-activity.ts index 79351ac47b..d1f6b0666d 100644 --- a/src/profile-query/cpu-activity.ts +++ b/src/profile-query/cpu-activity.ts @@ -69,7 +69,7 @@ function collectSliceSubtree( childrenStartPerParent: Array, interestingSliceIndexes: Set, nestingDepth: number, - time: number[], + time: Float64Array, result: CpuActivityEntry[], tsManager: TimestampManager ) { diff --git a/src/test/components/EmptyThreadIndicator.test.tsx b/src/test/components/EmptyThreadIndicator.test.tsx index d835738784..ca5b77e044 100644 --- a/src/test/components/EmptyThreadIndicator.test.tsx +++ b/src/test/components/EmptyThreadIndicator.test.tsx @@ -29,7 +29,7 @@ describe('EmptyThreadIndicator', function () { thread.processShutdownTime = 10; thread.registerTime = 3; thread.unregisterTime = 9; - thread.samples.time = [5, 6, 7]; + thread.samples.time = Float64Array.of(5, 6, 7); // Make it really easy to generate the component's props. function propsFromViewportRange(viewport: StartEndRange) { diff --git a/src/test/fixtures/profiles/processed-profile.ts b/src/test/fixtures/profiles/processed-profile.ts index 8056ab88ef..4a5a889acf 100644 --- a/src/test/fixtures/profiles/processed-profile.ts +++ b/src/test/fixtures/profiles/processed-profile.ts @@ -1501,13 +1501,13 @@ export function getCounterForThread( pid: thread.pid, mainThreadIndex, samples: { - time: sampleTimes.slice(), + time: Array.from(sampleTimes), // Create some arbitrary (positive integer) values for the number. number: config.hasCountNumber - ? sampleTimes.map((_, i) => Math.floor(50 * Math.sin(i) + 50)) + ? Array.from(sampleTimes, (_, i) => Math.floor(50 * Math.sin(i) + 50)) : undefined, // Create some arbitrary values for the count. - count: sampleTimes.map((_, i) => Math.sin(i)), + count: Array.from(sampleTimes, (_, i) => Math.sin(i)), length: thread.samples.length, }, display: deriveCounterDisplay(category, name), diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index 0479910d2b..4bd0e4d8c7 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -2396,7 +2396,7 @@ CallTree { 100, 100, ], - "time": Array [ + "time": Float64Array [ 4, 5, ], @@ -2684,7 +2684,7 @@ CallTree { 100, 100, ], - "time": Array [ + "time": Float64Array [ 3, 4, 5, @@ -3072,7 +3072,7 @@ Object { 100, 100, ], - "time": Array [ + "time": Float64Array [ 3, 4, 5, @@ -3530,7 +3530,7 @@ Object { 100, 100, ], - "time": Array [ + "time": Float64Array [ 4, 5, ], @@ -3914,7 +3914,7 @@ Object { 100, 100, ], - "time": Array [ + "time": Float64Array [ 3, 4, 5, @@ -4300,7 +4300,7 @@ Object { 100, 100, ], - "time": Array [ + "time": Float64Array [ 3, 4, 5, diff --git a/src/test/unit/activity-slice-tree.test.ts b/src/test/unit/activity-slice-tree.test.ts index 95af457973..91daea025b 100644 --- a/src/test/unit/activity-slice-tree.test.ts +++ b/src/test/unit/activity-slice-tree.test.ts @@ -5,7 +5,7 @@ import { getSlices, printSliceTree } from '../../utils/slice-tree'; function getSlicesEasy(threadCPUPercentage: number[]): string[] { - const time = threadCPUPercentage.map((_, i) => i); + const time = Float64Array.from(threadCPUPercentage, (_, i) => i); const cpuRatio = new Float64Array(threadCPUPercentage.map((p) => p / 100)); const slices = getSlices([0.05, 0.2, 0.4, 0.6, 0.8], cpuRatio, time); return printSliceTree(slices); @@ -46,7 +46,7 @@ describe('Activity slice tree', function () { { start: 0, end: 1, avg: 0.9, sum: 1000, parent: 0 }, ]; - expect(printSliceTree({ slices, time: [0, 1] })).toEqual([ + expect(printSliceTree({ slices, time: Float64Array.of(0, 1) })).toEqual([ '- 10% for 1.0ms (1 samples): 0.0ms - 1.0ms', ' - 90% for 1.0ms (1 samples): 0.0ms - 1.0ms', '- 20% for 1.0ms (1 samples): 0.0ms - 1.0ms', diff --git a/src/test/unit/combined-cpu.test.ts b/src/test/unit/combined-cpu.test.ts index 87fbc6501a..aa75cff535 100644 --- a/src/test/unit/combined-cpu.test.ts +++ b/src/test/unit/combined-cpu.test.ts @@ -10,7 +10,7 @@ function createSamplesTable(time: number[], cpuRatio: number[]): SamplesTable { const percentValues = cpuRatio.map((v) => Math.round(v * 100)); percentValues.push(0); return { - time, + time: Float64Array.from(time), threadCPUPercent: Uint8Array.from(percentValues), hasCPUDeltas: true, // Other required fields (stubbed for test purposes) @@ -35,7 +35,7 @@ describe('combineCPUDataFromThreads', function () { const result = combineCPUDataFromThreads(samples); expect(result).not.toBeNull(); - expect(result!.time).toEqual([0, 100, 200]); + expect(Array.from(result!.time)).toEqual([0, 100, 200]); expect(Array.from(result!.cpuRatio)).toEqual([0.0, 0.5, 0.8]); }); @@ -48,7 +48,7 @@ describe('combineCPUDataFromThreads', function () { const result = combineCPUDataFromThreads(samples); expect(result).not.toBeNull(); - expect(result!.time).toEqual([0, 100, 200]); + expect(Array.from(result!.time)).toEqual([0, 100, 200]); expect(Array.from(result!.cpuRatio)).toEqual([0, 0.9, 0.8]); }); @@ -62,7 +62,7 @@ describe('combineCPUDataFromThreads', function () { expect(result).not.toBeNull(); // Should have all unique time points - expect(result!.time).toEqual([0, 50, 100, 150, 200, 250]); + expect(Array.from(result!.time)).toEqual([0, 50, 100, 150, 200, 250]); // 0: thread1=bef, thread2=bef → 0.0 // 0- 50: thread1=0.5, thread2=bef → 0.5 @@ -87,7 +87,7 @@ describe('combineCPUDataFromThreads', function () { const result = combineCPUDataFromThreads(samples); expect(result).not.toBeNull(); - expect(result!.time).toEqual([0, 10, 20, 30, 40, 50]); + expect(Array.from(result!.time)).toEqual([0, 10, 20, 30, 40, 50]); // At times 0, 10, 20: only thread1 has samples // At times 30, 40, 50: thread1 has ended (30 > 20), only thread2 contributes diff --git a/src/test/unit/process-profile.test.ts b/src/test/unit/process-profile.test.ts index 11c3e63f05..84bd6bfc3b 100644 --- a/src/test/unit/process-profile.test.ts +++ b/src/test/unit/process-profile.test.ts @@ -284,12 +284,16 @@ describe('gecko counters processing', function () { expect(extractTime(childCounter)).toEqual(originalTime); expect( - computeTimeColumnForRawSamplesTable(processedCounters[0].samples) + Array.from( + computeTimeColumnForRawSamplesTable(processedCounters[0].samples) + ) ).toEqual(originalTime); // The subprocess times are offset when processed: expect( - computeTimeColumnForRawSamplesTable(processedCounters[1].samples) + Array.from( + computeTimeColumnForRawSamplesTable(processedCounters[1].samples) + ) ).toEqual(offsetTime); }); }); @@ -645,7 +649,7 @@ describe('gecko samples table processing', function () { hardcodedStackAfterProcessing ); const sampleTimes = computeTimeColumnForRawSamplesTable(processedSamples); - expect(sampleTimes.slice(0, 2)).toEqual(hardcodedTime); + expect(Array.from(sampleTimes.slice(0, 2))).toEqual(hardcodedTime); expect(ensureExists(processedSamples.eventDelay).slice(0, 2)).toEqual( hardcodedEventDelay ); diff --git a/src/test/unit/profile-query/cpu-activity.test.ts b/src/test/unit/profile-query/cpu-activity.test.ts index 5d1b7f8dc0..9528d19ac4 100644 --- a/src/test/unit/profile-query/cpu-activity.test.ts +++ b/src/test/unit/profile-query/cpu-activity.test.ts @@ -12,7 +12,7 @@ describe('profile-query cpu activity', function () { { start: 1, end: 3, avg: 0.75, sum: 40, parent: 0 }, { start: 2, end: 3, avg: 1, sum: 20, parent: 1 }, ]; - const time = [0, 10, 20, 30, 40]; + const time = Float64Array.of(0, 10, 20, 30, 40); const tsManager = new TimestampManager({ start: 0, end: 40 }); const result = collectSliceTree({ slices, time }, tsManager); @@ -43,6 +43,8 @@ describe('profile-query cpu activity', function () { it('returns an empty list when there are no slices', function () { const tsManager = new TimestampManager({ start: 0, end: 10 }); - expect(collectSliceTree({ slices: [], time: [] }, tsManager)).toEqual([]); + expect( + collectSliceTree({ slices: [], time: new Float64Array() }, tsManager) + ).toEqual([]); }); }); diff --git a/src/types/profile-derived.ts b/src/types/profile-derived.ts index e05e0d59e1..3e724b763c 100644 --- a/src/types/profile-derived.ts +++ b/src/types/profile-derived.ts @@ -128,7 +128,7 @@ export type SamplesTable = { // This is optional because older profiles didn't have that field. eventDelay?: Array; stack: Array; - time: Milliseconds[]; + time: Float64Array; // An optional weight array. If not present, then the weight is assumed to be 1. // See the WeightType type for more information. weight: null | number[]; @@ -163,7 +163,7 @@ export type SampleCategoriesAndSubcategories = { export type SamplesLikeTable = { stack: Array; - time: Milliseconds[]; + time: Float64Array; // An optional weight array. If not present, then the weight is assumed to be 1. // See the WeightType type for more information. weight: null | number[]; @@ -173,7 +173,7 @@ export type SamplesLikeTable = { }; export type CounterSamplesTable = { - time: Milliseconds[]; + time: Float64Array; // The number of times the Counter's "number" was changed since the previous sample. // This property was mandatory until the format version 42, it was made optional in 43. number?: number[]; @@ -186,12 +186,11 @@ export type CounterSamplesTable = { /** * The `JsAllocationsTable` type of the derived thread. * - * Currently identical in shape to the `RawJsAllocationsTable`. Splitting the - * derived type off lets us widen columns (e.g. store `time` as a typed array) - * without affecting the raw type. + * Differs from `RawJsAllocationsTable` in that the `time` column is always a + * `Float64Array`. */ export type JsAllocationsTable = { - time: Milliseconds[]; + time: Float64Array; className: string[]; typeName: string[]; coarseType: string[]; @@ -205,10 +204,11 @@ export type JsAllocationsTable = { /** * The `UnbalancedNativeAllocationsTable` type of the derived thread. * - * Currently identical in shape to the `RawUnbalancedNativeAllocationsTable`. + * Differs from `RawUnbalancedNativeAllocationsTable` in that the `time` column + * is always a `Float64Array`. */ export type UnbalancedNativeAllocationsTable = { - time: Milliseconds[]; + time: Float64Array; weight: Bytes[]; weightType: 'bytes'; stack: Array; @@ -218,8 +218,6 @@ export type UnbalancedNativeAllocationsTable = { /** * The `BalancedNativeAllocationsTable` type of the derived thread. - * - * Currently identical in shape to the `RawBalancedNativeAllocationsTable`. */ export type BalancedNativeAllocationsTable = UnbalancedNativeAllocationsTable & { diff --git a/src/utils/number-series.ts b/src/utils/number-series.ts index e676a9faeb..5a3252e4cf 100644 --- a/src/utils/number-series.ts +++ b/src/utils/number-series.ts @@ -2,8 +2,10 @@ * 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/. */ -export function numberSeriesFromDeltas(deltas: number[]): number[] { - const values = new Array(deltas.length); +export function numberSeriesFromDeltas( + deltas: number[] | Float64Array +): Float64Array { + const values = new Float64Array(deltas.length); let prev = 0; for (let i = 0; i < deltas.length; i++) { const current = prev + deltas[i]; @@ -13,7 +15,9 @@ export function numberSeriesFromDeltas(deltas: number[]): number[] { return values; } -export function numberSeriesToDeltas(values: number[]): number[] { +export function numberSeriesToDeltas( + values: number[] | Float64Array +): number[] { const deltas = new Array(values.length); let prev = 0; for (let i = 0; i < values.length; i++) { diff --git a/src/utils/slice-tree.ts b/src/utils/slice-tree.ts index c2289b2b7b..c218a2eb80 100644 --- a/src/utils/slice-tree.ts +++ b/src/utils/slice-tree.ts @@ -13,7 +13,7 @@ export type Slice = { function addIndexIntervalsExceedingThreshold( threshold: number, cpuRatio: Float64Array, - time: number[], + time: Float64Array, items: Slice[], parent: number | null, startIndex: number = 0, @@ -74,13 +74,13 @@ function addIndexIntervalsExceedingThreshold( export type SliceTree = { slices: Slice[]; - time: number[]; + time: Float64Array; }; export function getSlices( thresholds: number[], cpuRatio: Float64Array, - time: number[], + time: Float64Array, startIndex: number = 0, endIndex: number = cpuRatio.length - 1 ): SliceTree { @@ -114,7 +114,7 @@ export function getSlices( return { slices, time }; } -function sliceToString(slice: Slice, time: number[]): string { +function sliceToString(slice: Slice, time: Float64Array): string { const { avg, start, end } = slice; const startTime = time[start]; const endTime = time[end]; @@ -130,7 +130,7 @@ function appendSliceSubtree( childrenStartPerParent: Array, interestingSliceIndexes: Set, nestingDepth: number, - time: number[], + time: Float64Array, s: string[] ) { for (let i = startIndex; i < slices.length; i++) { From 14cc9ca6d1da32051c479e546dd3bbefa8630f87 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Sun, 5 Jul 2026 22:56:04 -0400 Subject: [PATCH 3/7] Add a raw/derived split for the frame table. Same principle as what we've done for the stack table and the samples table. The goal is to have the columns in the derived version always be typed arrays, and the raw ones can take either a typed array or a plain array. This commit doesn't introduce any differences between the raw and derived frame table; the two are identical for now. --- src/profile-logic/data-structures.ts | 10 ++++--- src/profile-logic/global-data-collector.ts | 8 +++--- src/profile-logic/import/simpleperf.ts | 8 +++--- src/profile-logic/insert-stack-labels.ts | 4 +-- src/profile-logic/merge-compare.ts | 8 +++--- src/profile-logic/process-profile.ts | 4 +-- src/profile-logic/profile-compacting.ts | 4 +-- src/profile-logic/profile-data.ts | 25 +++++++++++++++--- src/profile-logic/source-map-symbolication.ts | 12 ++++----- src/profile-logic/symbolication.ts | 4 +-- src/selectors/per-thread/thread.tsx | 3 +-- src/selectors/profile.ts | 7 +++++ src/test/fixtures/profiles/call-nodes.ts | 4 +-- src/test/fixtures/utils.ts | 4 ++- src/test/unit/profile-data.test.ts | 8 +++--- src/test/unit/profile-tree.test.ts | 17 ++++++------ src/types/actions.ts | 4 +-- src/types/profile-derived.ts | 26 ++++++++++++++++++- src/types/profile.ts | 4 +-- 19 files changed, 110 insertions(+), 54 deletions(-) diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index 76ca32a6d5..d21ee1ca7e 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -11,7 +11,7 @@ import type { RawProfileSharedData, RawThread, RawSamplesTable, - FrameTable, + RawFrameTable, RawStackTable, FuncTable, RawMarkerTable, @@ -118,7 +118,7 @@ export function getEmptySamplesTableWithEventDelay(): RawSamplesTable { }; } -export function getEmptyFrameTable(): FrameTable { +export function getEmptyRawFrameTable(): RawFrameTable { return { // Important! // If modifying this structure, please update all callers of this function to ensure @@ -138,7 +138,9 @@ export function getEmptyFrameTable(): FrameTable { }; } -export function shallowCloneFrameTable(frameTable: FrameTable): FrameTable { +export function shallowCloneRawFrameTable( + frameTable: RawFrameTable +): RawFrameTable { return { // Important! // If modifying this structure, please update all callers of this function to ensure @@ -433,7 +435,7 @@ export function getEmptyThread(overrides?: Partial): RawThread { export function getEmptySharedData(): RawProfileSharedData { return { stackTable: finishRawStackTableBuilder(getRawStackTableBuilder()), - frameTable: getEmptyFrameTable(), + frameTable: getEmptyRawFrameTable(), funcTable: getEmptyFuncTable(), resourceTable: getEmptyResourceTable(), nativeSymbols: getEmptyNativeSymbolTable(), diff --git a/src/profile-logic/global-data-collector.ts b/src/profile-logic/global-data-collector.ts index c52e170293..1055eeafb9 100644 --- a/src/profile-logic/global-data-collector.ts +++ b/src/profile-logic/global-data-collector.ts @@ -5,7 +5,7 @@ import { StringTable } from '../utils/string-table'; import { finishRawStackTableBuilder, - getEmptyFrameTable, + getEmptyRawFrameTable, getEmptyFuncTable, getEmptyNativeSymbolTable, getEmptyResourceTable, @@ -22,7 +22,7 @@ import type { IndexIntoSourceTable, RawProfileSharedData, SourceTable, - FrameTable, + RawFrameTable, FuncTable, ResourceTable, NativeSymbolTable, @@ -50,7 +50,7 @@ export class GlobalDataCollector { _stringArray: string[] = []; _stringTable: StringTable = StringTable.withBackingArray(this._stringArray); _sources: SourceTable = getEmptySourceTable(); - _frameTable: FrameTable = getEmptyFrameTable(); + _frameTable: RawFrameTable = getEmptyRawFrameTable(); _stackTableBuilder: RawStackTableBuilder = getRawStackTableBuilder(); _funcTable: FuncTable = getEmptyFuncTable(); _resourceTable: ResourceTable = getEmptyResourceTable(); @@ -299,7 +299,7 @@ export class GlobalDataCollector { return this._stringTable; } - getFrameTable(): FrameTable { + getFrameTable(): RawFrameTable { return this._frameTable; } diff --git a/src/profile-logic/import/simpleperf.ts b/src/profile-logic/import/simpleperf.ts index 83c38d8929..9bef272707 100644 --- a/src/profile-logic/import/simpleperf.ts +++ b/src/profile-logic/import/simpleperf.ts @@ -5,7 +5,7 @@ import type { Milliseconds } from 'firefox-profiler/types/units'; import type { CategoryList, CategoryColor, - FrameTable, + RawFrameTable, FuncTable, IndexIntoCategoryList, IndexIntoFrameTable, @@ -23,7 +23,7 @@ import type { import { getEmptyFuncTable, getEmptyResourceTable, - getEmptyFrameTable, + getEmptyRawFrameTable, getRawStackTableBuilder, finishRawStackTableBuilder, type RawStackTableBuilder, @@ -150,14 +150,14 @@ class FirefoxFuncTable { class FirefoxFrameTable { strings: StringTable; - frameTable: FrameTable = getEmptyFrameTable(); + frameTable: RawFrameTable = getEmptyRawFrameTable(); frameMap: Map = new Map(); constructor(strings: StringTable) { this.strings = strings; } - toJson(): FrameTable { + toJson(): RawFrameTable { return this.frameTable; } diff --git a/src/profile-logic/insert-stack-labels.ts b/src/profile-logic/insert-stack-labels.ts index 5d281bf46a..c446a56c17 100644 --- a/src/profile-logic/insert-stack-labels.ts +++ b/src/profile-logic/insert-stack-labels.ts @@ -10,7 +10,7 @@ import type { Category, } from '../types/profile'; import { - shallowCloneFrameTable, + shallowCloneRawFrameTable, shallowCloneFuncTable, } from 'firefox-profiler/profile-logic/data-structures'; import { StringTable } from 'firefox-profiler/utils/string-table'; @@ -105,7 +105,7 @@ export function insertStackLabels( sources, stringArray, } = profile.shared; - const frameTable = shallowCloneFrameTable(oldFrameTable); + const frameTable = shallowCloneRawFrameTable(oldFrameTable); const funcTable = shallowCloneFuncTable(oldFuncTable); const stringTable = StringTable.withBackingArray(stringArray); diff --git a/src/profile-logic/merge-compare.ts b/src/profile-logic/merge-compare.ts index a13e993e8e..9a67db9915 100644 --- a/src/profile-logic/merge-compare.ts +++ b/src/profile-logic/merge-compare.ts @@ -11,7 +11,7 @@ import { getEmptyProfile, getEmptyResourceTable, getEmptyNativeSymbolTable, - getEmptyFrameTable, + getEmptyRawFrameTable, getEmptyFuncTable, getRawStackTableBuilder, finishRawStackTableBuilder, @@ -51,7 +51,7 @@ import type { IndexIntoSourceTable, IndexIntoSourceLocationTable, FuncTable, - FrameTable, + RawFrameTable, Lib, NativeSymbolTable, ResourceTable, @@ -1057,9 +1057,9 @@ function mergeFrameTables( translationMapsForNativeSymbols: TranslationMapForNativeSymbols[], translationMapsForOriginalLocation: TranslationMapForOriginalLocation[], translationMapsForCategories: TranslationMapForCategories[] -): { frameTable: FrameTable; translationMaps: TranslationMapForFrames[] } { +): { frameTable: RawFrameTable; translationMaps: TranslationMapForFrames[] } { const translationMaps: TranslationMapForFrames[] = []; - const newFrameTable = getEmptyFrameTable(); + const newFrameTable = getEmptyRawFrameTable(); profiles.forEach((profile, profileIndex) => { const { frameTable } = profile.shared; diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index 31dfde4ce8..9e8d45187f 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -52,7 +52,7 @@ import type { RawThread, RawCounter, ExtensionTable, - FrameTable, + RawFrameTable, RawCounterSamplesTable, RawSamplesTable, RawMarkerTable, @@ -521,7 +521,7 @@ function _extractUnknownFunctionType( */ function _processFrameTable( geckoFrameStruct: GeckoFrameStruct, - sharedFrameTable: FrameTable, + sharedFrameTable: RawFrameTable, frameFuncs: IndexIntoFuncTable[], frameAddresses: (Address | null)[] ): IndexIntoFrameTable { diff --git a/src/profile-logic/profile-compacting.ts b/src/profile-logic/profile-compacting.ts index 3704452a0c..9544a7a28d 100644 --- a/src/profile-logic/profile-compacting.ts +++ b/src/profile-logic/profile-compacting.ts @@ -12,7 +12,7 @@ import type { RawMarkerTable, IndexIntoStackTable, RawStackTable, - FrameTable, + RawFrameTable, FuncTable, ResourceTable, NativeSymbolTable, @@ -159,7 +159,7 @@ export function computeCompactedProfile( frame: ColDesc.indexRefInt32(tcs.frameTable), prefixOffset: ColDesc.selfPrefixOffset(), }; - const frameTableDesc: TableDescription = { + const frameTableDesc: TableDescription = { address: ColDesc.noRef(), inlineDepth: ColDesc.noRef(), category: ColDesc.noRef(), diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index e071318d8b..20464f9e17 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -9,7 +9,7 @@ import { getRawStackTableBuilder, finishRawStackTableBuilder, getEmptyCallNodeTable, - shallowCloneFrameTable, + shallowCloneRawFrameTable, shallowCloneFuncTable, } from './data-structures'; import { @@ -54,6 +54,7 @@ import type { RawStackTable, SampleUnits, StackTable, + RawFrameTable, FrameTable, FuncTable, NativeSymbolTable, @@ -4364,7 +4365,7 @@ export function nudgeReturnAddresses(profile: Profile): Profile { // Create the new frame table. // Frames that were observed both from the instruction pointer and from // stack walking have to be duplicated. - const newFrameTable = shallowCloneFrameTable(frameTable); + const newFrameTable = shallowCloneRawFrameTable(frameTable); // Iterate over all *return address* frames, i.e. all frames that were obtained // by stack walking. for (const [frame, address] of returnAddressFrames) { @@ -4778,9 +4779,27 @@ export function computeTabToThreadIndexesMap( return tabToThreadIndexesMap; } +export function computeFrameTableFromRawFrameTable( + rawFrameTable: RawFrameTable +): FrameTable { + return { + address: rawFrameTable.address, + inlineDepth: rawFrameTable.inlineDepth, + category: rawFrameTable.category, + subcategory: rawFrameTable.subcategory, + func: rawFrameTable.func, + nativeSymbol: rawFrameTable.nativeSymbol, + innerWindowID: rawFrameTable.innerWindowID, + line: rawFrameTable.line, + column: rawFrameTable.column, + originalLocation: rawFrameTable.originalLocation, + length: rawFrameTable.length, + }; +} + export function computeStackTableFromRawStackTable( rawStackTable: RawStackTable, - frameTable: FrameTable, + frameTable: RawFrameTable, categories: CategoryList | undefined, defaultCategory: IndexIntoCategoryList ): StackTable { diff --git a/src/profile-logic/source-map-symbolication.ts b/src/profile-logic/source-map-symbolication.ts index f4d5ebe4c6..50cfca30ef 100644 --- a/src/profile-logic/source-map-symbolication.ts +++ b/src/profile-logic/source-map-symbolication.ts @@ -68,7 +68,7 @@ import { shallowCloneFuncTable, - shallowCloneFrameTable, + shallowCloneRawFrameTable, shallowCloneSourceLocationTable, } from './data-structures'; import { StringTable } from '../utils/string-table'; @@ -92,7 +92,7 @@ import type { IndexIntoFuncTable, IndexIntoFrameTable, FuncTable, - FrameTable, + RawFrameTable, RawProfileSharedData, SourceLocationTable, SourceTable, @@ -132,7 +132,7 @@ type ParsedSource = { // main thread in applySourceMapSymbolicationResponse, which builds new // tables from the current shared state at apply time. export type SourceMapSymbolicationInput = { - frameTable: FrameTable; + frameTable: RawFrameTable; funcTable: FuncTable; sourceLocationTable: SourceLocationTable; sources: SourceTable; @@ -185,7 +185,7 @@ export function symbolicateWithSourceMaps( * candidates for line/column remapping. */ function _identifyToSymbolicate( - frameTable: FrameTable, + frameTable: RawFrameTable, funcTable: FuncTable, sources: SourceTable ): { @@ -949,7 +949,7 @@ export function applySourceMapSymbolicationResponse( response: SourceMapSymbolicationResponse ): { newFuncTable: FuncTable; - newFrameTable: FrameTable; + newFrameTable: RawFrameTable; newSourceLocationTable: SourceLocationTable; newSources: SourceTable; newStringArray: string[]; @@ -958,7 +958,7 @@ export function applySourceMapSymbolicationResponse( shared; const newFuncTable = shallowCloneFuncTable(funcTable); - const newFrameTable = shallowCloneFrameTable(frameTable); + const newFrameTable = shallowCloneRawFrameTable(frameTable); const newSourceLocationTable = shallowCloneSourceLocationTable(sourceLocationTable); const newSources = _shallowCloneSourceTable(sources); diff --git a/src/profile-logic/symbolication.ts b/src/profile-logic/symbolication.ts index 52a53d7c1a..b77c921d17 100644 --- a/src/profile-logic/symbolication.ts +++ b/src/profile-logic/symbolication.ts @@ -6,7 +6,7 @@ import { finishRawStackTableBuilder, shallowCloneFuncTable, shallowCloneNativeSymbolTable, - shallowCloneFrameTable, + shallowCloneRawFrameTable, } from './data-structures'; import { SymbolsNotFoundError } from './errors'; @@ -716,7 +716,7 @@ function _partiallyApplySymbolicationStep( // Integrate the new native symbol column into the frame table and make a // copy so that we can add new frames below. - const frameTable = shallowCloneFrameTable({ + const frameTable = shallowCloneRawFrameTable({ ...oldFrameTable, nativeSymbol: newFrameTableNativeSymbolsColumn, }); diff --git a/src/selectors/per-thread/thread.tsx b/src/selectors/per-thread/thread.tsx index ce13fd664a..f83961bbbe 100644 --- a/src/selectors/per-thread/thread.tsx +++ b/src/selectors/per-thread/thread.tsx @@ -199,8 +199,7 @@ export function getBasicThreadSelectorsPerThread( getRawThread, getSamplesTable, ProfileSelectors.getStackTable, - (state: State) => - ProfileSelectors.getRawProfileSharedData(state).frameTable, + ProfileSelectors.getFrameTable, ProfileSelectors.getFunctionsReservedFuncTable, (state: State) => ProfileSelectors.getRawProfileSharedData(state).nativeSymbols, diff --git a/src/selectors/profile.ts b/src/selectors/profile.ts index 896ca26b27..9a2df2e36a 100644 --- a/src/selectors/profile.ts +++ b/src/selectors/profile.ts @@ -20,6 +20,7 @@ import { computeInnerWindowIDToTabMap, computeTabToThreadIndexesMap, computeStackTableFromRawStackTable, + computeFrameTableFromRawFrameTable, reserveFunctionsForCollapsedResources, computeSamplesTableFromRawSamplesTable, } from '../profile-logic/profile-data'; @@ -39,6 +40,7 @@ import type { Profile, RawProfileSharedData, StackTable, + FrameTable, CategoryList, IndexIntoCategoryList, RawThread, @@ -283,6 +285,11 @@ export const getStackTable: Selector = createSelector( computeStackTableFromRawStackTable ); +export const getFrameTable: Selector = createSelector( + (state: State) => getRawProfileSharedData(state).frameTable, + computeFrameTableFromRawFrameTable +); + export const getFuncTableWithReservedFunctions: Selector = createSelector( (state: State) => getRawProfileSharedData(state).funcTable, diff --git a/src/test/fixtures/profiles/call-nodes.ts b/src/test/fixtures/profiles/call-nodes.ts index 0ffed2dd52..8692daf1ab 100644 --- a/src/test/fixtures/profiles/call-nodes.ts +++ b/src/test/fixtures/profiles/call-nodes.ts @@ -1,7 +1,7 @@ /* 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 type { FuncTable, FrameTable, Profile } from 'firefox-profiler/types'; +import type { FuncTable, RawFrameTable, Profile } from 'firefox-profiler/types'; import { getEmptyThread, @@ -74,7 +74,7 @@ export default function getProfile(): Profile { funcFFrame, ] = frameFuncs.map((_, i) => i); - const frameTable: FrameTable = { + const frameTable: RawFrameTable = { func: frameFuncs.map((stringIndex) => funcTable.name.indexOf(stringIndex)), address: Array(frameFuncs.length).fill(-1), inlineDepth: Array(frameFuncs.length).fill(0), diff --git a/src/test/fixtures/utils.ts b/src/test/fixtures/utils.ts index 77e120fc72..83c68020cb 100644 --- a/src/test/fixtures/utils.ts +++ b/src/test/fixtures/utils.ts @@ -18,6 +18,7 @@ import { getOriginAnnotationForFunc, createThreadFromDerivedTables, computeStackTableFromRawStackTable, + computeFrameTableFromRawFrameTable, computeSamplesTableFromRawSamplesTable, computeJsAllocationsTableFromRawJsAllocationsTable, computeNativeAllocationsTableFromRawNativeAllocationsTable, @@ -174,11 +175,12 @@ export function computeThreadFromRawThread( rawThread.nativeAllocations ) : undefined; + const frameTable = computeFrameTableFromRawFrameTable(shared.frameTable); return createThreadFromDerivedTables( rawThread, samples, stackTable, - shared.frameTable, + frameTable, shared.funcTable, shared.nativeSymbols, shared.resourceTable, diff --git a/src/test/unit/profile-data.test.ts b/src/test/unit/profile-data.test.ts index 7be0938fde..206e8a07b6 100644 --- a/src/test/unit/profile-data.test.ts +++ b/src/test/unit/profile-data.test.ts @@ -22,6 +22,7 @@ import { extractProfileFilterPageData, findAddressProofForFile, calculateFunctionSizeLowerBound, + computeFrameTableFromRawFrameTable, getNativeSymbolsForCallNode, getNativeSymbolInfo, computeTimeColumnForRawSamplesTable, @@ -1475,7 +1476,7 @@ describe('calculateFunctionSizeLowerBound', function () { const nativeSymbolIndex = nativeSymbolsDict.symSomeFunc; const functionSizeLowerBound = calculateFunctionSizeLowerBound( - shared.frameTable, + computeFrameTableFromRawFrameTable(shared.frameTable), 0x1000, nativeSymbolIndex ); @@ -1769,12 +1770,13 @@ describe('getNativeSymbolInfo', function () { const { shared } = profile; const stringTable = StringTable.withBackingArray(shared.stringArray); const { symSomeFunc, symOtherFunc } = nativeSymbolsDictPerThread[0]; + const frameTable = computeFrameTableFromRawFrameTable(shared.frameTable); expect( getNativeSymbolInfo( symSomeFunc, shared.nativeSymbols, - shared.frameTable, + frameTable, stringTable ) ).toEqual({ @@ -1788,7 +1790,7 @@ describe('getNativeSymbolInfo', function () { getNativeSymbolInfo( symOtherFunc, shared.nativeSymbols, - shared.frameTable, + frameTable, stringTable ) ).toEqual({ diff --git a/src/test/unit/profile-tree.test.ts b/src/test/unit/profile-tree.test.ts index 566bedf105..90f8a59a91 100644 --- a/src/test/unit/profile-tree.test.ts +++ b/src/test/unit/profile-tree.test.ts @@ -13,6 +13,7 @@ import { } from '../../profile-logic/call-tree'; import { computeFlameGraphRows } from '../../profile-logic/flame-graph'; import { + computeFrameTableFromRawFrameTable, getCallNodeInfo, getInvertedCallNodeInfo, getOriginAnnotationForFunc, @@ -726,7 +727,7 @@ describe('origin annotation', function () { return getOriginAnnotationForFunc( funcNames.indexOf(funcName), null, - shared.frameTable, + computeFrameTableFromRawFrameTable(shared.frameTable), shared.funcTable, shared.resourceTable, stringTable, @@ -795,7 +796,7 @@ describe('getOriginAnnotationForFunc with originalLocation', function () { return getOriginAnnotationForFunc( 0, 0, - shared.frameTable, + computeFrameTableFromRawFrameTable(shared.frameTable), shared.funcTable, shared.resourceTable, stringTable, @@ -883,7 +884,7 @@ describe('getOriginalPositionForFrame', function () { getOriginalPositionForFrame( 0, 0, - shared.frameTable, + computeFrameTableFromRawFrameTable(shared.frameTable), shared.funcTable, shared.sourceLocationTable ) @@ -901,7 +902,7 @@ describe('getOriginalPositionForFrame', function () { getOriginalPositionForFrame( 0, 0, - shared.frameTable, + computeFrameTableFromRawFrameTable(shared.frameTable), shared.funcTable, shared.sourceLocationTable ) @@ -914,7 +915,7 @@ describe('getOriginalPositionForFrame', function () { getOriginalPositionForFrame( 0, 0, - shared.frameTable, + computeFrameTableFromRawFrameTable(shared.frameTable), shared.funcTable, shared.sourceLocationTable ) @@ -929,7 +930,7 @@ describe('getOriginalPositionForFrame', function () { getOriginalPositionForFrame( 0, 0, - shared.frameTable, + computeFrameTableFromRawFrameTable(shared.frameTable), shared.funcTable, shared.sourceLocationTable ) @@ -947,7 +948,7 @@ describe('getOriginalPositionForFrame', function () { getOriginalPositionForFrame( null, 0, - shared.frameTable, + computeFrameTableFromRawFrameTable(shared.frameTable), shared.funcTable, shared.sourceLocationTable ) @@ -966,7 +967,7 @@ describe('getOriginalPositionForFrame', function () { getOriginalPositionForFrame( 0, 0, - shared.frameTable, + computeFrameTableFromRawFrameTable(shared.frameTable), shared.funcTable, null ) diff --git a/src/types/actions.ts b/src/types/actions.ts index fdde883ad1..42dbae604c 100644 --- a/src/types/actions.ts +++ b/src/types/actions.ts @@ -15,7 +15,7 @@ import type { PageList, IndexIntoSourceTable, FuncTable, - FrameTable, + RawFrameTable, SourceLocationTable, SourceTable, } from './profile'; @@ -455,7 +455,7 @@ type ReceiveProfileAction = | { readonly type: 'BULK_SOURCE_MAP_SYMBOLICATION'; readonly newFuncTable: FuncTable; - readonly newFrameTable: FrameTable; + readonly newFrameTable: RawFrameTable; readonly newSourceLocationTable: SourceLocationTable; readonly newSources: SourceTable; readonly newStringArray: string[]; diff --git a/src/types/profile-derived.ts b/src/types/profile-derived.ts index 3e724b763c..57024192e2 100644 --- a/src/types/profile-derived.ts +++ b/src/types/profile-derived.ts @@ -7,10 +7,13 @@ import type { MarkerPayload, MarkerSchema } from './markers'; import type { ThreadIndex, Pid, + InnerWindowID, IndexIntoFuncTable, IndexIntoJsTracerEvents, IndexIntoCategoryList, + IndexIntoSubcategoryListForCategory, IndexIntoResourceTable, + IndexIntoNativeSymbolTable, IndexIntoLibs, CounterIndex, IndexIntoRawMarkerTable, @@ -20,7 +23,6 @@ import type { ProcessType, PausedRange, RawMarkerTable, - FrameTable, FuncTable, ResourceTable, NativeSymbolTable, @@ -29,6 +31,7 @@ import type { WeightType, SourceTable, IndexIntoSourceTable, + IndexIntoSourceLocationTable, CounterDisplayConfig, SourceLocationTable, } from './profile'; @@ -282,6 +285,27 @@ export type StackTable = { subcategory: Uint8Array | Uint16Array; // represents a Map }; +/** + * The `FrameTable` type of the derived thread. + * + * Currently identical in shape to the `RawFrameTable`. Splitting the derived + * type off lets us widen columns (e.g. store `func` as a typed array) without + * affecting the raw type. + */ +export type FrameTable = { + address: Array
; + inlineDepth: number[]; + category: (IndexIntoCategoryList | null)[]; + subcategory: (IndexIntoSubcategoryListForCategory | null)[]; + func: IndexIntoFuncTable[]; + nativeSymbol: (IndexIntoNativeSymbolTable | null)[]; + innerWindowID: (InnerWindowID | null)[]; + line: (number | null)[]; + column: (number | null)[]; + originalLocation: Array; + length: number; +}; + /** * Similar to the StackTable, but based on functions rather than on frames. * diff --git a/src/types/profile.ts b/src/types/profile.ts index 63c2ccff11..61a4060a2d 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -233,7 +233,7 @@ export type RawMarkerTable = { * Frames contain the context information about the function execution at the moment in * time. The caller/callee relationship between frames is defined by the StackTable. */ -export type FrameTable = { +export type RawFrameTable = { // If this is a frame for native code, the address is the address of the frame's // assembly instruction, relative to the native library that contains it. // @@ -1086,7 +1086,7 @@ export type SourceLocationTable = { export type RawProfileSharedData = { stackTable: RawStackTable; - frameTable: FrameTable; + frameTable: RawFrameTable; funcTable: FuncTable; resourceTable: ResourceTable; nativeSymbols: NativeSymbolTable; From 6d559438b13213a846e6de5ac79e3337b5b85bbd Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Sun, 5 Jul 2026 22:58:24 -0400 Subject: [PATCH 4/7] Store some columns of the derived frame table as typed arrays. Specifically: - `func` as Int32Array - `address` as Int32Array (already uses -1 for "no address") - `inlineDepth` as a Uint8Array. The raw frame table is unchanged, so this does not affect the profile format. --- src/profile-logic/profile-data.ts | 9 ++++-- .../__snapshots__/profile-view.test.ts.snap | 30 +++++++++---------- src/types/profile-derived.ts | 18 +++++++---- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 20464f9e17..6326f13a64 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -4782,12 +4782,15 @@ export function computeTabToThreadIndexesMap( export function computeFrameTableFromRawFrameTable( rawFrameTable: RawFrameTable ): FrameTable { + const address = new Int32Array(rawFrameTable.address); + const inlineDepth = new Uint8Array(rawFrameTable.inlineDepth); + const func = new Int32Array(rawFrameTable.func); return { - address: rawFrameTable.address, - inlineDepth: rawFrameTable.inlineDepth, + address, + inlineDepth, category: rawFrameTable.category, subcategory: rawFrameTable.subcategory, - func: rawFrameTable.func, + func, nativeSymbol: rawFrameTable.nativeSymbol, innerWindowID: rawFrameTable.innerWindowID, line: rawFrameTable.line, diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index 4bd0e4d8c7..ed4e431ab4 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -2410,7 +2410,7 @@ CallTree { "_thread": Object { "eTLD+1": undefined, "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -2443,7 +2443,7 @@ CallTree { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -2454,7 +2454,7 @@ CallTree { 7, 8, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -2798,7 +2798,7 @@ exports[`snapshots of selectors/profile matches the last stored run of selectedT Object { "eTLD+1": undefined, "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -2831,7 +2831,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -2842,7 +2842,7 @@ Object { 7, 8, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -3266,7 +3266,7 @@ exports[`snapshots of selectors/profile matches the last stored run of selectedT Object { "eTLD+1": undefined, "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -3299,7 +3299,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -3310,7 +3310,7 @@ Object { 7, 8, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -3640,7 +3640,7 @@ exports[`snapshots of selectors/profile matches the last stored run of selectedT Object { "eTLD+1": undefined, "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -3673,7 +3673,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -3684,7 +3684,7 @@ Object { 7, 8, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -4026,7 +4026,7 @@ exports[`snapshots of selectors/profile matches the last stored run of selectedT Object { "eTLD+1": undefined, "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -4059,7 +4059,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -4070,7 +4070,7 @@ Object { 7, 8, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, diff --git a/src/types/profile-derived.ts b/src/types/profile-derived.ts index 57024192e2..f7d61057f6 100644 --- a/src/types/profile-derived.ts +++ b/src/types/profile-derived.ts @@ -288,16 +288,22 @@ export type StackTable = { /** * The `FrameTable` type of the derived thread. * - * Currently identical in shape to the `RawFrameTable`. Splitting the derived - * type off lets us widen columns (e.g. store `func` as a typed array) without - * affecting the raw type. + * Differs from `RawFrameTable` in that the following columns are always + * stored as typed arrays: `address` (`Int32Array`, with `-1` as the sentinel + * for missing addresses), `inlineDepth` (`Uint8Array`), and `func` + * (`Int32Array`). In `RawFrameTable`, these columns may be either regular + * arrays or typed arrays, since regular arrays are convenient during + * construction. */ export type FrameTable = { - address: Array
; - inlineDepth: number[]; + // Differs from RawFrameTable: always Int32Array (-1 sentinel preserved). + address: Int32Array; + // Differs from RawFrameTable: always Uint8Array. + inlineDepth: Uint8Array; category: (IndexIntoCategoryList | null)[]; subcategory: (IndexIntoSubcategoryListForCategory | null)[]; - func: IndexIntoFuncTable[]; + // Differs from RawFrameTable: always Int32Array. + func: Int32Array; nativeSymbol: (IndexIntoNativeSymbolTable | null)[]; innerWindowID: (InnerWindowID | null)[]; line: (number | null)[]; From 7962c4e902adca56024c0d96af59721cd2a15065 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 6 Jul 2026 12:46:56 -0400 Subject: [PATCH 5/7] Introduce a `RawFrameTableBuilder` type. Similar to what we've done for the raw stack table builder: The builder always has plain arrays so that we can add frames one by one by pushing to the column arrays. --- src/profile-logic/data-structures.ts | 56 +++++++++++++++---- src/profile-logic/global-data-collector.ts | 15 +++-- src/profile-logic/import/simpleperf.ts | 8 ++- src/profile-logic/insert-stack-labels.ts | 12 +++- src/profile-logic/js-tracer.ts | 10 +++- src/profile-logic/merge-compare.ts | 10 +++- src/profile-logic/process-profile.ts | 4 +- src/profile-logic/profile-data.ts | 7 ++- src/profile-logic/source-map-symbolication.ts | 7 ++- src/profile-logic/symbolication.ts | 7 ++- .../fixtures/profiles/processed-profile.ts | 35 ++++++++---- 11 files changed, 121 insertions(+), 50 deletions(-) diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index d21ee1ca7e..9b4ee13869 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -28,9 +28,45 @@ import type { SourceTable, SourceLocationTable, IndexIntoFrameTable, + IndexIntoFuncTable, IndexIntoStackTable, + IndexIntoCategoryList, + IndexIntoSubcategoryListForCategory, + IndexIntoNativeSymbolTable, + IndexIntoSourceLocationTable, + InnerWindowID, + Address, } from 'firefox-profiler/types'; +/** + * Builder-variants of various tables. The columns here use plain + * arrays so that elements can be added one-by-one by pushing to + * the column arrays. + * + * The "raw" variants of these arrays (i.e. what's stored in the + * profile files) may be using typed arrays for some of the columns, + * and you can't push to a typed array. + */ +export type RawFrameTableBuilder = { + address: Array
; + inlineDepth: number[]; + category: (IndexIntoCategoryList | null)[]; + subcategory: (IndexIntoSubcategoryListForCategory | null)[]; + func: IndexIntoFuncTable[]; + nativeSymbol: (IndexIntoNativeSymbolTable | null)[]; + innerWindowID: (InnerWindowID | null)[]; + line: (number | null)[]; + column: (number | null)[]; + originalLocation: Array; + length: number; +}; + +export type RawStackTableBuilder = { + frame: IndexIntoFrameTable[]; + prefix: Array; + length: number; +}; + /** * This module collects all of the creation of new empty profile data structures. */ @@ -49,12 +85,6 @@ export function getEmptySamplesTable(): RawSamplesTable { }; } -export type RawStackTableBuilder = { - frame: IndexIntoFrameTable[]; - prefix: Array; - length: number; -}; - export function getRawStackTableBuilder(): RawStackTableBuilder { return { // Important! @@ -118,7 +148,7 @@ export function getEmptySamplesTableWithEventDelay(): RawSamplesTable { }; } -export function getEmptyRawFrameTable(): RawFrameTable { +export function getRawFrameTableBuilder(): RawFrameTableBuilder { return { // Important! // If modifying this structure, please update all callers of this function to ensure @@ -138,9 +168,9 @@ export function getEmptyRawFrameTable(): RawFrameTable { }; } -export function shallowCloneRawFrameTable( +export function getRawFrameTableBuilderWithExistingContents( frameTable: RawFrameTable -): RawFrameTable { +): RawFrameTableBuilder { return { // Important! // If modifying this structure, please update all callers of this function to ensure @@ -160,6 +190,12 @@ export function shallowCloneRawFrameTable( }; } +export function finishRawFrameTableBuilder( + builder: RawFrameTableBuilder +): RawFrameTable { + return builder; +} + export function getEmptyFuncTable(): FuncTable { return { // Important! @@ -435,7 +471,7 @@ export function getEmptyThread(overrides?: Partial): RawThread { export function getEmptySharedData(): RawProfileSharedData { return { stackTable: finishRawStackTableBuilder(getRawStackTableBuilder()), - frameTable: getEmptyRawFrameTable(), + frameTable: finishRawFrameTableBuilder(getRawFrameTableBuilder()), funcTable: getEmptyFuncTable(), resourceTable: getEmptyResourceTable(), nativeSymbols: getEmptyNativeSymbolTable(), diff --git a/src/profile-logic/global-data-collector.ts b/src/profile-logic/global-data-collector.ts index 1055eeafb9..03333b58c7 100644 --- a/src/profile-logic/global-data-collector.ts +++ b/src/profile-logic/global-data-collector.ts @@ -4,8 +4,9 @@ import { StringTable } from '../utils/string-table'; import { + finishRawFrameTableBuilder, finishRawStackTableBuilder, - getEmptyRawFrameTable, + getRawFrameTableBuilder, getEmptyFuncTable, getEmptyNativeSymbolTable, getEmptyResourceTable, @@ -22,7 +23,6 @@ import type { IndexIntoSourceTable, RawProfileSharedData, SourceTable, - RawFrameTable, FuncTable, ResourceTable, NativeSymbolTable, @@ -34,7 +34,10 @@ import type { Bytes, } from 'firefox-profiler/types'; import { ResourceType } from 'firefox-profiler/types'; -import type { RawStackTableBuilder } from './data-structures'; +import type { + RawFrameTableBuilder, + RawStackTableBuilder, +} from './data-structures'; /** * GlobalDataCollector collects data which is global in the processed profile @@ -50,7 +53,7 @@ export class GlobalDataCollector { _stringArray: string[] = []; _stringTable: StringTable = StringTable.withBackingArray(this._stringArray); _sources: SourceTable = getEmptySourceTable(); - _frameTable: RawFrameTable = getEmptyRawFrameTable(); + _frameTable: RawFrameTableBuilder = getRawFrameTableBuilder(); _stackTableBuilder: RawStackTableBuilder = getRawStackTableBuilder(); _funcTable: FuncTable = getEmptyFuncTable(); _resourceTable: ResourceTable = getEmptyResourceTable(); @@ -299,7 +302,7 @@ export class GlobalDataCollector { return this._stringTable; } - getFrameTable(): RawFrameTable { + getFrameTable(): RawFrameTableBuilder { return this._frameTable; } @@ -312,7 +315,7 @@ export class GlobalDataCollector { finish(): { libs: Lib[]; shared: RawProfileSharedData } { const shared: RawProfileSharedData = { stackTable: finishRawStackTableBuilder(this._stackTableBuilder), - frameTable: this._frameTable, + frameTable: finishRawFrameTableBuilder(this._frameTable), funcTable: this._funcTable, resourceTable: this._resourceTable, nativeSymbols: this._nativeSymbols, diff --git a/src/profile-logic/import/simpleperf.ts b/src/profile-logic/import/simpleperf.ts index 9bef272707..4110ed10f2 100644 --- a/src/profile-logic/import/simpleperf.ts +++ b/src/profile-logic/import/simpleperf.ts @@ -23,9 +23,11 @@ import type { import { getEmptyFuncTable, getEmptyResourceTable, - getEmptyRawFrameTable, + getRawFrameTableBuilder, getRawStackTableBuilder, + finishRawFrameTableBuilder, finishRawStackTableBuilder, + type RawFrameTableBuilder, type RawStackTableBuilder, getEmptySamplesTable, getEmptyRawMarkerTable, @@ -150,7 +152,7 @@ class FirefoxFuncTable { class FirefoxFrameTable { strings: StringTable; - frameTable: RawFrameTable = getEmptyRawFrameTable(); + frameTable: RawFrameTableBuilder = getRawFrameTableBuilder(); frameMap: Map = new Map(); constructor(strings: StringTable) { @@ -158,7 +160,7 @@ class FirefoxFrameTable { } toJson(): RawFrameTable { - return this.frameTable; + return finishRawFrameTableBuilder(this.frameTable); } findOrAddFrame( diff --git a/src/profile-logic/insert-stack-labels.ts b/src/profile-logic/insert-stack-labels.ts index c446a56c17..e614569e82 100644 --- a/src/profile-logic/insert-stack-labels.ts +++ b/src/profile-logic/insert-stack-labels.ts @@ -10,7 +10,8 @@ import type { Category, } from '../types/profile'; import { - shallowCloneRawFrameTable, + finishRawFrameTableBuilder, + getRawFrameTableBuilderWithExistingContents, shallowCloneFuncTable, } from 'firefox-profiler/profile-logic/data-structures'; import { StringTable } from 'firefox-profiler/utils/string-table'; @@ -105,7 +106,7 @@ export function insertStackLabels( sources, stringArray, } = profile.shared; - const frameTable = shallowCloneRawFrameTable(oldFrameTable); + const frameTable = getRawFrameTableBuilderWithExistingContents(oldFrameTable); const funcTable = shallowCloneFuncTable(oldFuncTable); const stringTable = StringTable.withBackingArray(stringArray); @@ -274,7 +275,12 @@ export function insertStackLabels( length: newStackCount, }; - const newShared = { ...profile.shared, stackTable, frameTable, funcTable }; + const newShared = { + ...profile.shared, + stackTable, + frameTable: finishRawFrameTableBuilder(frameTable), + funcTable, + }; const newThreads = updateRawThreadStacks(profile.threads, (oldStack) => oldStack !== null ? oldStackToNewStackPlusOne[oldStack] - 1 : null ); diff --git a/src/profile-logic/js-tracer.ts b/src/profile-logic/js-tracer.ts index e4664aee95..564a54802a 100644 --- a/src/profile-logic/js-tracer.ts +++ b/src/profile-logic/js-tracer.ts @@ -4,8 +4,10 @@ import { getEmptySamplesTableWithEventDelay, getEmptyRawMarkerTable, + finishRawFrameTableBuilder, finishRawStackTableBuilder, getRawStackTableBuilderWithExistingContents, + getRawFrameTableBuilderWithExistingContents, } from './data-structures'; import { StringTable } from '../utils/string-table'; import { ensureExists } from '../utils/types'; @@ -514,7 +516,10 @@ export function convertJsTracerToThreadWithoutSamples( samples, }; - const { funcTable, frameTable } = shared; + const { funcTable } = shared; + const frameTable = getRawFrameTableBuilderWithExistingContents( + shared.frameTable + ); const stackTable = getRawStackTableBuilderWithExistingContents( shared.stackTable ); @@ -625,8 +630,9 @@ export function convertJsTracerToThreadWithoutSamples( unmatchedEventEnds[unmatchedIndex] = end; } - // Write the augmented stackTable back to the shared data. + // Write the augmented stackTable and frameTable back to the shared data. shared.stackTable = finishRawStackTableBuilder(stackTable); + shared.frameTable = finishRawFrameTableBuilder(frameTable); return { thread, stackMap }; } diff --git a/src/profile-logic/merge-compare.ts b/src/profile-logic/merge-compare.ts index 9a67db9915..f18fdc1ac5 100644 --- a/src/profile-logic/merge-compare.ts +++ b/src/profile-logic/merge-compare.ts @@ -11,7 +11,8 @@ import { getEmptyProfile, getEmptyResourceTable, getEmptyNativeSymbolTable, - getEmptyRawFrameTable, + finishRawFrameTableBuilder, + getRawFrameTableBuilder, getEmptyFuncTable, getRawStackTableBuilder, finishRawStackTableBuilder, @@ -1059,7 +1060,7 @@ function mergeFrameTables( translationMapsForCategories: TranslationMapForCategories[] ): { frameTable: RawFrameTable; translationMaps: TranslationMapForFrames[] } { const translationMaps: TranslationMapForFrames[] = []; - const newFrameTable = getEmptyRawFrameTable(); + const newFrameTable = getRawFrameTableBuilder(); profiles.forEach((profile, profileIndex) => { const { frameTable } = profile.shared; @@ -1108,7 +1109,10 @@ function mergeFrameTables( translationMaps.push(oldFrameToNewFramePlusOne); }); - return { frameTable: newFrameTable, translationMaps }; + return { + frameTable: finishRawFrameTableBuilder(newFrameTable), + translationMaps, + }; } /** diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index 9e8d45187f..3e45a9a68b 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -16,6 +16,7 @@ import { getEmptyRawMarkerTable, getEmptyRawJsAllocationsTable, getEmptyRawUnbalancedNativeAllocationsTable, + type RawFrameTableBuilder, type RawStackTableBuilder, } from './data-structures'; import { immutableUpdate, ensureExists } from '../utils/types'; @@ -52,7 +53,6 @@ import type { RawThread, RawCounter, ExtensionTable, - RawFrameTable, RawCounterSamplesTable, RawSamplesTable, RawMarkerTable, @@ -521,7 +521,7 @@ function _extractUnknownFunctionType( */ function _processFrameTable( geckoFrameStruct: GeckoFrameStruct, - sharedFrameTable: RawFrameTable, + sharedFrameTable: RawFrameTableBuilder, frameFuncs: IndexIntoFuncTable[], frameAddresses: (Address | null)[] ): IndexIntoFrameTable { diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 6326f13a64..7374e55f61 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -7,9 +7,10 @@ import MixedTupleMap from 'mixedtuplemap'; import { oneLine } from 'common-tags'; import { getRawStackTableBuilder, + finishRawFrameTableBuilder, finishRawStackTableBuilder, getEmptyCallNodeTable, - shallowCloneRawFrameTable, + getRawFrameTableBuilderWithExistingContents, shallowCloneFuncTable, } from './data-structures'; import { @@ -4365,7 +4366,7 @@ export function nudgeReturnAddresses(profile: Profile): Profile { // Create the new frame table. // Frames that were observed both from the instruction pointer and from // stack walking have to be duplicated. - const newFrameTable = shallowCloneRawFrameTable(frameTable); + const newFrameTable = getRawFrameTableBuilderWithExistingContents(frameTable); // Iterate over all *return address* frames, i.e. all frames that were obtained // by stack walking. for (const [frame, address] of returnAddressFrames) { @@ -4448,7 +4449,7 @@ export function nudgeReturnAddresses(profile: Profile): Profile { const newShared: RawProfileSharedData = { ...profile.shared, - frameTable: newFrameTable, + frameTable: finishRawFrameTableBuilder(newFrameTable), stackTable: finishRawStackTableBuilder(newStackTable), }; diff --git a/src/profile-logic/source-map-symbolication.ts b/src/profile-logic/source-map-symbolication.ts index 50cfca30ef..2c013c5323 100644 --- a/src/profile-logic/source-map-symbolication.ts +++ b/src/profile-logic/source-map-symbolication.ts @@ -67,8 +67,9 @@ */ import { + finishRawFrameTableBuilder, shallowCloneFuncTable, - shallowCloneRawFrameTable, + getRawFrameTableBuilderWithExistingContents, shallowCloneSourceLocationTable, } from './data-structures'; import { StringTable } from '../utils/string-table'; @@ -958,7 +959,7 @@ export function applySourceMapSymbolicationResponse( shared; const newFuncTable = shallowCloneFuncTable(funcTable); - const newFrameTable = shallowCloneRawFrameTable(frameTable); + const newFrameTable = getRawFrameTableBuilderWithExistingContents(frameTable); const newSourceLocationTable = shallowCloneSourceLocationTable(sourceLocationTable); const newSources = _shallowCloneSourceTable(sources); @@ -1042,7 +1043,7 @@ export function applySourceMapSymbolicationResponse( return { newFuncTable, - newFrameTable, + newFrameTable: finishRawFrameTableBuilder(newFrameTable), newSourceLocationTable, newSources, newStringArray, diff --git a/src/profile-logic/symbolication.ts b/src/profile-logic/symbolication.ts index b77c921d17..a501c112b2 100644 --- a/src/profile-logic/symbolication.ts +++ b/src/profile-logic/symbolication.ts @@ -3,10 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { getRawStackTableBuilder, + finishRawFrameTableBuilder, finishRawStackTableBuilder, shallowCloneFuncTable, shallowCloneNativeSymbolTable, - shallowCloneRawFrameTable, + getRawFrameTableBuilderWithExistingContents, } from './data-structures'; import { SymbolsNotFoundError } from './errors'; @@ -716,7 +717,7 @@ function _partiallyApplySymbolicationStep( // Integrate the new native symbol column into the frame table and make a // copy so that we can add new frames below. - const frameTable = shallowCloneRawFrameTable({ + const frameTable = getRawFrameTableBuilderWithExistingContents({ ...oldFrameTable, nativeSymbol: newFrameTableNativeSymbolsColumn, }); @@ -892,7 +893,7 @@ function _partiallyApplySymbolicationStep( const newShared = { ...shared, - frameTable, + frameTable: finishRawFrameTableBuilder(frameTable), funcTable, nativeSymbols, }; diff --git a/src/test/fixtures/profiles/processed-profile.ts b/src/test/fixtures/profiles/processed-profile.ts index 4a5a889acf..40127d618b 100644 --- a/src/test/fixtures/profiles/processed-profile.ts +++ b/src/test/fixtures/profiles/processed-profile.ts @@ -9,7 +9,9 @@ import { getEmptyRawUnbalancedNativeAllocationsTable, getEmptyRawBalancedNativeAllocationsTable, getRawStackTableBuilderWithExistingContents, + finishRawFrameTableBuilder, finishRawStackTableBuilder, + getRawFrameTableBuilderWithExistingContents, } from '../../../profile-logic/data-structures'; import { mergeProfilesForDiffing } from '../../../profile-logic/merge-compare'; import { computeReferenceCPUDeltaPerMs } from '../../../profile-logic/cpu'; @@ -2043,30 +2045,38 @@ export function addInnerWindowIdToStacks( const stackTableBuilder = getRawStackTableBuilderWithExistingContents(stackTable); + const frameTableBuilder = + getRawFrameTableBuilderWithExistingContents(frameTable); for (const callNode of callNodesToDupe) { const stackIndex = getStackIndexForCallNodePath(shared, callNode); const foundFrameIndex = stackTable.frame[stackIndex]; // The found one comes from the first tab. - frameTable.innerWindowID[foundFrameIndex] = + frameTableBuilder.innerWindowID[foundFrameIndex] = listOfOperations[0].innerWindowID; // Clone this frame - const newFrameIndex = frameTable.length++; - frameTable.address.push(frameTable.address[foundFrameIndex]); - frameTable.inlineDepth.push(frameTable.inlineDepth[foundFrameIndex]); - frameTable.category.push(frameTable.category[foundFrameIndex]); - frameTable.subcategory.push(frameTable.subcategory[foundFrameIndex]); - frameTable.func.push(frameTable.func[foundFrameIndex]); - frameTable.nativeSymbol.push(frameTable.nativeSymbol[foundFrameIndex]); - frameTable.line.push(frameTable.line[foundFrameIndex]); - frameTable.column.push(frameTable.column[foundFrameIndex]); - frameTable.originalLocation.push( + const newFrameIndex = frameTableBuilder.length++; + frameTableBuilder.address.push(frameTable.address[foundFrameIndex]); + frameTableBuilder.inlineDepth.push( + frameTable.inlineDepth[foundFrameIndex] + ); + frameTableBuilder.category.push(frameTable.category[foundFrameIndex]); + frameTableBuilder.subcategory.push( + frameTable.subcategory[foundFrameIndex] + ); + frameTableBuilder.func.push(frameTable.func[foundFrameIndex]); + frameTableBuilder.nativeSymbol.push( + frameTable.nativeSymbol[foundFrameIndex] + ); + frameTableBuilder.line.push(frameTable.line[foundFrameIndex]); + frameTableBuilder.column.push(frameTable.column[foundFrameIndex]); + frameTableBuilder.originalLocation.push( frameTable.originalLocation[foundFrameIndex] ); // And that one comes from the second tab. - frameTable.innerWindowID.push(listOfOperations[1].innerWindowID); + frameTableBuilder.innerWindowID.push(listOfOperations[1].innerWindowID); // Clone the stack const newStackIndex = stackTableBuilder.length++; @@ -2079,6 +2089,7 @@ export function addInnerWindowIdToStacks( } shared.stackTable = finishRawStackTableBuilder(stackTableBuilder); + shared.frameTable = finishRawFrameTableBuilder(frameTableBuilder); const sampleTimes = ensureExists(samples.time); for (let sampleIndex = samples.length; sampleIndex >= 0; sampleIndex--) { From ae98d67daa5655da3db5f703b4ccefc1e9a79648 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 6 Jul 2026 13:48:09 -0400 Subject: [PATCH 6/7] Introduce builder types for the raw samples table and the allocation tables. Same idea as with the other builders. --- src/profile-logic/data-structures.ts | 125 ++++++++++++++++-- src/profile-logic/import/chrome.ts | 16 ++- src/profile-logic/import/dhat.ts | 17 ++- src/profile-logic/import/flame-graph.ts | 5 +- src/profile-logic/import/simpleperf.ts | 9 +- src/profile-logic/js-tracer.ts | 10 +- src/profile-logic/merge-compare.ts | 16 ++- src/profile-logic/process-profile.ts | 28 ++-- .../fixtures/profiles/processed-profile.ts | 29 +++- src/test/store/profile-view.test.ts | 11 +- src/test/store/useful-tabs.test.ts | 9 +- 11 files changed, 214 insertions(+), 61 deletions(-) diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index 9b4ee13869..194f609c0e 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -13,11 +13,11 @@ import type { RawSamplesTable, RawFrameTable, RawStackTable, - FuncTable, - RawMarkerTable, RawJsAllocationsTable, RawUnbalancedNativeAllocationsTable, RawBalancedNativeAllocationsTable, + FuncTable, + RawMarkerTable, ResourceTable, NativeSymbolTable, Profile, @@ -36,6 +36,10 @@ import type { IndexIntoSourceLocationTable, InnerWindowID, Address, + Bytes, + Milliseconds, + Tid, + WeightType, } from 'firefox-profiler/types'; /** @@ -47,6 +51,52 @@ import type { * profile files) may be using typed arrays for some of the columns, * and you can't push to a typed array. */ +export type RawSamplesTableBuilder = { + responsiveness?: Array; + eventDelay?: Array; + stack: Array; + time?: Milliseconds[]; + timeDeltas?: Milliseconds[]; + argumentValues?: Array; + weight: null | number[]; + weightType: WeightType; + threadCPUDelta?: Array; + threadId?: Tid[]; + length: number; +}; + +export type RawJsAllocationsTableBuilder = { + time: Milliseconds[]; + className: string[]; + typeName: string[]; + coarseType: string[]; + weight: Bytes[]; + weightType: 'bytes'; + inNursery: boolean[]; + stack: Array; + length: number; +}; + +export type RawUnbalancedNativeAllocationsTableBuilder = { + time: Milliseconds[]; + weight: Bytes[]; + weightType: 'bytes'; + stack: Array; + argumentValues?: Array; + length: number; +}; + +export type RawBalancedNativeAllocationsTableBuilder = { + time: Milliseconds[]; + weight: Bytes[]; + weightType: 'bytes'; + stack: Array; + argumentValues?: Array; + memoryAddress: number[]; + threadId: number[]; + length: number; +}; + export type RawFrameTableBuilder = { address: Array
; inlineDepth: number[]; @@ -71,7 +121,7 @@ export type RawStackTableBuilder = { * This module collects all of the creation of new empty profile data structures. */ -export function getEmptySamplesTable(): RawSamplesTable { +export function getRawSamplesTableBuilder(): RawSamplesTableBuilder { return { // Important! // If modifying this structure, please update all callers of this function to ensure @@ -97,6 +147,45 @@ export function getRawStackTableBuilder(): RawStackTableBuilder { }; } +/** + * Return a `RawSamplesTableBuilder` view of an existing samples table. If the + * table's time / timeDeltas columns are already plain arrays, they are aliased + * through so that in-place mutations on the builder are visible via the + * returned reference. If they are typed arrays (only produced by the JSLB + * loader), they are copied into plain arrays. + * + * The returned builder shares object identity with `existing` in the common + * (plain-array) case, so callers do not need to reassign it back onto the + * thread. In the typed-array case a new object is returned; callers should + * reassign it (`thread.samples = builder`) to preserve mutations. + */ +export function getRawSamplesTableBuilderFromExisting( + existing: RawSamplesTable +): RawSamplesTableBuilder { + const time = existing.time; + const timeDeltas = existing.timeDeltas; + if ( + (time === undefined || Array.isArray(time)) && + (timeDeltas === undefined || Array.isArray(timeDeltas)) + ) { + return existing as RawSamplesTableBuilder; + } + return { + ...existing, + time: time === undefined || Array.isArray(time) ? time : Array.from(time), + timeDeltas: + timeDeltas === undefined || Array.isArray(timeDeltas) + ? timeDeltas + : Array.from(timeDeltas), + }; +} + +export function finishRawSamplesTableBuilder( + builder: RawSamplesTableBuilder +): RawSamplesTable { + return builder; +} + export function getRawStackTableBuilderWithExistingContents( existing: RawStackTable ): RawStackTableBuilder { @@ -133,7 +222,7 @@ export function finishRawStackTableBuilder( * eventDelay is a new field and it replaced responsiveness. We should still * account for older profiles and use both of the flavors if needed. */ -export function getEmptySamplesTableWithEventDelay(): RawSamplesTable { +export function getRawSamplesTableBuilderWithEventDelay(): RawSamplesTableBuilder { return { // Important! // If modifying this structure, please update all callers of this function to ensure @@ -312,7 +401,7 @@ export function getEmptyRawMarkerTable(): RawMarkerTable { }; } -export function getEmptyRawJsAllocationsTable(): RawJsAllocationsTable { +export function getEmptyRawJsAllocationsTable(): RawJsAllocationsTableBuilder { // Important! // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not @@ -334,7 +423,7 @@ export function getEmptyRawJsAllocationsTable(): RawJsAllocationsTable { * The native allocation tables come in two varieties. Get one of the members of the * union. */ -export function getEmptyRawUnbalancedNativeAllocationsTable(): RawUnbalancedNativeAllocationsTable { +export function getEmptyRawUnbalancedNativeAllocationsTable(): RawUnbalancedNativeAllocationsTableBuilder { // Important! // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not @@ -352,7 +441,7 @@ export function getEmptyRawUnbalancedNativeAllocationsTable(): RawUnbalancedNati * The native allocation tables come in two varieties. Get one of the members of the * union. */ -export function getEmptyRawBalancedNativeAllocationsTable(): RawBalancedNativeAllocationsTable { +export function getEmptyRawBalancedNativeAllocationsTable(): RawBalancedNativeAllocationsTableBuilder { // Important! // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not @@ -368,6 +457,24 @@ export function getEmptyRawBalancedNativeAllocationsTable(): RawBalancedNativeAl }; } +export function finishRawJsAllocationsTableBuilder( + builder: RawJsAllocationsTableBuilder +): RawJsAllocationsTable { + return builder; +} + +export function finishRawUnbalancedNativeAllocationsTableBuilder( + builder: RawUnbalancedNativeAllocationsTableBuilder +): RawUnbalancedNativeAllocationsTable { + return builder; +} + +export function finishRawBalancedNativeAllocationsTableBuilder( + builder: RawBalancedNativeAllocationsTableBuilder +): RawBalancedNativeAllocationsTable { + return builder; +} + export function shallowCloneRawMarkerTable( markerTable: RawMarkerTable ): RawMarkerTable { @@ -458,7 +565,9 @@ export function getEmptyThread(overrides?: Partial): RawThread { pid: '0', tid: 0, // Creating samples with event delay since it's the new samples table. - samples: getEmptySamplesTableWithEventDelay(), + samples: finishRawSamplesTableBuilder( + getRawSamplesTableBuilderWithEventDelay() + ), markers: getEmptyRawMarkerTable(), }; diff --git a/src/profile-logic/import/chrome.ts b/src/profile-logic/import/chrome.ts index fefc76f986..e670e3afd8 100644 --- a/src/profile-logic/import/chrome.ts +++ b/src/profile-logic/import/chrome.ts @@ -10,8 +10,11 @@ import type { } from 'firefox-profiler/types'; import { + finishRawSamplesTableBuilder, getEmptyProfile, getEmptyThread, + getRawSamplesTableBuilderWithEventDelay, + type RawSamplesTableBuilder, type RawStackTableBuilder, } from '../data-structures'; import type { StringTable } from '../../utils/string-table'; @@ -270,6 +273,7 @@ export function attemptToConvertChromeProfile( type ThreadInfo = { thread: RawThread; + samples: RawSamplesTableBuilder; nodeIdToStackId: Map; lastSeenTime: number; lastSampledTime: number; @@ -316,7 +320,8 @@ function getThreadInfo( if (cachedThreadInfo) { return cachedThreadInfo; } - const thread = getEmptyThread(); + const samples = getRawSamplesTableBuilderWithEventDelay(); + const thread = getEmptyThread({ samples }); thread.pid = `${chunk.pid}`; // It looks like the TID information in Chrome's data isn't the system's TID // but some internal values only unique for a pid. Therefore let's generate a @@ -401,6 +406,7 @@ function getThreadInfo( const threadInfo: ThreadInfo = { thread, + samples, nodeIdToStackId, lastSeenTime: chunk.ts / 1000, lastSampledTime: 0, @@ -531,7 +537,7 @@ async function processTracingEvents( profile, profileEvent ); - const { thread, nodeIdToStackId } = threadInfo; + const { samples: samplesTable, nodeIdToStackId } = threadInfo; let profileChunks: any[] = []; if (profileEvent.name === 'Profile') { @@ -565,8 +571,6 @@ async function processTracingEvents( continue; } - const { samples: samplesTable } = thread; - if (nodes) { const parentMap = new Map(); for (const node of nodes) { @@ -785,6 +789,10 @@ async function processTracingEvents( const { shared } = globalDataCollector.finish(); profile.shared = shared; + for (const [thread, threadInfo] of threadInfoByThread) { + thread.samples = finishRawSamplesTableBuilder(threadInfo.samples); + } + return profile; } diff --git a/src/profile-logic/import/dhat.ts b/src/profile-logic/import/dhat.ts index c04e25d808..e0dd3f240c 100644 --- a/src/profile-logic/import/dhat.ts +++ b/src/profile-logic/import/dhat.ts @@ -10,6 +10,7 @@ import type { } from 'firefox-profiler/types'; import { + finishRawUnbalancedNativeAllocationsTableBuilder, getEmptyProfile, getEmptyThread, getEmptyRawUnbalancedNativeAllocationsTable, @@ -372,13 +373,15 @@ export function attemptToConvertDhat(json: unknown): Profile | null { thread.tid = i; thread.name = name; - thread.nativeAllocations = { - time: allocationsTable.time.slice(), - stack: allocationsTable.stack.slice(), - weight, - weightType: 'bytes', - length: allocationsTable.length, - }; + thread.nativeAllocations = finishRawUnbalancedNativeAllocationsTableBuilder( + { + time: allocationsTable.time.slice(), + stack: allocationsTable.stack.slice(), + weight, + weightType: 'bytes', + length: allocationsTable.length, + } + ); return thread; }); diff --git a/src/profile-logic/import/flame-graph.ts b/src/profile-logic/import/flame-graph.ts index b468d4d008..6a7e4a1fff 100644 --- a/src/profile-logic/import/flame-graph.ts +++ b/src/profile-logic/import/flame-graph.ts @@ -11,8 +11,10 @@ import type { Profile, } from 'firefox-profiler/types/profile'; import { + finishRawSamplesTableBuilder, getEmptyProfile, getEmptyThread, + getRawSamplesTableBuilderWithEventDelay, } from 'firefox-profiler/profile-logic/data-structures'; import { GlobalDataCollector } from 'firefox-profiler/profile-logic/global-data-collector'; import { ensureExists } from 'firefox-profiler/utils/types'; @@ -61,7 +63,7 @@ export function convertFlameGraphProfile(profileText: string): Profile { const frameTable = globalDataCollector.getFrameTable(); const stackTable = globalDataCollector.getStackTableBuilder(); - const { samples } = thread; + const samples = getRawSamplesTableBuilderWithEventDelay(); // Maps to deduplicate stacks, frames, and functions. const stackMap = new Map(); @@ -170,6 +172,7 @@ export function convertFlameGraphProfile(profileText: string): Profile { } // Finalize the profile. + thread.samples = finishRawSamplesTableBuilder(samples); profile.threads.push(thread); const { shared } = globalDataCollector.finish(); profile.shared = shared; diff --git a/src/profile-logic/import/simpleperf.ts b/src/profile-logic/import/simpleperf.ts index 4110ed10f2..75fce1f0d9 100644 --- a/src/profile-logic/import/simpleperf.ts +++ b/src/profile-logic/import/simpleperf.ts @@ -14,7 +14,6 @@ import type { IndexIntoStackTable, ProfileMeta, ResourceTable, - RawSamplesTable, Profile, RawProfileSharedData, RawThread, @@ -26,10 +25,12 @@ import { getRawFrameTableBuilder, getRawStackTableBuilder, finishRawFrameTableBuilder, + finishRawSamplesTableBuilder, finishRawStackTableBuilder, type RawFrameTableBuilder, type RawStackTableBuilder, - getEmptySamplesTable, + getRawSamplesTableBuilder, + type RawSamplesTableBuilder, getEmptyRawMarkerTable, getEmptyNativeSymbolTable, getEmptySourceTable, @@ -254,7 +255,7 @@ class FirefoxThread { strings: StringTable; - sampleTable: RawSamplesTable = getEmptySamplesTable(); + sampleTable: RawSamplesTableBuilder = getRawSamplesTableBuilder(); stackTable: FirefoxSampleTable; frameTable: FirefoxFrameTable; @@ -289,7 +290,7 @@ class FirefoxThread { isMainThread: this.isMainThread, pid: this.pid.toString(), tid: this.tid, - samples: this.sampleTable, + samples: finishRawSamplesTableBuilder(this.sampleTable), markers: getEmptyRawMarkerTable(), }; } diff --git a/src/profile-logic/js-tracer.ts b/src/profile-logic/js-tracer.ts index 564a54802a..fd9c5bbeb1 100644 --- a/src/profile-logic/js-tracer.ts +++ b/src/profile-logic/js-tracer.ts @@ -2,9 +2,10 @@ * 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 { - getEmptySamplesTableWithEventDelay, + getRawSamplesTableBuilderWithEventDelay, getEmptyRawMarkerTable, finishRawFrameTableBuilder, + finishRawSamplesTableBuilder, finishRawStackTableBuilder, getRawStackTableBuilderWithExistingContents, getRawFrameTableBuilderWithExistingContents, @@ -504,7 +505,7 @@ export function convertJsTracerToThreadWithoutSamples( stackMap: Map; } { const samples: RawSamplesTable = { - ...getEmptySamplesTableWithEventDelay(), + ...getRawSamplesTableBuilderWithEventDelay(), weight: [], weightType: 'tracing-ms', }; @@ -633,6 +634,7 @@ export function convertJsTracerToThreadWithoutSamples( // Write the augmented stackTable and frameTable back to the shared data. shared.stackTable = finishRawStackTableBuilder(stackTable); shared.frameTable = finishRawFrameTableBuilder(frameTable); + thread.samples = finishRawSamplesTableBuilder(samples); return { thread, stackMap }; } @@ -804,7 +806,7 @@ export function getSelfTimeSamplesFromJsTracer( const isNearlyEqual = (a: number, b: number) => Math.abs(a - b) < epsilon; // Each event type will have it's own timing information, later collapse these into // a single array. - const samples = getEmptySamplesTableWithEventDelay(); + const samples = getRawSamplesTableBuilderWithEventDelay(); const sampleWeights: number[] = []; samples.weight = sampleWeights; @@ -1009,5 +1011,5 @@ export function getSelfTimeSamplesFromJsTracer( ); } - return samples; + return finishRawSamplesTableBuilder(samples); } diff --git a/src/profile-logic/merge-compare.ts b/src/profile-logic/merge-compare.ts index f18fdc1ac5..d4173e7179 100644 --- a/src/profile-logic/merge-compare.ts +++ b/src/profile-logic/merge-compare.ts @@ -12,12 +12,13 @@ import { getEmptyResourceTable, getEmptyNativeSymbolTable, finishRawFrameTableBuilder, + finishRawSamplesTableBuilder, getRawFrameTableBuilder, getEmptyFuncTable, getRawStackTableBuilder, finishRawStackTableBuilder, getEmptyRawMarkerTable, - getEmptySamplesTableWithEventDelay, + getRawSamplesTableBuilderWithEventDelay, shallowCloneRawMarkerTable, getEmptySourceTable, } from './data-structures'; @@ -1188,7 +1189,7 @@ function combineSamplesDiffing( const newWeight: number[] = []; const newThreadId: Tid[] = []; const newSamples = { - ...getEmptySamplesTableWithEventDelay(), + ...getRawSamplesTableBuilderWithEventDelay(), weight: newWeight, threadId: newThreadId, }; @@ -1241,7 +1242,7 @@ function combineSamplesDiffing( } } - return newSamples; + return finishRawSamplesTableBuilder(newSamples); } type ThreadAndWeightMultiplier = { @@ -1368,7 +1369,7 @@ function combineSamplesForMerging(threads: RawThread[]): RawSamplesTable { const newThreadId: Tid[] = []; // Creating a new empty samples table to fill. const newSamples = { - ...getEmptySamplesTableWithEventDelay(), + ...getRawSamplesTableBuilderWithEventDelay(), threadId: newThreadId, }; @@ -1454,9 +1455,12 @@ function combineSamplesForMerging(threads: RawThread[]): RawSamplesTable { } if (newThreadCPUDelta !== undefined) { - return { ...newSamples, threadCPUDelta: newThreadCPUDelta }; + return finishRawSamplesTableBuilder({ + ...newSamples, + threadCPUDelta: newThreadCPUDelta, + }); } - return newSamples; + return finishRawSamplesTableBuilder(newSamples); } /** diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index 3e45a9a68b..407cd9a305 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -12,6 +12,9 @@ import { attemptToConvertDhat } from './import/dhat'; import { GlobalDataCollector } from './global-data-collector'; import { AddressLocator } from './address-locator'; import { + finishRawBalancedNativeAllocationsTableBuilder, + finishRawJsAllocationsTableBuilder, + finishRawUnbalancedNativeAllocationsTableBuilder, getEmptyExtensions, getEmptyRawMarkerTable, getEmptyRawJsAllocationsTable, @@ -756,29 +759,24 @@ function _processMarkers( nativeAllocations = null; } else if (hasMemoryAddresses) { // This is the newer native allocations with memory addresses. - nativeAllocations = { - time: inProgressNativeAllocations.time, - weight: inProgressNativeAllocations.weight, - weightType: inProgressNativeAllocations.weightType, - stack: inProgressNativeAllocations.stack, + nativeAllocations = finishRawBalancedNativeAllocationsTableBuilder({ + ...inProgressNativeAllocations, memoryAddress, threadId, - length: inProgressNativeAllocations.length, - }; + }); } else { // There is the older native allocations, without memory addresses. - nativeAllocations = { - time: inProgressNativeAllocations.time, - weight: inProgressNativeAllocations.weight, - weightType: inProgressNativeAllocations.weightType, - stack: inProgressNativeAllocations.stack, - length: inProgressNativeAllocations.length, - }; + nativeAllocations = finishRawUnbalancedNativeAllocationsTableBuilder( + inProgressNativeAllocations + ); } return { markers: markers, - jsAllocations: jsAllocations.length === 0 ? null : jsAllocations, + jsAllocations: + jsAllocations.length === 0 + ? null + : finishRawJsAllocationsTableBuilder(jsAllocations), nativeAllocations, }; } diff --git a/src/test/fixtures/profiles/processed-profile.ts b/src/test/fixtures/profiles/processed-profile.ts index 40127d618b..6f2d53cc0f 100644 --- a/src/test/fixtures/profiles/processed-profile.ts +++ b/src/test/fixtures/profiles/processed-profile.ts @@ -9,9 +9,15 @@ import { getEmptyRawUnbalancedNativeAllocationsTable, getEmptyRawBalancedNativeAllocationsTable, getRawStackTableBuilderWithExistingContents, + finishRawBalancedNativeAllocationsTableBuilder, finishRawFrameTableBuilder, + finishRawJsAllocationsTableBuilder, + finishRawSamplesTableBuilder, + finishRawUnbalancedNativeAllocationsTableBuilder, + getRawSamplesTableBuilderFromExisting, finishRawStackTableBuilder, getRawFrameTableBuilderWithExistingContents, + getRawSamplesTableBuilderWithEventDelay, } from '../../../profile-logic/data-structures'; import { mergeProfilesForDiffing } from '../../../profile-logic/merge-compare'; import { computeReferenceCPUDeltaPerMs } from '../../../profile-logic/cpu'; @@ -211,7 +217,7 @@ export function addMarkersToThreadWithCorrespondingSamples( // control the initial range. Because of that we need to add samples so that // the range includes these markers. Note that when a thread has no sample, // then the markers are used to compute the initial range. - const { samples } = thread; + const samples = getRawSamplesTableBuilderFromExisting(thread.samples); if (samples.length) { const firstMarkerTime = Math.min(...allTimes); const lastMarkerTime = Math.max(...allTimes); @@ -257,6 +263,7 @@ export function addMarkersToThreadWithCorrespondingSamples( samples.length++; } } + thread.samples = finishRawSamplesTableBuilder(samples); } export function getThreadWithMarkers( @@ -878,7 +885,7 @@ function _buildThreadFromTextOnlyStacks( ): RawThread { const thread = getEmptyThread(); - const { samples } = thread; + const samples = getRawSamplesTableBuilderWithEventDelay(); const stringTable = globalDataCollector.getStringTable(); const frameTable = globalDataCollector.getFrameTable(); @@ -1020,6 +1027,8 @@ function _buildThreadFromTextOnlyStacks( samples.time = sampleTimes; } + thread.samples = finishRawSamplesTableBuilder(samples); + return thread; } @@ -1632,7 +1641,6 @@ export function getProfileWithJsAllocations() { // Now add a RawJsAllocationsTable. const jsAllocations = getEmptyRawJsAllocationsTable(); - profile.threads[0].jsAllocations = jsAllocations; // The stack table is built sequentially, so we can assume that the stack indexes // match the func indexes. @@ -1659,6 +1667,9 @@ export function getProfileWithJsAllocations() { jsAllocations.length++; } + profile.threads[0].jsAllocations = + finishRawJsAllocationsTableBuilder(jsAllocations); + return { profile, funcNamesDict, funcNames }; } @@ -1712,7 +1723,6 @@ export function getProfileWithUnbalancedNativeAllocations() { // Now add a RawNativeAllocationsTable. const nativeAllocations = getEmptyRawUnbalancedNativeAllocationsTable(); - profile.threads[0].nativeAllocations = nativeAllocations; // The stack table is built sequentially, so we can assume that the stack indexes // match the func indexes. @@ -1740,6 +1750,9 @@ export function getProfileWithUnbalancedNativeAllocations() { nativeAllocations.length++; } + profile.threads[0].nativeAllocations = + finishRawUnbalancedNativeAllocationsTableBuilder(nativeAllocations); + return { profile, funcNamesDict }; } @@ -1781,7 +1794,6 @@ export function getProfileWithBalancedNativeAllocations() { // Now add a RawNativeAllocationsTable. const nativeAllocations = getEmptyRawBalancedNativeAllocationsTable(); const [thread] = profile.threads; - thread.nativeAllocations = nativeAllocations; const threadId = thread.tid; if (typeof threadId !== 'number') { throw new Error( @@ -1827,6 +1839,9 @@ export function getProfileWithBalancedNativeAllocations() { nativeAllocations.length++; } + thread.nativeAllocations = + finishRawBalancedNativeAllocationsTableBuilder(nativeAllocations); + return { profile, funcNamesDict }; } @@ -2015,7 +2030,7 @@ export function addInnerWindowIdToStacks( callNodesToDupe?: CallNodePath[] ) { const { stackTable, frameTable } = shared; - const { samples } = thread; + const samples = getRawSamplesTableBuilderFromExisting(thread.samples); const usedInnerWindowIDsSet = new Set(); for (const { innerWindowID, callNodes } of listOfOperations) { @@ -2123,6 +2138,8 @@ export function addInnerWindowIdToStacks( } } + thread.samples = finishRawSamplesTableBuilder(samples); + if (usedInnerWindowIDsSet.size !== 0) { thread.usedInnerWindowIDs = Array.from(usedInnerWindowIDsSet); } diff --git a/src/test/store/profile-view.test.ts b/src/test/store/profile-view.test.ts index 9fda33ec65..b89362fdd6 100644 --- a/src/test/store/profile-view.test.ts +++ b/src/test/store/profile-view.test.ts @@ -19,9 +19,10 @@ import { getThreadWithMarkers, } from '../fixtures/profiles/processed-profile'; import { + finishRawSamplesTableBuilder, getEmptyThread, getEmptyProfile, - getEmptySamplesTableWithEventDelay, + getRawSamplesTableBuilderWithEventDelay, } from '../../profile-logic/data-structures'; import { withAnalyticsMock } from '../fixtures/mocks/analytics'; import { getProfileWithNiceTracks } from '../fixtures/profiles/tracks'; @@ -53,11 +54,11 @@ import { checkBit } from '../../utils/bitset'; import type { TrackReference, Milliseconds, - RawThread, StartEndRange, Marker, MixedObject, CallNodePath, + RawThread, } from 'firefox-profiler/types'; describe('call node paths on implementation filter change', function () { @@ -3824,7 +3825,7 @@ describe('getProcessedEventDelays', function () { const profile = getEmptyProfile(); // Create event delay values. - const samples = getEmptySamplesTableWithEventDelay(); + const samples = getRawSamplesTableBuilderWithEventDelay(); if (eventDelay) { samples.eventDelay = eventDelay; } else { @@ -3842,7 +3843,9 @@ describe('getProcessedEventDelays', function () { .fill(0) .map((_, i) => i); samples.stack = Array(samples.length).fill(null); - profile.threads.push(getEmptyThread({ samples })); + profile.threads.push( + getEmptyThread({ samples: finishRawSamplesTableBuilder(samples) }) + ); const { dispatch, getState } = storeWithProfile(profile); diff --git a/src/test/store/useful-tabs.test.ts b/src/test/store/useful-tabs.test.ts index 2e770a2581..0d63df2759 100644 --- a/src/test/store/useful-tabs.test.ts +++ b/src/test/store/useful-tabs.test.ts @@ -13,7 +13,10 @@ import { getMergedProfileFromTextSamples, getProfileWithUnbalancedNativeAllocations, } from '../fixtures/profiles/processed-profile'; -import { getEmptySamplesTableWithEventDelay } from '../../profile-logic/data-structures'; +import { + finishRawSamplesTableBuilder, + getRawSamplesTableBuilderWithEventDelay, +} from '../../profile-logic/data-structures'; describe('getUsefulTabs', function () { it('hides the network chart and JS tracer when no data is in the thread', function () { @@ -79,7 +82,9 @@ describe('getUsefulTabs', function () { it('shows sample related tabs even when there are only allocation samples in the profile', function () { const { profile } = getProfileWithUnbalancedNativeAllocations(); for (const thread of profile.threads) { - thread.samples = getEmptySamplesTableWithEventDelay(); + thread.samples = finishRawSamplesTableBuilder( + getRawSamplesTableBuilderWithEventDelay() + ); } const { getState } = storeWithProfile(profile); expect(selectedThreadSelectors.getUsefulTabs(getState())).toEqual([ From 3b38bd60c9793f291c9361e288db3da27d2f2d9e Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 6 Jul 2026 13:59:09 -0400 Subject: [PATCH 7/7] Allow various columns to be typed arrays in the raw table / profile format. --- docs-developer/CHANGELOG-formats.md | 12 + src/app-logic/constants.ts | 2 +- src/profile-logic/cpu.ts | 2 +- src/profile-logic/data-structures.ts | 38 +- src/profile-logic/js-tracer.ts | 3 +- src/profile-logic/process-profile.ts | 9 +- .../processed-profile-versioning.ts | 12 + src/profile-logic/profile-compacting.ts | 7 +- src/profile-logic/profile-data.ts | 21 +- .../fixtures/profiles/processed-profile.ts | 2 +- .../__snapshots__/profiler-edit.test.ts.snap | 8 +- .../__snapshots__/profile-view.test.ts.snap | 16 +- src/test/store/receive-profile.test.ts | 1 - .../profile-conversion.test.ts.snap | 1264 +++++++++-------- .../profile-upgrading.test.ts.snap | 14 +- src/test/unit/process-profile.test.ts | 4 +- src/types/profile.ts | 20 +- 17 files changed, 791 insertions(+), 644 deletions(-) diff --git a/docs-developer/CHANGELOG-formats.md b/docs-developer/CHANGELOG-formats.md index 7a3c44138a..5210dc1932 100644 --- a/docs-developer/CHANGELOG-formats.md +++ b/docs-developer/CHANGELOG-formats.md @@ -6,6 +6,18 @@ Note that this is not an exhaustive list. Processed profile format upgraders can ## Processed profile format +### Version 67 + +The following raw columns can now optionally be stored as typed arrays, for profiles loaded from [JsonSlabs](https://github.com/mstange/json-slabs/) files (.jslb, .jslb.gz). Regular JS / JSON arrays are still accepted. + +- `thread.samples.time` and `thread.samples.timeDeltas` (`Float64Array`) +- `counter.samples.time` and `counter.samples.timeDeltas` (`Float64Array`) +- `thread.jsAllocations.time` (`Float64Array`) +- `thread.nativeAllocations.time` (`Float64Array`) +- `profile.shared.frameTable.func` (`Int32Array`) +- `profile.shared.frameTable.address` (`Int32Array`, with `-1` as the sentinel for missing addresses) +- `profile.shared.frameTable.inlineDepth` (`Uint8Array`) + ### Version 66 The `prefix` column of `profile.shared.stackTable` was replaced with a `prefixOffset` column, to improve compressibility. diff --git a/src/app-logic/constants.ts b/src/app-logic/constants.ts index 2979163779..ebd546bdca 100644 --- a/src/app-logic/constants.ts +++ b/src/app-logic/constants.ts @@ -12,7 +12,7 @@ export const GECKO_PROFILE_VERSION = 34; // The current version of the "processed" profile format. // Please don't forget to update the processed profile format changelog in // `docs-developer/CHANGELOG-formats.md`. -export const PROCESSED_PROFILE_VERSION = 66; +export const PROCESSED_PROFILE_VERSION = 67; // The following are the margin sizes for the left and right of the timeline. Independent // components need to share these values. diff --git a/src/profile-logic/cpu.ts b/src/profile-logic/cpu.ts index 4ed904bc5c..4048a0d0de 100644 --- a/src/profile-logic/cpu.ts +++ b/src/profile-logic/cpu.ts @@ -103,7 +103,7 @@ export function computeReferenceCPUDeltaPerMs(profile: Profile): number { */ export function computeThreadCPUPercent( threadCPUDelta: Array, - timeDeltas: number[], + timeDeltas: number[] | Float64Array, referenceCPUDeltaPerMs: number ): Uint8Array { const threadCPUPercent: Uint8Array = new Uint8Array( diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index 194f609c0e..b5f287a8b5 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -183,7 +183,15 @@ export function getRawSamplesTableBuilderFromExisting( export function finishRawSamplesTableBuilder( builder: RawSamplesTableBuilder ): RawSamplesTable { - return builder; + return { + ...builder, + time: + builder.time === undefined ? undefined : new Float64Array(builder.time), + timeDeltas: + builder.timeDeltas === undefined + ? undefined + : new Float64Array(builder.timeDeltas), + }; } export function getRawStackTableBuilderWithExistingContents( @@ -265,11 +273,11 @@ export function getRawFrameTableBuilderWithExistingContents( // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not // be caught by the type system. - address: frameTable.address.slice(), - inlineDepth: frameTable.inlineDepth.slice(), + address: Array.from(frameTable.address), + inlineDepth: Array.from(frameTable.inlineDepth), category: frameTable.category.slice(), subcategory: frameTable.subcategory.slice(), - func: frameTable.func.slice(), + func: Array.from(frameTable.func), nativeSymbol: frameTable.nativeSymbol.slice(), innerWindowID: frameTable.innerWindowID.slice(), line: frameTable.line.slice(), @@ -282,7 +290,12 @@ export function getRawFrameTableBuilderWithExistingContents( export function finishRawFrameTableBuilder( builder: RawFrameTableBuilder ): RawFrameTable { - return builder; + return { + ...builder, + address: new Int32Array(builder.address), + inlineDepth: new Uint8Array(builder.inlineDepth), + func: new Int32Array(builder.func), + }; } export function getEmptyFuncTable(): FuncTable { @@ -460,19 +473,28 @@ export function getEmptyRawBalancedNativeAllocationsTable(): RawBalancedNativeAl export function finishRawJsAllocationsTableBuilder( builder: RawJsAllocationsTableBuilder ): RawJsAllocationsTable { - return builder; + return { + ...builder, + time: new Float64Array(builder.time), + }; } export function finishRawUnbalancedNativeAllocationsTableBuilder( builder: RawUnbalancedNativeAllocationsTableBuilder ): RawUnbalancedNativeAllocationsTable { - return builder; + return { + ...builder, + time: new Float64Array(builder.time), + }; } export function finishRawBalancedNativeAllocationsTableBuilder( builder: RawBalancedNativeAllocationsTableBuilder ): RawBalancedNativeAllocationsTable { - return builder; + return { + ...builder, + time: new Float64Array(builder.time), + }; } export function shallowCloneRawMarkerTable( diff --git a/src/profile-logic/js-tracer.ts b/src/profile-logic/js-tracer.ts index fd9c5bbeb1..92ef451f82 100644 --- a/src/profile-logic/js-tracer.ts +++ b/src/profile-logic/js-tracer.ts @@ -9,6 +9,7 @@ import { finishRawStackTableBuilder, getRawStackTableBuilderWithExistingContents, getRawFrameTableBuilderWithExistingContents, + type RawSamplesTableBuilder, } from './data-structures'; import { StringTable } from '../utils/string-table'; import { ensureExists } from '../utils/types'; @@ -504,7 +505,7 @@ export function convertJsTracerToThreadWithoutSamples( thread: RawThread; stackMap: Map; } { - const samples: RawSamplesTable = { + const samples: RawSamplesTableBuilder = { ...getRawSamplesTableBuilderWithEventDelay(), weight: [], weightType: 'tracing-ms', diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index 407cd9a305..3e5278f474 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -1469,10 +1469,9 @@ function _processThread( * has its own timebase, and we don't want to keep converting timestamps when * we deal with the integrated profile. */ -export function adjustTableTimestamps( - table: Table, - delta: Milliseconds -): Table { +export function adjustTableTimestamps< + Table extends { time: Milliseconds[] | Float64Array }, +>(table: Table, delta: Milliseconds): Table { return { ...table, time: table.time.map((time) => time + delta), @@ -1480,7 +1479,7 @@ export function adjustTableTimestamps
( } export function adjustTableTimeDeltas< - Table extends { timeDeltas?: Milliseconds[] }, + Table extends { timeDeltas?: Milliseconds[] | Float64Array }, >(table: Table, delta: Milliseconds): Table { const { timeDeltas } = table; if (timeDeltas === undefined) { diff --git a/src/profile-logic/processed-profile-versioning.ts b/src/profile-logic/processed-profile-versioning.ts index 059fde27e1..ad89075c47 100644 --- a/src/profile-logic/processed-profile-versioning.ts +++ b/src/profile-logic/processed-profile-versioning.ts @@ -3276,6 +3276,18 @@ const _upgraders: { stackTable.prefixOffset = prefixOffset; delete stackTable.prefix; }, + [67]: (_profile: any) => { + // Various raw columns can now optionally be stored as typed arrays: + // - `thread.samples.time` / `timeDeltas` (`Float64Array`) + // - `counter.samples.time` / `timeDeltas` (`Float64Array`) + // - `thread.jsAllocations.time` (`Float64Array`) + // - `thread.nativeAllocations.time` (`Float64Array`) + // - `profile.shared.frameTable.func` (`Int32Array`) + // - `profile.shared.frameTable.address` (`Int32Array`, `-1` sentinel) + // - `profile.shared.frameTable.inlineDepth` (`Uint8Array`) + // Regular JS / JSON arrays are still accepted. All valid v66 profiles + // are valid v67 profiles, so no upgrader is needed. + }, // If you add a new upgrader here, please document the change in // `docs-developer/CHANGELOG-formats.md`. }; diff --git a/src/profile-logic/profile-compacting.ts b/src/profile-logic/profile-compacting.ts index 9544a7a28d..9adb8404a7 100644 --- a/src/profile-logic/profile-compacting.ts +++ b/src/profile-logic/profile-compacting.ts @@ -61,7 +61,10 @@ type ColumnDescription = null extends ( | { type: 'NO_REF' }; type TableDescription = { - [K in keyof T as T[K] extends Array | Int32Array + [K in keyof T as T[K] extends + | Array + | Int32Array + | Uint8Array ? K : never]: ColumnDescription; }; @@ -164,7 +167,7 @@ export function computeCompactedProfile( inlineDepth: ColDesc.noRef(), category: ColDesc.noRef(), subcategory: ColDesc.noRef(), - func: ColDesc.indexRef(tcs.funcTable), + func: ColDesc.indexRefInt32(tcs.funcTable), nativeSymbol: ColDesc.indexRefOrNull(tcs.nativeSymbols), innerWindowID: ColDesc.noRef(), line: ColDesc.noRef(), diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 7374e55f61..8c5356d17d 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -1944,7 +1944,7 @@ export function computeTimeColumnForRawSamplesTable( ): Float64Array { const { time, timeDeltas } = samples; if (time !== undefined) { - return Float64Array.from(time); + return time instanceof Float64Array ? time : new Float64Array(time); } return numberSeriesFromDeltas(ensureExists(timeDeltas)); } @@ -2268,7 +2268,7 @@ export function filterRawThreadSamplesToRange( ); const newSamples: RawSamplesTable = { length: endSampleIndex - beginSampleIndex, - time: Array.from(sampleTimes.slice(beginSampleIndex, endSampleIndex)), + time: sampleTimes.slice(beginSampleIndex, endSampleIndex), weight: samples.weight ? samples.weight.slice(beginSampleIndex, endSampleIndex) : null, @@ -2403,7 +2403,7 @@ export function filterCounterSamplesToRange( newCounter.samples = { length: endSampleIndex - beginSampleIndex, - time: Array.from(timeColumn.slice(beginSampleIndex, endSampleIndex)), + time: timeColumn.slice(beginSampleIndex, endSampleIndex), count: samples.count.slice(beginSampleIndex, endSampleIndex), number: samples.number ? samples.number.slice(beginSampleIndex, endSampleIndex) @@ -4783,9 +4783,18 @@ export function computeTabToThreadIndexesMap( export function computeFrameTableFromRawFrameTable( rawFrameTable: RawFrameTable ): FrameTable { - const address = new Int32Array(rawFrameTable.address); - const inlineDepth = new Uint8Array(rawFrameTable.inlineDepth); - const func = new Int32Array(rawFrameTable.func); + const address = + rawFrameTable.address instanceof Int32Array + ? rawFrameTable.address + : new Int32Array(rawFrameTable.address); + const inlineDepth = + rawFrameTable.inlineDepth instanceof Uint8Array + ? rawFrameTable.inlineDepth + : new Uint8Array(rawFrameTable.inlineDepth); + const func = + rawFrameTable.func instanceof Int32Array + ? rawFrameTable.func + : new Int32Array(rawFrameTable.func); return { address, inlineDepth, diff --git a/src/test/fixtures/profiles/processed-profile.ts b/src/test/fixtures/profiles/processed-profile.ts index 6f2d53cc0f..6e117bcf97 100644 --- a/src/test/fixtures/profiles/processed-profile.ts +++ b/src/test/fixtures/profiles/processed-profile.ts @@ -1533,7 +1533,7 @@ export function getCounterForThreadWithSamples( thread: RawThread, mainThreadIndex: ThreadIndex, samples: { - time?: number[]; + time?: number[] | Float64Array; number?: number[]; count?: number[]; length: number; 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 d186387a95..ddef611540 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 @@ -87,7 +87,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -1452,7 +1452,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -2817,7 +2817,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -4182,7 +4182,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "a.out", "sampleUnits": Object { diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index ed4e431ab4..75109d8fbf 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -423,7 +423,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Firefox", "sourceURL": "", @@ -443,7 +443,7 @@ Object { ], "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -476,7 +476,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -487,7 +487,7 @@ Object { 7, 8, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -912,6 +912,7 @@ Object { 55, 56, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -1055,7 +1056,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -1265,6 +1267,7 @@ Array [ 55, 56, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -1408,7 +1411,8 @@ Array [ "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, diff --git a/src/test/store/receive-profile.test.ts b/src/test/store/receive-profile.test.ts index 5de663db0f..afc2309460 100644 --- a/src/test/store/receive-profile.test.ts +++ b/src/test/store/receive-profile.test.ts @@ -1611,7 +1611,6 @@ describe('actions/receive-profile', function () { const expectedThreads = [ expect.objectContaining({ - ...profile1.threads[0], pid: '0 from profile 1', tid: '0 from profile 1', isMainThread: true, diff --git a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap index 713bff2592..5620674b85 100644 --- a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap @@ -591,7 +591,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "ART Trace (Android)", "sampleUnits": undefined, @@ -613,7 +613,7 @@ Object { "profilingLog": Object {}, "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -8677,7 +8677,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -11365,7 +11365,7 @@ Object { 1493, 1494, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -83833,7 +83833,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "ART Trace (Android)", "sampleUnits": undefined, @@ -83855,7 +83855,7 @@ Object { "profilingLog": Object {}, "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -100097,7 +100097,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -105511,7 +105511,7 @@ Object { 2531, 2532, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -329278,7 +329278,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 119159778.026, @@ -329293,7 +329293,7 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -329377,7 +329377,7 @@ Object { 297, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -329405,7 +329405,7 @@ Object { 23, 24, ], - "inlineDepth": Array [], + "inlineDepth": Uint8Array [], "innerWindowID": Array [ 0, 0, @@ -333080,7 +333080,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -337250,7 +337251,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -337346,7 +337348,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -337388,7 +337391,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -337454,7 +337458,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -345146,7 +345151,7 @@ Object { 14, 14, ], - "time": Array [ + "time": Float64Array [ 119159290.82000001, 119159291.979, 119159292.559, @@ -346053,6 +346058,7 @@ Object { 119159770.15899245, 119159770.67499243, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -347288,7 +347294,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -351002,7 +351009,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -351044,7 +351052,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -351224,7 +351233,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -351404,7 +351414,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -351584,7 +351595,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -351746,7 +351758,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -359406,7 +359419,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -364122,7 +364136,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -364259,7 +364274,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 119159778.026, @@ -364274,7 +364289,7 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -364358,7 +364373,7 @@ Object { 297, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -364386,7 +364401,7 @@ Object { 23, 24, ], - "inlineDepth": Array [], + "inlineDepth": Uint8Array [], "innerWindowID": Array [ 0, 0, @@ -368061,7 +368076,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -372231,7 +372247,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -372327,7 +372344,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -372369,7 +372387,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -372435,7 +372454,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -380127,7 +380147,7 @@ Object { 14, 14, ], - "time": Array [ + "time": Float64Array [ 119159290.82000001, 119159291.979, 119159292.559, @@ -381034,6 +381054,7 @@ Object { 119159770.15899245, 119159770.67499243, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -382269,7 +382290,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -385983,7 +386005,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -386025,7 +386048,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -386205,7 +386229,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -386385,7 +386410,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -386565,7 +386591,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -386727,7 +386754,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -394387,7 +394415,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -399103,7 +399132,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -399219,7 +399249,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 66155012.423, @@ -399234,7 +399264,7 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -399381,7 +399411,7 @@ Object { 31, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -399430,7 +399460,7 @@ Object { 40, 20, ], - "inlineDepth": Array [], + "inlineDepth": Uint8Array [], "innerWindowID": Array [ 0, 0, @@ -401380,7 +401410,7 @@ Object { 20, 44, ], - "time": Array [ + "time": Float64Array [ 66148721.172, 66148734.519, 66148820.363, @@ -401402,6 +401432,7 @@ Object { 66148973.421000004, 66148976.957, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -401811,7 +401842,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -401927,7 +401959,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Chrome Trace", "sourceURL": "", @@ -401940,11 +401972,11 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [], + "address": Int32Array [], "category": Array [], "column": Array [], - "func": Array [], - "inlineDepth": Array [], + "func": Int32Array [], + "inlineDepth": Uint8Array [], "innerWindowID": Array [], "length": 0, "line": Array [], @@ -402255,7 +402287,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -402303,7 +402336,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -402351,7 +402385,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -402399,7 +402434,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403137,7 +403173,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403185,7 +403222,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403263,7 +403301,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403335,7 +403374,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403407,7 +403447,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403479,7 +403520,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403545,7 +403587,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403587,7 +403630,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403629,7 +403673,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403670,7 +403715,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -403765,7 +403811,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 355035987.653, @@ -403780,7 +403826,7 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -404278,7 +404324,7 @@ Object { 25, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -404444,7 +404490,7 @@ Object { 115, 116, ], - "inlineDepth": Array [], + "inlineDepth": Uint8Array [], "innerWindowID": Array [ 0, 0, @@ -407523,7 +407569,7 @@ Object { 159, 163, ], - "time": Array [ + "time": Float64Array [ 355035818.698, 355035820.536, 355035821.623, @@ -407675,6 +407721,7 @@ Object { 355035985.6030004, 355035986.7690004, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -407776,7 +407823,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Chrome Trace", "sourceURL": "", @@ -407789,7 +407836,7 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -408488,7 +408535,7 @@ Object { 7, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -408721,7 +408768,7 @@ Object { 4, 141, ], - "inlineDepth": Array [], + "inlineDepth": Uint8Array [], "innerWindowID": Array [ 0, 0, @@ -412382,7 +412429,7 @@ Object { 213, 223, ], - "time": Array [ + "time": Float64Array [ 13.291, 14.666, 15.916, @@ -412588,6 +412635,7 @@ Object { 265.99999999999994, 267.24999999999994, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -412790,7 +412838,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -413035,7 +413084,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -413304,7 +413354,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -413399,7 +413450,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 66155012.423, @@ -413414,7 +413465,7 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -413561,7 +413612,7 @@ Object { 31, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -413610,7 +413661,7 @@ Object { 40, 20, ], - "inlineDepth": Array [], + "inlineDepth": Uint8Array [], "innerWindowID": Array [ 0, 0, @@ -414472,7 +414523,7 @@ Object { 20, 44, ], - "time": Array [ + "time": Float64Array [ 66148721.172, 66148734.519, 66148820.363, @@ -414494,6 +414545,7 @@ Object { 66148973.421000004, 66148976.957, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -414544,7 +414596,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -414566,7 +414618,7 @@ Object { "profilingLog": Object {}, "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -415178,7 +415230,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -415382,7 +415434,7 @@ Object { 70, 71, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -418704,7 +418756,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -418726,7 +418778,7 @@ Object { "profilingLog": Object {}, "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -420562,7 +420614,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -421174,7 +421226,7 @@ Object { 608, 609, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -434833,7 +434885,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -434855,7 +434907,7 @@ Object { "profilingLog": Object {}, "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -435548,7 +435600,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -435779,7 +435831,7 @@ Object { 227, 228, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -441509,7 +441561,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -441531,7 +441583,7 @@ Object { "profilingLog": Object {}, "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -443943,7 +443995,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -444747,7 +444799,7 @@ Object { 641, 642, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -459410,7 +459462,7 @@ Object { "keepProfileThreadOrder": true, "markerSchema": Array [], "platform": "Android", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "com.example.sampleapplication", "sourceCodeIsNotOnSearchfox": true, @@ -459423,7 +459475,7 @@ Object { }, "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -466068,7 +466120,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -468283,7 +468335,7 @@ Object { 2211, 2212, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -517286,9 +517338,10 @@ Object { "stack": Array [ 7677, ], - "time": Array [ + "time": Float64Array [ 1870805.135585, ], + "timeDeltas": undefined, "weight": Array [ 0, ], @@ -517320,9 +517373,10 @@ Object { "stack": Array [ 7659, ], - "time": Array [ + "time": Float64Array [ 1870804.962151, ], + "timeDeltas": undefined, "weight": Array [ 0, ], @@ -517360,7 +517414,7 @@ Object { 7631, 7643, ], - "time": Array [ + "time": Float64Array [ 1870798.267391, 1870799.627152, 1870800.996407, @@ -517369,6 +517423,7 @@ Object { 1870803.710581, 1870804.590554, ], + "timeDeltas": undefined, "weight": Array [ 0.25, 0.25, @@ -518183,7 +518238,7 @@ Object { 6534, 6636, ], - "time": Array [ + "time": Float64Array [ 1869019.844661, 1869021.346745, 1869021.653273, @@ -518963,6 +519018,7 @@ Object { 1870049.837894, 1870991.999199, ], + "timeDeltas": undefined, "weight": Array [ 0.25, 0.25, @@ -519771,9 +519827,10 @@ Object { "stack": Array [ 7340, ], - "time": Array [ + "time": Float64Array [ 1870000.035016, ], + "timeDeltas": undefined, "weight": Array [ 0, ], @@ -519912,7 +519969,7 @@ Object { 7562, 7581, ], - "time": Array [ + "time": Float64Array [ 1869945.803221, 1869946.223152, 1869946.412204, @@ -520022,6 +520079,7 @@ Object { 1870036.504092, 1870036.959297, ], + "timeDeltas": undefined, "weight": Array [ 0, 0.25, @@ -520160,9 +520218,10 @@ Object { "stack": Array [ 5767, ], - "time": Array [ + "time": Float64Array [ 1869648.972568, ], + "timeDeltas": undefined, "weight": Array [ 0.25, ], @@ -520192,7 +520251,8 @@ Object { "samples": Object { "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": Array [], "weightType": "tracing-ms", }, @@ -520234,7 +520294,7 @@ Object { 7628, 2453, ], - "time": Array [ + "time": Float64Array [ 1869546.779819, 1869730.306137, 1869731.146345, @@ -520249,6 +520309,7 @@ Object { 1870093.309895, 1870093.456949, ], + "timeDeltas": undefined, "weight": Array [ 0, 0, @@ -520292,9 +520353,10 @@ Object { "stack": Array [ 7694, ], - "time": Array [ + "time": Float64Array [ 1870805.427766, ], + "timeDeltas": undefined, "weight": Array [ 0, ], @@ -520328,11 +520390,12 @@ Object { 6599, 6599, ], - "time": Array [ + "time": Float64Array [ 1869736.799492, 1869738.643145, 1870093.536446, ], + "timeDeltas": undefined, "weight": Array [ 0, 0, @@ -520373,7 +520436,7 @@ Object { 2453, 2453, ], - "time": Array [ + "time": Float64Array [ 1869305.603191, 1869546.886035, 1869548.80852, @@ -520383,6 +520446,7 @@ Object { 1869728.695827, 1869730.289393, ], + "timeDeltas": undefined, "weight": Array [ 0, 0, @@ -520474,7 +520538,7 @@ Object { 1661, 1557, ], - "time": Array [ + "time": Float64Array [ 1869183.119464, 1869183.741288, 1869189.374398, @@ -520530,6 +520594,7 @@ Object { 1870037.747567, 1870038.148022, ], + "timeDeltas": undefined, "weight": Array [ 0.25, 0, @@ -520732,7 +520797,7 @@ Object { 1214, 1240, ], - "time": Array [ + "time": Float64Array [ 1869035.496831, 1869038.067792, 1869053.166018, @@ -520853,6 +520918,7 @@ Object { 1869160.276578, 1869161.658844, ], + "timeDeltas": undefined, "weight": Array [ 0.25, 0.25, @@ -521140,7 +521206,7 @@ Object { 6134, 7626, ], - "time": Array [ + "time": Float64Array [ 1869021.184909, 1869021.561212, 1869034.289177, @@ -521281,6 +521347,7 @@ Object { 1870050.761256, 1870051.337287, ], + "timeDeltas": undefined, "weight": Array [ 0.25, 0.25, @@ -521502,7 +521569,7 @@ Object { "keepProfileThreadOrder": true, "markerSchema": Array [], "platform": "Android", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "com.example.sampleapplication", "sourceCodeIsNotOnSearchfox": true, @@ -521515,7 +521582,7 @@ Object { }, "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -527752,7 +527819,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -529831,7 +529898,7 @@ Object { 2075, 2076, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -583375,7 +583442,7 @@ Object { 10979, 9066, ], - "time": Array [ + "time": Float64Array [ 51497.504337, 51498.075568, 51520.688359, @@ -583436,6 +583503,7 @@ Object { 52106.846732, 52107.312522, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -583490,7 +583558,7 @@ Object { 10822, 7347, ], - "time": Array [ + "time": Float64Array [ 50935.231164, 51170.542439, 51344.969219, @@ -583518,6 +583586,7 @@ Object { 52060.860041, 52060.931061, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -583552,7 +583621,7 @@ Object { 8481, 7347, ], - "time": Array [ + "time": Float64Array [ 50897.993536, 50898.090764, 50935.283518, @@ -583560,6 +583629,7 @@ Object { 51352.208852, 51352.61108, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -583594,7 +583664,7 @@ Object { 10805, 8513, ], - "time": Array [ + "time": Float64Array [ 51353.409302, 52053.582956, 52054.429474, @@ -583602,6 +583672,7 @@ Object { 52055.368444, 52055.72538, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -583633,11 +583704,12 @@ Object { 4897, 4909, ], - "time": Array [ + "time": Float64Array [ 50616.91355, 50617.185082, 50617.222115, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -583670,12 +583742,13 @@ Object { 4866, 4941, ], - "time": Array [ + "time": Float64Array [ 50616.525095, 50616.786243, 50619.430528, 50619.540631, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -583705,9 +583778,10 @@ Object { "stack": Array [ 4926, ], - "time": Array [ + "time": Float64Array [ 50619.140291, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -583791,7 +583865,7 @@ Object { 4943, 4280, ], - "time": Array [ + "time": Float64Array [ 50496.703862, 50497.004734, 50497.283102, @@ -583848,6 +583922,7 @@ Object { 50619.847208, 50620.099211, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -583877,9 +583952,10 @@ Object { "stack": Array [ 7347, ], - "time": Array [ + "time": Float64Array [ 51352.685225, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -584584,7 +584660,7 @@ Object { 10619, 10654, ], - "time": Array [ + "time": Float64Array [ 51326.95693, 51332.074205, 51332.495676, @@ -585262,6 +585338,7 @@ Object { 52018.068497, 52019.552087, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -586563,7 +586640,7 @@ Object { 11003, 8496, ], - "time": Array [ + "time": Float64Array [ 50020.471113, 50020.490138, 50021.143985, @@ -587838,6 +587915,7 @@ Object { 52538.163985, 52538.225498, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -587927,7 +588005,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "target/debug/examples/work_log (dhat)", "sourceURL": "", @@ -587940,437 +588018,437 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [ - -1, - -1, - 4434414523, - 4434380281, - 4434381997, - 4434340159, - 4434348176, - 4434348380, - 4434340956, - 4434340908, - 4434341004, - 4434378972, - 4434383623, - 4433768173, - 4433771704, - 4433869736, - 4433907427, - 4433860184, - 4433742924, - 4433707208, - 4433946914, - 4433764447, - 4433768748, - 4433907188, - 4433870895, - 4433815600, - 4433870407, - 4433859192, - 4433727438, - 4433705160, - 4433876308, - 4433787574, - 4433870773, - 4433860504, - 4433727790, - 4433705432, - 4433871619, - 4433807958, - 4433870549, - 4433859416, - 4433728494, - 4433705256, - 4433919000, - 4433773046, - 4433909157, - 4433859896, - 4433729022, - 4433704968, - 4433909731, - 4433810870, - 4433909301, - 4433690406, - 4433691566, - 4433892450, - 4433817678, - 4433581665, - 4433578169, - 4433933037, - 4433766287, - 4433768700, - 4433907284, - 4433669311, - 4433815552, - 4433668823, - 4433859736, - 4433728846, - 4433705112, - 4433674118, - 4433799222, - 4433669189, - 4433858808, - 4433729374, - 4433705480, - 4433670035, - 4433793398, - 4433669045, - 4433858712, - 4433727086, - 4433705720, - 4433918697, - 4435357725, - 4435357725, - 4435357725, - 4435357725, - 4435357725, - 4435357725, - 4435357725, - 4435516916, - 4435343797, - 4435343797, - 4435343797, - 4435343797, - 4435343797, - 4433577402, - 4433578253, - 4435335193, - 4435335193, - 4435335193, - 4435335193, - 4435335193, - 4435335193, - 4435335193, - 4433655678, - 4433613618, - 4433612853, - 4433577711, - 4433934793, - 4433875399, - 4433936988, - 4435372547, - 4435372547, - 4435372547, - 4435372547, - 4435372547, - 4435372547, - 4435372547, - 4435372547, - 4435372547, - 4435357280, - 4435357280, - 4433815847, - 4433936549, - 4433673815, - 4433932610, - 4435357056, - 4435357056, - 4435357056, - 4435357056, - 4435357056, - 4435357056, - 4435357056, - 4435357056, - 4435357056, - 4435357056, - 4435357056, - 4435357056, - 4433654538, - 4433612835, - 4433933476, - 4433613597, - 4434125051, - 4434104625, - 4434106255, - 4434105127, - 4434108174, - 4434099818, - 4434098492, - 4434098197, - 4434099068, - 4434135657, - 4434135353, - 4434101804, - 4434133186, - 4434121026, - 4434117512, - 4435473437, - 4434101625, - 4434074996, - 4434069158, - 4434075304, - 4434068856, - 4433815893, - 4433932226, - 4433674724, - 4433936110, - 4433947298, - 4434107048, - 4434104999, - 4434108254, - 4434099866, - 4434099572, - 4434128182, - 4434125664, - 4434120868, - 4434120956, - 4433614860, - 4433615607, - 4433615708, - 4433638093, - 4433643720, - 4433596840, - 4433655176, - 4433627732, - 4433623112, - 4433655937, - 4433632687, - 4433607592, - 4433616499, - 4433655080, - 4433631416, - 4433622616, - 4433604422, - 4433640585, - 4433599285, - 4433655128, - 4433624526, - 4433622824, - 4433599995, - 4433645099, - 4433598757, - 4433617170, - 4433622244, - 4433613973, - 4435447667, - 4435447667, - 4435447667, - 4435447667, - 4435447667, - 4435447667, - 4435447667, - 4435447667, - 4435447667, - 4435447667, - 4435447667, - 4435447667, - 4434148218, - 4435448212, - 4435448212, - 4434087285, - 4434074024, - 4434086536, - 4433815728, - 4433949042, - 4433946319, - 4433768978, - 4433850424, - 4433859688, - 4433726222, - 4433705864, - 4433876005, - 4433935232, - 4433949481, - 4433726398, - 4433705208, - 4433819813, - 4433781761, - 4433818693, - 4433860072, - 4433728142, - 4433705816, - 4433828929, - 4433778864, - 4433818901, - 4433860280, - 4433729198, - 4433705304, - 4433823555, - 4433805046, - 4433818837, - 4433860456, - 4433728318, - 4433705624, - 4433919303, - 4433948603, - 4433946403, - 4433771058, - 4433849848, - 4433859784, - 4433728670, - 4433705912, - 4433836977, - 4433775952, - 4433819045, - 4433946543, - 4433769810, - 4433850376, - 4433859576, - 4433726910, - 4433705672, - 4433823858, - 4433861497, - 4433796304, - 4433860661, - 4433858760, - 4433729726, - 4433705016, - 4433913481, - 4433813782, - 4433909013, - 4433860232, - 4433726574, - 4433705352, - 4433910034, - 4433861153, - 4433913750, - 4433914019, - 4434141931, - 4434147097, - 4434147741, - 4434140111, - 4433929880, - 4433688729, - 4433689109, - 4433816723, - 4433933915, - 4433934354, - 4433935671, - 4433948164, - 4433648904, - 4433647863, - 4433650174, - 4433607514, - 4433607291, - 4433656231, - 4433819469, - 4433862185, - 4434146400, - 4434146604, - 4434142812, - 4434142764, - 4434142860, - 4433999404, - 4433973292, - 4433972763, - 4433973243, - 4433968071, - 4433997012, - 4434001862, - 4434000938, - 4434000393, - 4433582481, - 4434003046, - 4434000609, - 4433962888, - 4433961639, - 4433963422, - 4433961210, - 4433960540, - 4433961097, - 4433970902, - 4433997295, - 4434002475, - 4434001324, - 4434099244, - 4433973088, - 4433971548, - 4435334870, - 4435334870, - 4435334870, - 4435334870, - 4435334870, - 4435334870, - 4435334870, - 4435334870, - 4435334870, - 4435334870, - 4433654735, - 4433612711, - 4433827905, - 4435358005, - 4435358005, - 4435358005, - 4435358005, - 4435358005, - 4435358005, - 4435358005, - 4435358005, - 4435358005, - 4435358005, - 4435358005, - 4433660741, - 4433663996, - 4433731851, - 4433737617, - 4433912977, - 4433860385, - 4433708661, - 4433706465, - 4433909412, - 4433875702, - 4433947725, - 4434084153, - 4434018698, - 4434018698, - 4433818089, - 4434005241, - 4433997138, - 4433930608, - 4433846007, - 4433894007, - 4433894247, - 4433815709, - 4433612772, - 4433827393, - 4433861841, - 4433576315, - 4433572249, - 4433573901, - 4433593039, - 4433592472, - 4433593710, - 4433593439, - 4433594183, - 4433584543, - 4433567363, - 4433584731, - 4433567295, - 4433576540, - 4433577809, - 4433816367, - 4433960859, - 4433967952, - 4433579108, - 4435357933, - 4435357933, - 4435357933, - 4435357933, - 4435357933, - 4435357933, - 4435357933, - 4435357933, - 4435357933, - 4435357933, - 4435357933, - 4435336372, - 4435336372, - 4435336372, - 4435336372, - 4435336372, - 4435336372, - 4435336372, - 4435336372, - 4435336372, - 4435336372, - 4435335008, - 4435335008, - 4435335008, - 4433926318, - 4433926497, - 4433816406, + "address": Int32Array [ + -1, + -1, + 139447227, + 139412985, + 139414701, + 139372863, + 139380880, + 139381084, + 139373660, + 139373612, + 139373708, + 139411676, + 139416327, + 138800877, + 138804408, + 138902440, + 138940131, + 138892888, + 138775628, + 138739912, + 138979618, + 138797151, + 138801452, + 138939892, + 138903599, + 138848304, + 138903111, + 138891896, + 138760142, + 138737864, + 138909012, + 138820278, + 138903477, + 138893208, + 138760494, + 138738136, + 138904323, + 138840662, + 138903253, + 138892120, + 138761198, + 138737960, + 138951704, + 138805750, + 138941861, + 138892600, + 138761726, + 138737672, + 138942435, + 138843574, + 138942005, + 138723110, + 138724270, + 138925154, + 138850382, + 138614369, + 138610873, + 138965741, + 138798991, + 138801404, + 138939988, + 138702015, + 138848256, + 138701527, + 138892440, + 138761550, + 138737816, + 138706822, + 138831926, + 138701893, + 138891512, + 138762078, + 138738184, + 138702739, + 138826102, + 138701749, + 138891416, + 138759790, + 138738424, + 138951401, + 140390429, + 140390429, + 140390429, + 140390429, + 140390429, + 140390429, + 140390429, + 140549620, + 140376501, + 140376501, + 140376501, + 140376501, + 140376501, + 138610106, + 138610957, + 140367897, + 140367897, + 140367897, + 140367897, + 140367897, + 140367897, + 140367897, + 138688382, + 138646322, + 138645557, + 138610415, + 138967497, + 138908103, + 138969692, + 140405251, + 140405251, + 140405251, + 140405251, + 140405251, + 140405251, + 140405251, + 140405251, + 140405251, + 140389984, + 140389984, + 138848551, + 138969253, + 138706519, + 138965314, + 140389760, + 140389760, + 140389760, + 140389760, + 140389760, + 140389760, + 140389760, + 140389760, + 140389760, + 140389760, + 140389760, + 140389760, + 138687242, + 138645539, + 138966180, + 138646301, + 139157755, + 139137329, + 139138959, + 139137831, + 139140878, + 139132522, + 139131196, + 139130901, + 139131772, + 139168361, + 139168057, + 139134508, + 139165890, + 139153730, + 139150216, + 140506141, + 139134329, + 139107700, + 139101862, + 139108008, + 139101560, + 138848597, + 138964930, + 138707428, + 138968814, + 138980002, + 139139752, + 139137703, + 139140958, + 139132570, + 139132276, + 139160886, + 139158368, + 139153572, + 139153660, + 138647564, + 138648311, + 138648412, + 138670797, + 138676424, + 138629544, + 138687880, + 138660436, + 138655816, + 138688641, + 138665391, + 138640296, + 138649203, + 138687784, + 138664120, + 138655320, + 138637126, + 138673289, + 138631989, + 138687832, + 138657230, + 138655528, + 138632699, + 138677803, + 138631461, + 138649874, + 138654948, + 138646677, + 140480371, + 140480371, + 140480371, + 140480371, + 140480371, + 140480371, + 140480371, + 140480371, + 140480371, + 140480371, + 140480371, + 140480371, + 139180922, + 140480916, + 140480916, + 139119989, + 139106728, + 139119240, + 138848432, + 138981746, + 138979023, + 138801682, + 138883128, + 138892392, + 138758926, + 138738568, + 138908709, + 138967936, + 138982185, + 138759102, + 138737912, + 138852517, + 138814465, + 138851397, + 138892776, + 138760846, + 138738520, + 138861633, + 138811568, + 138851605, + 138892984, + 138761902, + 138738008, + 138856259, + 138837750, + 138851541, + 138893160, + 138761022, + 138738328, + 138952007, + 138981307, + 138979107, + 138803762, + 138882552, + 138892488, + 138761374, + 138738616, + 138869681, + 138808656, + 138851749, + 138979247, + 138802514, + 138883080, + 138892280, + 138759614, + 138738376, + 138856562, + 138894201, + 138829008, + 138893365, + 138891464, + 138762430, + 138737720, + 138946185, + 138846486, + 138941717, + 138892936, + 138759278, + 138738056, + 138942738, + 138893857, + 138946454, + 138946723, + 139174635, + 139179801, + 139180445, + 139172815, + 138962584, + 138721433, + 138721813, + 138849427, + 138966619, + 138967058, + 138968375, + 138980868, + 138681608, + 138680567, + 138682878, + 138640218, + 138639995, + 138688935, + 138852173, + 138894889, + 139179104, + 139179308, + 139175516, + 139175468, + 139175564, + 139032108, + 139005996, + 139005467, + 139005947, + 139000775, + 139029716, + 139034566, + 139033642, + 139033097, + 138615185, + 139035750, + 139033313, + 138995592, + 138994343, + 138996126, + 138993914, + 138993244, + 138993801, + 139003606, + 139029999, + 139035179, + 139034028, + 139131948, + 139005792, + 139004252, + 140367574, + 140367574, + 140367574, + 140367574, + 140367574, + 140367574, + 140367574, + 140367574, + 140367574, + 140367574, + 138687439, + 138645415, + 138860609, + 140390709, + 140390709, + 140390709, + 140390709, + 140390709, + 140390709, + 140390709, + 140390709, + 140390709, + 140390709, + 140390709, + 138693445, + 138696700, + 138764555, + 138770321, + 138945681, + 138893089, + 138741365, + 138739169, + 138942116, + 138908406, + 138980429, + 139116857, + 139051402, + 139051402, + 138850793, + 139037945, + 139029842, + 138963312, + 138878711, + 138926711, + 138926951, + 138848413, + 138645476, + 138860097, + 138894545, + 138609019, + 138604953, + 138606605, + 138625743, + 138625176, + 138626414, + 138626143, + 138626887, + 138617247, + 138600067, + 138617435, + 138599999, + 138609244, + 138610513, + 138849071, + 138993563, + 139000656, + 138611812, + 140390637, + 140390637, + 140390637, + 140390637, + 140390637, + 140390637, + 140390637, + 140390637, + 140390637, + 140390637, + 140390637, + 140369076, + 140369076, + 140369076, + 140369076, + 140369076, + 140369076, + 140369076, + 140369076, + 140369076, + 140369076, + 140367712, + 140367712, + 140367712, + 138959022, + 138959201, + 138849110, ], "category": Array [ 0, @@ -589236,7 +589314,7 @@ Object { 9, 26, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -589668,7 +589746,7 @@ Object { 210, 212, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -595765,7 +595843,7 @@ Object { 360, 92, ], - "time": Array [ + "time": Float64Array [ 0, 0, 0, @@ -596005,7 +596083,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -596140,7 +596219,7 @@ Object { 360, 92, ], - "time": Array [ + "time": Float64Array [ 0, 0, 0, @@ -596380,7 +596459,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -596515,7 +596595,7 @@ Object { 360, 92, ], - "time": Array [ + "time": Float64Array [ 0, 0, 0, @@ -596755,7 +596835,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -596890,7 +596971,7 @@ Object { 360, 92, ], - "time": Array [ + "time": Float64Array [ 0, 0, 0, @@ -597130,7 +597211,8 @@ Object { "eventDelay": Array [], "length": 0, "stack": Array [], - "time": Array [], + "time": Float64Array [], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -597177,7 +597259,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Flamegraph", "sourceURL": "", @@ -597190,7 +597272,7 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -597658,7 +597740,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -597814,7 +597896,7 @@ Object { 152, 153, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -752970,7 +753052,7 @@ Object { 192, 192, ], - "time": Array [ + "time": Float64Array [ 0, 1, 2, @@ -905336,6 +905418,7 @@ Object { 152362, 152363, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, @@ -905382,7 +905465,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Flamegraph", "sourceURL": "", @@ -905395,7 +905478,7 @@ Object { "pages": Array [], "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -905416,14 +905499,14 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, 3, 4, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -905622,7 +905705,7 @@ Object { 4, 4, ], - "time": Array [ + "time": Float64Array [ 0, 1, 2, @@ -905642,6 +905725,7 @@ Object { 16, 17, ], + "timeDeltas": undefined, "weight": null, "weightType": "samples", }, diff --git a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap index 4b1a8867e6..66d77fe6cd 100644 --- a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap @@ -40,7 +40,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -62,7 +62,7 @@ Object { "profilingLog": Object {}, "shared": Object { "frameTable": Object { - "address": Array [ + "address": Int32Array [ -1, -1, -1, @@ -674,7 +674,7 @@ Object { null, null, ], - "func": Array [ + "func": Int32Array [ 0, 1, 2, @@ -878,7 +878,7 @@ Object { 70, 71, ], - "inlineDepth": Array [ + "inlineDepth": Uint8Array [ 0, 0, 0, @@ -7643,7 +7643,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -9019,7 +9019,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -10563,7 +10563,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 66, + "preprocessedProfileVersion": 67, "processType": 0, "product": "Firefox", "stackwalk": 1, diff --git a/src/test/unit/process-profile.test.ts b/src/test/unit/process-profile.test.ts index 84bd6bfc3b..945bfd78e6 100644 --- a/src/test/unit/process-profile.test.ts +++ b/src/test/unit/process-profile.test.ts @@ -510,7 +510,7 @@ describe('js allocation processing', function () { } // Assert that the transformation makes sense. - expect(jsAllocations.time).toEqual([0, 1, 2]); + expect(jsAllocations.time).toEqual(new Float64Array([0, 1, 2])); expect(jsAllocations.weight).toEqual([3, 5, 7]); // All addressses should be nudged by 1 byte, because js allocation stack frames @@ -594,7 +594,7 @@ describe('native allocation processing', function () { } // Assert that the transformation makes sense. - expect(nativeAllocations.time).toEqual([0, 1, 2]); + expect(nativeAllocations.time).toEqual(new Float64Array([0, 1, 2])); expect(nativeAllocations.weight).toEqual([3, 5, 7]); expect(nativeAllocations.stack).toEqual([8, 9, null]); }); diff --git a/src/types/profile.ts b/src/types/profile.ts index 61a4060a2d..bbac5d671b 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -126,9 +126,9 @@ export type RawSamplesTable = { // This is optional because older profiles didn't have that field. eventDelay?: Array; stack: Array; - time?: Milliseconds[]; + time?: Milliseconds[] | Float64Array; // If the `time` column is not present, then the `timeDeltas` column must be present. - timeDeltas?: Milliseconds[]; + timeDeltas?: Milliseconds[] | Float64Array; argumentValues?: Array; // An optional weight array. If not present, then the weight is assumed to be 1. // See the WeightType type for more information. @@ -155,7 +155,7 @@ export type RawSamplesTable = { * There is also a derived `JsAllocationsTable` type, see profile-derived.js. */ export type RawJsAllocationsTable = { - time: Milliseconds[]; + time: Milliseconds[] | Float64Array; className: string[]; typeName: string[]; // Currently only 'JSObject' coarseType: string[]; // Currently only 'Object', @@ -173,7 +173,7 @@ export type RawJsAllocationsTable = { * and threadId were added. */ export type RawUnbalancedNativeAllocationsTable = { - time: Milliseconds[]; + time: Milliseconds[] | Float64Array; // "weight" is used here rather than "bytes", so that this type can be // used as a SamplesLikeTable. weight: Bytes[]; @@ -245,7 +245,9 @@ export type RawFrameTable = { // // The library which this address is relative to is given by the frame's nativeSymbol: // frame -> nativeSymbol -> lib. - address: Array
; + // + // Frames with no address use the sentinel value `-1`. + address: Array
| Int32Array; // The inline depth for this frame. If there is an inline stack at an address, // we create multiple frames with the same address, one for each depth. @@ -273,7 +275,7 @@ export type RawFrameTable = { // // The frames of an inline stack at an address all have the same address and the same // nativeSymbol, but each has a different func and line. - inlineDepth: number[]; + inlineDepth: number[] | Uint8Array; // The category of the frame. This is used to calculate the category of the stack nodes // which use this frame: @@ -290,7 +292,7 @@ export type RawFrameTable = { subcategory: (IndexIntoSubcategoryListForCategory | null)[]; // The frame's function. - func: IndexIntoFuncTable[]; + func: IndexIntoFuncTable[] | Int32Array; // The symbol index (referring into this thread's nativeSymbols table) corresponding // to symbol that covers the frame address of this frame. Only non-null for native @@ -534,8 +536,8 @@ export type JsTracerTable = { }; export type RawCounterSamplesTable = { - time?: Milliseconds[]; - timeDeltas?: Milliseconds[]; + time?: Milliseconds[] | Float64Array; + timeDeltas?: Milliseconds[] | Float64Array; // The number of times the Counter's "number" was changed since the previous sample. // This property was mandatory until the format version 42, it was made optional in 43. number?: number[];