Skip to content
Draft
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
4 changes: 4 additions & 0 deletions docs-developer/CHANGELOG-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Note that this is not an exhaustive list. Processed profile format upgraders can

## Processed profile format

### Version 56

The `stringArray` is now shared across threads. The shared array is stored at `profile.shared.stringArray`.

### Version 55

Changes to the `MarkerSchema` type which is used for the elements of the array at `profile.meta.markerSchema`:
Expand Down
64 changes: 23 additions & 41 deletions src/actions/receive-profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ import type {
PageList,
} from 'firefox-profiler/types';

import type {
FuncToFuncsMap,
SymbolicationStepInfo,
} from '../profile-logic/symbolication';
import type { SymbolicationStepInfo } from '../profile-logic/symbolication';
import { assertExhaustiveCheck, ensureExists } from '../utils/flow';
import { bytesToBase64DataUrl } from 'firefox-profiler/utils/base64';
import type {
Expand Down Expand Up @@ -397,7 +394,7 @@ export function finalizeFullProfileView(
const thread = profile.threads[threadIndex];
const { samples, jsAllocations, nativeAllocations } = thread;
hasSamples = [samples, jsAllocations, nativeAllocations].some((table) =>
hasUsefulSamples(table?.stack, thread)
hasUsefulSamples(table?.stack, thread, profile.shared)
);
if (hasSamples) {
break;
Expand Down Expand Up @@ -477,11 +474,13 @@ export function finalizeOriginProfileView(
threadIndex < profile.threads.length;
threadIndex++
) {
const { frameTable } = profile.threads[threadIndex];
const { usedInnerWindowIDs } = profile.threads[threadIndex];
if (usedInnerWindowIDs === undefined) {
continue;
}

let originFound = false;
for (let frameIndex = 0; frameIndex < frameTable.length; frameIndex++) {
const innerWindowID = frameTable.innerWindowID[frameIndex];
for (const innerWindowID of usedInnerWindowIDs) {
if (innerWindowID === null || innerWindowID === 0) {
continue;
}
Expand Down Expand Up @@ -790,27 +789,20 @@ export function doneSymbolicating(): Action {
// reach the screen because it would be invalidated by the next symbolication update.
// So we queue up symbolication steps and run the update from requestIdleCallback.
export function bulkProcessSymbolicationSteps(
symbolicationStepsPerThread: Map<ThreadIndex, SymbolicationStepInfo[]>
symbolicationSteps: SymbolicationStepInfo[]
): ThunkAction<void> {
return (dispatch, getState) => {
const { threads } = getProfile(getState());
const oldFuncToNewFuncsMaps: Map<ThreadIndex, FuncToFuncsMap> = new Map();
const symbolicatedThreads = threads.map((oldThread, threadIndex) => {
const symbolicationSteps = symbolicationStepsPerThread.get(threadIndex);
if (symbolicationSteps === undefined) {
return oldThread;
}
const { thread, oldFuncToNewFuncsMap } = applySymbolicationSteps(
oldThread,
symbolicationSteps
);
oldFuncToNewFuncsMaps.set(threadIndex, oldFuncToNewFuncsMap);
return thread;
});
const profile = getProfile(getState());
const { threads, shared, oldFuncToNewFuncsMap } = applySymbolicationSteps(
profile.threads,
profile.shared,
symbolicationSteps
);
dispatch({
type: 'BULK_SYMBOLICATION',
oldFuncToNewFuncsMaps,
symbolicatedThreads,
oldFuncToNewFuncsMap,
symbolicatedShared: shared,
symbolicatedThreads: threads,
});
};
}
Expand All @@ -832,12 +824,12 @@ if (typeof window === 'object' && window.requestIdleCallback) {
// Queues up symbolication steps and bulk-processes them from requestIdleCallback,
// in order to improve UI responsiveness during symbolication.
class SymbolicationStepQueue {
_updates: Map<ThreadIndex, SymbolicationStepInfo[]>;
_updates: SymbolicationStepInfo[];
_updateObservers: Array<() => void>;
_requestedUpdate: boolean;

constructor() {
this._updates = new Map();
this._updates = [];
this._updateObservers = [];
this._requestedUpdate = false;
}
Expand All @@ -855,7 +847,7 @@ class SymbolicationStepQueue {
_dispatchUpdate(dispatch) {
const updates = this._updates;
const observers = this._updateObservers;
this._updates = new Map();
this._updates = [];
this._updateObservers = [];
this._requestedUpdate = false;

Expand All @@ -868,17 +860,11 @@ class SymbolicationStepQueue {

enqueueSingleSymbolicationStep(
dispatch: Dispatch,
threadIndex: ThreadIndex,
symbolicationStepInfo: SymbolicationStepInfo,
completionHandler: () => void
) {
this._scheduleUpdate(dispatch);
let threadSteps = this._updates.get(threadIndex);
if (threadSteps === undefined) {
threadSteps = [];
this._updates.set(threadIndex, threadSteps);
}
threadSteps.push(symbolicationStepInfo);
this._updates.push(symbolicationStepInfo);
this._updateObservers.push(completionHandler);
}
}
Expand Down Expand Up @@ -1013,15 +999,11 @@ export async function doSymbolicateProfile(
await symbolicateProfile(
profile,
symbolStore,
(
threadIndex: ThreadIndex,
symbolicationStepInfo: SymbolicationStepInfo
) => {
(symbolicationStepInfo: SymbolicationStepInfo) => {
completionPromises.push(
new Promise((resolve) => {
_symbolicationStepQueueSingleton.enqueueSingleSymbolicationStep(
dispatch,
threadIndex,
symbolicationStepInfo,
resolve
);
Expand Down Expand Up @@ -1895,7 +1877,7 @@ export function changeTabFilter(tabID: TabID | null): ThunkAction<void> {
const thread = profile.threads[threadIndex];
const { samples, jsAllocations, nativeAllocations } = thread;
hasSamples = [samples, jsAllocations, nativeAllocations].some((table) =>
hasUsefulSamples(table?.stack, thread)
hasUsefulSamples(table?.stack, thread, profile.shared)
);
if (hasSamples) {
break;
Expand Down
2 changes: 1 addition & 1 deletion src/app-logic/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const GECKO_PROFILE_VERSION = 31;
// 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 = 55;
export const PROCESSED_PROFILE_VERSION = 57;

// The following are the margin sizes for the left and right of the timeline. Independent
// components need to share these values.
Expand Down
21 changes: 8 additions & 13 deletions src/app-logic/url-handling.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import type {
DataSource,
Pid,
Profile,
RawThread,
RawProfileSharedData,
IndexIntoStackTable,
TabID,
TrackIndex,
Expand Down Expand Up @@ -995,10 +995,6 @@ const _upgraders: {|
return;
}

// The transform stack is for the selected thread.
// At the time this upgrader was written, there was only one selected thread.
const thread = profile.threads[selectedThread];

for (let i = 0; i < transforms.length; i++) {
const transform = transforms[i];
if (
Expand All @@ -1018,7 +1014,7 @@ const _upgraders: {|
// To be correct, we would need to apply all previous transforms and find
// the right stack in the filtered thread.
const callNodeStackIndex = getStackIndexFromVersion3JSCallNodePath(
thread,
profile.shared,
transform.callNodePath
);
if (callNodeStackIndex === null) {
Expand All @@ -1027,7 +1023,7 @@ const _upgraders: {|
}
// This property is not writable, make it an "any"
(transform: any).callNodePath = getVersion4JSCallNodePathFromStackIndex(
thread,
profile.shared,
callNodeStackIndex
);
}
Expand Down Expand Up @@ -1224,8 +1220,7 @@ const _upgraders: {|
return;
}

const threadIndex = selectedThreads[0];
const funcTableLength = profile.threads[threadIndex].funcTable.length;
const funcTableLength = profile.shared.funcTable.length;

// cr-{implementation}-{resourceIndex}-{wrongFuncIndex}
// -> cr-{implementation}-{resourceIndex}-{correctFuncIndex}
Expand Down Expand Up @@ -1268,10 +1263,10 @@ for (const destVersionStr of Object.keys(_upgraders)) {
// This should only be used for the URL upgrader, typically this
// operation would use a call node index rather than a stack.
function getStackIndexFromVersion3JSCallNodePath(
thread: RawThread,
shared: RawProfileSharedData,
oldCallNodePath: CallNodePath
): IndexIntoStackTable | null {
const { stackTable, funcTable, frameTable } = thread;
const { stackTable, funcTable, frameTable } = shared;
const stackIndexDepth: Map<IndexIntoStackTable | null, number> = new Map();
stackIndexDepth.set(null, -1);

Expand Down Expand Up @@ -1314,10 +1309,10 @@ function getStackIndexFromVersion3JSCallNodePath(
// Constructs the new JS CallNodePath from given stackIndex and returns it.
// This should only be used for the URL upgrader.
function getVersion4JSCallNodePathFromStackIndex(
thread: RawThread,
shared: RawProfileSharedData,
stackIndex: IndexIntoStackTable
): CallNodePath {
const { funcTable, stackTable, frameTable } = thread;
const { funcTable, stackTable, frameTable } = shared;
const callNodePath = [];
let nextStackIndex = stackIndex;
while (nextStackIndex !== null) {
Expand Down
3 changes: 2 additions & 1 deletion src/components/js-tracer/Chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { JsTracerCanvas } from './Canvas';
import {
getCommittedRange,
getPreviewSelection,
getStringTable,
} from 'firefox-profiler/selectors/profile';
import { selectedThreadSelectors } from 'firefox-profiler/selectors/per-thread';
import { getSelectedThreadsKey } from 'firefox-profiler/selectors/url-state';
Expand Down Expand Up @@ -136,7 +137,7 @@ const JsTracerExpensiveChart = explicitConnect<
>({
mapStateToProps: (state, ownProps) => ({
timeRange: getCommittedRange(state),
stringTable: selectedThreadSelectors.getStringTable(state),
stringTable: getStringTable(state),
threadsKey: getSelectedThreadsKey(state),
previewSelection: getPreviewSelection(state),
jsTracerTimingRows: ensureExists(
Expand Down
32 changes: 17 additions & 15 deletions src/profile-logic/active-tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,20 @@ export function computeActiveTabTracks(
const screenshots = [];
const topmostInnerWindowIDs = getTopmostInnerWindowIDs(relevantPages);
const innerWindowIDToPageMap = _getInnerWindowIDToPageMap(relevantPages);
const { stringArray } = profile.shared;
const stringTable = StringTable.withBackingArray(stringArray);

const screenshotNameIndex = stringTable.indexForString(
'CompositorScreenshot'
);

for (
let threadIndex = 0;
threadIndex < profile.threads.length;
threadIndex++
) {
const thread = profile.threads[threadIndex];
const { markers, stringArray } = thread;
const stringTable = StringTable.withBackingArray(stringArray);
const { markers } = thread;

if (thread.isMainThread) {
// This is a main thread, there is a possibility that it can be a global
Expand Down Expand Up @@ -120,9 +125,6 @@ export function computeActiveTabTracks(

// Check for screenshots.
const windowIDs: Set<string> = new Set();
const screenshotNameIndex = stringTable.indexForString(
'CompositorScreenshot'
);
if (screenshotNameIndex !== -1) {
for (let markerIndex = 0; markerIndex < markers.length; markerIndex++) {
if (markers.name[markerIndex] === screenshotNameIndex) {
Expand Down Expand Up @@ -174,11 +176,12 @@ function isTopmostThread(
thread: RawThread,
topmostInnerWindowIDs: Set<InnerWindowID>
): boolean {
const { frameTable, markers } = thread;
for (let frameIndex = 0; frameIndex < frameTable.length; frameIndex++) {
const innerWindowID = frameTable.innerWindowID[frameIndex];
if (innerWindowID !== null && topmostInnerWindowIDs.has(innerWindowID)) {
return true;
const { usedInnerWindowIDs, markers } = thread;
if (usedInnerWindowIDs !== undefined) {
for (const innerWindowID of usedInnerWindowIDs) {
if (innerWindowID !== null && topmostInnerWindowIDs.has(innerWindowID)) {
return true;
}
}
}

Expand Down Expand Up @@ -209,14 +212,13 @@ function _getActiveTabResourceName(
thread: RawThread,
innerWindowIDToPageMap: Map<InnerWindowID, Page>
): string | null {
if (thread.isMainThread) {
const { isMainThread, usedInnerWindowIDs } = thread;
if (isMainThread && usedInnerWindowIDs !== undefined) {
// This is a sub-frame.
// Get the first innerWindowID inside the thread that's also present of innerWindowIDToPageMap.
let firstInnerWindowID = ensureExists(thread.frameTable.innerWindowID).find(
let firstInnerWindowID = usedInnerWindowIDs.find(
(innerWindowID) =>
innerWindowID &&
innerWindowID !== 0 &&
innerWindowIDToPageMap.has(innerWindowID)
innerWindowID !== 0 && innerWindowIDToPageMap.has(innerWindowID)
);
if (firstInnerWindowID === undefined || firstInnerWindowID === null) {
const markerData = thread.markers.data.find((data) => {
Expand Down
33 changes: 27 additions & 6 deletions src/profile-logic/data-structures.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {

import type {
RawThread,
RawProfileSharedData,
RawSamplesTable,
SamplesTable,
FrameTable,
Expand Down Expand Up @@ -73,6 +74,20 @@ export function getEmptyRawStackTable(): RawStackTable {
};
}

export function shallowCloneRawStackTable(
stackTable: RawStackTable
): RawStackTable {
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.
frame: stackTable.frame.slice(),
prefix: stackTable.prefix.slice(),
length: stackTable.length,
};
}

/**
* Returns an empty samples table with eventDelay field instead of responsiveness.
* eventDelay is a new field and it replaced responsiveness. We should still
Expand Down Expand Up @@ -395,12 +410,6 @@ export function getEmptyThread(overrides?: $Shape<RawThread>): RawThread {
// Creating samples with event delay since it's the new samples table.
samples: getEmptySamplesTableWithEventDelay(),
markers: getEmptyRawMarkerTable(),
stackTable: getEmptyRawStackTable(),
frameTable: getEmptyFrameTable(),
stringArray: [],
funcTable: getEmptyFuncTable(),
resourceTable: getEmptyResourceTable(),
nativeSymbols: getEmptyNativeSymbolTable(),
};

return {
Expand All @@ -409,6 +418,17 @@ export function getEmptyThread(overrides?: $Shape<RawThread>): RawThread {
};
}

export function getEmptySharedData(): RawProfileSharedData {
return {
stackTable: getEmptyRawStackTable(),
frameTable: getEmptyFrameTable(),
funcTable: getEmptyFuncTable(),
resourceTable: getEmptyResourceTable(),
nativeSymbols: getEmptyNativeSymbolTable(),
stringArray: [],
};
}

export function getEmptyProfile(): Profile {
return {
meta: {
Expand Down Expand Up @@ -436,6 +456,7 @@ export function getEmptyProfile(): Profile {
},
libs: [],
pages: [],
shared: getEmptySharedData(),
threads: [],
};
}
Expand Down
Loading