Tracing: Generalize event filtering

This allows us to not only filter by ranges, but potentially also by
other criteria.

Change-Id: I7349ceeabbb2781473a3a4c803dab1006b7b8e50
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Ulf Hermann
2018-05-07 17:08:12 +02:00
parent 7e9b6f62c6
commit 95d7be1b28
8 changed files with 119 additions and 91 deletions

View File

@@ -365,7 +365,7 @@ void TimelineTraceManager::clearAll()
clearTypeStorage(); clearTypeStorage();
} }
void TimelineTraceManager::restrictToRange(qint64 startTime, qint64 endTime) void TimelineTraceManager::restrictByFilter(TraceEventFilter filter)
{ {
if (d->notesModel) if (d->notesModel)
d->notesModel->stash(); d->notesModel->stash();
@@ -374,9 +374,8 @@ void TimelineTraceManager::restrictToRange(qint64 startTime, qint64 endTime)
setVisibleFeatures(0); setVisibleFeatures(0);
QFutureInterface<void> future; QFutureInterface<void> future;
replayEvents(startTime, endTime, replayEvents(filter(std::bind(&TimelineTraceManagerPrivate::dispatch, d,
std::bind(&TimelineTraceManagerPrivate::dispatch, d, std::placeholders::_1, std::placeholders::_1, std::placeholders::_2)),
std::placeholders::_2),
[this]() { [this]() {
initialize(); initialize();
}, [this]() { }, [this]() {

View File

@@ -43,6 +43,7 @@ class TRACING_EXPORT TimelineTraceManager : public QObject
Q_OBJECT Q_OBJECT
public: public:
typedef std::function<void(const TraceEvent &, const TraceEventType &)> TraceEventLoader; typedef std::function<void(const TraceEvent &, const TraceEventType &)> TraceEventLoader;
typedef std::function<TraceEventLoader(TraceEventLoader)> TraceEventFilter;
typedef std::function<void()> Initializer; typedef std::function<void()> Initializer;
typedef std::function<void()> Finalizer; typedef std::function<void()> Finalizer;
typedef std::function<void()> Clearer; typedef std::function<void()> Clearer;
@@ -99,15 +100,14 @@ protected:
virtual void clearEventStorage(); virtual void clearEventStorage();
virtual void clearTypeStorage(); virtual void clearTypeStorage();
void restrictToRange(qint64 startTime, qint64 endTime); void restrictByFilter(TraceEventFilter filter);
void addEvent(const TraceEvent &event); void addEvent(const TraceEvent &event);
void addEventType(const TraceEventType &type); void addEventType(const TraceEventType &type);
virtual const TraceEventType &lookupType(int typeId) const = 0; virtual const TraceEventType &lookupType(int typeId) const = 0;
virtual TimelineTraceFile *createTraceFile() = 0; virtual TimelineTraceFile *createTraceFile() = 0;
virtual void replayEvents(qint64 rangeStart, qint64 rangeEnd, TraceEventLoader loader, virtual void replayEvents(TraceEventLoader loader, Initializer initializer, Finalizer finalizer,
Initializer initializer, Finalizer finalizer,
ErrorHandler errorHandler, QFutureInterface<void> &future) const = 0; ErrorHandler errorHandler, QFutureInterface<void> &future) const = 0;
private: private:

View File

@@ -163,9 +163,10 @@ void FlameGraphModel::restrictToFeatures(quint64 visibleFeatures)
clear(); clear();
QFutureInterface<void> future; QFutureInterface<void> future;
m_modelManager->replayQmlEvents(m_modelManager->traceStart(), m_modelManager->traceEnd(), const auto filter = m_modelManager->rangeFilter(m_modelManager->traceStart(),
std::bind(&FlameGraphModel::loadEvent, this, m_modelManager->traceEnd());
std::placeholders::_1, std::placeholders::_2), m_modelManager->replayQmlEvents(filter(std::bind(&FlameGraphModel::loadEvent, this,
std::placeholders::_1, std::placeholders::_2)),
std::bind(&FlameGraphModel::beginResetModel, this), std::bind(&FlameGraphModel::beginResetModel, this),
std::bind(&FlameGraphModel::finalize, this), std::bind(&FlameGraphModel::finalize, this),
[this](const QString &message) { [this](const QString &message) {

View File

@@ -146,13 +146,12 @@ const QmlEventType &QmlProfilerModelManager::eventType(int typeId) const
return d->eventTypes.at(typeId); return d->eventTypes.at(typeId);
} }
void QmlProfilerModelManager::replayEvents(qint64 rangeStart, qint64 rangeEnd, void QmlProfilerModelManager::replayEvents(TraceEventLoader loader, Initializer initializer,
TraceEventLoader loader, Initializer initializer,
Finalizer finalizer, ErrorHandler errorHandler, Finalizer finalizer, ErrorHandler errorHandler,
QFutureInterface<void> &future) const QFutureInterface<void> &future) const
{ {
replayQmlEvents(rangeStart, rangeEnd, static_cast<QmlEventLoader>(loader), initializer, replayQmlEvents(static_cast<QmlEventLoader>(loader), initializer, finalizer, errorHandler,
finalizer, errorHandler, future); future);
} }
static bool isStateful(const QmlEventType &type) static bool isStateful(const QmlEventType &type)
@@ -164,81 +163,19 @@ static bool isStateful(const QmlEventType &type)
return message == PixmapCacheEvent || message == MemoryAllocation; return message == PixmapCacheEvent || message == MemoryAllocation;
} }
void QmlProfilerModelManager::replayQmlEvents(qint64 rangeStart, qint64 rangeEnd, void QmlProfilerModelManager::replayQmlEvents(QmlEventLoader loader,
QmlEventLoader loader, Initializer initializer, Initializer initializer, Finalizer finalizer,
Finalizer finalizer, ErrorHandler errorHandler, ErrorHandler errorHandler,
QFutureInterface<void> &future) const QFutureInterface<void> &future) const
{ {
if (initializer) if (initializer)
initializer(); initializer();
QStack<QmlEvent> stack;
bool crossedRangeStart = false;
const auto result = d->file.replay([&](const QmlEvent &event) { const auto result = d->file.replay([&](const QmlEvent &event) {
if (future.isCanceled()) if (future.isCanceled())
return false; return false;
const QmlEventType &type = d->eventTypes[event.typeIndex()]; loader(event, d->eventTypes[event.typeIndex()]);
// No restrictions: load all events
if (rangeStart == -1 || rangeEnd == -1) {
loader(event, type);
return true;
}
// Double-check if rangeStart has been crossed. Some versions of Qt send dirty data.
qint64 adjustedTimestamp = event.timestamp();
if (event.timestamp() < rangeStart && !crossedRangeStart) {
if (type.rangeType() != MaximumRangeType) {
if (event.rangeStage() == RangeStart)
stack.push(event);
else if (event.rangeStage() == RangeEnd)
stack.pop();
return true;
} else if (isStateful(type)) {
adjustedTimestamp = rangeStart;
} else {
return true;
}
} else {
if (!crossedRangeStart) {
for (QmlEvent stashed : stack) {
stashed.setTimestamp(rangeStart);
loader(stashed, d->eventTypes[stashed.typeIndex()]);
}
stack.clear();
crossedRangeStart = true;
}
if (event.timestamp() > rangeEnd) {
if (type.rangeType() != MaximumRangeType) {
if (event.rangeStage() == RangeEnd) {
if (stack.isEmpty()) {
QmlEvent endEvent(event);
endEvent.setTimestamp(rangeEnd);
loader(endEvent, d->eventTypes[event.typeIndex()]);
} else {
stack.pop();
}
} else if (event.rangeStage() == RangeStart) {
stack.push(event);
}
return true;
} else if (isStateful(type)) {
adjustedTimestamp = rangeEnd;
} else {
return true;
}
}
}
if (adjustedTimestamp != event.timestamp()) {
QmlEvent adjusted(event);
adjusted.setTimestamp(adjustedTimestamp);
loader(adjusted, type);
} else {
loader(event, type);
}
return true; return true;
}); });
@@ -370,6 +307,22 @@ void QmlProfilerModelManager::detailsChanged(int typeId, const QString &newStrin
emit typeDetailsChanged(typeId); emit typeDetailsChanged(typeId);
} }
void QmlProfilerModelManager::restrictByFilter(QmlProfilerModelManager::QmlEventFilter filter)
{
return Timeline::TimelineTraceManager::restrictByFilter([filter](TraceEventLoader loader) {
const auto filteredQmlLoader = filter([loader](const QmlEvent &event,
const QmlEventType &type) {
loader(event, type);
});
return [filteredQmlLoader](const Timeline::TraceEvent &event,
const Timeline::TraceEventType &type) {
filteredQmlLoader(static_cast<const QmlEvent &>(event),
static_cast<const QmlEventType &>(type));
};
});
}
const Timeline::TraceEventType &QmlProfilerModelManager::lookupType(int typeIndex) const const Timeline::TraceEventType &QmlProfilerModelManager::lookupType(int typeIndex) const
{ {
return eventType(typeIndex); return eventType(typeIndex);
@@ -404,7 +357,7 @@ void QmlProfilerModelManager::addEvent(const QmlEvent &event)
void QmlProfilerModelManager::restrictToRange(qint64 start, qint64 end) void QmlProfilerModelManager::restrictToRange(qint64 start, qint64 end)
{ {
d->isRestrictedToRange = (start != -1 || end != -1); d->isRestrictedToRange = (start != -1 || end != -1);
TimelineTraceManager::restrictToRange(start, end); restrictByFilter(rangeFilter(start, end));
} }
bool QmlProfilerModelManager::isRestrictedToRange() const bool QmlProfilerModelManager::isRestrictedToRange() const
@@ -412,6 +365,78 @@ bool QmlProfilerModelManager::isRestrictedToRange() const
return d->isRestrictedToRange; return d->isRestrictedToRange;
} }
QmlProfilerModelManager::QmlEventFilter
QmlProfilerModelManager::rangeFilter(qint64 rangeStart, qint64 rangeEnd) const
{
return [rangeStart, rangeEnd, this] (QmlEventLoader loader) {
QStack<QmlEvent> stack;
bool crossedRangeStart = false;
return [=](const QmlEvent &event, const QmlEventType &type) mutable {
// No restrictions: load all events
if (rangeStart == -1 || rangeEnd == -1) {
loader(event, type);
return true;
}
// Double-check if rangeStart has been crossed. Some versions of Qt send dirty data.
qint64 adjustedTimestamp = event.timestamp();
if (event.timestamp() < rangeStart && !crossedRangeStart) {
if (type.rangeType() != MaximumRangeType) {
if (event.rangeStage() == RangeStart)
stack.push(event);
else if (event.rangeStage() == RangeEnd)
stack.pop();
return true;
} else if (isStateful(type)) {
adjustedTimestamp = rangeStart;
} else {
return true;
}
} else {
if (!crossedRangeStart) {
for (auto stashed : stack) {
stashed.setTimestamp(rangeStart);
loader(stashed, eventType(stashed.typeIndex()));
}
stack.clear();
crossedRangeStart = true;
}
if (event.timestamp() > rangeEnd) {
if (type.rangeType() != MaximumRangeType) {
if (event.rangeStage() == RangeEnd) {
if (stack.isEmpty()) {
QmlEvent endEvent(event);
endEvent.setTimestamp(rangeEnd);
loader(endEvent, type);
} else {
stack.pop();
}
} else if (event.rangeStage() == RangeStart) {
stack.push(event);
}
return true;
} else if (isStateful(type)) {
adjustedTimestamp = rangeEnd;
} else {
return true;
}
}
}
if (adjustedTimestamp != event.timestamp()) {
QmlEvent adjusted(event);
adjusted.setTimestamp(adjustedTimestamp);
loader(adjusted, type);
} else {
loader(event, type);
}
return true;
};
};
}
Timeline::TimelineTraceFile *QmlProfilerModelManager::createTraceFile() Timeline::TimelineTraceFile *QmlProfilerModelManager::createTraceFile()
{ {
return new Internal::QmlProfilerTraceFile(this); return new Internal::QmlProfilerTraceFile(this);

View File

@@ -50,6 +50,7 @@ class QMLPROFILER_EXPORT QmlProfilerModelManager : public Timeline::TimelineTrac
Q_OBJECT Q_OBJECT
public: public:
typedef std::function<void(const QmlEvent &, const QmlEventType &)> QmlEventLoader; typedef std::function<void(const QmlEvent &, const QmlEventType &)> QmlEventLoader;
typedef std::function<QmlEventLoader(QmlEventLoader)> QmlEventFilter;
explicit QmlProfilerModelManager(QObject *parent = nullptr); explicit QmlProfilerModelManager(QObject *parent = nullptr);
~QmlProfilerModelManager() override; ~QmlProfilerModelManager() override;
@@ -64,8 +65,7 @@ public:
void addEventTypes(const QVector<QmlEventType> &types); void addEventTypes(const QVector<QmlEventType> &types);
const QmlEventType &eventType(int typeId) const; const QmlEventType &eventType(int typeId) const;
void replayQmlEvents(qint64 rangeStart, qint64 rangeEnd, QmlEventLoader loader, void replayQmlEvents(QmlEventLoader loader, Initializer initializer, Finalizer finalizer,
Initializer initializer, Finalizer finalizer,
ErrorHandler errorHandler, QFutureInterface<void> &future) const; ErrorHandler errorHandler, QFutureInterface<void> &future) const;
void finalize() override; void finalize() override;
@@ -81,20 +81,22 @@ public:
void restrictToRange(qint64 start, qint64 end); void restrictToRange(qint64 start, qint64 end);
bool isRestrictedToRange() const; bool isRestrictedToRange() const;
QmlEventFilter rangeFilter(qint64 start, qint64 end) const;
signals: signals:
void typeDetailsChanged(int typeId); void typeDetailsChanged(int typeId);
void typeDetailsFinished(); void typeDetailsFinished();
private: private:
void detailsChanged(int typeId, const QString &newString); void detailsChanged(int typeId, const QString &newString);
void restrictByFilter(QmlEventFilter filter);
void clearEventStorage() override; void clearEventStorage() override;
void clearTypeStorage() override; void clearTypeStorage() override;
const Timeline::TraceEventType &lookupType(int typeId) const override; const Timeline::TraceEventType &lookupType(int typeId) const override;
Timeline::TimelineTraceFile *createTraceFile() override; Timeline::TimelineTraceFile *createTraceFile() override;
void replayEvents(qint64 rangeStart, qint64 rangeEnd, TraceEventLoader loader, void replayEvents(TraceEventLoader loader, Initializer initializer, Finalizer finalizer,
Initializer initializer, Finalizer finalizer,
ErrorHandler errorHandler, QFutureInterface<void> &future) const override; ErrorHandler errorHandler, QFutureInterface<void> &future) const override;
class QmlProfilerModelManagerPrivate; class QmlProfilerModelManagerPrivate;

View File

@@ -102,9 +102,10 @@ void QmlProfilerStatisticsModel::restrictToFeatures(quint64 features)
clear(); clear();
QFutureInterface<void> future; QFutureInterface<void> future;
m_modelManager->replayQmlEvents(m_modelManager->traceStart(), m_modelManager->traceEnd(), auto filter = m_modelManager->rangeFilter(m_modelManager->traceStart(),
std::bind(&QmlProfilerStatisticsModel::loadEvent, this, m_modelManager->traceEnd());
std::placeholders::_1, std::placeholders::_2), m_modelManager->replayQmlEvents(filter(std::bind(&QmlProfilerStatisticsModel::loadEvent, this,
std::placeholders::_1, std::placeholders::_2)),
std::bind(&QmlProfilerStatisticsModel::beginResetModel, this), std::bind(&QmlProfilerStatisticsModel::beginResetModel, this),
[this]() { [this]() {
finalize(); finalize();

View File

@@ -712,7 +712,7 @@ void QmlProfilerTraceFile::saveQtd(QIODevice *device)
QStack<QmlEvent> stack; QStack<QmlEvent> stack;
qint64 lastProgressTimestamp = traceStart(); qint64 lastProgressTimestamp = traceStart();
modelManager()->replayQmlEvents(-1, -1, [&](const QmlEvent &event, const QmlEventType &type) { modelManager()->replayQmlEvents([&](const QmlEvent &event, const QmlEventType &type) {
if (type.rangeType() != MaximumRangeType && event.rangeStage() == RangeStart) { if (type.rangeType() != MaximumRangeType && event.rangeStage() == RangeStart) {
stack.push(event); stack.push(event);
return; return;
@@ -858,7 +858,7 @@ void QmlProfilerTraceFile::saveQzt(QIODevice *device)
} }
qint64 lastProgressTimestamp = traceStart(); qint64 lastProgressTimestamp = traceStart();
modelManager()->replayQmlEvents(-1, -1, [&](const QmlEvent &event, const QmlEventType &type) { modelManager()->replayQmlEvents([&](const QmlEvent &event, const QmlEventType &type) {
Q_UNUSED(type); Q_UNUSED(type);
bufferStream << event; bufferStream << event;
// 32MB buffer should be plenty for efficient compression // 32MB buffer should be plenty for efficient compression

View File

@@ -70,7 +70,7 @@ void QmlProfilerTraceClientTest::testMessageReceived()
traceClient.stateChanged(QmlDebug::QmlDebugClient::NotConnected); traceClient.stateChanged(QmlDebug::QmlDebugClient::NotConnected);
QFutureInterface<void> future; QFutureInterface<void> future;
modelManager.replayQmlEvents(-1, -1, [&](const QmlEvent &event, const QmlEventType &type) { modelManager.replayQmlEvents([&](const QmlEvent &event, const QmlEventType &type) {
qint64 timestamp; qint64 timestamp;
qint32 message; qint32 message;
qint32 rangeType; qint32 rangeType;