speedometer.html (623 lines of code) (raw):
<!DOCTYPE html>
<html lang="en">
<head>
<title>Speedometer 3 Status Dashboard</title>
<link href="https://fonts.googleapis.com/css2?family=Zilla+Slab:wght@400;700&display=swap" rel="stylesheet">
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" rel="stylesheet">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<link rel="stylesheet" href="assets/main-ui-style.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script>
<script>
function round(number, decimals) {
return Math.round(number * Math.pow(10, decimals)) / Math.pow(10, decimals);
}
var platforms = ['windows10-64-shippable-qr'];
const searchParams = new URLSearchParams(window.location.search);
let likelyDataURL = 'https://sql.telemetry.mozilla.org/api/queries/89943/results.json?api_key=8pVTrtmbttRWqE5MGuCplgHncRFHON0JUj5zCSGh';
let timeWindow = '6wk';
let supportsCaR = true;
let range = '';
var timeChart;
var total_sig_firefox = 4591185;
var total_sig_chrome = 4592500;
var total_sig_car = 4781109;
var total_sig_safari = 0;
var total_sig_safari_tp = 0;
if (searchParams.get('os') == 'osx') {
total_sig_firefox = 4588949;
total_sig_chrome = 4589463;
total_sig_car = 4836588;
platforms = ['macosx1015-64-shippable-qr'];
} else if (searchParams.get('os') == 'windows-new') {
total_sig_firefox = 87472;
total_sig_chrome = 92125;
total_sig_car = 92557;
platforms = ['windows11-64-shippable-qr', 'windows11-64-24h2-shippable'];
} else if (searchParams.get('os') == 'osxm2') {
total_sig_firefox = 5042783;
total_sig_chrome = 5044184;
total_sig_car = 5043640;
total_sig_safari = 5045302;
total_sig_safari_tp = 37211;
platforms = ['macosx1400-64-shippable-qr'];
} else if (searchParams.get('os') == 'osxm4') {
total_sig_firefox = 5353492;
total_sig_chrome = 5353692;
total_sig_car = 5380083;
total_sig_safari = 5361588;
total_sig_safari_tp = 5361588;
platforms = ['macosx1500-aarch64-shippable'];
} else if (searchParams.get('os') == 'linux') {
total_sig_firefox = 4588786;
total_sig_chrome = 4589789;
total_sig_car = 4725642;
platforms = ['linux1804-64-shippable-qr'];
} else if (searchParams.get('os') == 'android-a55') {
total_sig_firefox = 228888;
total_sig_chrome = 230167;
total_sig_car = 230550;
platforms = ['android-hw-a55-14-0-aarch64-shippable'];
likelyDataURL = 'https://sql.telemetry.mozilla.org/api/queries/90959/results.json?api_key=uuoiM8Xdob1HcGB5UxdFG3TvZi6D08kfNR5pWC9O';
timeWindow = '4wk';
} else if (searchParams.get('os') == 'android-s24') {
total_sig_firefox = 274646;
total_sig_chrome = 275618;
total_sig_car = 280648;
platforms = ['android-hw-s24-14-0-aarch64-shippable'];
likelyDataURL = 'https://sql.telemetry.mozilla.org/api/queries/90959/results.json?api_key=uuoiM8Xdob1HcGB5UxdFG3TvZi6D08kfNR5pWC9O';
timeWindow = '4wk';
} else if (searchParams.get('os') == 'android-p6') {
total_sig_firefox = 275091;
total_sig_chrome = 275819;
total_sig_car = 281010;
platforms = ['android-hw-p6-13-0-aarch64-shippable'];
likelyDataURL = 'https://sql.telemetry.mozilla.org/api/queries/90959/results.json?api_key=uuoiM8Xdob1HcGB5UxdFG3TvZi6D08kfNR5pWC9O';
timeWindow = '4wk';
}
fetch(likelyDataURL)
.then(function (response) {
return response.json();
})
.then(function (data) {
loadDataLikely(data);
})
.catch(function (err) {
console.log('error: ' + err);
});
function getDiffColor(diff) {
if (diff < -0.05) {
return 'green'
} else if (diff < 0.095) {
return 'black'
} else if (diff < 0.2) {
return '#b38f00'
} else {
return 'red'
}
}
function getSelfDiffColor(diff) {
if (diff <= 0.98) {
return 'green';
} else if (diff >= 1.02) {
return 'red';
} else {
return 'black';
}
}
function fillData(tableId, row) {
if (!platforms.includes(row.platform)) {
return;
}
diffColor = getDiffColor(row.difference);
oldDiffColor = getDiffColor(row.olddifference);
let firefoxDiff = 1.0;
if (row.firefoxoldmean > 0) {
firefoxDiff = row.firefoxdifference;
}
let newLine = '';
newLine += '<tr onClick="openTestComparison(' + row.signature_id_firefox + ', ' + row.signature_id_chrome + ', ' + row.signature_id_car + ', ' + row.signature_id_safari + ', ' + row.signature_id_safari_tp + ');"><th scope="row" class="testName">' +
row.test.split('/')[0] + '</th><td>' +
round(row.firefoxmean, 2) + ' ms</td><td>' +
round(row.chromemean, 2) + ' ms</td>';
if (supportsCaR) {
newLine += '<td>' + round(row.carmean, 2) + ' ms</td>';
}
if (platforms.includes('macosx1400-64-shippable-qr') || platforms.includes('macosx1500-aarch64-shippable')) {
newLine += '<td>' +
round(row.safarimean, 2) + ' ms</td>';
newLine += '<td>' +
round(row.safaritpmean, 2) + ' ms</td>';
}
newLine += '<td style =\'color: ' + diffColor + '\'>' +
Math.round(-row.difference * 100) + '\%</td>'
if (supportsCaR) {
newLine += '<td style=\'color: ' + getDiffColor(row.cardifference) + '\'>' + Math.round(-row.cardifference * 100) + '\%</td>'
}
if (platforms.includes('macosx1400-64-shippable-qr') || platforms.includes('macosx1500-aarch64-shippable')) {
newLine += '<td style=\'color: ' + getDiffColor(row.safaridifference) + '\'>' + Math.round(-row.safaridifference * 100) + '\%</td>'
newLine += '<td style=\'color: ' + getDiffColor(row.safaritpdifference) + '\'>' + Math.round(-row.safaritpdifference * 100) + '\%</td>'
}
newLine += '<td>' +
round(row.firefoxoldmean, 2) + ' ms</td><td>' +
round(row.chromeoldmean, 2) + ' ms</td><td style=\'color: ' + oldDiffColor + '\'>' +
Math.round(-row.olddifference * 100) + '\%</td><td style=\'color: ' + getSelfDiffColor(firefoxDiff) + '\'>' +
Math.round(-(1 - firefoxDiff) * 100) + '%</td></tr >\n';
document.getElementById(tableId).innerHTML += newLine;
}
// Store testData globally so it can be accessed by other functions
let testData = {};
function loadDataLikely(data) {
let table = document.getElementById('tableLikely');
if (!platforms.includes('macosx1400-64-shippable-qr') && !platforms.includes('macosx1500-aarch64-shippable')) {
table.rows[0].cells[1].colSpan = 3;
table.rows[0].cells[2].colSpan = 2;
table.rows[1].deleteCell(3);
table.rows[1].deleteCell(3);
table.rows[1].deleteCell(5);
table.rows[1].deleteCell(5);
}
// Process all rows directly without date filtering first
if (searchParams.get('os') != 'windows-new') {
data.query_result.data.rows.forEach(fillData.bind(null, 'tableBodyLikely'));
} else {
// For windows-new, we need special handling
// Group by test name to avoid duplicates
testData = {};
// First pass: collect all data by test name
data.query_result.data.rows.forEach(function(row) {
if (platforms.includes(row.platform)) {
let testName = row.test.split('/')[0];
if (!testData[testName]) {
testData[testName] = [];
}
testData[testName].push(row);
}
});
// Second pass: for each test, select the appropriate row based on platform
Object.keys(testData).forEach(function(testName) {
let rows = testData[testName];
// Find a row from each platform
let oldRow = rows.find(r => r.platform === 'windows11-64-shippable-qr');
let newRow = rows.find(r => r.platform === 'windows11-64-24h2-shippable');
// If we have both, use the 24h2 row (newer platform)
if (oldRow && newRow) {
fillData('tableBodyLikely', newRow);
}
// If we only have one, use that
else if (oldRow) {
fillData('tableBodyLikely', oldRow);
}
else if (newRow) {
fillData('tableBodyLikely', newRow);
}
});
}
document.getElementById('timewindow').innerHTML = timeWindow + ' prior';
let arithmeticMeanOfLogsFX = 0;
let arithmeticMeanOfLogsFXOld = 0;
let arithmeticMeanOfLogsChrome = 0;
let arithmeticMeanOfLogsChromeOld = 0;
let arithmeticMeanOfLogsCaR = 0;
let arithmeticMeanOfLogsSafari = 0;
let arithmeticMeanOfLogsSafariTP = 0;
let numValues = 0;
let numValuesOld = 0;
// Calculate totals based on the rows that were actually displayed
// Get all rows that were added to the table
const tableRows = document.querySelectorAll('#tableBodyLikely tr:not(.sumRow)');
console.log("Table rows for calculation:", tableRows.length);
// For each row in the table, find the corresponding data row
tableRows.forEach(function(tableRow) {
const testName = tableRow.querySelector('.testName').textContent;
// Find the data row for this test
let dataRow;
if (searchParams.get('os') == 'windows-new') {
// For windows-new, find in our testData
const rows = testData[testName] || [];
const newRow = rows.find(r => r.platform === 'windows11-64-24h2-shippable');
const oldRow = rows.find(r => r.platform === 'windows11-64-shippable-qr');
dataRow = newRow || oldRow;
} else {
// For other platforms, find in the original data
dataRow = data.query_result.data.rows.find(r => {
return platforms.includes(r.platform) && r.test.split('/')[0] === testName;
});
}
if (dataRow) {
numValues++;
arithmeticMeanOfLogsFX += Math.log(dataRow.firefoxmean);
arithmeticMeanOfLogsChrome += Math.log(dataRow.chromemean);
arithmeticMeanOfLogsCaR += Math.log(dataRow.carmean);
arithmeticMeanOfLogsSafari += Math.log(dataRow.safarimean);
arithmeticMeanOfLogsSafariTP += Math.log(dataRow.safaritpmean);
if (dataRow.firefoxoldmean > 0) {
numValuesOld++;
arithmeticMeanOfLogsFXOld += Math.log(dataRow.firefoxoldmean);
arithmeticMeanOfLogsChromeOld += Math.log(dataRow.chromeoldmean);
}
}
});
let FxGeoMean = Math.exp(arithmeticMeanOfLogsFX / numValues);
let ChromeGeoMean = Math.exp(arithmeticMeanOfLogsChrome / numValues);
let CaRGeoMean = Math.exp(arithmeticMeanOfLogsCaR / numValues);
let SafariGeoMean = Math.exp(arithmeticMeanOfLogsSafari / numValues);
let SafariTPGeoMean = Math.exp(arithmeticMeanOfLogsSafariTP / numValues);
let FxOldGeoMean = Math.exp(arithmeticMeanOfLogsFXOld / numValuesOld);
let ChromeOldGeoMean = Math.exp(arithmeticMeanOfLogsChromeOld / numValuesOld);
let diff = FxGeoMean / ChromeGeoMean - 1;
let CaRdiff = FxGeoMean / CaRGeoMean - 1;
let Safaridiff = FxGeoMean / SafariGeoMean - 1;
let SafariTPdiff = FxGeoMean / SafariTPGeoMean - 1;
let oldDiff = ChromeOldGeoMean > 0 ? (FxOldGeoMean / ChromeOldGeoMean - 1) : 0;
let diffColor = getDiffColor(diff);
let oldDiffColor = getDiffColor(oldDiff);
let firefoxDiff = 0;
if (FxOldGeoMean > 0) {
firefoxDiff = FxGeoMean / FxOldGeoMean;
}
let newLine = '<tr onClick="openTestComparison(' + total_sig_firefox + ', ' + total_sig_chrome + ', ' + total_sig_car + ', ' + total_sig_safari + ', ' + total_sig_safari_tp + ');" class="sumRow"><th scope="row" class="testName">Total Score</th><td>' +
round(FxGeoMean, 2) + ' ms</td><td>' +
round(ChromeGeoMean, 2) + ' ms</td>';
if (supportsCaR) {
newLine += '<td>' +
round(CaRGeoMean, 2) + ' ms</td>';
}
if (platforms.includes('macosx1400-64-shippable-qr') || platforms.includes('macosx1500-aarch64-shippable')) {
newLine += '<td>' +
round(SafariGeoMean, 2) + ' ms</td>';
newLine += '<td>' +
round(SafariTPGeoMean, 2) + ' ms</td>';
}
newLine += '<td style=\'color: ' + diffColor + '\'>' +
round(-diff * 100, 2) + '\%</td>';
if (supportsCaR) {
newLine += '<td style=\'color: ' + getDiffColor(CaRdiff) + '\'>' +
round(-CaRdiff * 100, 2) + '\%</td>';
}
if (platforms.includes('macosx1400-64-shippable-qr') || platforms.includes('macosx1500-aarch64-shippable')) {
newLine += '<td style=\'color: ' + getDiffColor(Safaridiff) + '\'>' +
round(-Safaridiff * 100, 2) + '\%</td>';
newLine += '<td style=\'color: ' + getDiffColor(SafariTPdiff) + '\'>' +
round(-SafariTPdiff * 100, 2) + '\%</td>';
}
newLine += '<td>' +
round(FxOldGeoMean, 2) + ' ms</td><td>' +
round(ChromeOldGeoMean, 2) + ' ms</td><td style=\'color: ' + oldDiffColor + '\'>' +
round(-oldDiff * 100, 2) + '\%</td><td style=\'color: ' + getSelfDiffColor(firefoxDiff) + '\'>' +
Math.round(-(1 - firefoxDiff) * 100) + '%</td></tr >\n';
document.getElementById("tableBodyLikely").innerHTML += newLine;
}
function openTestComparison(signatureFirefox, signatureChrome, signatureCaR, signatureSafari, signatureSafariTP) {
let repoFirefox = 'mozilla-central';
let repoChrome = 'mozilla-central';
let linkString = "https://treeherder.mozilla.org/perfherder/graphs?highlightAlerts=1&highlightChangelogData=1&highlightCommonAlerts=0&series=" + repoFirefox + "," + signatureFirefox + ",1,13&series=" + repoChrome + "," + signatureChrome + ",1,13&timerange=7776000";
if (supportsCaR) {
linkString += "&series=" + repoChrome + "," + signatureCaR + ",1,13";
}
if (signatureSafari > 0) {
linkString += "&series=" + repoChrome + "," + signatureSafari + ",1,13";
}
if (signatureSafariTP > 0) {
linkString += "&series=" + repoChrome + "," + signatureSafariTP + ",1,13";
}
window.open(linkString, "_blank")
}
function loadChart() {
if (searchParams.get('os') == 'osx') {
document.getElementById("macbutton").style = "background-color:gray;";
} else if (searchParams.get('os') == 'windows-new') {
document.getElementById("winnewbutton").style = "background-color:gray;";
} else if (searchParams.get('os') == 'linux') {
document.getElementById("linuxbutton").style = "background-color:gray;";
} else if (searchParams.get('os') == 'android-s24') {
document.getElementById("mobilebutton").style.backgroundColor = "gray";
} else if (searchParams.get('os') == 'android-a55') {
document.getElementById("mobilebutton2").style.backgroundColor = "gray";
} else if (searchParams.get('os') == 'android-p6') {
document.getElementById("mobilebutton3").style.backgroundColor = "gray";
} else if (searchParams.get('os') == 'osxm2') {
document.getElementById("osxm2button").style.backgroundColor = "gray";
} else if (searchParams.get('os') == 'osxm4') {
document.getElementById("osxm4button").style.backgroundColor = "gray";
} else {
document.getElementById("windowsbutton").style = "background-color:gray;";
}
fetch('https://sql.telemetry.mozilla.org/api/queries/89971/results.json?api_key=6cd9JzffuVjIytWO8fuXs33HXKRjiy9bHJmHfndj')
.then(function (response) {
return response.json();
})
.then(function (data) {
displayChart(data);
})
.catch(function (err) {
console.log('error: ' + err);
});
}
function displayChart(data) {
const ctx = document.getElementById('myChart').getContext('2d');
let xyValuesFx = [];
let xyValuesChrome = [];
let xyValuesCaR = [];
let xyValuesSafari = [];
let xyValuesSafariTP = [];
// For windows-new, we need to handle multiple platforms
if (searchParams.get('os') == 'windows-new') {
// Group data points by timestamp and application to avoid duplicates
let dataPoints = {};
data.query_result.data.rows.forEach(function (row) {
// Basic filtering
if (!platforms.includes(row.platform)) {
if (!(row.platform == 'macosx1300-64-shippable-qr' && platforms.includes('macosx1400-64-shippable-qr'))) {
return;
}
}
if (row.geomean > 130 && !(platforms.includes('android-hw-s24-14-0-aarch64-shippable') ||
platforms.includes('android-hw-p6-13-0-aarch64-shippable') ||
platforms.includes('android-hw-a55-14-0-aarch64-shippable'))) {
return;
}
if (row.platform == 'macosx1300-64-shippable-qr' && row.application == 'chrome' && new Date(row.push_timestamp) < new Date('2024-01-24 00:00')) {
return;
}
// Create a key combining timestamp and application
const key = `${row.push_timestamp}_${row.application}`;
// Prefer 24h2 platform over shippable-qr when both exist for the same timestamp
if (!dataPoints[key] || row.platform === 'windows11-64-24h2-shippable') {
dataPoints[key] = row;
}
});
// Process the filtered data points
Object.values(dataPoints).forEach(function(row) {
if (row.application == 'firefox' || row.application == 'fenix') { xyValuesFx.push({ x: row.push_timestamp, y: row.geomean }); }
if (row.application == 'chrome' || row.application == 'chrome-m') { xyValuesChrome.push({ x: row.push_timestamp, y: row.geomean }); }
if (row.application == 'custom-car' || row.application == 'cstm-car-m') { xyValuesCaR.push({ x: row.push_timestamp, y: row.geomean }); }
if (row.application == 'safari') { xyValuesSafari.push({ x: row.push_timestamp, y: row.geomean }); }
if (row.application == 'safari-tp') { xyValuesSafariTP.push({ x: row.push_timestamp, y: row.geomean }); }
});
} else {
// For other platforms, use the original approach
data.query_result.data.rows.forEach(function (row) {
if (!platforms.includes(row.platform)) {
if (!(row.platform == 'macosx1300-64-shippable-qr' && platforms.includes('macosx1400-64-shippable-qr'))) {
return;
}
}
if (row.geomean > 130 && !(platforms.includes('android-hw-s24-14-0-aarch64-shippable') ||
platforms.includes('android-hw-p6-13-0-aarch64-shippable') ||
platforms.includes('android-hw-a55-14-0-aarch64-shippable'))) {
return;
}
if (row.platform == 'macosx1300-64-shippable-qr' && row.application == 'chrome' && new Date(row.push_timestamp) < new Date('2024-01-24 00:00')) {
return;
}
if (row.application == 'firefox' || row.application == 'fenix') { xyValuesFx.push({ x: row.push_timestamp, y: row.geomean }); }
if (row.application == 'chrome' || row.application == 'chrome-m') { xyValuesChrome.push({ x: row.push_timestamp, y: row.geomean }); }
if (row.application == 'custom-car' || row.application == 'cstm-car-m') { xyValuesCaR.push({ x: row.push_timestamp, y: row.geomean }); }
if (row.application == 'safari') { xyValuesSafari.push({ x: row.push_timestamp, y: row.geomean }); }
if (row.application == 'safari-tp') { xyValuesSafariTP.push({ x: row.push_timestamp, y: row.geomean }); }
});
}
let cfg = {
type: "scatter",
data: {
datasets: [
{
label: 'Firefox',
pointRadius: 3,
pointBackgroundColor: "#FF9500",
pointBorderColor: "#000000",
pointBorderWidth: 0.5,
data: xyValuesFx
}
]
},
options: {
aspectRatio: 1.5,
scales: {
x: {
type: 'time',
time: {
unit: 'week'
}
},
y: {
beginAtZero: true
}
},
plugins: {
annotation: {
annotations: {
speedometer31Line: {
type: 'line',
xMin: '2025-03-21',
xMax: '2025-03-21',
borderColor: 'rgba(65, 105, 225, 0.7)',
borderWidth: 2,
label: {
display: true,
content: 'Speedometer 3.1 Update',
position: 'start',
backgroundColor: 'rgba(65, 105, 225, 0.7)',
font: {
weight: 'bold',
size: 16
},
padding: {
top: 6,
bottom: 6,
left: 8,
right: 8
}
}
}
}
}
}
}
};
cfg.data.datasets.push({
label: 'Chrome',
pointRadius: 3,
pointBackgroundColor: "#1DA462",
pointBorderColor: "#000000",
pointBorderWidth: 0.5,
data: xyValuesChrome
});
if (xyValuesCaR.length > 0) {
cfg.data.datasets.push({
label: 'Chromium-as-Release',
pointRadius: 3,
pointBackgroundColor: "#2773da",
pointBorderColor: "#000000",
pointBorderWidth: 0.5,
data: xyValuesCaR
});
}
if (platforms.includes('macosx1400-64-shippable-qr') || platforms.includes('macosx1500-aarch64-shippable')) {
cfg.data.datasets.push({
label: 'Safari',
pointRadius: 3,
pointBackgroundColor: "#44444444",
pointBorderColor: "#000000",
pointBorderWidth: 0.5,
data: xyValuesSafari
});
cfg.data.datasets.push({
label: 'Safari TP',
pointRadius: 3,
pointBackgroundColor: "#777",
pointBorderColor: "#44444444",
pointBorderWidth: 0.5,
data: xyValuesSafariTP
});
}
timeChart = new Chart("myChart", cfg);
}
function changeRange(newRange) {
if (newRange == 'all') {
timeChart.options.scales.x.min = '';
} else {
var d = new Date();
d.setMonth(d.getMonth() - newRange);
timeChart.options.scales.x.min = d;
}
timeChart.update();
}
</script>
<style>
body {
background-color: #f0f0f0;
}
h3 {
font-family: sans-serif;
}
.styled-table {
border-collapse: collapse;
margin: 25px 0;
font-size: 0.9em;
font-family: sans-serif;
min-width: 400px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
}
.styled-table thead th {
background-color: #3366ff;
color: #ffffff;
text-align: center;
}
.styled-table th {
padding: 12px 15px;
}
.styled-table td {
text-align: right;
}
.styled-table .testName {
text-align: left;
}
.tableBody tr {
border-bottom: 1px solid #dddddd;
}
.tableBody .sumRow {
border-top: 2px solid black;
}
.tableBody tr:nth-of-type(odd) {
background-color: #f3f3f3;
}
.tableBody tr:nth-of-type(even) {
background-color: #e3e3e3;
}
.tableBody tr:hover {
background-color: #d0d0d0;
}
.buttons td {
width: 250px;
text-align: center;
font-family: sans-serif;
font-size: 20px;
}
a {
padding-left: 30px;
padding-right: 30px;
padding-top: 10px;
padding-bottom: 10px;
text-decoration: none;
color: black;
border-radius: 5px;
}
a:hover {
background-color: lightgray;
}
</style>
</head>
<body onload="loadChart();">
<div class="top">
<div class="top-title-container">
<div class="top-title">
<b>moz://a</b> performance portal
</div>
</div>
<a href="https://firefox-source-docs.mozilla.org/performance/reporting_a_performance_problem.html" class="btn" title="Report a Performance Bug">
<i class="fa-solid fa-bug"></i> Report Performance Issue
</a>
</div>
<div class="main-content">
<aside class="main-sidebar" id="main-sidebar">
</aside>
<div class="content">
<center>
<table class="buttons">
<tr><td colspan="2" style="height:50px;"><a href="speedometer.html" id="windowsbutton">Windows 10</a></td><td colspan="2" style="height:50px;"><a href="speedometer.html?os=windows-new" id="winnewbutton">Windows 11</a></td><td colspan="2" style="height:50px;"><a href="speedometer.html?os=linux" id="linuxbutton">Linux</a></td></tr>
<td colspan="3" style="height:50px;"><a href="speedometer.html?os=osxm4" id="osxm4button">Mac OSX (M4)</a></td><td colspan="3" style="height:50px;"><a href="speedometer.html?os=osxm2" id="osxm2button">Mac OSX (M2)</a></td></tr>
<tr><td colspan="2" style="height:50px;"><a href="speedometer.html?os=android-s24" id="mobilebutton">Android (S24)</a></td><td colspan="2" style="height:50px;"><a href="speedometer.html?os=android-a55" id="mobilebutton2">Android (A55)</a></td><td colspan="2" style="height:50px;"><a href="speedometer.html?os=android-p6" id="mobilebutton3">Android (P6)</a></td></tr>
</table>
<h3>Current Status (lower is better)</h3>
<br>
<div style="width:100%;max-width:900px">
<canvas id="myChart" style="width:100%;max-width:900px"></canvas>
<table class="buttons">
<tr><td><a onclick="changeRange('all')" id="allbutton">All time</a></td><td><a onclick="changeRange(3)" id="month3button">3 months</a></td><td><a onclick="changeRange(1)" id="month1button">1 month</a></td></tr>
</table>
</div>
<br>
<h3>Breakdown: Speedometer 3 Likely Candidates</h3>
<table class="styled-table" id="tableLikely">
<col>
<col>
<colgroup span="2"></colgroup>
<col>
<colgroup span="2"></colgroup>
<col>
<thead>
<tr><th rowspan="2"></th><th colspan="5" scope="colgroup">Total time (Lower is better)<br />1wk moving average</th><th colspan="4">Difference</th><th colspan="2" scope="colgroup" id="timewindow">6wk prior</th><th rowspan="2">Difference</th><th rowspan="2">Firefox<br />Evolution</th></tr>
<tr><th scope="col">Firefox<br />Nightly</th><th scope="col">Chrome<br />Release</th><th scope="col">Chromium-<br />As-Release</th><th scope="col">Safari<br/>Release</th><th scope="col">Safari<br />TP</th><th scope="col">Chrome<br />Release</th><th scope="col">Chromium-<br />As-Release</th><th scope="col">Safari<br/>Release</th><th scope="col">Safari<br />TP</th><th scope="col">Firefox<br />Nightly</th><th scope="col">Chrome<br />Release</th></tr>
</thead>
<tbody class="tableBody" id="tableBodyLikely">
</tbody>
</table>
</center>
</div> <!-- content -->
</div> <!-- main-content -->
<script src="assets/main-ui.js"></script>
</body>
</html>