/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://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 http://www.qt.io/terms-conditions. For further information ** use the contact form at http://www.qt.io/contact-us. ** ** 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 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "qv8profilerdatamodel.h" #include "qmlprofilermodelmanager.h" #include "qmlprofilerdetailsrewriter.h" #include "qmlprofilerbasemodel_p.h" #include #include QT_BEGIN_NAMESPACE Q_DECLARE_TYPEINFO(QmlProfiler::QV8ProfilerDataModel::QV8EventData, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(QmlProfiler::QV8ProfilerDataModel::QV8EventSub, Q_MOVABLE_TYPE); QT_END_NAMESPACE namespace QmlProfiler { typedef QHash EventHash; static EventHash cloneEventHash(const EventHash &src) { EventHash result; const EventHash::ConstIterator cend = src.constEnd(); for (EventHash::ConstIterator it = src.constBegin(); it != cend; ++it) result.insert(it.key(), new QV8ProfilerDataModel::QV8EventSub(it.value())); return result; } QV8ProfilerDataModel::QV8EventData &QV8ProfilerDataModel::QV8EventData::operator=(const QV8EventData &ref) { if (this == &ref) return *this; displayName = ref.displayName; eventHashStr = ref.eventHashStr; filename = ref.filename; functionName = ref.functionName; line = ref.line; totalTime = ref.totalTime; totalPercent = ref.totalPercent; selfTime = ref.selfTime; SelfTimeInPercent = ref.SelfTimeInPercent; typeId = ref.typeId; qDeleteAll(parentHash); parentHash = cloneEventHash(ref.parentHash); qDeleteAll(childrenHash); childrenHash = cloneEventHash(ref.childrenHash); return *this; } QV8ProfilerDataModel::QV8EventData::QV8EventData() { line = -1; typeId = -1; totalTime = 0; selfTime = 0; totalPercent = 0; SelfTimeInPercent = 0; } QV8ProfilerDataModel::QV8EventData::~QV8EventData() { qDeleteAll(parentHash.values()); parentHash.clear(); qDeleteAll(childrenHash.values()); childrenHash.clear(); } class QV8ProfilerDataModel::QV8ProfilerDataModelPrivate : public QmlProfilerBaseModel::QmlProfilerBaseModelPrivate { public: QV8ProfilerDataModelPrivate(QV8ProfilerDataModel *qq) : QmlProfilerBaseModel::QmlProfilerBaseModelPrivate(qq) {} QHash v8EventHash; QList pendingRewrites; QHash v8parents; QV8EventData v8RootEvent; qint64 v8MeasuredTime; private: Q_DECLARE_PUBLIC(QV8ProfilerDataModel) }; QV8ProfilerDataModel::QV8ProfilerDataModel(Utils::FileInProjectFinder *fileFinder, QmlProfilerModelManager *parent) : QmlProfilerBaseModel(fileFinder, parent, new QV8ProfilerDataModelPrivate(this)) { Q_D(QV8ProfilerDataModel); d->v8MeasuredTime = 0; clearV8RootEvent(); } void QV8ProfilerDataModel::clear() { Q_D(QV8ProfilerDataModel); qDeleteAll(d->v8EventHash.values()); d->v8EventHash.clear(); d->v8parents.clear(); clearV8RootEvent(); d->v8MeasuredTime = 0; d->pendingRewrites.clear(); QmlProfilerBaseModel::clear(); } bool QV8ProfilerDataModel::isEmpty() const { Q_D(const QV8ProfilerDataModel); return d->v8EventHash.isEmpty(); } QV8ProfilerDataModel::QV8EventData *QV8ProfilerDataModel::v8EventDescription(int typeId) const { Q_D(const QV8ProfilerDataModel); foreach (QV8EventData *event, d->v8EventHash) { if (event->typeId == typeId) return event; } return 0; } qint64 QV8ProfilerDataModel::v8MeasuredTime() const { Q_D(const QV8ProfilerDataModel); return d->v8MeasuredTime; } QList QV8ProfilerDataModel::getV8Events() const { Q_D(const QV8ProfilerDataModel); return d->v8EventHash.values(); } QString getHashStringForV8Event(const QString &displayName, const QString &function) { return QString::fromLatin1("%1:%2").arg(displayName, function); } void QV8ProfilerDataModel::addV8Event(int depth, const QString &function, const QString &filename, int lineNumber, double totalTime, double selfTime) { Q_D(QV8ProfilerDataModel); QString displayName = filename.mid(filename.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char(':') + QString::number(lineNumber); QString hashStr = getHashStringForV8Event(displayName, function); // time is given in milliseconds, but internally we store it in microseconds totalTime *= 1e6; selfTime *= 1e6; // accumulate information QV8EventData *eventData = d->v8EventHash[hashStr]; if (!eventData) { eventData = new QV8EventData; eventData->displayName = displayName; eventData->eventHashStr = hashStr; eventData->filename = filename; eventData->functionName = function; eventData->line = lineNumber; eventData->totalTime = totalTime; eventData->selfTime = selfTime; d->v8EventHash[hashStr] = eventData; } else { eventData->totalTime += totalTime; eventData->selfTime += selfTime; } d->v8parents[depth] = eventData; QV8EventData *parentEvent = 0; if (depth == 0) { parentEvent = &d->v8RootEvent; d->v8MeasuredTime += totalTime; } if (depth > 0 && d->v8parents.contains(depth-1)) parentEvent = d->v8parents.value(depth-1); if (parentEvent != 0) { if (!eventData->parentHash.contains(parentEvent->eventHashStr)) { QV8EventSub *newParentSub = new QV8EventSub(parentEvent); newParentSub->totalTime = totalTime; eventData->parentHash.insert(parentEvent->eventHashStr, newParentSub); } else { QV8EventSub *newParentSub = eventData->parentHash.value(parentEvent->eventHashStr); newParentSub->totalTime += totalTime; } if (!parentEvent->childrenHash.contains(eventData->eventHashStr)) { QV8EventSub *newChildSub = new QV8EventSub(eventData); newChildSub->totalTime = totalTime; parentEvent->childrenHash.insert(eventData->eventHashStr, newChildSub); } else { QV8EventSub *newChildSub = parentEvent->childrenHash.value(eventData->eventHashStr); newChildSub->totalTime += totalTime; } } } void QV8ProfilerDataModel::detailsChanged(int requestId, const QString &newString) { Q_D(QV8ProfilerDataModel); QTC_ASSERT(requestId < d->pendingRewrites.count(), return); d->pendingRewrites[requestId]->filename = newString; } void QV8ProfilerDataModel::detailsDone() { Q_D(QV8ProfilerDataModel); d->pendingRewrites.clear(); QmlProfilerBaseModel::detailsDone(); } void QV8ProfilerDataModel::complete() { Q_D(QV8ProfilerDataModel); if (!d->v8EventHash.isEmpty()) { double totalTimes = d->v8MeasuredTime; double selfTimes = 0; foreach (const QV8EventData *v8event, d->v8EventHash) { selfTimes += v8event->selfTime; } // prevent divisions by 0 if (totalTimes == 0) totalTimes = 1; if (selfTimes == 0) selfTimes = 1; // insert root event in eventlist // the +1 ns is to get it on top of the sorted list d->v8RootEvent.totalTime = d->v8MeasuredTime + 1; d->v8RootEvent.selfTime = 0; QString rootEventHash = getHashStringForV8Event( tr(""), tr("Main Program")); QV8EventData *v8RootEventPointer = d->v8EventHash[rootEventHash]; if (v8RootEventPointer) { d->v8RootEvent = *v8RootEventPointer; } else { d->v8EventHash[rootEventHash] = new QV8EventData; *(d->v8EventHash[rootEventHash]) = d->v8RootEvent; } foreach (QV8EventData *v8event, d->v8EventHash) { v8event->totalPercent = v8event->totalTime * 100.0 / totalTimes; v8event->SelfTimeInPercent = v8event->selfTime * 100.0 / selfTimes; } int index = d->pendingRewrites.size(); foreach (QV8EventData *v8event, d->v8EventHash.values()) { v8event->typeId = index++; d->pendingRewrites << v8event; d->detailsRewriter->requestDetailsForLocation(index, QmlDebug::QmlEventLocation(v8event->filename, v8event->line, 1)); } d->v8RootEvent.typeId = d->v8EventHash[rootEventHash]->typeId; } else { // On empty data, still add a fake root event clearV8RootEvent(); } QmlProfilerBaseModel::complete(); } void QV8ProfilerDataModel::clearV8RootEvent() { Q_D(QV8ProfilerDataModel); d->v8RootEvent.displayName = tr(""); d->v8RootEvent.eventHashStr = tr(""); d->v8RootEvent.functionName = tr("Main Program"); d->v8RootEvent.line = -1; d->v8RootEvent.totalTime = 0; d->v8RootEvent.totalPercent = 0; d->v8RootEvent.selfTime = 0; d->v8RootEvent.SelfTimeInPercent = 0; d->v8RootEvent.typeId = -1; qDeleteAll(d->v8RootEvent.parentHash.values()); qDeleteAll(d->v8RootEvent.childrenHash.values()); d->v8RootEvent.parentHash.clear(); d->v8RootEvent.childrenHash.clear(); } void QV8ProfilerDataModel::save(QXmlStreamWriter &stream) { Q_D(QV8ProfilerDataModel); stream.writeStartElement(QLatin1String("v8profile")); // v8 profiler output stream.writeAttribute(QLatin1String("totalTime"), QString::number(d->v8MeasuredTime)); foreach (const QV8EventData *v8event, d->v8EventHash) { stream.writeStartElement(QLatin1String("event")); stream.writeAttribute(QLatin1String("index"), QString::number( d->v8EventHash.keys().indexOf( v8event->eventHashStr))); stream.writeTextElement(QLatin1String("displayname"), v8event->displayName); stream.writeTextElement(QLatin1String("functionname"), v8event->functionName); if (!v8event->filename.isEmpty()) { stream.writeTextElement(QLatin1String("filename"), v8event->filename); stream.writeTextElement(QLatin1String("line"), QString::number(v8event->line)); } stream.writeTextElement(QLatin1String("totalTime"), QString::number(v8event->totalTime)); stream.writeTextElement(QLatin1String("selfTime"), QString::number(v8event->selfTime)); if (!v8event->childrenHash.isEmpty()) { stream.writeStartElement(QLatin1String("childrenEvents")); QStringList childrenIndexes; QStringList childrenTimes; QStringList parentTimes; foreach (const QV8EventSub *v8child, v8event->childrenHash) { childrenIndexes << QString::number(v8child->reference->typeId); childrenTimes << QString::number(v8child->totalTime); parentTimes << QString::number(v8child->totalTime); } stream.writeAttribute(QLatin1String("list"), childrenIndexes.join(QLatin1String(", "))); stream.writeAttribute(QLatin1String("childrenTimes"), childrenTimes.join(QLatin1String(", "))); stream.writeAttribute(QLatin1String("parentTimes"), parentTimes.join(QLatin1String(", "))); stream.writeEndElement(); } stream.writeEndElement(); } stream.writeEndElement(); // v8 profiler output } void QV8ProfilerDataModel::load(QXmlStreamReader &stream) { Q_D(QV8ProfilerDataModel); QHash v8eventBuffer; QHash childrenIndexes; QHash childrenTimes; QHash parentTimes; QV8EventData *v8event = 0; // time computation d->v8MeasuredTime = 0; double cumulatedV8Time = 0; // get the v8 time QXmlStreamAttributes attributes = stream.attributes(); if (attributes.hasAttribute(QLatin1String("totalTime"))) d->v8MeasuredTime = attributes.value(QLatin1String("totalTime")).toString().toDouble(); bool finishedReading = false; while (!stream.atEnd() && !stream.hasError() && !finishedReading) { QXmlStreamReader::TokenType token = stream.readNext(); const QStringRef elementName = stream.name(); switch (token) { case QXmlStreamReader::StartDocument : continue; case QXmlStreamReader::StartElement : { if (elementName == QLatin1String("event")) { QXmlStreamAttributes attributes = stream.attributes(); if (attributes.hasAttribute(QLatin1String("index"))) { int ndx = attributes.value(QLatin1String("index")).toString().toInt(); if (!v8eventBuffer.value(ndx)) v8eventBuffer[ndx] = new QV8EventData; v8event = v8eventBuffer[ndx]; } else { v8event = 0; } break; } if (!v8event) break; if (elementName == QLatin1String("childrenEvents")) { QXmlStreamAttributes attributes = stream.attributes(); int eventIndex = v8eventBuffer.key(v8event); if (attributes.hasAttribute(QLatin1String("list"))) { // store for later parsing (we haven't read all the events yet) childrenIndexes[eventIndex] = attributes.value(QLatin1String("list")).toString(); } if (attributes.hasAttribute(QLatin1String("childrenTimes"))) { childrenTimes[eventIndex] = attributes.value(QLatin1String("childrenTimes")).toString(); } if (attributes.hasAttribute(QLatin1String("parentTimes"))) parentTimes[eventIndex] = attributes.value(QLatin1String("parentTimes")).toString(); } stream.readNext(); if (stream.tokenType() != QXmlStreamReader::Characters) break; QString readData = stream.text().toString(); if (elementName == QLatin1String("displayname")) { v8event->displayName = readData; break; } if (elementName == QLatin1String("functionname")) { v8event->functionName = readData; break; } if (elementName == QLatin1String("filename")) { v8event->filename = readData; break; } if (elementName == QLatin1String("line")) { v8event->line = readData.toInt(); break; } if (elementName == QLatin1String("totalTime")) { v8event->totalTime = readData.toDouble(); cumulatedV8Time += v8event->totalTime; break; } if (elementName == QLatin1String("selfTime")) { v8event->selfTime = readData.toDouble(); break; } break; } case QXmlStreamReader::EndElement : { if (elementName == QLatin1String("v8profile")) { finishedReading = true; // done reading the v8 profile data break; } } default: break; } } // backwards compatibility if (d->v8MeasuredTime == 0) d->v8MeasuredTime = cumulatedV8Time; // find v8events' children and parents typedef QHash ::ConstIterator ChildIndexConstIt; const ChildIndexConstIt icend = childrenIndexes.constEnd(); for (ChildIndexConstIt it = childrenIndexes.constBegin(); it != icend; ++it) { const int parentIndex = it.key(); const QStringList childrenStrings = it.value().split(QLatin1Char(',')); QStringList childrenTimesStrings = childrenTimes.value(parentIndex).split(QLatin1String(", ")); QStringList parentTimesStrings = parentTimes.value(parentIndex).split(QLatin1String(", ")); for (int ndx = 0; ndx < childrenStrings.count(); ndx++) { int childIndex = childrenStrings[ndx].toInt(); if (v8eventBuffer.value(childIndex)) { QV8EventSub *newChild = new QV8EventSub(v8eventBuffer[childIndex]); QV8EventSub *newParent = new QV8EventSub(v8eventBuffer[parentIndex]); if (childrenTimesStrings.count() > ndx) newChild->totalTime = childrenTimesStrings[ndx].toDouble(); if (parentTimesStrings.count() > ndx) newParent->totalTime = parentTimesStrings[ndx].toDouble(); v8eventBuffer[parentIndex]->childrenHash.insert( newChild->reference->displayName, newChild); v8eventBuffer[childIndex]->parentHash.insert( newParent->reference->displayName, newParent); } } } // store v8 events foreach (QV8EventData *storedV8Event, v8eventBuffer) { storedV8Event->eventHashStr = getHashStringForV8Event( storedV8Event->displayName, storedV8Event->functionName); d->v8EventHash[storedV8Event->eventHashStr] = storedV8Event; } } } // namespace QmlProfiler