assets/ml-metrics.js (357 lines of code) (raw):
window.telemetryData = [];
// Set some defaults.
window.platformStatus = 'windows11-64-shippable-qr';
window.historyStatus = 30;
window.allCharts = [];
function sortTable(columnIndex, tableId) {
const table = document.getElementById(tableId);
const tbody = table.getElementsByTagName('tbody')[0];
const rows = Array.from(tbody.getElementsByTagName('tr'));
const isAscending = table.getAttribute('data-sort-direction') === 'asc';
const direction = isAscending ? 1 : -1;
rows.sort((a, b) => {
const cellA = a.getElementsByTagName('td')[columnIndex].innerText.toLowerCase();
const cellB = b.getElementsByTagName('td')[columnIndex].innerText.toLowerCase();
if (!isNaN(cellA) && !isNaN(cellB)) {
return direction * (parseFloat(cellA) - parseFloat(cellB));
} else {
return direction * cellA.localeCompare(cellB);
}
});
while (tbody.firstChild) {
tbody.removeChild(tbody.firstChild);
}
rows.forEach(row => tbody.appendChild(row));
table.setAttribute('data-sort-direction', isAscending ? 'desc' : 'asc');
}
function findNearestHistory(target, list) {
if (!Array.isArray(list) || list.length === 0) {
throw new Error("The list must be a non-empty array.");
}
return list.reduce((nearest, current) => {
return Math.abs(current - target) < Math.abs(nearest - target) ? current : nearest;
});
}
function setHistory(history) {
// Find max number of days.
const latestDate = new Date(window.data.at(-1).date);
const furthestDate = new Date(window.data.at(0).date);
window.historyStatus = history;
const input = document.getElementById('history-select');
input.value = history;
displayCharts();
}
function setPlatform(platform) {
window.platformStatus = platform;
displayContent();
}
function writeContentTitle() {
const title = document.getElementById('content-title');
title.innerText = `${window.platformStatus} / ${window.historyStatus} days`;
}
function calculateDelta(values) {
const n = values.length;
// Create x values (0 to n-1) representing the time sequence of data points
const xValues = Array.from({ length: n }, (_, i) => i);
// Calculate the averages of x and y (the subset values)
const xAvg = xValues.reduce((sum, x) => sum + x, 0) / n;
const yAvg = values.reduce((sum, y) => sum + y, 0) / n;
// Calculate the slope (m) of the line of best fit using the least squares method
const numerator = xValues.reduce((sum, x, i) => sum + (x - xAvg) * (values[i] - yAvg), 0);
const denominator = xValues.reduce((sum, x) => sum + Math.pow(x - xAvg, 2), 0);
const slope = numerator / denominator;
// Calculate the y-intercept (b) of the line of best fit
const intercept = yAvg - slope * xAvg;
// Calculate the predicted y-values (trend line) for the first and last x-values
const firstY = intercept; // y-value at x = 0 (first data point)
const lastY = slope * (n - 1) + intercept; // y-value at x = n - 1 (last data point)
// Calculate the percentage improvement between the first and last points on the trend line
const improvement = ((lastY - firstY) / firstY) * 100;
return improvement.toFixed(2);
}
function displayMetrics(id, title1, values1, title2, values2) {
function getDeltaColor(improvement) {
if (improvement > 5)
return "red";
else if (improvement < -5)
return "green";
else
return "black";
}
const improvement1 = calculateDelta(values1);
const improvement2 = calculateDelta(values2);
document.getElementById(id).innerHTML = `
<div class="metric-item">${title1} Δ: <font style="color: ${getDeltaColor(improvement1)};">${improvement1 > 0 ? "+" + improvement1 : improvement1}%</font></div>
<div class="metric-item">${title2} Δ: <font style="color: ${getDeltaColor(improvement2)};">${improvement2 > 0 ? "+" + improvement2 : improvement2}%</font></div>
`;
}
function plotChart(id, labels, dataset, unit) {
const ctx = document.getElementById(id+"-canvas").getContext('2d');
let chart = new Chart(ctx, {
type: 'scatter',
data: {
labels: labels,
datasets: dataset
},
options: {
scales: {
x: {
type: 'time',
time: { unit: 'day', tooltipFormat: 'll' },
title: { display: true, text: 'Timestamp' }
},
y: {
beginAtZero: false,
title: { display: true, text: `${unit}` }
}
},
plugins: {
legend: {
display: false
},
zoom : {
pan: {
enabled: false,
},
zoom: {
mode: 'xy',
drag: {
enabled: true,
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1,
backgroundColor: 'rgba(54, 162, 235, 0.3)'
},
onZoom({ chart }) {
window.allCharts.forEach(c => {
c.zoomScale(
'x',
{ min: Math.trunc(chart.scales.x.min), max: Math.trunc(chart.scales.x.max) },
'none'
);
});
},
},
},
}
},
plugins: [backgroundColorPlugin]
});
//displayMetrics(id+"-metrics", title1, values1, title2, values2);
window.allCharts.push(chart);
}
function updateChart() {
Chart.helpers.each(Chart.instances, function (instance) {
instance.options.scales.xAxes[0].time.min = leftEnd;
instance.options.scales.xAxes[0].time.max = rightEnd;
instance.update();
});
}
function displayChartForTest(data, id, unit) {
labels = data.map(row => row.date);
dataset = [{
label: "firefox",
data: data.map(row => row.value),
fill: false,
borderWidth: 1,
tension: 0.1,
}];
// Only add trendline's when there are enough data points.
dataset.forEach(ds => {
if (ds.data.length > 5) {
ds["trendlineLinear"] = {
style: "rgba(255,105,180, .8)",
lineStyle: "dotted",
width: 2
}
}
});
plotChart(id, labels, dataset, unit);
}
function displayCharts() {
while (window.allCharts.length > 0) {
window.allCharts.pop().destroy();
}
const history = window.historyStatus;
const latestDate = new Date(window.data.at(-1).date);
let cutoffDate = new Date(latestDate);
cutoffDate.setDate(latestDate.getDate() - history);
cutoffDate = cutoffDate.toISOString().split('T')[0];
const filteredData = window.data.filter(row => row.date >= cutoffDate && row.platform == platformStatus);
window.suites.forEach(suite => {
suite.tests.forEach(test => {
const label=`${suite.name}-${test.name}`;
const testData = filteredData.filter(
row => row.suite == suite.name && row.test == test.name);
displayChartForTest(testData, label, test.unit);
});
});
}
const backgroundColorPlugin = {
id: 'backgroundColor',
beforeDraw: (chart) => {
const {ctx, width, height} = chart;
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, width, height);
}
};
function fixupMLNaming() {
window.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 loadData(dataUrl) {
fetch(dataUrl)
.then(response => response.json())
.then(data => {
window.data = data.query_result.data.rows;
fixupMLNaming();
window.data.sort((a, b) => new Date(a.date) - new Date(b.date));
displayContent();
})
.catch(error => console.error('Error loading the JSON file:', error));
}
function generateContent(dataUrl) {
const historyInput = document.getElementById('history-select');
historyInput.value = window.historyStatus;
loadData(dataUrl);
}
function displayContent() {
displayCharts();
displayTable();
}
// Calculate the differences between firefox vs itself from 1 and 4 weeks ago.
function displayTable() {
const table = document.querySelector('#summary-table tbody');
table.innerHTML='';
window.suites.forEach(suite => {
const testslength = suite.tests.length;
let isFirstSuiteRow = true;
suite.tests.forEach(test => {
const row = document.createElement('tr');
if (isFirstSuiteRow) {
row.style.borderTop = '2px solid #ddd';
}
const filteredData = window.data.filter(row => row.suite == suite.name && row.test == test.name && row.platform == platformStatus);
const firefoxAvg_latest = calculate_average(filteredData, 0);
const firefoxAvg_1wk_ago = calculate_average(filteredData, 7);
const firefoxAvg_4wk_ago = calculate_average(filteredData, 28);
const difference_1wk = ((firefoxAvg_latest - firefoxAvg_1wk_ago) / firefoxAvg_latest) * 100;
const difference_4wk = ((firefoxAvg_latest - firefoxAvg_4wk_ago) / firefoxAvg_latest) * 100;
let class_1wk = "";
if (difference_1wk < -5) {
class_1wk = 'positive-difference';
} else if (difference_1wk > 5) {
class_1wk = 'negative-difference';
}
let class_4wk = "";
if (difference_4wk < -5) {
class_4wk = 'positive-difference';
} else if (difference_4wk > 5) {
class_4wk = 'negative-difference';
}
// Build row HTML
row.innerHTML = `
${isFirstSuiteRow ? `<td rowspan="${testslength}">${suite.name}</td>` : ''}
<td class="test-cell ${class_1wk}">${test.name} (${test.unit})</td>
<td class="${class_1wk}" style="border-left: 1px solid #ddd;">${firefoxAvg_latest.toFixed(2)}</td>
<td class="${class_1wk}" style="border-left: 1px solid #ddd;">${firefoxAvg_1wk_ago.toFixed(2)}</td>
<td class="${class_1wk}">${difference_1wk.toFixed(2)}</td>
<td class="${class_4wk}" style="border-left: 1px solid #ddd;">${firefoxAvg_4wk_ago.toFixed(2)}</td>
<td class="${class_4wk}">${difference_4wk.toFixed(2)}</td>
`;
// Add click handler for the entire row (excluding suite cell)
row.addEventListener('click', (event) => {
// Exclude clicks on the suite cell
const suiteCell = isFirstSuiteRow ? row.querySelector('td[rowspan]') : null;
if (suiteCell && suiteCell.contains(event.target)) {
return;
}
// Navigate to the corresponding test section
window.location.hash = `${suite.name}-${test.name}-section`;
});
isFirstSuiteRow = false;
table.appendChild(row);
});
});
}
// Take the most recent 7 entries and average.
function calculate_average(data, daysAgo) {
if (data.length === 0) return 0;
data.sort((a, b) => new Date(b.date) - new Date(a.date));
const mostRecentDate = new Date(data[0].date);
const currentDate = new Date(mostRecentDate);
currentDate.setDate(currentDate.getDate() - daysAgo);
const cutoffDate = new Date(currentDate);
cutoffDate.setDate(cutoffDate.getDate() - 6);
const last7DaysData = data.filter(item => {
const itemDate = new Date(item.date);
return itemDate >= cutoffDate && itemDate <= currentDate;
});
const total = last7DaysData.reduce((sum, item) => sum + item.value, 0);
return last7DaysData.length > 0 ? total / last7DaysData.length : 0;
}
function createSummaryTable(tests) {
calculate7DayAverage();
}
function createSidebarSections(tests) {
var sectionsHTML = '';
sectionsHTML += `<a href="#summary-section">Summary</a>\n`;
window.suites.forEach(suite => {
suite.tests.forEach(test => {
sectionsHTML += `<a href="#${suite.name}-${test.name}-section">(${suite.name}) ${test.name}</a>\n`;
});
});
document.querySelector('.sections').innerHTML = sectionsHTML;
}
function createChartsContent() {
var chartsHTML = '';
window.suites.forEach(suite => {
suite.tests.forEach(test => {
let name=`${suite.name}-${test.name}`;
chartsHTML += `
<div class="row-title" id="${name}-section">(${suite.name}) ${test.name}</div>
<div class="content-row">
<div class="canvas-column">
<div id="${name}-metrics" class="metric-container"></div>
<canvas id="${name}-canvas"></canvas>
<button onclick="window.allCharts.forEach(chart => chart.resetZoom())">
Reset Zoom
</button>
</div>
</div>
`;
});
});
document.querySelector('.charts-content').innerHTML = chartsHTML;
}