Skip to content
Closed
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
12 changes: 12 additions & 0 deletions docs-developer/CHANGELOG-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/app-logic/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions src/profile-logic/combined-cpu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ArrayBuffer>;
cpuRatio: Float64Array;
maxCpuRatio: number;
length: number;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -126,7 +126,7 @@ export function combineCPUDataFromThreads(
}

return {
time: combinedTime,
time: Float64Array.from(combinedTime),
cpuRatio: Float64Array.from(combinedCPURatio),
maxCpuRatio: combinedMaxCpuRatio,
length: combinedTime.length,
Expand Down
2 changes: 1 addition & 1 deletion src/profile-logic/cpu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function computeReferenceCPUDeltaPerMs(profile: Profile): number {
*/
export function computeThreadCPUPercent(
threadCPUDelta: Array<number | null>,
timeDeltas: number[],
timeDeltas: number[] | Float64Array<ArrayBuffer>,
referenceCPUDeltaPerMs: number
): Uint8Array {
const threadCPUPercent: Uint8Array = new Uint8Array(
Expand Down
156 changes: 134 additions & 22 deletions src/profile-logic/data-structures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@ import type {
RawProfileSharedData,
RawThread,
RawSamplesTable,
FrameTable,
RawFrameTable,
RawStackTable,
FuncTable,
RawMarkerTable,
JsAllocationsTable,
UnbalancedNativeAllocationsTable,
BalancedNativeAllocationsTable,
ResourceTable,
NativeSymbolTable,
Profile,
Expand All @@ -28,14 +25,100 @@ import type {
SourceTable,
SourceLocationTable,
IndexIntoFrameTable,
IndexIntoFuncTable,
IndexIntoStackTable,
IndexIntoCategoryList,
IndexIntoSubcategoryListForCategory,
IndexIntoNativeSymbolTable,
IndexIntoSourceLocationTable,
InnerWindowID,
Address,
Bytes,
Milliseconds,
Tid,
WeightType,
} 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 RawSamplesTableBuilder = {
responsiveness?: Array<Milliseconds | null>;
eventDelay?: Array<Milliseconds | null>;
stack: Array<IndexIntoStackTable | null>;
time?: Milliseconds[];
timeDeltas?: Milliseconds[];
argumentValues?: Array<number | null>;
weight: null | number[];
weightType: WeightType;
threadCPUDelta?: Array<number | null>;
threadId?: Tid[];
length: number;
};

export type RawJsAllocationsTableBuilder = {
time: Milliseconds[];
className: string[];
typeName: string[];
coarseType: string[];
weight: Bytes[];
weightType: 'bytes';
inNursery: boolean[];
stack: Array<IndexIntoStackTable | null>;
length: number;
};

export type RawUnbalancedNativeAllocationsTableBuilder = {
time: Milliseconds[];
weight: Bytes[];
weightType: 'bytes';
stack: Array<IndexIntoStackTable | null>;
argumentValues?: Array<number | null>;
length: number;
};

export type RawBalancedNativeAllocationsTableBuilder = {
time: Milliseconds[];
weight: Bytes[];
weightType: 'bytes';
stack: Array<IndexIntoStackTable | null>;
argumentValues?: Array<number | null>;
memoryAddress: number[];
threadId: number[];
length: number;
};

export type RawFrameTableBuilder = {
address: Array<Address | -1>;
inlineDepth: number[];
category: (IndexIntoCategoryList | null)[];
subcategory: (IndexIntoSubcategoryListForCategory | null)[];
func: IndexIntoFuncTable[];
nativeSymbol: (IndexIntoNativeSymbolTable | null)[];
innerWindowID: (InnerWindowID | null)[];
line: (number | null)[];
column: (number | null)[];
originalLocation: Array<IndexIntoSourceLocationTable | null>;
length: number;
};

export type RawStackTableBuilder = {
frame: IndexIntoFrameTable[];
prefix: Array<IndexIntoStackTable | null>;
length: number;
};

/**
* 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
Expand All @@ -49,12 +132,6 @@ export function getEmptySamplesTable(): RawSamplesTable {
};
}

export type RawStackTableBuilder = {
frame: IndexIntoFrameTable[];
prefix: Array<IndexIntoStackTable | null>;
length: number;
};

export function getRawStackTableBuilder(): RawStackTableBuilder {
return {
// Important!
Expand All @@ -67,6 +144,39 @@ 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 getRawStackTableBuilderWithExistingContents(
existing: RawStackTable
): RawStackTableBuilder {
Expand Down Expand Up @@ -103,7 +213,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
Expand All @@ -118,7 +228,7 @@ export function getEmptySamplesTableWithEventDelay(): RawSamplesTable {
};
}

export function getEmptyFrameTable(): FrameTable {
export function getRawFrameTableBuilder(): RawFrameTableBuilder {
return {
// Important!
// If modifying this structure, please update all callers of this function to ensure
Expand All @@ -138,17 +248,19 @@ export function getEmptyFrameTable(): FrameTable {
};
}

export function shallowCloneFrameTable(frameTable: FrameTable): FrameTable {
export function getRawFrameTableBuilderWithExistingContents(
frameTable: RawFrameTable
): RawFrameTableBuilder {
return {
// 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
// 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(),
Expand Down Expand Up @@ -274,7 +386,7 @@ export function getEmptyRawMarkerTable(): RawMarkerTable {
};
}

export function getEmptyJsAllocationsTable(): JsAllocationsTable {
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
Expand All @@ -296,7 +408,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(): 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
Expand All @@ -314,7 +426,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(): 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
Expand Down Expand Up @@ -420,7 +532,7 @@ export function getEmptyThread(overrides?: Partial<RawThread>): RawThread {
pid: '0',
tid: 0,
// Creating samples with event delay since it's the new samples table.
samples: getEmptySamplesTableWithEventDelay(),
samples: getRawSamplesTableBuilderWithEventDelay(),
markers: getEmptyRawMarkerTable(),
};

Expand All @@ -433,7 +545,7 @@ export function getEmptyThread(overrides?: Partial<RawThread>): RawThread {
export function getEmptySharedData(): RawProfileSharedData {
return {
stackTable: finishRawStackTableBuilder(getRawStackTableBuilder()),
frameTable: getEmptyFrameTable(),
frameTable: getRawFrameTableBuilder(),
funcTable: getEmptyFuncTable(),
resourceTable: getEmptyResourceTable(),
nativeSymbols: getEmptyNativeSymbolTable(),
Expand Down
12 changes: 7 additions & 5 deletions src/profile-logic/global-data-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { StringTable } from '../utils/string-table';
import {
finishRawStackTableBuilder,
getEmptyFrameTable,
getRawFrameTableBuilder,
getEmptyFuncTable,
getEmptyNativeSymbolTable,
getEmptyResourceTable,
Expand All @@ -22,7 +22,6 @@ import type {
IndexIntoSourceTable,
RawProfileSharedData,
SourceTable,
FrameTable,
FuncTable,
ResourceTable,
NativeSymbolTable,
Expand All @@ -34,7 +33,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
Expand All @@ -50,7 +52,7 @@ export class GlobalDataCollector {
_stringArray: string[] = [];
_stringTable: StringTable = StringTable.withBackingArray(this._stringArray);
_sources: SourceTable = getEmptySourceTable();
_frameTable: FrameTable = getEmptyFrameTable();
_frameTable: RawFrameTableBuilder = getRawFrameTableBuilder();
_stackTableBuilder: RawStackTableBuilder = getRawStackTableBuilder();
_funcTable: FuncTable = getEmptyFuncTable();
_resourceTable: ResourceTable = getEmptyResourceTable();
Expand Down Expand Up @@ -299,7 +301,7 @@ export class GlobalDataCollector {
return this._stringTable;
}

getFrameTable(): FrameTable {
getFrameTable(): RawFrameTableBuilder {
return this._frameTable;
}

Expand Down
Loading
Loading