Percect cache control for static files, fix few issues with charts
This commit is contained in:
parent
ebed937b76
commit
bd9e12a299
26
server.go
26
server.go
@ -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))
|
||||||
})
|
})
|
||||||
|
@ -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
|
|
||||||
}]
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user