diff --git a/embed.go b/embed.go
index ff25851..6b2743d 100644
--- a/embed.go
+++ b/embed.go
@@ -4,3 +4,6 @@ import "embed"
//go:embed templates/*
var templatesFS embed.FS
+
+//go:embed static/*
+var staticFS embed.FS
diff --git a/server.go b/server.go
index c9b0b5c..e4cd405 100644
--- a/server.go
+++ b/server.go
@@ -79,6 +79,13 @@ func Start(c *Config) {
tmpl := template.Must(template.ParseFS(templatesFS, "templates/*.tmpl"))
r.SetHTMLTemplate(tmpl)
+ // Serve static files
+ fileServer := http.FileServer(http.FS(staticFS))
+ r.GET("/static/*filepath", func(c *gin.Context) {
+ c.Header("Cache-Control", "public, max-age=3600")
+ fileServer.ServeHTTP(c.Writer, c.Request)
+ })
+
r.GET("/", func(c *gin.Context) { c.Redirect(http.StatusTemporaryRedirect, "/benchmarks") })
r.GET("/benchmarks", getBenchmarks)
diff --git a/static/js/benchmark.js b/static/js/benchmark.js
index e69de29..c890306 100644
--- a/static/js/benchmark.js
+++ b/static/js/benchmark.js
@@ -0,0 +1,583 @@
+// Define a set of colors to be used for the charts
+var colors = Highcharts.getOptions().colors;
+
+function getLineChartOptions(title, description, unit, maxY = null) {
+ return {
+ chart: {
+ type: 'line',
+ backgroundColor: null, // Set background to transparent
+ style: {
+ color: '#FFFFFF'
+ },
+ zooming: {
+ type: 'x'
+ }
+ },
+ 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: '{series.name}: {point.y:.2f} ' + unit + '
', // 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: [],
+ exporting: {
+ buttons: {
+ contextButton: {
+ menuItems: [
+ 'viewFullscreen',
+ 'printChart',
+ 'separator',
+ 'downloadPNG',
+ 'downloadJPEG',
+ 'downloadPDF',
+ 'downloadSVG',
+ 'separator',
+ 'downloadCSV',
+ 'downloadXLS'
+ ]
+ }
+ }
+ }
+ };
+}
+
+function getBarChartOptions(title, unit, maxY = null) {
+ return {
+ chart: {
+ type: 'bar',
+ backgroundColor: null, // Set background to transparent
+ style: {
+ color: '#FFFFFF'
+ }
+ },
+ title: {
+ 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 '' + this.point.category + ': ' + 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: []
+ };
+}
+
+function createChart(chartId, title, description, unit, dataArrays, maxY = null) {
+ var options = getLineChartOptions(title, description, unit, maxY);
+ options.series = dataArrays.map(function(dataArray, index) {
+ return {name: dataArray.label, data: dataArray.data, color: colors[index % colors.length]};
+ });
+
+ Highcharts.chart(chartId, options);
+}
+
+function createBarChart(chartId, title, unit, categories, data, colors, maxY = null) {
+ var options = getBarChartOptions(title, unit, maxY);
+ options.xAxis.categories = categories;
+ options.series = [{
+ name: title,
+ data: data,
+ colorByPoint: true,
+ colors: colors
+ }];
+
+ Highcharts.chart(chartId, options);
+}
+
+function calculateAverage(data) {
+ const sum = data.reduce((acc, value) => acc + value, 0);
+ return sum / data.length;
+}
+
+function calculatePercentile(data, percentile) {
+ data.sort((a, b) => a - b);
+ const index = Math.ceil(percentile / 100 * data.length) - 1;
+ return data[index];
+}
+
+// Create line charts
+createChart('fpsChart', 'FPS', 'More is better', 'fps', fpsDataArrays);
+createChart('frameTimeChart', 'Frametime', 'Less is better', 'ms', frameTimeDataArrays);
+createChart('cpuLoadChart', 'CPU Load', '', '%', cpuLoadDataArrays, 100);
+createChart('gpuLoadChart', 'GPU Load', '', '%', gpuLoadDataArrays, 100);
+createChart('cpuTempChart', 'CPU Temperature', '', '°C', cpuTempDataArrays);
+createChart('gpuTempChart', 'GPU Temperature', '', '°C', gpuTempDataArrays);
+createChart('gpuCoreClockChart', 'GPU Core Clock', '', 'MHz', gpuCoreClockDataArrays);
+createChart('gpuMemClockChart', 'GPU Memory Clock', '', 'MHz', gpuMemClockDataArrays);
+createChart('gpuVRAMUsedChart', 'GPU VRAM Usage', '', '%', gpuVRAMUsedDataArrays, 100);
+createChart('gpuPowerChart', 'GPU Power', '', 'W', gpuPowerDataArrays);
+createChart('ramUsedChart', 'RAM Usage', '', 'GB', ramUsedDataArrays);
+createChart('swapUsedChart', 'SWAP Usage', '', 'GB', swapUsedDataArrays);
+
+// Calculate average CPU and GPU load
+var cpuLoadAverages = cpuLoadDataArrays.map(function(dataArray) {
+ return calculateAverage(dataArray.data);
+});
+
+var gpuLoadAverages = gpuLoadDataArrays.map(function(dataArray) {
+ return calculateAverage(dataArray.data);
+});
+
+// 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('gpuLoadSummaryChart', 'Average GPU Load', '%', gpuLoadDataArrays.map(function(dataArray) { return dataArray.label; }), gpuLoadAverages, colors, 100);
+
+// Calculate and render min, max, and average FPS
+var categories = [];
+var minFPSData = [];
+var avgFPSData = [];
+var maxFPSData = [];
+
+fpsDataArrays.forEach(function(dataArray) {
+ var minFPS = calculatePercentile(dataArray.data, 1);
+ var avgFPS = calculateAverage(dataArray.data);
+ var maxFPS = calculatePercentile(dataArray.data, 97);
+
+ categories.push(dataArray.label);
+ minFPSData.push(minFPS);
+ avgFPSData.push(avgFPS);
+ maxFPSData.push(maxFPS);
+});
+
+Highcharts.chart('minMaxAvgChart', {
+ chart: {
+ type: 'bar',
+ backgroundColor: null
+ },
+ title: {
+ text: 'Min/Avg/Max FPS',
+ style: {
+ color: '#FFFFFF',
+ fontSize: '16px'
+ }
+ },
+ 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 '' + this.series.name + ': ' + 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
+var avgFPSData = fpsDataArrays.map(function(dataArray) {
+ return calculateAverage(dataArray.data);
+});
+
+// Calculate FPS as a percentage of the first element
+var firstFPS = avgFPSData[0];
+var percentageFPSData = avgFPSData.map(function(fps) {
+ return (fps / firstFPS) * 100;
+});
+
+// Create bar chart for FPS percentage
+Highcharts.chart('avgChart', {
+ chart: {
+ type: 'bar',
+ backgroundColor: null
+ },
+ title: {
+ text: 'Average FPS in %',
+ style: {
+ color: '#FFFFFF',
+ fontSize: '16px'
+ }
+ },
+ 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 '' + this.point.category + ': ' + 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 calculateSpikes(data, threshold) {
+ if (data.length < 6) {
+ throw new Error("Data length must be greater than or equal to 6.");
+ }
+
+ let spikeCount = 0;
+
+ // Helper function to calculate the moving average with a minimum of 6 points
+ function movingAverage(arr, index) {
+ const windowSize = Math.max(6, Math.ceil(arr.length * 0.05)); // 5 % of the data
+ const halfWindowSize = Math.floor(windowSize / 2);
+ const start = Math.max(0, index - halfWindowSize);
+ const end = Math.min(arr.length - 1, index + halfWindowSize);
+ const actualWindowSize = end - start + 1;
+
+ let sum = 0;
+ for (let i = start; i <= end; i++) {
+ sum += arr[i];
+ }
+ return sum / actualWindowSize;
+ }
+
+ for (let i = 0; i < data.length; i++) {
+ const currentPoint = data[i];
+ const movingAvg = movingAverage(data, i);
+
+ const change = Math.abs(currentPoint - movingAvg) / movingAvg * 100;
+
+ if (change > threshold) {
+ spikeCount++;
+ }
+ }
+
+ return (spikeCount / data.length) * 100;
+}
+
+function updateSpikesChart(threshold) {
+ document.getElementById('spikeThresholdValue').innerText = threshold + '%';
+
+ var spikePercentages = fpsDataArrays.map(function(dataArray) {
+ return calculateSpikes(dataArray.data, threshold);
+ });
+
+ Highcharts.chart('spikesChart', {
+ chart: {
+ type: 'bar',
+ backgroundColor: null
+ },
+ title: {
+ text: 'FPS Spikes',
+ style: {
+ color: '#FFFFFF',
+ fontSize: '16px'
+ }
+ },
+ 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 '' + this.point.category + ': ' + 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
+ }]
+ });
+}
+
+// Initial render of spikes chart
+updateSpikesChart(document.getElementById('spikeThreshold').value);
diff --git a/templates/benchmark.tmpl b/templates/benchmark.tmpl
index b6c13e4..62b13c8 100644
--- a/templates/benchmark.tmpl
+++ b/templates/benchmark.tmpl
@@ -195,592 +195,6 @@
-
+
{{template "footer.tmpl" .}}