perfview.html (621 lines of code) (raw):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pageload Comparison</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
.summary-table {
width: 100%;
margin-top: 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 10px;
}
th, td {
padding: 10px;
border-bottom: 1px solid #ddd;
font-size: 14px;
word-wrap: break-word;
}
th {
background-color: #f2f2f2;
}
td {
font-family: 'Courier New', monospace;
}
tr:hover {
background-color: #f0f0f0;
}
th:last-child, td:last-child {
border-right: none;
}
.content {
margin-left: 260px;
margin-right: 40px;
padding: 20px;
align-items: center;
}
.sidebar {
width: 220px;
padding: 20px;
background-color: #333;
color: white;
position: fixed;
top: 0;
left: 0;
height: 100%;
}
.sidebar a {
display: block;
color: white;
padding: 10px 0;
text-decoration: none;
}
.sidebar a:hover {
background-color: #575757;
}
.sidebar .dropdown {
margin-bottom: 20px;
}
.dropdown select {
width: 100%;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
font-size: 16px;
}
.dropdown input {
width: 40%;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
font-size: 16px;
}
.row {
display: flex;
width: 100%;
margin-bottom: 30px;
align-items: center;
justify-content: center;
}
.content-title {
font-size: 30px;
font-weight: bold;
margin-bottom: 30px;
text-align: center;
width: 100%;
}
.row-title {
font-size: 32px;
font-weight: bold;
margin-bottom: 10px;
border-bottom: 3px double #000;
text-align: center;
width: 100%;
}
.content-row {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 20px;
}
.canvas-column canvas {
width: 100%;
height: 600px;
background-color: white;
border-radius: 1px;
}
.visual-progress-container {
border-radius: 1px;
margin-top: 10px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
background-color: #ffffff;
border: 1px solid #ddd;
border-radius: 10px;
}
th, td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #ddd;
font-size: 14px;
word-wrap: break-word;
}
th {
background-color: #f2f2f2;
}
tr:hover {
background-color: #f0f0f0;
cursor: pointer;
}
.table-container {
width: 1000px;
border: none;
margin: 0 auto;
}
.video-container {
height: 400px;
border: none;
margin: 0 auto;
align-items: center;
}
.filmstrip-container {
display: flex;
flex-direction: row;
overflow-x: auto;
white-space: nowrap;
width: 100%;
border: 1px solid;
max-width: 1000px;
margin: 0 auto;
}
.filmstrip-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.filmstrip-image {
width: auto;
height: 200px;
border: 1px solid #ddd;
}
.timestamp-container {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 10px;
}
.timestamp {
font-size: 14px;
color: #666;
text-align: center;
margin-bottom: 5px;
}
.metriclabel {
font-size: 14px;
color: #666;
text-align: center;
margin-bottom: 5px;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
padding-top: 60px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.8);
}
.modal-content {
margin: auto;
display: block;
max-width: 80%;
max-height: 80%;
border: 2px solid #fff;
border-radius: 5px;
}
.close {
position: absolute;
top: 20px;
right: 35px;
color: #fff;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
.caption {
text-align: center;
color: #ccc;
font-size: 18px;
padding: 10px 0;
}
</style>
<script>
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
// Get os and percentile parameters, if they exist
const os = urlParams.get('os');
if (os) {
window.platformStatus = os;
} else {
window.platformStatus = 'windows-11';
}
// Set some defaults.
window.allCharts = [];
function setPlatform(platform) {
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('os', platform);
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
window.history.replaceState(null, '', newUrl);
window.platformStatus = platform;
displayAllContent();
}
function displayVisualProgress(type) {
const platform = window.platformStatus;
window.metrics.forEach(metric => {
const filteredData = window.data.filter(row => row.test === metric && row.platform === platform);
const metricData = filteredData[0];
const chromeData = metricData.chrome.metrics[type+'Progress'];
const firefoxData = metricData.firefox.metrics[type+'Progress'];
const filmstripTimestamps = Object.keys(metricData.chrome.filmstrip).map(Number); // Convert to numbers
// Interpolation helper
const interpolateVisualProgress = (timestamps, visualProgress) => {
const interpolatedProgress = [];
// Helper function to find visual progress at a given timestamp
const findProgressAt = (timestamp) => {
for (let i = 0; i < visualProgress.length - 1; i++) {
const start = visualProgress[i];
const end = visualProgress[i + 1];
if (timestamp >= start.timestamp && timestamp <= end.timestamp) {
// Linear interpolation formula
//const fraction = (timestamp - start.timestamp) / (end.timestamp - start.timestamp);
//return start.percent + fraction * (end.percent - start.percent);
return start.percent;
}
}
return timestamp < visualProgress[0].timestamp ? visualProgress[0].percent : visualProgress[visualProgress.length - 1].percent;
};
// Interpolate progress for each filmstrip timestamp
timestamps.forEach(timestamp => {
interpolatedProgress.push(findProgressAt(timestamp));
});
return interpolatedProgress;
};
// Interpolate both Firefox and Chrome's visual progress over filmstrip timestamps
const interpolatedChromeProgress = interpolateVisualProgress(filmstripTimestamps, chromeData);
const interpolatedFirefoxProgress = interpolateVisualProgress(filmstripTimestamps, firefoxData);
const siElement = document.getElementById(`${metric}-${type}`);
siElement.innerHTML = '';
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 200;
siElement.appendChild(canvas);
const ctx = canvas.getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: filmstripTimestamps,
datasets: [
{
label: 'Chrome',
data: interpolatedChromeProgress,
borderColor: 'rgba(54, 162, 235, 1)', // Blue
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderWidth: 2,
fill: false,
},
{
label: 'Firefox',
data: interpolatedFirefoxProgress,
borderColor: 'rgba(255, 99, 132, 1)', // Red
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderWidth: 2,
fill: false,
}
]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right',
},
title: {
display: true,
text: `${type} (${metric})`,
font: {
size: 14
}
}
},
scales: {
x: {
title: {
display: true,
text: 'Timestamp (s)',
}
},
y: {
title: {
display: true,
text: 'Visual Progress (%)',
},
min: 0,
max: 140,
}
}
}
});
});
}
function displayTable() {
const platform = window.platformStatus;
window.metrics.forEach(metric => {
const filteredData = window.data.filter(row => row.test === metric && row.platform === platform);
const metricData = filteredData[0];
const chromeData = {
"FirstContentfulPaint": metricData.chrome.metrics.fcp.median,
"LargestContentfulPaint": metricData.chrome.metrics.largestContentfulPaintrenderTime.median,
"SpeedIndex": metricData.chrome.metrics.SpeedIndex.median,
"VisualComplete85": metricData.chrome.metrics.VisualComplete85.median,
"domComplete": metricData.chrome.metrics.domComplete.median,
"loadEventEnd": metricData.chrome.metrics.loadEventEnd.median
};
const firefoxData = {
"FirstContentfulPaint": metricData.firefox.metrics.fcp.median,
"LargestContentfulPaint": metricData.firefox.metrics.largestContentfulPaintrenderTime.median,
"SpeedIndex": metricData.firefox.metrics.SpeedIndex.median,
"VisualComplete85": metricData.firefox.metrics.VisualComplete85.median,
"domComplete": metricData.firefox.metrics.domComplete.median,
"loadEventEnd": metricData.firefox.metrics.loadEventEnd.median
};
const tableElement = document.getElementById(`${metric}-table`);
// Create the table headings
const table = document.createElement('table');
table.className = 'comparison-table';
table.innerHTML = `
<thead>
<tr>
<th>Metric</th>
<th>Chrome</th>
<th>Firefox</th>
<th>Difference</th>
</tr>
</thead>
<tbody>
</tbody>
`;
// Populate the table body with data
const tbody = table.querySelector('tbody');
Object.keys(chromeData).forEach(key => {
const chromeValue = chromeData[key];
const firefoxValue = firefoxData[key];
const difference = ((chromeValue - firefoxValue) / firefoxValue * 100).toFixed(2);
let differenceColor = '';
if (difference > 10) {
differenceColor = 'green';
} else {
differenceColor = 'red';
}
const row = document.createElement('tr');
row.innerHTML = `
<td>${key}</td>
<td>${chromeValue}</td>
<td>${firefoxValue}</td>
<td style="color:${differenceColor}">${difference}%</td>
`;
tbody.appendChild(row);
});
tableElement.innerHTML = '';
tableElement.appendChild(table);
});
}
function displayFilmstrips() {
const platform = window.platformStatus;
window.metrics.forEach(metric => {
const filteredData = window.data.filter(row => row.test === metric && row.platform === platform);
const filmstripData = filteredData[0];
const filmstripElement = document.getElementById(`${metric}-filmstrip`);
filmstripElement.innerHTML = '';
// Get the list of timestamps (assumes Chrome and Firefox have the same timestamps)
let timestamps;
const firefox_timestamps = Object.keys(filmstripData.firefox.filmstrip);
const chrome_timestamps = Object.keys(filmstripData.chrome.filmstrip);
if (firefox_timestamps.length > chrome_timestamps.length) {
timestamps = chrome_timestamps;
} else {
timestamps = firefox_timestamps;
}
timestamps.forEach(timestamp => {
// Create a container for each timestamp
const timestampContainer = document.createElement('div');
timestampContainer.className = 'timestamp-container';
// Create a timestamp label
const timestampLabel = document.createElement('div');
timestampLabel.className = 'timestamp';
timestampLabel.textContent = `${timestamp} ms`;
timestampContainer.appendChild(timestampLabel);
let filmstripprefix = "filmstrip/";
// Create Firefox image element
const firefoxImage = document.createElement('img');
ffisrc = filmstripData.firefox.filmstrip[timestamp];
firefoxImage.src = filmstripprefix.concat(ffisrc);
firefoxImage.alt = `Firefox Frame at ${timestamp} ms`;
firefoxImage.className = 'filmstrip-image';
timestampContainer.appendChild(firefoxImage);
// Create a Firefox metric label
const firefoxMetricLabel = document.createElement('div');
firefoxMetricLabel.className = 'metriclabel';
firefoxMetricLabel.textContent = ``;
timestampContainer.appendChild(firefoxMetricLabel);
// Create Chrome image element
const chromeImage = document.createElement('img');
cfisrc = filmstripData.chrome.filmstrip[timestamp];
chromeImage.src = filmstripprefix.concat(cfisrc);
chromeImage.alt = `Chrome Frame at ${timestamp} ms`;
chromeImage.className = 'filmstrip-image';
timestampContainer.appendChild(chromeImage);
// Create a Chrome metric label
const chromeMetricLabel = document.createElement('div');
chromeMetricLabel.className = 'metriclabel';
chromeMetricLabel.textContent = ``;
timestampContainer.appendChild(chromeMetricLabel);
// Attach click event listeners for modal functionality
[firefoxImage, chromeImage].forEach(img => {
img.onclick = function() {
const modal = document.getElementById("imageModal");
const enlargedImage = document.getElementById("enlargedImage");
const caption = document.getElementById("caption");
modal.style.display = "block";
enlargedImage.src = this.src;
caption.textContent = this.alt;
};
});
// Append the container to the filmstrip row
filmstripElement.appendChild(timestampContainer);
});
});
}
function displayVideos() {
const platform = window.platformStatus;
window.metrics.forEach(metric => {
const filteredData = window.data.filter(row => row.test === metric && row.platform === platform);
const metricData = filteredData[0];
const videoPath = metricData.video_side_by_side;
let videoHTML = `
<video controls height="400">
<source src="videos/${videoPath}" type="video/mp4">
</video>
`;
document.getElementById(`${metric}-video`).innerHTML = videoHTML;
});
}
function displayAllContent() {
displayTable();
displayVideos();
displayFilmstrips();
displayVisualProgress('SpeedIndex');
displayVisualProgress('PerceptualSpeedIndex');
displayVisualProgress('ContentfulSpeedIndex');
}
function loadData(dataUrl) {
fetch(dataUrl)
.then(response => response.json())
.then(data => {
window.data = data;
displayAllContent();
})
.catch(error => console.error('Error loading the JSON file:', error));
}
function generateContent(dataUrl) {
const platformInput = document.getElementById('platform-select');
platformInput.value = window.platformStatus;
loadData(dataUrl);
}
function createSidebarSections() {
var sectionsHTML = '';
window.metrics.forEach(metric => {
sectionsHTML += `<a href="#${metric}-section">${metric}</a>\n`;
});
document.querySelector('.sections').innerHTML = sectionsHTML;
}
function createPageloadContent() {
let pageloadHTML = '';
window.metrics.forEach(metric => {
pageloadHTML += `
<div class="row-title" id="${metric}-section">${metric}</div>
<div class="row">
<div class="table-container" id="${metric}-table">
</div>
</div>
<div class="row">
<div class="video-container" id="${metric}-video">
</div>
</div>
<div class="row">
<div class="filmstrip-container" id="${metric}-filmstrip">
<div id="firefox-filmstrip" class="filmstrip-row"></div>
<div id="chrome-filmstrip" class="filmstrip-row"></div>
</div>
</div>
<div class="row">
<div class="visual-progress-container" id="${metric}-SpeedIndex">
</div>
</div>
<div class="row">
<div class="visual-progress-container" id="${metric}-PerceptualSpeedIndex">
</div>
</div>
<div class="row">
<div class="visual-progress-container" id="${metric}-ContentfulSpeedIndex">
</div>
</div>
`;
});
document.querySelector('.pageload-content').innerHTML = pageloadHTML;
}
document.addEventListener("DOMContentLoaded", () => {
const dataUrl = 'data.json';
generateContent(dataUrl);
// Add horizontal scroll behavior to all filmstrip containers
document.querySelectorAll('.filmstrip-container').forEach(container => {
container.addEventListener('wheel', (event) => {
event.preventDefault();
container.scrollLeft += event.deltaY;
});
});
const closeModal = document.getElementById("closeModal");
const modal = document.getElementById("imageModal");
// Close modal when close button is clicked
closeModal.onclick = function() {
modal.style.display = "none";
};
// Close modal when clicking outside the image
modal.onclick = function(event) {
if (event.target === modal) {
modal.style.display = "none";
}
};
// Close modal when pressing escape
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' || event.key === 'Esc') {
modal.style.display = "none";
}
});
});
</script>
</head>
<body>
<div class="sidebar">
<div class="dropdown">
<label for="platform-select">Platform:</label>
<select id="platform-select" onchange="setPlatform(this.value)">
<option value="linux-18">Linux</option>
<option value="windows-11">Windows</option>
<!--
<option value="android">Android</option>
-->
</select>
</div>
<div class="sections">
</div>
</div>
<div id="imageModal" class="modal">
<span id="closeModal" class="close">×</span>
<img id="enlargedImage" class="modal-content">
<div id="caption" class="caption"></div>
</div>
<div class="content">
<div class="pageload-content">
</div>
</div>
<script>
var metrics = [
"docs_google_sharing", "en_wikipedia_barack_obama", "mail_yahoo", "twitter_BarackObama", "amazon_nb_sb_noss_1", "bing_barack_obama", "cnn_index_html", "fandom_fallout_76", "instagram",
];
// Create sidebar sections
createSidebarSections(metrics);
// Create main content
createPageloadContent(metrics);
</script>
</body>
</html>