document.addEventListener('DOMContentLoaded', () => {
const converter = new showdown.Converter();
const element = document.getElementById('aiSummaryMarkdown');
element.innerHTML = converter.makeHtml(element.innerHTML);
});
// ===========================================================================================
// Common chart options
const commonChartOptions = {
chart: {backgroundColor: null, style: {color: '#FFFFFF'}, animation: false},
title: {style: {color: '#FFFFFF', fontSize: '16px'}},
subtitle: {style: {color: '#FFFFFF', fontSize: '12px'}},
xAxis: {labels: {style: {color: '#FFFFFF'}}, lineColor: '#FFFFFF', tickColor: '#FFFFFF'},
yAxis: {labels: {style: {color: '#FFFFFF'}}, gridLineColor: 'rgba(255, 255, 255, 0.1)', title: {text: false}},
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}}});
const colors = Highcharts.getOptions().colors;
// ===========================================================================================
function getLineChartOptions(title, description, unit, maxY = null) {
return {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'line', zooming: {type: 'x'}},
title: {...commonChartOptions.title, text: title},
subtitle: {...commonChartOptions.subtitle, text: description},
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;}}},
tooltip: {...commonChartOptions.tooltip, pointFormat: `{series.name}: {point.y:.2f} ${unit}
`},
plotOptions: {line: {marker: {enabled: false, symbol: 'circle', radius: 1.5, states: {hover: {enabled: true}}}, lineWidth: 1}},
legend: {...commonChartOptions.legend, enabled: true},
series: [],
exporting: {buttons: {contextButton: {menuItems: ['viewFullscreen', 'printChart', 'separator', 'downloadPNG', 'downloadJPEG', 'downloadPDF', 'downloadSVG', 'separator', 'downloadCSV', 'downloadXLS']}}}
};
}
function createChart(chartId, title, description, unit, dataArrays, maxY = null) {
const options = getLineChartOptions(title, description, unit, maxY);
options.series = dataArrays.map((dataArray, index) => ({name: dataArray.label, data: dataArray.data, color: colors[index % colors.length]}));
Highcharts.chart(chartId, options);
}
// Create line charts
createChart('fpsChart', 'FPS', 'More is better', 'fps', fpsDataArrays);
createChart('fpsChart2', 'FPS', 'More is better', 'fps', fpsDataArrays);
createChart('frameTimeChart', 'Frametime', 'Less is better', 'ms', frameTimeDataArrays);
createChart('frameTimeChart2', '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', '', 'GB', gpuVRAMUsedDataArrays);
createChart('gpuPowerChart', 'GPU Power', '', 'W', gpuPowerDataArrays);
createChart('ramUsedChart', 'RAM Usage', '', 'GB', ramUsedDataArrays);
createChart('swapUsedChart', 'SWAP Usage', '', 'GB', swapUsedDataArrays);
// ===========================================================================================
function calculateAverage(data) {
return data.reduce((acc, value) => acc + value, 0) / data.length;
}
// Calculate average CPU, GPU, GPU Core clock and GPU memory clock load
const fpsAverages = fpsDataArrays.map(dataArray => calculateAverage(dataArray.data));
const frametimeAverages = frameTimeDataArrays.map(dataArray => calculateAverage(dataArray.data));
const cpuLoadAverages = cpuLoadDataArrays.map(dataArray => calculateAverage(dataArray.data));
const gpuLoadAverages = gpuLoadDataArrays.map(dataArray => calculateAverage(dataArray.data));
const gpuCoreClockAverages = gpuCoreClockDataArrays.map(dataArray => calculateAverage(dataArray.data));
const gpuMemClockAverages = gpuMemClockDataArrays.map(dataArray => calculateAverage(dataArray.data));
function getBarChartOptions(title, unit, maxY = null) {
return {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'bar'},
title: {...commonChartOptions.title, text: title},
xAxis: {...commonChartOptions.xAxis, categories: [], title: {text: null}},
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 `${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},
series: []
};
}
function createBarChart(chartId, title, unit, categories, data, colors, maxY = null) {
const options = getBarChartOptions(title, unit, maxY);
options.xAxis.categories = categories;
options.series = [{name: title, data: data, colorByPoint: true, colors: colors}];
Highcharts.chart(chartId, options);
}
// Create bar charts for average CPU, GPU, GPU Core clock and GPU memory clock load
createBarChart('fpsSummaryChart', 'Average FPS', 'fps', fpsDataArrays.map(dataArray => dataArray.label), fpsAverages, colors, 100);
createBarChart('frametimeSummaryChart', 'Average Frametime', 'ms', frameTimeDataArrays.map(dataArray => dataArray.label), frametimeAverages, colors, 100);
createBarChart('cpuLoadSummaryChart', 'Average CPU Load', '%', cpuLoadDataArrays.map(dataArray => dataArray.label), cpuLoadAverages, colors, 100);
createBarChart('gpuLoadSummaryChart', 'Average GPU Load', '%', gpuLoadDataArrays.map(dataArray => dataArray.label), gpuLoadAverages, colors, 100);
createBarChart('gpuCoreClockSummaryChart', 'Average GPU Core Clock', 'Hz', gpuCoreClockDataArrays.map(dataArray => dataArray.label), gpuCoreClockAverages, colors);
createBarChart('gpuMemClockSummaryChart', 'Average GPU Memory Clock', 'Hz', gpuMemClockDataArrays.map(dataArray => dataArray.label), gpuMemClockAverages, colors);
// ===========================================================================================
function calculatePercentile(data, percentile) {
data.sort((a, b) => a - b);
return data[Math.ceil(percentile / 100 * data.length) - 1];
}
// Calculate and render min, max, and average FPS
const categories = [];
const minFPSData = [];
const avgFPSData1 = [];
const maxFPSData = [];
fpsDataArrays.forEach(dataArray => {
categories.push(dataArray.label);
minFPSData.push(calculatePercentile(dataArray.data, 1));
avgFPSData1.push(calculateAverage(dataArray.data));
maxFPSData.push(calculatePercentile(dataArray.data, 97));
});
Highcharts.chart('fpsMinMaxAvgChart', {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'bar'},
title: {...commonChartOptions.title, text: 'Min/Avg/Max FPS'},
subtitle: {...commonChartOptions.subtitle, text: 'More is better'},
xAxis: {...commonChartOptions.xAxis, categories: categories},
yAxis: {...commonChartOptions.yAxis, title: {text: 'FPS', align: 'high', style: {color: '#FFFFFF'}}},
tooltip: {...commonChartOptions.tooltip, valueSuffix: ' FPS', 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: {...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'}]
});
// Calculate average FPS for each filename
const avgFPSData2 = fpsDataArrays.map(dataArray => calculateAverage(dataArray.data));
// Calculate FPS as a percentage of the first element
const firstFPS = avgFPSData2[0];
const percentageFPSData = avgFPSData2.map(fps => (fps / firstFPS) * 100);
// Ensure the minimum FPS percentage is 100%
const minPercentage = Math.min(...percentageFPSData);
const normalizedPercentageFPSData = percentageFPSData.map(percentage => percentage - minPercentage + 100);
// Create an array of objects to sort both categories and data together
const sortedData = fpsDataArrays.map((dataArray, index) => ({
label: dataArray.label,
percentage: normalizedPercentageFPSData[index]
}));
// Sort the array by percentage
sortedData.sort((a, b) => a.percentage - b.percentage);
// Extract sorted categories and data
const sortedCategories = sortedData.map(item => item.label);
const sortedPercentageFPSData = sortedData.map(item => item.percentage);
// Create bar chart for FPS percentage
Highcharts.chart('fpsAvgChart', {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'bar'},
title: {...commonChartOptions.title, text: 'Avg FPS comparison in %'},
subtitle: {...commonChartOptions.subtitle, text: 'More is better'},
xAxis: {...commonChartOptions.xAxis, categories: sortedCategories},
yAxis: {...commonChartOptions.yAxis, min: 95, title: {text: 'Percentage (%)', align: 'high', style: {color: '#FFFFFF'}}},
tooltip: {...commonChartOptions.tooltip, valueSuffix: ' %', 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},
series: [{name: 'FPS Percentage', data: sortedPercentageFPSData, colorByPoint: true, colors: colors}]
});
// Function to filter out the top and bottom 3% of FPS values
function filterOutliers(data) {
data.sort((a, b) => a - b);
return data.slice(Math.floor(data.length * 0.01), Math.ceil(data.length * 0.97));
}
// Function to count occurrences of each FPS value
function countFPS(data) {
const counts = {};
data.forEach(fps => {
const roundedFPS = Math.round(fps);
counts[roundedFPS] = (counts[roundedFPS] || 0) + 1;
});
let fpsArray = Object.keys(counts).map(key => [parseInt(key), counts[key]]).sort((a, b) => a[0] - b[0]);
while (fpsArray.length > 100) {
let minDiff = Infinity;
let minIndex = -1;
for (let i = 0; i < fpsArray.length - 1; i++) {
const diff = fpsArray[i + 1][0] - fpsArray[i][0];
if (diff < minDiff) {
minDiff = diff;
minIndex = i;
}
}
fpsArray[minIndex][1] += fpsArray[minIndex + 1][1];
fpsArray[minIndex][0] = (fpsArray[minIndex][0] + fpsArray[minIndex + 1][0]) / 2;
fpsArray.splice(minIndex + 1, 1);
}
return fpsArray;
}
// Calculate counts for each dataset after filtering outliers
const densityData = fpsDataArrays.map(dataArray => ({name: dataArray.label, data: countFPS(filterOutliers(dataArray.data))}));
// Create the chart
Highcharts.chart('fpsDensityChart', {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'areaspline'},
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
tooltip: {...commonChartOptions.tooltip, shared: true, formatter: function() {return `${this.points[0].series.name}: ${this.points[0].y} points at ~${Math.round(this.points[0].x)} FPS`;}},
plotOptions: {areaspline: {fillOpacity: 0.5, marker: {enabled: false}}},
legend: {...commonChartOptions.legend, enabled: true},
series: densityData
});
function calculateStandardDeviation(data) {
const mean = calculateAverage(data);
const squaredDiffs = data.map(value => Math.pow(value - mean, 2));
const avgSquaredDiff = calculateAverage(squaredDiffs);
return Math.sqrt(avgSquaredDiff);
}
function calculateVariance(data) {
const mean = calculateAverage(data);
const squaredDiffs = data.map(value => Math.pow(value - mean, 2));
return calculateAverage(squaredDiffs);
}
const sdvCategories = fpsDataArrays.map(dataArray => dataArray.label);
const standardDeviations = fpsDataArrays.map(dataArray => calculateStandardDeviation(dataArray.data));
const variances = fpsDataArrays.map(dataArray => calculateVariance(dataArray.data));
Highcharts.chart('fpsStddevVarianceChart', {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'bar'},
title: {...commonChartOptions.title, text: 'FPS Stability'},
subtitle: {...commonChartOptions.subtitle, text: 'Measures of FPS consistency (std. dev.) and spread (variance). Less is better.'},
xAxis: {...commonChartOptions.xAxis, categories: sdvCategories},
yAxis: {...commonChartOptions.yAxis, title: {text: 'Value', align: 'high', style: {color: '#FFFFFF'}}},
tooltip: {...commonChartOptions.tooltip, formatter: function() {return `${this.series.name}: ${this.y.toFixed(2)}`;}},
plotOptions: {bar: {dataLabels: {enabled: true, style: {color: '#FFFFFF'}, formatter: function() {return this.y.toFixed(2);}}}},
legend: {...commonChartOptions.legend, enabled: true},
series: [
{name: 'Std. Dev.', data: standardDeviations, color: '#FF5733'},
{name: 'Variance', data: variances, color: '#33FF57'}
]
});
// ===========================================================================================
// Frametime-related charts
// Calculate and render min, max, and average Frametime
const frametimeCategories = [];
const minFrametimeData = [];
const avgFrametimeData1 = [];
const maxFrametimeData = [];
frameTimeDataArrays.forEach(dataArray => {
frametimeCategories.push(dataArray.label);
minFrametimeData.push(calculatePercentile(dataArray.data, 1));
avgFrametimeData1.push(calculateAverage(dataArray.data));
maxFrametimeData.push(calculatePercentile(dataArray.data, 97));
});
Highcharts.chart('frametimeMinMaxAvgChart', {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'bar'},
title: {...commonChartOptions.title, text: 'Min/Avg/Max Frametime'},
subtitle: {...commonChartOptions.subtitle, text: 'Less is better'},
xAxis: {...commonChartOptions.xAxis, categories: frametimeCategories},
yAxis: {...commonChartOptions.yAxis, title: {text: 'Frametime (ms)', align: 'high', style: {color: '#FFFFFF'}}},
tooltip: {...commonChartOptions.tooltip, valueSuffix: ' ms', formatter: function() {return `${this.series.name}: ${this.y.toFixed(2)} ms`;}},
plotOptions: {bar: {dataLabels: {enabled: true, style: {color: '#FFFFFF'}, formatter: function() {return this.y.toFixed(2) + ' ms';}}}},
legend: {...commonChartOptions.legend, reversed: true, enabled: true},
series: [{name: '97th', data: maxFrametimeData, color: '#00FF00'}, {name: 'AVG', data: avgFrametimeData1, color: '#0000FF'}, {name: '1%', data: minFrametimeData, color: '#FF0000'}]
});
// Calculate average Frametime for each filename
const avgFrametimeData2 = frameTimeDataArrays.map(dataArray => calculateAverage(dataArray.data));
// Calculate Frametime as a percentage of the first element
const firstFrametime = avgFrametimeData2[0];
const percentageFrametimeData = avgFrametimeData2.map(frametime => (frametime / firstFrametime) * 100);
// Ensure the minimum Frametime percentage is 100%
const minFrametimePercentage = Math.min(...percentageFrametimeData);
const normalizedPercentageFrametimeData = percentageFrametimeData.map(percentage => percentage - minFrametimePercentage + 100);
// Create an array of objects to sort both categories and data together
const sortedFrametimeData = frameTimeDataArrays.map((dataArray, index) => ({
label: dataArray.label,
percentage: normalizedPercentageFrametimeData[index]
}));
// Sort the array by percentage
sortedFrametimeData.sort((a, b) => a.percentage - b.percentage);
// Extract sorted categories and data
const sortedFrametimeCategories = sortedFrametimeData.map(item => item.label);
const sortedPercentageFrametimeData = sortedFrametimeData.map(item => item.percentage);
// Create bar chart for Frametime percentage
Highcharts.chart('frametimeAvgChart', {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'bar'},
title: {...commonChartOptions.title, text: 'Avg Frametime comparison in %'},
subtitle: {...commonChartOptions.subtitle, text: 'Less is better'},
xAxis: {...commonChartOptions.xAxis, categories: sortedFrametimeCategories},
yAxis: {...commonChartOptions.yAxis, min: 95, title: {text: 'Percentage (%)', align: 'high', style: {color: '#FFFFFF'}}},
tooltip: {...commonChartOptions.tooltip, valueSuffix: ' %', 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},
series: [{name: 'Frametime Percentage', data: sortedPercentageFrametimeData, colorByPoint: true, colors: colors}]
});
// Function to count occurrences of each Frametime value
function countFrametime(data) {
const counts = {};
data.forEach(frametime => {
const roundedFrametime = Math.round(frametime);
counts[roundedFrametime] = (counts[roundedFrametime] || 0) + 1;
});
let frametimeArray = Object.keys(counts).map(key => [parseInt(key), counts[key]]).sort((a, b) => a[0] - b[0]);
while (frametimeArray.length > 100) {
let minDiff = Infinity;
let minIndex = -1;
for (let i = 0; i < frametimeArray.length - 1; i++) {
const diff = frametimeArray[i + 1][0] - frametimeArray[i][0];
if (diff < minDiff) {
minDiff = diff;
minIndex = i;
}
}
frametimeArray[minIndex][1] += frametimeArray[minIndex + 1][1];
frametimeArray[minIndex][0] = (frametimeArray[minIndex][0] + frametimeArray[minIndex + 1][0]) / 2;
frametimeArray.splice(minIndex + 1, 1);
}
return frametimeArray;
}
// Calculate counts for each dataset after filtering outliers
const frametimeDensityData = frameTimeDataArrays.map(dataArray => ({name: dataArray.label, data: countFrametime(filterOutliers(dataArray.data))}));
// Create the chart
Highcharts.chart('frametimeDensityChart', {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'areaspline'},
title: {...commonChartOptions.title, text: 'Frametime Density'},
xAxis: {...commonChartOptions.xAxis, title: {text: 'Frametime (ms)', style: {color: '#FFFFFF'}}, labels: {style: {color: '#FFFFFF'}}}, // Show X-axis labels in white
tooltip: {...commonChartOptions.tooltip, shared: true, formatter: function() {return `${this.points[0].series.name}: ${this.points[0].y} points at ~${Math.round(this.points[0].x)} ms`;}},
plotOptions: {areaspline: {fillOpacity: 0.5, marker: {enabled: false}}},
legend: {...commonChartOptions.legend, enabled: true},
series: frametimeDensityData
});
const frametimeSdvCategories = frameTimeDataArrays.map(dataArray => dataArray.label);
const frametimeStandardDeviations = frameTimeDataArrays.map(dataArray => calculateStandardDeviation(dataArray.data));
const frametimeVariances = frameTimeDataArrays.map(dataArray => calculateVariance(dataArray.data));
Highcharts.chart('frametimeStddevVarianceChart', {
...commonChartOptions,
chart: {...commonChartOptions.chart, type: 'bar'},
title: {...commonChartOptions.title, text: 'Frametime Stability'},
subtitle: {...commonChartOptions.subtitle, text: 'Measures of Frametime consistency (std. dev.) and spread (variance). Less is better.'},
xAxis: {...commonChartOptions.xAxis, categories: frametimeSdvCategories},
yAxis: {...commonChartOptions.yAxis, title: {text: 'Value', align: 'high', style: {color: '#FFFFFF'}}},
tooltip: {...commonChartOptions.tooltip, formatter: function() {return `${this.series.name}: ${this.y.toFixed(2)}`;}},
plotOptions: {bar: {dataLabels: {enabled: true, style: {color: '#FFFFFF'}, formatter: function() {return this.y.toFixed(2);}}}},
legend: {...commonChartOptions.legend, enabled: true},
series: [
{name: 'Std. Dev.', data: frametimeStandardDeviations, color: '#FF5733'},
{name: 'Variance', data: frametimeVariances, color: '#33FF57'}
]
});