File indexing completed on 2024-05-12 17:11:44
0001 /**************************************************************************** 0002 ** MIT License 0003 ** 0004 ** Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com 0005 ** Author: Sérgio Martins <sergio.martins@kdab.com> 0006 ** 0007 ** This file is part of KDToolBox (https://github.com/KDAB/KDToolBox). 0008 ** 0009 ** Permission is hereby granted, free of charge, to any person obtaining a copy 0010 ** of this software and associated documentation files (the "Software"), to deal 0011 ** in the Software without restriction, including without limitation the rights 0012 ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 0013 ** copies of the Software, ** and to permit persons to whom the Software is 0014 ** furnished to do so, subject to the following conditions: 0015 ** 0016 ** The above copyright notice and this permission notice (including the next paragraph) 0017 ** shall be included in all copies or substantial portions of the Software. 0018 ** 0019 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 0020 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 0021 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 0022 ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 0023 ** LIABILITY, WHETHER IN AN ACTION OF ** CONTRACT, TORT OR OTHERWISE, 0024 ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 0025 ** DEALINGS IN THE SOFTWARE. 0026 ****************************************************************************/ 0027 0028 #ifndef UIWATCHDOG_H 0029 #define UIWATCHDOG_H 0030 0031 #include <QThread> 0032 #include <QTimer> 0033 #include <QMutex> 0034 #include <QElapsedTimer> 0035 #include <QLoggingCategory> 0036 #include <QDebug> 0037 0038 #ifdef Q_OS_WIN 0039 # include <Windows.h> 0040 #endif 0041 0042 #define MAX_TIME_BLOCKED 300 // ms 0043 0044 Q_DECLARE_LOGGING_CATEGORY(uidelays) 0045 Q_LOGGING_CATEGORY(uidelays, "uidelays") 0046 0047 class UiWatchdog; 0048 class UiWatchdogWorker : public QObject 0049 { 0050 public: 0051 enum Option { 0052 OptionNone = 0, 0053 OptionDebugBreak = 1 0054 }; 0055 typedef int Options; 0056 0057 private: 0058 UiWatchdogWorker(Options options) 0059 : QObject() 0060 , m_watchTimer(new QTimer(this)) 0061 , m_options(options) 0062 { 0063 qCDebug(uidelays) << "UiWatchdogWorker created"; 0064 connect(m_watchTimer, &QTimer::timeout, this, &UiWatchdogWorker::checkUI); 0065 } 0066 0067 ~UiWatchdogWorker() 0068 { 0069 qCDebug(uidelays) << "UiWatchdogWorker destroyed"; 0070 stop(); 0071 } 0072 0073 void start(int frequency_msecs = 200) 0074 { 0075 m_watchTimer->start(frequency_msecs); 0076 m_elapsedTimeSinceLastBeat.start(); 0077 } 0078 0079 void stop() 0080 { 0081 m_watchTimer->stop(); 0082 } 0083 0084 void checkUI() 0085 { 0086 qint64 elapsed; 0087 0088 { 0089 QMutexLocker l(&m_mutex); 0090 elapsed = m_elapsedTimeSinceLastBeat.elapsed(); 0091 } 0092 0093 if (elapsed > MAX_TIME_BLOCKED) { 0094 qDebug() << "UI is blocked !" << elapsed; // Add custom action here! 0095 if ((m_options & OptionDebugBreak)) 0096 debugBreak(); 0097 } 0098 } 0099 0100 void debugBreak() 0101 { 0102 #ifdef Q_OS_WIN 0103 DebugBreak(); 0104 #endif 0105 } 0106 0107 void reset() 0108 { 0109 QMutexLocker l(&m_mutex); 0110 m_elapsedTimeSinceLastBeat.restart(); 0111 } 0112 0113 QTimer *const m_watchTimer; 0114 QElapsedTimer m_elapsedTimeSinceLastBeat; 0115 QMutex m_mutex; 0116 const Options m_options; 0117 friend class UiWatchdog; 0118 }; 0119 0120 class UiWatchdog : public QObject 0121 { 0122 public: 0123 0124 explicit UiWatchdog(UiWatchdogWorker::Options options = UiWatchdogWorker::OptionNone, QObject *parent = nullptr) 0125 : QObject(parent) 0126 , m_uiTimer(new QTimer(this)) 0127 , m_options(options) 0128 { 0129 QLoggingCategory::setFilterRules("uidelays.debug=false"); 0130 qCDebug(uidelays) << "UiWatchdog created"; 0131 connect(m_uiTimer, &QTimer::timeout, this, &UiWatchdog::onUiBeat); 0132 } 0133 0134 ~UiWatchdog() 0135 { 0136 stop(); 0137 qCDebug(uidelays) << "UiWatchdog destroyed"; 0138 } 0139 0140 void start(int frequency_msecs = 100) 0141 { 0142 if (m_worker) 0143 return; 0144 0145 m_uiTimer->start(frequency_msecs); 0146 0147 m_worker = new UiWatchdogWorker(m_options); 0148 m_watchDogThread = new QThread(this); 0149 m_worker->moveToThread(m_watchDogThread); 0150 m_watchDogThread->start(); 0151 connect(m_watchDogThread, &QThread::started, m_worker, [this, frequency_msecs] { 0152 m_worker->start(frequency_msecs); 0153 }); 0154 } 0155 0156 void stop() 0157 { 0158 if (!m_worker) 0159 return; 0160 0161 m_uiTimer->stop(); 0162 m_worker->deleteLater(); 0163 m_watchDogThread->quit(); 0164 const bool didquit = m_watchDogThread->wait(2000); 0165 qCDebug(uidelays) << "watch thread quit?" << didquit; 0166 delete m_watchDogThread; 0167 0168 m_watchDogThread = nullptr; 0169 m_worker = nullptr; 0170 } 0171 0172 void onUiBeat() 0173 { 0174 m_worker->reset(); 0175 } 0176 0177 private: 0178 QTimer *const m_uiTimer; 0179 QThread *m_watchDogThread = nullptr; 0180 UiWatchdogWorker *m_worker = nullptr; 0181 const UiWatchdogWorker::Options m_options; 0182 }; 0183 0184 #endif