Unsolved QML Application slowly increases CPU usage over time to 100% and becomes unresponsive
-
I've got a QML based application that runs fine for about 12 hours but when I leave it to run overnight the application's UI is no longer responsive to the touchscreen. If i SSH in i can see that on one thread the CPU load is 100%. However the Application still shows updating values on the UI. I have the application polling a sensor in a separate worker thread every ~5s and that appears to be working no issue.
When I profiled the QML app I saw that a lot of the time ~60% was spent servicing events related to the Keyboard.
I'm a bit new to Embedded GUI development and Qt so i was hoping to get some pointers in the right direction. I can't really post the source code unfortunately. Below are screenshots of HTOP output
HTOP: Start of Application
HTOP: End of Application
-
@egbiomated are you sure, you're not creating new objects or at the very least new/additional bindings with each poll from the worker?
Seems like the app is super busy with property signals and updates 31.9% each
-
@J-Hilk said in QML Application slowly increases CPU usage over time to 100% and becomes unresponsive:
are you sure, you're not creating new objects or at the very least new/additional bindings with each poll from the worker?
Seems like the app is super busy with property signals and updates 31.9% eachSorry but I don't think i fully understand how iI would be doing that. Are you referring to the main thread where my application is running my worker passes a qobject to my ApplicationData class slot which then updates it's own private variables that are in a list [QObject]. In terms of the QML:
Isn't all that CPU time associated with the Keyboard?
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 import QtQuick.Controls.Material 2.15 import QtQuick.Layouts 1.3 import "." import QtQuick.VirtualKeyboard 2.2 import "./lib.js" as Lib ApplicationWindow { id: window width: Style.windowWidth height: Style.windowHeight visible: true contentOrientation: Qt.LandscapeOrientation property int headerState: 0 Timer { interval: 5000; running: true; repeat: true onTriggered: { switch(headerState) { case 0: titleLabel.text = Lib.getAuthState() headerState += 1 break; case 1: titleLabel.text = applicationData.getDateTime().toString().split(' ').slice(0,5).join(' ') headerState += 1 break; case 2: headerState += 1 break; default: headerState = 0 titleLabel.text = stackView.currentItem.title } } } Shortcut { sequences: ["Esc", "Back"] enabled: stackView.depth > 1 onActivated: { stackView.pop() listView.currentIndex = -1 } } Shortcut { sequence: "Menu" onActivated: optionsMenu.open() } header: ToolBar { contentHeight: toolButton.implicitHeight transform: Rotation { id: rotation angle: 90 origin.x: 400 origin.y: 400 } RowLayout { // spacing: 20 anchors.fill: parent // width: 480 ToolButton { id: toolButton text: stackView.depth > 1 ? "\u25C0" : "\u2630" font.pixelSize: Qt.application.font.pixelSize * 1.6 onClicked: { if (stackView.depth > 1) { stackView.pop() } else { drawer.open() } } } Label { id: titleLabel height: parent.height Layout.alignment: horizontalCenter font.pixelSize: 20 elide: Label.ElideRight horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter text: Lib.getAuthState() } ToolButton { id: menuButton text: "\u2606" font.pointSize: Style.h3 font.bold: true } } } ListModel { id: menuModel ListElement { title: "Home"; source: "qrc:/pages/SensorList.qml"; } ListElement { title: "Sensor Setup"; source: "qrc:/pages/SensorsSetup.qml"; section: "Setup"; } ListElement { title: "Calibration"; source: "qrc:/pages/Calibration.qml"; section: "Setup"; } ListElement { title: "Alarm Setup"; source: "qrc:/pages/AlarmSetup.qml"; section: "Setup"; } ListElement { title: "Analog Outputs"; source: "qrc:/pages/AnalogOutputs.qml"; section: "Configuration" ; } ListElement { title: "Relay Outputs"; source: "qrc:/pages/AlarmOutputs.qml"; section: "Configuration"; } ListElement { title: "Address Setup"; source: "qrc:/pages/SetAddress.qml"; section: "Configuration"; } ListElement { title: "Device Setup"; source: "qrc:/pages/DeviceSetup.qml"; section: "Configuration"; } } StackView { width: Style.windowWidth height: Style.windowHeight id: stackView initialItem: "/pages/SensorList.qml" } //ABOUT Dialog Dialog { id: aboutDialog modal: true focus: true title: "Device Information" x: (window.width - width) / 2 y: window.height / 6 width: Math.min(window.width, window.height) * .9 contentHeight: aboutColumn.height font.pointSize: Style.h1 font.bold: true background: Rectangle { width: parent.width height: parent.height color: "white" } Column { id: aboutColumn spacing: 20 Text { width: aboutDialog.availableWidth text: "Device S/N: 123209" font.pixelSize: Style.h2 } Text { width: aboutDialog.availableWidth text: "Device Uptime: 5D 3H 33M" font.pixelSize: Style.h2 } } } InputPanel { id: inputPanel width: 480 active: true rotation: 90 y: 165 x: Qt.inputMethod.visible ? -180 : -180 - inputPanel.height } }
Is there something you see here that would lead you to any conclusion.
-
I have encountered performance problems with
StackView
in the past.Full disclosure: I do think that some of my struggles with
StackView
stem from my neglecting to actually study it properly and understand the various ways to use it.For my applications, it has just become easier to avoid
StackView
. In your case, you will need to choose whether to avoid it or whether instead to learn about it further.I have been able to avoid it because I was using it simply to manage a single foregrounded full-screen item at a time.
If all you need is to create a "conceptual stack" of equally-sized items (like "pages" in your app), such that only one of them is ever visible at a time, then you can do this without a
StackView
. Just create each "page" to be the same size and location, but link thevisible
boolean to a custom expression that ensures that it only becomestrue
for one of the pages at a time.It will turn into something like the following:
Item { id: front anchors.fill: parent property int whichScreen: 0 MainMenu { anchors.fill: parent visible: front.whichScreen == 0 onGotoPitchTrain: { front.whichScreen = 1 } onGotoKeySigTrain: { front.whichScreen = 2 } } PitchTrain { id: pt anchors.fill: parent visible: front.whichScreen == 1 } KeySigTrain { id: ks anchors.fill: parent visible: front.whichScreen == 2 } }
-
@KH-219Design Do you think that the stack view would still be an issue even when the device is idling the CPU run's high when the device IDLE's over night i'm not switching between the screens that the stack view is loading all of the time. But like you said I don't have a solid understanding of its internals.
-
What is that timer for? Does speeding it up cause the problem to happen faster? Are there C++ objects being allocated as a result of things changing?
-
@egbiomated said in QML Application slowly increases CPU usage over time to 100% and becomes unresponsive:
i'm not switching between the screens that the stack view is loading
It has been over a year (possibly more than 2) since I have used
StackView
, so my memory has faded regarding the exact unwanted symptoms I observed and the exact triggering scenarios.However, if no methods on the
StackView
are being called and no properties on theStackView
are being mutated when you encounter the unwanted CPU usage, then it does seem improbable to blameStackView
in that case. -
@fcarney
The timer is just switching a banner in the Header of the Page from a Text String to a Date Object. The date object is generated in C++ and passed through the context -
@KH-219Design Properties are being updated on the item that is displayed on the StackView but the stackview itself is not changing. The page displayed.
Each Page in the stack view consists of a page like this there are a fixed number of sensors that I am dealing with that can be on or off at any given time so i use a listview to render all the components so the updates do happen with a fair degree of nesting
import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 import QtQuick.Controls.Material 2.12 import QtQuick.Layouts 1.3 import "../" import "../lib.js" as Lib import "../components" as Components Page { // width: window.width // height: window.height width: Style.windowWidth height: Style.windowHeight // title: qsTr("Sensors") id: sensorSetupPage title: qsTr("Home") transform: Rotation { id: pageRotation angle: 90 origin.x: 400 origin.y: 352.5 } Component.onCompleted: { applicationData.setDeviceState(0) } ListView { id: sensorListView width: parent.width height: parent.height model: Lib.getAvailableSensor(applicationData.sensor_list).length delegate: Components.DynamicSensor { width: parent.width height: Math.ceil((sensorListView.height-50)/(Lib.getAvailableSensor(applicationData.sensor_list).length)) sensorData: applicationData.sensor_list[Lib.getAvailableSensorIndex(index)] numAvailable: Lib.getAvailableSensor(applicationData.sensor_list).length MouseArea { anchors.fill: parent onClicked: { // applicationData.sInFocus = Lib.getAvailableSensorIndex(index) applicationData.setSensorInfocus(Lib.getAvailableSensorIndex(index)) stackView.push("qrc:/pages/SensorDetail.qml") } } } } }
import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Controls.Material 2.12 import Qt.labs.settings 1.0 import "../" import "../lib.js" as Lib Rectangle { id: dynamicSensor width: parent.width height: parent.height property var sensorData function getSensorUnits() { if (sensorData.sensorUnits === 0) { return "PPM" } else if (sensorData.sensorUnits === 1) { return "PPB" } else { return "% 0<sub>2</sub>" } } function parseO2Data() { var units = getSensorUnits(); if (sensorData.o2Level > 0) { if (units === "PPM") { return sensorData.o2Level.toFixed(3) } else if (units === "PPB") { return sensorData.o2Level.toFixed(1) } else { return sensorData.o2Level.toFixed(2) } } else { return 0.00 } } function getColor() { if (sensorData.alarmTripped && !sensorData.standby && !applicationData.alarm_list[Lib.getAvailableSensorIndex(index)].ack) { return "#ff5252" } else if (sensorData.pulseCount > 900000) { return "yellow" } else { return "transparent" } } property string sensorName: "Sensor 1: Condensate Pump" property string sensorUnits: getSensorUnits() property int numAvailable: Lib.getAvailableSensor() property int sensorIndex: Lib.getAvailableSensorIndex(index) property bool isLast: numAvailable === (sensorIndex) ? true : false property real rowSpacers: (numAvailable < 3 ? 24 : 16) // color: (sensorData.alarmTripped && !sensorData.standby && !applicationData.alarm_list[Lib.getAvailableSensorIndex(index)].ack) ? "#ff5252" : "transparent" color: getColor() function getFontSize() { var baseSize = Style.h1 + (numAvailable < 3 ? 30 : 15) // if (getSensorUnits() === "PPM") { // baseSize -= 30 //} return baseSize } Column { width: (parent.width - (Style.gutter*2)) leftPadding: Style.gutter // height: parent.height topPadding: 10 // spacing: 1 Text { id: sensorNameRow width: parent.width height: (Style.h3 + rowSpacers) horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter font.bold: true font.underline: true font.pointSize: Style.h3 // text: sensorName text: "Sensor " + (sensorIndex + 1) + ": " + sensorData.sensorName // bottomPadding: 10 // toPadding: 10 } Row { id: oxygenUnitRow width: parent.width height: (Style.h3 + rowSpacers) Text { width: parent.width/2 height: parent.height horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter font.bold: true font.pointSize: Style.h3 text: "Dissolved 0<sub>2</sub>" } Text { width: parent.width/2 height: parent.height rightPadding: Style.gutter horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter font.bold: true font.pointSize: Style.h3 text: sensorUnits } } Text { width: parent.width height: dynamicSensor.height - (sensorNameRow.height + oxygenUnitRow.height + temperatureRow.height + 10) topPadding: 5 bottomPadding: 5 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.bold: true // font.pointSize: Style.h1 + (numAvailable < 3 ? 30 : 15) font.pointSize: getFontSize() text: sensorData.standby ? "--.--" : parseO2Data() } Row { id: temperatureRow width: parent.width height: (Style.h3 + rowSpacers) Text { width: parent.width/3 height: parent.height horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignBottom font.bold: true font.pointSize: Style.h3 text: !sensorData.standby ? "Online" : "Standby" } Text { width: parent.width/3 height: parent.height horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignBottom font.bold: true font.pointSize: Style.h3 text: !applicationData.alarm_list[sensorIndex].ack ? "" : "Alarm Ack." } Text { width: parent.width/3 height: parent.height rightPadding: Style.gutter horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter font.bold: true font.pointSize: Style.h3 text: sensorData.standby ? "--.--" : (sensorData.temp).toFixed(2) + (sensorData.tempUnits === 0 ? " \xB0C" : " \xB0F") } } Rectangle { width: parent.width height: isLast ? 0 : 2 border.width: isLast ? 0 : 2 border.color: "black" } } }
-
You are going to have to create a minimal program that no longer exhibits the problem and slowly add things until you find what is causing this. Or start taking out chunks and testing that. You will have to do process of elimination. I would start with removing the virtual keyboard since you have a bunch of stuff having to do with keyboard processing. Also, make sure there is not a cat sleeping on a wireless keyboard somewhere.
-
You could also run
dmesg
to look at the kernel log, to see if there are any strange messages related to input drivers. (I have nothing in mind that I would expect to find, I'm just continuing along the lines of the keyboard and touchscreen investigation.)In addition to keyboard, I'm looking at the flame diagram again and seeing
filterLocaleIndices
andonLocaleChanged
near the middle of both the two main "mountains". Could there be somecron
job or something that runs overnight and updates/adds/removes locales? or switches the user's locale?@fcarney 's suggestion about process of elimination makes sense.
I wonder if you really have to run it overnight, or if perhaps you can force the device to "sleep" or to go to the lock screen a couple times, and maybe that is all it takes to trigger it?
-
That flame graph is just from the first 4 minutes of operation not the entire 24 hours sorry should've said that before. dmesg doesn't turn up anything that would make me think there is an issue and cron doesn't exist on the machine it's the stock boot2qt image