Merge remote-tracking branch 'origin/develop' into dev/syncCamera

This commit is contained in:
Yann Lanthony 2019-09-16 12:24:22 +02:00
commit 633b8c0e00
No known key found for this signature in database
GPG key ID: 519FAE6DF7A70642
12 changed files with 857 additions and 25 deletions

View file

@ -24,15 +24,15 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Log**
If applicable, copy paste the relevant log output (please embed the text in a markdown code tag "```" )
If applicable, copy paste the relevant log output (please embed the text in a markdown code tag "\`\`\`" )
**Desktop (please complete the following and other pertinent information):**
- OS: [e.g. win 10, osx, ]
- Version [e.g. 2019.1]
- Python version [e.g. 2.6]
- Qt/PySide version [e.g. 5.12.4]
- Binary version (if applicable) [e.g. 2019.1]
- Commit reference (if applicable) [e.g. 08ddbe2]
- Meshroom version: please specify if you are using a release version or your own build
- Binary version (if applicable) [e.g. 2019.1]
- Commit reference (if applicable) [e.g. 08ddbe2]
**Additional context**
Add any other context about the problem here.

31
.github/ISSUE_TEMPLATE/question_help.md vendored Normal file
View file

@ -0,0 +1,31 @@
---
name: Question or help needed
about: Ask question or for help for issues not related to program failures (e.g. "where I can find this feature", "my dataset is not reconstructed properly", "which parameter setting shall I use" etc...)
title: "[question]"
labels: type:question
assignees: ''
---
**Describe the problem**
A clear and concise description of what the problem is.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Dataset**
If applicable, add a link or *few* images to help better understand where the problem may come from.
**Log**
If applicable, copy paste the relevant log output (please embed the text in a markdown code tag "\`\`\`" )
**Desktop (please complete the following and other pertinent information):**
- OS: [e.g. win 10, osx, ]
- Python version [e.g. 2.6]
- Qt/PySide version [e.g. 5.12.4]
- Meshroom version: please specify if you are using a release version or your own build
- Binary version (if applicable) [e.g. 2019.1]
- Commit reference (if applicable) [e.g. 08ddbe2]
**Additional context**
Add any other context about the problem here.

View file

@ -1,5 +1,5 @@
# packaging
cx_Freeze
cx_Freeze==5.1.1
# testing
pytest

View file

@ -285,6 +285,7 @@ class NodeChunk(BaseObject):
# ask and wait for the stats thread to stop
self.statThread.stopRequest()
self.statThread.join()
self.statistics = stats.Statistics()
del runningProcesses[self.name]
self.upgradeStatusTo(Status.SUCCESS)

View file

@ -1,8 +1,18 @@
from collections import defaultdict
import subprocess
import logging
import psutil
import time
import threading
import platform
import os
import sys
if sys.version_info[0] == 2:
# On Python 2 use C implementation for performance and to avoid lots of warnings
from xml.etree import cElementTree as ET
else:
import xml.etree.ElementTree as ET
def bytes2human(n):
@ -25,15 +35,58 @@ def bytes2human(n):
class ComputerStatistics:
def __init__(self):
# TODO: init
self.nbCores = 0
self.cpuFreq = 0
self.ramTotal = 0
self.ramAvailable = 0 # GB
self.vramAvailable = 0 # GB
self.swapAvailable = 0
self.gpuMemoryTotal = 0
self.gpuName = ''
self.curves = defaultdict(list)
self._isInit = False
def initOnFirstTime(self):
if self._isInit:
return
self._isInit = True
self.cpuFreq = psutil.cpu_freq().max
self.ramTotal = psutil.virtual_memory().total / 1024/1024/1024
if platform.system() == "Windows":
from distutils import spawn
# If the platform is Windows and nvidia-smi
# could not be found from the environment path,
# try to find it from system drive with default installation path
self.nvidia_smi = spawn.find_executable('nvidia-smi')
if self.nvidia_smi is None:
self.nvidia_smi = "%s\\Program Files\\NVIDIA Corporation\\NVSMI\\nvidia-smi.exe" % os.environ['systemdrive']
else:
self.nvidia_smi = "nvidia-smi"
try:
p = subprocess.Popen([self.nvidia_smi, "-q", "-x"], stdout=subprocess.PIPE)
xmlGpu, stdError = p.communicate()
smiTree = ET.fromstring(xmlGpu)
gpuTree = smiTree.find('gpu')
try:
self.gpuMemoryTotal = gpuTree.find('fb_memory_usage').find('total').text.split(" ")[0]
except Exception as e:
logging.debug('Failed to get gpuMemoryTotal: "{}".'.format(str(e)))
pass
try:
self.gpuName = gpuTree.find('product_name').text
except Exception as e:
logging.debug('Failed to get gpuName: "{}".'.format(str(e)))
pass
except Exception as e:
logging.debug('Failed to get information from nvidia_smi at init: "{}".'.format(str(e)))
def _addKV(self, k, v):
if isinstance(v, tuple):
for ki, vi in v._asdict().items():
@ -45,11 +98,41 @@ class ComputerStatistics:
self.curves[k].append(v)
def update(self):
self.initOnFirstTime()
self._addKV('cpuUsage', psutil.cpu_percent(percpu=True)) # interval=None => non-blocking (percentage since last call)
self._addKV('ramUsage', psutil.virtual_memory().percent)
self._addKV('swapUsage', psutil.swap_memory().percent)
self._addKV('vramUsage', 0)
self._addKV('ioCounters', psutil.disk_io_counters())
self.updateGpu()
def updateGpu(self):
try:
p = subprocess.Popen([self.nvidia_smi, "-q", "-x"], stdout=subprocess.PIPE)
xmlGpu, stdError = p.communicate()
smiTree = ET.fromstring(xmlGpu)
gpuTree = smiTree.find('gpu')
try:
self._addKV('gpuMemoryUsed', gpuTree.find('fb_memory_usage').find('used').text.split(" ")[0])
except Exception as e:
logging.debug('Failed to get gpuMemoryUsed: "{}".'.format(str(e)))
pass
try:
self._addKV('gpuUsed', gpuTree.find('utilization').find('gpu_util').text.split(" ")[0])
except Exception as e:
logging.debug('Failed to get gpuUsed: "{}".'.format(str(e)))
pass
try:
self._addKV('gpuTemperature', gpuTree.find('temperature').find('gpu_temp').text.split(" ")[0])
except Exception as e:
logging.debug('Failed to get gpuTemperature: "{}".'.format(str(e)))
pass
except Exception as e:
logging.debug('Failed to get information from nvidia_smi: "{}".'.format(str(e)))
return
def toDict(self):
return self.__dict__
@ -145,12 +228,13 @@ class ProcStatistics:
class Statistics:
"""
"""
fileVersion = 1.0
fileVersion = 2.0
def __init__(self):
self.computer = ComputerStatistics()
self.process = ProcStatistics()
self.times = []
self.interval = 5
def update(self, proc):
'''
@ -169,19 +253,28 @@ class Statistics:
'computer': self.computer.toDict(),
'process': self.process.toDict(),
'times': self.times,
'interval': self.interval
}
def fromDict(self, d):
version = d.get('fileVersion', 1.0)
version = d.get('fileVersion', 0.0)
if version != self.fileVersion:
logging.info('Cannot load statistics, version was {} and we only support {}.'.format(version, self.fileVersion))
self.computer = {}
self.process = {}
self.times = []
return
self.computer.fromDict(d.get('computer', {}))
self.process.fromDict(d.get('process', {}))
self.times = d.get('times', [])
logging.debug('Statistics: file version was {} and the current version is {}.'.format(version, self.fileVersion))
self.computer = {}
self.process = {}
self.times = []
try:
self.computer.fromDict(d.get('computer', {}))
except Exception as e:
logging.debug('Failed while loading statistics: computer: "{}".'.format(str(e)))
try:
self.process.fromDict(d.get('process', {}))
except Exception as e:
logging.debug('Failed while loading statistics: process: "{}".'.format(str(e)))
try:
self.times = d.get('times', [])
except Exception as e:
logging.debug('Failed while loading statistics: times: "{}".'.format(str(e)))
bytesPerGiga = 1024. * 1024. * 1024.
@ -204,7 +297,7 @@ class StatisticsThread(threading.Thread):
try:
while True:
self.updateStats()
if self._stopFlag.wait(60):
if self._stopFlag.wait(self.statistics.interval):
# stopFlag has been set
# update stats one last time and exit main loop
if self.proc.is_running():

View file

@ -30,7 +30,7 @@ class ConvertSfMFormat(desc.CommandLineNode):
label='Describer Types',
description='Describer types to keep.',
value=['sift'],
values=['sift', 'sift_float', 'sift_upright', 'akaze', 'akaze_liop', 'akaze_mldb', 'cctag3', 'cctag4', 'sift_ocv', 'akaze_ocv'],
values=['sift', 'sift_float', 'sift_upright', 'akaze', 'akaze_liop', 'akaze_mldb', 'cctag3', 'cctag4', 'sift_ocv', 'akaze_ocv', 'unknown'],
exclusive=False,
uid=[0],
joinChar=',',

View file

@ -0,0 +1,34 @@
import QtQuick 2.9
import QtQuick.Controls 2.3
/**
* A custom CheckBox designed to be used in ChartView's legend.
*/
CheckBox {
id: root
property color color
leftPadding: 0
font.pointSize: 8
indicator: Rectangle {
width: 11
height: width
border.width: 1
border.color: root.color
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
Rectangle {
anchors.fill: parent
anchors.margins: parent.border.width + 1
visible: parent.parent.checkState != Qt.Unchecked
anchors.topMargin: parent.parent.checkState === Qt.PartiallyChecked ? 5 : 2
anchors.bottomMargin: anchors.topMargin
color: parent.border.color
anchors.centerIn: parent
}
}
}

View file

@ -0,0 +1,105 @@
import QtQuick 2.9
import QtQuick.Controls 2.9
import QtCharts 2.3
/**
* ChartViewLegend is an interactive legend component for ChartViews.
* It provides a CheckBox for each series that can control its visibility,
* and highlight on hovering.
*/
Flow {
id: root
// The ChartView to create the legend for
property ChartView chartView
// Currently hovered series
property var hoveredSeries: null
readonly property ButtonGroup buttonGroup: ButtonGroup {
id: legendGroup
exclusive: false
}
/// Shortcut function to clear legend
function clear() {
seriesModel.clear();
}
// Update internal ListModel when ChartView's series change
Connections {
target: chartView
onSeriesAdded: seriesModel.append({"series": series})
onSeriesRemoved: {
for(var i = 0; i < seriesModel.count; ++i)
{
if(seriesModel.get(i)["series"] === series)
{
seriesModel.remove(i);
return;
}
}
}
}
onChartViewChanged: {
clear();
for(var i = 0; i < chartView.count; ++i)
seriesModel.append({"series": chartView.series(i)});
}
Repeater {
// ChartView series can't be accessed directly as a model.
// Use an intermediate ListModel populated with those series.
model: ListModel {
id: seriesModel
}
ChartViewCheckBox {
ButtonGroup.group: legendGroup
checked: series.visible
text: series.name
color: series.color
onHoveredChanged: {
if(hovered && series.visible)
root.hoveredSeries = series;
else
root.hoveredSeries = null;
}
// hovered serie properties override
states: [
State {
when: series && root.hoveredSeries === series
PropertyChanges { target: series; width: 5.0 }
},
State {
when: series && root.hoveredSeries && root.hoveredSeries !== series
PropertyChanges { target: series; width: 0.2 }
}
]
MouseArea {
anchors.fill: parent
onClicked: {
if(mouse.modifiers & Qt.ControlModifier)
root.soloSeries(index);
else
series.visible = !series.visible;
}
}
}
}
/// Hide all series but the one at index 'idx'
function soloSeries(idx) {
for(var i = 0; i < seriesModel.count; i++) {
chartView.series(i).visible = false;
}
chartView.series(idx).visible = true;
}
}

View file

@ -0,0 +1,4 @@
module Charts
ChartViewLegend 1.0 ChartViewLegend.qml
ChartViewCheckBox 1.0 ChartViewCheckBox.qml

View file

@ -89,8 +89,10 @@ FocusScope {
// only set text file viewer source when ListView is fully ready
// (either empty or fully populated with a valid currentChunk)
// to avoid going through an empty url when switching between two nodes
if(!chunksLV.count || chunksLV.currentChunk)
textFileViewer.source = Filepath.stringToUrl(currentFile);
logComponentLoader.source = Filepath.stringToUrl(currentFile);
}
TabButton {
@ -111,12 +113,35 @@ FocusScope {
}
}
TextFileViewer {
id: textFileViewer
Loader {
id: logComponentLoader
clip: true
Layout.fillWidth: true
Layout.fillHeight: true
autoReload: chunksLV.currentChunk !== undefined && chunksLV.currentChunk.statusName === "RUNNING"
// source is set in fileSelector
property url source
sourceComponent: fileSelector.currentItem.fileProperty === "statisticsFile" ? statViewerComponent : textFileViewerComponent
}
Component {
id: textFileViewerComponent
TextFileViewer {
id: textFileViewer
source: logComponentLoader.source
Layout.fillWidth: true
Layout.fillHeight: true
autoReload: chunksLV.currentChunk !== undefined && chunksLV.currentChunk.statusName === "RUNNING"
// source is set in fileSelector
}
}
Component {
id: statViewerComponent
StatViewer {
id: statViewer
Layout.fillWidth: true
Layout.fillHeight: true
source: logComponentLoader.source
}
}
}
}

View file

@ -0,0 +1,539 @@
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
}
}
}
ChartView {
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: "<span style='color: " + textColor + "'>%</span>"
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: "<span style='color: " + textColor + "'>Minutes</span>"
color: textColor
gridLineColor: textColor
minorGridLineColor: textColor
shadesColor: textColor
shadesBorderColor: textColor
labelsColor: textColor
}
}
}
/**************************
*** RAM UI ***
**************************/
ColumnLayout {
ChartView {
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: valueAxisY2
min: 0
max: 100
titleText: "<span style='color: " + textColor + "'>%</span>"
color: textColor
gridLineColor: textColor
minorGridLineColor: textColor
shadesColor: textColor
shadesBorderColor: textColor
labelsColor: textColor
}
ValueAxis {
id: valueAxisRam
min: 0
max: root.ramTotal
titleText: "<span style='color: " + textColor + "'>GB</span>"
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: "<span style='color: " + textColor + "'>Minutes</span>"
color: textColor
gridLineColor: textColor
minorGridLineColor: textColor
shadesColor: textColor
shadesBorderColor: textColor
labelsColor: textColor
}
}
}
/**************************
*** GPU UI ***
**************************/
ColumnLayout {
ChartView {
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: "<span style='color: " + textColor + "'>%, °C</span>"
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: "<span style='color: " + textColor + "'>Minutes</span>"
color: textColor
gridLineColor: textColor
minorGridLineColor: textColor
shadesColor: textColor
shadesBorderColor: textColor
labelsColor: textColor
}
}
}
}
}
}

View file

@ -1,5 +1,5 @@
# runtime
psutil
psutil>=5.6.3
enum34;python_version<"3.4"
PySide2==5.13.0
markdown==2.6.11