From 0c653aea7485c7567ba3400dd46362f740843241 Mon Sep 17 00:00:00 2001 From: Mika Date: Fri, 9 Jan 2026 14:49:05 +0000 Subject: [PATCH] Add results_visualization/js/app.js --- results_visualization/js/app.js | 178 ++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 results_visualization/js/app.js diff --git a/results_visualization/js/app.js b/results_visualization/js/app.js new file mode 100644 index 0000000..8759d5b --- /dev/null +++ b/results_visualization/js/app.js @@ -0,0 +1,178 @@ +'use strict'; + +// Globale Variable zum Halten der Spike-Daten +let spikeData = []; +let filteredData = []; +let chartInstance = null; + +/** + * Ruft Spike-Daten von der API ab. + * @param {Object} params - Parameter für Filter (z. B. {time_range, event_type, cpu_id}). + * @returns {Promise} - Promise mit den Spike-Daten. + */ +async function fetchSpikeData(params = {}) { + const query = new URLSearchParams(params).toString(); + const url = query ? `/api/spike_data?${query}` : '/api/spike_data'; + try { + const response = await fetch(url); + if (!response.ok) { + console.error('Fehler beim Abruf von /api/spike_data:', response.status); + return []; + } + const data = await response.json(); + return Array.isArray(data) ? data : []; + } catch (err) { + console.error('Netzwerkfehler oder ungültige Antwort:', err); + return []; + } +} + +/** + * Initialisiert die Anwendung, UI-Elemente und Chart. + */ +function initApp() { + const eventFilter = document.getElementById('filter-event'); + const timeRangeFilter = document.getElementById('filter-time'); + const cpuFilter = document.getElementById('filter-cpu'); + + [eventFilter, timeRangeFilter, cpuFilter].forEach(el => { + if (el) el.addEventListener('change', applyFilters); + }); + + // Erste Datenladung und Chart-Setup + fetchSpikeData().then(data => { + spikeData = data; + filteredData = data; + renderChart(data); + updateStats(data); + }); +} + +/** + * Filtert Spike-Daten basierend auf Benutzerwahl und aktualisiert die Anzeige. + */ +function applyFilters() { + const eventType = document.getElementById('filter-event')?.value || ''; + const timeRange = document.getElementById('filter-time')?.value || ''; + const cpuId = document.getElementById('filter-cpu')?.value || ''; + + let params = {}; + if (eventType) params.event_type = eventType; + if (timeRange) params.time_range = timeRange; + if (cpuId) params.cpu_id = cpuId; + + fetchSpikeData(params).then(data => { + filteredData = data; + updateChart(data); + updateStats(data); + }); +} + +/** + * Rendert den Chart mit gegebenen Daten. + * @param {Array} data + */ +function renderChart(data) { + const ctx = document.getElementById('spikeChart'); + if (!ctx) return; + + const timestamps = data.map(d => new Date(d.timestamp)); + const values = data.map(d => d.value); + + if (chartInstance) { + chartInstance.destroy(); + } + + chartInstance = new Chart(ctx, { + type: 'line', + data: { + labels: timestamps, + datasets: [{ + label: 'P99-Spikes', + data: values, + borderColor: '#007acc', + backgroundColor: 'rgba(0, 122, 204, 0.2)', + pointRadius: 2, + fill: false, + tension: 0.1 + }] + }, + options: { + responsive: true, + scales: { + x: { + type: 'time', + time: { + unit: 'minute' + } + }, + y: { + beginAtZero: true + } + }, + plugins: { + legend: { + position: 'top' + } + } + } + }); +} + +/** + * Aktualisiert bestehenden Chart mit neuen Daten. + * @param {Array} data + */ +function updateChart(data) { + if (!chartInstance) { + renderChart(data); + return; + } + chartInstance.data.labels = data.map(d => new Date(d.timestamp)); + chartInstance.data.datasets[0].data = data.map(d => d.value); + chartInstance.update(); +} + +/** + * Berechnet und zeigt statistische Kennzahlen an. + * @param {Array} data + */ +function updateStats(data) { + const medianEl = document.getElementById('stat-median'); + const p99El = document.getElementById('stat-p99'); + const deltaEl = document.getElementById('stat-delta'); + + if (!data || data.length === 0) { + [medianEl, p99El, deltaEl].forEach(el => { + if (el) el.textContent = '-'; + }); + return; + } + + const values = [...data.map(d => d.value)].sort((a, b) => a - b); + const median = computePercentile(values, 50); + const p99 = computePercentile(values, 99); + const deltaTail = (p99 - median).toFixed(2); + + if (medianEl) medianEl.textContent = median.toFixed(2); + if (p99El) p99El.textContent = p99.toFixed(2); + if (deltaEl) deltaEl.textContent = deltaTail; +} + +/** + * Berechnet Perzentilwert aus sortiertem Array. + * @param {Array} sortedValues + * @param {number} percentile + * @returns {number} + */ +function computePercentile(sortedValues, percentile) { + if (sortedValues.length === 0) return 0; + const index = (percentile / 100) * (sortedValues.length - 1); + const lower = Math.floor(index); + const upper = lower + 1; + const weight = index % 1; + if (upper >= sortedValues.length) return sortedValues[lower]; + return sortedValues[lower] * (1 - weight) + sortedValues[upper] * weight; +} + +window.addEventListener('load', initApp); \ No newline at end of file