import QtQuick 2.7 import QtQuick.Controls 2.3 import QtCharts 2.2 import QtQuick.Layouts 1.11 import Utils 1.0 import Charts 1.0 import MaterialIcons 2.2 Item { id: root implicitWidth: 500 implicitHeight: 500 /// Statistics source file property url source property var sourceModified: undefined property var jsonObject property real fileVersion: 0.0 property int nbReads: 1 property real deltaTime: 1 property int nbCores: 0 property int cpuFrequency: 0 property int ramTotal property string ramLabel: "RAM: " property int gpuTotalMemory property int gpuMaxAxis: 100 property string gpuName property color textColor: Colors.sysPalette.text readonly property var colors: [ "#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4", "#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722", "#b71c1c", "#880E4F", "#4A148C", "#311B92", "#1A237E", "#0D47A1", "#01579B", "#006064", "#004D40", "#1B5E20", "#33691E", "#827717", "#F57F17", "#FF6F00", "#E65100", "#BF360C" ] onSourceChanged: { sourceModified = undefined; resetCharts() readSourceFile() } function getPropertyWithDefault(prop, name, defaultValue) { if(prop.hasOwnProperty(name)) { return prop[name]; } return defaultValue; } Timer { id: reloadTimer interval: root.deltaTime * 60000; running: true; repeat: false onTriggered: readSourceFile() } function readSourceFile() { // make sure we are trying to load a statistics file if(!Filepath.urlToString(source).endsWith("statistics")) return; var xhr = new XMLHttpRequest; xhr.open("GET", source); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status == 200) { if(sourceModified === undefined || sourceModified < xhr.getResponseHeader('Last-Modified')) { try { root.jsonObject = JSON.parse(xhr.responseText); } catch(exc) { console.warning("Failed to parse statistics file: " + source) root.jsonObject = {}; return; } resetCharts(); sourceModified = xhr.getResponseHeader('Last-Modified') root.createCharts(); reloadTimer.restart(); } } }; xhr.send(); } function resetCharts() { root.fileVersion = 0.0 cpuLegend.clear() cpuChart.removeAllSeries() ramChart.removeAllSeries() gpuChart.removeAllSeries() } function createCharts() { root.deltaTime = getPropertyWithDefault(jsonObject, 'interval', 30) / 60.0; root.fileVersion = getPropertyWithDefault(jsonObject, 'fileVersion', 0.0) initCpuChart() initRamChart() initGpuChart() } /************************** *** CPU *** **************************/ function initCpuChart() { var categories = [] var categoryCount = 0 var category do { category = jsonObject.computer.curves["cpuUsage." + categoryCount] if(category !== undefined) { categories.push(category) categoryCount++ } } while(category !== undefined) var nbCores = categories.length root.nbCores = nbCores root.cpuFrequency = getPropertyWithDefault(jsonObject.computer, 'cpuFreq', -1) root.nbReads = categories[0].length-1 for(var j = 0; j < nbCores; j++) { var lineSerie = cpuChart.createSeries(ChartView.SeriesTypeLine, "CPU" + j, valueAxisX, valueAxisY) if(categories[j].length === 1) { lineSerie.append(0, categories[j][0]) lineSerie.append(root.deltaTime, categories[j][0]) } else { for(var k = 0; k < categories[j].length; k++) { lineSerie.append(k * root.deltaTime, categories[j][k]) } } lineSerie.color = colors[j % colors.length] } var averageLine = cpuChart.createSeries(ChartView.SeriesTypeLine, "AVERAGE", valueAxisX, valueAxisY) var average = [] for(var l = 0; l < categories[0].length; l++) { average.push(0) } for(var m = 0; m < categories.length; m++) { for(var n = 0; n < categories[m].length; n++) { average[n] += categories[m][n] } } for(var q = 0; q < average.length; q++) { average[q] = average[q] / (categories.length) averageLine.append(q * root.deltaTime, average[q]) } averageLine.color = colors[colors.length-1] } function hideOtherCpu(index) { for(var i = 0; i < cpuChart.count; i++) { cpuChart.series(i).visible = false; } cpuChart.series(index).visible = true; } /************************** *** RAM *** **************************/ function initRamChart() { var ram = getPropertyWithDefault(jsonObject.computer.curves, 'ramUsage', -1) root.ramTotal = getPropertyWithDefault(jsonObject.computer, 'ramTotal', -1) root.ramLabel = "RAM: " if(root.ramTotal <= 0) { var maxRamPeak = 0 for(var i = 0; i < ram.length; i++) { maxRamPeak = Math.max(maxRamPeak, ram[i]) } root.ramTotal = maxRamPeak root.ramLabel = "RAM Max Peak: " } var ramSerie = ramChart.createSeries(ChartView.SeriesTypeLine, root.ramLabel + root.ramTotal + "GB", valueAxisX2, valueAxisRam) if(ram.length === 1) { // Create 2 entries if we have only one input value to create a segment that can be display ramSerie.append(0, ram[0]) ramSerie.append(root.deltaTime, ram[0]) } else { for(var i = 0; i < ram.length; i++) { ramSerie.append(i * root.deltaTime, ram[i]) } } ramSerie.color = colors[10] } /************************** *** GPU *** **************************/ function initGpuChart() { root.gpuTotalMemory = getPropertyWithDefault(jsonObject.computer, 'gpuMemoryTotal', 0) root.gpuName = getPropertyWithDefault(jsonObject.computer, 'gpuName', '') var gpuUsedMemory = getPropertyWithDefault(jsonObject.computer.curves, 'gpuMemoryUsed', 0) var gpuUsed = getPropertyWithDefault(jsonObject.computer.curves, 'gpuUsed', 0) var gpuTemperature = getPropertyWithDefault(jsonObject.computer.curves, 'gpuTemperature', 0) var gpuUsedSerie = gpuChart.createSeries(ChartView.SeriesTypeLine, "GPU", valueAxisX3, valueAxisY3) var gpuUsedMemorySerie = gpuChart.createSeries(ChartView.SeriesTypeLine, "Memory", valueAxisX3, valueAxisY3) var gpuTemperatureSerie = gpuChart.createSeries(ChartView.SeriesTypeLine, "Temperature", valueAxisX3, valueAxisY3) if(gpuUsedMemory.length === 1) { gpuUsedSerie.append(0, gpuUsed[0]) gpuUsedSerie.append(1 * root.deltaTime, gpuUsed[0]) gpuUsedMemorySerie.append(0, gpuUsedMemory[0] / root.gpuTotalMemory * 100) gpuUsedMemorySerie.append(1 * root.deltaTime, gpuUsedMemory[0] / root.gpuTotalMemory * 100) gpuTemperatureSerie.append(0, gpuTemperature[0]) gpuTemperatureSerie.append(1 * root.deltaTime, gpuTemperature[0]) root.gpuMaxAxis = Math.max(gpuMaxAxis, gpuTemperature[0]) } else { for(var i = 0; i < gpuUsedMemory.length; i++) { gpuUsedSerie.append(i * root.deltaTime, gpuUsed[i]) gpuUsedMemorySerie.append(i * root.deltaTime, gpuUsedMemory[i] / root.gpuTotalMemory * 100) gpuTemperatureSerie.append(i * root.deltaTime, gpuTemperature[i]) root.gpuMaxAxis = Math.max(gpuMaxAxis, gpuTemperature[i]) } } } /************************** *** UI *** **************************/ ScrollView { height: root.height width: root.width ScrollBar.vertical.policy: ScrollBar.AlwaysOn ColumnLayout { width: root.width /************************** *** CPU UI *** **************************/ ColumnLayout { Layout.fillWidth: true Button { id: toggleCpuBtn Layout.fillWidth: true text: "Toggle CPU's" state: "closed" onClicked: state === "opened" ? state = "closed" : state = "opened" MaterialLabel { text: MaterialIcons.arrow_drop_down font.pointSize: 14 anchors.right: parent.right } states: [ State { name: "opened" PropertyChanges { target: cpuBtnContainer; visible: true } PropertyChanges { target: toggleCpuBtn; down: true } }, State { name: "closed" PropertyChanges { target: cpuBtnContainer; visible: false } PropertyChanges { target: toggleCpuBtn; down: false } } ] } Item { id: cpuBtnContainer Layout.fillWidth: true implicitHeight: childrenRect.height Layout.leftMargin: 25 RowLayout { width: parent.width anchors.horizontalCenter: parent.horizontalCenter ChartViewCheckBox { id: allCPU text: "ALL" color: textColor checkState: cpuLegend.buttonGroup.checkState leftPadding: 0 onClicked: { var _checked = checked; for(var i = 0; i < cpuChart.count; ++i) { cpuChart.series(i).visible = _checked; } } } ChartViewLegend { id: cpuLegend Layout.fillWidth: true Layout.fillHeight: true chartView: cpuChart } } } InteractiveChartView { id: cpuChart Layout.fillWidth: true Layout.preferredHeight: width/2 margins.top: 0 margins.bottom: 0 antialiasing: true legend.visible: false theme: ChartView.ChartThemeLight backgroundColor: "transparent" plotAreaColor: "transparent" titleColor: textColor visible: (root.fileVersion > 0.0) // only visible if we have valid information title: "CPU: " + root.nbCores + " cores, " + root.cpuFrequency + "Hz" ValueAxis { id: valueAxisY min: 0 max: 100 titleText: "%" color: textColor gridLineColor: textColor minorGridLineColor: textColor shadesColor: textColor shadesBorderColor: textColor labelsColor: textColor } ValueAxis { id: valueAxisX min: 0 max: root.deltaTime * Math.max(1, root.nbReads) titleText: "Minutes" color: textColor gridLineColor: textColor minorGridLineColor: textColor shadesColor: textColor shadesBorderColor: textColor labelsColor: textColor } } } /************************** *** RAM UI *** **************************/ ColumnLayout { InteractiveChartView { id: ramChart margins.top: 0 margins.bottom: 0 Layout.fillWidth: true Layout.preferredHeight: width/2 antialiasing: true legend.color: textColor legend.labelColor: textColor legend.visible: false theme: ChartView.ChartThemeLight backgroundColor: "transparent" plotAreaColor: "transparent" titleColor: textColor visible: (root.fileVersion > 0.0) // only visible if we have valid information title: root.ramLabel + root.ramTotal + "GB" ValueAxis { id: valueAxisRam min: 0 max: 100 titleText: "%" color: textColor gridLineColor: textColor minorGridLineColor: textColor shadesColor: textColor shadesBorderColor: textColor labelsColor: textColor } ValueAxis { id: valueAxisX2 min: 0 max: root.deltaTime * Math.max(1, root.nbReads) titleText: "Minutes" color: textColor gridLineColor: textColor minorGridLineColor: textColor shadesColor: textColor shadesBorderColor: textColor labelsColor: textColor } } } /************************** *** GPU UI *** **************************/ ColumnLayout { InteractiveChartView { id: gpuChart Layout.fillWidth: true Layout.preferredHeight: width/2 margins.top: 0 margins.bottom: 0 antialiasing: true legend.color: textColor legend.labelColor: textColor theme: ChartView.ChartThemeLight backgroundColor: "transparent" plotAreaColor: "transparent" titleColor: textColor visible: (root.fileVersion >= 2.0) // No GPU information was collected before stats 2.0 fileVersion title: (root.gpuName || root.gpuTotalMemory) ? ("GPU: " + root.gpuName + ", " + root.gpuTotalMemory + "MB") : "No GPU" ValueAxis { id: valueAxisY3 min: 0 max: root.gpuMaxAxis titleText: "%, °C" color: textColor gridLineColor: textColor minorGridLineColor: textColor shadesColor: textColor shadesBorderColor: textColor labelsColor: textColor } ValueAxis { id: valueAxisX3 min: 0 max: root.deltaTime * Math.max(1, root.nbReads) titleText: "Minutes" color: textColor gridLineColor: textColor minorGridLineColor: textColor shadesColor: textColor shadesBorderColor: textColor labelsColor: textColor } } } } } }