Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/node-tools/profiler-edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { parse as parseToml } from 'smol-toml';

import {
optimizeProfileForStorage,
serializeProfileToJsonSlabsFile,
serializeProfileToJsonString,
unserializeProfileOfArbitraryFormat,
Expand Down Expand Up @@ -205,7 +206,9 @@ async function encodeProfileWithFilename(
filename: string
): Promise<Uint8Array> {
if (filename.endsWith('.jslb') || filename.endsWith('.jslb.gz')) {
const bytes = serializeProfileToJsonSlabsFile(profile);
const bytes = serializeProfileToJsonSlabsFile(
optimizeProfileForStorage(profile)
);
if (filename.endsWith('.jslb.gz')) {
return compress(bytes);
}
Expand Down
90 changes: 90 additions & 0 deletions src/profile-logic/process-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ import {
getFriendlyThreadName,
nudgeReturnAddresses,
} from '../profile-logic/profile-data';
import {
toInt32Array,
toUint8Array,
toFloat64Array,
} from '../utils/typed-arrays';
import { computeStringIndexMarkerFieldsByDataType } from '../profile-logic/marker-schema';
import { convertJsTracerToThread } from '../profile-logic/js-tracer';

Expand Down Expand Up @@ -109,6 +114,7 @@ import type {
IndexIntoCategoryList,
IndexIntoFrameTable,
CounterDisplayConfig,
RawProfileSharedData,
} from 'firefox-profiler/types';
import { decompress, isGzip } from 'firefox-profiler/utils/gz';
import { jsonEncodeObjectWithTypedArraysAsRegularArrays } from 'firefox-profiler/utils/json-with-typed-arrays';
Expand Down Expand Up @@ -2058,6 +2064,90 @@ export function serializeProfileToJsonString(profile: Profile): string {
return jsonEncodeObjectWithTypedArraysAsRegularArrays(profile);
}

/**
* Convert all columns of a profile which are eligible to be stored as typed
* arrays into typed arrays.
*
* This is useful if the profile will be saved as a JSLB file:
* `serializeProfileToJsonSlabsFile` will be able to store these columns as binary
* slabs instead of as JSON arrays, which speeds up both the saving and the loading
* of the file.
*
* Does not mutate the input profile.
*/
export function optimizeProfileForStorage(profile: Profile): Profile {
return {
...profile,
shared: convertSharedTablesEligibleColumns(profile.shared),
threads: profile.threads.map(convertThreadEligibleColumns),
counters: profile.counters?.map(convertCounterEligibleColumns),
};
}

function convertSharedTablesEligibleColumns(
shared: RawProfileSharedData
): RawProfileSharedData {
const { stackTable, frameTable } = shared;
return {
...shared,
stackTable: {
frame: toInt32Array(stackTable.frame),
prefixOffset: toInt32Array(stackTable.prefixOffset),
length: stackTable.length,
},
frameTable: {
...frameTable,
address: toInt32Array(frameTable.address),
inlineDepth: toUint8Array(frameTable.inlineDepth),
func: toInt32Array(frameTable.func),
},
};
}

function convertThreadEligibleColumns(thread: RawThread): RawThread {
const newThread: RawThread = {
...thread,
samples: convertSamplesTimesToTypedArrays(thread.samples),
};
if (thread.jsAllocations !== undefined) {
newThread.jsAllocations = {
...thread.jsAllocations,
time: toFloat64Array(thread.jsAllocations.time),
};
}
if (thread.nativeAllocations !== undefined) {
newThread.nativeAllocations = {
...thread.nativeAllocations,
time: toFloat64Array(thread.nativeAllocations.time),
};
}
return newThread;
}

function convertSamplesTimesToTypedArrays(
samples: RawSamplesTable
): RawSamplesTable {
const result = { ...samples };
if (samples.time !== undefined) {
result.time = toFloat64Array(samples.time);
}
if (samples.timeDeltas !== undefined) {
result.timeDeltas = toFloat64Array(samples.timeDeltas);
}
return result;
}

function convertCounterEligibleColumns(counter: RawCounter): RawCounter {
const samples: RawCounterSamplesTable = { ...counter.samples };
if (counter.samples.time !== undefined) {
samples.time = toFloat64Array(counter.samples.time);
}
if (counter.samples.timeDeltas !== undefined) {
samples.timeDeltas = toFloat64Array(counter.samples.timeDeltas);
}
return { ...counter, samples };
}

/**
* Take a profile and convert it to a Uint8Array in the JsonSlabs format.
*
Expand Down
33 changes: 12 additions & 21 deletions src/profile-logic/profile-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ import type {
} from 'firefox-profiler/types';
import { SelectedState, ResourceType } from 'firefox-profiler/types';
import type { CallNodeInfo, SuffixOrderIndex } from './call-node-info';
import {
toFloat64Array,
toInt32Array,
toUint8Array,
} from 'firefox-profiler/utils/typed-arrays';

/**
* Various helpers for dealing with the profile as a data structure.
Expand Down Expand Up @@ -1944,7 +1949,7 @@ export function computeTimeColumnForRawSamplesTable(
): Float64Array<ArrayBuffer> {
const { time, timeDeltas } = samples;
if (time !== undefined) {
return time instanceof Float64Array ? time : new Float64Array(time);
return toFloat64Array(time);
}
return numberSeriesFromDeltas(ensureExists(timeDeltas));
}
Expand Down Expand Up @@ -2793,8 +2798,7 @@ export function computeJsAllocationsTableFromRawJsAllocationsTable(
raw: RawJsAllocationsTable
): JsAllocationsTable {
return {
time:
raw.time instanceof Float64Array ? raw.time : new Float64Array(raw.time),
time: toFloat64Array(raw.time),
className: raw.className,
typeName: raw.typeName,
coarseType: raw.coarseType,
Expand All @@ -2809,8 +2813,7 @@ export function computeJsAllocationsTableFromRawJsAllocationsTable(
export function computeNativeAllocationsTableFromRawNativeAllocationsTable(
raw: RawNativeAllocationsTable
): NativeAllocationsTable {
const time =
raw.time instanceof Float64Array ? raw.time : new Float64Array(raw.time);
const time = toFloat64Array(raw.time);
if ('memoryAddress' in raw) {
return {
time,
Expand Down Expand Up @@ -4784,18 +4787,9 @@ export function computeTabToThreadIndexesMap(
export function computeFrameTableFromRawFrameTable(
rawFrameTable: RawFrameTable
): FrameTable {
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);
const address = toInt32Array(rawFrameTable.address);
const inlineDepth = toUint8Array(rawFrameTable.inlineDepth);
const func = toInt32Array(rawFrameTable.func);
return {
address,
inlineDepth,
Expand Down Expand Up @@ -4863,10 +4857,7 @@ export function computeStackTableFromRawStackTable(
}

// The frame column is a typed array in the derived stack table.
const frame =
rawStackTable.frame instanceof Int32Array
? rawStackTable.frame
: new Int32Array(rawStackTable.frame);
const frame = toInt32Array(rawStackTable.frame);

return {
frame,
Expand Down
21 changes: 21 additions & 0 deletions src/utils/typed-arrays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* 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/. */

export function toInt32Array(
arr: Array<number> | Int32Array<ArrayBuffer>
): Int32Array<ArrayBuffer> {
return arr instanceof Int32Array ? arr : new Int32Array(arr);
}

export function toUint8Array(
arr: Array<number> | Uint8Array<ArrayBuffer>
): Uint8Array<ArrayBuffer> {
return arr instanceof Uint8Array ? arr : new Uint8Array(arr);
}

export function toFloat64Array(
arr: Array<number> | Float64Array<ArrayBuffer>
): Float64Array<ArrayBuffer> {
return arr instanceof Float64Array ? arr : new Float64Array(arr);
}
Loading