index.html (672 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>Performance Bugs</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="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> <link rel="stylesheet" href="assets/main-ui-style.css"> <style> .row { width: 90%; margin-bottom: 30px; } .row-title { font-size: 20px; font-weight: bold; margin-bottom: 10px; border-bottom: 3px double #000; padding-bottom: 5px; text-align: center; } .stats-row { display: flex; justify-content: space-between; gap: 20px; align: center; width: 300px; } .charts-row { display: flex; justify-content: space-between; gap: 20px; } .metrics-container { width: 100%; margin-bottom: 30px; } .metrics { display: flex; justify-content: space-around; width: 100%; text-align: center; } .metric-column { display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: #e0e0e0; border-radius: 10px; padding: 20px; margin: 0 10px; flex: 1; cursor: pointer; } .metric-title { font-size: 20px; margin-bottom: 10px; } .metric-number { font-size: 50px; } .canvas-column canvas { height: 450px; background-color: white; border-radius: 10px; } .sub-row { flex: 1; } 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; } .bug-table-container { table-layout: fixed; margin-bottom: 30px; max-height: 500px; overflow-y: auto; } .search-container { width: 100%; display: flex; justify-content: center; margin-bottom: 20px; } #search-box { width: 50%; padding: 10px; border-radius: 5px; } </style> <script> function displayMetrics() { const regressionCount = window.regressions.length; const untriagedCount = window.untriaged.length; const highImpactCount = window.highImpact.length; document.getElementById('regression-count').textContent = regressionCount; document.getElementById('untriaged-count').textContent = untriagedCount; document.getElementById('high-impact-count').textContent = highImpactCount; } 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 filterRegressionTable() { // Split out any terms if a comma was used. const searchInput = document.getElementById('search-box').value.toLowerCase(); const searchTerms = searchInput.split(',').map(term => term.trim()); const table = document.getElementById('regression-table'); const rows = table.getElementsByTagName('tbody')[0].getElementsByTagName('tr'); let count = 0; Array.from(rows).forEach(row => { const summaryCell = row.getElementsByTagName('td')[4]; const summaryText = summaryCell ? summaryCell.textContent.toLowerCase() : ''; // Check if any search term matches the summary text const matches = searchTerms.some(term => summaryText.includes(term)); if (matches) { row.style.display = ''; count++; } else { row.style.display = 'none'; } }); const countElement = document.getElementById('bug-table-count'); countElement.innerText = `Results found: ${count}`; } function displayBugTables() { const regressions = window.regressions.sort((a, b) => { return (new Date(b.creation_time)) - (new Date(a.creation_time)); }); const regressionTableBody = document.querySelector('#regression-table tbody'); regressions.forEach(bug => { const row = document.createElement('tr'); const creationDate = (new Date(bug.creation_time)).toISOString().split('T')[0]; const lastChangeTime = new Date(bug.last_change_time); const currentTime = new Date(); const timeDifference = currentTime - lastChangeTime; // milliseconds const lastUpdate = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); // days row.innerHTML = ` <td>${bug.id}</td> <td>${bug.severity}</td> <td>${bug.priority}</td> <td>${bug.component}</td> <td>${bug.summary}</td> <td>${creationDate}</td> <td>${lastUpdate}</td> `; row.onclick = () => { window.top.location.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug.id}`; }; regressionTableBody.appendChild(row); }); const countElement = document.getElementById('bug-table-count'); countElement.innerText = `Results found: ${regressions.length}`; } const backgroundColorPlugin = { id: 'backgroundColor', beforeDraw: (chart) => { const {ctx, width, height} = chart; ctx.fillStyle = '#ececec'; ctx.fillRect(0, 0, width, height); } }; function displayOSChart() { const os_counts = window.allBugs.reduce((acc, bug) => { acc[bug.op_sys] = (acc[bug.op_sys] || 0) + 1; return acc; }, {}); const labels = Object.keys(os_counts).map(key => `${key} (${os_counts[key]})`); const data = Object.values(os_counts); const navigateToBugzilla = (label) => { const op_sys = label.split(' (')[0]; const url = `https://bugzilla.mozilla.org/buglist.cgi?f1=cf_performance_impact&o1=isnotempty&query_format=advanced&bug_status=__open__&op_sys=${encodeURIComponent(op_sys)}&f3=cf_performance_impact&o3=notequals&v3=none`; window.top.location.href = url; }; const ctx = document.getElementById('os-canvas').getContext('2d'); const osChart = new Chart(ctx, { type: 'pie', data: { labels: labels, datasets: [{ data: data }] }, options: { responsive: true, onClick: (event, elements) => { if (elements.length > 0) { const chartElement = elements[0]; const label = osChart.data.labels[chartElement.index]; navigateToBugzilla(label); } }, plugins: { legend: { position: 'right', onClick: (e, legendItem, legend) => { const label = legendItem.text; navigateToBugzilla(label); }, labels: { boxWidth: 20, maxWidth: 300, font: { size: 14 } } } } }, plugins: [backgroundColorPlugin] }); } function displayComponentTable() { const componentCounts = window.allBugs.reduce((acc, bug) => { acc[bug.component] = (acc[bug.component] || 0) + 1; return acc; }, {}); const totalBugs = window.allBugs.length; const components = Object.keys(componentCounts).map(component => ({ name: component, count: componentCounts[component], percentage: ((componentCounts[component] / totalBugs) * 100).toFixed(2) })); components.sort((a, b) => b.count - a.count); const tableBody = document.querySelector('#component-table tbody'); components.forEach(component => { const row = document.createElement('tr'); row.innerHTML = ` <td>${component.name}</td> <td>${component.count}</td> <td>${component.percentage}%</td> `; row.onclick = () => { const url = `https://bugzilla.mozilla.org/buglist.cgi?f1=cf_performance_impact&o1=isnotempty&query_format=advanced&bug_status=__open__&component=${encodeURIComponent(component.name)}&f3=cf_performance_impact&o3=notequals&v3=none`; window.top.location.href = url; }; tableBody.appendChild(row); }); } function displayHighImpactChart() { const highImpactBugs = window.highImpact; const componentCounts = highImpactBugs.reduce((acc, bug) => { acc[bug.component] = (acc[bug.component] || 0) + 1; return acc; }, {}); const labels = Object.keys(componentCounts).map(key => `${key} (${componentCounts[key]})`); const data = Object.values(componentCounts); const navigateToBugzilla = (label) => { const component = label.split(' (')[0]; const url = `https://bugzilla.mozilla.org/buglist.cgi?f1=cf_performance_impact&o1=equals&v1=high&query_format=advanced&bug_status=__open__&component=${encodeURIComponent(component)}`; window.top.location.href = url; }; const ctx = document.getElementById('high-impact-canvas').getContext('2d'); const highImpactComponentChart = new Chart(ctx, { type: 'pie', data: { labels: labels, datasets: [{ data: data, }] }, options: { responsive: true, onClick: (event, elements) => { if (elements.length > 0) { const chartElement = elements[0]; const label = highImpactComponentChart.data.labels[chartElement.index]; navigateToBugzilla(label); } }, plugins: { legend: { position: 'right', onClick: (e, legendItem, legend) => { const label = legendItem.text; navigateToBugzilla(label); }, labels: { boxWidth: 20, maxWidth: 300, font: { size: 12 } } } } }, plugins: [backgroundColorPlugin] }); } function displayComponentChart() { const componentCounts = window.allBugs.reduce((acc, bug) => { acc[bug.component] = (acc[bug.component] || 0) + 1; return acc; }, {}); const labels = Object.keys(componentCounts).map(key => `${key} (${componentCounts[key]})`); const data = Object.values(componentCounts); const navigateToBugzilla = (label) => { const component = label.split(' (')[0]; const url = `https://bugzilla.mozilla.org/buglist.cgi?f1=cf_performance_impact&o1=isnotempty&query_format=advanced&bug_status=__open__&component=${encodeURIComponent(component)}&f3=cf_performance_impact&o3=notequals&v3=none`; window.top.location.href = url; }; const ctx = document.getElementById('component-canvas').getContext('2d'); const componentChart = new Chart(ctx, { type: 'pie', data: { labels: labels, datasets: [{ data: data, }] }, options: { responsive: true, onClick: (event, elements) => { if (elements.length > 0) { const chartElement = elements[0]; const label = componentChart.data.labels[chartElement.index]; navigateToBugzilla(label); } }, plugins: { legend: { position: 'bottom', onClick: (e, legendItem, legend) => { const label = legendItem.text; navigateToBugzilla(label); }, labels: { boxWidth: 20, maxWidth: 300, font: { size: 12 } } } } }, plugins: [backgroundColorPlugin] }); } function displayImpactChart() { const highImpactLength = window.highImpact.length; const medImpactLength = window.medImpact.length; const lowImpactLength = window.lowImpact.length; const untriagedLength = window.untriaged.length; const needinfoLength = window.needinfo.length; const labels = [ `High (${highImpactLength})`, `Medium (${medImpactLength})`, `Low (${lowImpactLength})`, `Untriaged (${untriagedLength})`, `Pending Needinfo (${needinfoLength})`, ]; const navigateToBugzilla = (label) => { let impact = label.split(' ')[0]; // Perf-triage has a dedicated search query. if (impact === 'Untriaged') { const url = 'https://bugzilla.mozilla.org/buglist.cgi?query_based_on=Performance%20Triage&query_format=advanced&resolution=---&f1=OP&f2=cf_performance_impact&o2=equals&v2=%3F&f3=CP&f4=OP&f5=product&o5=equals&v5=Core&f6=component&o6=equals&v6=Performance&f7=keywords&o7=notsubstring&v7=meta&f8=cf_performance_impact&o8=isempty&f9=CP&f10=OP&f11=cf_performance_impact&o11=equals&v11=pending-needinfo&f12=flagtypes.name&o12=notsubstring&v12=needinfo&f13=CP&j_top=OR&order=Bug%20Number&include_fields=id&include_fields=summary&include_fields=status' window.top.location.href = url; } else { if (impact === 'Pending') { impact = 'pending-needinfo'; } const url = `https://bugzilla.mozilla.org/buglist.cgi?f1=cf_performance_impact&v1=${impact}&o1=equals&query_format=advanced&bug_status=__open__` window.top.location.href = url; } }; const ctx = document.getElementById('impact-canvas').getContext('2d'); const impactChart = new Chart(ctx, { type: 'pie', data: { labels: labels, datasets: [{ data: [highImpactLength, medImpactLength, lowImpactLength, untriagedLength, needinfoLength], backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#4D5360'], hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#4D5360'] }] }, options: { responsive: true, onClick: (event, elements) => { if (elements.length > 0) { const chartElement = elements[0]; const label = impactChart.data.labels[chartElement.index]; navigateToBugzilla(label); } }, plugins: { legend: { position: 'right', onClick: (e, legendItem, legend) => { const label = legendItem.text; navigateToBugzilla(label); }, labels: { boxWidth: 20, maxWidth: 300, font: { size: 14 } }, } } }, plugins: [backgroundColorPlugin] }); } function displayPerfKeywordsChart() { const perfCounts = window.allBugs.reduce((acc, bug) => { let hasPerfKeyword = false; bug.keywords.forEach(keyword => { if (keyword.startsWith('perf:')) { hasPerfKeyword = true; acc[keyword] = (acc[keyword] || 0) + 1; } }); if (!hasPerfKeyword) { acc['unspecified'] = (acc['unspecified'] || 0) + 1; } return acc; }, {}); const labels = Object.keys(perfCounts).map(key => `${key.replace('perf:', '')} (${perfCounts[key]})`); const data = Object.values(perfCounts); const navigateToBugzilla = (label) => { const value = label.split(' (')[0]; if (value === 'unspecified') { const url = 'https://bugzilla.mozilla.org/buglist.cgi?f2=cf_performance_impact&bug_status=__open__&f1=keywords&query_format=advanced&o1=notsubstring&v1=perf%3A&o2=isnotempty&f3=cf_performance_impact&o3=notequals&v3=none'; window.top.location.href = url; } else { const keyword = encodeURIComponent(`perf:${value}`); const url = `https://bugzilla.mozilla.org/buglist.cgi?o1=substring&query_format=advanced&bug_status=__open__&f2=cf_performance_impact&o2=isnotempty&v1=${keyword}&f1=keywords&f3=cf_performance_impact&o3=notequals&v3=none` window.top.location.href = url; } }; const ctx = document.getElementById('keyword-canvas').getContext('2d'); const perfKeywordChart = new Chart(ctx, { type: 'pie', data: { labels: labels, datasets: [{ data: data, }] }, options: { responsive: true, onClick: (event, elements) => { if (elements.length > 0) { const chartElement = elements[0]; const label = perfKeywordChart.data.labels[chartElement.index]; navigateToBugzilla(label); } }, plugins: { legend: { position: 'right', onClick: (e, legendItem, legend) => { const label = legendItem.text; navigateToBugzilla(label); }, labels: { boxWidth: 20, maxWidth: 300, font: { size: 14 } } } } }, plugins: [backgroundColorPlugin] }); } function loadBugzillaData() { const bugzillaUrl = 'https://raw.githubusercontent.com/mozilla/performance-data/refs/heads/main/bugzilla-data-all.json'; fetch(bugzillaUrl) .then(response => response.json()) .then(data => { // bugzilla sorts from oldest->newest, so reverse the arrays. window.highImpact = data.high.bugs.reverse(); window.medImpact = data.medium.bugs.reverse(); window.lowImpact = data.low.bugs.reverse(); window.untriaged = data.untriaged.bugs.reverse(); window.needinfo = data.needinfo.bugs.reverse(); window.regressions = data.regressions.bugs.reverse(); window.allBugs = [...window.highImpact, ...window.medImpact, ...window.lowImpact, ...window.needinfo, ...window.regressions]; window.allBugs.sort((a, b) => (new Date(b.last_change_time)) - (new Date(a.last_change_time))); // Remove any duplicate bugs. (bugs => { const seen = new Set(); return bugs.filter(bug => { if (seen.has(bug.id)) { return false; } else { seen.add(bug.id); return true; } }); })(window.allBugs); displayMetrics(); displayPerfKeywordsChart(); displayImpactChart(); displayHighImpactChart(); displayComponentTable(); displayOSChart(); displayBugTables(); }) .catch(error => console.error('Error fetching Bugzilla data:', error)); } document.addEventListener("DOMContentLoaded", () => { loadBugzillaData(); document.getElementById('search-box').addEventListener('keyup', filterRegressionTable); }); </script> </head> <body> <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"> <div class="row"> <div class="row-title">Current Bug Status</div> <div class="metrics-container"> <div class="metrics"> <div class="metric-column" onclick="window.top.location.href='https://bugzilla.mozilla.org/buglist.cgi?query_based_on=Performance%20Triage&query_format=advanced&resolution=---&f1=OP&f2=cf_performance_impact&o2=equals&v2=%3F&f3=CP&f4=OP&f5=product&o5=equals&v5=Core&f6=component&o6=equals&v6=Performance&f7=keywords&o7=notsubstring&v7=meta&f8=cf_performance_impact&o8=isempty&f9=CP&f10=OP&f11=cf_performance_impact&o11=equals&v11=pending-needinfo&f12=flagtypes.name&o12=notsubstring&v12=needinfo&f13=CP&j_top=OR&order=Bug%20Number&include_fields=id&include_fields=summary&include_fields=status'"> <div class="metric-title">Untriaged</div> <div class="metric-number" id="untriaged-count">0</div> </div> <div class="metric-column" onclick="window.top.location.href='https://bugzilla.mozilla.org/buglist.cgi?query_format=advanced&o1=substring&f1=keywords&classification=Client%20Software&classification=Developer%20Infrastructure&classification=Components&classification=Server%20Software&classification=Other&o2=anywordssubstr&f2=keywords&v2=perf-alert&v1=regression&resolution=---'"> <div class="metric-title">Regressions</div> <div class="metric-number" id="regression-count">0</div> </a> </div> <div class="metric-column" onclick="window.top.location.href=`https://bugzilla.mozilla.org/buglist.cgi?f1=cf_performance_impact&o1=equals&v1=high&query_format=advanced&bug_status=__open__`"> <div class="metric-title">High Impact</div> <div class="metric-number" id="high-impact-count">0</div> </div> </div> </div> </div> <div class="row"> <div class="row-title" id="regression-title">Open Perf-Alert Regressions</div> <div class="search-container"> <input type="text" id="search-box" placeholder="Search by summary"> </div> <div class="bug-table-container"> <div id="bug-table-count">Results found:</div> <table id="regression-table"> <thead> <tr> <th onclick="sortTable(0, 'regression-table')">ID</th> <th onclick="sortTable(1, 'regression-table')">Severity</th> <th onclick="sortTable(2, 'regression-table')">Priority</th> <th onclick="sortTable(3, 'regression-table')">Component</th> <th onclick="sortTable(4, 'regression-table')">Summary</th> <th onclick="sortTable(5, 'regression-table')">Creation Date</th> <th onclick="sortTable(6, 'regression-table')">Last Update (days)</th> </tr> </thead> <tbody></tbody> </table> </div> </div> <div class="row"> <div class="charts-row"> <div class="sub-row"> <div class="row-title">Performance Impact Distribution</div> <div class="charts-row"> <div class="canvas-column"> <canvas id="impact-canvas"></canvas> </div> </div> </div> <div class="sub-row"> <div class="row-title">High Impact Distribution</div> <div class="charts-row"> <div class="canvas-column"> <canvas id="high-impact-canvas"></canvas> </div> </div> </div> </div> </div> <div class="row"> <div class="charts-row"> <div class="sub-row"> <div class="row-title">OS Distribution</div> <div class="charts-row"> <div class="canvas-column"> <canvas id="os-canvas"></canvas> </div> </div> </div> <div class="sub-row"> <div class="row-title">Performance Type Distribution</div> <div class="charts-row"> <div class="canvas-column"> <canvas id="keyword-canvas"></canvas> </div> </div> </div> </div> </div> <div class="row"> <div class="row-title">Component Distribution</div> <div class="bug-table-container" style="max-height: 500px"> <table id="component-table"> <thead> <tr> <th>Component</th> <th>Number of Bugs</th> <th>Percentage of Bugs</th> </tr> </thead> <tbody></tbody> </table> </div> </div> </div> <!-- content --> </div> <!-- main-content --> <script src="assets/main-ui.js"></script> </body> </html>