Commit 548a86f5 authored by Ulf Hermann's avatar Ulf Hermann

QmlProfiler: Add text marks for QML/JS types into documents

The text marks are little labels next to the lines in the editor
that tell you how much of total run time was spent in the
respective QML/JS construct during the last profiling session.
This is similar to what the valgrind profiler does.

We add the text marks only when the documents are loaded into an
editor. This keeps the number of text marks manageable. Multiple
events on a single line are shown using a tooltip.

Task-number: QTCREATORBUG-17757
Change-Id: Ie38b8ab880a718a1ef72ef343d84070ab34bc5bc
Reviewed-by: default avatarhjk <hjk@qt.io>
parent 8d7feb4b
......@@ -43,7 +43,8 @@ SOURCES += \
qmlprofilertraceview.cpp \
qmlprofilerviewmanager.cpp \
qmltypedevent.cpp \
scenegraphtimelinemodel.cpp
scenegraphtimelinemodel.cpp \
qmlprofilertextmark.cpp
HEADERS += \
debugmessagesmodel.h \
......@@ -88,7 +89,8 @@ HEADERS += \
qmlprofilertraceview.h \
qmlprofilerviewmanager.h \
qmltypedevent.h \
scenegraphtimelinemodel.h
scenegraphtimelinemodel.h \
qmlprofilertextmark.h
RESOURCES += \
qml/qmlprofiler.qrc
......
......@@ -55,6 +55,7 @@ QtcPlugin {
"qmlprofilerstatewidget.cpp", "qmlprofilerstatewidget.h",
"qmlprofilerstatisticsmodel.cpp", "qmlprofilerstatisticsmodel.h",
"qmlprofilerstatisticsview.cpp", "qmlprofilerstatisticsview.h",
"qmlprofilertextmark.cpp", "qmlprofilertextmark.h",
"qmlprofilertimelinemodel.cpp", "qmlprofilertimelinemodel.h",
"qmlprofilertool.cpp", "qmlprofilertool.h",
"qmlprofilertraceclient.cpp", "qmlprofilertraceclient.h",
......
......@@ -36,6 +36,7 @@ const char LAST_TRACE_FILE[] = "Analyzer.QmlProfiler.LastTraceFile";
const char AGGREGATE_TRACES[] = "Analyzer.QmlProfiler.AggregateTraces";
const char SETTINGS[] = "Analyzer.QmlProfiler.Settings";
const char ANALYZER[] = "Analyzer";
const char TEXT_MARK_CATEGORY[] = "Analyzer.QmlProfiler.TextMark";
const int QML_MIN_LEVEL = 1; // Set to 0 to remove the empty line between models in the timeline
......
......@@ -188,15 +188,14 @@ void QmlProfilerDataModel::QmlProfilerDataModelPrivate::rewriteType(int typeInde
type.setDisplayName(getDisplayName(type));
type.setData(getInitialDetails(type));
// Only bindings and signal handlers need rewriting
if (type.rangeType() != Binding && type.rangeType() != HandlingSignal)
return;
const QmlEventLocation &location = type.location();
// There is no point in looking for invalid locations
if (!type.location().isValid())
if (!location.isValid())
return;
detailsRewriter->requestDetailsForLocation(typeIndex, type.location());
// Only bindings and signal handlers need rewriting
if (type.rangeType() == Binding || type.rangeType() == HandlingSignal)
detailsRewriter->requestDetailsForLocation(typeIndex, location);
}
static bool isStateful(const QmlEventType &type)
......
......@@ -138,6 +138,7 @@ class QmlProfilerModelManager::QmlProfilerModelManagerPrivate
public:
QmlProfilerDataModel *model;
QmlProfilerNotesModel *notesModel;
QmlProfilerTextMarkModel *textMarkModel;
QmlProfilerModelManager::State state;
QmlProfilerTraceTime *traceTime;
......@@ -172,6 +173,7 @@ QmlProfilerModelManager::QmlProfilerModelManager(QObject *parent) :
d->state = Empty;
d->traceTime = new QmlProfilerTraceTime(this);
d->notesModel = new QmlProfilerNotesModel(this);
d->textMarkModel = new QmlProfilerTextMarkModel(this);
connect(d->model, &QmlProfilerDataModel::allTypesLoaded,
this, &QmlProfilerModelManager::processingDone);
}
......@@ -196,6 +198,11 @@ QmlProfilerNotesModel *QmlProfilerModelManager::notesModel() const
return d->notesModel;
}
QmlProfilerTextMarkModel *QmlProfilerModelManager::textMarkModel() const
{
return d->textMarkModel;
}
bool QmlProfilerModelManager::isEmpty() const
{
return d->model->isEmpty();
......@@ -242,12 +249,29 @@ void QmlProfilerModelManager::addEvent(const QmlEvent &event)
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();
if (location.isValid()) {
d->textMarkModel->addTextMarkId(
firstTypeId + i, QmlEventLocation(
d->model->findLocalFile(location.filename()), location.line(),
location.column()));
}
}
}
void QmlProfilerModelManager::addEventType(const QmlEventType &type)
{
const int typeId = d->model->eventTypes().count();
d->model->addEventType(type);
const QmlEventLocation &location = type.location();
if (location.isValid()) {
d->textMarkModel->addTextMarkId(
typeId, QmlEventLocation(d->model->findLocalFile(location.filename()),
location.line(), location.column()));
}
}
void QmlProfilerModelManager::QmlProfilerModelManagerPrivate::dispatch(const QmlEvent &event,
......
......@@ -30,6 +30,7 @@
#include "qmleventlocation.h"
#include "qmlevent.h"
#include "qmleventtype.h"
#include "qmlprofilertextmark.h"
#include <utils/fileinprojectfinder.h>
......@@ -97,6 +98,7 @@ public:
QmlProfilerTraceTime *traceTime() const;
QmlProfilerDataModel *qmlModel() const;
QmlProfilerNotesModel *notesModel() const;
QmlProfilerTextMarkModel *textMarkModel() const;
bool isEmpty() const;
uint numLoadedEvents() const;
......
......@@ -225,6 +225,53 @@ void QmlProfilerStatisticsView::clear()
d->m_statsParents->clear();
}
QString QmlProfilerStatisticsView::summary(const QVector<int> &typeIds) const
{
const double cutoff = 0.1;
const double round = 0.05;
double maximum = 0;
double sum = 0;
for (int typeId : typeIds) {
const double percentage = d->model->getData()[typeId].percentOfTime;
if (percentage > maximum)
maximum = percentage;
sum += percentage;
}
const QLatin1Char percent('%');
if (sum < cutoff)
return QLatin1Char('<') + QString::number(cutoff, 'f', 1) + percent;
if (typeIds.length() == 1)
return QLatin1Char('~') + QString::number(maximum, 'f', 1) + percent;
// add/subtract 0.05 to avoid problematic rounding
if (maximum < cutoff)
return QChar(0x2264) + QString::number(sum + round, 'f', 1) + percent;
return QChar(0x2265) + QString::number(qMax(maximum - round, cutoff), 'f', 1) + percent;
}
QStringList QmlProfilerStatisticsView::details(int typeId) const
{
const QmlEventType &type = d->model->getTypes()[typeId];
const QChar ellipsisChar(0x2026);
const int maxColumnWidth = 32;
QString data = type.data();
if (data.length() > maxColumnWidth)
data = data.left(maxColumnWidth - 1) + ellipsisChar;
return QStringList({
QmlProfilerStatisticsMainView::nameForType(type.rangeType()),
data,
QString::number(d->model->getData()[typeId].percentOfTime, 'f', 2) + QLatin1Char('%')
});
}
QModelIndex QmlProfilerStatisticsView::selectedModelIndex() const
{
return d->m_statsTree->selectedModelIndex();
......
......@@ -79,6 +79,9 @@ public:
~QmlProfilerStatisticsView();
void clear() override;
QString summary(const QVector<int> &typeIds) const;
QStringList details(int typeId) const;
public slots:
void selectByTypeId(int typeIndex) override;
void onVisibleFeaturesChanged(quint64 features) override;
......
/****************************************************************************
**
** 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 "qmlprofilertextmark.h"
#include "qmlprofilerconstants.h"
#include <QPainter>
#include <QLayout>
namespace QmlProfiler {
namespace Internal {
QmlProfilerTextMark::QmlProfilerTextMark(QmlProfilerTool *tool, int typeId, const QString &fileName,
int lineNumber) :
TextMark(fileName, lineNumber, Constants::TEXT_MARK_CATEGORY), m_tool(tool),
m_typeIds(1, typeId)
{
setWidthFactor(3.5);
}
void QmlProfilerTextMark::addTypeId(int typeId)
{
m_typeIds.append(typeId);
}
void QmlProfilerTextMark::paint(QPainter *painter, const QRect &paintRect) const
{
painter->save();
painter->setPen(Qt::black);
painter->fillRect(paintRect, Qt::white);
painter->drawRect(paintRect);
painter->drawText(paintRect, m_tool->summary(m_typeIds), Qt::AlignRight | Qt::AlignVCenter);
painter->restore();
}
void QmlProfilerTextMark::clicked()
{
int typeId = m_typeIds.takeFirst();
m_typeIds.append(typeId);
m_tool->selectType(typeId);
}
QmlProfilerTextMarkModel::QmlProfilerTextMarkModel(QObject *parent) : QObject(parent)
{
}
QmlProfilerTextMarkModel::~QmlProfilerTextMarkModel()
{
qDeleteAll(m_marks);
}
void QmlProfilerTextMarkModel::clear()
{
qDeleteAll(m_marks);
m_marks.clear();
m_ids.clear();
}
void QmlProfilerTextMarkModel::addTextMarkId(int typeId, const QmlEventLocation &location)
{
m_ids.insert(location.filename(), {typeId, location.line(), location.column()});
}
void QmlProfilerTextMarkModel::createMarks(QmlProfilerTool *tool, const QString &fileName)
{
auto first = m_ids.find(fileName);
QVarLengthArray<TextMarkId> ids;
for (auto it = first; it != m_ids.end() && it.key() == fileName;) {
ids.append({it->typeId, it->lineNumber > 0 ? it->lineNumber : 1, it->columnNumber});
it = m_ids.erase(it);
}
std::sort(ids.begin(), ids.end(), [](const TextMarkId &a, const TextMarkId &b) {
return (a.lineNumber == b.lineNumber) ? (a.columnNumber < b.columnNumber)
: (a.lineNumber < b.lineNumber);
});
int lineNumber = -1;
for (auto it = ids.begin(), end = ids.end(); it != end; ++it) {
if (it->lineNumber == lineNumber) {
m_marks.last()->addTypeId(it->typeId);
} else {
lineNumber = it->lineNumber;
m_marks.append(new QmlProfilerTextMark(tool, it->typeId, fileName, it->lineNumber));
}
}
}
bool QmlProfilerTextMark::addToolTipContent(QLayout *target)
{
QGridLayout *layout = new QGridLayout;
layout->setHorizontalSpacing(10);
for (int row = 0, rowEnd = m_typeIds.length(); row != rowEnd; ++row) {
const QStringList typeDetails = m_tool->details(m_typeIds[row]);
for (int column = 0, columnEnd = typeDetails.length(); column != columnEnd; ++column) {
QLabel *label = new QLabel;
label->setAlignment(column == columnEnd - 1 ? Qt::AlignRight : Qt::AlignLeft);
label->setTextFormat(Qt::PlainText);
label->setText(typeDetails[column]);
layout->addWidget(label, row, column);
}
}
target->addItem(layout);
return true;
}
} // namespace Internal
} // 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 "qmleventlocation.h"
#include "qmlprofilertool.h"
#include <texteditor/textmark.h>
namespace QmlProfiler {
namespace Internal {
class QmlProfilerTextMark : public TextEditor::TextMark
{
public:
QmlProfilerTextMark(QmlProfilerTool *tool, int typeId, const QString &fileName, int lineNumber);
void addTypeId(int typeId);
void paint(QPainter *painter, const QRect &rect) const override;
void clicked() override;
bool isClickable() const override { return true; }
bool addToolTipContent(QLayout *target) override;
private:
QmlProfilerTool *m_tool;
QVector<int> m_typeIds;
};
class QmlProfilerTextMarkModel : public QObject
{
public:
QmlProfilerTextMarkModel(QObject *parent = nullptr);
~QmlProfilerTextMarkModel();
void clear();
void addTextMarkId(int typeId, const QmlEventLocation &location);
void createMarks(QmlProfilerTool *tool, const QString &fileName);
private:
struct TextMarkId {
int typeId;
int lineNumber;
int columnNumber;
};
QMultiHash<QString, TextMarkId> m_ids;
QVector<QmlProfilerTextMark *> m_marks;
};
} // namespace Internal
} // namespace QmlProfiler
......@@ -36,6 +36,7 @@
#include "qmlprofilerrunconfigurationaspect.h"
#include "qmlprofilersettings.h"
#include "qmlprofilerplugin.h"
#include "qmlprofilertextmark.h"
#include <debugger/debuggericons.h>
#include <debugger/analyzer/analyzermanager.h>
......@@ -285,6 +286,15 @@ QmlProfilerTool::QmlProfilerTool(QObject *parent)
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::updateRunActions,
this, &QmlProfilerTool::updateRunActions);
QmlProfilerTextMarkModel *model = d->m_profilerModelManager->textMarkModel();
if (EditorManager *editorManager = EditorManager::instance()) {
connect(editorManager, &EditorManager::editorCreated,
model, [this, model](Core::IEditor *editor, const QString &fileName) {
Q_UNUSED(editor);
model->createMarks(this, fileName);
});
}
}
QmlProfilerTool::~QmlProfilerTool()
......@@ -463,6 +473,11 @@ void QmlProfilerTool::gotoSourceLocation(const QString &fileUrl, int lineNumber,
EditorManager::DoNotSwitchToDesignMode | EditorManager::DoNotSwitchToEditMode);
}
void QmlProfilerTool::selectType(int typeId)
{
d->m_viewContainer->typeSelected(typeId);
}
void QmlProfilerTool::updateTimeDisplay()
{
double seconds = 0;
......@@ -517,6 +532,18 @@ void QmlProfilerTool::setButtonsEnabled(bool enable)
d->m_recordFeaturesMenu->setEnabled(enable);
}
void QmlProfilerTool::createTextMarks()
{
QmlProfilerTextMarkModel *model = d->m_profilerModelManager->textMarkModel();
foreach (IDocument *document, DocumentModel::openedDocuments())
model->createMarks(this, document->filePath().toString());
}
void QmlProfilerTool::clearTextMarks()
{
d->m_profilerModelManager->textMarkModel()->clear();
}
bool QmlProfilerTool::prepareTool()
{
if (d->m_recordButton->isChecked()) {
......@@ -574,6 +601,16 @@ void QmlProfilerTool::startRemoteTool(ProjectExplorer::RunConfiguration *rc)
ProjectExplorerPlugin::startRunControl(runControl, ProjectExplorer::Constants::QML_PROFILER_RUN_MODE);
}
QString QmlProfilerTool::summary(const QVector<int> &typeIds) const
{
return d->m_viewContainer->statisticsView()->summary(typeIds);
}
QStringList QmlProfilerTool::details(int typeId) const
{
return d->m_viewContainer->statisticsView()->details(typeId);
}
void QmlProfilerTool::logState(const QString &msg)
{
MessageManager::write(msg, MessageManager::Flash);
......@@ -765,6 +802,7 @@ void QmlProfilerTool::profilerDataModelStateChanged()
setButtonsEnabled(true);
break;
case QmlProfilerModelManager::ClearingData :
clearTextMarks();
d->m_recordButton->setEnabled(false);
setButtonsEnabled(false);
clearDisplay();
......@@ -783,6 +821,7 @@ void QmlProfilerTool::profilerDataModelStateChanged()
updateTimeDisplay();
d->m_recordButton->setEnabled(true);
setButtonsEnabled(true);
createTextMarks();
break;
default:
break;
......
......@@ -56,6 +56,9 @@ public:
bool prepareTool();
void startRemoteTool(ProjectExplorer::RunConfiguration *rc);
QString summary(const QVector<int> &typeIds) const;
QStringList details(int typeId) const;
static QList <QAction *> profilerContextMenuActions();
// display dialogs / log output
......@@ -75,6 +78,7 @@ public slots:
void setRecording(bool recording);
void gotoSourceLocation(const QString &fileUrl, int lineNumber, int columnNumber);
void selectType(int typeId);
private slots:
void clearData();
......@@ -100,6 +104,8 @@ private:
bool checkForUnsavedNotes();
void restoreFeatureVisibility();
void setButtonsEnabled(bool enable);
void createTextMarks();
void clearTextMarks();
class QmlProfilerToolPrivate;
QmlProfilerToolPrivate *d;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment