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 <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2024-08-20 18:10:17 +02:00
parent 3a67a30dbf
commit 4740ecfecf
4 changed files with 100 additions and 9 deletions

View File

@@ -14,6 +14,7 @@
#include <aggregation/aggregate.h> #include <aggregation/aggregate.h>
#include <utils/algorithm.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/outputformatter.h> #include <utils/outputformatter.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -62,6 +63,9 @@ public:
OutputFormatter formatter; OutputFormatter formatter;
QList<QPair<QString, OutputFormat>> queuedOutput; QList<QPair<QString, OutputFormat>> queuedOutput;
QTimer queueTimer; QTimer queueTimer;
QList<int> queuedSizeHistory;
int formatterCalls = 0;
bool discardExcessiveOutput = false;
bool flushRequested = false; bool flushRequested = false;
bool scrollToBottom = true; bool scrollToBottom = true;
@@ -493,6 +497,11 @@ void OutputWindow::filterNewContent()
void OutputWindow::handleNextOutputChunk() void OutputWindow::handleNextOutputChunk()
{ {
QTC_ASSERT(!d->queuedOutput.isEmpty(), return); QTC_ASSERT(!d->queuedOutput.isEmpty(), return);
discardExcessiveOutput();
if (d->queuedOutput.isEmpty())
return;
auto &chunk = d->queuedOutput.first(); auto &chunk = d->queuedOutput.first();
// We want to break off the chunks along line breaks, if possible. // 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; QElapsedTimer formatterTimer;
formatterTimer.start(); formatterTimer.start();
d->formatter.appendMessage(out, format); d->formatter.appendMessage(out, format);
++d->formatterCalls;
if (formatterTimer.elapsed() > d->queueTimer.interval()) { if (formatterTimer.elapsed() > d->queueTimer.interval()) {
d->queueTimer.setInterval(std::min(maxInterval, d->queueTimer.intervalAsDuration() * 2)); d->queueTimer.setInterval(std::min(maxInterval, d->queueTimer.intervalAsDuration() * 2));
d->chunkSize = std::max(minChunkSize, d->chunkSize / 2); d->chunkSize = std::max(minChunkSize, d->chunkSize / 2);
@@ -570,11 +580,60 @@ void OutputWindow::handleOutputChunk(const QString &output, OutputFormat format)
enableUndoRedo(); 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<QString, OutputFormat> &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() void OutputWindow::updateAutoScroll()
{ {
d->scrollToBottom = verticalScrollBar()->sliderPosition() >= verticalScrollBar()->maximum() - 1; 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<QString, OutputFormat> &c) { return val + c.first.size(); });
}
void OutputWindow::setMaxCharCount(int count) void OutputWindow::setMaxCharCount(int count)
{ {
d->maxCharCount = count; d->maxCharCount = count;
@@ -668,9 +727,7 @@ void OutputWindow::clear()
void OutputWindow::flush() void OutputWindow::flush()
{ {
const int totalQueuedSize = std::accumulate(d->queuedOutput.cbegin(), d->queuedOutput.cend(), 0, if (totalQueuedSize() > 5 * d->chunkSize) {
[](int val, const QPair<QString, OutputFormat> &c) { return val + c.first.size(); });
if (totalQueuedSize > 5 * d->chunkSize) {
d->flushRequested = true; d->flushRequested = true;
return; return;
} }
@@ -684,14 +741,19 @@ void OutputWindow::flush()
void OutputWindow::reset() void OutputWindow::reset()
{ {
flush(); flush();
d->queueTimer.stop();
d->formatter.reset();
d->scrollToBottom = true;
if (!d->queuedOutput.isEmpty()) { 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->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; d->flushRequested = false;
} }
@@ -741,6 +803,11 @@ void OutputWindow::setWordWrapEnabled(bool wrap)
setWordWrapMode(QTextOption::NoWrap); setWordWrapMode(QTextOption::NoWrap);
} }
void OutputWindow::setDiscardExcessiveOutput(bool discard)
{
d->discardExcessiveOutput = discard;
}
#ifdef WITH_TESTS #ifdef WITH_TESTS
// Handles all lines starting with "A" and the following ones up to and including the next // Handles all lines starting with "A" and the following ones up to and including the next

View File

@@ -73,9 +73,11 @@ public:
signals: signals:
void wheelZoom(); void wheelZoom();
void outputDiscarded();
public slots: public slots:
void setWordWrapEnabled(bool wrap); void setWordWrapEnabled(bool wrap);
void setDiscardExcessiveOutput(bool discard);
protected: protected:
virtual void handleLink(const QPoint &pos); virtual void handleLink(const QPoint &pos);
@@ -97,7 +99,10 @@ private:
void filterNewContent(); void filterNewContent();
void handleNextOutputChunk(); void handleNextOutputChunk();
void handleOutputChunk(const QString &output, Utils::OutputFormat format); void handleOutputChunk(const QString &output, Utils::OutputFormat format);
void discardExcessiveOutput();
void discardPendingToolOutput();
void updateAutoScroll(); void updateAutoScroll();
int totalQueuedSize() const;
using TextMatchingFunction = std::function<bool(const QString &text)>; using TextMatchingFunction = std::function<bool(const QString &text)>;
TextMatchingFunction makeMatchingFunction() const; TextMatchingFunction makeMatchingFunction() const;

View File

@@ -6,9 +6,9 @@
#include "buildmanager.h" #include "buildmanager.h"
#include "projectexplorerconstants.h" #include "projectexplorerconstants.h"
#include "projectexplorericons.h" #include "projectexplorericons.h"
#include "projectexplorersettings.h"
#include "projectexplorertr.h" #include "projectexplorertr.h"
#include "showoutputtaskhandler.h" #include "showoutputtaskhandler.h"
#include "taskhub.h"
#include <coreplugin/outputwindow.h> #include <coreplugin/outputwindow.h>
#include <coreplugin/dialogs/ioptionspage.h> #include <coreplugin/dialogs/ioptionspage.h>
@@ -109,14 +109,24 @@ CompileOutputWindow::CompileOutputWindow(QAction *cancelBuildAction) :
CompileOutputSettings &s = compileOutputSettings(); CompileOutputSettings &s = compileOutputSettings();
m_outputWindow->setWordWrapEnabled(s.wrapOutput()); m_outputWindow->setWordWrapEnabled(s.wrapOutput());
m_outputWindow->setDiscardExcessiveOutput(s.discardOutput());
m_outputWindow->setMaxCharCount(s.maxCharCount()); m_outputWindow->setMaxCharCount(s.maxCharCount());
connect(&s.wrapOutput, &Utils::BaseAspect::changed, m_outputWindow, [this] { connect(&s.wrapOutput, &Utils::BaseAspect::changed, m_outputWindow, [this] {
m_outputWindow->setWordWrapEnabled(compileOutputSettings().wrapOutput()); 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] { connect(&s.maxCharCount, &Utils::BaseAspect::changed, m_outputWindow, [this] {
m_outputWindow->setMaxCharCount(compileOutputSettings().maxCharCount()); 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() CompileOutputWindow::~CompileOutputWindow()
@@ -255,6 +265,13 @@ CompileOutputSettings::CompileOutputSettings()
popUp.setSettingsKey("ProjectExplorer/Settings/ShowCompilerOutput"); popUp.setSettingsKey("ProjectExplorer/Settings/ShowCompilerOutput");
popUp.setLabelText(Tr::tr("Open Compile Output when building")); 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.setSettingsKey("ProjectExplorer/Settings/MaxBuildOutputLines");
maxCharCount.setRange(1, Core::Constants::DEFAULT_MAX_CHAR_COUNT); maxCharCount.setRange(1, Core::Constants::DEFAULT_MAX_CHAR_COUNT);
maxCharCount.setDefaultValue(Core::Constants::DEFAULT_MAX_CHAR_COUNT); maxCharCount.setDefaultValue(Core::Constants::DEFAULT_MAX_CHAR_COUNT);
@@ -268,6 +285,7 @@ CompileOutputSettings::CompileOutputSettings()
return Column { return Column {
wrapOutput, wrapOutput,
popUp, popUp,
discardOutput,
Row { parts.at(0), maxCharCount, parts.at(1), st }, Row { parts.at(0), maxCharCount, parts.at(1), st },
st st
}; };

View File

@@ -33,6 +33,7 @@ public:
Utils::BoolAspect popUp{this}; Utils::BoolAspect popUp{this};
Utils::BoolAspect wrapOutput{this}; Utils::BoolAspect wrapOutput{this};
Utils::BoolAspect discardOutput{this};
Utils::IntegerAspect maxCharCount{this}; Utils::IntegerAspect maxCharCount{this};
}; };