assets/ml.js (437 lines of code) (raw):
const platformMapping = {
macOS: "macosx1015-64-shippable-qr",
linuxHPE: "linux1804-64-shippable",
windows11Ref: "windows11-64-24h2-hw-ref-shippable",
windows11: "windows11-64-24h2-shippable"
};
const platformDescriptions = {
windows11Ref: "Dell OptiPlex 7080, Intel Core i7-10700, 16GB RAM, 512GB SSD.",
windows11: "HP EliteBook 850 G7, Intel Core i5-10210U, 8GB RAM, 256GB SSD.",
linuxHPE: "HPE Moonshot m710x, Intel Xeon D-1587, 32GB RAM, 480GB SSD.",
macOS: "Apple Mac Mini R8, 8GB RAM, 256GB SSD."
};
// Keep track of existing charts
const charts = {};
function clearChart(ctx) {
if (charts[ctx.id]) {
charts[ctx.id].destroy(); // Destroy the existing chart
delete charts[ctx.id]; // Remove reference
// Fully reset the canvas to prevent Chart.js from reusing old bindings
const parent = ctx.parentNode;
const newCanvas = document.createElement("canvas");
newCanvas.id = ctx.id;
parent.replaceChild(newCanvas, ctx);
return newCanvas; // Return the new canvas element
}
return ctx; // If no chart existed, return the original canvas
}
function createChart(ctx, dataPoints, unit) {
ctx = clearChart(ctx);
charts[ctx.id] = new Chart(ctx, {
type: "bar",
data: {
labels: dataPoints.map((point) => point.label),
datasets: [
{
data: dataPoints.map((point) => point.value),
backgroundColor: ctx.id.includes("ram")
? "rgba(255, 99, 132, 0.6)"
: "rgba(54, 162, 235, 0.6)"
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: { y: { beginAtZero: true } },
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(tooltipItem) {
return tooltipItem.raw + " " + unit;
}
}
}
}
}
});
}
function createLineChart(ctx, dataPoints, unit) {
ctx = clearChart(ctx);
const dates = dataPoints.map((point) => new Date(point.date));
charts[ctx.id] = new Chart(ctx, {
type: "line",
data: {
labels: dates.map((date) =>
date.toLocaleDateString("en-US", { day: "numeric", month: "short" })
),
datasets: [
{
data: dataPoints.map((point) => point.value),
borderColor: ctx.id.includes("ram")
? "rgba(255, 99, 132, 1)"
: "rgba(54, 162, 235, 1)",
backgroundColor: ctx.id.includes("ram")
? "rgba(255, 99, 132, 0.2)"
: "rgba(54, 162, 235, 0.2)",
fill: true,
tension: 0.3
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: "category",
ticks: { autoSkip: true, maxTicksLimit: 10 }
},
y: { beginAtZero: true }
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(tooltipItem) {
return tooltipItem.raw + " " + unit;
}
}
}
}
}
});
}
function createTwoLineChart(ctx, dataRows, unit) {
ctx = clearChart(ctx);
// For the x-axis labels, we'll convert the date string into something readable
const labels = dataRows.map(row => {
const d = new Date(row.date);
// e.g. "26 Mar"
return d.toLocaleDateString("en-US", { day: "numeric", month: "short" });
});
// Prepare the two data arrays:
// 1) Engine Creation p50
// 2) Inference p50
const engineCreationData = dataRows.map(row => row.engine_creation_p50);
const inferenceData = dataRows.map(row => row.inference_p50);
charts[ctx.id] = new Chart(ctx, {
type: "line",
data: {
labels,
datasets: [
{
label: "Engine Creation p50 (ms)",
data: engineCreationData,
borderColor: "rgba(54, 162, 235, 1)",
backgroundColor: "rgba(54, 162, 235, 0.2)",
fill: true,
tension: 0.3
},
{
label: "Inference p50 (ms)",
data: inferenceData,
borderColor: "rgba(255, 99, 132, 1)",
backgroundColor: "rgba(255, 99, 132, 0.2)",
fill: true,
tension: 0.3
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: "category",
ticks: {
autoSkip: true,
maxTicksLimit: 10
}
},
y: {
beginAtZero: true
}
},
plugins: {
legend: {
display: true // Show which line is which
},
tooltip: {
callbacks: {
// Show e.g. "1.17 ms" in the tooltip
label: function(tooltipItem) {
return tooltipItem.raw + " " + unit;
}
}
}
}
}
});
}
async function loadPlatformData(platform) {
document.getElementById("platformDescription").innerText =
platformDescriptions[platform];
document
.querySelectorAll(".nav-buttons button")
.forEach((button) => button.classList.remove("active"));
console.log(platform);
document.getElementById(platform).classList.add("active");
const dataUrl =
"https://raw.githubusercontent.com/mozilla/performance-data/refs/heads/main/ml-data.json";
await createCharts(dataUrl, platformMapping[platform]);
}
var data = null;
var engineData = null;
function indexData(data) {
const index = {};
data.forEach((entry) => {
const suiteKey = entry.suite.toLowerCase();
const testKey = entry.test.toLowerCase();
const platformKey = entry.platform.toLowerCase();
if (!index[suiteKey]) index[suiteKey] = {};
if (!index[suiteKey][testKey]) index[suiteKey][testKey] = {};
if (!index[suiteKey][testKey][platformKey])
index[suiteKey][testKey][platformKey] = [];
index[suiteKey][testKey][platformKey].push({
date: new Date(entry.date),
value: entry.value
});
});
// Ensure dates are sorted
Object.values(index).forEach((suite) =>
Object.values(suite).forEach((test) =>
Object.values(test).forEach((platformData) =>
platformData.sort((a, b) => a.date - b.date)
)
)
);
return index;
}
function mergeLatencies(first, second) {
return first.map((runItem, index) => ({
...runItem,
value: runItem.value + (second[index]?.value || 0)
}));
}
function createSmartTabTopic(platform) {
var peakMem = getValues(
"smart tab grouping",
"topic-peak-memory-usage",
platform
);
var initLatency = getValues(
"smart tab grouping",
"topic-cold-start-initialization-latency",
platform
);
var runLatency = getValues(
"smart tab grouping",
"topic-model-run-latency",
platform
);
// Example usage
var mergedLatency = mergeLatencies(initLatency, runLatency);
createLineChart(
document.getElementById("smartTabTopicChart"),
mergedLatency,
"ms"
);
createLineChart(
document.getElementById("ramSmartTabTopicChart"),
peakMem,
"MiB"
);
}
function createSmartTabSuggest(platform) {
var peakMem = getValues(
"smart tab grouping",
"embedding-peak-memory-usage",
platform
);
var initLatency = getValues(
"smart tab grouping",
"embedding-cold-start-initialization-latency",
platform
);
var runLatency = getValues(
"smart tab grouping",
"embedding-model-run-latency",
platform
);
// Example usage
var mergedLatency = mergeLatencies(initLatency, runLatency);
createLineChart(
document.getElementById("smartTabSuggestChart"),
mergedLatency,
"ms"
);
createLineChart(
document.getElementById("ramSmartTabSuggestChart"),
peakMem,
"MiB"
);
}
async function createCharts(dataUrl, platform) {
if (!data) {
try {
const response = await fetch(dataUrl);
data = await response.json();
data = data.query_result.data.rows;
fixupMLNaming();
data = indexData(data);
} catch (error) {
console.error("Error fetching or processing data:", error);
}
}
refreshAllCharts(platform);
}
function refreshAllCharts(platform) {
createSmartTabSuggest(platform);
createSmartTabTopic(platform);
/*
createChart(
document.getElementById("autofillChart"),
getValues("autofill", "form-fill-time", platform),
"ms"
);
createChart(
document.getElementById("smartTabSuggestChart"),
getValues("smart tab grouping", "topic-suggestion-time", platform),
"ms"
);
createChart(
document.getElementById("linkPreviewChart"),
getValues("link preview", "preview-generation-time", platform),
"tokens/s"
);
createChart(
document.getElementById("ramAutofillChart"),
getValues("autofill", "residual-memory-usage", platform),
"MiB"
);
createChart(
document.getElementById("ramSmartTabSuggestChart"),
getValues("smart tab grouping", "topic-residual-memory-usage", platform),
"MiB"
);
createChart(
document.getElementById("ramLinkPreviewChart"),
getValues("link preview", "residual-memory-usage", platform),
"MiB"
);
createChart(
document.getElementById("memoryPressure"),
getValues("system", "memory-pressure", platform),
"MiB"
);
*/
}
function fixupMLNaming() {
data.forEach((row) => {
row.test = row.test.replace("total-memory-usage", "residual-memory-usage");
// Rename the suite to something more readable.
if (row.suite === "browser_ml_engine_perf.js") {
row.suite = "Basic ML Perf";
}
// Extract the prefix from each test name and use that as the suite.
if (row.suite === "browser_ml_engine_multi_perf.js") {
const fields = row.test.split("-");
const prefix = fields.shift();
const test = fields.join("-");
// We use browser_ml_suggest_feature_perf.js for suggest feature
// so skipping the intent and suggest models here
if (prefix.includes("intent") || prefix.includes("suggest")) {
return;
}
row.test = test;
}
if (row.suite === "browser_ml_suggest_feature_perf.js") {
const fields = row.test.split("-");
const prefix = fields.shift();
const test = fields.join("-");
if (prefix.includes("INTENT") && !test.includes("model-run-latency")) {
row.suite = "Suggest";
} else if (prefix.includes("SUGGEST")) {
row.suite = "Suggest";
} else {
return;
}
row.test = test;
}
if (row.suite === "browser_ml_autofill_perf.js") {
row.suite = "Autofill";
row.test = row.test.replace("AUTOFILL-", "");
}
if (row.suite === "browser_ml_summarizer_perf.js") {
row.suite = "Summarizer";
row.test = row.test
.replace("SUM-", "")
.replace("ONNX-COMMUNITY-", "")
.replace("XENOVA-", "");
}
if (row.suite === "browser_ml_smart_tab_perf.js") {
row.suite = "Smart Tab Grouping";
row.test = row.test
.replace("SMART-TAB-TOPIC-", "Topic-")
.replace("SMART-TAB-EMBEDDING-", "Embedding-");
}
});
}
function getValues(suite, test, platform) {
return (
data[suite.toLowerCase()]?.[test.toLowerCase()]?.[platform.toLowerCase()] ||
[]
);
}
function getEngineIdData(engineId) {
return engineData.filter(e => e.engine_id === engineId).sort(e => e.date);
}
function createEngineStats(engineId, engineData) {
let totalEngineSuccess = 0;
let totalEngineFailure = 0;
let totalInferenceSuccess = 0;
let totalInferenceFailure = 0;
engineData.forEach(entry => {
totalEngineSuccess += (entry.engine_creation_success_count || 0);
totalEngineFailure += (entry.engine_creation_failure_count || 0);
totalInferenceSuccess += (entry.inference_success_count || 0);
totalInferenceFailure += (entry.inference_failure_count || 0);
});
// 2) Populate the grid with totals
document.getElementById(`${engineId}-engineSuccessCount`).textContent = totalEngineSuccess;
document.getElementById(`${engineId}-engineFailureCount`).textContent = totalEngineFailure;
document.getElementById(`${engineId}-inferenceSuccessCount`).textContent = totalInferenceSuccess;
document.getElementById(`${engineId}-inferenceFailureCount`).textContent = totalInferenceFailure;
}
function createEngineRow(engineId) {
const engineData = getEngineIdData(engineId);
const featureContainer = document.getElementById("gridContainer");
const htmlBlock = `
<div class="feature-group-${engineId}">
<div class="feature-title">${engineId}</div>
<div class="chart-container">
<!-- Chart Section -->
<div class="chart-wrapper">
<canvas id="${engineId}-chart"></canvas>
</div>
<!-- Stats Section -->
<div class="stats-section">
<!-- Engine Creation Stats Card -->
<div class="stats-card">
<div class="stats-title">Engine Creation Stats</div>
<div class="stats-grid">
<!-- Row 1: Labels -->
<div>Success Count</div>
<div>Failure Count</div>
<!-- Row 2: Values -->
<div id="${engineId}-engineSuccessCount"></div>
<div id="${engineId}-engineFailureCount"></div>
</div>
</div>
<!-- Inference Stats Card -->
<div class="stats-card">
<div class="stats-title">Inference Stats</div>
<div class="stats-grid">
<!-- Row 1: Labels -->
<div>Success Count</div>
<div>Failure Count</div>
<!-- Row 2: Values -->
<div id="${engineId}-inferenceSuccessCount"></div>
<div id="${engineId}-inferenceFailureCount"></div>
</div>
</div>
</div>
</div>
</div>
`
featureContainer.insertAdjacentHTML("beforeend", htmlBlock);
createTwoLineChart(
document.getElementById(`${engineId}-chart`),
engineData,
"ms"
);
createEngineStats(engineId, engineData);
}
function createFeatureSelect(engineIds) {
const selectContainer = document.getElementById("featureSelect");
const htmlBlockAll = `
<option value="all" selected>All</option>
`
selectContainer.insertAdjacentHTML("beforeend", htmlBlockAll);
for (let engineId of engineIds) {
const htmlBlock = `
<option value="feature-group-${engineId}">${engineId}</option>
`;
selectContainer.insertAdjacentHTML("beforeend", htmlBlock);
}
}
function renderEngineData(engineData) {
const engineIds = [...new Set(engineData.map(e => e.engine_id))].sort();
createFeatureSelect(engineIds);
for (let engineId of engineIds) {
createEngineRow(engineId);
}
}
async function loadEngineData() {
const dataUrl =
"https://raw.githubusercontent.com/mozilla/performance-data/refs/heads/main/ml-engine-data.json";
if (!engineData) {
try {
const response = await fetch(dataUrl);
engineData = await response.json();
engineData = engineData.query_result.data.rows;
renderEngineData(engineData);
} catch (error) {
console.error("Error fetching or processing data:", error);
}
}
}