diff --git a/.gitignore b/.gitignore index 06a75af4b65b66c59b6f5ae7c2f33f8f22e24cc7..3e973e62289455910dd4a938d062aa0f339176bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -postgres/ \ No newline at end of file +postgres/ +*.csv diff --git a/visualize.html b/visualize.html new file mode 100644 index 0000000000000000000000000000000000000000..6febae2549d22b36c3cfc3b4f8f92761a829a415 --- /dev/null +++ b/visualize.html @@ -0,0 +1,285 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <title>Performance Visualization</title> + <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js"></script> + <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script> + + + <style> + body { + font-family: Arial, sans-serif; + margin: 0 auto; + padding: 20px; + } + + #charts-container { + display: flex; + width: 100%; + flex-direction: column; + gap: 50px; + } + + #chart-container, + #error-chart-container { + width: 100%; + height: 600px; + } + + canvas { + display: block; + } + </style> +</head> + +<body> + <div id="charts-container"> + <div> + <h2>Latency `requests.get(url, timeout=20)`</h2> + <button id="filter60">Filter sub-60s response time</button> + <button id="filter10">Filter sub-10s response time</button> + <button id="filter1">Filter sub-1s response time</button> + <button id="show100">Show 100%</button> + <button id="show10">Show 10%</button> + <button id="show5">Show 5%</button> + <div id="chart-container"> + <canvas id="performanceChart"></canvas> + </div> + </div> + + + <h2>Error Rate Over Time (HTTP status != 200)</h2> + <div id="error-chart-container"> + <canvas id="errorRateChart"></canvas> + </div> + </div> + + <script> + // Ensure Papa is loaded before running the script + function initVisualization() { + let outlierFilter = 10; // Initial state for the 10s limit + let showMod = 10; + let chart; // Reference to the chart instance + + fetch('exported.csv') + .then(response => response.text()) + .then(csvText => { + const parsed = Papa.parse(csvText, { header: true, dynamicTyping: true }); + + document.getElementById('filter60').addEventListener('click', () => { + outlierFilter = 60; + updateChart(); + }); + document.getElementById('filter10').addEventListener('click', () => { + outlierFilter = 10; + updateChart(); + }); + document.getElementById('filter1').addEventListener('click', () => { + outlierFilter = 1; + updateChart(); + }); + + document.getElementById('show100').addEventListener('click', () => { + showMod = 1; + updateChart(); + }); + document.getElementById('show10').addEventListener('click', () => { + showMod = 10; + updateChart(); + }); + document.getElementById('show5').addEventListener('click', () => { + showMod = 20; + updateChart(); + }); + + function updateChart() { + const filteredData = parsed.data.filter(row => row.duration <= outlierFilter); + + const uniqueUrls = [...new Set(filteredData.map(row => row.url))]; + + const datasets = uniqueUrls.map((url, index) => { + const urlData = filteredData.filter(row => row.url === url); + const sampledData = urlData.filter((_, idx) => idx % showMod === 0); + return { + label: url, + data: sampledData.map(row => ({ + x: new Date(row.timestamp), + y: row.duration + })), + borderColor: `hsl(${index * 360 / uniqueUrls.length}, 70%, 50%)`, + backgroundColor: `hsla(${index * 360 / uniqueUrls.length}, 70%, 50%, 0.1)`, + borderWidth: 1, + pointRadius: 1, + pointHoverRadius: 5 + }; + }); + + // Update the chart data + chart.data.datasets = datasets; + chart.options.plugins.tooltip = showMod > 10; + chart.update(); + } + + const ctx = document.getElementById('performanceChart').getContext('2d'); + chart = new Chart(ctx, { + type: 'scatter', + data: { + datasets: [] + }, + options: { + responsive: true, + plugins: { + title: { + display: true, + text: 'Response Time by URL Over Time' + }, + tooltip: { + enabled: showMod > 10 + } + }, + scales: { + x: { + type: 'time', + time: { + unit: 'day' + }, + title: { + display: true, + text: 'Timestamp' + } + }, + y: { + title: { + display: true, + text: 'Response Time (seconds)' + }, + beginAtZero: true + } + }, + animation: false + } + }); + + // Initial chart update + updateChart(); + + initErrorRateChart(parsed); + }) + .catch(error => console.error('Error loading CSV:', error)); + + } + + function initErrorRateChart(parsed) { + // Aggregate error rate per hour + const aggregateErrorRate = (data) => { + const aggregatedData = {}; + data.forEach(row => { + if (row.timestamp == null) { + return; + } + const hour = new Date(row.timestamp).toISOString().slice(0, 13); // Format: YYYY-MM-DDTHH + if (!aggregatedData[hour]) { + aggregatedData[hour] = {}; + } + if (!aggregatedData[hour][row.url]) { + aggregatedData[hour][row.url] = { total: 0, errors: 0 }; + } + aggregatedData[hour][row.url].total += 1; + if (row.status_code !== 200) { + aggregatedData[hour][row.url].errors += 1; + } + }); + + // Convert aggregated data into a chart-friendly format + const chartData = {}; + //console.log("aggregatedData size", Object.keys(aggregatedData).length); + for (const hour in aggregatedData) { + for (const url in aggregatedData[hour]) { + if (!chartData[url]) { + chartData[url] = []; + } + const total = aggregatedData[hour][url].total; + const errors = aggregatedData[hour][url].errors; + // YYYY-MM-DDTHH + const tt = new Date(hour + ":00:00Z"); + chartData[url].push({ + x: tt, + y: (errors / total) * 100 // Error rate percentage + }); + } + } + // sort by time + for (const url in chartData) { + chartData[url].sort((a, b) => a.x - b.x); + } + return chartData; + }; + + const errorRateData = aggregateErrorRate(parsed.data); + + + // Create datasets for the error rate chart + const errorRateDatasets = Object.keys(errorRateData).map((url, index) => ({ + label: url, + data: errorRateData[url], + borderColor: `hsl(${index * 360 / Object.keys(errorRateData).length}, 70%, 50%)`, + backgroundColor: `hsla(${index * 360 / Object.keys(errorRateData).length}, 70%, 50%, 0.1)`, + borderWidth: 1, + pointRadius: 1, + pointHoverRadius: 5 + })); + console.log(errorRateDatasets); + + // Create the error rate chart + const errorCtx = document.getElementById('errorRateChart').getContext('2d'); + new Chart(errorCtx, { + type: 'line', + data: { + datasets: errorRateDatasets + }, + options: { + responsive: true, + plugins: { + title: { + display: true, + text: 'Error Rate by URL Over Time (per hour)' + } + }, + scales: { + x: { + type: 'time', + time: { + unit: 'day' + }, + title: { + display: true, + text: 'Timestamp' + } + }, + y: { + title: { + display: true, + text: 'Error Rate (%)' + }, + beginAtZero: true, + //max: 100 + } + }, + animation: false + } + }); + } + + // Wait for dependencies to load + if (window.Papa && window.Chart) { + initVisualization(); + } else { + window.addEventListener('load', initVisualization); + } + </script> +</body> + +</html> \ No newline at end of file