// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqmlprofilerclient_p_p.h" #include "qqmldebugconnection_p.h" QT_BEGIN_NAMESPACE QQmlProfilerClientPrivate::~QQmlProfilerClientPrivate() { } int QQmlProfilerClientPrivate::resolveType(const QQmlProfilerTypedEvent &event) { int typeIndex = -1; if (event.serverTypeId != 0) { QHash::ConstIterator it = serverTypeIds.constFind(event.serverTypeId); if (it != serverTypeIds.constEnd()) { typeIndex = it.value(); } else { typeIndex = eventReceiver->numLoadedEventTypes(); eventReceiver->addEventType(event.type); serverTypeIds[event.serverTypeId] = typeIndex; } } else { QHash::ConstIterator it = eventTypeIds.constFind(event.type); if (it != eventTypeIds.constEnd()) { typeIndex = it.value(); } else { typeIndex = eventReceiver->numLoadedEventTypes(); eventReceiver->addEventType(event.type); eventTypeIds[event.type] = typeIndex; } } return typeIndex; } int QQmlProfilerClientPrivate::resolveStackTop() { if (rangesInProgress.isEmpty()) return -1; QQmlProfilerTypedEvent &typedEvent = rangesInProgress.top(); int typeIndex = typedEvent.event.typeIndex(); if (typeIndex >= 0) return typeIndex; typeIndex = resolveType(typedEvent); typedEvent.event.setTypeIndex(typeIndex); while (!pendingMessages.isEmpty() && pendingMessages.head().timestamp() < typedEvent.event.timestamp()) { forwardEvents(pendingMessages.dequeue()); } forwardEvents(typedEvent.event); return typeIndex; } void QQmlProfilerClientPrivate::forwardEvents(const QQmlProfilerEvent &last) { forwardDebugMessages(last.timestamp()); eventReceiver->addEvent(last); } void QQmlProfilerClientPrivate::forwardDebugMessages(qint64 untilTimestamp) { while (!pendingDebugMessages.isEmpty() && pendingDebugMessages.front().timestamp() <= untilTimestamp) { eventReceiver->addEvent(pendingDebugMessages.dequeue()); } } void QQmlProfilerClientPrivate::processCurrentEvent() { // RangeData and RangeLocation always apply to the range on the top of the stack. Furthermore, // all ranges are perfectly nested. This is why we can defer the type resolution until either // the range ends or a child range starts. With only the information in RangeStart we wouldn't // be able to uniquely identify the event type. Message rangeStage = currentEvent.type.rangeType() == MaximumRangeType ? currentEvent.type.message() : currentEvent.event.rangeStage(); switch (rangeStage) { case RangeStart: resolveStackTop(); rangesInProgress.push(currentEvent); break; case RangeEnd: { int typeIndex = resolveStackTop(); if (typeIndex == -1) break; currentEvent.event.setTypeIndex(typeIndex); while (!pendingMessages.isEmpty()) forwardEvents(pendingMessages.dequeue()); forwardEvents(currentEvent.event); rangesInProgress.pop(); break; } case RangeData: if (!rangesInProgress.isEmpty()) rangesInProgress.top().type.setData(currentEvent.type.data()); break; case RangeLocation: if (!rangesInProgress.isEmpty()) rangesInProgress.top().type.setLocation(currentEvent.type.location()); break; case DebugMessage: currentEvent.event.setTypeIndex(resolveType(currentEvent)); pendingDebugMessages.enqueue(currentEvent.event); break; default: { int typeIndex = resolveType(currentEvent); currentEvent.event.setTypeIndex(typeIndex); if (rangesInProgress.isEmpty()) forwardEvents(currentEvent.event); else pendingMessages.enqueue(currentEvent.event); break; } } } void QQmlProfilerClientPrivate::sendRecordingStatus(int engineId) { Q_Q(QQmlProfilerClient); QPacket stream(connection->currentDataStreamVersion()); stream << recording << engineId; // engineId -1 is OK. It means "all of them" if (recording) { stream << requestedFeatures << flushInterval; stream << true; // yes, we support type IDs } q->sendMessage(stream.data()); } QQmlProfilerClient::QQmlProfilerClient(QQmlDebugConnection *connection, QQmlProfilerEventReceiver *eventReceiver, quint64 features) : QQmlDebugClient(*(new QQmlProfilerClientPrivate(connection, eventReceiver))) { Q_D(QQmlProfilerClient); setRequestedFeatures(features); connect(this, &QQmlDebugClient::stateChanged, this, &QQmlProfilerClient::onStateChanged); connect(d->engineControl.data(), &QQmlEngineControlClient::engineAboutToBeAdded, this, &QQmlProfilerClient::sendRecordingStatus); connect(d->engineControl.data(), &QQmlEngineControlClient::engineAboutToBeRemoved, this, [d](int engineId) { // We may already be done with that engine. Then we don't need to block it. if (d->trackedEngines.contains(engineId)) d->engineControl->blockEngine(engineId); }); connect(this, &QQmlProfilerClient::traceFinished, d->engineControl.data(), [d](qint64 timestamp, const QList &engineIds) { Q_UNUSED(timestamp); // The engines might not be blocked because the trace can get finished before engine control // sees them. for (int blocked : d->engineControl->blockedEngines()) { if (engineIds.contains(blocked)) d->engineControl->releaseEngine(blocked); } }); } QQmlProfilerClient::~QQmlProfilerClient() { //Disable profiling if started by client //Profiling data will be lost!! if (isRecording()) setRecording(false); } void QQmlProfilerClient::clearEvents() { Q_D(QQmlProfilerClient); d->rangesInProgress.clear(); d->pendingMessages.clear(); d->pendingDebugMessages.clear(); if (d->recordedFeatures != 0) { d->recordedFeatures = 0; emit recordedFeaturesChanged(0); } emit cleared(); } void QQmlProfilerClient::clearAll() { Q_D(QQmlProfilerClient); d->serverTypeIds.clear(); d->eventTypeIds.clear(); d->trackedEngines.clear(); clearEvents(); } void QQmlProfilerClientPrivate::finalize() { while (!rangesInProgress.isEmpty()) { currentEvent = rangesInProgress.top(); currentEvent.event.setRangeStage(RangeEnd); currentEvent.event.setTimestamp(maximumTime); processCurrentEvent(); } forwardDebugMessages(std::numeric_limits::max()); } void QQmlProfilerClient::sendRecordingStatus(int engineId) { Q_D(QQmlProfilerClient); d->sendRecordingStatus(engineId); } bool QQmlProfilerClient::isRecording() const { Q_D(const QQmlProfilerClient); return d->recording; } void QQmlProfilerClient::setRecording(bool v) { Q_D(QQmlProfilerClient); if (v == d->recording) return; d->recording = v; if (state() == Enabled) sendRecordingStatus(); emit recordingChanged(v); } quint64 QQmlProfilerClient::recordedFeatures() const { Q_D(const QQmlProfilerClient); return d->recordedFeatures; } void QQmlProfilerClient::setRequestedFeatures(quint64 features) { Q_D(QQmlProfilerClient); d->requestedFeatures = features; if (features & static_cast(1) << ProfileDebugMessages) { if (d->messageClient.isNull()) { d->messageClient.reset(new QQmlDebugMessageClient(connection())); connect(d->messageClient.data(), &QQmlDebugMessageClient::message, this, [this](QtMsgType type, const QString &text, const QQmlDebugContextInfo &context) { Q_D(QQmlProfilerClient); d->updateFeatures(ProfileDebugMessages); d->currentEvent.event.setTimestamp(context.timestamp > 0 ? context.timestamp : 0); d->currentEvent.event.setTypeIndex(-1); d->currentEvent.event.setString(text); d->currentEvent.type = QQmlProfilerEventType( DebugMessage, MaximumRangeType, type, QQmlProfilerEventLocation(context.file, context.line, 1)); d->currentEvent.serverTypeId = 0; d->processCurrentEvent(); }); } } else { d->messageClient.reset(); } } void QQmlProfilerClient::setFlushInterval(quint32 flushInterval) { Q_D(QQmlProfilerClient); d->flushInterval = flushInterval; } QQmlProfilerClient::QQmlProfilerClient(QQmlProfilerClientPrivate &dd) : QQmlDebugClient(dd) { Q_D(QQmlProfilerClient); connect(d->engineControl.data(), &QQmlEngineControlClient::engineAboutToBeAdded, this, &QQmlProfilerClient::sendRecordingStatus); } bool QQmlProfilerClientPrivate::updateFeatures(ProfileFeature feature) { Q_Q(QQmlProfilerClient); quint64 flag = 1ULL << feature; if (!(requestedFeatures & flag)) return false; if (!(recordedFeatures & flag)) { recordedFeatures |= flag; emit q->recordedFeaturesChanged(recordedFeatures); } return true; } void QQmlProfilerClient::onStateChanged(State status) { if (status == Enabled) { sendRecordingStatus(-1); } else { Q_D(QQmlProfilerClient); d->finalize(); } } void QQmlProfilerClient::messageReceived(const QByteArray &data) { Q_D(QQmlProfilerClient); QPacket stream(d->connection->currentDataStreamVersion(), data); stream >> d->currentEvent; d->maximumTime = qMax(d->currentEvent.event.timestamp(), d->maximumTime); if (d->currentEvent.type.message() == Complete) { d->finalize(); emit complete(d->maximumTime); } else if (d->currentEvent.type.message() == Event && d->currentEvent.type.detailType() == StartTrace) { const QList engineIds = d->currentEvent.event.numbers, qint32>(); d->trackedEngines.append(engineIds); d->forwardDebugMessages(d->currentEvent.event.timestamp()); emit traceStarted(d->currentEvent.event.timestamp(), engineIds); } else if (d->currentEvent.type.message() == Event && d->currentEvent.type.detailType() == EndTrace) { const QList engineIds = d->currentEvent.event.numbers, qint32>(); for (int engineId : engineIds) d->trackedEngines.removeAll(engineId); d->forwardDebugMessages(d->currentEvent.event.timestamp()); emit traceFinished(d->currentEvent.event.timestamp(), engineIds); } else if (d->updateFeatures(d->currentEvent.type.feature())) { d->processCurrentEvent(); } } QT_END_NAMESPACE #include "moc_qqmlprofilerclient_p.cpp"