forked from qt-creator/qt-creator
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:
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <aggregation/aggregate.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/outputformatter.h>
|
||||
#include <utils/qtcassert.h>
|
||||
@@ -62,6 +63,9 @@ public:
|
||||
OutputFormatter formatter;
|
||||
QList<QPair<QString, OutputFormat>> queuedOutput;
|
||||
QTimer queueTimer;
|
||||
QList<int> 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<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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
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<QString, OutputFormat> &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
|
||||
|
@@ -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<bool(const QString &text)>;
|
||||
TextMatchingFunction makeMatchingFunction() const;
|
||||
|
@@ -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 <coreplugin/outputwindow.h>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
@@ -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
|
||||
};
|
||||
|
@@ -33,6 +33,7 @@ public:
|
||||
|
||||
Utils::BoolAspect popUp{this};
|
||||
Utils::BoolAspect wrapOutput{this};
|
||||
Utils::BoolAspect discardOutput{this};
|
||||
Utils::IntegerAspect maxCharCount{this};
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user