From e7e6611bff7a97d10e45116e4e21c0eadd7e14c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Fri, 13 Feb 2026 12:15:51 +0100 Subject: [PATCH 1/2] Make the source table non-optional in the gecko profile format This patch changes the gecko profiler format to always include source table. The backend patch also increases the gecko profile format version. Backend bug: https://bugzilla.mozilla.org/show_bug.cgi?id=2016666 This PR has to be deployed before we can land the backend. --- docs-developer/CHANGELOG-formats.md | 4 +++ src/app-logic/constants.ts | 2 +- src/profile-logic/gecko-profile-versioning.ts | 23 +++++++++++++ src/profile-logic/process-profile.ts | 6 ++-- src/test/fixtures/profiles/gecko-profile.ts | 15 ++++++++ .../__snapshots__/profile-view.test.ts.snap | 2 +- .../profile-conversion.test.ts.snap | 32 ++++++++--------- .../profile-upgrading.test.ts.snap | 34 +++++++++++++++++-- src/test/unit/process-profile.test.ts | 3 +- src/types/gecko-profile.ts | 3 +- 10 files changed, 97 insertions(+), 27 deletions(-) diff --git a/docs-developer/CHANGELOG-formats.md b/docs-developer/CHANGELOG-formats.md index 551f33a9a9..f09d82f84a 100644 --- a/docs-developer/CHANGELOG-formats.md +++ b/docs-developer/CHANGELOG-formats.md @@ -132,6 +132,10 @@ Older versions are not documented in this changelog but can be found in [process ## Gecko profile format +### Version 33 + +The `sources` field in the Gecko profile format is now non-optional. An upgrader was added that creates an empty `SourceTable` for profiles that don't have one. + ### Version 32 `frameTable` `location` string field was changed to include an optional `sourceIndex` at the end of the string inside brackets for JS sources. For example, new JS frames look like this: `functionName (http://script.url/:1234:1234)[1234]` with the last number being its `sourceIndex`. This index references entries in the shared `SourceTable` in `profile.sources` (added in in the same version) which centralizes all source file information. diff --git a/src/app-logic/constants.ts b/src/app-logic/constants.ts index 4679913070..7e24498c22 100644 --- a/src/app-logic/constants.ts +++ b/src/app-logic/constants.ts @@ -7,7 +7,7 @@ import type { MarkerPhase } from 'firefox-profiler/types'; // The current version of the Gecko profile format. // Please don't forget to update the gecko profile format changelog in // `docs-developer/CHANGELOG-formats.md`. -export const GECKO_PROFILE_VERSION = 32; +export const GECKO_PROFILE_VERSION = 33; // The current version of the "processed" profile format. // Please don't forget to update the processed profile format changelog in diff --git a/src/profile-logic/gecko-profile-versioning.ts b/src/profile-logic/gecko-profile-versioning.ts index d6987c52af..2f8d81bc9a 100644 --- a/src/profile-logic/gecko-profile-versioning.ts +++ b/src/profile-logic/gecko-profile-versioning.ts @@ -1498,6 +1498,29 @@ const _upgraders: { // within the existing location string. // - The source ID is optional, so v31 profiles are valid v32 profiles. }, + [33]: (profile: any) => { + // Make SourceTable non-optional by adding an empty one if it doesn't exist. + // The sources table was added as optional in version 32, but we now require + // it to always be present to simplify the code that uses it and support + // source maps in the future. + // Source IDs are still optional in the frameTable location strings. + function convertToVersion33Recursive(p: any) { + if (!p.sources) { + p.sources = { + schema: { + uuid: 0, + filename: 1, + }, + data: [], + }; + } + + for (const subprocessProfile of p.processes) { + convertToVersion33Recursive(subprocessProfile); + } + } + convertToVersion33Recursive(profile); + }, // If you add a new upgrader here, please document the change in // `docs-developer/CHANGELOG-formats.md`. diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index 755c639813..56cd59182f 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -196,7 +196,7 @@ type ExtractionInfo = { { funcIndex: IndexIntoFuncTable; frameAddress: Address | null } >; globalDataCollector: GlobalDataCollector; - geckoSourceTable: GeckoSourceTable | undefined; + geckoSourceTable: GeckoSourceTable; }; /** @@ -214,7 +214,7 @@ export function extractFuncsAndResourcesFromFrameLocations( libs: LibMapping[], extensions: ExtensionTable = getEmptyExtensions(), globalDataCollector: GlobalDataCollector, - geckoSourceTable: GeckoSourceTable | undefined + geckoSourceTable: GeckoSourceTable ): { funcTable: FuncTable; resourceTable: ResourceTable; @@ -539,7 +539,7 @@ function _extractJsFunction( if (sourceIndex !== undefined) { const geckoSourceIdx = parseInt(sourceIndex, 10); // Look up the UUID for this source index from the process's sources table - if (geckoSourceTable && geckoSourceIdx < geckoSourceTable.data.length) { + if (geckoSourceIdx < geckoSourceTable.data.length) { const uuidIndex = geckoSourceTable.schema.uuid; const filenameIndex = geckoSourceTable.schema.filename; const uuid = geckoSourceTable.data[geckoSourceIdx][uuidIndex]; diff --git a/src/test/fixtures/profiles/gecko-profile.ts b/src/test/fixtures/profiles/gecko-profile.ts index c1d9b57219..eff7779cbb 100644 --- a/src/test/fixtures/profiles/gecko-profile.ts +++ b/src/test/fixtures/profiles/gecko-profile.ts @@ -21,6 +21,7 @@ import type { GeckoMarkerTuple, VisualMetrics, Nanoseconds, + GeckoSourceTable, } from 'firefox-profiler/types'; import { @@ -45,6 +46,16 @@ function getEmptyMarkers(): GeckoMarkers { }; } +export function getEmptySourceTable(): GeckoSourceTable { + return { + schema: { + uuid: 0 as const, + filename: 1 as const, + }, + data: [], + }; +} + export function createGeckoMarkerStack({ stackIndex, time, @@ -111,6 +122,7 @@ export function createGeckoSubprocessProfile( meta: contentProcessMeta, pausedRanges: [], libs: [contentProcessBinary, ...parentProfile.libs.slice(1)], // libs are stringified in the Gecko profile + sources: getEmptySourceTable(), pages: [ { tabID: 123123, @@ -315,6 +327,7 @@ export function createGeckoProfile(): GeckoProfile { meta: parentProcessMeta, libs: [parentProcessBinary].concat(extraBinaries), pages: parentProcessPages, + sources: getEmptySourceTable(), counters: parentProcessCounters, profilerOverhead: parentProcessOverhead, pausedRanges: [], @@ -384,6 +397,7 @@ export function createGeckoProfileWithMarkers( meta: geckoProfile.meta, libs: geckoProfile.libs, pages: geckoProfile.pages, + sources: geckoProfile.sources, pausedRanges: [], threads: [_createGeckoThreadWithMarkers(markers)], processes: [], @@ -950,6 +964,7 @@ export function createGeckoProfileWithJsTimings(): GeckoProfile { meta: geckoProfile.meta, libs: geckoProfile.libs, pages: geckoProfile.pages, + sources: geckoProfile.sources, pausedRanges: [], threads: [ _createGeckoThreadWithJsTimings('GeckoMain'), diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index 585ec17655..9a12a5e6a9 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -426,7 +426,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [ Object { diff --git a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap index d0b166b1e9..c154ff2243 100644 --- a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap @@ -604,7 +604,7 @@ Object { "symbolicated": true, "toolkit": undefined, "updateChannel": undefined, - "version": 32, + "version": 33, "visualMetrics": undefined, }, "pages": Array [], @@ -85577,7 +85577,7 @@ Object { "symbolicated": true, "toolkit": undefined, "updateChannel": undefined, - "version": 32, + "version": 33, "visualMetrics": undefined, }, "pages": Array [], @@ -334708,7 +334708,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { @@ -370190,7 +370190,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { @@ -405651,7 +405651,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { @@ -408280,7 +408280,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { @@ -410641,7 +410641,7 @@ Object { "startTime": 1700159839203.051, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { @@ -414171,7 +414171,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { @@ -419436,7 +419436,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { @@ -420466,7 +420466,7 @@ Object { "symbolicated": true, "toolkit": undefined, "updateChannel": undefined, - "version": 32, + "version": 33, "visualMetrics": undefined, }, "pages": Array [], @@ -426405,7 +426405,7 @@ Object { "symbolicated": true, "toolkit": undefined, "updateChannel": undefined, - "version": 32, + "version": 33, "visualMetrics": undefined, }, "pages": Array [], @@ -441300,7 +441300,7 @@ Object { "symbolicated": true, "toolkit": undefined, "updateChannel": undefined, - "version": 32, + "version": 33, "visualMetrics": undefined, }, "pages": Array [], @@ -447504,7 +447504,7 @@ Object { "symbolicated": true, "toolkit": undefined, "updateChannel": undefined, - "version": 32, + "version": 33, "visualMetrics": undefined, }, "pages": Array [], @@ -606878,7 +606878,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { @@ -630473,7 +630473,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { @@ -938356,7 +938356,7 @@ Object { "startTime": 0, "symbolicated": true, "toolkit": "", - "version": 32, + "version": 33, }, "pages": Array [], "shared": Object { diff --git a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap index b45e81d8d4..4dc2b9cdae 100644 --- a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap @@ -53,7 +53,7 @@ Object { "symbolicated": true, "toolkit": undefined, "updateChannel": undefined, - "version": 32, + "version": 33, "visualMetrics": undefined, }, "pages": Array [], @@ -6389,7 +6389,7 @@ Object { "stackwalk": 1, "startTime": 1460221352723.438, "toolkit": "cocoa", - "version": 32, + "version": 33, }, "pausedRanges": Array [], "processes": Array [ @@ -6501,6 +6501,13 @@ Object { }, "pausedRanges": Array [], "processes": Array [], + "sources": Object { + "data": Array [], + "schema": Object { + "filename": 1, + "uuid": 0, + }, + }, "threads": Array [ Object { "frameTable": Object { @@ -6783,6 +6790,13 @@ Object { ], }, ], + "sources": Object { + "data": Array [], + "schema": Object { + "filename": 1, + "uuid": 0, + }, + }, "threads": Array [ Object { "frameTable": Object { @@ -7845,7 +7859,7 @@ Object { "stackwalk": 1, "startTime": 1460221352723.438, "toolkit": "cocoa", - "version": 32, + "version": 33, }, "pages": Array [ Object { @@ -7985,6 +7999,13 @@ Object { ], "pausedRanges": Array [], "processes": Array [], + "sources": Object { + "data": Array [], + "schema": Object { + "filename": 1, + "uuid": 0, + }, + }, "threads": Array [ Object { "frameTable": Object { @@ -8349,6 +8370,13 @@ Object { ], }, ], + "sources": Object { + "data": Array [], + "schema": Object { + "filename": 1, + "uuid": 0, + }, + }, "threads": Array [ Object { "frameTable": Object { diff --git a/src/test/unit/process-profile.test.ts b/src/test/unit/process-profile.test.ts index a10b4dd7ae..ce3781b616 100644 --- a/src/test/unit/process-profile.test.ts +++ b/src/test/unit/process-profile.test.ts @@ -15,6 +15,7 @@ import { createGeckoCounter, createGeckoMarkerStack, createGeckoProfilerOverhead, + getEmptySourceTable, getVisualMetrics, } from '../fixtures/profiles/gecko-profile'; import { ensureExists } from '../../utils/types'; @@ -113,7 +114,7 @@ describe('extract functions and resource from location strings', function () { libs, extensions, globalDataCollector, - undefined + getEmptySourceTable() ); const { diff --git a/src/types/gecko-profile.ts b/src/types/gecko-profile.ts index 30492c4d64..9fd5521206 100644 --- a/src/types/gecko-profile.ts +++ b/src/types/gecko-profile.ts @@ -606,8 +606,7 @@ export type GeckoProfileWithMeta = { meta: Meta; libs: LibMapping[]; pages?: PageList; - // Optional because older Firefox versions may not have this table. - sources?: GeckoSourceTable; + sources: GeckoSourceTable; threads: GeckoThread[]; pausedRanges: PausedRange[]; processes: GeckoSubprocessProfile[]; From 37b31b05ba11d0f59c94a5b935e78478ffb1d54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 16 Feb 2026 13:24:50 +0100 Subject: [PATCH 2/2] Fix some typos in the CHANGELOG-formats.md file --- docs-developer/CHANGELOG-formats.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs-developer/CHANGELOG-formats.md b/docs-developer/CHANGELOG-formats.md index f09d82f84a..10e1ec1e55 100644 --- a/docs-developer/CHANGELOG-formats.md +++ b/docs-developer/CHANGELOG-formats.md @@ -128,7 +128,7 @@ We've also cleaned up the ResourceTable format: ### Older Versions -Older versions are not documented in this changelog but can be found in [processed-profile-versioning.jt](../src/profile-logic/processed-profile-versioning.ts). +Older versions are not documented in this changelog but can be found in [processed-profile-versioning.ts](../src/profile-logic/processed-profile-versioning.ts). ## Gecko profile format @@ -138,7 +138,7 @@ The `sources` field in the Gecko profile format is now non-optional. An upgrader ### Version 32 -`frameTable` `location` string field was changed to include an optional `sourceIndex` at the end of the string inside brackets for JS sources. For example, new JS frames look like this: `functionName (http://script.url/:1234:1234)[1234]` with the last number being its `sourceIndex`. This index references entries in the shared `SourceTable` in `profile.sources` (added in in the same version) which centralizes all source file information. +`frameTable` `location` string field was changed to include an optional `sourceIndex` at the end of the string inside brackets for JS sources. For example, new JS frames look like this: `functionName (http://script.url/:1234:1234)[1234]` with the last number being its `sourceIndex`. This index references entries in the shared `SourceTable` in `profile.sources` (added in the same version) which centralizes all source file information. ### Version 31 @@ -167,4 +167,4 @@ The `searchable` property is implemented in the marker schema. Previously all th ### Older Versions -Older versions are not documented in this changelog but can be found in [gecko-profile-versioning.jt](../src/profile-logic/gecko-profile-versioning.ts). +Older versions are not documented in this changelog but can be found in [gecko-profile-versioning.ts](../src/profile-logic/gecko-profile-versioning.ts).