forked from qt-creator/qt-creator
Core: add an ui watchdog to QtSingleApplication
Adds the UI Watchdog of the KDToolBox to the QtSingleApplication that periodically prints a debug message while the ui thread is locked with the current duration of the lock if QTC_UI_WATCHDOG is defined in the environment. Additionally this gives the oportunity to break whenever the ui thread is locked. Change-Id: I32aaa9f34822fa7f4a0dd1aa703fca49955b34a4 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
@@ -674,5 +674,19 @@
|
||||
"License": "MIT License",
|
||||
"LicenseFile": "src/libs/3rdparty/toml11/LICENSE",
|
||||
"Copyright": "Copyright (c) 2017-2024 Toru Niina."
|
||||
},
|
||||
{
|
||||
"Id": "ui_watchdog",
|
||||
"Name": "UI Watchdog",
|
||||
"QDocModule": "qtcreator",
|
||||
"QtParts": ["tools"],
|
||||
"QtUsage": "Used for watching the UI thread of Qt Creator.",
|
||||
"Path": "src/libs/3rdparty/ui_watchdog",
|
||||
"Description": "Header-only TOML config file parser and serializer for C++.",
|
||||
"Homepage": "https://github.com/KDABLabs/KDToolBox/tree/master/qt/ui_watchdog",
|
||||
"Version": "master",
|
||||
"License": "MIT License",
|
||||
"LicenseFile": "src/libs/3rdparty/ui_watchdog/LICENSE.txt",
|
||||
"Copyright": "2018 Klarälvdalens Datakonsult AB, a KDAB Group company"
|
||||
}
|
||||
]
|
||||
|
7
src/libs/3rdparty/ui_watchdog/CMakeLists.txt
vendored
Normal file
7
src/libs/3rdparty/ui_watchdog/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# This file is part of KDToolBox.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
add_subdirectory(example)
|
4
src/libs/3rdparty/ui_watchdog/LICENSE.txt
vendored
Normal file
4
src/libs/3rdparty/ui_watchdog/LICENSE.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
KDToolBox is © Klarälvdalens Datakonsult AB (KDAB) and is made available
|
||||
under the terms of the MIT license (see LICENSES/MIT.txt).
|
||||
|
||||
Contact KDAB at <info@kdab.com> to inquire about commercial licensing.
|
9
src/libs/3rdparty/ui_watchdog/LICENSES/MIT.txt
vendored
Normal file
9
src/libs/3rdparty/ui_watchdog/LICENSES/MIT.txt
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
src/libs/3rdparty/ui_watchdog/README.md
vendored
Normal file
20
src/libs/3rdparty/ui_watchdog/README.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# UI Watchdog
|
||||
|
||||
Header-only tool that monitors the main thread and breaks the program whenever
|
||||
its event loop hasn't run for `MAX_TIME_BLOCKED` (default 300ms).
|
||||
|
||||
Currently it only breaks the program when running on Windows, but actions to perform
|
||||
can be customized next to the "Add custom action here" comment.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```cpp
|
||||
#include "uiwatchdog.h"
|
||||
|
||||
(...)
|
||||
|
||||
UiWatchdog dog;
|
||||
dog.start();
|
||||
|
||||
return app.exec();
|
||||
```
|
22
src/libs/3rdparty/ui_watchdog/example/CMakeLists.txt
vendored
Normal file
22
src/libs/3rdparty/ui_watchdog/example/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# This file is part of KDToolBox.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
find_package(
|
||||
Qt${QT_VERSION_MAJOR}
|
||||
${QT_REQUIRED_VERSION}
|
||||
CONFIG
|
||||
REQUIRED
|
||||
Core
|
||||
Gui
|
||||
Widgets
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
set(ui_watchdog_example_SOURCES ../uiwatchdog.h main.cpp)
|
||||
|
||||
add_executable(ui_watchdog_example ${ui_watchdog_example_SOURCES})
|
||||
target_link_libraries(ui_watchdog_example PUBLIC Qt::Core Qt::Gui Qt::Widgets)
|
53
src/libs/3rdparty/ui_watchdog/example/main.cpp
vendored
Normal file
53
src/libs/3rdparty/ui_watchdog/example/main.cpp
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
This file is part of KDToolBox.
|
||||
|
||||
SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sérgio Martins <sergio.martins@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "uiwatchdog.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QtWidgets>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QWidget w;
|
||||
auto layout = new QVBoxLayout(&w);
|
||||
auto button = new QPushButton(QStringLiteral("Block UI completely"));
|
||||
auto button2 = new QPushButton(QStringLiteral("Sleep every other second"));
|
||||
auto button3 = new QPushButton(QStringLiteral("Cancel sleep"));
|
||||
layout->addWidget(button);
|
||||
layout->addWidget(button2);
|
||||
layout->addStretch();
|
||||
layout->addWidget(button3);
|
||||
|
||||
QObject::connect(button, &QPushButton::clicked, [] {
|
||||
qDebug() << "Blocking forever!";
|
||||
while (true)
|
||||
;
|
||||
});
|
||||
|
||||
auto sleepTimer = new QTimer(&w);
|
||||
sleepTimer->setInterval(1000);
|
||||
QObject::connect(sleepTimer, &QTimer::timeout, [] {
|
||||
qDebug() << "Sleeping";
|
||||
QThread::sleep(1);
|
||||
qDebug() << "Waking up";
|
||||
});
|
||||
|
||||
QObject::connect(button2, &QPushButton::clicked, sleepTimer, QOverload<>::of(&QTimer::start));
|
||||
QObject::connect(button3, &QPushButton::clicked, sleepTimer, &QTimer::stop);
|
||||
|
||||
UiWatchdog dog;
|
||||
dog.start();
|
||||
w.resize(800, 800);
|
||||
w.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
160
src/libs/3rdparty/ui_watchdog/uiwatchdog.h
vendored
Normal file
160
src/libs/3rdparty/ui_watchdog/uiwatchdog.h
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
This file is part of KDToolBox.
|
||||
|
||||
SPDX-FileCopyrightText: 2018 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sérgio Martins <sergio.martins@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef UIWATCHDOG_H
|
||||
#define UIWATCHDOG_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMutex>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#define MAX_TIME_BLOCKED 300 // ms
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(uidelays)
|
||||
Q_LOGGING_CATEGORY(uidelays, "uidelays")
|
||||
|
||||
class UiWatchdog;
|
||||
class UiWatchdogWorker : public QObject
|
||||
{
|
||||
public:
|
||||
enum Option
|
||||
{
|
||||
OptionNone = 0,
|
||||
OptionDebugBreak = 1
|
||||
};
|
||||
typedef int Options;
|
||||
|
||||
private:
|
||||
UiWatchdogWorker(Options options)
|
||||
: QObject()
|
||||
, m_watchTimer(new QTimer(this))
|
||||
, m_options(options)
|
||||
{
|
||||
qCDebug(uidelays) << "UiWatchdogWorker created";
|
||||
connect(m_watchTimer, &QTimer::timeout, this, &UiWatchdogWorker::checkUI);
|
||||
}
|
||||
|
||||
~UiWatchdogWorker()
|
||||
{
|
||||
qCDebug(uidelays) << "UiWatchdogWorker destroyed";
|
||||
stop();
|
||||
}
|
||||
|
||||
void start(int frequency_msecs = 200)
|
||||
{
|
||||
m_watchTimer->start(frequency_msecs);
|
||||
m_elapsedTimeSinceLastBeat.start();
|
||||
}
|
||||
|
||||
void stop() { m_watchTimer->stop(); }
|
||||
|
||||
void checkUI()
|
||||
{
|
||||
qint64 elapsed;
|
||||
|
||||
{
|
||||
QMutexLocker l(&m_mutex);
|
||||
elapsed = m_elapsedTimeSinceLastBeat.elapsed();
|
||||
}
|
||||
|
||||
if (elapsed > MAX_TIME_BLOCKED)
|
||||
{
|
||||
qDebug() << "UI is blocked !" << elapsed; // Add custom action here!
|
||||
if ((m_options & OptionDebugBreak))
|
||||
debugBreak();
|
||||
}
|
||||
}
|
||||
|
||||
void debugBreak()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
DebugBreak();
|
||||
#endif
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
QMutexLocker l(&m_mutex);
|
||||
m_elapsedTimeSinceLastBeat.restart();
|
||||
}
|
||||
|
||||
QTimer *const m_watchTimer;
|
||||
QElapsedTimer m_elapsedTimeSinceLastBeat;
|
||||
QMutex m_mutex;
|
||||
const Options m_options;
|
||||
friend class UiWatchdog;
|
||||
};
|
||||
|
||||
class UiWatchdog : public QObject
|
||||
{
|
||||
public:
|
||||
explicit UiWatchdog(UiWatchdogWorker::Options options = UiWatchdogWorker::OptionNone, QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
, m_uiTimer(new QTimer(this))
|
||||
, m_options(options)
|
||||
{
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("uidelays.debug=false"));
|
||||
qCDebug(uidelays) << "UiWatchdog created";
|
||||
connect(m_uiTimer, &QTimer::timeout, this, &UiWatchdog::onUiBeat);
|
||||
}
|
||||
|
||||
~UiWatchdog()
|
||||
{
|
||||
stop();
|
||||
qCDebug(uidelays) << "UiWatchdog destroyed";
|
||||
}
|
||||
|
||||
void start(int frequency_msecs = 100)
|
||||
{
|
||||
if (m_worker)
|
||||
return;
|
||||
|
||||
m_uiTimer->start(frequency_msecs);
|
||||
|
||||
m_worker = new UiWatchdogWorker(m_options);
|
||||
m_watchDogThread = new QThread(this);
|
||||
m_worker->moveToThread(m_watchDogThread);
|
||||
m_watchDogThread->start();
|
||||
connect(m_watchDogThread, &QThread::started, m_worker,
|
||||
[this, frequency_msecs] { m_worker->start(frequency_msecs); });
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (!m_worker)
|
||||
return;
|
||||
|
||||
m_uiTimer->stop();
|
||||
m_worker->deleteLater();
|
||||
m_watchDogThread->quit();
|
||||
const bool didquit = m_watchDogThread->wait(2000);
|
||||
qCDebug(uidelays) << "watch thread quit?" << didquit;
|
||||
delete m_watchDogThread;
|
||||
|
||||
m_watchDogThread = nullptr;
|
||||
m_worker = nullptr;
|
||||
}
|
||||
|
||||
void onUiBeat() { m_worker->reset(); }
|
||||
|
||||
private:
|
||||
QTimer *const m_uiTimer;
|
||||
QThread *m_watchDogThread = nullptr;
|
||||
UiWatchdogWorker *m_worker = nullptr;
|
||||
const UiWatchdogWorker::Options m_options;
|
||||
};
|
||||
|
||||
#endif
|
@@ -4,6 +4,8 @@
|
||||
#include "qtsingleapplication.h"
|
||||
#include "qtlocalpeer.h"
|
||||
|
||||
#include "../../libs/3rdparty/ui_watchdog/uiwatchdog.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileOpenEvent>
|
||||
#include <QLockFile>
|
||||
@@ -26,11 +28,19 @@ static QString instancesLockFilename(const QString &appSessionId)
|
||||
return res + appSessionId + QLatin1String("-instances");
|
||||
}
|
||||
|
||||
static const char s_uiWatchDog[] = "QTC_UI_WATCHDOG";
|
||||
|
||||
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
|
||||
: QApplication(argc, argv),
|
||||
firstPeer(-1),
|
||||
pidPeer(0)
|
||||
{
|
||||
if (qEnvironmentVariableIsSet(s_uiWatchDog)) {
|
||||
auto watchDog = new UiWatchdog(UiWatchdogWorker::OptionNone, this);
|
||||
qDebug() << s_uiWatchDog << "env var is set. The freezes of main thread, above"
|
||||
<< MAX_TIME_BLOCKED << "ms, will be reported.";
|
||||
watchDog->start();
|
||||
}
|
||||
this->appId = appId;
|
||||
|
||||
const QString appSessionId = QtLocalPeer::appSessionId(appId);
|
||||
|
Reference in New Issue
Block a user