1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "debugutil_p.h"
30#include "qqmldebugprocess_p.h"
31#include "../../../shared/util.h"
32
33#include <private/qqmlprofilerclient_p.h>
34#include <private/qqmldebugconnection_p.h>
35
36#include <QtTest/qtest.h>
37#include <QtTest/qsignalspy.h>
38#include <QtCore/qlibraryinfo.h>
39
40#include <QtGui/private/qguiapplication_p.h>
41#include <QtGui/qpa/qplatformintegration.h>
42
43class QQmlProfilerTestClient : public QQmlProfilerEventReceiver
44{
45 Q_OBJECT
46
47public:
48 QQmlProfilerTestClient(QQmlDebugConnection *connection) :
49 client(new QQmlProfilerClient(connection, this))
50 {
51 connect(sender: client.data(), signal: &QQmlProfilerClient::traceStarted,
52 receiver: this, slot: &QQmlProfilerTestClient::startTrace);
53 connect(sender: client.data(), signal: &QQmlProfilerClient::traceFinished,
54 receiver: this, slot: &QQmlProfilerTestClient::endTrace);
55 }
56
57 void startTrace(qint64 timestamp, const QList<int> &engineIds);
58 void endTrace(qint64 timestamp, const QList<int> &engineIds);
59
60 QPointer<QQmlProfilerClient> client; // Owned by QQmlDebugTest
61 QVector<QQmlProfilerEventType> types;
62
63 QVector<QQmlProfilerEvent> qmlMessages;
64 QVector<QQmlProfilerEvent> javascriptMessages;
65 QVector<QQmlProfilerEvent> jsHeapMessages;
66 QVector<QQmlProfilerEvent> asynchronousMessages;
67 QVector<QQmlProfilerEvent> pixmapMessages;
68
69 int numLoadedEventTypes() const override;
70 void addEventType(const QQmlProfilerEventType &type) override;
71 void addEvent(const QQmlProfilerEvent &event) override;
72
73private:
74 qint64 lastTimestamp = -1;
75};
76
77void QQmlProfilerTestClient::startTrace(qint64 timestamp, const QList<int> &engineIds)
78{
79 types.append(t: QQmlProfilerEventType(Event, MaximumRangeType, StartTrace));
80 asynchronousMessages.append(t: QQmlProfilerEvent(timestamp, types.length() - 1,
81 engineIds.toVector()));
82}
83
84void QQmlProfilerTestClient::endTrace(qint64 timestamp, const QList<int> &engineIds)
85{
86 types.append(t: QQmlProfilerEventType(Event, MaximumRangeType, EndTrace));
87 asynchronousMessages.append(t: QQmlProfilerEvent(timestamp, types.length() - 1,
88 engineIds.toVector()));
89}
90
91int QQmlProfilerTestClient::numLoadedEventTypes() const
92{
93 return types.length();
94}
95
96void QQmlProfilerTestClient::addEventType(const QQmlProfilerEventType &type)
97{
98 types.append(t: type);
99}
100
101void QQmlProfilerTestClient::addEvent(const QQmlProfilerEvent &event)
102{
103 const int typeIndex = event.typeIndex();
104 QVERIFY(typeIndex < types.length());
105
106 const QQmlProfilerEventType &type = types[typeIndex];
107
108 QVERIFY(event.timestamp() >= lastTimestamp);
109 lastTimestamp = event.timestamp();
110
111 switch (type.message()) {
112 case Event: {
113 switch (type.detailType()) {
114 case StartTrace:
115 QFAIL("StartTrace should not be passed on as event");
116 break;
117 case EndTrace:
118 QFAIL("EndTrace should not be passed on as event");
119 break;
120 case AnimationFrame:
121 asynchronousMessages.append(t: event);
122 break;
123 case Mouse:
124 case Key:
125 qmlMessages.append(t: event);
126 break;
127 default:
128 QFAIL(qPrintable(QString::fromLatin1("Event with unknown detailType %1 received at %2.")
129 .arg(type.detailType()).arg(event.timestamp())));
130 break;
131 }
132 break;
133 }
134 case RangeStart:
135 case RangeData:
136 case RangeLocation:
137 case RangeEnd:
138 QFAIL("Range stages are transmitted as part of events");
139 break;
140 case Complete:
141 QFAIL("Complete should not be passed on as event");
142 break;
143 case PixmapCacheEvent:
144 pixmapMessages.append(t: event);
145 break;
146 case SceneGraphFrame:
147 asynchronousMessages.append(t: event);
148 break;
149 case MemoryAllocation:
150 jsHeapMessages.append(t: event);
151 break;
152 case DebugMessage:
153 // Unhandled
154 break;
155 case MaximumMessage:
156 switch (type.rangeType()) {
157 case Painting:
158 QFAIL("QtQuick1 paint message received.");
159 break;
160 case Compiling:
161 case Creating:
162 case Binding:
163 case HandlingSignal:
164 qmlMessages.append(t: event);
165 break;
166 case Javascript:
167 javascriptMessages.append(t: event);
168 break;
169 default:
170 QFAIL(qPrintable(
171 QString::fromLatin1("Unknown range event %1 received at %2.")
172 .arg(type.rangeType()).arg(event.timestamp())));
173 break;
174 }
175 break;
176 }
177}
178
179class tst_QQmlProfilerService : public QQmlDebugTest
180{
181 Q_OBJECT
182
183private:
184 enum MessageListType {
185 MessageListQML,
186 MessageListJavaScript,
187 MessageListJsHeap,
188 MessageListAsynchronous,
189 MessageListPixmap
190 };
191
192 enum CheckType {
193 CheckMessageType = 1 << 0,
194 CheckDetailType = 1 << 1,
195 CheckLine = 1 << 2,
196 CheckColumn = 1 << 3,
197 CheckDataEndsWith = 1 << 4,
198 CheckFileEndsWith = 1 << 5,
199 CheckNumbers = 1 << 6,
200
201 CheckType = CheckMessageType | CheckDetailType | CheckLine | CheckColumn | CheckFileEndsWith
202 };
203
204 ConnectResult connectTo(bool block, const QString &file, bool recordFromStart = true,
205 uint flushInterval = 0, bool restrictServices = true,
206 const QString &executable
207 = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene");
208 void checkProcessTerminated();
209 void checkTraceReceived();
210 void checkJsHeap();
211 bool verify(MessageListType type, int expectedPosition,
212 const QQmlProfilerEventType &expected, quint32 checks,
213 const QVector<qint64> &expectedNumbers);
214
215 QList<QQmlDebugClient *> createClients() override;
216 QScopedPointer<QQmlProfilerTestClient> m_client;
217
218private slots:
219 void cleanup() override;
220
221 void connect_data();
222 void connect();
223 void pixmapCacheData();
224 void scenegraphData();
225 void profileOnExit();
226 void controlFromJS();
227 void signalSourceLocation();
228 void javascript();
229 void flushInterval();
230 void translationBinding();
231 void memory();
232 void compile();
233 void multiEngine();
234 void batchOverflow();
235
236private:
237 bool m_recordFromStart = true;
238 bool m_flushInterval = false;
239 bool m_isComplete = false;
240
241 // Don't use ({...}) here as MSVC will interpret that as the "QVector(int size)" ctor.
242 const QVector<qint64> m_rangeStart = (QVector<qint64>() << RangeStart);
243 const QVector<qint64> m_rangeEnd = (QVector<qint64>() << RangeEnd);
244};
245
246#define VERIFY(type, position, expected, checks, numbers) \
247 QVERIFY(verify(type, position, expected, checks, numbers))
248
249QQmlDebugTest::ConnectResult tst_QQmlProfilerService::connectTo(
250 bool block, const QString &file, bool recordFromStart, uint flushInterval,
251 bool restrictServices, const QString &executable)
252{
253 m_recordFromStart = recordFromStart;
254 m_flushInterval = flushInterval;
255 m_isComplete = false;
256
257 // ### Still using qmlscene due to QTBUG-33377
258 return QQmlDebugTest::connectTo(
259 executable,
260 services: restrictServices ? "CanvasFrameRate,EngineControl,DebugMessages" : QString(),
261 extraArgs: testFile(fileName: file), block);
262}
263
264void tst_QQmlProfilerService::checkProcessTerminated()
265{
266 // If the process ends before connect(), we get a non-success value from connect()
267 // That's not a problem as we will still receive the trace. We check that process has terminated
268 // cleanly here.
269
270 // Wait for the process to finish by itself, if that hasn't happened already
271 QVERIFY(m_client);
272 QVERIFY(m_client->client);
273 QTRY_COMPARE(m_client->client->state(), QQmlDebugClient::NotConnected);
274 QVERIFY(m_process);
275 QVERIFY(m_process->exitStatus() != QProcess::CrashExit);
276 QTRY_COMPARE(m_process->exitStatus(), QProcess::NormalExit);
277}
278
279void tst_QQmlProfilerService::checkTraceReceived()
280{
281 QVERIFY(m_process->exitStatus() != QProcess::CrashExit);
282 QTRY_VERIFY2(m_isComplete, "No trace received in time.");
283
284 QVector<qint64> numbers;
285
286 // must start with "StartTrace"
287 QQmlProfilerEventType expected(Event, MaximumRangeType, StartTrace);
288 VERIFY(MessageListAsynchronous, 0, expected, CheckMessageType | CheckDetailType, numbers);
289
290 // must end with "EndTrace"
291 expected = QQmlProfilerEventType(Event, MaximumRangeType, EndTrace);
292 VERIFY(MessageListAsynchronous, m_client->asynchronousMessages.length() - 1, expected,
293 CheckMessageType | CheckDetailType, numbers);
294}
295
296void tst_QQmlProfilerService::checkJsHeap()
297{
298 QVERIFY(m_client);
299 QVERIFY2(m_client->jsHeapMessages.count() > 0, "no JavaScript heap messages received");
300
301 bool seen_alloc = false;
302 bool seen_small = false;
303 bool seen_large = false;
304 qint64 allocated = 0;
305 qint64 used = 0;
306 qint64 lastTimestamp = -1;
307 foreach (const QQmlProfilerEvent &message, m_client->jsHeapMessages) {
308 const auto amount = message.number<qint64>(i: 0);
309 const QQmlProfilerEventType &type = m_client->types.at(i: message.typeIndex());
310 switch (type.detailType()) {
311 case HeapPage:
312 allocated += amount;
313 seen_alloc = true;
314 break;
315 case SmallItem:
316 used += amount;
317 seen_small = true;
318 break;
319 case LargeItem:
320 allocated += amount;
321 used += amount;
322 seen_large = true;
323 break;
324 }
325
326 QVERIFY(message.timestamp() >= lastTimestamp);
327 // The heap will only be consistent after all events of the same timestamp are processed.
328 if (lastTimestamp == -1) {
329 lastTimestamp = message.timestamp();
330 continue;
331 }
332
333 if (message.timestamp() == lastTimestamp)
334 continue;
335
336 lastTimestamp = message.timestamp();
337
338 QVERIFY2(used >= 0, QString::fromLatin1("Negative memory usage seen: %1")
339 .arg(used).toUtf8().constData());
340
341 QVERIFY2(allocated >= 0, QString::fromLatin1("Negative memory allocation seen: %1")
342 .arg(allocated).toUtf8().constData());
343
344 QVERIFY2(used <= allocated,
345 QString::fromLatin1("More memory usage than allocation seen: %1 > %2")
346 .arg(used).arg(allocated).toUtf8().constData());
347 }
348
349 QVERIFY2(seen_alloc, "No heap allocation seen");
350 QVERIFY2(seen_small, "No small item seen");
351 QVERIFY2(seen_large, "No large item seen");
352}
353
354bool tst_QQmlProfilerService::verify(tst_QQmlProfilerService::MessageListType type,
355 int expectedPosition, const QQmlProfilerEventType &expected,
356 quint32 checks, const QVector<qint64> &expectedNumbers)
357{
358 if (!m_client) {
359 qWarning() << "No debug client available";
360 return false;
361 }
362
363 const QVector<QQmlProfilerEvent> *target = nullptr;
364 switch (type) {
365 case MessageListQML: target = &(m_client->qmlMessages); break;
366 case MessageListJavaScript: target = &(m_client->javascriptMessages); break;
367 case MessageListJsHeap: target = &(m_client->jsHeapMessages); break;
368 case MessageListAsynchronous: target = &(m_client->asynchronousMessages); break;
369 case MessageListPixmap: target = &(m_client->pixmapMessages); break;
370 }
371
372 if (!target) {
373 qWarning() << "Invalid MessageListType" << type;
374 return false;
375 }
376
377 if (target->length() <= expectedPosition) {
378 qWarning() << "Not enough events. expected position:" << expectedPosition
379 << "length:" << target->length();
380 return false;
381 }
382
383 int position = expectedPosition;
384 qint64 timestamp = target->at(i: expectedPosition).timestamp();
385 while (position > 0 && target->at(i: position - 1).timestamp() == timestamp)
386 --position;
387
388 QStringList warnings;
389
390 do {
391 const QQmlProfilerEvent &event = target->at(i: position);
392 const QQmlProfilerEventType &actual = m_client->types.at(i: event.typeIndex());
393 if ((checks & CheckMessageType) &&
394 (actual.message() != expected.message()
395 || actual.rangeType() != expected.rangeType())) {
396 warnings << QString::fromLatin1(str: "%1: unexpected messageType or rangeType. "
397 "actual: %2, %3 - expected: %4, %5")
398 .arg(a: position).arg(a: actual.message()).arg(a: actual.rangeType())
399 .arg(a: expected.message()).arg(a: expected.rangeType());
400 continue;
401 }
402 if ((checks & CheckDetailType) && actual.detailType() != expected.detailType()) {
403 warnings << QString::fromLatin1(str: "%1: unexpected detailType. actual: %2 - expected: %3")
404 .arg(a: position).arg(a: actual.detailType()).arg(a: expected.detailType());
405 continue;
406 }
407
408 const QQmlProfilerEventLocation expectedLocation = expected.location();
409 const QQmlProfilerEventLocation actualLocation = actual.location();
410
411 if ((checks & CheckLine) && actualLocation.line() != expectedLocation.line()) {
412 warnings << QString::fromLatin1(str: "%1: unexpected line. actual: %2 - expected: %3")
413 .arg(a: position).arg(a: actualLocation.line())
414 .arg(a: expectedLocation.line());
415 continue;
416 }
417 if ((checks & CheckColumn) && actualLocation.column() != expectedLocation.column()) {
418 warnings << QString::fromLatin1(str: "%1: unexpected column. actual: %2 - expected: %3")
419 .arg(a: position).arg(a: actualLocation.column())
420 .arg(a: expectedLocation.column());
421 continue;
422 }
423 if ((checks & CheckFileEndsWith) &&
424 !actualLocation.filename().endsWith(s: expectedLocation.filename())) {
425 warnings << QString::fromLatin1(str: "%1: unexpected fileName. actual: %2 - expected: %3")
426 .arg(a: position).arg(a: actualLocation.filename())
427 .arg(a: expectedLocation.filename());
428 continue;
429 }
430
431 if ((checks & CheckDataEndsWith) && !actual.data().endsWith(s: expected.data())) {
432 warnings << QString::fromLatin1(str: "%1: unexpected detailData. actual: %2 - expected: %3")
433 .arg(a: position).arg(a: actual.data()).arg(a: expected.data());
434 continue;
435 }
436
437 if (checks & CheckNumbers) {
438 const QVector<qint64> actualNumbers = event.numbers<QVector<qint64>>();
439 if (actualNumbers != expectedNumbers) {
440
441 QStringList expectedList;
442 for (qint64 number : expectedNumbers)
443 expectedList.append(t: QString::number(number));
444 QStringList actualList;
445 for (qint64 number : actualNumbers)
446 actualList.append(t: QString::number(number));
447
448 warnings << QString::fromLatin1(
449 str: "%1: unexpected numbers. actual [%2] - expected: [%3]")
450 .arg(a: position)
451 .arg(a: actualList.join(sep: QLatin1String(", ")))
452 .arg(a: expectedList.join(sep: QLatin1String(", ")));
453 continue;
454 }
455 }
456
457 return true;
458 } while (++position < target->length() && target->at(i: position).timestamp() == timestamp);
459
460 foreach (const QString &message, warnings)
461 qWarning() << message.toLocal8Bit().constData();
462
463 return false;
464}
465
466QList<QQmlDebugClient *> tst_QQmlProfilerService::createClients()
467{
468 m_client.reset(other: new QQmlProfilerTestClient(m_connection));
469 m_client->client->setRecording(m_recordFromStart);
470 m_client->client->setFlushInterval(m_flushInterval);
471 QObject::connect(sender: m_client->client.data(), signal: &QQmlProfilerClient::complete,
472 context: this, slot: [this](){ m_isComplete = true; });
473 return QList<QQmlDebugClient *>({m_client->client});
474}
475
476void tst_QQmlProfilerService::cleanup()
477{
478 auto log = [this](const QQmlProfilerEvent &data, int i) {
479 const QQmlProfilerEventType &type = m_client->types.at(i: data.typeIndex());
480 const QQmlProfilerEventLocation location = type.location();
481 qDebug() << i << data.timestamp() << type.message() << type.rangeType() << type.detailType()
482 << location.filename() << location.line() << location.column()
483 << data.numbers<QVector<qint64>>();
484 };
485
486 if (m_client && QTest::currentTestFailed()) {
487 qDebug() << "QML Messages:" << m_client->qmlMessages.count();
488 int i = 0;
489 for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->qmlMessages))
490 log(data, i++);
491
492 qDebug() << " ";
493 qDebug() << "JavaScript Messages:" << m_client->javascriptMessages.count();
494 i = 0;
495
496 for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->javascriptMessages))
497 log(data, i++);
498
499 qDebug() << " ";
500 qDebug() << "Asynchronous Messages:" << m_client->asynchronousMessages.count();
501 i = 0;
502 for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->asynchronousMessages))
503 log(data, i++);
504
505 qDebug() << " ";
506 qDebug() << "Pixmap Cache Messages:" << m_client->pixmapMessages.count();
507 i = 0;
508 for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->pixmapMessages))
509 log(data, i++);
510
511 qDebug() << " ";
512 qDebug() << "Javascript Heap Messages:" << m_client->jsHeapMessages.count();
513 i = 0;
514 for (const QQmlProfilerEvent &data : qAsConst(t&: m_client->jsHeapMessages))
515 log(data, i++);
516
517 qDebug() << " ";
518 }
519
520 m_client.reset();
521 QQmlDebugTest::cleanup();
522}
523
524void tst_QQmlProfilerService::connect_data()
525{
526 QTest::addColumn<bool>(name: "blockMode");
527 QTest::addColumn<bool>(name: "restrictMode");
528 QTest::addColumn<bool>(name: "traceEnabled");
529 QTest::newRow(dataTag: "normal/unrestricted/disabled") << false << false << false;
530 QTest::newRow(dataTag: "block/unrestricted/disabled") << true << false << false;
531 QTest::newRow(dataTag: "normal/restricted/disabled") << false << true << false;
532 QTest::newRow(dataTag: "block/restricted/disabled") << true << true << false;
533 QTest::newRow(dataTag: "normal/unrestricted/enabled") << false << false << true;
534 QTest::newRow(dataTag: "block/unrestricted/enabled") << true << false << true;
535 QTest::newRow(dataTag: "normal/restricted/enabled") << false << true << true;
536 QTest::newRow(dataTag: "block/restricted/enabled") << true << true << true;
537}
538
539void tst_QQmlProfilerService::connect()
540{
541 QFETCH(bool, blockMode);
542 QFETCH(bool, restrictMode);
543 QFETCH(bool, traceEnabled);
544
545 QCOMPARE(connectTo(blockMode, "test.qml", traceEnabled, 0, restrictMode), ConnectSuccess);
546
547 if (!traceEnabled)
548 m_client->client->setRecording(true);
549
550 QTRY_VERIFY(m_client->numLoadedEventTypes() > 0);
551 m_client->client->setRecording(false);
552 checkTraceReceived();
553 checkJsHeap();
554}
555
556void tst_QQmlProfilerService::pixmapCacheData()
557{
558
559 QCOMPARE(connectTo(true, "pixmapCacheTest.qml"), ConnectSuccess);
560
561 // Don't wait for readyReadStandardOutput before the loop. It may have already arrived.
562 while (m_process->output().indexOf(s: QLatin1String("image loaded")) == -1 &&
563 m_process->output().indexOf(s: QLatin1String("image error")) == -1)
564 QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput())));
565
566 m_client->client->setRecording(false);
567
568 checkTraceReceived();
569 checkJsHeap();
570
571 auto createType = [](PixmapEventType type) {
572 return QQmlProfilerEventType(PixmapCacheEvent, MaximumRangeType, type);
573 };
574
575 QVector<qint64> numbers;
576
577 // image starting to load
578 VERIFY(MessageListPixmap, 0, createType(PixmapLoadingStarted),
579 CheckMessageType | CheckDetailType, numbers);
580
581 // image size
582 numbers = QVector<qint64>({2, 2, 1});
583 VERIFY(MessageListPixmap, 1, createType(PixmapSizeKnown),
584 CheckMessageType | CheckDetailType | CheckNumbers, numbers);
585
586 // image loaded
587 VERIFY(MessageListPixmap, 2, createType(PixmapLoadingFinished),
588 CheckMessageType | CheckDetailType, numbers);
589
590 // cache size
591 VERIFY(MessageListPixmap, 3, createType(PixmapCacheCountChanged),
592 CheckMessageType | CheckDetailType, numbers);
593}
594
595void tst_QQmlProfilerService::scenegraphData()
596{
597 QCOMPARE(connectTo(true, "scenegraphTest.qml"), ConnectSuccess);
598
599 while (!m_process->output().contains(s: QLatin1String("tick")))
600 QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput())));
601 m_client->client->setRecording(false);
602
603 checkTraceReceived();
604 checkJsHeap();
605
606 // Check that at least one frame was rendered.
607 // There should be a SGContextFrame + SGRendererFrame + SGRenderLoopFrame sequence,
608 // but we can't be sure to get the SGRenderLoopFrame in the threaded renderer.
609 //
610 // Since the rendering happens in a different thread, there could be other unrelated events
611 // interleaved. Also, events could carry the same time stamps and be sorted in an unexpected way
612 // if the clocks are acting up.
613 qint64 contextFrameTime = -1;
614 qint64 renderFrameTime = -1;
615#if QT_CONFIG(opengl) //Software renderer doesn't have context frames
616 if (QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::OpenGL)) {
617 foreach (const QQmlProfilerEvent &msg, m_client->asynchronousMessages) {
618 const QQmlProfilerEventType &type = m_client->types.at(i: msg.typeIndex());
619 if (type.message() == SceneGraphFrame) {
620 if (type.detailType() == SceneGraphContextFrame) {
621 contextFrameTime = msg.timestamp();
622 break;
623 }
624 }
625 }
626
627 QVERIFY(contextFrameTime != -1);
628 }
629#endif
630 foreach (const QQmlProfilerEvent &msg, m_client->asynchronousMessages) {
631 const QQmlProfilerEventType &type = m_client->types.at(i: msg.typeIndex());
632 if (type.detailType() == SceneGraphRendererFrame) {
633 QVERIFY(msg.timestamp() >= contextFrameTime);
634 renderFrameTime = msg.timestamp();
635 break;
636 }
637 }
638
639 QVERIFY(renderFrameTime != -1);
640
641 foreach (const QQmlProfilerEvent &msg, m_client->asynchronousMessages) {
642 const QQmlProfilerEventType &type = m_client->types.at(i: msg.typeIndex());
643 if (type.detailType() == SceneGraphRenderLoopFrame) {
644 if (msg.timestamp() >= contextFrameTime) {
645 // Make sure SceneGraphRenderLoopFrame is not between SceneGraphContextFrame and
646 // SceneGraphRendererFrame. A SceneGraphRenderLoopFrame before everything else is
647 // OK as the scene graph might decide to do an initial rendering.
648 QVERIFY(msg.timestamp() >= renderFrameTime);
649 break;
650 }
651 }
652 }
653}
654
655void tst_QQmlProfilerService::profileOnExit()
656{
657 QCOMPARE(connectTo(true, "exit.qml"), ConnectSuccess);
658 checkProcessTerminated();
659
660 checkTraceReceived();
661 checkJsHeap();
662}
663
664void tst_QQmlProfilerService::controlFromJS()
665{
666 QCOMPARE(connectTo(true, "controlFromJS.qml", false), ConnectSuccess);
667
668 QTRY_VERIFY(m_client->numLoadedEventTypes() > 0);
669 m_client->client->setRecording(false);
670 checkTraceReceived();
671 checkJsHeap();
672}
673
674void tst_QQmlProfilerService::signalSourceLocation()
675{
676 QCOMPARE(connectTo(true, "signalSourceLocation.qml"), ConnectSuccess);
677
678 while (!(m_process->output().contains(s: QLatin1String("500"))))
679 QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput())));
680 m_client->client->setRecording(false);
681 checkTraceReceived();
682 checkJsHeap();
683
684 auto createType = [](int line, int column) {
685 return QQmlProfilerEventType(
686 MaximumMessage, HandlingSignal, -1,
687 QQmlProfilerEventLocation(QLatin1String("signalSourceLocation.qml"), line,
688 column));
689 };
690
691 VERIFY(MessageListQML, 4, createType(8, 28), CheckType | CheckNumbers, m_rangeStart);
692 VERIFY(MessageListQML, 6, createType(7, 21), CheckType | CheckNumbers, m_rangeEnd);
693}
694
695void tst_QQmlProfilerService::javascript()
696{
697 QCOMPARE(connectTo(true, "javascript.qml"), ConnectSuccess);
698
699 while (!(m_process->output().contains(s: QLatin1String("done"))))
700 QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput())));
701 m_client->client->setRecording(false);
702 checkTraceReceived();
703 checkJsHeap();
704
705 VERIFY(MessageListJavaScript, 2, QQmlProfilerEventType(MaximumMessage, Javascript),
706 CheckMessageType | CheckDetailType | CheckNumbers, m_rangeStart);
707
708 VERIFY(MessageListJavaScript, 3,
709 QQmlProfilerEventType(
710 MaximumMessage, Javascript, -1,
711 QQmlProfilerEventLocation(QLatin1String("javascript.qml"), 4, 5)),
712 CheckType | CheckNumbers, m_rangeStart);
713
714 VERIFY(MessageListJavaScript, 4, QQmlProfilerEventType(
715 MaximumMessage, Javascript, -1,
716 QQmlProfilerEventLocation(), QLatin1String("something")),
717 CheckMessageType | CheckDetailType | CheckDataEndsWith | CheckNumbers, m_rangeStart);
718
719 VERIFY(MessageListJavaScript, 10, QQmlProfilerEventType(MaximumMessage, Javascript),
720 CheckMessageType | CheckDetailType | CheckNumbers, m_rangeEnd);
721}
722
723void tst_QQmlProfilerService::flushInterval()
724{
725 QCOMPARE(connectTo(true, "timer.qml", true, 1), ConnectSuccess);
726
727 // Make sure we get multiple messages
728 QTRY_VERIFY(m_client->qmlMessages.length() > 0);
729 QVERIFY(m_client->qmlMessages.length() < 100);
730 QTRY_VERIFY(m_client->qmlMessages.length() > 100);
731
732 m_client->client->setRecording(false);
733 checkTraceReceived();
734 checkJsHeap();
735}
736
737void tst_QQmlProfilerService::translationBinding()
738{
739 QCOMPARE(connectTo(true, "qstr.qml"), ConnectSuccess);
740 checkProcessTerminated();
741
742 checkTraceReceived();
743 checkJsHeap();
744
745 const QQmlProfilerEventType type(MaximumMessage, Binding);
746
747 VERIFY(MessageListQML, 4, type, CheckDetailType | CheckMessageType | CheckNumbers,
748 m_rangeStart);
749 VERIFY(MessageListQML, 5, type, CheckDetailType | CheckMessageType | CheckNumbers,
750 m_rangeEnd);
751}
752
753void tst_QQmlProfilerService::memory()
754{
755 QCOMPARE(connectTo(true, "memory.qml"), ConnectSuccess);
756 checkProcessTerminated();
757
758 checkTraceReceived();
759 checkJsHeap();
760
761 QVERIFY(m_client);
762 int smallItems = 0;
763 for (const auto& message : m_client->jsHeapMessages) {
764 const QQmlProfilerEventType &type = m_client->types[message.typeIndex()];
765 if (type.detailType() == SmallItem)
766 ++smallItems;
767 }
768
769 QVERIFY(smallItems > 5);
770}
771
772static bool hasCompileEvents(const QVector<QQmlProfilerEventType> &types)
773{
774 for (const QQmlProfilerEventType &type : types) {
775 if (type.message() == MaximumMessage && type.rangeType() == Compiling)
776 return true;
777 }
778 return false;
779}
780
781void tst_QQmlProfilerService::compile()
782{
783 // Flush interval so that we actually get the events before we stop recording.
784 connectTo(block: true, file: "test.qml", recordFromStart: true, flushInterval: 100);
785
786 QVERIFY(m_client);
787
788 // We need to check specifically for compile events as we can otherwise stop recording after the
789 // StartTrace has arrived, but before it compiles anything.
790 QTRY_VERIFY(hasCompileEvents(m_client->types));
791 m_client->client->setRecording(false);
792
793 checkTraceReceived();
794 checkJsHeap();
795
796 Message rangeStage = MaximumMessage;
797 for (const auto& message : m_client->qmlMessages) {
798 const QQmlProfilerEventType &type = m_client->types[message.typeIndex()];
799 if (type.rangeType() == Compiling) {
800 switch (rangeStage) {
801 case MaximumMessage:
802 QCOMPARE(message.rangeStage(), RangeStart);
803 break;
804 case RangeStart:
805 QCOMPARE(message.rangeStage(), RangeEnd);
806 break;
807 default:
808 QFAIL("Wrong range stage");
809 }
810 rangeStage = message.rangeStage();
811 QCOMPARE(type.message(), MaximumMessage);
812 QCOMPARE(type.location().filename(), testFileUrl("test.qml").toString());
813 QCOMPARE(type.location().line(), 0);
814 QCOMPARE(type.location().column(), 0);
815 }
816 }
817
818 QCOMPARE(rangeStage, RangeEnd);
819}
820
821void tst_QQmlProfilerService::multiEngine()
822{
823 QCOMPARE(connectTo(true, "quit.qml", true, 0, false, debugJsServerPath("qqmlprofilerservice")),
824 ConnectSuccess);
825
826 QSignalSpy spy(m_client->client, SIGNAL(complete(qint64)));
827
828 checkTraceReceived();
829 checkJsHeap();
830
831 QTRY_COMPARE(m_process->state(), QProcess::NotRunning);
832 QCOMPARE(m_process->exitStatus(), QProcess::NormalExit);
833
834 QCOMPARE(spy.count(), 1);
835}
836
837void tst_QQmlProfilerService::batchOverflow()
838{
839 // The trace client checks that the events are received in order.
840 QCOMPARE(connectTo(true, "batchOverflow.qml"), ConnectSuccess);
841 checkProcessTerminated();
842 checkTraceReceived();
843 checkJsHeap();
844}
845
846QTEST_MAIN(tst_QQmlProfilerService)
847
848#include "tst_qqmlprofilerservice.moc"
849

source code of qtdeclarative/tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp