QmlProfiler: Pixmap Cache
authorChristiaan Janssen <christiaan.janssen@digia.com>
Wed, 24 Oct 2012 14:09:29 +0000 (16:09 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Tue, 30 Apr 2013 11:07:16 +0000 (13:07 +0200)
Change-Id: Ibc237bb162c24030438b89d54fa8802ee66b080a
Reviewed-by: Kai Koehne <kai.koehne@digia.com>

src/qml/debugger/qqmlprofilerservice.cpp
src/qml/debugger/qqmlprofilerservice_p.h
src/quick/util/qquickpixmapcache.cpp
tests/auto/qml/debugger/qqmlprofilerservice/data/TestImage_2x2.png [new file with mode: 0644]
tests/auto/qml/debugger/qqmlprofilerservice/data/pixmapCacheTest.qml [new file with mode: 0644]
tests/auto/qml/debugger/qqmlprofilerservice/qqmlprofilerservice.pro
tests/auto/qml/debugger/qqmlprofilerservice/tst_qqmlprofilerservice.cpp
tests/auto/qml/debugger/shared/debugutil.cpp
tests/auto/qml/debugger/shared/debugutil_p.h

index cb85fc0..e016b2f 100644 (file)
@@ -76,6 +76,15 @@ QByteArray QQmlProfilerData::toByteArray() const
     if (messageType == (int)QQmlProfilerService::Event &&
             detailType == (int)QQmlProfilerService::AnimationFrame)
         ds << framerate << animationcount;
+    if (messageType == (int)QQmlProfilerService::PixmapCacheEvent) {
+        ds << detailData;
+        switch (detailType) {
+        case QQmlProfilerService::PixmapSizeKnown: ds << line << column; break;
+        case QQmlProfilerService::PixmapReferenceCountChanged: ds << animationcount; break;
+        case QQmlProfilerService::PixmapCacheCountChanged: ds << animationcount; break;
+        default: break;
+        }
+    }
     return data;
 }
 
@@ -238,6 +247,30 @@ void QQmlProfilerService::endRange(RangeType range)
     processMessage(rd);
 }
 
+void QQmlProfilerService::pixmapEventImpl(PixmapEventType eventType, const QUrl &url)
+{
+    // assuming enabled checked by caller
+    QQmlProfilerData rd = {m_timer.nsecsElapsed(), (int)PixmapCacheEvent, (int)eventType,
+                           url.toString(), -1, -1, -1, -1, -1};
+    processMessage(rd);
+}
+
+void QQmlProfilerService::pixmapEventImpl(PixmapEventType eventType, const QUrl &url, int width, int height)
+{
+    // assuming enabled checked by caller
+    QQmlProfilerData rd = {m_timer.nsecsElapsed(), (int)PixmapCacheEvent, (int)eventType,
+                           url.toString(), width, height, -1, -1, -1};
+    processMessage(rd);
+}
+
+void QQmlProfilerService::pixmapEventImpl(PixmapEventType eventType, const QUrl &url, int count)
+{
+    // assuming enabled checked by caller
+    QQmlProfilerData rd = {m_timer.nsecsElapsed(), (int)PixmapCacheEvent, (int)eventType,
+                           url.toString(), -1, -1, -1, count, -1};
+    processMessage(rd);
+}
+
 void QQmlProfilerService::animationFrameImpl(qint64 delta)
 {
     Q_ASSERT(QQmlDebugService::isDebuggingEnabled());
index a50fb5e..30eb2eb 100644 (file)
@@ -74,10 +74,10 @@ struct Q_AUTOTEST_EXPORT QQmlProfilerData
 
     //###
     QString detailData; //used by RangeData and RangeLocation
-    int line;           //used by RangeLocation
-    int column;         //used by RangeLocation
+    int line;           //used by RangeLocation, also as "width" for pixmaps
+    int column;         //used by RangeLocation, also as "height" for pixmaps
     int framerate;      //used by animation events
-    int animationcount; //used by animation events
+    int animationcount; //used by animation events, also as "cache/reference count" for pixmaps
     int bindingType;
 
     QByteArray toByteArray() const;
@@ -99,6 +99,7 @@ public:
         RangeLocation,
         RangeEnd,
         Complete, // end of transmission
+        PixmapCacheEvent,
 
         MaximumMessage
     };
@@ -132,6 +133,17 @@ public:
         MaximumBindingType
     };
 
+    enum PixmapEventType {
+        PixmapSizeKnown,
+        PixmapReferenceCountChanged,
+        PixmapCacheCountChanged,
+        PixmapLoadingStarted,
+        PixmapLoadingFinished,
+        PixmapLoadingError,
+
+        MaximumPixmapEventType
+    };
+
     static void initialize();
 
     static bool startProfiling();
@@ -163,6 +175,10 @@ private:
     void rangeLocation(RangeType, const QUrl &, int, int);
     void endRange(RangeType);
 
+    // overloading depending on parameters
+    void pixmapEventImpl(PixmapEventType eventType, const QUrl &url);
+    void pixmapEventImpl(PixmapEventType eventType, const QUrl &url, int width, int height);
+    void pixmapEventImpl(PixmapEventType eventType, const QUrl &url, int count);
 
     bool profilingEnabled();
     void setProfilingEnabled(bool enable);
@@ -183,6 +199,7 @@ private:
     friend struct QQmlHandlingSignalProfiler;
     friend struct QQmlObjectCreatingProfiler;
     friend struct QQmlCompilingProfiler;
+    friend struct QQmlPixmapProfiler;
 };
 
 //
@@ -288,6 +305,49 @@ struct QQmlCompilingProfiler {
     bool enabled;
 };
 
+struct QQmlPixmapProfiler {
+    QQmlPixmapProfiler() {
+        QQmlProfilerService *instance = QQmlProfilerService::instance;
+        enabled = instance ?
+                    instance->profilingEnabled() : false;
+    }
+
+    ~QQmlPixmapProfiler() {}
+
+    void startLoading(const QUrl &pixmapUrl) {
+        if (enabled) {
+            QQmlProfilerService::instance->pixmapEventImpl(QQmlProfilerService::PixmapLoadingStarted, pixmapUrl);
+        }
+    }
+    void finishLoading(const QUrl &pixmapUrl) {
+        if (enabled) {
+            QQmlProfilerService::instance->pixmapEventImpl(QQmlProfilerService::PixmapLoadingFinished, pixmapUrl);
+        }
+    }
+    void errorLoading(const QUrl &pixmapUrl) {
+        if (enabled) {
+            QQmlProfilerService::instance->pixmapEventImpl(QQmlProfilerService::PixmapLoadingError, pixmapUrl);
+        }
+    }
+    void cacheCountChanged(const QUrl &pixmapUrl, int cacheCount) {
+        if (enabled) {
+            QQmlProfilerService::instance->pixmapEventImpl(QQmlProfilerService::PixmapCacheCountChanged, pixmapUrl, cacheCount);
+        }
+    }
+    void referenceCountChanged(const QUrl &pixmapUrl, int referenceCount) {
+        if (enabled) {
+            QQmlProfilerService::instance->pixmapEventImpl(QQmlProfilerService::PixmapReferenceCountChanged, pixmapUrl, referenceCount);
+        }
+    }
+    void setSize(const QUrl &pixmapUrl, int width, int height) {
+        if (enabled) {
+            QQmlProfilerService::instance->pixmapEventImpl(QQmlProfilerService::PixmapSizeKnown, pixmapUrl, width, height);
+        }
+    }
+
+    bool enabled;
+};
+
 QT_END_NAMESPACE
 
 #endif // QQMLPROFILERSERVICE_P_H
index c5968c2..9cf8c58 100644 (file)
@@ -71,6 +71,8 @@
 #include <QQmlFile>
 #include <QMetaMethod>
 
+#include <private/qqmlprofilerservice_p.h>
+
 #define IMAGEREQUEST_MAX_REQUEST_COUNT       8
 #define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
 #define CACHE_EXPIRE_TIME 30
@@ -890,11 +892,15 @@ bool QQuickPixmapReply::event(QEvent *event)
         if (data) {
             Event *de = static_cast<Event *>(event);
             data->pixmapStatus = (de->error == NoError) ? QQuickPixmap::Ready : QQuickPixmap::Error;
-
+            QQmlPixmapProfiler pixmapProfiler;
             if (data->pixmapStatus == QQuickPixmap::Ready) {
+                pixmapProfiler.finishLoading(data->url);
                 data->textureFactory = de->textureFactory;
                 data->implicitSize = de->implicitSize;
+                if (data->implicitSize.width() > 0)
+                    pixmapProfiler.setSize(url, data->implicitSize.width(), data->implicitSize.height());
             } else {
+                pixmapProfiler.errorLoading(data->url);
                 data->errorString = de->errorString;
                 data->removeFromCache(); // We don't continue to cache error'd pixmaps
             }
@@ -920,6 +926,7 @@ int QQuickPixmapData::cost() const
 void QQuickPixmapData::addref()
 {
     ++refCount;
+    QQmlPixmapProfiler().referenceCountChanged(url, refCount);
     if (prevUnreferencedPtr) 
         pixmapStore()->referencePixmap(this);
 }
@@ -928,6 +935,7 @@ void QQuickPixmapData::release()
 {
     Q_ASSERT(refCount > 0);
     --refCount;
+    QQmlPixmapProfiler().referenceCountChanged(url, refCount);
     if (refCount == 0) {
         if (reply) {
             QQuickPixmapReply *cancelReply = reply;
@@ -958,6 +966,10 @@ void QQuickPixmapData::addToCache()
         QQuickPixmapKey key = { &url, &requestSize };
         pixmapStore()->m_cache.insert(key, this);
         inCache = true;
+        QQmlPixmapProfiler pixmapProfiler;
+        pixmapProfiler.cacheCountChanged(url, pixmapStore()->m_cache.count());
+        if (implicitSize.width() > 0)
+            pixmapProfiler.setSize(url, implicitSize.width(), implicitSize.height());
     }
 }
 
@@ -965,6 +977,7 @@ void QQuickPixmapData::removeFromCache()
 {
     if (inCache) {
         QQuickPixmapKey key = { &url, &requestSize };
+        QQmlPixmapProfiler().cacheCountChanged(url, pixmapStore()->m_cache.count());
         pixmapStore()->m_cache.remove(key);
         inCache = false;
     }
@@ -1238,14 +1251,21 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques
 
         if (!(options & QQuickPixmap::Asynchronous)) {
             bool ok = false;
+            QQmlPixmapProfiler pixmapProfiler;
+            pixmapProfiler.startLoading(url);
             d = createPixmapDataSync(this, engine, url, requestSize, &ok);
             if (ok) {
+                pixmapProfiler.finishLoading(url);
+                if (d->implicitSize.width() > 0)
+                    QQmlPixmapProfiler().setSize(url, d->implicitSize.width(), d->implicitSize.height());
                 if (options & QQuickPixmap::Cache)
                     d->addToCache();
                 return;
             }
-            if (d)  // loadable, but encountered error while loading
+            if (d) { // loadable, but encountered error while loading
+                pixmapProfiler.errorLoading(url);
                 return;
+            }
         } 
 
         if (!engine)
diff --git a/tests/auto/qml/debugger/qqmlprofilerservice/data/TestImage_2x2.png b/tests/auto/qml/debugger/qqmlprofilerservice/data/TestImage_2x2.png
new file mode 100644 (file)
index 0000000..30228cb
Binary files /dev/null and b/tests/auto/qml/debugger/qqmlprofilerservice/data/TestImage_2x2.png differ
diff --git a/tests/auto/qml/debugger/qqmlprofilerservice/data/pixmapCacheTest.qml b/tests/auto/qml/debugger/qqmlprofilerservice/data/pixmapCacheTest.qml
new file mode 100644 (file)
index 0000000..d56786b
--- /dev/null
@@ -0,0 +1,13 @@
+import QtQuick 2.0
+
+Rectangle {
+    Image {
+        source: "TestImage_2x2.png"
+        onStatusChanged: switch (status) {
+                           case 0: console.log("no image"); break;
+                           case 1: console.log("image loaded"); break;
+                           case 2: console.log("image loading"); break;
+                           case 3: console.log("image error"); break;
+                         }
+    }
+}
index 5bff33d..b2b325d 100644 (file)
@@ -12,3 +12,6 @@ TESTDATA = data/*
 
 QT += core qml testlib gui-private
 DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
+
+OTHER_FILES += \
+    data/pixmapCacheTest.qml
index 9982e5d..8ad4f52 100644 (file)
@@ -77,6 +77,7 @@ public:
         RangeLocation,
         RangeEnd,
         Complete, // end of transmission
+        PixmapCacheEvent,
 
         MaximumMessage
     };
@@ -102,6 +103,17 @@ public:
         MaximumRangeType
     };
 
+    enum PixmapEventType {
+        PixmapSizeKnown,
+        PixmapReferenceCountChanged,
+        PixmapCacheCountChanged,
+        PixmapLoadingStarted,
+        PixmapLoadingFinished,
+        PixmapLoadingError,
+
+        MaximumPixmapEventType
+    };
+
     QQmlProfilerClient(QQmlDebugConnection *connection)
         : QQmlDebugClient(QLatin1String("CanvasFrameRate"), connection)
     {
@@ -148,6 +160,7 @@ private slots:
     void blockingConnectWithTraceEnabled();
     void blockingConnectWithTraceDisabled();
     void nonBlockingConnect();
+    void pixmapCacheData();
     void profileOnExit();
 };
 
@@ -219,6 +232,16 @@ void QQmlProfilerClient::messageReceived(const QByteArray &message)
         QVERIFY(data.line >= -2);
         break;
     }
+    case QQmlProfilerClient::PixmapCacheEvent: {
+        stream >> data.detailType >> data.detailData;
+        if (data.detailType == QQmlProfilerClient::PixmapSizeKnown)
+            stream >> data.line >> data.column;
+        if (data.detailType == QQmlProfilerClient::PixmapReferenceCountChanged)
+            stream >> data.animationcount;
+        if (data.detailType == QQmlProfilerClient::PixmapCacheCountChanged)
+            stream >> data.animationcount;
+        break;
+    }
     default:
         QString failMsg = QString("Unknown message type:") + data.messageType;
         QFAIL(qPrintable(failMsg));
@@ -320,6 +343,51 @@ void tst_QQmlProfilerService::nonBlockingConnect()
     QCOMPARE(m_client->traceMessages.last().detailType, (int)QQmlProfilerClient::EndTrace);
 }
 
+void tst_QQmlProfilerService::pixmapCacheData()
+{
+    connect(true, "pixmapCacheTest.qml");
+    QTRY_COMPARE(m_client->state(), QQmlDebugClient::Enabled);
+
+    m_client->setTraceState(true);
+    QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput())));
+
+    QVERIFY(m_process->output().indexOf(QLatin1String("image loaded")) != -1 ||
+            m_process->output().indexOf(QLatin1String("image error")) != -1 );
+
+
+    m_client->setTraceState(false);
+
+    QVERIFY2(QQmlDebugTest::waitForSignal(m_client, SIGNAL(complete())), "No trace received in time.");
+    QVERIFY(m_client->traceMessages.count());
+
+    // must start with "StartTrace"
+    QCOMPARE(m_client->traceMessages.first().messageType, (int)QQmlProfilerClient::Event);
+    QCOMPARE(m_client->traceMessages.first().detailType, (int)QQmlProfilerClient::StartTrace);
+
+    // image starting to load
+    QCOMPARE(m_client->traceMessages[8].messageType, (int)QQmlProfilerClient::PixmapCacheEvent);
+    QCOMPARE(m_client->traceMessages[8].detailType, (int)QQmlProfilerClient::PixmapLoadingStarted);
+
+    // image loaded
+    QCOMPARE(m_client->traceMessages[9].messageType, (int)QQmlProfilerClient::PixmapCacheEvent);
+    QCOMPARE(m_client->traceMessages[9].detailType, (int)QQmlProfilerClient::PixmapLoadingFinished);
+
+    // image size
+    QCOMPARE(m_client->traceMessages[10].messageType, (int)QQmlProfilerClient::PixmapCacheEvent);
+    QCOMPARE(m_client->traceMessages[10].detailType, (int)QQmlProfilerClient::PixmapSizeKnown);
+    QCOMPARE(m_client->traceMessages[10].line, 2); // width
+    QCOMPARE(m_client->traceMessages[10].column, 2); // height
+
+    // cache size
+    QCOMPARE(m_client->traceMessages[11].messageType, (int)QQmlProfilerClient::PixmapCacheEvent);
+    QCOMPARE(m_client->traceMessages[11].detailType, (int)QQmlProfilerClient::PixmapCacheCountChanged);
+
+    // must end with "EndTrace"
+    QCOMPARE(m_client->traceMessages.last().messageType, (int)QQmlProfilerClient::Event);
+    QCOMPARE(m_client->traceMessages.last().detailType, (int)QQmlProfilerClient::EndTrace);
+
+}
+
 void tst_QQmlProfilerService::profileOnExit()
 {
     connect(true, "exit.qml");
index 6585f7e..ff3140f 100644 (file)
@@ -182,6 +182,8 @@ void QQmlDebugProcess::processAppOutput()
 {
     m_mutex.lock();
 
+    bool outputFromAppItself = false;
+
     QString newOutput = m_process.readAll();
     m_output.append(newOutput);
     m_outputBuffer.append(newOutput);
@@ -208,7 +210,13 @@ void QQmlDebugProcess::processAppOutput()
                 m_eventLoop.quit();
                 continue;
             }
+        } else {
+            // set to true if there is output not coming from the debugger
+            outputFromAppItself = true;
         }
     }
     m_mutex.unlock();
+
+    if (outputFromAppItself)
+        emit readyReadStandardOutput();
 }
index 363aabb..11b16a1 100644 (file)
@@ -98,6 +98,9 @@ public:
     QString output() const;
     void stop();
 
+signals:
+    void readyReadStandardOutput();
+
 private slots:
     void timeout();
     void processAppOutput();