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