Commit 4a1e5a63 authored by Ulf Hermann's avatar Ulf Hermann
Browse files

QmlProfiler: Drive event loading from the model manager



We want to get rid of the big master list of QmlEvent in
QmlProfilerDataModel, as that gets very large for longer traces. In
order to reduce the dependencies on that list we load the events on the
fly into the child models while they are being received, rather than
having the child models query QmlProfilerDataModel for the event list
later.

As the trace client so far only emitted rangedEvent() for complete
ranges we run into problems with models that need their events sorted.
The rangedEvent() signals were sorted by end time, rather than start
time which makes it inconvenient to analyze them in a stack based way,
for aggregation. This is solved by passing on all the details from the
trace client to the models, with the QmlProfilerDataModel aggregating
the type information before having the events dispatched to the child
models.

Change-Id: I5831a20551f21cf91e27d298a709f604ebd96c3e
Reviewed-by: Jörg Bornemann's avatarJoerg Bornemann <joerg.bornemann@qt.io>
Reviewed-by: Ulf Hermann's avatarUlf Hermann <ulf.hermann@qt.io>
parent dd87df7e
......@@ -63,7 +63,7 @@ FlameGraphModel::FlameGraphModel(QmlProfilerModelManager *modelManager,
void FlameGraphModel::clear()
{
beginResetModel();
m_stackBottom = FlameGraphData();
m_stackBottom = FlameGraphData(0, -1, 1);
m_callStack.clear();
m_callStack.append(QmlEvent());
m_stackTop = &m_stackBottom;
......@@ -102,15 +102,16 @@ void FlameGraphModel::loadEvent(const QmlEvent &event, const QmlEventType &type)
beginResetModel();
const QmlEvent *potentialParent = &(m_callStack.top());
while (potentialParent->isValid() &&
potentialParent->timestamp() + potentialParent->duration() <= event.timestamp()) {
if (event.rangeStage() == RangeEnd) {
m_stackTop->duration += event.timestamp() - potentialParent->timestamp();
m_callStack.pop();
m_stackTop = m_stackTop->parent;
potentialParent = &(m_callStack.top());
} else {
QTC_ASSERT(event.rangeStage() == RangeStart, return);
m_callStack.push(event);
m_stackTop = pushChild(m_stackTop, event);
}
m_callStack.push(event);
m_stackTop = pushChild(m_stackTop, event);
}
void FlameGraphModel::finalize()
......@@ -126,31 +127,6 @@ void FlameGraphModel::onModelManagerStateChanged()
{
if (m_modelManager->state() == QmlProfilerModelManager::ClearingData)
clear();
else if (m_modelManager->state() == QmlProfilerModelManager::ProcessingData)
loadData();
}
void FlameGraphModel::loadData(qint64 rangeStart, qint64 rangeEnd)
{
clear();
const bool checkRanges = (rangeStart != -1) && (rangeEnd != -1);
const QVector<QmlEvent> &eventList = m_modelManager->qmlModel()->events();
const QVector<QmlEventType> &typesList = m_modelManager->qmlModel()->eventTypes();
for (int i = 0; i < eventList.size(); ++i) {
const QmlEvent &event = eventList[i];
if (checkRanges) {
if ((event.timestamp() + event.duration() < rangeStart)
|| (event.timestamp() > rangeEnd))
continue;
}
loadEvent(event, typesList[event.typeIndex()]);
}
finalize();
}
static QString nameForType(RangeType typeNumber)
......@@ -223,12 +199,11 @@ FlameGraphData *FlameGraphModel::pushChild(FlameGraphData *parent, const QmlEven
foreach (FlameGraphData *child, parent->children) {
if (child->typeIndex == data.typeIndex()) {
++child->calls;
child->duration += data.duration();
return child;
}
}
FlameGraphData *child = new FlameGraphData(parent, data.typeIndex(), data.duration());
FlameGraphData *child = new FlameGraphData(parent, data.typeIndex());
parent->children.append(child);
return child;
}
......@@ -293,5 +268,10 @@ QHash<int, QByteArray> FlameGraphModel::roleNames() const
return names;
}
QmlProfilerModelManager *FlameGraphModel::modelManager() const
{
return m_modelManager;
}
} // namespace Internal
} // namespace QmlProfiler
......@@ -80,12 +80,12 @@ public:
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
QmlProfilerModelManager *modelManager() const;
public slots:
void loadEvent(const QmlEvent &event, const QmlEventType &type);
void finalize();
void onModelManagerStateChanged();
void loadData(qint64 rangeStart = -1, qint64 rangeEnd = -1);
void loadNotes(int typeId, bool emitSignal);
void clear();
......
......@@ -37,7 +37,7 @@ namespace Internal {
FlameGraphView::FlameGraphView(QWidget *parent, QmlProfilerModelManager *manager) :
QmlProfilerEventsView(parent), m_content(new QQuickWidget(this)),
m_model(new FlameGraphModel(manager, this)), m_isRestrictedToRange(false)
m_model(new FlameGraphModel(manager, this))
{
setWindowTitle(QStringLiteral("Flamegraph"));
setObjectName(QStringLiteral("QmlProfilerFlamegraph"));
......@@ -66,22 +66,6 @@ FlameGraphView::FlameGraphView(QWidget *parent, QmlProfilerModelManager *manager
this, SIGNAL(gotoSourceLocation(QString,int,int)));
}
void FlameGraphView::clear()
{
m_isRestrictedToRange = false;
}
void FlameGraphView::restrictToRange(qint64 rangeStart, qint64 rangeEnd)
{
m_isRestrictedToRange = (rangeStart != -1 || rangeEnd != -1);
m_model->loadData(rangeStart, rangeEnd);
}
bool FlameGraphView::isRestrictedToRange() const
{
return m_isRestrictedToRange;
}
void FlameGraphView::selectByTypeId(int typeIndex)
{
m_content->rootObject()->setProperty("selectedTypeId", typeIndex);
......@@ -107,7 +91,7 @@ void FlameGraphView::contextMenuEvent(QContextMenuEvent *ev)
menu.addActions(QmlProfilerTool::profilerContextMenuActions());
menu.addSeparator();
getGlobalStatsAction = menu.addAction(tr("Show Full Range"));
if (!isRestrictedToRange())
if (!m_model->modelManager()->isRestrictedToRange())
getGlobalStatsAction->setEnabled(false);
if (menu.exec(position) == getGlobalStatsAction)
......
......@@ -40,10 +40,6 @@ class FlameGraphView : public QmlProfilerEventsView
public:
FlameGraphView(QWidget *parent, QmlProfilerModelManager *manager);
void clear() override;
void restrictToRange(qint64 rangeStart, qint64 rangeEnd) override;
bool isRestrictedToRange() const override;
public slots:
void selectByTypeId(int typeIndex) override;
void onVisibleFeaturesChanged(quint64 features) override;
......@@ -54,7 +50,6 @@ protected:
private:
QQuickWidget *m_content;
FlameGraphModel *m_model;
bool m_isRestrictedToRange;
};
} // namespace Internal
......
......@@ -142,12 +142,12 @@ bool MemoryUsageModel::accepted(const QmlEventType &type) const
void MemoryUsageModel::loadEvent(const QmlEvent &event, const QmlEventType &type)
{
while (!m_rangeStack.empty() && m_rangeStack.top().endTime < event.timestamp())
m_rangeStack.pop();
if (type.message != MemoryAllocation) {
if (type.rangeType != MaximumRangeType) {
m_rangeStack.push(RangeStackFrame(event.typeIndex(), event.timestamp(),
event.timestamp() + event.duration()));
if (event.rangeStage() == RangeStart)
m_rangeStack.push(RangeStackFrame(event.typeIndex(), event.timestamp()));
else if (event.rangeStage() == RangeEnd)
m_rangeStack.pop();
}
return;
}
......
......@@ -76,12 +76,11 @@ protected:
private:
struct RangeStackFrame {
RangeStackFrame() : originTypeIndex(-1), startTime(-1), endTime(-1) {}
RangeStackFrame(int originTypeIndex, qint64 startTime, qint64 endTime) :
originTypeIndex(originTypeIndex), startTime(startTime), endTime(endTime) {}
RangeStackFrame() : originTypeIndex(-1), startTime(-1) {}
RangeStackFrame(int originTypeIndex, qint64 startTime) :
originTypeIndex(originTypeIndex), startTime(startTime) {}
int originTypeIndex;
qint64 startTime;
qint64 endTime;
};
static QString memoryTypeName(int type);
......
......@@ -198,10 +198,6 @@ void QmlProfilerClientManager::connectClientSignals()
this, &QmlProfilerClientManager::qmlComplete);
connect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::newEngine,
this, &QmlProfilerClientManager::qmlNewEngine);
connect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::rangedEvent,
d->modelManager, &QmlProfilerModelManager::addQmlEvent);
connect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::debugMessage,
d->modelManager, &QmlProfilerModelManager::addDebugMessage);
connect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::traceFinished,
d->modelManager->traceTime(), &QmlProfilerTraceTime::increaseEndTime);
connect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::traceStarted,
......@@ -212,6 +208,8 @@ void QmlProfilerClientManager::connectClientSignals()
d->qmlclientplugin.data(), &QmlProfilerTraceClient::setRequestedFeatures);
connect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::recordedFeaturesChanged,
d->profilerState, &QmlProfilerStateManager::setRecordedFeatures);
connect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::qmlEvent,
d->modelManager, &QmlProfilerModelManager::addQmlEvent);
}
}
......@@ -222,10 +220,6 @@ void QmlProfilerClientManager::disconnectClientSignals()
this, &QmlProfilerClientManager::qmlComplete);
disconnect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::newEngine,
this, &QmlProfilerClientManager::qmlNewEngine);
disconnect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::rangedEvent,
d->modelManager, &QmlProfilerModelManager::addQmlEvent);
disconnect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::debugMessage,
d->modelManager, &QmlProfilerModelManager::addDebugMessage);
disconnect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::traceFinished,
d->modelManager->traceTime(), &QmlProfilerTraceTime::increaseEndTime);
disconnect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::traceStarted,
......@@ -236,6 +230,8 @@ void QmlProfilerClientManager::disconnectClientSignals()
d->qmlclientplugin.data(), &QmlProfilerTraceClient::setRequestedFeatures);
disconnect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::recordedFeaturesChanged,
d->profilerState, &QmlProfilerStateManager::setRecordedFeatures);
disconnect(d->qmlclientplugin.data(), &QmlProfilerTraceClient::qmlEvent,
d->modelManager, &QmlProfilerModelManager::addQmlEvent);
}
}
......
......@@ -28,10 +28,12 @@
#include "qmlprofilernotesmodel.h"
#include "qmlprofilerdetailsrewriter.h"
#include "qmlprofilereventtypes.h"
#include "qmltypedevent.h"
#include <utils/qtcassert.h>
#include <QUrl>
#include <QDebug>
#include <QStack>
#include <algorithm>
namespace QmlProfiler {
......@@ -39,10 +41,16 @@ namespace QmlProfiler {
class QmlProfilerDataModel::QmlProfilerDataModelPrivate
{
public:
void rewriteType(int typeIndex);
int resolveType(const QmlEventType &type);
int resolveStackTop();
QVector<QmlEventType> eventTypes;
QVector<QmlEvent> eventList;
QHash<QmlEventType, int> eventTypeIds;
QStack<QmlTypedEvent> rangesInProgress;
QmlProfilerModelManager *modelManager;
int modelId;
Internal::QmlProfilerDetailsRewriter *detailsRewriter;
......@@ -108,9 +116,7 @@ QmlProfilerDataModel::QmlProfilerDataModel(Utils::FileInProjectFinder *fileFinde
connect(d->detailsRewriter, &QmlProfilerDetailsRewriter::rewriteDetailsString,
this, &QmlProfilerDataModel::detailsChanged);
connect(d->detailsRewriter, &QmlProfilerDetailsRewriter::eventDetailsChanged,
this, &QmlProfilerDataModel::detailsDone);
connect(this, &QmlProfilerDataModel::requestReload,
d->detailsRewriter, &QmlProfilerDetailsRewriter::reloadDocuments);
this, &QmlProfilerDataModel::allTypesLoaded);
}
QmlProfilerDataModel::~QmlProfilerDataModel()
......@@ -142,6 +148,9 @@ void QmlProfilerDataModel::setData(qint64 traceStart, qint64 traceEnd,
d->eventTypes = types;
for (int id = 0; id < types.count(); ++id)
d->eventTypeIds[types[id]] = id;
foreach (const QmlEvent &event, d->eventList)
d->modelManager->dispatch(event, d->eventTypes[event.typeIndex()]);
}
int QmlProfilerDataModel::count() const
......@@ -156,6 +165,7 @@ void QmlProfilerDataModel::clear()
d->eventList.clear();
d->eventTypes.clear();
d->eventTypeIds.clear();
d->rangesInProgress.clear();
d->detailsRewriter->clearRequests();
}
......@@ -165,11 +175,6 @@ bool QmlProfilerDataModel::isEmpty() const
return d->eventList.isEmpty();
}
inline static bool operator<(const QmlEvent &t1, const QmlEvent &t2)
{
return t1.timestamp() < t2.timestamp();
}
inline static uint qHash(const QmlEventType &type)
{
return qHash(type.location.filename) ^
......@@ -190,66 +195,139 @@ inline static bool operator==(const QmlEventType &type1,
type1.location.filename == type2.location.filename;
}
void QmlProfilerDataModel::processData()
void QmlProfilerDataModel::QmlProfilerDataModelPrivate::rewriteType(int typeIndex)
{
Q_D(QmlProfilerDataModel);
// post-processing
// sort events by start time, using above operator<
std::sort(d->eventList.begin(), d->eventList.end());
QmlEventType &type = eventTypes[typeIndex];
type.displayName = getDisplayName(type);
type.data = getInitialDetails(type);
// rewrite strings
int n = d->eventTypes.count();
for (int i = 0; i < n; i++) {
QmlEventType *event = &d->eventTypes[i];
event->displayName = getDisplayName(*event);
event->data = getInitialDetails(*event);
// Only bindings and signal handlers need rewriting
if (type.rangeType != Binding && type.rangeType != HandlingSignal)
return;
//
// request further details from files
//
// There is no point in looking for invalid locations
if (type.location.filename.isEmpty() || type.location.line < 0 || type.location.column < 0)
return;
if (event->rangeType != Binding && event->rangeType != HandlingSignal)
continue;
// This skips anonymous bindings in Qt4.8 (we don't have valid location data for them)
if (event->location.filename.isEmpty())
continue;
detailsRewriter->requestDetailsForLocation(typeIndex, type.location);
}
// Skip non-anonymous bindings from Qt4.8 (we already have correct details for them)
if (event->location.column == -1)
continue;
int QmlProfilerDataModel::QmlProfilerDataModelPrivate::resolveType(const QmlEventType &type)
{
QHash<QmlEventType, int>::ConstIterator it = eventTypeIds.constFind(type);
d->detailsRewriter->requestDetailsForLocation(i, event->location);
int typeIndex = -1;
if (it != eventTypeIds.constEnd()) {
typeIndex = it.value();
} else {
typeIndex = eventTypes.size();
eventTypeIds[type] = typeIndex;
eventTypes.append(type);
rewriteType(typeIndex);
}
return typeIndex;
}
emit requestReload();
int QmlProfilerDataModel::QmlProfilerDataModelPrivate::resolveStackTop()
{
if (rangesInProgress.isEmpty())
return -1;
QmlTypedEvent &typedEvent = rangesInProgress.top();
int typeIndex = typedEvent.event.typeIndex();
if (typeIndex >= 0)
return typeIndex;
typeIndex = resolveType(typedEvent.type);
typedEvent.event.setTypeIndex(typeIndex);
eventList.append(typedEvent.event);
modelManager->dispatch(eventList.last(), eventTypes[typeIndex]);
return typeIndex;
}
void QmlProfilerDataModel::addEvent(Message message, RangeType rangeType, int detailType,
qint64 startTime, qint64 duration, const QString &data,
const QmlEventLocation &location, qint64 ndata1, qint64 ndata2,
qint64 ndata3, qint64 ndata4, qint64 ndata5)
void QmlProfilerDataModel::addEvent(const QmlEvent &event, const QmlEventType &type)
{
Q_D(QmlProfilerDataModel);
QString displayName;
QmlEventType typeData(displayName, location, message, rangeType, detailType,
message == DebugMessage ? QString() : data);
QmlEvent eventData = (message == DebugMessage) ?
QmlEvent(startTime, duration, -1, data) :
QmlEvent(startTime, duration, -1, {ndata1, ndata2, ndata3, ndata4, ndata5});
QHash<QmlEventType, int>::Iterator it = d->eventTypeIds.find(typeData);
if (it != d->eventTypeIds.end()) {
eventData.setTypeIndex(it.value());
} else {
eventData.setTypeIndex(d->eventTypes.size());
d->eventTypeIds[typeData] = eventData.typeIndex();
d->eventTypes.append(typeData);
// RangeData and RangeLocation always apply to the range on the top of the stack. Furthermore,
// all ranges are perfectly nested. This is why we can defer the type resolution until either
// the range ends or a child range starts. With only the information in RangeStart we wouldn't
// be able to uniquely identify the event type.
Message rangeStage = type.rangeType == MaximumRangeType ? type.message : event.rangeStage();
switch (rangeStage) {
case RangeStart:
d->resolveStackTop();
d->rangesInProgress.push(QmlTypedEvent({event, type}));
break;
case RangeEnd: {
int typeIndex = d->resolveStackTop();
QTC_ASSERT(typeIndex != -1, break);
d->eventList.append(event);
QmlEvent &appended = d->eventList.last();
appended.setTypeIndex(typeIndex);
d->modelManager->dispatch(appended, d->eventTypes[typeIndex]);
d->rangesInProgress.pop();
break;
}
case RangeData:
d->rangesInProgress.top().type.data = type.data;
break;
case RangeLocation:
d->rangesInProgress.top().type.location = type.location;
break;
default: {
d->eventList.append(event);
QmlEvent &appended = d->eventList.last();
int typeIndex = d->resolveType(type);
appended.setTypeIndex(typeIndex);
d->modelManager->dispatch(appended, d->eventTypes[typeIndex]);
break;
}
}
}
void QmlProfilerDataModel::replayEvents(qint64 rangeStart, qint64 rangeEnd,
QmlProfilerModelManager::EventLoader loader) const
{
Q_D(const QmlProfilerDataModel);
QStack<QmlEvent> stack;
foreach (const QmlEvent &event, d->eventList) {
const QmlEventType &type = d->eventTypes[event.typeIndex()];
if (rangeStart != -1 && rangeEnd != -1) {
if (event.timestamp() < rangeStart) {
if (type.rangeType != MaximumRangeType) {
if (event.rangeStage() == RangeStart)
stack.push(event);
else if (event.rangeStage() == RangeEnd)
stack.pop();
}
continue;
} else if (event.timestamp() > rangeEnd) {
if (type.rangeType != MaximumRangeType) {
if (event.rangeStage() == RangeEnd) {
if (stack.isEmpty()) {
QmlEvent endEvent(event);
endEvent.setTimestamp(rangeEnd);
loader(event, d->eventTypes[event.typeIndex()]);
} else {
stack.pop();
}
} else if (event.rangeStage() == RangeStart) {
stack.push(event);
}
}
continue;
} else if (!stack.isEmpty()) {
foreach (QmlEvent stashed, stack) {
stashed.setTimestamp(rangeStart);
loader(stashed, d->eventTypes[stashed.typeIndex()]);
}
stack.clear();
}
}
d->eventList.append(eventData);
loader(event, type);
}
}
qint64 QmlProfilerDataModel::lastTimeMark() const
......@@ -258,22 +336,20 @@ qint64 QmlProfilerDataModel::lastTimeMark() const
if (d->eventList.isEmpty())
return 0;
return d->eventList.last().timestamp() + d->eventList.last().duration();
return d->eventList.last().timestamp();
}
void QmlProfilerDataModel::detailsChanged(int requestId, const QString &newString)
void QmlProfilerDataModel::finalize()
{
Q_D(QmlProfilerDataModel);
QTC_ASSERT(requestId < d->eventTypes.count(), return);
QmlEventType *event = &d->eventTypes[requestId];
event->data = newString;
d->detailsRewriter->reloadDocuments();
}
void QmlProfilerDataModel::detailsDone()
void QmlProfilerDataModel::detailsChanged(int requestId, const QString &newString)
{
Q_D(QmlProfilerDataModel);
d->modelManager->processingDone();
QTC_ASSERT(requestId < d->eventTypes.count(), return);
d->eventTypes[requestId].data = newString;
}
} // namespace QmlProfiler
......@@ -49,22 +49,21 @@ public:
const QVector<QmlEventType> &eventTypes() const;
void setData(qint64 traceStart, qint64 traceEnd, const QVector<QmlEventType> &types,
const QVector<QmlEvent> &events);
void processData();
int count() const;
void clear();
bool isEmpty() const;
void addEvent(Message message, RangeType rangeType, int bindingType, qint64 startTime,
qint64 duration, const QString &data, const QmlEventLocation &location,
qint64 ndata1, qint64 ndata2, qint64 ndata3, qint64 ndata4, qint64 ndata5);
void addEvent(const QmlEvent &event, const QmlEventType &type);
void replayEvents(qint64 startTime, qint64 endTime,
QmlProfilerModelManager::EventLoader loader) const;
void finalize();
qint64 lastTimeMark() const;
signals:
void requestReload();
void allTypesLoaded();
protected slots:
void detailsChanged(int requestId, const QString &newString);
void detailsDone();
private:
class QmlProfilerDataModelPrivate;
......
......@@ -38,10 +38,7 @@ class QMLPROFILER_EXPORT QmlProfilerEventsView : public QWidget
Q_OBJECT
public:
QmlProfilerEventsView(QWidget *parent = 0) : QWidget(parent) {}
virtual void clear() = 0;
virtual void restrictToRange(qint64 rangeStart, qint64 rangeEnd) = 0;
virtual bool isRestrictedToRange() const = 0;
virtual void clear() {}
signals:
void gotoSourceLocation(const QString &fileName, int lineNumber, int columnNumber);
......
......@@ -37,6 +37,8 @@
#include <QFile>
#include <QMessageBox>
#include <functional>
namespace QmlProfiler {
namespace Internal {
......@@ -59,22 +61,19 @@ Q_STATIC_ASSERT(sizeof(ProfileFeatureNames) == sizeof(char *) * MaximumProfileFe
/////////////////////////////////////////////////////////////////////
QmlProfilerTraceTime::QmlProfilerTraceTime(QObject *parent) :
QObject(parent), m_startTime(-1), m_endTime(-1)
{
}
QmlProfilerTraceTime::~QmlProfilerTraceTime()
QObject(parent), m_startTime(-1), m_endTime(-1),
m_restrictedStartTime(-1), m_restrictedEndTime(-1)