From 87c12c4e1b762f890602ea603acc92bda1909e94 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 23 Mar 2018 10:56:31 +0100 Subject: [PATCH] QmlProfiler: Store statistics data in a saner way We don't need to retain the durations after finalize(), and we can use vectors instead of hashes, as we will cover almost all types anyway. Vectors allow us to potentially use the position of an item in the data as its row. Change-Id: I2c09406f0e0a42f5f517f8444755b1664efb8f3b Reviewed-by: Ulf Hermann --- .../qmlprofilerstatisticsmodel.cpp | 92 +++++++++---------- .../qmlprofiler/qmlprofilerstatisticsmodel.h | 66 ++++++++++--- .../qmlprofiler/qmlprofilerstatisticsview.cpp | 62 +++++++------ .../qmlprofiler/qmlprofilerstatisticsview.h | 3 +- 4 files changed, 134 insertions(+), 89 deletions(-) diff --git a/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.cpp b/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.cpp index f4b9c931fae..a28647cbd0f 100644 --- a/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.cpp @@ -52,16 +52,16 @@ QString QmlProfilerStatisticsModel::nameForType(RangeType typeNumber) double QmlProfilerStatisticsModel::durationPercent(int typeId) const { - const QmlEventStats &global = m_data[-1]; - const QmlEventStats &stats = m_data[typeId]; - return double(stats.duration - stats.durationRecursive) / double(global.duration) * 100l; + return (typeId >= 0) + ? double(m_data[typeId].totalNonRecursive()) / double(m_rootDuration) * 100 + : 100; } double QmlProfilerStatisticsModel::durationSelfPercent(int typeId) const { - const QmlEventStats &global = m_data[-1]; - const QmlEventStats &stats = m_data[typeId]; - return double(stats.durationSelf) / double(global.duration) * 100l; + return (typeId >= 0) + ? (double(m_data[typeId].self) / double(m_rootDuration) * 100) + : 0; } QmlProfilerStatisticsModel::QmlProfilerStatisticsModel(QmlProfilerModelManager *modelManager) @@ -122,7 +122,8 @@ bool QmlProfilerStatisticsModel::isRestrictedToRange() const return m_modelManager->isRestrictedToRange(); } -const QHash &QmlProfilerStatisticsModel::getData() const +const QVector & +QmlProfilerStatisticsModel::getData() const { return m_data; } @@ -193,11 +194,11 @@ QString QmlProfilerStatisticsModel::summary(const QVector &typeIds) const void QmlProfilerStatisticsModel::clear() { + m_rootDuration = 0; m_data.clear(); m_notes.clear(); m_callStack.clear(); m_compileStack.clear(); - m_durations.clear(); if (!m_calleesModel.isNull()) m_calleesModel->clear(); if (!m_callersModel.isNull()) @@ -256,42 +257,40 @@ void QmlProfilerStatisticsModel::loadEvent(const QmlEvent &event, const QmlEvent if (!m_acceptedTypes.contains(type.rangeType())) return; + const int typeIndex = event.typeIndex(); bool isRecursive = false; QStack &stack = type.rangeType() == Compiling ? m_compileStack : m_callStack; switch (event.rangeStage()) { case RangeStart: stack.push(event); + if (m_data.length() <= typeIndex) + m_data.resize(m_modelManager->numLoadedEventTypes()); break; case RangeEnd: { // update stats QTC_ASSERT(!stack.isEmpty(), return); - QTC_ASSERT(stack.top().typeIndex() == event.typeIndex(), return); - QmlEventStats *stats = &m_data[event.typeIndex()]; + QTC_ASSERT(stack.top().typeIndex() == typeIndex, return); + QmlEventStats &stats = m_data[typeIndex]; qint64 duration = event.timestamp() - stack.top().timestamp(); - stats->duration += duration; - stats->durationSelf += duration; - if (duration < stats->minTime) - stats->minTime = duration; - if (duration > stats->maxTime) - stats->maxTime = duration; - stats->calls++; - // for median computing - m_durations[event.typeIndex()].append(duration); + stats.total += duration; + stats.self += duration; + stats.durations.push_back(duration); stack.pop(); // recursion detection: check whether event was already in stack for (int ii = 0; ii < stack.size(); ++ii) { - if (stack.at(ii).typeIndex() == event.typeIndex()) { + if (stack.at(ii).typeIndex() == typeIndex) { isRecursive = true; - stats->durationRecursive += duration; + stats.recursive += duration; break; } } if (!stack.isEmpty()) - m_data[stack.top().typeIndex()].durationSelf -= duration; + m_data[stack.top().typeIndex()].self -= duration; else - m_data[-1].duration += duration; + m_rootDuration += duration; + break; } default: @@ -307,20 +306,8 @@ void QmlProfilerStatisticsModel::loadEvent(const QmlEvent &event, const QmlEvent void QmlProfilerStatisticsModel::finalize() { - // post-process: calc mean time, median time, percentoftime - for (QHash::iterator it = m_data.begin(); it != m_data.end(); ++it) { - QVector eventDurations = m_durations[it.key()]; - if (!eventDurations.isEmpty()) { - Utils::sort(eventDurations); - it->medianTime = eventDurations.at(eventDurations.count()/2); - } - } - - // insert root event - QmlEventStats &rootEvent = m_data[-1]; - rootEvent.minTime = rootEvent.maxTime = rootEvent.medianTime = rootEvent.duration; - rootEvent.durationSelf = 0; - rootEvent.calls = 1; + for (QmlEventStats &stats : m_data) + stats.finalize(); emit dataAvailable(); } @@ -346,15 +333,15 @@ QmlProfilerStatisticsRelativesModel::QmlProfilerStatisticsRelativesModel( this, &QmlProfilerStatisticsRelativesModel::dataAvailable); } -const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesMap & +const QVector & QmlProfilerStatisticsRelativesModel::getData(int typeId) const { - QHash ::ConstIterator it = m_data.find(typeId); + auto it = m_data.find(typeId); if (it != m_data.end()) { return it.value(); } else { - static const QmlStatisticsRelativesMap emptyMap; - return emptyMap; + static const QVector emptyVector; + return emptyVector; } } @@ -363,6 +350,18 @@ const QVector &QmlProfilerStatisticsRelativesModel::getTypes() con return m_modelManager->eventTypes(); } +bool operator<(const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesData &a, + const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesData &b) +{ + return a.typeIndex < b.typeIndex; +} + +bool operator<(const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesData &a, + int typeIndex) +{ + return a.typeIndex < typeIndex; +} + void QmlProfilerStatisticsRelativesModel::loadEvent(RangeType type, const QmlEvent &event, bool isRecursive) { @@ -379,15 +378,16 @@ void QmlProfilerStatisticsRelativesModel::loadEvent(RangeType type, const QmlEve int selfTypeIndex = (m_relation == QmlProfilerStatisticsCallers) ? event.typeIndex() : callerTypeIndex; - QmlStatisticsRelativesMap &relativesMap = m_data[selfTypeIndex]; - QmlStatisticsRelativesMap::Iterator it = relativesMap.find(relativeTypeIndex); - if (it != relativesMap.end()) { + QVector &relatives = m_data[selfTypeIndex]; + auto it = std::lower_bound(relatives.begin(), relatives.end(), relativeTypeIndex); + if (it != relatives.end() && it->typeIndex == relativeTypeIndex) { it->calls++; it->duration += event.timestamp() - stack.top().startTime; it->isRecursive = isRecursive || it->isRecursive; } else { - relativesMap.insert(relativeTypeIndex, QmlStatisticsRelativesData( - event.timestamp() - stack.top().startTime, 1, isRecursive)); + relatives.insert(it, QmlStatisticsRelativesData( + event.timestamp() - stack.top().startTime, 1, relativeTypeIndex, + isRecursive)); } stack.pop(); break; diff --git a/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.h b/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.h index 805df505482..673bf23372b 100644 --- a/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.h +++ b/src/plugins/qmlprofiler/qmlprofilerstatisticsmodel.h @@ -30,6 +30,8 @@ #include "qmleventlocation.h" #include "qmlprofilerconstants.h" +#include + #include #include #include @@ -52,13 +54,46 @@ public: static QString nameForType(RangeType typeNumber); struct QmlEventStats { - qint64 duration = 0; - qint64 durationSelf = 0; - qint64 durationRecursive = 0; + std::vector durations; + qint64 total = 0; + qint64 self = 0; + qint64 recursive = 0; + qint64 minimum = 0; + qint64 maximum = 0; + qint64 median = 0; qint64 calls = 0; - qint64 minTime = std::numeric_limits::max(); - qint64 maxTime = 0; - qint64 medianTime = 0; + + void finalize() + { + static const qint64 qint64Max = std::numeric_limits::max(); + size_t size = durations.size(); + QTC_ASSERT(size <= qint64Max, size = qint64Max); + calls = static_cast(size); + + if (size == 0) + return; + + std::sort(durations.begin(), durations.end()); + const auto avg + = [](const qint64 a, const qint64 b) { return a / 2ll + b / 2ll + ((a & 1) + (b & 1)) / 2ll; }; + + const size_t half = size / 2; + median = (size & 1) == 0 ? avg(durations[half - 1], durations[half]) : durations[half]; + minimum = durations.front(); + maximum = durations.back(); + + durations.clear(); + } + + qint64 average() const + { + return calls == 0 ? 0 : total / calls; + } + + qint64 totalNonRecursive() const + { + return total - recursive; + } }; double durationPercent(int typeId) const; @@ -72,9 +107,10 @@ public: QStringList details(int typeIndex) const; QString summary(const QVector &typeIds) const; - const QHash &getData() const; + const QVector &getData() const; const QVector &getTypes() const; const QHash &getNotes() const; + qint64 rootDuration() const { return m_rootDuration; } int count() const; void clear(); @@ -93,7 +129,7 @@ private: void dataChanged(); void notesChanged(int typeIndex); - QHash m_data; + QVector m_data; QPointer m_calleesModel; QPointer m_callersModel; @@ -104,7 +140,8 @@ private: QStack m_callStack; QStack m_compileStack; - QHash> m_durations; + + qint64 m_rootDuration = 0; }; class QmlProfilerStatisticsRelativesModel : public QObject @@ -113,13 +150,14 @@ class QmlProfilerStatisticsRelativesModel : public QObject public: struct QmlStatisticsRelativesData { - QmlStatisticsRelativesData(qint64 duration = 0, qint64 calls = 0, bool isRecursive = false) - : duration(duration), calls(calls), isRecursive(isRecursive) {} + QmlStatisticsRelativesData(qint64 duration = 0, qint64 calls = 0, int typeIndex = -1, + bool isRecursive = false) + : duration(duration), calls(calls), typeIndex(typeIndex), isRecursive(isRecursive) {} qint64 duration; qint64 calls; + int typeIndex; bool isRecursive; }; - typedef QHash QmlStatisticsRelativesMap; QmlProfilerStatisticsRelativesModel(QmlProfilerModelManager *modelManager, QmlProfilerStatisticsModel *statisticsModel, @@ -128,7 +166,7 @@ public: int count() const; void clear(); - const QmlStatisticsRelativesMap &getData(int typeId) const; + const QVector &getData(int typeId) const; const QVector &getTypes() const; void loadEvent(RangeType type, const QmlEvent &event, bool isRecursive); @@ -139,7 +177,7 @@ signals: void dataAvailable(); protected: - QHash m_data; + QHash> m_data; QPointer m_modelManager; struct Frame { diff --git a/src/plugins/qmlprofiler/qmlprofilerstatisticsview.cpp b/src/plugins/qmlprofiler/qmlprofilerstatisticsview.cpp index 984c125c545..24cb91c58fe 100644 --- a/src/plugins/qmlprofiler/qmlprofilerstatisticsview.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerstatisticsview.cpp @@ -418,13 +418,13 @@ void QmlProfilerStatisticsMainView::buildModel() void QmlProfilerStatisticsMainView::updateNotes(int typeIndex) { - const QHash &eventList = m_model->getData(); + const QVector &eventList = m_model->getData(); const QHash ¬eList = m_model->getNotes(); QStandardItem *rootItem = m_standardItemModel->invisibleRootItem(); for (int rowIndex = 0; rowIndex < rootItem->rowCount(); ++rowIndex) { int rowType = rootItem->child(rowIndex)->data(TypeIdRole).toInt(); - if (rowType != typeIndex && typeIndex != -1) + if (rowType == -1 || (rowType != typeIndex && typeIndex != -1)) continue; const QmlProfilerStatisticsModel::QmlEventStats &stats = eventList[rowType]; @@ -434,10 +434,10 @@ void QmlProfilerStatisticsMainView::updateNotes(int typeIndex) if (it != noteList.end()) { item->setBackground(colors()->noteBackground); item->setToolTip(it.value()); - } else if (stats.durationRecursive > 0) { + } else if (stats.recursive > 0) { item->setBackground(colors()->noteBackground); item->setToolTip(tr("+%1 in recursive calls") - .arg(Timeline::formatTime(stats.durationRecursive))); + .arg(Timeline::formatTime(stats.recursive))); } else if (!item->toolTip().isEmpty()){ item->setBackground(colors()->defaultBackground); item->setToolTip(QString()); @@ -468,14 +468,21 @@ QStringList QmlProfilerStatisticsMainView::details(int typeId) const void QmlProfilerStatisticsMainView::parseModel() { - const QHash &eventList = m_model->getData(); + const QVector &eventList = m_model->getData(); const QVector &typeList = m_model->getTypes(); - QHash::ConstIterator it; - for (it = eventList.constBegin(); it != eventList.constEnd(); ++it) { - int typeIndex = it.key(); - const QmlProfilerStatisticsModel::QmlEventStats &stats = it.value(); - const QmlEventType &type = (typeIndex != -1 ? typeList[typeIndex] : *rootEventType()); + QmlProfilerStatisticsModel::QmlEventStats rootEventStats; + rootEventStats.total = rootEventStats.maximum = rootEventStats.minimum = rootEventStats.median + = m_model->rootDuration(); + rootEventStats.calls = rootEventStats.total > 0 ? 1 : 0; + + for (int typeIndex = -1; typeIndex < eventList.size(); ++typeIndex) { + const QmlProfilerStatisticsModel::QmlEventStats &stats = typeIndex >= 0 + ? eventList[typeIndex] : rootEventStats; + if (stats.calls == 0) + continue; + + const QmlEventType &type = typeIndex >= 0 ? typeList[typeIndex] : *rootEventType(); QStandardItem *rootItem = m_standardItemModel->invisibleRootItem(); QList newRow; @@ -491,30 +498,30 @@ void QmlProfilerStatisticsMainView::parseModel() + QLatin1String(" %"), percent); newRow << new StatisticsViewItem( - Timeline::formatTime(stats.duration - stats.durationRecursive), - stats.duration - stats.durationRecursive); + Timeline::formatTime(stats.totalNonRecursive()), + stats.totalNonRecursive()); const double percentSelf = m_model->durationSelfPercent(typeIndex); newRow << new StatisticsViewItem(QString::number(percentSelf, 'f', 2) + QLatin1String(" %"), percentSelf); - newRow << new StatisticsViewItem(Timeline::formatTime(stats.durationSelf), - stats.durationSelf); + newRow << new StatisticsViewItem(Timeline::formatTime(stats.self), + stats.self); newRow << new StatisticsViewItem(QString::number(stats.calls), stats.calls); - const qint64 timePerCall = stats.calls > 0 ? stats.duration / stats.calls : 0; + const qint64 timePerCall = stats.average(); newRow << new StatisticsViewItem(Timeline::formatTime(timePerCall), timePerCall); - newRow << new StatisticsViewItem(Timeline::formatTime(stats.medianTime), - stats.medianTime); + newRow << new StatisticsViewItem(Timeline::formatTime(stats.median), + stats.median); - newRow << new StatisticsViewItem(Timeline::formatTime(stats.maxTime), - stats.maxTime); + newRow << new StatisticsViewItem(Timeline::formatTime(stats.maximum), + stats.maximum); - newRow << new StatisticsViewItem(Timeline::formatTime(stats.minTime), - stats.minTime); + newRow << new StatisticsViewItem(Timeline::formatTime(stats.minimum), + stats.minimum); newRow << new StatisticsViewItem(type.data().isEmpty() ? tr("Source code not available") : type.data(), type.data()); @@ -682,7 +689,7 @@ void QmlProfilerStatisticsRelativesView::displayType(int typeIndex) } void QmlProfilerStatisticsRelativesView::rebuildTree( - const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesMap &map) + const QVector &data) { Q_ASSERT(treeModel()); treeModel()->clear(); @@ -690,11 +697,10 @@ void QmlProfilerStatisticsRelativesView::rebuildTree( QStandardItem *topLevelItem = treeModel()->invisibleRootItem(); const QVector &typeList = m_model->getTypes(); - QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesMap::const_iterator it; - for (it = map.constBegin(); it != map.constEnd(); ++it) { - const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesData &stats = it.value(); - int typeIndex = it.key(); - const QmlEventType &type = (typeIndex != -1 ? typeList[typeIndex] : *rootEventType()); + for (auto it = data.constBegin(); it != data.constEnd(); ++it) { + const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesData &stats = *it; + const QmlEventType &type = stats.typeIndex != -1 ? typeList[stats.typeIndex] + : *rootEventType(); QList newRow; // ToDo: here we were going to search for the data in the other model @@ -712,7 +718,7 @@ void QmlProfilerStatisticsRelativesView::rebuildTree( type.data(), type.data()); QStandardItem *first = newRow.at(RelativeLocation); - first->setData(typeIndex, TypeIdRole); + first->setData(stats.typeIndex, TypeIdRole); const QmlEventLocation location(type.location()); first->setData(location.filename(), FilenameRole); first->setData(location.line(), LineRole); diff --git a/src/plugins/qmlprofiler/qmlprofilerstatisticsview.h b/src/plugins/qmlprofiler/qmlprofilerstatisticsview.h index 351147d0e78..3123c84ec0c 100644 --- a/src/plugins/qmlprofiler/qmlprofilerstatisticsview.h +++ b/src/plugins/qmlprofiler/qmlprofilerstatisticsview.h @@ -166,7 +166,8 @@ signals: void gotoSourceLocation(const QString &fileName, int lineNumber, int columnNumber); private: - void rebuildTree(const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesMap &map); + void rebuildTree( + const QVector &data); void updateHeader(); QStandardItemModel *treeModel();