(entries)
| 686 | } |
| 687 | |
| 688 | function createThroughputChart(entries) { |
| 689 | const chartElement = document.getElementById("throughput-chart"); |
| 690 | if (!chartElement) return; |
| 691 | |
| 692 | const allFiles = Object.keys(entries[0].hyperfine_results); |
| 693 | |
| 694 | function buildSeries(sampledEntries) { |
| 695 | return allFiles |
| 696 | .map((file) => { |
| 697 | const dataPoints = sampledEntries |
| 698 | .map((entry) => { |
| 699 | const fileData = entry.hyperfine_results[file]; |
| 700 | if (fileData && fileData.results && fileData.results[0] && fileData.input_size) { |
| 701 | const throughputMBps = fileData.input_size / fileData.results[0].mean / (1024 * 1024); |
| 702 | return { |
| 703 | x: new Date(entry.timestamp).getTime(), |
| 704 | y: Math.round(throughputMBps * 10) / 10, |
| 705 | }; |
| 706 | } |
| 707 | return null; |
| 708 | }) |
| 709 | .filter((point) => point !== null); |
| 710 | return { |
| 711 | name: file.replace(/\./g, " ").replace(/-/g, " "), |
| 712 | data: dataPoints, |
| 713 | }; |
| 714 | }) |
| 715 | .filter((s) => s.data.length > 0); |
| 716 | } |
| 717 | |
| 718 | // Compute low/high water marks from full history (p10/p90 across all files) |
| 719 | const historyValues = buildSeries(entries) |
| 720 | .flatMap((s) => s.data.map((p) => p.y)) |
| 721 | .filter((v) => typeof v === "number" && !isNaN(v)) |
| 722 | .sort((a, b) => a - b); |
| 723 | const p10 = historyValues[Math.floor(historyValues.length * 0.1)] ?? 0; |
| 724 | const p90 = historyValues[Math.floor(historyValues.length * 0.9)] ?? 0; |
| 725 | const throughputThresholds = { bad: Math.round(p10 * 10) / 10, good: Math.round(p90 * 10) / 10 }; |
| 726 | |
| 727 | const sampled = sampleEntries(entries); |
| 728 | const series = buildSeries(sampled); |
| 729 | |
| 730 | let allValues = []; |
| 731 | series.forEach((s) => s.data.forEach((point) => allValues.push(point.y))); |
| 732 | |
| 733 | const { outliers, normal } = detectOutliers(allValues); |
| 734 | const hasOutliers = outliers.length > 0; |
| 735 | |
| 736 | const normalSeries = []; |
| 737 | const outlierSeries = []; |
| 738 | series.forEach((s) => { |
| 739 | const validValues = s.data.filter((p) => p !== null); |
| 740 | const avgValue = validValues.length > 0 ? validValues.reduce((sum, p) => sum + p.y, 0) / validValues.length : 0; |
| 741 | if (outliers.includes(avgValue) || s.data.some((p) => p !== null && outliers.includes(p.y))) { |
| 742 | outlierSeries.push(s); |
| 743 | } else { |
| 744 | normalSeries.push(s); |
| 745 | } |
no test coverage detected