Skip to content
Open
5 changes: 5 additions & 0 deletions docs-developer/CHANGELOG-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ Note that this is not an exhaustive list. Processed profile format upgraders can

## Processed profile format

### Version 60

The following tables have moved into `profile.shared`: `stackTable`, `frameTable`, `funcTable`, `resourceTable`, `nativeSymbols`. They are no longer per-thread.
Marker and sample data (including allocation samples) remains within the thread.

### Version 59

A new optional `usedInnerWindowIDs` field was added to the `Thread` type. This field contains an array of inner window IDs. It is used for the tab selector dropdown in the profiler UI, together with the information from `profile.pages`. When a tab is selected in this dropdown, threads that don't have an inner window ID for the selected tab in their `usedInnerWindowIDs` field are hidden. The array is treated as a set - the order of items in it has no meaning.
Expand Down
6 changes: 4 additions & 2 deletions src/actions/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import type {
BrowserConnection,
BrowserConnectionStatus,
} from 'firefox-profiler/app-logic/browser-connection';
import type { ProfileUpgradeInfo } from 'firefox-profiler/profile-logic/processed-profile-versioning';

export function changeSelectedTab(selectedTab: TabSlug): ThunkAction<void> {
return (dispatch, getState) => {
Expand Down Expand Up @@ -129,12 +130,13 @@ export function setHasZoomedViaMousewheel() {
export function setupInitialUrlState(
location: Location,
profile: Profile | null,
browserConnection: BrowserConnection | null
browserConnection: BrowserConnection | null,
profileUpgradeInfo?: ProfileUpgradeInfo
): ThunkAction<void> {
return (dispatch) => {
let urlState;
try {
urlState = stateFromLocation(location, profile);
urlState = stateFromLocation(location, profile, profileUpgradeInfo);
} catch (e) {
if (e.name === 'UrlUpgradeError') {
// The error is an URL upgrade error, let's fire a fatal error.
Expand Down
4 changes: 2 additions & 2 deletions src/actions/profile-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getLocalTracksByPid,
getThreads,
getLastNonShiftClick,
getReservedFunctionsForResources,
} from 'firefox-profiler/selectors/profile';
import {
getThreadSelectors,
Expand Down Expand Up @@ -1829,9 +1830,8 @@ export function addCollapseResourceTransformToStack(
implementation: ImplementationFilter
): ThunkAction<void> {
return (dispatch, getState) => {
const threadSelectors = getThreadSelectorsFromThreadsKey(threadsKey);
const reservedFunctionsForResources =
threadSelectors.getReservedFunctionsForResources(getState());
getReservedFunctionsForResources(getState());
const collapsedFuncIndex = ensureExists(
ensureExists(reservedFunctionsForResources).get(resourceIndex)
);
Expand Down
14 changes: 7 additions & 7 deletions src/actions/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import type {
ThunkAction,
CheckedSharingOptions,
StartEndRange,
ThreadIndex,
State,
Profile,
ProfileIndexTranslationMaps,
} from 'firefox-profiler/types';
import { compress } from 'firefox-profiler/utils/gz';
import { serializeProfile } from 'firefox-profiler/profile-logic/process-profile';
Expand Down Expand Up @@ -160,14 +160,14 @@ async function persistJustUploadedProfileInformationToDb(
if (removeProfileInformation) {
// In case you wonder, committedRanges is either an empty array (if the
// range was sanitized) or `null` (otherwise).
const { committedRanges, oldThreadIndexToNew } = sanitizedInformation;
const { committedRanges, translationMaps } = sanitizedInformation;

// Predicts the URL we'll have after local sanitization.
predictedUrl = urlPredictor(
profileSanitized(
profileToken,
committedRanges,
oldThreadIndexToNew,
translationMaps,
profileName,
null /* prepublished State */
)
Expand Down Expand Up @@ -411,7 +411,7 @@ export function attemptToPublish(
const removeProfileInformation =
getRemoveProfileInformation(prePublishedState);
if (removeProfileInformation) {
const { committedRanges, oldThreadIndexToNew, profile } =
const { committedRanges, translationMaps, profile } =
sanitizedInformation;
// Hide the old UI gracefully.
await dispatch(hideStaleProfile());
Expand All @@ -421,7 +421,7 @@ export function attemptToPublish(
profileSanitized(
hash,
committedRanges,
oldThreadIndexToNew,
translationMaps,
profileName,
prePublishedState
)
Expand Down Expand Up @@ -508,15 +508,15 @@ export function resetUploadState(): Action {
export function profileSanitized(
hash: string,
committedRanges: StartEndRange[] | null,
oldThreadIndexToNew: Map<ThreadIndex, ThreadIndex> | null,
translationMaps: ProfileIndexTranslationMaps | null,
profileName: string,
prePublishedState: State | null
): Action {
return {
type: 'SANITIZED_PROFILE_PUBLISHED',
hash,
committedRanges,
oldThreadIndexToNew,
translationMaps,
profileName,
prePublishedState,
};
Expand Down
85 changes: 39 additions & 46 deletions src/actions/receive-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,7 @@ import type {
MixedObject,
} from 'firefox-profiler/types';

import type {
FuncToFuncsMap,
SymbolicationStepInfo,
} from '../profile-logic/symbolication';
import type { SymbolicationStepInfo } from '../profile-logic/symbolication';
import { assertExhaustiveCheck } from '../utils/types';
import { bytesToBase64DataUrl } from 'firefox-profiler/utils/base64';
import type {
Expand All @@ -105,6 +102,7 @@ import type {
} from '../profile-logic/symbol-store';
import type { SymbolTableAsTuple } from 'firefox-profiler/profile-logic/symbol-store-db';
import SymbolStoreDB from 'firefox-profiler/profile-logic/symbol-store-db';
import type { ProfileUpgradeInfo } from 'firefox-profiler/profile-logic/processed-profile-versioning';

/**
* This file collects all the actions that are used for receiving the profile in the
Expand Down Expand Up @@ -347,7 +345,7 @@ export function finalizeFullProfileView(
const thread = profile.threads[threadIndex];
const { samples, jsAllocations, nativeAllocations } = thread;
hasSamples = [samples, jsAllocations, nativeAllocations].some((table) =>
hasUsefulSamples(table?.stack, thread, profile.shared)
hasUsefulSamples(table?.stack, profile.shared)
);
if (hasSamples) {
break;
Expand Down Expand Up @@ -456,28 +454,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, shared } = 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,
shared,
symbolicationSteps
);
oldFuncToNewFuncsMaps.set(threadIndex, oldFuncToNewFuncsMap);
return thread;
});
const {
threads: symbolicatedThreads,
shared: symbolicatedShared,
oldFuncToNewFuncsMap,
} = applySymbolicationSteps(threads, shared, symbolicationSteps);
dispatch({
type: 'BULK_SYMBOLICATION',
oldFuncToNewFuncsMaps,
oldFuncToNewFuncsMap,
symbolicatedThreads,
symbolicatedShared,
});
};
}
Expand All @@ -499,12 +489,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 @@ -522,7 +512,7 @@ class SymbolicationStepQueue {
_dispatchUpdate(dispatch: Dispatch) {
const updates = this._updates;
const observers = this._updateObservers;
this._updates = new Map();
this._updates = [];
this._updateObservers = [];
this._requestedUpdate = false;

Expand All @@ -535,17 +525,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 @@ -805,15 +789,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(undefined)
);
Expand Down Expand Up @@ -1265,9 +1245,12 @@ export function getProfileUrlForHash(hash: string): string {

export function retrieveProfileFromStore(
hash: string,
initialLoad: boolean = false
profileUpgradeInfo?: ProfileUpgradeInfo
): ThunkAction<Promise<void>> {
return retrieveProfileOrZipFromUrl(getProfileUrlForHash(hash), initialLoad);
return retrieveProfileOrZipFromUrl(
getProfileUrlForHash(hash),
profileUpgradeInfo
);
}

/**
Expand All @@ -1277,7 +1260,7 @@ export function retrieveProfileFromStore(
*/
export function retrieveProfileOrZipFromUrl(
profileUrl: string,
initialLoad: boolean = false
profileUpgradeInfo?: ProfileUpgradeInfo
): ThunkAction<Promise<void>> {
return async function (dispatch) {
dispatch(waitingForProfileFromUrl(profileUrl));
Expand All @@ -1295,12 +1278,14 @@ export function retrieveProfileOrZipFromUrl(
const serializedProfile = response.profile;
const profile = await unserializeProfileOfArbitraryFormat(
serializedProfile,
profileUrl
profileUrl,
profileUpgradeInfo
);
if (profile === undefined) {
throw new Error('Unable to parse the profile.');
}

const initialLoad = profileUpgradeInfo !== undefined;
await dispatch(loadProfile(profile, {}, initialLoad));
break;
}
Expand Down Expand Up @@ -1522,7 +1507,8 @@ export function retrieveProfilesToCompare(
// the url and processing the UrlState.
export function retrieveProfileForRawUrl(
location: Location,
browserConnectionStatus?: BrowserConnectionStatus
browserConnectionStatus?: BrowserConnectionStatus,
profileUpgradeInfo: ProfileUpgradeInfo = {}
): ThunkAction<Promise<Profile | null>> {
return async (dispatch, getState) => {
const pathParts = location.pathname.split('/').filter((d) => d);
Expand All @@ -1543,22 +1529,29 @@ export function retrieveProfileForRawUrl(
dispatch(setDataSource(dataSource));

switch (dataSource) {
case 'from-browser':
case 'from-browser': {
if (browserConnectionStatus === undefined) {
throw new Error(
'Error: all callers of this function should supply a browserConnectionStatus argument for from-browser'
);
}
const initialLoad = profileUpgradeInfo !== undefined;
await dispatch(
retrieveProfileFromBrowser(browserConnectionStatus, true)
retrieveProfileFromBrowser(browserConnectionStatus, initialLoad)
);
break;
}
case 'public':
await dispatch(retrieveProfileFromStore(pathParts[1], true));
await dispatch(
retrieveProfileFromStore(pathParts[1], profileUpgradeInfo)
);
break;
case 'from-url':
await dispatch(
retrieveProfileOrZipFromUrl(decodeURIComponent(pathParts[1]), true)
retrieveProfileOrZipFromUrl(
decodeURIComponent(pathParts[1]),
profileUpgradeInfo
)
);
break;
case 'compare': {
Expand Down Expand Up @@ -1709,7 +1702,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, profile.shared)
hasUsefulSamples(table?.stack, profile.shared)
);
if (hasSamples) {
break;
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 = 32;
// 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 = 59;
export const PROCESSED_PROFILE_VERSION = 60;

// The following are the margin sizes for the left and right of the timeline. Independent
// components need to share these values.
Expand Down
Loading