flightlesssomething/templates/benchmark.tmpl
2024-07-11 19:33:15 +03:00

787 lines
24 KiB
Cheetah

{{template "header.tmpl" .}}
<div class="d-flex justify-content-between align-items-center">
<h2>Benchmark #{{ .benchmark.ID }}</h2>
</div>
<div class="text-center">
<h5><b>{{ .benchmark.Title }}</b></h5>
<p>{{ .benchmark.Description }}</p>
<p><small>Submitted <b>{{ .benchmark.CreatedAtHumanized }}</b> by <b>{{ .benchmark.User.Username }}.</b></small></p>
</div>
{{if eq .benchmark.UserID .userID }}
<div class="row">
<div class="col-12">
<a class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#exampleModal">Delete benchmark</a>
</div>
</div>
<div class="modal" id="exampleModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete confirmation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this benchmark?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No, cancel</button>
<form hx-delete="/benchmark/{{ .benchmark.ID }}" >
<button type="submit" class="btn btn-primary">Yes, delete</button>
</form>
</div>
</div>
</div>
</div>
{{end}}
<div class="row">
<div class="col-12">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th scope="col">Label</th>
<th scope="col">OS</th>
<th scope="col">CPU</th>
<th scope="col">GPU</th>
<th scope="col">RAM</th>
<th scope="col">OS specific</th>
</tr>
</thead>
<tbody>
{{- range .benchmarkData }}
<tr>
<th scope="row">{{ .Label }}</th>
<td>{{ .SpecOS }}</td>
<td>{{ .SpecGPU }}</td>
<td>{{ .SpecCPU }}</td>
<td>{{ .SpecRAM }}</td>
<td>{{ .SpecLinuxKernel }} {{ .SpecLinuxScheduler }}</td>
</tr>
{{- end }}
</tbody>
</table>
</div>
</div>
<div id="fpsChart" style="height:250pt;"></div>
<div id="frameTimeChart" style="height:250pt;"></div>
<div class="accordion" id="accordionFlushExample">
<div class="accordion-item">
<h2 class="accordion-header" id="flush-headingTwo">
<button id="hideAdditionalMetricsBtn" class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#flush-collapseTwo" aria-expanded="false" aria-controls="flush-collapseTwo">
Additional metrics
</button>
</h2>
<div id="flush-collapseTwo" class="accordion-collapse collapse" aria-labelledby="flush-headingTwo" data-bs-parent="#accordionFlushExample">
<div id="cpuLoadChart" style="height:250pt;"></div>
<div id="gpuLoadChart" style="height:250pt;"></div>
<div id="cpuTempChart" style="height:250pt;"></div>
<div id="gpuTempChart" style="height:250pt;"></div>
<div id="gpuCoreClockChart" style="height:250pt;"></div>
<div id="gpuMemClockChart" style="height:250pt;"></div>
<div id="gpuVRAMUsedChart" style="height:250pt;"></div>
<div id="gpuPowerChart" style="height:250pt;"></div>
<div id="ramUsedChart" style="height:250pt;"></div>
<div id="swapUsedChart" style="height:250pt;"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div id="cpuLoadSummaryChart" style="height:250pt;"></div>
</div>
<div class="col-md-6">
<div id="gpuLoadSummaryChart" style="height:250pt;"></div>
</div>
</div>
<div id="minMaxAvgChart" style="height:500pt;"></div>
<div id="avgChart" style="height:250pt;"></div>
<div>
<label for="spikeThreshold" style="color: #FFFFFF;">Ignore Spike Threshold (%):</label>
<input type="range" id="spikeThreshold" name="spikeThreshold" min="5" max="150" value="50" oninput="updateSpikesChart(this.value)">
<span id="spikeThresholdValue" style="color: #FFFFFF;">50%</span>
<div id="spikesChart" style="height:250pt;"></div>
</div>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://code.highcharts.com/modules/export-data.js"></script>
<script src="https://code.highcharts.com/modules/full-screen.js"></script>
<script src="https://code.highcharts.com/modules/boost.js"></script>
<script>
var fpsDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataFPS }} },
{{- end }}
];
var frameTimeDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataFrameTime }} },
{{- end }}
];
var cpuLoadDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataCPULoad }} },
{{- end }}
];
var gpuLoadDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataGPULoad }} },
{{- end }}
];
var cpuTempDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataCPUTemp }} },
{{- end }}
];
var gpuTempDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataGPUTemp }} },
{{- end }}
];
var gpuCoreClockDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataGPUCoreClock }} },
{{- end }}
];
var gpuMemClockDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataGPUMemClock }} },
{{- end }}
];
var gpuVRAMUsedDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataGPUVRAMUsed }} },
{{- end }}
];
var gpuPowerDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataGPUPower }} },
{{- end }}
];
var ramUsedDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataRAMUsed }} },
{{- end }}
];
var swapUsedDataArrays = [
{{- range .benchmarkData }}
{ label: '{{ .Label }}', data: {{ .DataSwapUsed }} },
{{- end }}
];
</script>
<script>
// 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: '<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: [],
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 '<b>' + this.series.name + '</b>: ' + this.y.toFixed(2) + ' ' + unit;
}
},
plotOptions: {
bar: {
dataLabels: {
enabled: true,
style: {
color: '#FFFFFF'
},
formatter: function() {
return this.y.toFixed(2);
}
}
}
},
legend: {
enabled: false
},
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 '<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
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 '<b>' + this.series.name + '</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 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 '<b>' + this.series.name + '</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
}]
});
}
// Initial render of spikes chart
updateSpikesChart(document.getElementById('spikeThreshold').value);
</script>
{{template "footer.tmpl" .}}