Commit c84f650f authored by Ulf Hermann's avatar Ulf Hermann
Browse files

QmlProfiler: Integrate QmlProfilerDataModel into model manager



There is no need to keep them separate as the data model is not
accessed from the outside anymore. This removes a lot of indirection.

Change-Id: I91da4dfa816295300c8cfcca22430d5c5b3298c0
Reviewed-by: Christian Kandeler's avatarChristian Kandeler <christian.kandeler@qt.io>
parent 78daf47a
......@@ -21,7 +21,6 @@ SOURCES += \
qmlprofilerbindingloopsrenderpass.cpp \
qmlprofilerclientmanager.cpp \
qmlprofilerconfigwidget.cpp \
qmlprofilerdatamodel.cpp \
qmlprofilerdetailsrewriter.cpp \
qmlprofilermodelmanager.cpp \
qmlprofilernotesmodel.cpp \
......@@ -65,7 +64,6 @@ HEADERS += \
qmlprofilerclientmanager.h \
qmlprofilerconfigwidget.h \
qmlprofilerconstants.h \
qmlprofilerdatamodel.h \
qmlprofilerdetailsrewriter.h \
qmlprofilereventsview.h \
qmlprofilereventtypes.h \
......
......@@ -38,7 +38,6 @@ QtcPlugin {
"qmlprofilerclientmanager.cpp", "qmlprofilerclientmanager.h",
"qmlprofilerconfigwidget.cpp", "qmlprofilerconfigwidget.h",
"qmlprofilerconfigwidget.ui", "qmlprofilerconstants.h",
"qmlprofilerdatamodel.cpp", "qmlprofilerdatamodel.h",
"qmlprofilerdetailsrewriter.cpp", "qmlprofilerdetailsrewriter.h",
"qmlprofilereventsview.h",
"qmlprofilereventtypes.h",
......
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qmlprofilerdatamodel.h"
#include "qmlprofilermodelmanager.h"
#include "qmlprofilernotesmodel.h"
#include "qmlprofilerdetailsrewriter.h"
#include "qmlprofilereventtypes.h"
#include "qmltypedevent.h"
#include <utils/qtcassert.h>
#include <utils/temporaryfile.h>
#include <QUrl>
#include <QDebug>
#include <QStack>
#include <algorithm>
namespace QmlProfiler {
class QmlProfilerDataModel::QmlProfilerDataModelPrivate
{
public:
QmlProfilerDataModelPrivate() : file("qmlprofiler-data") { }
void rewriteType(int typeIndex);
int resolveStackTop();
QVector<QmlEventType> eventTypes;
Internal::QmlProfilerDetailsRewriter *detailsRewriter = 0;
Utils::TemporaryFile file;
QDataStream eventStream;
};
QString getDisplayName(const QmlEventType &event)
{
if (event.location().filename().isEmpty()) {
return QmlProfilerModelManager::tr("<bytecode>");
} else {
const QString filePath = QUrl(event.location().filename()).path();
return filePath.mid(filePath.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char(':') +
QString::number(event.location().line());
}
}
QString getInitialDetails(const QmlEventType &event)
{
QString details = event.data();
// generate details string
if (!details.isEmpty()) {
details = details.replace(QLatin1Char('\n'),QLatin1Char(' ')).simplified();
if (details.isEmpty()) {
if (event.rangeType() == Javascript)
details = QmlProfilerDataModel::tr("anonymous function");
} else {
QRegExp rewrite(QLatin1String("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)"));
bool match = rewrite.exactMatch(details);
if (match)
details = rewrite.cap(1) + QLatin1String(": ") + rewrite.cap(3);
if (details.startsWith(QLatin1String("file://")) ||
details.startsWith(QLatin1String("qrc:/")))
details = details.mid(details.lastIndexOf(QLatin1Char('/')) + 1);
}
} else if (event.rangeType() == Painting) {
// QtQuick1 animations always run in GUI thread.
details = QmlProfilerDataModel::tr("GUI Thread");
}
return details;
}
QmlProfilerDataModel::QmlProfilerDataModel(QObject *parent) :
QObject(parent), d_ptr(new QmlProfilerDataModelPrivate)
{
Q_D(QmlProfilerDataModel);
Q_ASSERT(parent);
d->detailsRewriter = new QmlProfilerDetailsRewriter(this);
connect(d->detailsRewriter, &QmlProfilerDetailsRewriter::rewriteDetailsString,
this, &QmlProfilerDataModel::detailsChanged);
connect(d->detailsRewriter, &QmlProfilerDetailsRewriter::eventDetailsChanged,
this, &QmlProfilerDataModel::allTypesLoaded);
if (!d->file.open())
emit traceFileError();
else
d->eventStream.setDevice(&d->file);
}
QmlProfilerDataModel::~QmlProfilerDataModel()
{
Q_D(QmlProfilerDataModel);
delete d;
}
const QmlEventType &QmlProfilerDataModel::eventType(int typeId) const
{
Q_D(const QmlProfilerDataModel);
return d->eventTypes.at(typeId);
}
const QVector<QmlEventType> &QmlProfilerDataModel::eventTypes() const
{
Q_D(const QmlProfilerDataModel);
return d->eventTypes;
}
void QmlProfilerDataModel::addEventTypes(const QVector<QmlEventType> &types)
{
Q_D(QmlProfilerDataModel);
int typeIndex = d->eventTypes.length();
d->eventTypes.append(types);
for (const int end = d->eventTypes.length(); typeIndex < end; ++typeIndex)
d->rewriteType(typeIndex);
}
void QmlProfilerDataModel::addEventType(const QmlEventType &type)
{
Q_D(QmlProfilerDataModel);
int typeIndex = d->eventTypes.length();
d->eventTypes.append(type);
d->rewriteType(typeIndex);
}
void QmlProfilerDataModel::populateFileFinder(
const ProjectExplorer::RunConfiguration *runConfiguration)
{
Q_D(QmlProfilerDataModel);
d->detailsRewriter->populateFileFinder(runConfiguration);
}
QString QmlProfilerDataModel::findLocalFile(const QString &remoteFile)
{
Q_D(QmlProfilerDataModel);
return d->detailsRewriter->getLocalFile(remoteFile);
}
void QmlProfilerDataModel::addEvent(const QmlEvent &event)
{
Q_D(QmlProfilerDataModel);
d->eventStream << event;
}
void QmlProfilerDataModel::addEvents(const QVector<QmlEvent> &events)
{
Q_D(QmlProfilerDataModel);
for (const QmlEvent &event : events)
d->eventStream << event;
}
void QmlProfilerDataModel::clear()
{
Q_D(QmlProfilerDataModel);
d->file.remove();
d->eventStream.unsetDevice();
if (!d->file.open())
emit traceFileError();
else
d->eventStream.setDevice(&d->file);
d->eventTypes.clear();
d->detailsRewriter->clearRequests();
}
bool QmlProfilerDataModel::isEmpty() const
{
Q_D(const QmlProfilerDataModel);
return d->file.pos() == 0;
}
void QmlProfilerDataModel::QmlProfilerDataModelPrivate::rewriteType(int typeIndex)
{
QmlEventType &type = eventTypes[typeIndex];
type.setDisplayName(getDisplayName(type));
type.setData(getInitialDetails(type));
const QmlEventLocation &location = type.location();
// There is no point in looking for invalid locations
if (!location.isValid())
return;
// Only bindings and signal handlers need rewriting
if (type.rangeType() == Binding || type.rangeType() == HandlingSignal)
detailsRewriter->requestDetailsForLocation(typeIndex, location);
}
static bool isStateful(const QmlEventType &type)
{
// Events of these types carry state that has to be taken into account when adding later events:
// PixmapCacheEvent: Total size of the cache and size of pixmap currently being loaded
// MemoryAllocation: Total size of the JS heap and the amount of it currently in use
const Message message = type.message();
return message == PixmapCacheEvent || message == MemoryAllocation;
}
bool QmlProfilerDataModel::replayEvents(qint64 rangeStart, qint64 rangeEnd,
QmlProfilerModelManager::EventLoader loader) const
{
Q_D(const QmlProfilerDataModel);
QStack<QmlEvent> stack;
QmlEvent event;
QFile file(d->file.fileName());
if (!file.open(QIODevice::ReadOnly))
return false;
QDataStream stream(&file);
bool crossedRangeStart = false;
while (!stream.atEnd()) {
stream >> event;
if (stream.status() == QDataStream::ReadPastEnd)
break;
const QmlEventType &type = d->eventTypes[event.typeIndex()];
if (rangeStart != -1 && rangeEnd != -1) {
// Double-check if rangeStart has been crossed. Some versions of Qt send dirty data.
if (event.timestamp() < rangeStart && !crossedRangeStart) {
if (type.rangeType() != MaximumRangeType) {
if (event.rangeStage() == RangeStart)
stack.push(event);
else if (event.rangeStage() == RangeEnd)
stack.pop();
continue;
} else if (isStateful(type)) {
event.setTimestamp(rangeStart);
} else {
continue;
}
} else {
if (!crossedRangeStart) {
foreach (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);
}
continue;
} else if (isStateful(type)) {
event.setTimestamp(rangeEnd);
} else {
continue;
}
}
}
}
loader(event, type);
}
return true;
}
void QmlProfilerDataModel::finalize()
{
Q_D(QmlProfilerDataModel);
d->file.flush();
d->detailsRewriter->reloadDocuments();
}
void QmlProfilerDataModel::detailsChanged(int typeId, const QString &newString)
{
Q_D(QmlProfilerDataModel);
QTC_ASSERT(typeId < d->eventTypes.count(), return);
d->eventTypes[typeId].setData(newString);
}
} // namespace QmlProfiler
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "qmlprofilermodelmanager.h"
#include "qmlprofilereventtypes.h"
#include "qmleventlocation.h"
#include "qmleventtype.h"
#include "qmlevent.h"
#include <utils/fileinprojectfinder.h>
#include <projectexplorer/runconfiguration.h>
namespace QmlProfiler {
class QMLPROFILER_EXPORT QmlProfilerDataModel : public QObject
{
Q_OBJECT
public:
explicit QmlProfilerDataModel(QObject *parent = nullptr);
~QmlProfilerDataModel();
const QmlEventType &eventType(int typeId) const;
const QVector<QmlEventType> &eventTypes() const;
void addEventTypes(const QVector<QmlEventType> &types);
void addEventType(const QmlEventType &type);
void populateFileFinder(const ProjectExplorer::RunConfiguration *runConfiguration = nullptr);
QString findLocalFile(const QString &remoteFile);
void clear();
bool isEmpty() const;
void addEvent(const QmlEvent &event);
void addEvents(const QVector<QmlEvent> &events);
bool replayEvents(qint64 startTime, qint64 endTime,
QmlProfilerModelManager::EventLoader loader) const;
void finalize();
signals:
void allTypesLoaded();
void traceFileError();
protected slots:
void detailsChanged(int typeId, const QString &newString);
private:
class QmlProfilerDataModelPrivate;
QmlProfilerDataModelPrivate *d_ptr;
Q_DECLARE_PRIVATE(QmlProfilerDataModel)
};
} // namespace QmlProfiler
......@@ -25,17 +25,19 @@
#include "qmlprofilermodelmanager.h"
#include "qmlprofilerconstants.h"
#include "qmlprofilerdatamodel.h"
#include "qmlprofilertracefile.h"
#include "qmlprofilernotesmodel.h"
#include "qmlprofilerdetailsrewriter.h"
#include <coreplugin/progressmanager/progressmanager.h>
#include <utils/runextensions.h>
#include <utils/qtcassert.h>
#include <utils/temporaryfile.h>
#include <QDebug>
#include <QFile>
#include <QMessageBox>
#include <QStack>
#include <functional>
......@@ -136,7 +138,8 @@ void QmlProfilerTraceTime::restrictToRange(qint64 startTime, qint64 endTime)
class QmlProfilerModelManager::QmlProfilerModelManagerPrivate
{
public:
QmlProfilerDataModel *model;
QmlProfilerModelManagerPrivate() : file("qmlprofiler-data") {}
QmlProfilerNotesModel *notesModel;
QmlProfilerTextMarkModel *textMarkModel;
......@@ -155,7 +158,15 @@ public:
QHash<ProfileFeature, QVector<EventLoader> > eventLoaders;
QVector<Finalizer> finalizers;
QVector<QmlEventType> eventTypes;
QmlProfilerDetailsRewriter *detailsRewriter;
Utils::TemporaryFile file;
QDataStream eventStream;
void dispatch(const QmlEvent &event, const QmlEventType &type);
void rewriteType(int typeIndex);
int resolveStackTop();
};
......@@ -169,17 +180,21 @@ QmlProfilerModelManager::QmlProfilerModelManager(QObject *parent) :
d->visibleFeatures = 0;
d->recordedFeatures = 0;
d->aggregateTraces = false;
d->model = new QmlProfilerDataModel(this);
d->state = Empty;
d->traceTime = new QmlProfilerTraceTime(this);
d->notesModel = new QmlProfilerNotesModel(this);
d->textMarkModel = new QmlProfilerTextMarkModel(this);
connect(d->model, &QmlProfilerDataModel::allTypesLoaded,
d->detailsRewriter = new QmlProfilerDetailsRewriter(this);
connect(d->detailsRewriter, &QmlProfilerDetailsRewriter::rewriteDetailsString,
this, &QmlProfilerModelManager::detailsChanged);
connect(d->detailsRewriter, &QmlProfilerDetailsRewriter::eventDetailsChanged,
this, &QmlProfilerModelManager::processingDone);
connect(d->model, &QmlProfilerDataModel::traceFileError,
this, [this]() {
emit error(tr("Could not open a temporary file for storing QML traces."));
});
if (d->file.open())
d->eventStream.setDevice(&d->file);
else
emit error(tr("Cannot open temporary trace file to store events."));
}
QmlProfilerModelManager::~QmlProfilerModelManager()
......@@ -204,7 +219,7 @@ QmlProfilerTextMarkModel *QmlProfilerModelManager::textMarkModel() const
bool QmlProfilerModelManager::isEmpty() const
{
return d->model->isEmpty();
return d->file.pos() == 0;
}
uint QmlProfilerModelManager::numLoadedEvents() const
......@@ -214,7 +229,7 @@ uint QmlProfilerModelManager::numLoadedEvents() const
uint QmlProfilerModelManager::numLoadedEventTypes() const
{
return d->model->eventTypes().count();
return d->eventTypes.count();
}
int QmlProfilerModelManager::registerModelProxy()
......@@ -234,28 +249,28 @@ int QmlProfilerModelManager::numRegisteredFinalizers() const
void QmlProfilerModelManager::addEvents(const QVector<QmlEvent> &events)
{
d->model->addEvents(events);
const QVector<QmlEventType> &types = d->model->eventTypes();
for (const QmlEvent &event : events)
d->dispatch(event, types[event.typeIndex()]);
for (const QmlEvent &event : events) {
d->eventStream << event;
d->dispatch(event, d->eventTypes[event.typeIndex()]);
}
}
void QmlProfilerModelManager::addEvent(const QmlEvent &event)
{
d->model->addEvent(event);
d->dispatch(event, d->model->eventType(event.typeIndex()));
d->eventStream << event;
d->dispatch(event, d->eventTypes.at(event.typeIndex()));
}
void QmlProfilerModelManager::addEventTypes(const QVector<QmlEventType> &types)
{
const int firstTypeId = d->model->eventTypes().count();
d->model->addEventTypes(types);
for (int i = 0, end = types.length(); i < end; ++i) {
const QmlEventLocation &location = types[i].location();
const int firstTypeId = d->eventTypes.length();;
d->eventTypes.append(types);
for (int typeId = firstTypeId, end = d->eventTypes.length(); typeId < end; ++typeId) {
d->rewriteType(typeId);
const QmlEventLocation &location = d->eventTypes[typeId].location();
if (location.isValid()) {
d->textMarkModel->addTextMarkId(
firstTypeId + i, QmlEventLocation(
d->model->findLocalFile(location.filename()), location.line(),
d->textMarkModel->addTextMarkId(typeId, QmlEventLocation(
findLocalFile(location.filename()), location.line(),
location.column()));
}
}
......@@ -263,25 +278,97 @@ void QmlProfilerModelManager::addEventTypes(const QVector<QmlEventType> &types)
void QmlProfilerModelManager::addEventType(const QmlEventType &type)
{
const int typeId = d->model->eventTypes().count();
d->model->addEventType(type);
const int typeId = d->eventTypes.count();
d->eventTypes.append(type);
d->rewriteType(typeId);
const QmlEventLocation &location = type.location();
if (location.isValid()) {
d->textMarkModel->addTextMarkId(
typeId, QmlEventLocation(d->model->findLocalFile(location.filename()),
typeId, QmlEventLocation(findLocalFile(location.filename()),
location.line(), location.column()));
}
}
const QVector<QmlEventType> &QmlProfilerModelManager::eventTypes() const
{
return d->model->eventTypes();
return d->eventTypes;
}
bool QmlProfilerModelManager::replayEvents(qint64 startTime, qint64 endTime,
static bool isStateful(const QmlEventType &type)
{
// Events of these types carry state that has to be taken into account when adding later events:
// PixmapCacheEvent: Total size of the cache and size of pixmap currently being loaded
// MemoryAllocation: Total size of the JS heap and the amount of it currently in use
const Message message = type.message();
return message == PixmapCacheEvent || message == MemoryAllocation;
}
bool QmlProfilerModelManager::replayEvents(qint64 rangeStart, qint64 rangeEnd,
EventLoader loader) const
{
return d->model->replayEvents(startTime, endTime, loader);
QStack<QmlEvent> stack;
QmlEvent event;
QFile file(d->file.fileName());
if (!file.open(QIODevice::ReadOnly))
return false;
QDataStream stream(&file);
bool crossedRangeStart = false;
while (!stream.atEnd()) {
stream >> event;
if (stream.status() == QDataStream::ReadPastEnd)
break;
const QmlEventType &type = d->eventTypes[event.typeIndex()];
if (rangeStart != -1 && rangeEnd != -1) {
// Double-check if rangeStart has been crossed. Some versions of Qt send dirty data.
if (event.timestamp() < rangeStart && !crossedRangeStart) {
if (type.rangeType() != MaximumRangeType) {
if (event.rangeStage() == RangeStart)
stack.push(event);
else if (event.rangeStage() == RangeEnd)
stack.pop();
continue;
} else if (isStateful(type)) {
event.setTimestamp(rangeStart);