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}; };