From 13cb0b6df3f1ca0ba3d8852616033633087a2456 Mon Sep 17 00:00:00 2001 From: Christiaan Janssen Date: Wed, 22 Jun 2011 15:02:33 +0200 Subject: [PATCH] QmlProfiler: Dependencies view Change-Id: I1172d66b8e577994df787e2bb14cdd03a2affb35 Reviewed-on: http://codereview.qt.nokia.com/613 Reviewed-by: Qt Sanity Bot Reviewed-by: Kai Koehne --- src/plugins/qmlprofiler/qml/MainView.qml | 1 + src/plugins/qmlprofiler/qmlprofiler.pro | 6 +- .../qmlprofiler/qmlprofilercalltreeview.cpp | 293 ++++++++++++++++++ .../qmlprofiler/qmlprofilercalltreeview.h | 70 +++++ .../qmlprofiler/qmlprofilersummaryview.cpp | 4 +- .../qmlprofiler/qmlprofilersummaryview.h | 2 +- src/plugins/qmlprofiler/qmlprofilertool.cpp | 20 +- src/plugins/qmlprofiler/tracewindow.cpp | 19 +- src/plugins/qmlprofiler/tracewindow.h | 2 +- 9 files changed, 405 insertions(+), 12 deletions(-) create mode 100644 src/plugins/qmlprofiler/qmlprofilercalltreeview.cpp create mode 100644 src/plugins/qmlprofiler/qmlprofilercalltreeview.h diff --git a/src/plugins/qmlprofiler/qml/MainView.qml b/src/plugins/qmlprofiler/qml/MainView.qml index e08c25336f6..eee32f17450 100644 --- a/src/plugins/qmlprofiler/qml/MainView.qml +++ b/src/plugins/qmlprofiler/qml/MainView.qml @@ -76,6 +76,7 @@ Rectangle { root.clearAll(); } + // todo: consider nestingLevel if (!Plotter.valuesdone) Plotter.ranges.push( { type: type, start: startTime, duration: length, label: data, fileName: fileName, line: line } ); } diff --git a/src/plugins/qmlprofiler/qmlprofiler.pro b/src/plugins/qmlprofiler/qmlprofiler.pro index 57281e14674..00330638669 100644 --- a/src/plugins/qmlprofiler/qmlprofiler.pro +++ b/src/plugins/qmlprofiler/qmlprofiler.pro @@ -24,7 +24,8 @@ SOURCES += \ qmlprofilersummaryview.cpp \ qmlprojectanalyzerruncontrolfactory.cpp \ localqmlprofilerrunner.cpp \ - codaqmlprofilerrunner.cpp + codaqmlprofilerrunner.cpp \ + qmlprofilercalltreeview.cpp HEADERS += \ qmlprofilerconstants.h \ @@ -39,7 +40,8 @@ HEADERS += \ qmlprojectanalyzerruncontrolfactory.h \ abstractqmlprofilerrunner.h \ localqmlprofilerrunner.h \ - codaqmlprofilerrunner.h + codaqmlprofilerrunner.h \ + qmlprofilercalltreeview.h RESOURCES += \ qml/qml.qrc diff --git a/src/plugins/qmlprofiler/qmlprofilercalltreeview.cpp b/src/plugins/qmlprofiler/qmlprofilercalltreeview.cpp new file mode 100644 index 00000000000..8eda0accea7 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilercalltreeview.cpp @@ -0,0 +1,293 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** No Commercial Usage +** +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "qmlprofilercalltreeview.h" + +#include +#include + +#include +#include + +using namespace QmlProfiler::Internal; + +struct BindingData +{ + BindingData() : + displayname(0), filename(0), location(0), details(0), + line(-1), rangeType(-1), level(-1), childrenHash(0), parentBinding(0) {} + + ~BindingData() { + delete displayname; + delete filename; + delete location; + delete childrenHash; + } + QString *displayname; + QString *filename; + QString *location; + QString *details; + int line; + int rangeType; + qint64 level; + QHash *childrenHash; + + // reference to parent binding stored in the hash + BindingData *parentBinding; +}; + +typedef QHash BindingHash; + +enum ItemRole { + LocationRole = Qt::UserRole+1, + FilenameRole = Qt::UserRole+2, + LineRole = Qt::UserRole+3 +}; + +class QmlProfilerCallTreeView::QmlProfilerCallTreeViewPrivate +{ +public: + QmlProfilerCallTreeViewPrivate(QmlProfilerCallTreeView *qq) : q(qq) {} + + void recursiveClearHash(BindingHash *hash); + void buildModelFromHash( BindingHash *hash, QStandardItem *parentItem ); + + QmlProfilerCallTreeView *q; + + QStandardItemModel *m_model; +// ToDo: avoid unnecessary allocations by using global hash +// BindingHash m_globalHash; + BindingHash m_rootHash; + QList m_bindingBuffer; +}; + +QmlProfilerCallTreeView::QmlProfilerCallTreeView(QWidget *parent) : + QTreeView(parent), d(new QmlProfilerCallTreeViewPrivate(this)) +{ + setObjectName("QmlProfilerCallTreeView"); + setRootIsDecorated(true); + header()->setResizeMode(QHeaderView::Interactive); + header()->setMinimumSectionSize(50); + setSortingEnabled(false); + setFrameStyle(QFrame::NoFrame); + + d->m_model = new QStandardItemModel(this); + + setModel(d->m_model); + + d->m_model->setColumnCount(3); + setHeaderLabels(); + + connect(this,SIGNAL(clicked(QModelIndex)), this,SLOT(jumpToItem(QModelIndex))); +} + +QmlProfilerCallTreeView::~QmlProfilerCallTreeView() +{ + clean(); + delete d->m_model; +} + +void QmlProfilerCallTreeView::clean() +{ + d->m_model->clear(); + d->m_model->setColumnCount(3); + + // clean the hashes + d->recursiveClearHash(&d->m_rootHash); +// d->recursiveClearHash(&d->m_globalHash); + + setHeaderLabels(); + setSortingEnabled(false); +} + +void QmlProfilerCallTreeView::addRangedEvent(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length, + const QStringList &data, const QString &fileName, int line) +{ + Q_UNUSED(startTime); + Q_UNUSED(nestingInType); + Q_UNUSED(length); + + const QChar colon = QLatin1Char(':'); + QString localName, displayName, location, details; + + if (fileName.isEmpty()) { + displayName = tr(""); + location = QLatin1String("--"); + + } else { + localName = QUrl(fileName).toLocalFile(); + displayName = localName.mid(localName.lastIndexOf(QChar('/')) + 1) + colon + QString::number(line); + location = fileName+colon+QString::number(line); + } + + if (data.isEmpty()) + details = tr("Source code not available"); + else + details = data.join(" ").replace('\n'," "); + + // ToDo: instead of allocating each new event, store them in the global hash + BindingData *newBinding = new BindingData; + newBinding->displayname = new QString(displayName); + newBinding->filename = new QString(fileName); + newBinding->line = line; + newBinding->level = nestingLevel; + newBinding->rangeType = type; + newBinding->location = new QString(location); + newBinding->details = new QString(details); + newBinding->childrenHash = 0; + + d->m_bindingBuffer.prepend(newBinding); + + if (nestingLevel == 1) { + // top level: insert buffered stuff + BindingHash *currentHash = &(d->m_rootHash); + BindingData *lastBinding = 0; + int lastLevel = 0; + + while (d->m_bindingBuffer.length()) { + BindingData *bindingInfo = d->m_bindingBuffer.at(0); + // find the data's place + if (bindingInfo->level > lastLevel) { + currentHash = lastBinding ? lastBinding->childrenHash : &(d->m_rootHash); + bindingInfo->parentBinding = lastBinding; + ++lastLevel; + } else if (bindingInfo->level == lastLevel) { + bindingInfo->parentBinding = lastBinding->parentBinding; + } else if (bindingInfo->level < lastLevel) { + while (bindingInfo->level < lastLevel) { + bindingInfo->parentBinding = lastBinding->parentBinding ? lastBinding->parentBinding->parentBinding : 0; + currentHash = bindingInfo->parentBinding ? bindingInfo->parentBinding->childrenHash : &(d->m_rootHash); + --lastLevel; + } + } + + BindingHash::iterator it = currentHash->find(*bindingInfo->location); + if (it == currentHash->end()) { + bindingInfo->childrenHash = new BindingHash; + currentHash->insert(*bindingInfo->location, bindingInfo); + lastBinding = bindingInfo; + } else { + lastBinding = it.value(); + delete bindingInfo; + } + + d->m_bindingBuffer.removeFirst(); + } + } +} + +void QmlProfilerCallTreeView::complete() +{ + // build the model from the hashed data + d->buildModelFromHash( &d->m_rootHash, d->m_model->invisibleRootItem()); + + expandAll(); + resizeColumnToContents(0); + resizeColumnToContents(1); +} + +void QmlProfilerCallTreeView::jumpToItem(const QModelIndex &index) +{ + QStandardItem *clickedItem = d->m_model->itemFromIndex(index); + QStandardItem *infoItem; + if (clickedItem->parent()) + infoItem = clickedItem->parent()->child(clickedItem->row(), 0); + else + infoItem = d->m_model->item(index.row(), 0); + + int line = infoItem->data(LineRole).toInt(); + if (line == -1) + return; + QString fileName = infoItem->data(FilenameRole).toString(); + emit gotoSourceLocation(fileName, line); +} + +void QmlProfilerCallTreeView::setHeaderLabels() +{ + d->m_model->setHeaderData(0, Qt::Horizontal, QVariant(tr("Location"))); + d->m_model->setHeaderData(1, Qt::Horizontal, QVariant(tr("Type"))); + d->m_model->setHeaderData(2, Qt::Horizontal, QVariant(tr("Details"))); +} + +void QmlProfilerCallTreeView::QmlProfilerCallTreeViewPrivate::recursiveClearHash(BindingHash *hash) { + QHashIterator it(*hash); + while (it.hasNext()) { + it.next(); + if (it.value()->childrenHash) + recursiveClearHash(it.value()->childrenHash); + delete it.value(); + } + hash->clear(); +} + +inline QString nameForType(int typeNumber) +{ + switch (typeNumber) { + case 0: return QmlProfilerCallTreeView::tr("Paint"); + case 1: return QmlProfilerCallTreeView::tr("Compile"); + case 2: return QmlProfilerCallTreeView::tr("Create"); + case 3: return QmlProfilerCallTreeView::tr("Binding"); + case 4: return QmlProfilerCallTreeView::tr("Signal"); + } + return QString(); +} + +void QmlProfilerCallTreeView::QmlProfilerCallTreeViewPrivate::buildModelFromHash( BindingHash *hash, QStandardItem *parentItem ) +{ + QHashIterator it(*hash); + + while (it.hasNext()) { + it.next(); + BindingData *binding = it.value(); + + QStandardItem *nameColumn = new QStandardItem(*binding->displayname); + nameColumn->setEditable(false); + + QStandardItem *typeColumn = new QStandardItem(nameForType(binding->rangeType)); + typeColumn->setEditable(false); + + QStandardItem *detailsColumn = new QStandardItem(*binding->details); + detailsColumn->setEditable(false); + + QStandardItem *firstColumn = nameColumn; + firstColumn->setData(QVariant(*binding->location),LocationRole); + firstColumn->setData(QVariant(*binding->filename),FilenameRole); + firstColumn->setData(QVariant(binding->line),LineRole); + + QList newRow; + newRow << nameColumn << typeColumn << detailsColumn; + parentItem->appendRow(newRow); + if (!binding->childrenHash->isEmpty()) + buildModelFromHash(binding->childrenHash, nameColumn); + } +} diff --git a/src/plugins/qmlprofiler/qmlprofilercalltreeview.h b/src/plugins/qmlprofiler/qmlprofilercalltreeview.h new file mode 100644 index 00000000000..bcdc622e8f8 --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilercalltreeview.h @@ -0,0 +1,70 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** No Commercial Usage +** +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QMLPROFILERCALLTREEVIEW_H +#define QMLPROFILERCALLTREEVIEW_H + +#include + +namespace QmlProfiler { +namespace Internal { + +class QmlProfilerCallTreeView : public QTreeView +{ + Q_OBJECT + +public: + explicit QmlProfilerCallTreeView(QWidget *parent = 0); + ~QmlProfilerCallTreeView(); + +signals: + void gotoSourceLocation(const QString &fileName, int lineNumber); + +public slots: + void clean(); + void addRangedEvent(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length, + const QStringList &data, const QString &fileName, int line); + void complete(); + void jumpToItem(const QModelIndex &index); + +private: + class QmlProfilerCallTreeViewPrivate; + QmlProfilerCallTreeViewPrivate *d; + + void setHeaderLabels(); +}; + +} // namespace Internal +} // namespace QmlProfiler + +#endif // QMLPROFILERCALLTREEVIEW_H diff --git a/src/plugins/qmlprofiler/qmlprofilersummaryview.cpp b/src/plugins/qmlprofiler/qmlprofilersummaryview.cpp index ba67d78b9ff..307d920c57a 100644 --- a/src/plugins/qmlprofiler/qmlprofilersummaryview.cpp +++ b/src/plugins/qmlprofiler/qmlprofilersummaryview.cpp @@ -135,11 +135,13 @@ void QmlProfilerSummaryView::clean() setSortingEnabled(false); } -void QmlProfilerSummaryView::addRangedEvent(int type, qint64 startTime, qint64 length, +void QmlProfilerSummaryView::addRangedEvent(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length, const QStringList &data, const QString &fileName, int line) { Q_UNUSED(startTime); Q_UNUSED(data); + Q_UNUSED(nestingLevel); + Q_UNUSED(nestingInType); if (type != QmlProfilerSummaryViewPrivate::Binding && type != QmlProfilerSummaryViewPrivate::HandlingSignal) return; diff --git a/src/plugins/qmlprofiler/qmlprofilersummaryview.h b/src/plugins/qmlprofiler/qmlprofilersummaryview.h index 546a10a0126..8de750d8063 100644 --- a/src/plugins/qmlprofiler/qmlprofilersummaryview.h +++ b/src/plugins/qmlprofiler/qmlprofilersummaryview.h @@ -52,7 +52,7 @@ signals: public slots: void clean(); - void addRangedEvent(int type, qint64 startTime, qint64 length, + void addRangedEvent(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length, const QStringList &data, const QString &fileName, int line); void complete(); void jumpToItem(const QModelIndex &index); diff --git a/src/plugins/qmlprofiler/qmlprofilertool.cpp b/src/plugins/qmlprofiler/qmlprofilertool.cpp index cd73bfb031b..9084b3f9777 100644 --- a/src/plugins/qmlprofiler/qmlprofilertool.cpp +++ b/src/plugins/qmlprofiler/qmlprofilertool.cpp @@ -37,6 +37,7 @@ #include "qmlprofilerconstants.h" #include "qmlprofilerattachdialog.h" #include "qmlprofilersummaryview.h" +#include "qmlprofilercalltreeview.h" #include "tracewindow.h" #include "timelineview.h" @@ -91,6 +92,7 @@ public: int m_connectionAttempts; TraceWindow *m_traceWindow; QmlProfilerSummaryView *m_summary; + QmlProfilerCallTreeView *m_calltree; ProjectExplorer::Project *m_project; Utils::FileInProjectFinder m_projectFinder; ProjectExplorer::RunConfiguration *m_runConfiguration; @@ -214,13 +216,21 @@ void QmlProfilerTool::initializeDockWidgets() d->m_summary = new QmlProfilerSummaryView(mw); - connect(d->m_traceWindow, SIGNAL(range(int,qint64,qint64,QStringList,QString,int)), - d->m_summary, SLOT(addRangedEvent(int,qint64,qint64,QStringList,QString,int))); + connect(d->m_traceWindow, SIGNAL(range(int,int,int,qint64,qint64,QStringList,QString,int)), + d->m_summary, SLOT(addRangedEvent(int,int,int,qint64,qint64,QStringList,QString,int))); connect(d->m_traceWindow, SIGNAL(viewUpdated()), d->m_summary, SLOT(complete())); connect(d->m_summary, SIGNAL(gotoSourceLocation(QString,int)), this, SLOT(gotoSourceLocation(QString,int))); + d->m_calltree = new QmlProfilerCallTreeView(mw); + connect(d->m_traceWindow, SIGNAL(range(int,int,int,qint64,qint64,QStringList,QString,int)), + d->m_calltree, SLOT(addRangedEvent(int,int,int,qint64,qint64,QStringList,QString,int))); + connect(d->m_traceWindow, SIGNAL(viewUpdated()), + d->m_calltree, SLOT(complete())); + connect(d->m_calltree, SIGNAL(gotoSourceLocation(QString,int)), + this, SLOT(gotoSourceLocation(QString,int))); + Core::ICore *core = Core::ICore::instance(); Core::ActionManager *am = core->actionManager(); Core::ActionContainer *manalyzer = am->actionContainer(Analyzer::Constants::M_DEBUG_ANALYZER); @@ -246,8 +256,13 @@ void QmlProfilerTool::initializeDockWidgets() analyzerMgr->createDockWidget(this, tr("Timeline"), d->m_traceWindow, Qt::BottomDockWidgetArea); + QDockWidget *calltreeDock = + analyzerMgr->createDockWidget(this, tr("Dependencies"), + d->m_calltree, Qt::BottomDockWidgetArea); + mw->splitDockWidget(mw->toolBarDockWidget(), summaryDock, Qt::Vertical); mw->tabifyDockWidget(summaryDock, timelineDock); + mw->tabifyDockWidget(timelineDock, calltreeDock); } @@ -387,6 +402,7 @@ void QmlProfilerTool::clearDisplay() { d->m_traceWindow->clearDisplay(); d->m_summary->clean(); + d->m_calltree->clean(); } void QmlProfilerTool::attach() diff --git a/src/plugins/qmlprofiler/tracewindow.cpp b/src/plugins/qmlprofiler/tracewindow.cpp index 9bb8acfa7b3..71f11dfa802 100644 --- a/src/plugins/qmlprofiler/tracewindow.cpp +++ b/src/plugins/qmlprofiler/tracewindow.cpp @@ -114,7 +114,7 @@ signals: void complete(); void gap(qint64); void event(int event, qint64 time); - void range(int type, qint64 startTime, qint64 length, const QStringList &data, const QString &fileName, int line); + void range(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length, const QStringList &data, const QString &fileName, int line); void sample(int, int, int, bool); @@ -135,18 +135,23 @@ private: int m_rangeCount[MaximumRangeType]; qint64 m_maximumTime; bool m_recording; + int m_nestingLevel; + int m_nestingInType[MaximumRangeType]; }; TracePlugin::TracePlugin(QDeclarativeDebugConnection *client) : QDeclarativeDebugClient(QLatin1String("CanvasFrameRate"), client), - m_inProgressRanges(0), m_maximumTime(0), m_recording(false) + m_inProgressRanges(0), m_maximumTime(0), m_recording(false), m_nestingLevel(0) { ::memset(m_rangeCount, 0, MaximumRangeType * sizeof(int)); + ::memset(m_nestingInType, 0, MaximumRangeType * sizeof(int)); } void TracePlugin::clearView() { ::memset(m_rangeCount, 0, MaximumRangeType * sizeof(int)); + ::memset(m_nestingInType, 0, MaximumRangeType * sizeof(int)); + m_nestingLevel = 0; emit clear(); } @@ -214,6 +219,8 @@ void TracePlugin::messageReceived(const QByteArray &data) m_rangeStartTimes[range].push(time); m_inProgressRanges |= (static_cast(1) << range); ++m_rangeCount[range]; + ++m_nestingLevel; + ++m_nestingInType[range]; } else if (messageType == RangeData) { QString data; stream >> data; @@ -244,7 +251,9 @@ void TracePlugin::messageReceived(const QByteArray &data) Location location = m_rangeLocations[range].count() ? m_rangeLocations[range].pop() : Location(); qint64 startTime = m_rangeStartTimes[range].pop(); - emit this->range((RangeType)range, startTime, time - startTime, data, location.fileName, location.line); + emit this->range((RangeType)range, m_nestingLevel, m_nestingInType[range], startTime, time - startTime, data, location.fileName, location.line); + --m_nestingLevel; + --m_nestingInType[range]; if (m_rangeCount[range] == 0) { int count = m_rangeDatas[range].count() + m_rangeStartTimes[range].count() + @@ -295,8 +304,8 @@ void TraceWindow::reset(QDeclarativeDebugConnection *conn) delete m_plugin.data(); m_plugin = new TracePlugin(conn); connect(m_plugin.data(), SIGNAL(complete()), this, SIGNAL(viewUpdated())); - connect(m_plugin.data(), SIGNAL(range(int,qint64,qint64,QStringList,QString,int)), - this, SIGNAL(range(int,qint64,qint64,QStringList,QString,int))); + connect(m_plugin.data(), SIGNAL(range(int,int,int,qint64,qint64,QStringList,QString,int)), + this, SIGNAL(range(int,int,int,qint64,qint64,QStringList,QString,int))); m_view->rootContext()->setContextProperty("connection", m_plugin.data()); m_view->setSource(QUrl("qrc:/qmlprofiler/MainView.qml")); diff --git a/src/plugins/qmlprofiler/tracewindow.h b/src/plugins/qmlprofiler/tracewindow.h index 30cb6a11bce..eae46f5be4d 100644 --- a/src/plugins/qmlprofiler/tracewindow.h +++ b/src/plugins/qmlprofiler/tracewindow.h @@ -72,7 +72,7 @@ signals: void viewUpdated(); void gotoSourceLocation(const QString &fileUrl, int lineNumber); void timeChanged(qreal newTime); - void range(int type, qint64 startTime, qint64 length, const QStringList &data, const QString &fileName, int line); + void range(int type, int nestingLevel, int nestingInType, qint64 startTime, qint64 length, const QStringList &data, const QString &fileName, int line); private: QWeakPointer m_plugin;