`).join('');
}
function renderTimeSeriesChart(data) {
if(data.length === 0) return;
const timeBuckets = data.reduce((acc, row) => {
const bucketTime = new Date(row.timestamp);
bucketTime.setMinutes(Math.floor(bucketTime.getMinutes()/5)*5, 0, 0); // 5-min buckets
const key = bucketTime.toISOString();
if(!acc[key]) acc[key] = [];
acc[key].push(row.response_time_ms);
return acc;
}, {});
const chartData = Object.entries(timeBuckets).map(([time, times]) => {
const sortedTimes = times.sort((a,b) => a-b);
return {
x: time,
p50: getPercentile(sortedTimes, 0.50),
p95: getPercentile(sortedTimes, 0.95),
p99: getPercentile(sortedTimes, 0.99)
};
});
if(charts.time) charts.time.destroy();
charts.time = new Chart('resp-timeseries-chart', {
type: 'line',
data: {
datasets: [
{ label: 'p50', data: chartData.map(d=>({x:d.x, y:d.p50})), borderColor: '#3b82f6', tension: 0.2, pointRadius: 0 },
{ label: 'p95', data: chartData.map(d=>({x:d.x, y:d.p95})), borderColor: '#f97316', tension: 0.2, pointRadius: 0 },
{ label: 'p99', data: chartData.map(d=>({x:d.x, y:d.p99})), borderColor: '#ef4444', tension: 0.2, pointRadius: 0 }
]
},
options: { responsive: true, maintainAspectRatio: false, scales: { x: { type: 'time', time: { unit: 'minute' }, ticks:{color:'#94a3b8'} }, y: { ticks:{color:'#94a3b8'}, title:{display:true, text:'Response Time (ms)', color:'#94a3b8'} } }, plugins: { legend:{labels:{color:'#e2e8f0'}}} }
});
}
function renderEndpointTable(data) {
const endpointGroups = data.reduce((acc, row) => {
if(!acc[row.endpoint]) acc[row.endpoint] = [];
acc[row.endpoint].push(row);
return acc;
}, {});
let tableData = Object.entries(endpointGroups).map(([endpoint, rows]) => {
const successRows = rows.filter(r => r.success);
const responseTimes = successRows.map(r => r.response_time_ms).sort((a,b)=>a-b);
return {
endpoint,
count: rows.length,
avg: (responseTimes.reduce((a,b)=>a+b,0) / responseTimes.length || 0),
p95: getPercentile(responseTimes, 0.95),
errorRate: (1 - successRows.length / rows.length) * 100
};
});
tableData.sort((a,b) => {
const valA = a[endpointSort.key];
const valB = b[endpointSort.key];
if(endpointSort.order === 'asc') return valA - valB;
return valB - valA;
});
const headers = [
{ key: 'endpoint', name: 'Endpoint' }, { key: 'count', name: 'Requests' },
{ key: 'avg', name: 'Avg Time (ms)' }, { key: 'p95', name: 'p95 Time (ms)' }, { key: 'errorRate', name: 'Error Rate' }
];
elements.tableHead.innerHTML = `${headers.map(h => `${h.name}${endpointSort.key === h.key ? (endpointSort.order === 'desc' ? ' â–¼' : ' â–²') : ''} `).join('')} `;
elements.tableBody.innerHTML = tableData.map(row => `
${row.endpoint}
${row.count}
${row.avg.toFixed(0)}
${row.p95.toFixed(0)}
${row.errorRate.toFixed(1)}%
`).join('');
}
elements.tableHead.addEventListener('click', (e) => {
if(e.target.tagName === 'TH') {
const key = e.target.dataset.key;
if(endpointSort.key === key) {
endpointSort.order = endpointSort.order === 'asc' ? 'desc' : 'asc';
} else {
endpointSort.key = key;
endpointSort.order = 'desc';
}
updateDashboard();
}
});
window.respDownloadPDF = () => {
html2canvas(document.getElementById('resp-dashboard-output'), { backgroundColor: '#0f172a', scale: 2 }).then(canvas => {
const pdf = new jspdf.jsPDF({ orientation: 'landscape', unit: 'pt', format: 'a4' });
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 40, 40, pdf.internal.pageSize.getWidth() - 80, 0);
pdf.save('Application_Response_Time_Dashboard.pdf');
});
};
});
