Percect cache control for static files, fix few issues with charts

This commit is contained in:
Erikas 2024-07-14 18:10:04 +03:00
parent ebed937b76
commit bd9e12a299
2 changed files with 117 additions and 604 deletions

View File

@ -2,8 +2,7 @@ package flightlesssomething
import ( import (
"bytes" "bytes"
"crypto/sha1" "fmt"
"encoding/hex"
"html/template" "html/template"
"io/fs" "io/fs"
"net/http" "net/http"
@ -100,21 +99,11 @@ func Start(c *Config) {
return return
} }
// Read file content into a byte slice // Generate ETag based on file modification time
content, err := fs.ReadFile(staticFS, "static"+filepath) etag := fmt.Sprintf("%x-%x", fileInfo.ModTime().Unix(), fileInfo.Size())
if err != nil {
c.Status(http.StatusInternalServerError)
return
}
// Generate ETag based on file content // Set ETag header
hash := sha1.New()
hash.Write(content)
etag := hex.EncodeToString(hash.Sum(nil))
// Set ETag and Cache-Control headers
c.Header("ETag", etag) c.Header("ETag", etag)
c.Header("Cache-Control", "public, max-age=3600")
// Check if the ETag matches // Check if the ETag matches
if match := c.GetHeader("If-None-Match"); match == etag { if match := c.GetHeader("If-None-Match"); match == etag {
@ -122,6 +111,13 @@ func Start(c *Config) {
return return
} }
// Read file content into a byte slice
content, err := fs.ReadFile(staticFS, "static"+filepath)
if err != nil {
c.Status(http.StatusInternalServerError)
return
}
// Serve the file with ETag and Last-Modified headers // Serve the file with ETag and Last-Modified headers
http.ServeContent(c.Writer, c.Request, fileInfo.Name(), fileInfo.ModTime(), bytes.NewReader(content)) http.ServeContent(c.Writer, c.Request, fileInfo.Name(), fileInfo.ModTime(), bytes.NewReader(content))
}) })

View File

@ -1,236 +1,71 @@
Highcharts.setOptions({ // Common chart options
chart: { const commonChartOptions = {
animation: false chart: {backgroundColor: null, style: {color: '#FFFFFF'}, animation: false},
}, title: {style: {color: '#FFFFFF', fontSize: '16px'}},
plotOptions: { subtitle: {style: {color: '#FFFFFF', fontSize: '12px'}},
series: { xAxis: {labels: {style: {color: '#FFFFFF'}}, lineColor: '#FFFFFF', tickColor: '#FFFFFF'},
animation: false yAxis: {labels: {style: {color: '#FFFFFF'}}, gridLineColor: 'rgba(255, 255, 255, 0.1)'},
} tooltip: {backgroundColor: '#1E1E1E', borderColor: '#FFFFFF', style: {color: '#FFFFFF'}},
} legend: {itemStyle: {color: '#FFFFFF'}},
}); credits: {enabled: false},
plotOptions: {series: {animation: false}}
};
// Highcharts global options
Highcharts.setOptions({chart: {animation: false}, plotOptions: {series: {animation: false}}});
var colors = Highcharts.getOptions().colors; const colors = Highcharts.getOptions().colors;
function getLineChartOptions(title, description, unit, maxY = null) { function getLineChartOptions(title, description, unit, maxY = null) {
return { return {
chart: { ...commonChartOptions,
type: 'line', chart: {...commonChartOptions.chart, type: 'line', zooming: {type: 'x'}},
backgroundColor: null, // Set background to transparent title: {...commonChartOptions.title, text: title},
style: { subtitle: {...commonChartOptions.subtitle, text: description},
color: '#FFFFFF' xAxis: {...commonChartOptions.xAxis, labels: {enabled: false}}, // Hide X-axis labels
}, yAxis: {...commonChartOptions.yAxis, max: maxY, labels: {...commonChartOptions.yAxis.labels, formatter: function() {return this.value.toFixed(2) + ' ' + unit;}}},
zooming: { tooltip: {...commonChartOptions.tooltip, pointFormat: `<span style="color:{series.color}">{series.name}</span>: <b>{point.y:.2f} ${unit}</b><br/>`},
type: 'x' plotOptions: {line: {marker: {enabled: false, symbol: 'circle', radius: 1.5, states: {hover: {enabled: true}}}, lineWidth: 1}},
} legend: {...commonChartOptions.legend, enabled: true},
},
title: {
text: title,
style: {
color: '#FFFFFF',
fontSize: '16px'
}
},
subtitle: {
text: description,
style: {
color: '#FFFFFF',
fontSize: '12px'
}
},
xAxis: {
lineColor: '#FFFFFF',
tickColor: '#FFFFFF',
labels: {
enabled: false
}
},
yAxis: {
title: {
text: null
},
labels: {
formatter: function() {
return this.value.toFixed(2) + ' ' + unit;
},
style: {
color: '#FFFFFF'
}
},
gridLineColor: 'rgba(255, 255, 255, 0.1)',
max: maxY
},
legend: {
align: 'center',
verticalAlign: 'bottom',
itemStyle: {
color: '#FFFFFF'
}
},
tooltip: {
shared: false,
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y:.2f} ' + unit + '</b><br/>', // Include unit in tooltip
backgroundColor: '#1E1E1E',
borderColor: '#FFFFFF',
style: {
color: '#FFFFFF'
}
},
plotOptions: {
line: {
marker: {
enabled: false,
symbol: 'circle',
lineColor: null,
radius: 1.5,
states: {
hover: {
enabled: true,
}
}
},
lineWidth: 1,
animation: false
}
},
credits: {
enabled: false
},
series: [], series: [],
exporting: { exporting: {buttons: {contextButton: {menuItems: ['viewFullscreen', 'printChart', 'separator', 'downloadPNG', 'downloadJPEG', 'downloadPDF', 'downloadSVG', 'separator', 'downloadCSV', 'downloadXLS']}}}
buttons: {
contextButton: {
menuItems: [
'viewFullscreen',
'printChart',
'separator',
'downloadPNG',
'downloadJPEG',
'downloadPDF',
'downloadSVG',
'separator',
'downloadCSV',
'downloadXLS'
]
}
}
}
}; };
} }
function getBarChartOptions(title, unit, maxY = null) { function getBarChartOptions(title, unit, maxY = null) {
return { return {
chart: { ...commonChartOptions,
type: 'bar', chart: {...commonChartOptions.chart, type: 'bar'},
backgroundColor: null, // Set background to transparent title: {...commonChartOptions.title, text: title},
style: { xAxis: {...commonChartOptions.xAxis, categories: [], title: {text: null}},
color: '#FFFFFF' yAxis: {...commonChartOptions.yAxis, min: 0, max: maxY, title: {text: unit, align: 'high', style: {color: '#FFFFFF'}}, labels: {...commonChartOptions.yAxis.labels, formatter: function() {return this.value.toFixed(2) + ' ' + unit;}}},
} tooltip: {...commonChartOptions.tooltip, valueSuffix: ' ' + unit, formatter: function() {return `<b>${this.point.category}</b>: ${this.y.toFixed(2)} ${unit}`;}},
}, plotOptions: {bar: {dataLabels: {enabled: true, style: {color: '#FFFFFF'}, formatter: function() {return this.y.toFixed(2) + ' ' + unit;}}}},
title: { legend: {enabled: false},
text: title,
style: {
color: '#FFFFFF',
fontSize: '16px'
}
},
xAxis: {
categories: [],
title: {
text: null
},
labels: {
style: {
color: '#FFFFFF'
}
}
},
yAxis: {
min: 0,
max: maxY,
title: {
text: unit,
align: 'high',
style: {
color: '#FFFFFF'
}
},
labels: {
overflow: 'justify',
style: {
color: '#FFFFFF'
},
formatter: function() {
return this.value.toFixed(2) + ' ' + unit;
}
},
gridLineColor: 'rgba(255, 255, 255, 0.1)'
},
tooltip: {
valueSuffix: ' ' + unit,
backgroundColor: '#1E1E1E',
borderColor: '#FFFFFF',
style: {
color: '#FFFFFF'
},
formatter: function() {
return '<b>' + this.point.category + '</b>: ' + this.y.toFixed(2) + ' ' + unit;
}
},
plotOptions: {
bar: {
dataLabels: {
enabled: true,
style: {
color: '#FFFFFF'
},
formatter: function() {
return this.y.toFixed(2) + ' ' + unit;
}
}
}
},
legend: {
enabled: false, // Disable legend
},
credits: {
enabled: false
},
series: [] series: []
}; };
} }
function createChart(chartId, title, description, unit, dataArrays, maxY = null) { function createChart(chartId, title, description, unit, dataArrays, maxY = null) {
var options = getLineChartOptions(title, description, unit, maxY); const options = getLineChartOptions(title, description, unit, maxY);
options.series = dataArrays.map(function(dataArray, index) { options.series = dataArrays.map((dataArray, index) => ({name: dataArray.label, data: dataArray.data, color: colors[index % colors.length]}));
return {name: dataArray.label, data: dataArray.data, color: colors[index % colors.length]};
});
Highcharts.chart(chartId, options); Highcharts.chart(chartId, options);
} }
function createBarChart(chartId, title, unit, categories, data, colors, maxY = null) { function createBarChart(chartId, title, unit, categories, data, colors, maxY = null) {
var options = getBarChartOptions(title, unit, maxY); const options = getBarChartOptions(title, unit, maxY);
options.xAxis.categories = categories; options.xAxis.categories = categories;
options.series = [{ options.series = [{name: title, data: data, colorByPoint: true, colors: colors}];
name: title,
data: data,
colorByPoint: true,
colors: colors
}];
Highcharts.chart(chartId, options); Highcharts.chart(chartId, options);
} }
function calculateAverage(data) { function calculateAverage(data) {
const sum = data.reduce((acc, value) => acc + value, 0); return data.reduce((acc, value) => acc + value, 0) / data.length;
return sum / data.length;
} }
function calculatePercentile(data, percentile) { function calculatePercentile(data, percentile) {
data.sort((a, b) => a - b); data.sort((a, b) => a - b);
const index = Math.ceil(percentile / 100 * data.length) - 1; return data[Math.ceil(percentile / 100 * data.length) - 1];
return data[index];
} }
// Create line charts // Create line charts
@ -248,256 +83,87 @@ createChart('ramUsedChart', 'RAM Usage', '', 'GB', ramUsedDataArrays);
createChart('swapUsedChart', 'SWAP Usage', '', 'GB', swapUsedDataArrays); createChart('swapUsedChart', 'SWAP Usage', '', 'GB', swapUsedDataArrays);
// Calculate average CPU and GPU load // Calculate average CPU and GPU load
var cpuLoadAverages = cpuLoadDataArrays.map(function(dataArray) { const cpuLoadAverages = cpuLoadDataArrays.map(dataArray => calculateAverage(dataArray.data));
return calculateAverage(dataArray.data); const gpuLoadAverages = gpuLoadDataArrays.map(dataArray => calculateAverage(dataArray.data));
});
var gpuLoadAverages = gpuLoadDataArrays.map(function(dataArray) {
return calculateAverage(dataArray.data);
});
// Create bar charts for average CPU and GPU load // Create bar charts for average CPU and GPU load
createBarChart('cpuLoadSummaryChart', 'Average CPU Load', '%', cpuLoadDataArrays.map(function(dataArray) { return dataArray.label; }), cpuLoadAverages, colors, 100); createBarChart('cpuLoadSummaryChart', 'Average CPU Load', '%', cpuLoadDataArrays.map(dataArray => dataArray.label), cpuLoadAverages, colors, 100);
createBarChart('gpuLoadSummaryChart', 'Average GPU Load', '%', gpuLoadDataArrays.map(function(dataArray) { return dataArray.label; }), gpuLoadAverages, colors, 100); createBarChart('gpuLoadSummaryChart', 'Average GPU Load', '%', gpuLoadDataArrays.map(dataArray => dataArray.label), gpuLoadAverages, colors, 100);
// Calculate and render min, max, and average FPS // Calculate and render min, max, and average FPS
var categories = []; const categories = [];
var minFPSData = []; const minFPSData = [];
var avgFPSData = []; const avgFPSData1 = [];
var maxFPSData = []; const maxFPSData = [];
fpsDataArrays.forEach(function(dataArray) {
var minFPS = calculatePercentile(dataArray.data, 1);
var avgFPS = calculateAverage(dataArray.data);
var maxFPS = calculatePercentile(dataArray.data, 97);
fpsDataArrays.forEach(dataArray => {
categories.push(dataArray.label); categories.push(dataArray.label);
minFPSData.push(minFPS); minFPSData.push(calculatePercentile(dataArray.data, 1));
avgFPSData.push(avgFPS); avgFPSData1.push(calculateAverage(dataArray.data));
maxFPSData.push(maxFPS); maxFPSData.push(calculatePercentile(dataArray.data, 97));
}); });
Highcharts.chart('minMaxAvgChart', { Highcharts.chart('minMaxAvgChart', {
chart: { ...commonChartOptions,
type: 'bar', chart: {...commonChartOptions.chart, type: 'bar'},
backgroundColor: null title: {...commonChartOptions.title, text: 'Min/Avg/Max FPS'},
}, subtitle: {...commonChartOptions.subtitle, text: 'More is better'},
title: { xAxis: {...commonChartOptions.xAxis, categories: categories},
text: 'Min/Avg/Max FPS', yAxis: {...commonChartOptions.yAxis, title: {text: 'FPS', align: 'high', style: {color: '#FFFFFF'}}},
style: { tooltip: {...commonChartOptions.tooltip, valueSuffix: ' FPS', formatter: function() {return `<b>${this.series.name}</b>: ${this.y.toFixed(2)} FPS`;}},
color: '#FFFFFF', plotOptions: {bar: {dataLabels: {enabled: true, style: {color: '#FFFFFF'}, formatter: function() {return this.y.toFixed(2) + ' fps';}}}},
fontSize: '16px' legend: {...commonChartOptions.legend, reversed: true, enabled: true},
} series: [{name: '97th', data: maxFPSData, color: '#00FF00'}, {name: 'AVG', data: avgFPSData1, color: '#0000FF'}, {name: '1%', data: minFPSData, color: '#FF0000'}]
},
subtitle: {
text: 'More is better',
style: {
color: '#FFFFFF'
}
},
xAxis: {
categories: categories,
title: {
text: null
},
labels: {
style: {
color: '#FFFFFF'
}
}
},
yAxis: {
min: 0,
title: {
text: 'FPS',
align: 'high',
style: {
color: '#FFFFFF'
}
},
labels: {
overflow: 'justify',
style: {
color: '#FFFFFF'
}
},
gridLineColor: 'rgba(255, 255, 255, 0.1)'
},
tooltip: {
valueSuffix: ' FPS',
backgroundColor: '#1E1E1E',
borderColor: '#FFFFFF',
style: {
color: '#FFFFFF'
},
formatter: function() {
return '<b>' + this.series.name + '</b>: ' + this.y.toFixed(2) + ' FPS';
}
},
plotOptions: {
bar: {
dataLabels: {
enabled: true,
style: {
color: '#FFFFFF'
},
formatter: function() {
return this.y.toFixed(2) + ' fps';
}
}
}
},
legend: {
reversed: true,
itemStyle: {
color: '#FFFFFF'
}
},
credits: {
enabled: false
},
series: [{
name: '97th',
data: maxFPSData,
color: '#00FF00'
}, {
name: 'AVG',
data: avgFPSData,
color: '#0000FF'
}, {
name: '1%',
data: minFPSData,
color: '#FF0000'
}]
}); });
// Calculate average FPS for each filename // Calculate average FPS for each filename
var avgFPSData = fpsDataArrays.map(function(dataArray) { const avgFPSData2 = fpsDataArrays.map(dataArray => calculateAverage(dataArray.data));
return calculateAverage(dataArray.data);
});
// Calculate FPS as a percentage of the first element // Calculate FPS as a percentage of the first element
var firstFPS = avgFPSData[0]; const firstFPS = avgFPSData2[0];
var percentageFPSData = avgFPSData.map(function(fps) { const percentageFPSData = avgFPSData2.map(fps => (fps / firstFPS) * 100);
return (fps / firstFPS) * 100;
});
// Create bar chart for FPS percentage // Create bar chart for FPS percentage
Highcharts.chart('avgChart', { Highcharts.chart('avgChart', {
chart: { ...commonChartOptions,
type: 'bar', chart: {...commonChartOptions.chart, type: 'bar'},
backgroundColor: null title: {...commonChartOptions.title, text: 'Average FPS in %'},
}, xAxis: {...commonChartOptions.xAxis, categories: fpsDataArrays.map(dataArray => dataArray.label)},
title: { yAxis: {...commonChartOptions.yAxis, title: {text: 'Percentage (%)', align: 'high', style: {color: '#FFFFFF'}}},
text: 'Average FPS in %', tooltip: {...commonChartOptions.tooltip, valueSuffix: ' %', formatter: function() {return `<b>${this.point.category}</b>: ${this.y.toFixed(2)} %`;}},
style: { plotOptions: {bar: {dataLabels: {enabled: true, style: {color: '#FFFFFF'}, formatter: function() {return this.y.toFixed(2) + ' %';}}}},
color: '#FFFFFF', legend: {enabled: false},
fontSize: '16px' series: [{name: 'FPS Percentage', data: percentageFPSData, colorByPoint: true, colors: colors}]
}
},
xAxis: {
categories: fpsDataArrays.map(function(dataArray) { return dataArray.label; }),
title: {
text: null
},
labels: {
style: {
color: '#FFFFFF'
}
}
},
yAxis: {
min: 0,
title: {
text: 'Percentage (%)',
align: 'high',
style: {
color: '#FFFFFF'
}
},
labels: {
overflow: 'justify',
style: {
color: '#FFFFFF'
}
},
gridLineColor: 'rgba(255, 255, 255, 0.1)'
},
tooltip: {
valueSuffix: ' %',
backgroundColor: '#1E1E1E',
borderColor: '#FFFFFF',
style: {
color: '#FFFFFF'
},
formatter: function() {
return '<b>' + this.point.category + '</b>: ' + this.y.toFixed(2) + ' %';
}
},
plotOptions: {
bar: {
dataLabels: {
enabled: true,
style: {
color: '#FFFFFF'
},
formatter: function() {
return this.y.toFixed(2) + ' %';
}
}
}
},
legend: {
enabled: false
},
credits: {
enabled: false
},
series: [{
name: 'FPS Percentage',
data: percentageFPSData,
colorByPoint: true,
colors: colors
}]
}); });
// Function to filter out the top and bottom 3% of FPS values // Function to filter out the top and bottom 3% of FPS values
function filterOutliers(data) { function filterOutliers(data) {
data.sort((a, b) => a - b); data.sort((a, b) => a - b);
var start = Math.floor(data.length * 0.01); // Ignore bottom 1% return data.slice(Math.floor(data.length * 0.01), Math.ceil(data.length * 0.97));
var end = Math.ceil(data.length * 0.97); // Ignore top 1%
return data.slice(start, end);
} }
// Function to count occurrences of each FPS value // Function to count occurrences of each FPS value
function countFPS(data) { function countFPS(data) {
var counts = {}; const counts = {};
data.forEach(function(fps) { data.forEach(fps => {
var roundedFPS = Math.round(fps); const roundedFPS = Math.round(fps);
counts[roundedFPS] = (counts[roundedFPS] || 0) + 1; counts[roundedFPS] = (counts[roundedFPS] || 0) + 1;
}); });
var fpsArray = Object.keys(counts).map(function(key) { let fpsArray = Object.keys(counts).map(key => [parseInt(key), counts[key]]).sort((a, b) => a[0] - b[0]);
return [parseInt(key), counts[key]];
}).sort(function(a, b) {
return a[0] - b[0];
});
// Combine closest FPS values until we have 100 or fewer points
while (fpsArray.length > 100) { while (fpsArray.length > 100) {
var minDiff = Infinity; let minDiff = Infinity;
var minIndex = -1; let minIndex = -1;
// Find the pair with the smallest difference for (let i = 0; i < fpsArray.length - 1; i++) {
for (var i = 0; i < fpsArray.length - 1; i++) { const diff = fpsArray[i + 1][0] - fpsArray[i][0];
var diff = fpsArray[i + 1][0] - fpsArray[i][0];
if (diff < minDiff) { if (diff < minDiff) {
minDiff = diff; minDiff = diff;
minIndex = i; minIndex = i;
} }
} }
// Combine the closest pair
fpsArray[minIndex][1] += fpsArray[minIndex + 1][1]; fpsArray[minIndex][1] += fpsArray[minIndex + 1][1];
fpsArray[minIndex][0] = (fpsArray[minIndex][0] + fpsArray[minIndex + 1][0]) / 2; fpsArray[minIndex][0] = (fpsArray[minIndex][0] + fpsArray[minIndex + 1][0]) / 2;
fpsArray.splice(minIndex + 1, 1); fpsArray.splice(minIndex + 1, 1);
@ -507,118 +173,43 @@ function countFPS(data) {
} }
// Calculate counts for each dataset after filtering outliers // Calculate counts for each dataset after filtering outliers
var densityData = fpsDataArrays.map(function(dataArray) { const densityData = fpsDataArrays.map(dataArray => ({name: dataArray.label, data: countFPS(filterOutliers(dataArray.data))}));
var filteredData = filterOutliers(dataArray.data);
return {
name: dataArray.label,
data: countFPS(filteredData)
};
});
// Create the chart // Create the chart
Highcharts.chart('densityChart', { Highcharts.chart('densityChart', {
chart: { ...commonChartOptions,
type: 'areaspline', chart: {...commonChartOptions.chart, type: 'areaspline'},
backgroundColor: null title: {...commonChartOptions.title, text: 'FPS Density'},
}, xAxis: {...commonChartOptions.xAxis, title: {text: 'FPS', style: {color: '#FFFFFF'}}, labels: {style: {color: '#FFFFFF'}}}, // Show X-axis labels in white
title: { yAxis: {...commonChartOptions.yAxis, title: {text: 'Count', style: {color: '#FFFFFF'}}},
text: 'FPS Density', tooltip: {...commonChartOptions.tooltip, shared: true, formatter: function() {return `<b>${this.points[0].series.name}</b>: ${this.points[0].y} points at ~${Math.round(this.points[0].x)} FPS`;}},
style: { plotOptions: {areaspline: {fillOpacity: 0.5, marker: {enabled: false}}},
color: '#FFFFFF', legend: {...commonChartOptions.legend, enabled: true},
fontSize: '16px'
}
},
xAxis: {
title: {
text: 'FPS',
style: {
color: '#FFFFFF'
}
},
labels: {
style: {
color: '#FFFFFF'
}
}
},
yAxis: {
title: {
text: 'Count',
style: {
color: '#FFFFFF'
}
},
labels: {
style: {
color: '#FFFFFF'
}
},
gridLineColor: 'rgba(255, 255, 255, 0.1)'
},
tooltip: {
shared: true,
backgroundColor: '#1E1E1E',
borderColor: '#FFFFFF',
style: {
color: '#FFFFFF'
},
formatter: function() {
var points = this.points;
var tooltipText = '<b>' + points[0].series.name + '</b>: ' + points[0].y + ' points at ~' + Math.round(points[0].x) + ' FPS';
return tooltipText;
}
},
plotOptions: {
areaspline: {
fillOpacity: 0.5,
marker: {
enabled: false
}
}
},
legend: {
enabled: true,
itemStyle: {
color: '#FFFFFF'
}
},
credits: {
enabled: false
},
series: densityData series: densityData
}); });
function calculateSpikes(data, threshold) { function calculateSpikes(data, threshold) {
if (data.length < 6) { if (data.length < 6) throw new Error("Data length must be greater than or equal to 6.");
throw new Error("Data length must be greater than or equal to 6.");
}
let spikeCount = 0; let spikeCount = 0;
// Helper function to calculate the moving average with a minimum of 6 points
function movingAverage(arr, index) { function movingAverage(arr, index) {
const windowSize = Math.max(6, Math.ceil(arr.length * 0.05)); // 5 % of the data const windowSize = Math.max(6, Math.ceil(arr.length * 0.05));
const halfWindowSize = Math.floor(windowSize / 2); const halfWindowSize = Math.floor(windowSize / 2);
const start = Math.max(0, index - halfWindowSize); const start = Math.max(0, index - halfWindowSize);
const end = Math.min(arr.length - 1, index + halfWindowSize); const end = Math.min(arr.length - 1, index + halfWindowSize);
const actualWindowSize = end - start + 1; const actualWindowSize = end - start + 1;
let sum = 0; let sum = 0;
for (let i = start; i <= end; i++) { for (let i = start; i <= end; i++) sum += arr[i];
sum += arr[i];
}
return sum / actualWindowSize; return sum / actualWindowSize;
} }
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
const currentPoint = data[i]; const currentPoint = data[i];
const movingAvg = movingAverage(data, i); const movingAvg = movingAverage(data, i);
const change = Math.abs(currentPoint - movingAvg) / movingAvg * 100; const change = Math.abs(currentPoint - movingAvg) / movingAvg * 100;
if (change > threshold) spikeCount++;
if (change > threshold) {
spikeCount++;
}
} }
return (spikeCount / data.length) * 100; return (spikeCount / data.length) * 100;
@ -627,93 +218,19 @@ function calculateSpikes(data, threshold) {
function updateSpikesChart(threshold) { function updateSpikesChart(threshold) {
document.getElementById('spikeThresholdValue').innerText = threshold + '%'; document.getElementById('spikeThresholdValue').innerText = threshold + '%';
var spikePercentages = fpsDataArrays.map(function(dataArray) { const spikePercentages = fpsDataArrays.map(dataArray => calculateSpikes(dataArray.data, threshold));
return calculateSpikes(dataArray.data, threshold);
});
Highcharts.chart('spikesChart', { Highcharts.chart('spikesChart', {
chart: { ...commonChartOptions,
type: 'bar', chart: {...commonChartOptions.chart, type: 'bar'},
backgroundColor: null title: {...commonChartOptions.title, text: 'FPS Spikes'},
}, subtitle: {...commonChartOptions.subtitle, text: 'Less is better'},
title: { xAxis: {...commonChartOptions.xAxis, categories: categories},
text: 'FPS Spikes', yAxis: {...commonChartOptions.yAxis, title: {text: 'Percentage (%)', align: 'high', style: {color: '#FFFFFF'}}},
style: { tooltip: {...commonChartOptions.tooltip, valueSuffix: ' %', formatter: function() {return `<b>${this.point.category}</b>: ${this.y.toFixed(2)} %`;}},
color: '#FFFFFF', plotOptions: {bar: {dataLabels: {enabled: true, style: {color: '#FFFFFF'}, formatter: function() {return this.y.toFixed(2) + ' %';}}}},
fontSize: '16px' legend: {enabled: false},
} series: [{name: 'Spike Percentage', data: spikePercentages, colorByPoint: true, colors: colors}]
},
subtitle: {
text: 'Less is better',
style: {
color: '#FFFFFF',
fontSize: '12px'
}
},
xAxis: {
categories: categories,
title: {
text: null
},
labels: {
style: {
color: '#FFFFFF'
}
}
},
yAxis: {
min: 0,
title: {
text: 'Percentage (%)',
align: 'high',
style: {
color: '#FFFFFF'
}
},
labels: {
overflow: 'justify',
style: {
color: '#FFFFFF'
}
},
gridLineColor: 'rgba(255, 255, 255, 0.1)'
},
tooltip: {
valueSuffix: ' %',
backgroundColor: '#1E1E1E',
borderColor: '#FFFFFF',
style: {
color: '#FFFFFF'
},
formatter: function() {
return '<b>' + this.point.category + '</b>: ' + this.y.toFixed(2) + ' %';
}
},
plotOptions: {
bar: {
dataLabels: {
enabled: true,
style: {
color: '#FFFFFF'
},
formatter: function() {
return this.y.toFixed(2) + ' %';
}
}
}
},
legend: {
enabled: false
},
credits: {
enabled: false
},
series: [{
name: 'Spike Percentage',
data: spikePercentages,
colorByPoint: true,
colors: colors
}]
}); });
} }