commit b20332d9188e3380d327f4f226b564a3367f4029
parent 2d763ae17a22d3c3ced19e000ae2219015c47900
Author: Florian Quèze <florian@queze.net>
Date: Tue, 2 Dec 2025 06:54:05 +0000
Bug 2003359 - include the bugzilla components of each test in the xpcshell-date.json files, r=jmaher.
Differential Revision: https://phabricator.services.mozilla.com/D274638
Diffstat:
2 files changed, 93 insertions(+), 2 deletions(-)
diff --git a/testing/timings/JSON_FORMAT.md b/testing/timings/JSON_FORMAT.md
@@ -95,6 +95,12 @@ String tables for efficient storage. All strings are deduplicated and stored onc
"mozilla::dom::Something::Crash",
"EMPTY: no crashing thread identified",
...
+ ],
+ "components": [ // Bugzilla components (Product :: Component format)
+ "Core :: Storage: IndexedDB",
+ "Testing :: XPCShell Harness",
+ "Firefox :: General",
+ ...
]
}
```
@@ -120,12 +126,13 @@ const jobName = tables.jobNames[taskInfo.jobNameIds[taskIdId]]; // "te
### testInfo
-Maps test IDs to their test paths and names. These are parallel arrays indexed by `testId`:
+Maps test IDs to their test paths, names, and components. These are parallel arrays indexed by `testId`:
```json
{
"testPathIds": [0, 0, 1, 2, ...], // Index into tables.testPaths
- "testNameIds": [0, 1, 2, 3, ...] // Index into tables.testNames
+ "testNameIds": [0, 1, 2, 3, ...], // Index into tables.testNames
+ "componentIds": [5, 5, 12, null, ...] // Index into tables.components (null if unknown)
}
```
@@ -135,6 +142,8 @@ const testId = 10;
const testPath = tables.testPaths[testInfo.testPathIds[testId]]; // "dom/indexedDB/test/unit"
const testName = tables.testNames[testInfo.testNameIds[testId]]; // "test_foo.js"
const fullPath = testPath ? `${testPath}/${testName}` : testName;
+const componentId = testInfo.componentIds[testId];
+const component = componentId !== null ? tables.components[componentId] : "Unknown"; // "Core :: Storage: IndexedDB"
```
### testRuns
@@ -385,4 +394,6 @@ Dates are sorted in descending order (newest first).
- **Task ID formats differ between files:**
- Test timing data: Always includes retry suffix (e.g., `"YJJe4a0CRIqbAmcCo8n63w.0"`)
- Resource usage data: Omits `.0` for retry 0 (e.g., `"YJJe4a0CRIqbAmcCo8n63w"`), includes suffix for retries > 0 (e.g., `"YJJe4a0CRIqbAmcCo8n63w.1"`)
+- **Component mapping:** Components are fetched from the TaskCluster index `gecko.v2.mozilla-central.latest.source.source-bugzilla-info` and mapped to test paths. The component ID in `testInfo.componentIds` may be `null` if the test path is not found in the mapping
+- Components are formatted as `"Product :: Component"` (e.g., `"Core :: Storage: IndexedDB"`)
- The data structure is optimized for sequential access patterns used by the dashboards
diff --git a/testing/timings/fetch-xpcshell-data.js b/testing/timings/fetch-xpcshell-data.js
@@ -29,6 +29,7 @@ const PROFILE_CACHE_DIR = "./profile-cache";
let previousRunData = null;
let allJobsCache = null;
+let componentsData = null;
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
@@ -271,6 +272,62 @@ async function processJobsWithWorkers(jobs, targetDate = null) {
});
}
+// Fetch Bugzilla component mapping data
+async function fetchComponentsData() {
+ if (componentsData) {
+ return componentsData;
+ }
+
+ console.log("Fetching Bugzilla component mapping...");
+ const url = `${TASKCLUSTER_BASE_URL}/api/index/v1/task/gecko.v2.mozilla-central.latest.source.source-bugzilla-info/artifacts/public/components-normalized.json`;
+
+ try {
+ componentsData = await fetchJson(url);
+ console.log("Component mapping loaded successfully");
+ return componentsData;
+ } catch (error) {
+ console.error("Failed to fetch component mapping:", error);
+ return null;
+ }
+}
+
+// Look up component for a test path
+function findComponentForPath(testPath) {
+ if (!componentsData || !componentsData.paths) {
+ return null;
+ }
+
+ const parts = testPath.split("/");
+ let current = componentsData.paths;
+
+ for (const part of parts) {
+ if (typeof current === "number") {
+ return current;
+ }
+ if (typeof current === "object" && current !== null && part in current) {
+ current = current[part];
+ } else {
+ return null;
+ }
+ }
+
+ return typeof current === "number" ? current : null;
+}
+
+// Get component string from component ID
+function getComponentString(componentId) {
+ if (!componentsData || !componentsData.components || componentId == null) {
+ return null;
+ }
+
+ const component = componentsData.components[String(componentId)];
+ if (!component || !Array.isArray(component) || component.length !== 2) {
+ return null;
+ }
+
+ return `${component[0]} :: ${component[1]}`;
+}
+
// Helper function to determine if a status should include message data
function shouldIncludeMessage(status) {
return status === "SKIP" || status.startsWith("FAIL");
@@ -287,6 +344,7 @@ function createDataTables(jobResults) {
taskIds: [],
messages: [],
crashSignatures: [],
+ components: [],
};
// Maps for O(1) string lookups
@@ -299,6 +357,7 @@ function createDataTables(jobResults) {
taskIds: new Map(),
messages: new Map(),
crashSignatures: new Map(),
+ components: new Map(),
};
// Task info maps task ID index to repository and job name indexes
@@ -311,6 +370,7 @@ function createDataTables(jobResults) {
const testInfo = {
testPathIds: [],
testNameIds: [],
+ componentIds: [],
};
// Map for fast testId lookup: fullPath -> testId
@@ -363,9 +423,17 @@ function createDataTables(jobResults) {
const testPathId = findStringIndex("testPaths", testPath);
const testNameId = findStringIndex("testNames", testName);
+ // Look up the component for this test
+ const componentIdRaw = findComponentForPath(fullPath);
+ const componentString = getComponentString(componentIdRaw);
+ const componentId = componentString
+ ? findStringIndex("components", componentString)
+ : null;
+
testId = testInfo.testPathIds.length;
testInfo.testPathIds.push(testPathId);
testInfo.testNameIds.push(testNameId);
+ testInfo.componentIds.push(componentId);
testIdMap.set(fullPath, testId);
}
@@ -450,6 +518,7 @@ function sortStringTablesByFrequency(dataStructure) {
taskIds: new Array(tables.taskIds.length).fill(0),
messages: new Array(tables.messages.length).fill(0),
crashSignatures: new Array(tables.crashSignatures.length).fill(0),
+ components: new Array(tables.components.length).fill(0),
};
// Count taskInfo references
@@ -471,6 +540,11 @@ function sortStringTablesByFrequency(dataStructure) {
for (const testNameId of testInfo.testNameIds) {
frequencyCounts.testNames[testNameId]++;
}
+ for (const componentId of testInfo.componentIds) {
+ if (componentId !== null) {
+ frequencyCounts.components[componentId]++;
+ }
+ }
// Count testRuns references
for (const testGroup of testRuns) {
@@ -566,6 +640,9 @@ function sortStringTablesByFrequency(dataStructure) {
testNameIds: testInfo.testNameIds.map(oldId =>
indexMaps.testNames.get(oldId)
),
+ componentIds: testInfo.componentIds.map(oldId =>
+ oldId === null ? null : indexMaps.components.get(oldId)
+ ),
};
// Remap testRuns indices
@@ -1141,6 +1218,9 @@ async function main() {
await fetchPreviousRunData();
}
+ // Fetch component mapping data
+ await fetchComponentsData();
+
// Check for --revision parameter (format: project:revision)
const revisionIndex = process.argv.findIndex(arg => arg === "--revision");
if (revisionIndex !== -1 && revisionIndex + 1 < process.argv.length) {