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">&times;</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>