1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmlprofilerclient_p_p.h"
5#include "qqmldebugconnection_p.h"
6
7QT_BEGIN_NAMESPACE
8
9QQmlProfilerClientPrivate::~QQmlProfilerClientPrivate()
10{
11}
12
13int QQmlProfilerClientPrivate::resolveType(const QQmlProfilerTypedEvent &event)
14{
15 int typeIndex = -1;
16 if (event.serverTypeId != 0) {
17 QHash<qint64, int>::ConstIterator it = serverTypeIds.constFind(key: event.serverTypeId);
18
19 if (it != serverTypeIds.constEnd()) {
20 typeIndex = it.value();
21 } else {
22 typeIndex = eventReceiver->numLoadedEventTypes();
23 eventReceiver->addEventType(type: event.type);
24 serverTypeIds[event.serverTypeId] = typeIndex;
25 }
26 } else {
27 QHash<QQmlProfilerEventType, int>::ConstIterator it = eventTypeIds.constFind(key: event.type);
28
29 if (it != eventTypeIds.constEnd()) {
30 typeIndex = it.value();
31 } else {
32 typeIndex = eventReceiver->numLoadedEventTypes();
33 eventReceiver->addEventType(type: event.type);
34 eventTypeIds[event.type] = typeIndex;
35 }
36 }
37 return typeIndex;
38}
39
40int QQmlProfilerClientPrivate::resolveStackTop()
41{
42 if (rangesInProgress.isEmpty())
43 return -1;
44
45 QQmlProfilerTypedEvent &typedEvent = rangesInProgress.top();
46 int typeIndex = typedEvent.event.typeIndex();
47 if (typeIndex >= 0)
48 return typeIndex;
49
50 typeIndex = resolveType(event: typedEvent);
51 typedEvent.event.setTypeIndex(typeIndex);
52 while (!pendingMessages.isEmpty()
53 && pendingMessages.head().timestamp() < typedEvent.event.timestamp()) {
54 forwardEvents(last: pendingMessages.dequeue());
55 }
56 forwardEvents(last: typedEvent.event);
57 return typeIndex;
58}
59
60void QQmlProfilerClientPrivate::forwardEvents(const QQmlProfilerEvent &last)
61{
62 forwardDebugMessages(untilTimestamp: last.timestamp());
63 eventReceiver->addEvent(event: last);
64}
65
66void QQmlProfilerClientPrivate::forwardDebugMessages(qint64 untilTimestamp)
67{
68 while (!pendingDebugMessages.isEmpty()
69 && pendingDebugMessages.front().timestamp() <= untilTimestamp) {
70 eventReceiver->addEvent(event: pendingDebugMessages.dequeue());
71 }
72}
73
74void QQmlProfilerClientPrivate::processCurrentEvent()
75{
76 // RangeData and RangeLocation always apply to the range on the top of the stack. Furthermore,
77 // all ranges are perfectly nested. This is why we can defer the type resolution until either
78 // the range ends or a child range starts. With only the information in RangeStart we wouldn't
79 // be able to uniquely identify the event type.
80 Message rangeStage = currentEvent.type.rangeType() == MaximumRangeType ?
81 currentEvent.type.message() : currentEvent.event.rangeStage();
82 switch (rangeStage) {
83 case RangeStart:
84 resolveStackTop();
85 rangesInProgress.push(t: currentEvent);
86 break;
87 case RangeEnd: {
88 int typeIndex = resolveStackTop();
89 if (typeIndex == -1)
90 break;
91 currentEvent.event.setTypeIndex(typeIndex);
92 while (!pendingMessages.isEmpty())
93 forwardEvents(last: pendingMessages.dequeue());
94 forwardEvents(last: currentEvent.event);
95 rangesInProgress.pop();
96 break;
97 }
98 case RangeData:
99 if (!rangesInProgress.isEmpty())
100 rangesInProgress.top().type.setData(currentEvent.type.data());
101 break;
102 case RangeLocation:
103 if (!rangesInProgress.isEmpty())
104 rangesInProgress.top().type.setLocation(currentEvent.type.location());
105 break;
106 case DebugMessage:
107 currentEvent.event.setTypeIndex(resolveType(event: currentEvent));
108 pendingDebugMessages.enqueue(t: currentEvent.event);
109 break;
110 default: {
111 int typeIndex = resolveType(event: currentEvent);
112 currentEvent.event.setTypeIndex(typeIndex);
113 if (rangesInProgress.isEmpty())
114 forwardEvents(last: currentEvent.event);
115 else
116 pendingMessages.enqueue(t: currentEvent.event);
117 break;
118 }
119 }
120}
121
122void QQmlProfilerClientPrivate::sendRecordingStatus(int engineId)
123{
124 Q_Q(QQmlProfilerClient);
125 QPacket stream(connection->currentDataStreamVersion());
126 stream << recording << engineId; // engineId -1 is OK. It means "all of them"
127 if (recording) {
128 stream << requestedFeatures << flushInterval;
129 stream << true; // yes, we support type IDs
130 }
131 q->sendMessage(message: stream.data());
132}
133
134QQmlProfilerClient::QQmlProfilerClient(QQmlDebugConnection *connection,
135 QQmlProfilerEventReceiver *eventReceiver,
136 quint64 features)
137 : QQmlDebugClient(*(new QQmlProfilerClientPrivate(connection, eventReceiver)))
138{
139 Q_D(QQmlProfilerClient);
140 setRequestedFeatures(features);
141 connect(sender: this, signal: &QQmlDebugClient::stateChanged, context: this, slot: &QQmlProfilerClient::onStateChanged);
142 connect(sender: d->engineControl.data(), signal: &QQmlEngineControlClient::engineAboutToBeAdded,
143 context: this, slot: &QQmlProfilerClient::sendRecordingStatus);
144 connect(sender: d->engineControl.data(), signal: &QQmlEngineControlClient::engineAboutToBeRemoved,
145 context: this, slot: [d](int engineId) {
146 // We may already be done with that engine. Then we don't need to block it.
147 if (d->trackedEngines.contains(t: engineId))
148 d->engineControl->blockEngine(engineId);
149 });
150 connect(sender: this, signal: &QQmlProfilerClient::traceFinished,
151 context: d->engineControl.data(), slot: [d](qint64 timestamp, const QList<int> &engineIds) {
152 Q_UNUSED(timestamp);
153 // The engines might not be blocked because the trace can get finished before engine control
154 // sees them.
155 for (int blocked : d->engineControl->blockedEngines()) {
156 if (engineIds.contains(t: blocked))
157 d->engineControl->releaseEngine(engineId: blocked);
158 }
159 });
160}
161
162QQmlProfilerClient::~QQmlProfilerClient()
163{
164 //Disable profiling if started by client
165 //Profiling data will be lost!!
166 if (isRecording())
167 setRecording(false);
168}
169
170void QQmlProfilerClient::clearEvents()
171{
172 Q_D(QQmlProfilerClient);
173 d->rangesInProgress.clear();
174 d->pendingMessages.clear();
175 d->pendingDebugMessages.clear();
176 if (d->recordedFeatures != 0) {
177 d->recordedFeatures = 0;
178 emit recordedFeaturesChanged(features: 0);
179 }
180 emit cleared();
181}
182
183void QQmlProfilerClient::clearAll()
184{
185 Q_D(QQmlProfilerClient);
186 d->serverTypeIds.clear();
187 d->eventTypeIds.clear();
188 d->trackedEngines.clear();
189 clearEvents();
190}
191
192void QQmlProfilerClientPrivate::finalize()
193{
194 while (!rangesInProgress.isEmpty()) {
195 currentEvent = rangesInProgress.top();
196 currentEvent.event.setRangeStage(RangeEnd);
197 currentEvent.event.setTimestamp(maximumTime);
198 processCurrentEvent();
199 }
200 forwardDebugMessages(untilTimestamp: std::numeric_limits<qint64>::max());
201}
202
203
204void QQmlProfilerClient::sendRecordingStatus(int engineId)
205{
206 Q_D(QQmlProfilerClient);
207 d->sendRecordingStatus(engineId);
208}
209
210bool QQmlProfilerClient::isRecording() const
211{
212 Q_D(const QQmlProfilerClient);
213 return d->recording;
214}
215
216void QQmlProfilerClient::setRecording(bool v)
217{
218 Q_D(QQmlProfilerClient);
219 if (v == d->recording)
220 return;
221
222 d->recording = v;
223
224 if (state() == Enabled)
225 sendRecordingStatus();
226
227 emit recordingChanged(arg: v);
228}
229
230quint64 QQmlProfilerClient::recordedFeatures() const
231{
232 Q_D(const QQmlProfilerClient);
233 return d->recordedFeatures;
234}
235
236void QQmlProfilerClient::setRequestedFeatures(quint64 features)
237{
238 Q_D(QQmlProfilerClient);
239 d->requestedFeatures = features;
240 if (features & static_cast<quint64>(1) << ProfileDebugMessages) {
241 if (d->messageClient.isNull()) {
242 d->messageClient.reset(other: new QQmlDebugMessageClient(connection()));
243 connect(sender: d->messageClient.data(), signal: &QQmlDebugMessageClient::message, context: this,
244 slot: [this](QtMsgType type, const QString &text, const QQmlDebugContextInfo &context)
245 {
246 Q_D(QQmlProfilerClient);
247 d->updateFeatures(feature: ProfileDebugMessages);
248 d->currentEvent.event.setTimestamp(context.timestamp > 0 ? context.timestamp : 0);
249 d->currentEvent.event.setTypeIndex(-1);
250 d->currentEvent.event.setString(text);
251 d->currentEvent.type = QQmlProfilerEventType(
252 DebugMessage, MaximumRangeType, type,
253 QQmlProfilerEventLocation(context.file, context.line, 1));
254 d->currentEvent.serverTypeId = 0;
255 d->processCurrentEvent();
256 });
257 }
258 } else {
259 d->messageClient.reset();
260 }
261}
262
263void QQmlProfilerClient::setFlushInterval(quint32 flushInterval)
264{
265 Q_D(QQmlProfilerClient);
266 d->flushInterval = flushInterval;
267}
268
269QQmlProfilerClient::QQmlProfilerClient(QQmlProfilerClientPrivate &dd) :
270 QQmlDebugClient(dd)
271{
272 Q_D(QQmlProfilerClient);
273 connect(sender: d->engineControl.data(), signal: &QQmlEngineControlClient::engineAboutToBeAdded,
274 context: this, slot: &QQmlProfilerClient::sendRecordingStatus);
275}
276
277bool QQmlProfilerClientPrivate::updateFeatures(ProfileFeature feature)
278{
279 Q_Q(QQmlProfilerClient);
280 quint64 flag = 1ULL << feature;
281 if (!(requestedFeatures & flag))
282 return false;
283 if (!(recordedFeatures & flag)) {
284 recordedFeatures |= flag;
285 emit q->recordedFeaturesChanged(features: recordedFeatures);
286 }
287 return true;
288}
289
290void QQmlProfilerClient::onStateChanged(State status)
291{
292 if (status == Enabled) {
293 sendRecordingStatus(engineId: -1);
294 } else {
295 Q_D(QQmlProfilerClient);
296 d->finalize();
297 }
298
299}
300
301void QQmlProfilerClient::messageReceived(const QByteArray &data)
302{
303 Q_D(QQmlProfilerClient);
304 QPacket stream(d->connection->currentDataStreamVersion(), data);
305
306 stream >> d->currentEvent;
307
308 d->maximumTime = qMax(a: d->currentEvent.event.timestamp(), b: d->maximumTime);
309 if (d->currentEvent.type.message() == Complete) {
310 d->finalize();
311 emit complete(maximumTime: d->maximumTime);
312 } else if (d->currentEvent.type.message() == Event
313 && d->currentEvent.type.detailType() == StartTrace) {
314 const QList<int> engineIds = d->currentEvent.event.numbers<QList<int>, qint32>();
315 d->trackedEngines.append(l: engineIds);
316 d->forwardDebugMessages(untilTimestamp: d->currentEvent.event.timestamp());
317 emit traceStarted(timestamp: d->currentEvent.event.timestamp(), engineIds);
318 } else if (d->currentEvent.type.message() == Event
319 && d->currentEvent.type.detailType() == EndTrace) {
320 const QList<int> engineIds = d->currentEvent.event.numbers<QList<int>, qint32>();
321 for (int engineId : engineIds)
322 d->trackedEngines.removeAll(t: engineId);
323 d->forwardDebugMessages(untilTimestamp: d->currentEvent.event.timestamp());
324 emit traceFinished(timestamp: d->currentEvent.event.timestamp(), engineIds);
325 } else if (d->updateFeatures(feature: d->currentEvent.type.feature())) {
326 d->processCurrentEvent();
327 }
328}
329
330QT_END_NAMESPACE
331
332#include "moc_qqmlprofilerclient_p.cpp"
333

source code of qtdeclarative/src/qmldebug/qqmlprofilerclient.cpp