diff --git a/.selfcheck_suppressions b/.selfcheck_suppressions index 764b8818308..cdac1ba33ab 100644 --- a/.selfcheck_suppressions +++ b/.selfcheck_suppressions @@ -12,7 +12,7 @@ funcArgNamesDifferent:*/moc_resultsview.cpp funcArgNamesDifferent:*/moc_threadhandler.cpp funcArgNamesDifferent:*/moc_threadresult.cpp naming-varname:*/gui/ui_*.h -functionStatic:*/ui_fileview.h +functionStatic:*/gui/ui_*.h # --debug-warnings suppressions valueFlowBailout diff --git a/gui/checkthread.cpp b/gui/checkthread.cpp index e10bf1ddf5a..ab20602a09b 100644 --- a/gui/checkthread.cpp +++ b/gui/checkthread.cpp @@ -105,8 +105,9 @@ int CheckThread::executeCommand(std::string exe, std::vector args, } -CheckThread::CheckThread(ThreadResult &result) : - mResult(result) +CheckThread::CheckThread(ThreadResult &result, int threadIndex) + : mResult(result) + , mThreadIndex(threadIndex) {} void CheckThread::setSettings(const Settings &settings, std::shared_ptr supprs) @@ -147,9 +148,14 @@ void CheckThread::run() while (file && mState == Running) { const std::string& fname = file->spath(); qDebug() << "Checking file" << QString::fromStdString(fname); + + const Details details{ mThreadIndex, QString::fromStdString(fname), QTime::currentTime(), }; + emit startCheck(details); + cppcheck.check(*file); runAddonsAndTools(mSettings, nullptr, QString::fromStdString(fname)); - emit fileChecked(QString::fromStdString(fname)); + + emit finishCheck(details); if (mState == Running) mResult.getNextFile(file); @@ -160,9 +166,15 @@ void CheckThread::run() while (fileSettings && mState == Running) { const std::string& fname = fileSettings->filename(); qDebug() << "Checking file" << QString::fromStdString(fname); - cppcheck.check(*fileSettings); + + const Details details{ mThreadIndex, QString::fromStdString(fname), QTime::currentTime(), }; + emit startCheck(details); + + cppcheck.check(*file); runAddonsAndTools(mSettings, fileSettings, QString::fromStdString(fname)); - emit fileChecked(QString::fromStdString(fname)); + + emit finishCheck(details); + if (mState == Running) mResult.getNextFileSettings(fileSettings); @@ -486,3 +498,4 @@ QString CheckThread::clangTidyCmd() return QString(); } + diff --git a/gui/checkthread.h b/gui/checkthread.h index 4247a45094c..e78f1580440 100644 --- a/gui/checkthread.h +++ b/gui/checkthread.h @@ -36,6 +36,7 @@ #include #include #include +#include class ThreadResult; @@ -49,7 +50,14 @@ class ThreadResult; class CheckThread : public QThread { Q_OBJECT public: - explicit CheckThread(ThreadResult &result); + struct Details { + int threadIndex; + QString file; + QTime startTime; + }; + +public: + CheckThread(ThreadResult &result, int threadIndex); /** * @brief Set settings for cppcheck @@ -102,8 +110,8 @@ class CheckThread : public QThread { */ void done(); - // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name) - caused by generated MOC code - void fileChecked(const QString &file); + void startCheck(CheckThread::Details details); + void finishCheck(CheckThread::Details details); protected: /** @@ -126,6 +134,7 @@ class CheckThread : public QThread { std::atomic mState{Ready}; ThreadResult &mResult; + int mThreadIndex{}; Settings mSettings; std::shared_ptr mSuppressions; @@ -150,5 +159,6 @@ class CheckThread : public QThread { QStringList mClangIncludePaths; QList mSuppressionsUi; }; +Q_DECLARE_METATYPE(CheckThread::Details); /// @} #endif // CHECKTHREAD_H diff --git a/gui/gui.pro b/gui/gui.pro index 132f113d270..8706e86c86e 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -70,7 +70,8 @@ FORMS = about.ui \ librarydialog.ui \ libraryaddfunctiondialog.ui \ libraryeditargdialog.ui \ - newsuppressiondialog.ui + newsuppressiondialog.ui \ + threaddetails.ui TRANSLATIONS = cppcheck_de.ts \ cppcheck_es.ts \ @@ -156,6 +157,7 @@ HEADERS += aboutdialog.h \ settingsdialog.h \ showtypes.h \ statsdialog.h \ + threaddetails.h \ threadhandler.h \ threadresult.h \ translationhandler.h \ @@ -199,6 +201,7 @@ SOURCES += aboutdialog.cpp \ settingsdialog.cpp \ showtypes.cpp \ statsdialog.cpp \ + threaddetails.cpp \ threadhandler.cpp \ threadresult.cpp \ translationhandler.cpp \ diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 6d0a1a8d5ce..c0d42a32d0d 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -53,6 +53,7 @@ #include "threadresult.h" #include "translationhandler.h" #include "utils.h" +#include "threaddetails.h" #include "ui_mainwindow.h" @@ -183,6 +184,7 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : connect(mUI->mActionShowHidden, &QAction::triggered, mUI->mResults, &ResultsView::showHiddenResults); connect(mUI->mActionViewStats, &QAction::triggered, this, &MainWindow::showStatistics); connect(mUI->mActionLibraryEditor, &QAction::triggered, this, &MainWindow::showLibraryEditor); + connect(mUI->mActionShowThreadDetails, &QAction::triggered, this, &MainWindow::showThreadDetails); connect(mUI->mActionReanalyzeModified, &QAction::triggered, this, &MainWindow::reAnalyzeModified); connect(mUI->mActionReanalyzeAll, &QAction::triggered, this, &MainWindow::reAnalyzeAll); @@ -1069,6 +1071,7 @@ bool MainWindow::getCppcheckSettings(Settings& settings, Suppressions& supprs) settings.exename = QCoreApplication::applicationFilePath().toStdString(); settings.templateFormat = "{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]"; + settings.reportProgress = 10; // default to --check-level=normal for GUI for now settings.setCheckLevel(Settings::CheckLevel::normal); @@ -2109,6 +2112,20 @@ void MainWindow::showLibraryEditor() libraryDialog.exec(); } +void MainWindow::showThreadDetails() +{ + if (ThreadDetails::instance()) + return; + auto* threadDetails = new ThreadDetails(this); + connect(mThread, &ThreadHandler::threadDetailsUpdated, + threadDetails, &ThreadDetails::threadDetailsUpdated, Qt::QueuedConnection); + connect(mThread, &ThreadHandler::progress, + threadDetails, &ThreadDetails::progress, Qt::QueuedConnection); + threadDetails->setAttribute(Qt::WA_DeleteOnClose); + threadDetails->show(); + mThread->emitThreadDetailsUpdated(); +} + void MainWindow::filterResults() { mUI->mResults->filterResults(mLineEditFilter->text()); diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 76fda4f0de6..e29d4d48f5b 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -200,6 +200,9 @@ public slots: /** @brief Slot for showing the library editor */ void showLibraryEditor(); + /** @brief Slot for showing the thread details window */ + void showThreadDetails(); + private slots: /** @brief Slot for checkthread's done signal */ diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index a90949344e2..551874d3225 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -64,7 +64,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint @@ -84,13 +84,13 @@ Checking for updates - Qt::RichText + Qt::TextFormat::RichText true - Qt::TextBrowserInteraction + Qt::TextInteractionFlag::TextBrowserInteraction @@ -104,7 +104,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -124,7 +124,7 @@ 0 0 640 - 22 + 21 @@ -192,6 +192,7 @@ + @@ -1040,6 +1041,14 @@ EULA... + + + Thread Details + + + Show thread details + + diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index 8336a697878..b8ff3d8c598 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -162,7 +162,7 @@ void ResultsView::setResultsSource(ResultsTree::ResultsSource source) mUI->mTree->setResultsSource(source); } -void ResultsView::progress(int value, const QString& description) +void ResultsView::filesCheckedProgress(int value, const QString& description) { mUI->mProgress->setValue(value); mUI->mProgress->setFormat(QString("%p% (%1)").arg(description)); diff --git a/gui/resultsview.h b/gui/resultsview.h index 770f9c0c3fa..500d40df981 100644 --- a/gui/resultsview.h +++ b/gui/resultsview.h @@ -307,7 +307,7 @@ public slots: * @param value Current progress value * @param description Description to accompany the progress */ - void progress(int value, const QString& description); + void filesCheckedProgress(int value, const QString& description); /** * @brief Slot for new error to be displayed diff --git a/gui/test/resultstree/testresultstree.cpp b/gui/test/resultstree/testresultstree.cpp index 732ee0715d0..afda38e0075 100644 --- a/gui/test/resultstree/testresultstree.cpp +++ b/gui/test/resultstree/testresultstree.cpp @@ -93,6 +93,14 @@ void ThreadHandler::stop() { void ThreadHandler::threadDone() { throw 1; } +// NOLINTBEGIN(performance-unnecessary-value-param) +void ThreadHandler::startCheck(CheckThread::Details /*unused*/) { + throw 1; +} +void ThreadHandler::finishCheck(CheckThread::Details /*unused*/) { + throw 1; +} +// NOLINTEND(performance-unnecessary-value-param) Application& ApplicationList::getApplication(const int /*unused*/) { throw 1; } @@ -106,7 +114,8 @@ QString XmlReport::unquoteMessage(const QString &message) { return message; } XmlReport::XmlReport(const QString& filename) : Report(filename) {} -void ThreadResult::fileChecked(const QString & /*unused*/) { +// NOLINTNEXTLINE(performance-unnecessary-value-param) +void ThreadResult::finishCheck(CheckThread::Details /*unused*/) { throw 1; } void ThreadResult::reportOut(const std::string & /*unused*/, Color /*unused*/) { @@ -115,6 +124,9 @@ void ThreadResult::reportOut(const std::string & /*unused*/, Color /*unused*/) { void ThreadResult::reportErr(const ErrorMessage & /*unused*/) { throw 1; } +void ThreadResult::reportProgress(const std::string &/*filename*/, const char /*stage*/[], const std::size_t /*value*/) { + throw 1; +} // Test... diff --git a/gui/threaddetails.cpp b/gui/threaddetails.cpp new file mode 100644 index 00000000000..9036c18305d --- /dev/null +++ b/gui/threaddetails.cpp @@ -0,0 +1,54 @@ +#include "threaddetails.h" +#include "ui_threaddetails.h" + +#include + +ThreadDetails* ThreadDetails::mInstance; + +ThreadDetails::ThreadDetails(QWidget *parent) + : QWidget(parent) + , mUi(new Ui::ThreadDetails) +{ + mInstance = this; + setWindowFlags(Qt::Window); + mUi->setupUi(this); + connect(&mTimer, &QTimer::timeout, this, &ThreadDetails::updateUI); + mTimer.start(1000); +} + +ThreadDetails::~ThreadDetails() +{ + mInstance = nullptr; + delete mUi; +} + +// NOLINTNEXTLINE(performance-unnecessary-value-param) - false positive +void ThreadDetails::threadDetailsUpdated(QMap threadDetails) +{ + QMutexLocker locker(&mMutex); + mThreadDetails = threadDetails; + if (threadDetails.empty()) { + mProgress.clear(); + } +} + +// cppcheck-suppress passedByValue +// NOLINTNEXTLINE(performance-unnecessary-value-param) - false positive +void ThreadDetails::progress(QString filename, QString stage, std::size_t value) { + QMutexLocker locker(&mMutex); + mProgress[filename] = {QString(), stage + QString(value > 0 ? ": %1%" : "").arg(value)}; +} + +void ThreadDetails::updateUI() { + QString text("Thread\tStart time\tFile/Progress\n"); + { + QMutexLocker locker(&mMutex); + for (const auto& td: mThreadDetails) { + auto& timeProgress = mProgress[td.file]; + if (timeProgress.first.isEmpty() && !timeProgress.second.isEmpty()) + timeProgress.first = QTime::currentTime().toString(Qt::TextDate); + text += QString("%1\t%2\t%3\n\t%4\t%5\n").arg(td.threadIndex).arg(td.startTime.toString(Qt::TextDate)).arg(td.file).arg(timeProgress.first).arg(timeProgress.second); + } + } + mUi->plainTextEdit->setPlainText(text); +} diff --git a/gui/threaddetails.h b/gui/threaddetails.h new file mode 100644 index 00000000000..253b53d5407 --- /dev/null +++ b/gui/threaddetails.h @@ -0,0 +1,44 @@ +#ifndef THREADDETAILS_H +#define THREADDETAILS_H + +#include +#include +#include +#include +#include "checkthread.h" + +namespace Ui { + class ThreadDetails; +} + +class ThreadDetails : public QWidget +{ + Q_OBJECT + +public: + explicit ThreadDetails(QWidget *parent = nullptr); + ~ThreadDetails() override; + + static ThreadDetails* instance() { + return mInstance; + } + +public slots: + void threadDetailsUpdated(QMap threadDetails); + void progress(QString filename, QString stage, std::size_t value); + +private slots: + void updateUI(); + +private: + static ThreadDetails* mInstance; + Ui::ThreadDetails *mUi; + QMap> mProgress; + QMap mThreadDetails; + QTimer mTimer; + + /** accessing mProgress and mThreadDetails in slots that are triggered from different threads */ + QMutex mMutex; +}; + +#endif // THREADDETAILS_H diff --git a/gui/threaddetails.ui b/gui/threaddetails.ui new file mode 100644 index 00000000000..420340cf77d --- /dev/null +++ b/gui/threaddetails.ui @@ -0,0 +1,28 @@ + + + ThreadDetails + + + + 0 + 0 + 640 + 300 + + + + Thread Details + + + + + + true + + + + + + + + diff --git a/gui/threadhandler.cpp b/gui/threadhandler.cpp index ad4d0e2a61d..55f60de673d 100644 --- a/gui/threadhandler.cpp +++ b/gui/threadhandler.cpp @@ -146,11 +146,15 @@ void ThreadHandler::createThreads(const int count) removeThreads(); //Create new threads for (int i = mThreads.size(); i < count; i++) { - mThreads << new CheckThread(mResults); + mThreads << new CheckThread(mResults, i + 1); connect(mThreads.last(), &CheckThread::done, this, &ThreadHandler::threadDone, Qt::QueuedConnection); - connect(mThreads.last(), &CheckThread::fileChecked, - &mResults, &ThreadResult::fileChecked, Qt::QueuedConnection); + connect(mThreads.last(), &CheckThread::finishCheck, + &mResults, &ThreadResult::finishCheck, Qt::QueuedConnection); + connect(mThreads.last(), &CheckThread::startCheck, + this, &ThreadHandler::startCheck, Qt::QueuedConnection); + connect(mThreads.last(), &CheckThread::finishCheck, + this, &ThreadHandler::finishCheck, Qt::QueuedConnection); } } @@ -164,8 +168,12 @@ void ThreadHandler::removeThreads() } disconnect(thread, &CheckThread::done, this, &ThreadHandler::threadDone); - disconnect(thread, &CheckThread::fileChecked, - &mResults, &ThreadResult::fileChecked); + disconnect(thread, &CheckThread::finishCheck, + &mResults, &ThreadResult::finishCheck); + disconnect(mThreads.last(), &CheckThread::startCheck, + this, &ThreadHandler::startCheck); + disconnect(mThreads.last(), &CheckThread::finishCheck, + this, &ThreadHandler::finishCheck); delete thread; } @@ -215,8 +223,8 @@ void ThreadHandler::stop() void ThreadHandler::initialize(const ResultsView *view) { - connect(&mResults, &ThreadResult::progress, - view, &ResultsView::progress); + connect(&mResults, &ThreadResult::filesCheckedProgress, + view, &ResultsView::filesCheckedProgress); connect(&mResults, &ThreadResult::error, view, &ResultsView::error); @@ -226,6 +234,9 @@ void ThreadHandler::initialize(const ResultsView *view) connect(&mResults, &ThreadResult::debugError, this, &ThreadHandler::debugError); + + connect(&mResults, &ThreadResult::progress, + this, &ThreadHandler::progress); } void ThreadHandler::loadSettings(const QSettings &settings) @@ -317,3 +328,24 @@ void ThreadHandler::setCheckStartTime(QDateTime checkStartTime) { mCheckStartTime = std::move(checkStartTime); } + +// cppcheck-suppress passedByValueCallback +// NOLINTNEXTLINE(performance-unnecessary-value-param) +void ThreadHandler::startCheck(CheckThread::Details details) +{ + mThreadDetails[details.threadIndex] = details; + emitThreadDetailsUpdated(); +} + +// cppcheck-suppress passedByValueCallback +// NOLINTNEXTLINE(performance-unnecessary-value-param) +void ThreadHandler::finishCheck(CheckThread::Details details) +{ + mThreadDetails.remove(details.threadIndex); + emitThreadDetailsUpdated(); +} + +void ThreadHandler::emitThreadDetailsUpdated() +{ + emit threadDetailsUpdated(mThreadDetails); +} diff --git a/gui/threadhandler.h b/gui/threadhandler.h index cc578bd5643..9f3d01f17e6 100644 --- a/gui/threadhandler.h +++ b/gui/threadhandler.h @@ -178,6 +178,11 @@ class ThreadHandler : public QObject { */ void setCheckStartTime(QDateTime checkStartTime); + /** + * @brief Emit the threadDetailsUpdated signal + */ + void emitThreadDetailsUpdated(); + signals: /** * @brief Signal that all threads are done @@ -191,6 +196,11 @@ class ThreadHandler : public QObject { // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name) - caused by generated MOC code void debugError(const ErrorItem &item); + /** + * @brief Emitted when thread details are updated. + */ + void threadDetailsUpdated(QMap threadDetails); + public slots: /** @@ -198,6 +208,22 @@ public slots: * */ void stop(); + + /** + * @brief Slot threads use to signal this class that it started checking a file + * @param details Details about what file is being checked and by what thread + */ + void startCheck(CheckThread::Details details); + + /** + * @brief Slot threads use to signal this class that it finish checking a file + * @param details Details about what file finished being checked and by what thread + */ + void finishCheck(CheckThread::Details details); + +signals: + void progress(QString filename, QString stage, std::size_t value); + protected slots: /** * @brief Slot that a single thread is done @@ -285,6 +311,12 @@ protected slots: Settings mCheckSettings; std::shared_ptr mCheckSuppressions; /// @} + + /** + * @brief Details about currently running threads + */ + QMap mThreadDetails; + private: /** diff --git a/gui/threadresult.cpp b/gui/threadresult.cpp index ea858c92e4a..f3a7e67ab16 100644 --- a/gui/threadresult.cpp +++ b/gui/threadresult.cpp @@ -34,18 +34,19 @@ void ThreadResult::reportOut(const std::string &outmsg, Color /*c*/) emit log(QString::fromStdString(outmsg)); } -void ThreadResult::fileChecked(const QString &file) +// NOLINTNEXTLINE(performance-unnecessary-value-param) +void ThreadResult::finishCheck(CheckThread::Details details) { std::lock_guard locker(mutex); - mProgress += QFile(file).size(); + mCheckedFileSize += QFile(details.file).size(); mFilesChecked++; - if (mMaxProgress > 0) { - const int value = static_cast(PROGRESS_MAX * mProgress / mMaxProgress); + if (mTotalFileSize > 0) { + const int value = static_cast(PROGRESS_MAX * mCheckedFileSize / mTotalFileSize); const QString description = tr("%1 of %2 files checked").arg(mFilesChecked).arg(mTotalFiles); - emit progress(value, description); + emit filesCheckedProgress(value, description); } } @@ -59,6 +60,11 @@ void ThreadResult::reportErr(const ErrorMessage &msg) emit debugError(item); } +// NOLINTNEXTLINE(readability-avoid-const-params-in-decls) - false positive this is an overload +void ThreadResult::reportProgress(const std::string &filename, const char stage[], const std::size_t value) { + emit progress(QString::fromStdString(filename), stage, value); +} + void ThreadResult::getNextFile(const FileWithDetails*& file) { std::lock_guard locker(mutex); @@ -87,7 +93,7 @@ void ThreadResult::setFiles(std::list files) mTotalFiles = files.size(); mFiles = std::move(files); mItNextFile = mFiles.cbegin(); - mProgress = 0; + mCheckedFileSize = 0; mFilesChecked = 0; // Determine the total size of all of the files to check, so that we can @@ -95,7 +101,7 @@ void ThreadResult::setFiles(std::list files) quint64 sizeOfFiles = std::accumulate(mFiles.cbegin(), mFiles.cend(), 0, [](quint64 total, const FileWithDetails& file) { return total + file.size(); }); - mMaxProgress = sizeOfFiles; + mTotalFileSize = sizeOfFiles; } void ThreadResult::setProject(const ImportProject &prj) @@ -105,13 +111,13 @@ void ThreadResult::setProject(const ImportProject &prj) mItNextFile = mFiles.cbegin(); mFileSettings = prj.fileSettings; mItNextFileSettings = mFileSettings.cbegin(); - mProgress = 0; + mCheckedFileSize = 0; mFilesChecked = 0; mTotalFiles = prj.fileSettings.size(); // Determine the total size of all of the files to check, so that we can // show an accurate progress estimate - mMaxProgress = std::accumulate(prj.fileSettings.begin(), prj.fileSettings.end(), quint64{ 0 }, [](quint64 v, const FileSettings& fs) { + mTotalFileSize = std::accumulate(prj.fileSettings.begin(), prj.fileSettings.end(), quint64{ 0 }, [](quint64 v, const FileSettings& fs) { return v + QFile(QString::fromStdString(fs.filename())).size(); }); } diff --git a/gui/threadresult.h b/gui/threadresult.h index dc7b5c0372a..b7bece3e28c 100644 --- a/gui/threadresult.h +++ b/gui/threadresult.h @@ -23,6 +23,7 @@ #include "color.h" #include "errorlogger.h" #include "filesettings.h" +#include "checkthread.h" #include #include @@ -83,22 +84,25 @@ class ThreadResult : public QObject, public ErrorLogger { { (void) metric; } + // NOLINTNEXTLINE(readability-avoid-const-params-in-decls) - false positive this is an overload + void reportProgress(const std::string &filename, const char stage[], const std::size_t value) final; public slots: /** - * @brief Slot threads use to signal this class that a specific file is checked - * @param file File that is checked + * @brief Slot threads use to signal this class that it finish checking a file + * @param details Details about what file finished being checked and by what thread */ - void fileChecked(const QString &file); + void finishCheck(CheckThread::Details details); + signals: /** - * @brief Progress signal - * @param value Current progress - * @param description Description of the current stage + * @brief Files checked progress + * @param value Current progress (0 - PROGRESS_MAX) + * @param description Description of the current stage (example: "13/45 files checked") */ // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name) - caused by generated MOC code - void progress(int value, const QString& description); + void filesCheckedProgress(int value, const QString& description); /** * @brief Signal of a new error @@ -124,6 +128,8 @@ public slots: // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name) - caused by generated MOC code void debugError(const ErrorItem &item); + void progress(QString filename, QString stage, std::size_t value); + protected: /** @@ -142,17 +148,11 @@ public slots: std::list mFileSettings; std::list::const_iterator mItNextFileSettings{mFileSettings.cbegin()}; - /** - * @brief Max progress - * - */ - quint64 mMaxProgress{}; + /** @brief Total file size */ + quint64 mTotalFileSize{}; - /** - * @brief Current progress - * - */ - quint64 mProgress{}; + /** @brief File size of checked files */ + quint64 mCheckedFileSize{}; /** * @brief Current number of files checked