Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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.
    3882b66a-1d40-4fe4-83e0-dbd36b5678e6-image.png
    576e4d9a-5b97-4db4-9b41-788306f717eb-image.png

    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
    d5f79ca6-bacf-43ae-bdab-6f21bde978d5-image.png

    HTOP: End of Application
    e3b114d2-e0a2-4ea8-bed7-7c21714f64e6-image.png


  • Moderators

    @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% each

    Sorry 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 the visible boolean to a custom expression that ensures that it only becomes true 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 the StackView are being mutated when you encounter the unwanted CPU usage, then it does seem improbable to blame StackView 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 and onLocaleChanged near the middle of both the two main "mountains". Could there be some cron 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


Log in to reply