aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp')
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp426
1 files changed, 426 insertions, 0 deletions
diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp
new file mode 100644
index 0000000000..65b99ef7ca
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp
@@ -0,0 +1,426 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** 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.
+**
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlprofilerservice.h"
+#include "qv4profileradapter.h"
+#include "qqmlprofileradapter.h"
+#include "qqmlprofilerservicefactory.h"
+#include <private/qqmlengine_p.h>
+
+#include <QtCore/qdatastream.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qcoreapplication.h>
+
+QT_BEGIN_NAMESPACE
+
+QQmlProfilerServiceImpl::QQmlProfilerServiceImpl(QObject *parent) :
+ QQmlConfigurableDebugService<QQmlProfilerService>(1, parent),
+ m_waitingForStop(false)
+{
+ m_timer.start();
+}
+
+QQmlProfilerServiceImpl::~QQmlProfilerServiceImpl()
+{
+ // No need to lock here. If any engine or global profiler is still trying to register at this
+ // point we have a nasty bug anyway.
+ qDeleteAll(m_engineProfilers.values());
+ qDeleteAll(m_globalProfilers);
+}
+
+void QQmlProfilerServiceImpl::dataReady(QQmlAbstractProfilerAdapter *profiler)
+{
+ QMutexLocker lock(&m_configMutex);
+ bool dataComplete = true;
+ for (QMultiMap<qint64, QQmlAbstractProfilerAdapter *>::iterator i(m_startTimes.begin()); i != m_startTimes.end();) {
+ if (i.value() == profiler) {
+ m_startTimes.erase(i++);
+ } else {
+ if (i.key() == -1)
+ dataComplete = false;
+ ++i;
+ }
+ }
+ m_startTimes.insert(0, profiler);
+ if (dataComplete) {
+ QList<QQmlEngine *> enginesToRelease;
+ foreach (QQmlEngine *engine, m_stoppingEngines) {
+ foreach (QQmlAbstractProfilerAdapter *engineProfiler, m_engineProfilers.values(engine)) {
+ if (m_startTimes.values().contains(engineProfiler)) {
+ enginesToRelease.append(engine);
+ break;
+ }
+ }
+ }
+ sendMessages();
+ foreach (QQmlEngine *engine, enginesToRelease) {
+ m_stoppingEngines.removeOne(engine);
+ emit detachedFromEngine(engine);
+ }
+ }
+}
+
+void QQmlProfilerServiceImpl::engineAboutToBeAdded(QQmlEngine *engine)
+{
+ Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO,
+ "QML profilers have to be added from the engine thread");
+
+ QMutexLocker lock(&m_configMutex);
+ QQmlProfilerAdapter *qmlAdapter = new QQmlProfilerAdapter(this, QQmlEnginePrivate::get(engine));
+ QV4ProfilerAdapter *v4Adapter = new QV4ProfilerAdapter(this, QV8Engine::getV4(engine->handle()));
+ addEngineProfiler(qmlAdapter, engine);
+ addEngineProfiler(v4Adapter, engine);
+ QQmlConfigurableDebugService<QQmlProfilerService>::engineAboutToBeAdded(engine);
+}
+
+void QQmlProfilerServiceImpl::engineAdded(QQmlEngine *engine)
+{
+ Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO,
+ "QML profilers have to be added from the engine thread");
+
+ QMutexLocker lock(&m_configMutex);
+ foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine))
+ profiler->stopWaiting();
+}
+
+void QQmlProfilerServiceImpl::engineAboutToBeRemoved(QQmlEngine *engine)
+{
+ Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO,
+ "QML profilers have to be removed from the engine thread");
+
+ QMutexLocker lock(&m_configMutex);
+ bool isRunning = false;
+ foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) {
+ if (profiler->isRunning())
+ isRunning = true;
+ profiler->startWaiting();
+ }
+ if (isRunning) {
+ m_stoppingEngines.append(engine);
+ stopProfiling(engine);
+ } else {
+ emit detachedFromEngine(engine);
+ }
+}
+
+void QQmlProfilerServiceImpl::engineRemoved(QQmlEngine *engine)
+{
+ Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO,
+ "QML profilers have to be removed from the engine thread");
+
+ QMutexLocker lock(&m_configMutex);
+ foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) {
+ removeProfilerFromStartTimes(profiler);
+ delete profiler;
+ }
+ m_engineProfilers.remove(engine);
+}
+
+void QQmlProfilerServiceImpl::addEngineProfiler(QQmlAbstractProfilerAdapter *profiler, QQmlEngine *engine)
+{
+ profiler->moveToThread(thread());
+ profiler->synchronize(m_timer);
+ m_engineProfilers.insert(engine, profiler);
+}
+
+void QQmlProfilerServiceImpl::addGlobalProfiler(QQmlAbstractProfilerAdapter *profiler)
+{
+ QMutexLocker lock(&m_configMutex);
+ profiler->synchronize(m_timer);
+ m_globalProfilers.append(profiler);
+ // Global profiler, not connected to a specific engine.
+ // Global profilers are started whenever any engine profiler is started and stopped when
+ // all engine profilers are stopped.
+ quint64 features = 0;
+ foreach (QQmlAbstractProfilerAdapter *engineProfiler, m_engineProfilers)
+ features |= engineProfiler->features();
+
+ if (features != 0)
+ profiler->startProfiling(features);
+}
+
+void QQmlProfilerServiceImpl::removeGlobalProfiler(QQmlAbstractProfilerAdapter *profiler)
+{
+ QMutexLocker lock(&m_configMutex);
+ removeProfilerFromStartTimes(profiler);
+ m_globalProfilers.removeOne(profiler);
+ delete profiler;
+}
+
+void QQmlProfilerServiceImpl::removeProfilerFromStartTimes(const QQmlAbstractProfilerAdapter *profiler)
+{
+ for (QMultiMap<qint64, QQmlAbstractProfilerAdapter *>::iterator i(m_startTimes.begin());
+ i != m_startTimes.end();) {
+ if (i.value() == profiler) {
+ m_startTimes.erase(i++);
+ break;
+ } else {
+ ++i;
+ }
+ }
+}
+
+/*!
+ * Start profiling the given \a engine. If \a engine is 0, start all engine profilers that aren't
+ * currently running.
+ *
+ * If any engine profiler is started like that also start all global profilers.
+ */
+void QQmlProfilerServiceImpl::startProfiling(QQmlEngine *engine, quint64 features)
+{
+ QMutexLocker lock(&m_configMutex);
+
+ QByteArray message;
+ QQmlDebugStream d(&message, QIODevice::WriteOnly);
+
+ d << m_timer.nsecsElapsed() << (int)Event << (int)StartTrace;
+ bool startedAny = false;
+ if (engine != 0) {
+ foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) {
+ if (!profiler->isRunning()) {
+ profiler->startProfiling(features);
+ startedAny = true;
+ }
+ }
+ if (startedAny)
+ d << idForObject(engine);
+ } else {
+ QSet<QQmlEngine *> engines;
+ for (QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin());
+ i != m_engineProfilers.end(); ++i) {
+ if (!i.value()->isRunning()) {
+ engines << i.key();
+ i.value()->startProfiling(features);
+ startedAny = true;
+ }
+ }
+ foreach (QQmlEngine *profiledEngine, engines)
+ d << idForObject(profiledEngine);
+ }
+
+ if (startedAny) {
+ foreach (QQmlAbstractProfilerAdapter *profiler, m_globalProfilers) {
+ if (!profiler->isRunning())
+ profiler->startProfiling(features);
+ }
+
+ emit startFlushTimer();
+ }
+
+ emit messageToClient(name(), message);
+}
+
+/*!
+ * Stop profiling the given \a engine. If \a engine is 0, stop all currently running engine
+ * profilers.
+ *
+ * If afterwards no more engine profilers are running, also stop all global profilers. Otherwise
+ * only make them report their data.
+ */
+void QQmlProfilerServiceImpl::stopProfiling(QQmlEngine *engine)
+{
+ QMutexLocker lock(&m_configMutex);
+ QList<QQmlAbstractProfilerAdapter *> stopping;
+ QList<QQmlAbstractProfilerAdapter *> reporting;
+
+ bool stillRunning = false;
+ for (QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin());
+ i != m_engineProfilers.end(); ++i) {
+ if (i.value()->isRunning()) {
+ if (engine == 0 || i.key() == engine) {
+ m_startTimes.insert(-1, i.value());
+ stopping << i.value();
+ } else {
+ stillRunning = true;
+ }
+ }
+ }
+
+ if (stopping.isEmpty())
+ return;
+
+ foreach (QQmlAbstractProfilerAdapter *profiler, m_globalProfilers) {
+ if (!profiler->isRunning())
+ continue;
+ m_startTimes.insert(-1, profiler);
+ if (stillRunning) {
+ reporting << profiler;
+ } else {
+ stopping << profiler;
+ }
+ }
+
+ emit stopFlushTimer();
+ m_waitingForStop = true;
+
+ foreach (QQmlAbstractProfilerAdapter *profiler, reporting)
+ profiler->reportData();
+
+ foreach (QQmlAbstractProfilerAdapter *profiler, stopping)
+ profiler->stopProfiling();
+}
+
+/*
+ Send the queued up messages.
+*/
+void QQmlProfilerServiceImpl::sendMessages()
+{
+ QList<QByteArray> messages;
+
+ QByteArray data;
+
+ if (m_waitingForStop) {
+ QQmlDebugStream traceEnd(&data, QIODevice::WriteOnly);
+ traceEnd << m_timer.nsecsElapsed() << (int)Event << (int)EndTrace;
+
+ QSet<QQmlEngine *> seen;
+ foreach (QQmlAbstractProfilerAdapter *profiler, m_startTimes) {
+ for (QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin());
+ i != m_engineProfilers.end(); ++i) {
+ if (i.value() == profiler && !seen.contains(i.key())) {
+ seen << i.key();
+ traceEnd << idForObject(i.key());
+ }
+ }
+ }
+ }
+
+ while (!m_startTimes.empty()) {
+ QQmlAbstractProfilerAdapter *first = m_startTimes.begin().value();
+ m_startTimes.erase(m_startTimes.begin());
+ if (!m_startTimes.empty()) {
+ qint64 next = first->sendMessages(m_startTimes.begin().key(), messages);
+ if (next != -1)
+ m_startTimes.insert(next, first);
+ } else {
+ first->sendMessages(std::numeric_limits<qint64>::max(), messages);
+ }
+ }
+
+ if (m_waitingForStop) {
+ //indicate completion
+ messages << data;
+ data.clear();
+
+ QQmlDebugStream ds(&data, QIODevice::WriteOnly);
+ ds << (qint64)-1 << (int)Complete;
+ messages << data;
+ m_waitingForStop = false;
+ }
+
+ emit messagesToClient(name(), messages);
+
+ // Restart flushing if any profilers are still running
+ foreach (const QQmlAbstractProfilerAdapter *profiler, m_engineProfilers) {
+ if (profiler->isRunning()) {
+ emit startFlushTimer();
+ break;
+ }
+ }
+}
+
+void QQmlProfilerServiceImpl::stateAboutToBeChanged(QQmlDebugService::State newState)
+{
+ QMutexLocker lock(&m_configMutex);
+
+ if (state() == newState)
+ return;
+
+ // Stop all profiling and send the data before we get disabled.
+ if (newState != Enabled) {
+ foreach (QQmlEngine *engine, m_engineProfilers.keys())
+ stopProfiling(engine);
+ }
+}
+
+void QQmlProfilerServiceImpl::messageReceived(const QByteArray &message)
+{
+ QMutexLocker lock(&m_configMutex);
+
+ QByteArray rwData = message;
+ QQmlDebugStream stream(&rwData, QIODevice::ReadOnly);
+
+ int engineId = -1;
+ quint64 features = std::numeric_limits<quint64>::max();
+ bool enabled;
+ uint flushInterval = 0;
+ stream >> enabled;
+ if (!stream.atEnd())
+ stream >> engineId;
+ if (!stream.atEnd())
+ stream >> features;
+ if (!stream.atEnd()) {
+ stream >> flushInterval;
+ m_flushTimer.setInterval(flushInterval);
+ if (flushInterval > 0) {
+ connect(&m_flushTimer, SIGNAL(timeout()), this, SLOT(flush()));
+ connect(this, SIGNAL(startFlushTimer()), &m_flushTimer, SLOT(start()));
+ connect(this, SIGNAL(stopFlushTimer()), &m_flushTimer, SLOT(stop()));
+ } else {
+ disconnect(&m_flushTimer, SIGNAL(timeout()), this, SLOT(flush()));
+ disconnect(this, SIGNAL(startFlushTimer()), &m_flushTimer, SLOT(start()));
+ disconnect(this, SIGNAL(stopFlushTimer()), &m_flushTimer, SLOT(stop()));
+ }
+ }
+
+ // If engineId == -1 objectForId() and then the cast will return 0.
+ if (enabled)
+ startProfiling(qobject_cast<QQmlEngine *>(objectForId(engineId)), features);
+ else
+ stopProfiling(qobject_cast<QQmlEngine *>(objectForId(engineId)));
+
+ stopWaiting();
+}
+
+void QQmlProfilerServiceImpl::flush()
+{
+ QMutexLocker lock(&m_configMutex);
+
+ foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers) {
+ if (profiler->isRunning()) {
+ m_startTimes.insert(-1, profiler);
+ profiler->reportData();
+ }
+ }
+
+ foreach (QQmlAbstractProfilerAdapter *profiler, m_globalProfilers) {
+ if (profiler->isRunning()) {
+ m_startTimes.insert(-1, profiler);
+ profiler->reportData();
+ }
+ }
+}
+
+QT_END_NAMESPACE