From 4740ecfecfadd1be190d7cc8f1e90c580e32248e Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 20 Aug 2024 18:10:17 +0200 Subject: [PATCH] Core: Optionally discard excessive build output If the respective setting is enabled, we detect when incoming output would take an unreasonable amount of time to print and discard it in that case. Task-number: QTCREATORBUG-30135 Change-Id: I00b9646ba88c7cbce9d19d7ce4d305cce7be3de0 Reviewed-by: David Schulz --- src/plugins/coreplugin/outputwindow.cpp | 83 +++++++++++++++++-- src/plugins/coreplugin/outputwindow.h | 5 ++ .../projectexplorer/compileoutputwindow.cpp | 20 ++++- .../projectexplorer/compileoutputwindow.h | 1 + 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/src/plugins/coreplugin/outputwindow.cpp b/src/plugins/coreplugin/outputwindow.cpp index 681b8496814..565e996c483 100644 --- a/src/plugins/coreplugin/outputwindow.cpp +++ b/src/plugins/coreplugin/outputwindow.cpp @@ -14,6 +14,7 @@ #include +#include #include #include #include @@ -62,6 +63,9 @@ public: OutputFormatter formatter; QList> queuedOutput; QTimer queueTimer; + QList queuedSizeHistory; + int formatterCalls = 0; + bool discardExcessiveOutput = false; bool flushRequested = false; bool scrollToBottom = true; @@ -493,6 +497,11 @@ void OutputWindow::filterNewContent() void OutputWindow::handleNextOutputChunk() { QTC_ASSERT(!d->queuedOutput.isEmpty(), return); + + discardExcessiveOutput(); + if (d->queuedOutput.isEmpty()) + return; + auto &chunk = d->queuedOutput.first(); // We want to break off the chunks along line breaks, if possible. @@ -552,6 +561,7 @@ void OutputWindow::handleOutputChunk(const QString &output, OutputFormat format) QElapsedTimer formatterTimer; formatterTimer.start(); d->formatter.appendMessage(out, format); + ++d->formatterCalls; if (formatterTimer.elapsed() > d->queueTimer.interval()) { d->queueTimer.setInterval(std::min(maxInterval, d->queueTimer.intervalAsDuration() * 2)); d->chunkSize = std::max(minChunkSize, d->chunkSize / 2); @@ -570,11 +580,60 @@ void OutputWindow::handleOutputChunk(const QString &output, OutputFormat format) enableUndoRedo(); } +void OutputWindow::discardExcessiveOutput() +{ + // Unless the user instructs us to, we do not mess with the output. + if (!d->discardExcessiveOutput) + return; + + // Criterion 1: Are we being flooded? + // If the pending output has been growing for the last ten times the output formatter + // was invoked and it is considerably larger than the chunk size, we discard it. + const int queuedSize = totalQueuedSize(); + if (!d->queuedSizeHistory.isEmpty() && d->queuedSizeHistory.last() > queuedSize) + d->queuedSizeHistory.clear(); + d->queuedSizeHistory << queuedSize; + bool discard = d->queuedSizeHistory.size() > int(10) && queuedSize > 5 * d->chunkSize; + + // Criterion 2: Are we too slow? + // If it would take longer than a minute to print the pending output and we have + // already presented a reasonable amount of output to the user, we discard it. + if (!discard) { + discard = d->formatterCalls >= 10 + && (queuedSize / d->chunkSize) * d->queueTimer.intervalAsDuration() > 60s; + } + + if (discard) { + discardPendingToolOutput(); + d->queuedSizeHistory.clear(); + return; + } +} + +void OutputWindow::discardPendingToolOutput() +{ + Utils::erase(d->queuedOutput, [](const std::pair &chunk) { + return chunk.second != NormalMessageFormat && chunk.second != ErrorMessageFormat; + }); + d->formatter.appendMessage(Tr::tr("[Discarding excessive amount of pending output.]\n"), + ErrorMessageFormat); + emit outputDiscarded(); +} + void OutputWindow::updateAutoScroll() { d->scrollToBottom = verticalScrollBar()->sliderPosition() >= verticalScrollBar()->maximum() - 1; } +int OutputWindow::totalQueuedSize() const +{ + return std::accumulate( + d->queuedOutput.cbegin(), + d->queuedOutput.cend(), + 0, + [](int val, const QPair &c) { return val + c.first.size(); }); +} + void OutputWindow::setMaxCharCount(int count) { d->maxCharCount = count; @@ -668,9 +727,7 @@ void OutputWindow::clear() void OutputWindow::flush() { - const int totalQueuedSize = std::accumulate(d->queuedOutput.cbegin(), d->queuedOutput.cend(), 0, - [](int val, const QPair &c) { return val + c.first.size(); }); - if (totalQueuedSize > 5 * d->chunkSize) { + if (totalQueuedSize() > 5 * d->chunkSize) { d->flushRequested = true; return; } @@ -684,14 +741,19 @@ void OutputWindow::flush() void OutputWindow::reset() { flush(); - d->queueTimer.stop(); - d->formatter.reset(); - d->scrollToBottom = true; if (!d->queuedOutput.isEmpty()) { + discardPendingToolOutput(); + flush(); + + // For the unlikely case that we ourselves have sent excessive amount of output + // via NormalMessageFormat or ErrorMessageFormat. d->queuedOutput.clear(); - d->formatter.appendMessage(Tr::tr("[Discarding excessive amount of pending output.]\n"), - ErrorMessageFormat); } + d->queueTimer.stop(); + d->queuedSizeHistory.clear(); + d->formatter.reset(); + d->formatterCalls = 0; + d->scrollToBottom = true; d->flushRequested = false; } @@ -741,6 +803,11 @@ void OutputWindow::setWordWrapEnabled(bool wrap) setWordWrapMode(QTextOption::NoWrap); } +void OutputWindow::setDiscardExcessiveOutput(bool discard) +{ + d->discardExcessiveOutput = discard; +} + #ifdef WITH_TESTS // Handles all lines starting with "A" and the following ones up to and including the next diff --git a/src/plugins/coreplugin/outputwindow.h b/src/plugins/coreplugin/outputwindow.h index b6fc49001f3..dc5bde19f8f 100644 --- a/src/plugins/coreplugin/outputwindow.h +++ b/src/plugins/coreplugin/outputwindow.h @@ -73,9 +73,11 @@ public: signals: void wheelZoom(); + void outputDiscarded(); public slots: void setWordWrapEnabled(bool wrap); + void setDiscardExcessiveOutput(bool discard); protected: virtual void handleLink(const QPoint &pos); @@ -97,7 +99,10 @@ private: void filterNewContent(); void handleNextOutputChunk(); void handleOutputChunk(const QString &output, Utils::OutputFormat format); + void discardExcessiveOutput(); + void discardPendingToolOutput(); void updateAutoScroll(); + int totalQueuedSize() const; using TextMatchingFunction = std::function; TextMatchingFunction makeMatchingFunction() const; diff --git a/src/plugins/projectexplorer/compileoutputwindow.cpp b/src/plugins/projectexplorer/compileoutputwindow.cpp index eae2e2bc805..f3ea5854fa0 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.cpp +++ b/src/plugins/projectexplorer/compileoutputwindow.cpp @@ -6,9 +6,9 @@ #include "buildmanager.h" #include "projectexplorerconstants.h" #include "projectexplorericons.h" -#include "projectexplorersettings.h" #include "projectexplorertr.h" #include "showoutputtaskhandler.h" +#include "taskhub.h" #include #include @@ -109,14 +109,24 @@ CompileOutputWindow::CompileOutputWindow(QAction *cancelBuildAction) : CompileOutputSettings &s = compileOutputSettings(); m_outputWindow->setWordWrapEnabled(s.wrapOutput()); + m_outputWindow->setDiscardExcessiveOutput(s.discardOutput()); m_outputWindow->setMaxCharCount(s.maxCharCount()); connect(&s.wrapOutput, &Utils::BaseAspect::changed, m_outputWindow, [this] { m_outputWindow->setWordWrapEnabled(compileOutputSettings().wrapOutput()); }); + connect(&s.discardOutput, &Utils::BaseAspect::changed, m_outputWindow, [this] { + m_outputWindow->setDiscardExcessiveOutput(compileOutputSettings().discardOutput()); + }); connect(&s.maxCharCount, &Utils::BaseAspect::changed, m_outputWindow, [this] { m_outputWindow->setMaxCharCount(compileOutputSettings().maxCharCount()); }); + connect(m_outputWindow, &Core::OutputWindow::outputDiscarded, this, [] { + TaskHub::addTask( + Task::Warning, + Tr::tr("Discarded excessive compile output."), + Constants::TASK_CATEGORY_COMPILE); + }); } CompileOutputWindow::~CompileOutputWindow() @@ -255,6 +265,13 @@ CompileOutputSettings::CompileOutputSettings() popUp.setSettingsKey("ProjectExplorer/Settings/ShowCompilerOutput"); popUp.setLabelText(Tr::tr("Open Compile Output when building")); + discardOutput.setSettingsKey("ProjectExplorer/Settings/DiscardCompilerOutput"); + discardOutput.setLabelText(Tr::tr("Discard Excessive Output")); + discardOutput.setToolTip( + Tr::tr( + "If this option is enabled, compile output will be discarded if it continuously comes " + "in faster than it can be handled.")); + maxCharCount.setSettingsKey("ProjectExplorer/Settings/MaxBuildOutputLines"); maxCharCount.setRange(1, Core::Constants::DEFAULT_MAX_CHAR_COUNT); maxCharCount.setDefaultValue(Core::Constants::DEFAULT_MAX_CHAR_COUNT); @@ -268,6 +285,7 @@ CompileOutputSettings::CompileOutputSettings() return Column { wrapOutput, popUp, + discardOutput, Row { parts.at(0), maxCharCount, parts.at(1), st }, st }; diff --git a/src/plugins/projectexplorer/compileoutputwindow.h b/src/plugins/projectexplorer/compileoutputwindow.h index cab706c9f52..d732cd6d029 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.h +++ b/src/plugins/projectexplorer/compileoutputwindow.h @@ -33,6 +33,7 @@ public: Utils::BoolAspect popUp{this}; Utils::BoolAspect wrapOutput{this}; + Utils::BoolAspect discardOutput{this}; Utils::IntegerAspect maxCharCount{this}; };