blob: 462401a093579173a7e54e792207b577efb27e1e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** 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-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qqmlprofilerservice.h"
#include "qv4profileradapter.h"
#include "qqmlprofileradapter.h"
#include "qqmlprofilerservicefactory.h"
#include <private/qjsengine_p.h>
#include <private/qqmldebugpluginmanager_p.h>
#include <QtCore/qurl.h>
#include <QtCore/qtimer.h>
#include <QtCore/qthread.h>
#include <QtCore/qcoreapplication.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
Q_QML_DEBUG_PLUGIN_LOADER(QQmlAbstractProfilerAdapter)
QQmlProfilerServiceImpl::QQmlProfilerServiceImpl(QObject *parent) :
QQmlConfigurableDebugService<QQmlProfilerService>(1, parent),
m_waitingForStop(false), m_globalEnabled(false), m_globalFeatures(0)
{
m_timer.start();
QQmlAbstractProfilerAdapter *quickAdapter =
loadQQmlAbstractProfilerAdapter(QLatin1String("QQuickProfilerAdapter"));
if (quickAdapter) {
addGlobalProfiler(quickAdapter);
quickAdapter->setService(this);
}
}
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);
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<QJSEngine *> enginesToRelease;
for (QJSEngine *engine : qAsConst(m_stoppingEngines)) {
const auto range = qAsConst(m_engineProfilers).equal_range(engine);
const auto startTimesEnd = m_startTimes.cend();
for (auto it = range.first; it != range.second; ++it) {
if (std::find(m_startTimes.cbegin(), startTimesEnd, *it) != startTimesEnd) {
enginesToRelease.append(engine);
break;
}
}
}
sendMessages();
for (QJSEngine *engine : qAsConst(enginesToRelease)) {
m_stoppingEngines.removeOne(engine);
emit detachedFromEngine(engine);
}
}
}
void QQmlProfilerServiceImpl::engineAboutToBeAdded(QJSEngine *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);
if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(engine)) {
QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(qmlEngine);
QQmlProfilerAdapter *qmlAdapter = new QQmlProfilerAdapter(this, enginePrivate);
addEngineProfiler(qmlAdapter, engine);
QQmlProfilerAdapter *compileAdapter
= new QQmlProfilerAdapter(this, &(enginePrivate->typeLoader));
addEngineProfiler(compileAdapter, engine);
}
QV4ProfilerAdapter *v4Adapter = new QV4ProfilerAdapter(this, engine->handle());
addEngineProfiler(v4Adapter, engine);
QQmlConfigurableDebugService<QQmlProfilerService>::engineAboutToBeAdded(engine);
}
void QQmlProfilerServiceImpl::engineAdded(QJSEngine *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);
if (m_globalEnabled)
startProfiling(engine, m_globalFeatures);
const auto range = qAsConst(m_engineProfilers).equal_range(engine);
for (auto it = range.first; it != range.second; ++it)
(*it)->stopWaiting();
}
void QQmlProfilerServiceImpl::engineAboutToBeRemoved(QJSEngine *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;
const auto range = qAsConst(m_engineProfilers).equal_range(engine);
for (auto it = range.first; it != range.second; ++it) {
QQmlAbstractProfilerAdapter *profiler = *it;
if (profiler->isRunning())
isRunning = true;
profiler->startWaiting();
}
if (isRunning) {
m_stoppingEngines.append(engine);
stopProfiling(engine);
} else {
emit detachedFromEngine(engine);
}
}
void QQmlProfilerServiceImpl::engineRemoved(QJSEngine *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);
const auto range = qAsConst(m_engineProfilers).equal_range(engine);
for (auto it = range.first; it != range.second; ++it) {
QQmlAbstractProfilerAdapter *profiler = *it;
removeProfilerFromStartTimes(profiler);
delete profiler;
}
m_engineProfilers.remove(engine);
}
void QQmlProfilerServiceImpl::addEngineProfiler(QQmlAbstractProfilerAdapter *profiler, QJSEngine *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;
for (QQmlAbstractProfilerAdapter *engineProfiler : qAsConst(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);
}
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(QJSEngine *engine, quint64 features)
{
QMutexLocker lock(&m_configMutex);
if (features & static_cast<quint64>(1) << ProfileDebugMessages) {
if (QDebugMessageService *messageService =
QQmlDebugConnector::instance()->service<QDebugMessageService>())
messageService->synchronizeTime(m_timer);
}
QQmlDebugPacket d;
d << m_timer.nsecsElapsed() << static_cast<qint32>(Event) << static_cast<qint32>(StartTrace);
bool startedAny = false;
if (engine != nullptr) {
const auto range = qAsConst(m_engineProfilers).equal_range(engine);
for (auto it = range.first; it != range.second; ++it) {
QQmlAbstractProfilerAdapter *profiler = *it;
if (!profiler->isRunning()) {
profiler->startProfiling(features);
startedAny = true;
}
}
if (startedAny)
d << idForObject(engine);
} else {
m_globalEnabled = true;
m_globalFeatures = features;
QSet<QJSEngine *> engines;
for (QMultiHash<QJSEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin());
i != m_engineProfilers.end(); ++i) {
if (!i.value()->isRunning()) {
engines << i.key();
i.value()->startProfiling(features);
startedAny = true;
}
}
for (QJSEngine *profiledEngine : qAsConst(engines))
d << idForObject(profiledEngine);
}
if (startedAny) {
for (QQmlAbstractProfilerAdapter *profiler : qAsConst(m_globalProfilers)) {
if (!profiler->isRunning())
profiler->startProfiling(features);
}
emit startFlushTimer();
emit messageToClient(name(), d.data());
}
}
/*!
* 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(QJSEngine *engine)
{
QMutexLocker lock(&m_configMutex);
QList<QQmlAbstractProfilerAdapter *> stopping;
QList<QQmlAbstractProfilerAdapter *> reporting;
if (engine == nullptr)
m_globalEnabled = false;
bool stillRunning = false;
for (QMultiHash<QJSEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin());
i != m_engineProfilers.end(); ++i) {
if (i.value()->isRunning()) {
m_startTimes.insert(-1, i.value());
if (engine == nullptr || i.key() == engine) {
stopping << i.value();
} else {
reporting << i.value();
stillRunning = true;
}
}
}
if (stopping.isEmpty())
return;
for (QQmlAbstractProfilerAdapter *profiler : qAsConst(m_globalProfilers)) {
if (!profiler->isRunning())
continue;
m_startTimes.insert(-1, profiler);
if (stillRunning) {
reporting << profiler;
} else {
stopping << profiler;
}
}
emit stopFlushTimer();
m_waitingForStop = true;
for (QQmlAbstractProfilerAdapter *profiler : qAsConst(reporting))
profiler->reportData();
for (QQmlAbstractProfilerAdapter *profiler : qAsConst(stopping))
profiler->stopProfiling();
}
/*
Send the queued up messages.
*/
void QQmlProfilerServiceImpl::sendMessages()
{
QList<QByteArray> messages;
QQmlDebugPacket traceEnd;
if (m_waitingForStop) {
traceEnd << m_timer.nsecsElapsed() << static_cast<qint32>(Event)
<< static_cast<qint32>(EndTrace);
QSet<QJSEngine *> seen;
for (QQmlAbstractProfilerAdapter *profiler : qAsConst(m_startTimes)) {
for (QMultiHash<QJSEngine *, 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());
qint64 next = first->sendMessages(m_startTimes.isEmpty() ?
std::numeric_limits<qint64>::max() :
m_startTimes.begin().key(), messages);
if (next != -1)
m_startTimes.insert(next, first);
if (messages.length() >= QQmlAbstractProfilerAdapter::s_numMessagesPerBatch) {
emit messagesToClient(name(), messages);
messages.clear();
}
}
bool stillRunning = false;
for (const QQmlAbstractProfilerAdapter *profiler : qAsConst(m_engineProfilers)) {
if (profiler->isRunning()) {
stillRunning = true;
break;
}
}
if (m_waitingForStop) {
// EndTrace can be sent multiple times, as it's engine specific.
messages << traceEnd.data();
if (!stillRunning) {
// Complete is only sent once, when no engines are running anymore.
QQmlDebugPacket ds;
ds << static_cast<qint64>(-1) << static_cast<qint32>(Complete);
messages << ds.data();
m_waitingForStop = false;
}
}
emit messagesToClient(name(), messages);
// Restart flushing if any profilers are still running
if (stillRunning)
emit startFlushTimer();
}
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) {
for (auto it = m_engineProfilers.keyBegin(), end = m_engineProfilers.keyEnd();
it != end; ++it) {
stopProfiling(*it);
}
}
}
void QQmlProfilerServiceImpl::messageReceived(const QByteArray &message)
{
QMutexLocker lock(&m_configMutex);
QQmlDebugPacket stream(message);
int engineId = -1;
quint64 features = std::numeric_limits<quint64>::max();
bool enabled;
quint32 flushInterval = 0;
stream >> enabled;
if (!stream.atEnd())
stream >> engineId;
if (!stream.atEnd())
stream >> features;
if (!stream.atEnd()) {
stream >> flushInterval;
m_flushTimer.setInterval(
static_cast<int>(qMin(flushInterval,
static_cast<quint32>(std::numeric_limits<int>::max()))));
auto timerStart = static_cast<void(QTimer::*)()>(&QTimer::start);
if (flushInterval > 0) {
connect(&m_flushTimer, &QTimer::timeout, this, &QQmlProfilerServiceImpl::flush);
connect(this, &QQmlProfilerServiceImpl::startFlushTimer, &m_flushTimer, timerStart);
connect(this, &QQmlProfilerServiceImpl::stopFlushTimer, &m_flushTimer, &QTimer::stop);
} else {
disconnect(&m_flushTimer, &QTimer::timeout, this, &QQmlProfilerServiceImpl::flush);
disconnect(this, &QQmlProfilerServiceImpl::startFlushTimer, &m_flushTimer, timerStart);
disconnect(this, &QQmlProfilerServiceImpl::stopFlushTimer,
&m_flushTimer, &QTimer::stop);
}
}
bool useMessageTypes = false;
if (!stream.atEnd())
stream >> useMessageTypes;
// If engineId == -1 objectForId() and then the cast will return 0.
if (enabled && useMessageTypes) // If the client doesn't support message types don't profile.
startProfiling(qobject_cast<QJSEngine *>(objectForId(engineId)), features);
else if (!enabled) // On stopProfiling the client doesn't repeat useMessageTypes.
stopProfiling(qobject_cast<QJSEngine *>(objectForId(engineId)));
stopWaiting();
}
void QQmlProfilerServiceImpl::flush()
{
QMutexLocker lock(&m_configMutex);
QList<QQmlAbstractProfilerAdapter *> reporting;
for (QQmlAbstractProfilerAdapter *profiler : qAsConst(m_engineProfilers)) {
if (profiler->isRunning()) {
m_startTimes.insert(-1, profiler);
reporting.append(profiler);
}
}
for (QQmlAbstractProfilerAdapter *profiler : qAsConst(m_globalProfilers)) {
if (profiler->isRunning()) {
m_startTimes.insert(-1, profiler);
reporting.append(profiler);
}
}
for (QQmlAbstractProfilerAdapter *profiler : qAsConst(reporting))
profiler->reportData();
}
QT_END_NAMESPACE
#include "moc_qqmlprofilerservice.cpp"