summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2016-05-30 13:30:02 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2016-06-10 07:32:07 +0000
commit2870aaa09b86080941d7f44aa8910c73f6ce99c2 (patch)
treed4565fdc550c615846a729b91c96683089057850
parentc1c66f5b34aa6a4e721ab3e7fcd067777ba5fca7 (diff)
add QmlMessageTrace
A utility to be instantiated in the application-under-test, which will install itself as the message handler, parse the output of the requested logging categories for tracing, and generate a message trace in QML. Change-Id: I3961c27d00ecead5a1204cb2f6928af4f9da26aa Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r--README9
-rw-r--r--examples/hover.qml79
-rw-r--r--examples/traceHover.qml51
-rw-r--r--examples/traces/flickable.qml (renamed from examples/flickable.qml)0
-rw-r--r--examples/traces/mpta.qml (renamed from examples/mpta.qml)0
-rw-r--r--imports/Diagrams/UmlSequence/Message.qml14
-rw-r--r--imports/Diagrams/UmlSequence/ObjectInstance.qml2
-rw-r--r--src/src.pro3
-rw-r--r--src/trace/plugin.cpp55
-rw-r--r--src/trace/qmldir2
-rw-r--r--src/trace/qmlmessagetrace.cpp503
-rw-r--r--src/trace/qmlmessagetrace_p.h121
-rw-r--r--src/trace/trace.pro24
13 files changed, 852 insertions, 11 deletions
diff --git a/README b/README
index 1935945..c230e42 100644
--- a/README
+++ b/README
@@ -1,15 +1,12 @@
The initial goal of the UmlQuick project is to automatically generate
-message trace diagrams from a declarative QML form by instrumenting the
+message trace diagrams via a declarative QML form by instrumenting the
code being traced. Perhaps other types of diagrams could be created in
QML too. It was inspired by the UMLGraph project
http://www.umlgraph.org/doc/seq-intro.html but rewritten because I
wanted to try to use QML instead of gnu pic macros.
-The code here is only for viewing the generated QML, and there are a couple
-of examples. Generating QML inside core parts of Qt depends on
-https://codereview.qt-project.org/#change,43201
-and there is an example patch for qtbase to trace touch events here
-https://codereview.qt-project.org/#change,65307
+There are some components for viewing the generated QML, and a few
+examples: how to generate the trace, and examples of complete traces.
It should work with Qt 5.7 or newer (due to some limited usage of
QtQuick.Controls 2).
diff --git a/examples/hover.qml b/examples/hover.qml
new file mode 100644
index 0000000..a807d50
--- /dev/null
+++ b/examples/hover.qml
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Labs UmlQuick project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.8
+import Qt.labs.UmlQuick.Trace 1.0
+
+Rectangle {
+ color: outerMA.containsMouse ? "lightsteelblue" : "gray"
+ width: 100; height: 400
+ objectName: "outerRect"
+
+ MessageTrace {
+ category: "qt.quick.hover.trace"
+ outputPrefix: "hoverSequence-"
+ }
+
+ MouseArea {
+ id: outerMA
+ objectName: "outerMA"
+ anchors.fill: parent
+ hoverEnabled: true
+ }
+
+ Rectangle {
+ color: "blue"
+ border.color: innerMA.containsMouse ? "white" : "transparent"
+ width: 80; height: 80; x: 10; y: 10
+ objectName: "innerRect"
+
+ MouseArea {
+ id: innerMA
+ objectName: "innerMA"
+ anchors.fill: parent
+// propagateHoverEvents: true // proposed in https://codereview.qt-project.org/#/c/160688/
+ hoverEnabled: true
+ onContainsMouseChanged: Qt.quit() // stop after this to avoid an excessive message trace
+ }
+ }
+ Shortcut {
+ sequence: StandardKey.Quit
+ onActivated: Qt.quit() // control-Q will quit at any time, and write the message trace
+ }
+}
diff --git a/examples/traceHover.qml b/examples/traceHover.qml
new file mode 100644
index 0000000..5a04ded
--- /dev/null
+++ b/examples/traceHover.qml
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Labs UmlQuick project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import Qt.labs.UmlQuick.Trace 1.0
+
+/*
+ This is a means of instrumenting any pure-QML application to generate a
+ sequence diagram showing how hover events are propagated. Run it like this:
+
+ qml traceHover.qml testApplication.qml
+*/
+MessageTrace {
+ category: "qt.quick.hover.trace"
+ outputPrefix: "hoverSequence-"
+}
diff --git a/examples/flickable.qml b/examples/traces/flickable.qml
index 2079fbd..2079fbd 100644
--- a/examples/flickable.qml
+++ b/examples/traces/flickable.qml
diff --git a/examples/mpta.qml b/examples/traces/mpta.qml
index a0932c0..a0932c0 100644
--- a/examples/mpta.qml
+++ b/examples/traces/mpta.qml
diff --git a/imports/Diagrams/UmlSequence/Message.qml b/imports/Diagrams/UmlSequence/Message.qml
index 19d38cf..92b441f 100644
--- a/imports/Diagrams/UmlSequence/Message.qml
+++ b/imports/Diagrams/UmlSequence/Message.qml
@@ -52,8 +52,14 @@ MouseArea {
hoverEnabled: true
z: 1
property bool toSelf: from && from === to
- property bool backwards: from ? from.x > to.x : false
+ property bool backwards: from && to ? from.x > to.x : false
property real timestamp: 0
+ Text {
+ x: -root.x
+ text: root.timestamp
+ color: "darkblue"
+ font.pointSize: 8
+ }
Rectangle {
width: 150
height: 20
@@ -89,10 +95,10 @@ MouseArea {
x: backwards || toSelf ? 0 : parent.width - width
}
ToolTip {
- text: root.methodSignature + "\n" + root.backtrace + "\n\nparams:\n" + root.params
+ text: root.methodSignature + "\n" + root.backtrace + "\n\nparams:\n" + root.params + "\n\ntimestamp: " + root.timestamp
visible: root.containsMouse
}
- anchors.left: backwards ? to.horizontalCenter : from ? from.horizontalCenter : undefined
- anchors.right: backwards ? from.horizontalCenter : toSelf ? undefined : to.horizontalCenter
+ anchors.left: backwards && to ? to.horizontalCenter : from ? from.horizontalCenter : undefined
+ anchors.right: backwards && from ? from.horizontalCenter : toSelf ? undefined : to ? to.horizontalCenter : undefined
}
diff --git a/imports/Diagrams/UmlSequence/ObjectInstance.qml b/imports/Diagrams/UmlSequence/ObjectInstance.qml
index b8dea17..a2b45f3 100644
--- a/imports/Diagrams/UmlSequence/ObjectInstance.qml
+++ b/imports/Diagrams/UmlSequence/ObjectInstance.qml
@@ -96,7 +96,7 @@ Rectangle {
Text {
id: rotatedLabel
color: "darkblue"
- text: objectClassLabel.text
+ text: objectClassLabel.text + " " + objectNameLabel.text
anchors.centerIn: parent
}
}
diff --git a/src/src.pro b/src/src.pro
new file mode 100644
index 0000000..2018864
--- /dev/null
+++ b/src/src.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+SUBDIRS += \
+ trace
diff --git a/src/trace/plugin.cpp b/src/trace/plugin.cpp
new file mode 100644
index 0000000..0ae90b2
--- /dev/null
+++ b/src/trace/plugin.cpp
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Shawn Rutledge
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Labs UmlQuick project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <QtQml>
+
+#include "qmlmessagetrace_p.h"
+
+class TracePlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
+
+public:
+ void registerTypes(const char *uri)
+ {
+ qmlRegisterType<QmlMessageTrace>(uri, 1, 0, "MessageTrace");
+ }
+};
+
+#include "plugin.moc"
diff --git a/src/trace/qmldir b/src/trace/qmldir
new file mode 100644
index 0000000..7cbebbb
--- /dev/null
+++ b/src/trace/qmldir
@@ -0,0 +1,2 @@
+module Qt.labs.UmlQuick.Trace
+plugin trace
diff --git a/src/trace/qmlmessagetrace.cpp b/src/trace/qmlmessagetrace.cpp
new file mode 100644
index 0000000..fe6e72d
--- /dev/null
+++ b/src/trace/qmlmessagetrace.cpp
@@ -0,0 +1,503 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Shawn Rutledge
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Labs UmlQuick project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include "qmlmessagetrace_p.h"
+#include <QCoreApplication>
+#include <QDateTime>
+#include <QDebug>
+#include <QFile>
+#include <QStringList>
+#include <private/qobject_p.h>
+
+/*!
+ \class QmlMessageTrace
+ \inmodule UmlQuick
+
+ \brief The QmlMessageTrace class provides a QML output stream for rendering
+ a UML \l {https://en.wikipedia.org/wiki/Sequence_diagram}{Sequence diagram}
+ (also known as a Message Trace diagram).
+
+ \section1 Basic Use
+
+ Any QML application can instantiate a trace object. It will immediately
+ begin capturing categorized log output. It induces each line of output from
+ the given category to also include a backtrace; then it tries to
+ reconstruct sequences of method calls by splicing the backtraces together.
+ The result is that often a few qCDebug calls in strategic "destination"
+ functions (such as functions which handle events, make state changes and
+ emit signals) will be enough to reconstruct a fairly complete UML Message
+ Trace diagram.
+
+ When the trace is completed, simply run the qml output file with the qml
+ runtime tool. It will import components from Diagrams.UmlSequence, which
+ should be installed already if you have built this module properly.
+
+ For example, to trace hover events in a QML application, create a file
+ traceHover.qml which looks like the following:
+
+ \code
+ import Qt.labs.UmlQuick.Trace 1.0
+
+ MessageTrace {
+ category: "qt.quick.hover.trace"
+ outputPrefix: "hoverSequence-"
+ }
+ \endcode
+
+ Then start up the qml runtime, loading traceHover.qml first, and then your
+ application. After exiting, you can view the sequence diagram via the
+ qml output.
+
+ \code
+ $ qml traceHover.qml testApplication.qml
+
+ $ ls hoverSequence*
+ hoverSequence-20160601-17.24.45.357.qml
+
+ $ qml hoverSequence-20160601-17.24.45.357.qml
+ \endcode
+
+ Alternatively, your application can be instrumented:
+ import Qt.labs.UmlQuick.Trace, declare a MessageTrace somewhere (perhaps as
+ a child of the root Window or Item, or in some component where the tracing
+ is relevant), and use the \l enabled property to start and stop tracing.
+ Each time \l enabled is set to false, a new qml file will be written. Its
+ filename will begin with the given outputPrefix, to which will be appended
+ the timestamp when the trace was started. In this way it's possible to view
+ traces more dynamically: if you want to trace the series of method calls
+ which led to a particular behavior, enable the trace when the user begins a
+ specific action, and disable it when that action is done.
+
+ In order to generate a valid sequence diagram, the \l category should
+ have a maximum of one log output per call. If it has more than one, the
+ diagram will show that the method called itself. That would be incorrect:
+ the self-call should be reserved to indicate recursion.
+
+ The category debug output should not include Q_FUNC_INFO, because that
+ information is already available in every log message, so including it
+ again would be redundant. Every qCDebug for tracing class methods should
+ include \c this as the first parameter. If you include extra information, e.g.
+ \code
+ qCDebug(QC_CATEGORY) << this << parameter1 << parameter2
+ \endcode
+ it will appear in tooltips (along with the backtraces) over the arrows on
+ the diagram. Typically you should include information about any interesting
+ method parameters; but too much information will make the diagram hard to
+ read.
+*/
+
+/*!
+ \fn QDebug::QDebug(QIODevice *device)
+
+ Constructs a debug stream that writes to the given \a device.
+*/
+
+QT_BEGIN_NAMESPACE
+
+//#define MT_DEBUG_ENABLED
+#ifdef MT_DEBUG_ENABLED
+#define MT_DEBUG printf
+#else
+#define MT_DEBUG(format, args...) ((void)0)
+#endif
+
+QHash<QByteArray, QList<QmlMessageTrace*> > QmlMessageTrace::m_categoryInstances;
+QtMessageHandler QmlMessageTrace::m_parentMessageHandler(nullptr);
+
+// Regex for stuff like QQuickMouseArea(0x16b9cf0, name="outerMA", parent=0x16fc070, geometry=0,0 100x400)
+// Captures only the part outside parentheses and the part inside
+QRegularExpression QmlMessageTrace::m_regexObjectFormatted(QStringLiteral(
+ "([\\w_]+)\\((.+)\\)"));
+
+// Regex for pointers like 0x16ec750
+QRegularExpression QmlMessageTrace::m_regexPointer(QStringLiteral(
+ "0[xX]([0-9a-fA-F]+)"));
+
+int QmlMessageTrace::m_refCount(0);
+
+static QString pointerHash(void* ptr)
+{
+ QByteArray ret;
+ ret.setNum((qulonglong)ptr, 16);
+ // qml doesn't like an ID to start with a digit
+ // so, shift 0-9 to g-p
+ for (int i = 0; i < ret.length(); ++i) {
+ QChar ch(ret[i]);
+ if (ch.isDigit())
+ ret[i] = (char)(ret[i] + 55);
+ }
+ if (ret.length() == 1)
+ return QStringLiteral("null");
+ return QLatin1String(ret);
+}
+
+void QmlMessageTrace::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &text)
+{
+ bool consumed = false;
+ if (m_categoryInstances.contains(context.category)) {
+ QList<QmlMessageTrace *> instances = m_categoryInstances.value(context.category);
+ QString formatted = qFormatLogMessage(type, context, text);
+ for (QmlMessageTrace *tracer : instances) {
+ tracer->log(type, context, formatted);
+ consumed = true;
+ }
+ }
+// qDebug() << Q_FUNC_INFO << context.category << text << "found tracers?" << m_categoryInstances.value(context.category);
+ if (!consumed)
+ m_parentMessageHandler(type, context, text);
+}
+
+QmlMessageTrace::QmlMessageTrace()
+ : QObject()
+ , m_previousTimestamp(0)
+ , m_enabled(true)
+ , m_category(nullptr)
+ , m_outputPrefix("messagetrace")
+{
+ ++m_refCount;
+ if (qEnvironmentVariableIsSet("QT_MESSAGE_PATTERN")) {
+ qFatal("please unset the QT_MESSAGE_PATTERN env variable before trying to use QmlMessageTrace");
+ QCoreApplication::exit(-1);
+ }
+ qSetMessagePattern(QStringLiteral("%{time process}|%{backtrace depth=20}|%{message}"));
+}
+
+QmlMessageTrace::~QmlMessageTrace()
+{
+ if (--m_refCount == 0)
+ qInstallMessageHandler(nullptr);
+ if (!m_messages.isEmpty())
+ writeQml();
+}
+
+void QmlMessageTrace::setCategory(QString cat)
+{
+ QByteArray category = cat.toUtf8();
+ if (m_category == category)
+ return;
+
+ if (m_categoryInstances.isEmpty()) {
+ // first-use init
+ QLoggingCategory::installFilter(&QmlMessageTrace::categoryFilter);
+ }
+
+ if (m_categoryInstances.contains(m_category))
+ m_categoryInstances[m_category].removeAll(this);
+ if (m_categoryInstances.contains(category))
+ m_categoryInstances[category].append(this);
+ else
+ m_categoryInstances.insert(category, QList<QmlMessageTrace *>() << this);
+ if (!m_parentMessageHandler) {
+ m_parentMessageHandler = qInstallMessageHandler(messageHandler);
+ }
+
+ m_category = category;
+ emit categoryChanged();
+}
+
+void QmlMessageTrace::setOutputPrefix(QString outputPrefix)
+{
+ if (m_outputPrefix == outputPrefix)
+ return;
+
+ m_outputPrefix = outputPrefix;
+ emit outputPrefixChanged();
+}
+
+void QmlMessageTrace::setEnabled(bool enabled)
+{
+ if (m_enabled == enabled)
+ return;
+
+ m_enabled = enabled;
+ if (!enabled)
+ writeQml();
+ emit enabledChanged();
+}
+
+void QmlMessageTrace::categoryFilter(QLoggingCategory *cat)
+{
+ if (m_categoryInstances.contains(cat->categoryName())) {
+ cat->setEnabled(QtDebugMsg, true);
+ // TODO warning, fatal
+ } else {
+ cat->setEnabled(QtDebugMsg, false);
+ }
+//MT_DEBUG("%s %d\n", cat->categoryName(), cat->isDebugEnabled());
+}
+
+QString QmlMessageTrace::category() const
+{
+ return QString::fromUtf8(m_category);
+}
+
+// A utility function to make QRegularExpression if/else matches possible
+static bool hasMatch(const QRegularExpression &regex, QRegularExpressionMatch &match, const QString &text) {
+ match = regex.match(text);
+ return match.hasMatch();
+}
+
+static void * stringToPointer(const QString &str) {
+ bool ptrOk = false;
+ void *ret = (void *)(str.toULongLong(&ptrOk, 16));
+ if (!ptrOk)
+ ret = nullptr;
+ return ret;
+}
+
+void QmlMessageTrace::parseClassAndMethod(const QString &classAndMethod, QString &className, QString &methodName)
+{
+ int scopingIdx = classAndMethod.indexOf(QStringLiteral("::"));
+ if (scopingIdx >= 0) {
+ className = classAndMethod.left(scopingIdx).split(' ', QString::SkipEmptyParts).last();
+ int leftParenIdx = classAndMethod.indexOf('(', scopingIdx);
+ scopingIdx += 2; // get past it
+ methodName = leftParenIdx >= 0 ? classAndMethod.mid(scopingIdx, leftParenIdx - scopingIdx) : classAndMethod.mid(scopingIdx);
+ if (className.startsWith('Q') && className.endsWith(QStringLiteral("Application")))
+ className = QStringLiteral("QGuiApplication");
+ int qmlSuffixIdx = className.indexOf(QStringLiteral("_QML"));
+ if (qmlSuffixIdx > 0)
+ className = className.left(qmlSuffixIdx);
+ }
+}
+
+void QmlMessageTrace::addObjectInstance(void *obj, const QString &objClass)
+{
+ QString id = obj ? pointerHash(obj) : QStringLiteral("ufo_") + objClass;
+ m_tracedObjectsById.insert(id, objClass);
+ m_recentObjectsByClass.insert(objClass, obj);
+ if (objClass == QStringLiteral("QQuickWindow"))
+ m_recentObjectsByClass.insert(QStringLiteral("QWindow"), obj);
+ if (obj && objClass.startsWith('Q') && !objClass.endsWith("Private")) {
+ QObject * q = static_cast<QObject*>(obj);
+ m_objects.insert(obj, q);
+ m_objects.insert(QObjectPrivate::get(q), q);
+ }
+}
+
+void QmlMessageTrace::writeObjectInstance(QFile &f, QObject *o)
+{
+ if (!o)
+ return;
+ QString className = o->metaObject()->className();
+ int qmlSuffixIdx = className.indexOf(QStringLiteral("_QML"));
+ if (qmlSuffixIdx > 0)
+ className = className.left(qmlSuffixIdx);
+ QString oName = o->objectName();
+ if (oName.isEmpty())
+ oName = QStringLiteral("0x%1").arg(qulonglong(o), 0, 16);
+ f.write(QStringLiteral(" ObjectInstance { id: %1; objectName: \"%2\"; objectClass: \"%3\" }\n")
+ .arg(pointerHash(o)).arg(oName).arg(className).toUtf8());
+}
+
+void QmlMessageTrace::logBacktrace(QStringList trace)
+{
+ if (trace.length() < 2)
+ return;
+ Message m;
+ m.timestamp = m_previousTimestamp;
+ m.callerPointer = nullptr;
+ m.calleePointer = nullptr;
+ QString calleeClass;
+ parseClassAndMethod(trace.takeFirst(), calleeClass, m.calleeMethod);
+ if (m.calleeMethod.isEmpty())
+ return;
+ parseClassAndMethod(trace.first(), m.callerClass, m.callerMethod);
+ m.backtrace = trace;
+ logBacktrace(trace);
+ m_messages.append(m);
+}
+
+void QmlMessageTrace::log(QtMsgType type, const QMessageLogContext &context, const QString &rawText)
+{
+ if (type != QtDebugMsg)
+ return; // so far we don't handle warning and critical
+
+ MT_DEBUG("\n%s\n", qPrintable(rawText));
+ Message m;
+
+ // Extract info from pipe-separated stuff, and clean up the backtrace
+ m.backtrace = rawText.split('|');
+ m.params = m.backtrace.takeLast(); // %{message}
+ m.timestamp = m.backtrace.takeFirst().trimmed().toDouble(); // %{time process}
+ {
+ int idx = m.backtrace.indexOf(QStringLiteral("QDebug::~QDebug"));
+ for (int i = 0; i <= idx; ++i)
+ m.backtrace.removeFirst();
+ }
+ m.backtrace.removeFirst(); // the callee
+
+ // --------
+ // Callee
+ QString calleeClassAndMethod = QLatin1String(context.function);
+ QString calleeClassName;
+
+ parseClassAndMethod(calleeClassAndMethod, calleeClassName, m.calleeMethod);
+
+ QString callee0xPointerStr;
+ m.calleePointer = nullptr;
+ QRegularExpressionMatch match;
+ if (hasMatch(m_regexObjectFormatted, match, m.params) && match.capturedStart() == 0 && match.lastCapturedIndex() == 2) {
+ // Got formatted info about "this" which was supposed to be the first parameter to qCDebug
+ // something like QQuickMouseArea(0x16b9cf0, name="outerMA", parent=0x16fc070, geometry=0,0 100x400)
+// calleeClassName = match.captured(1).toUtf8(); // strictly unnecessary: we got it via parseClassAndMethod above
+ QString calleeInfo = match.captured(2);
+ match = m_regexPointer.match(calleeInfo);
+ callee0xPointerStr = match.captured(0);
+ m.calleePointer = stringToPointer(match.captured(1));
+ if (m.calleePointer) {
+ calleeInfo.remove(0, match.capturedEnd(1));
+ if (calleeInfo.startsWith(','))
+ calleeInfo.remove(0, 1);
+ calleeInfo = calleeInfo.trimmed();
+ }
+// MT_DEBUG("callee %s::%s %p guts %s from %s\n", qPrintable(calleeClassName), qPrintable(m.calleeMethod), m.calleePointer, qPrintable(calleeInfo), qPrintable(m.params));
+ } else if (hasMatch(m_regexPointer, match, m.params) && match.capturedStart() == 0) {
+ // Got just a plain pointer to "this" which was supposed to be the first parameter to qCDebug
+// MT_DEBUG("captured pointer %s @%d from %s\n", qPrintable(match.captured(1)), match.capturedStart(), qPrintable(m.params));
+ callee0xPointerStr = match.captured(0);
+ m.calleePointer = stringToPointer(match.captured(1));
+ } else {
+ printf("ignoring call to unknown object instance: %s %s:%s\n", context.category, context.function, qPrintable(m.params));
+ }
+ MT_DEBUG(" callee %s::%s %p\n", qPrintable(calleeClassName), qPrintable(m.calleeMethod), m.calleePointer);
+
+ if (m.calleePointer) {
+ if (!m_tracedObjectsById.contains(pointerHash(m.calleePointer)))
+ addObjectInstance(m.calleePointer, calleeClassName);
+ } else if (m_recentObjectsByClass.contains(calleeClassName)) {
+ // callee object is unknown; assume it's the last one with the same classname that we've seen
+ m.calleePointer = m_recentObjectsByClass.value(calleeClassName);
+ } else if (!calleeClassName.isEmpty()) {
+ // totally unknown object, but we do know the classname
+ addObjectInstance(m.calleePointer, calleeClassName);
+ }
+ if (calleeClassName.isEmpty())
+ printf("failed to find callee class name in %s\n", qPrintable(rawText));
+
+ // --------
+ // Caller
+ m.callerPointer = nullptr;
+
+ // Guess the caller object ID; the m.backtrace can't tell us the caller's "this" pointer, unfortunately
+ if (!m.backtrace.isEmpty()) {
+ QString callerClassAndMethod = m.backtrace.first();
+ parseClassAndMethod(callerClassAndMethod, m.callerClass, m.callerMethod);
+ if (m_recentObjectsByClass.contains(m.callerClass)) {
+ m.callerPointer = m_recentObjectsByClass.value(calleeClassName);
+ } else if (!m.callerClass.isEmpty()) {
+ addObjectInstance(nullptr, m.callerClass);
+ }
+ MT_DEBUG(" caller %p %s :: %s from %s\n", m.callerPointer, qPrintable(m.callerClass), qPrintable(m.callerMethod), qPrintable(callerClassAndMethod));
+ // TODO deduce where we are in the message trace from caller method
+
+ QStringList leadingBacktrace = m.backtrace;
+ leadingBacktrace.removeFirst();
+
+ m_previousTimestamp = m.timestamp;
+
+ logBacktrace(leadingBacktrace);
+ }
+
+// QString rawLog = rawText;
+// rawLog.replace(QStringLiteral("|"), QStringLiteral("\\n"));
+// rawLog.replace(QStringLiteral("\""), QStringLiteral("\\\""));
+
+ m.params.replace(QStringLiteral("\""), QStringLiteral("\\\""));
+ // Now that both objects exist we can declare the arrow
+// if (isReturn)
+// m_messageElement.append(" Return { \n from: " + m_fromObjectId + "\n to: " +
+// m_toObjectId + "\n fromMethod: \"" + m_fromMethod + "\"\n");
+// else
+
+ m.calleeSignature = QLatin1String(context.function);
+ m_messages.append(m);
+}
+
+void QmlMessageTrace::writeQml()
+{
+ QString filePath = QString::fromLocal8Bit("%1%2.qml").arg(m_outputPrefix).arg(QDateTime::currentDateTime().toString(Qt::ISODate));
+ MT_DEBUG("-> %s\n", qPrintable(filePath));
+ QFile f(filePath);
+ if (f.open(QFile::WriteOnly)) {
+ f.write("import QtQuick 2.0\nimport Diagrams.UmlSequence 1.0\n\nUmlSequenceDiagram {\n");
+ QSet<void *> objectsWritten;
+ QSet<QString> ufosWritten;
+ for (Message &m : m_messages) {
+ QObject *callee = m_objects.value(m.calleePointer);
+ m.calleePointer = callee;
+ if (callee && !objectsWritten.contains(callee)) {
+ writeObjectInstance(f, callee);
+ objectsWritten.insert(callee);
+ }
+ QObject *caller = m_objects.value(m.callerPointer);
+ if (!caller) {
+ void *p = m_recentObjectsByClass.value(m.callerClass);
+ caller = m_objects.value(p);
+ }
+ m.callerPointer = caller;
+ if (caller) {
+ if (!objectsWritten.contains(caller)) {
+ writeObjectInstance(f, caller);
+ objectsWritten.insert(caller);
+ }
+ } else if (!ufosWritten.contains(m.callerClass)) {
+ f.write(QStringLiteral(" ObjectInstance { id: %1; objectName: \"%1\"; objectClass: \"%2\" }\n")
+ .arg(QStringLiteral("ufo_") + m.callerClass).arg(m.callerClass).toUtf8());
+ ufosWritten.insert(m.callerClass);
+ }
+ f.write(m.toQml().toUtf8());
+ }
+ f.write("}\n");
+ f.close();
+ } else {
+ printf("failed to write %s", qPrintable(filePath));
+ }
+}
+
+QString QmlMessageTrace::Message::toQml() const
+{
+ QString callerId = callerPointer ? pointerHash(callerPointer) : QStringLiteral("ufo_") + callerClass;
+ return QStringLiteral(
+ " Message { \n from: %1\n to: %2\n method: \"%3\"\n methodSignature: \"%4\"\n"
+ " fromMethod: \"%5\"\n timestamp: %6\n backtrace: \"%7\"\n params: \"%8\"\n }\n")
+ .arg(callerId).arg(pointerHash(calleePointer)).arg(calleeMethod).arg(calleeSignature)
+ .arg(callerMethod).arg(timestamp).arg(backtrace.join(QStringLiteral("\\n"))).arg(params);
+}
+
+QT_END_NAMESPACE
diff --git a/src/trace/qmlmessagetrace_p.h b/src/trace/qmlmessagetrace_p.h
new file mode 100644
index 0000000..737b20d
--- /dev/null
+++ b/src/trace/qmlmessagetrace_p.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Shawn Rutledge
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Labs UmlQuick project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef QMLMESSAGETRACE_H
+#define QMLMESSAGETRACE_H
+
+#include <QFile>
+#include <QHash>
+#include <QLoggingCategory>
+#include <QRegularExpression>
+
+QT_BEGIN_NAMESPACE
+
+class QmlMessageTrace : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged)
+ Q_PROPERTY(QString outputPrefix READ outputPrefix WRITE setOutputPrefix NOTIFY outputPrefixChanged)
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+
+public:
+ QmlMessageTrace();
+ ~QmlMessageTrace();
+
+ QString category() const;
+ void setCategory(QString category);
+
+ QString outputPrefix() const { return m_outputPrefix; }
+ void setOutputPrefix(QString outputPrefix);
+
+ bool enabled() const { return m_enabled; }
+ void setEnabled(bool enabled);
+
+signals:
+ void categoryChanged();
+ void outputPrefixChanged();
+ void enabledChanged();
+
+private:
+ static void categoryFilter(QLoggingCategory *cat);
+ static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &text);
+ void log(QtMsgType type, const QMessageLogContext &context, const QString &rawText);
+ void logBacktrace(QStringList trace);
+ void parseClassAndMethod(const QString &classAndMethod, QString &className, QString &methodName);
+ void addObjectInstance(void *obj, const QString &objClass);
+ void writeObjectInstance(QFile &f, QObject *o);
+ void writeQml();
+
+private:
+ struct Message {
+ double timestamp;
+ QtMsgType type;
+ void *callerPointer;
+ QString callerClass;
+ QString callerMethod;
+ void *calleePointer;
+ QString calleeMethod;
+ QString calleeSignature;
+ QStringList backtrace;
+ QString params;
+ QString toQml() const;
+ };
+
+ static QHash<QByteArray, QList<QmlMessageTrace*> > m_categoryInstances;
+ static QtMessageHandler m_parentMessageHandler;
+
+ static QRegularExpression m_regexObjectFormatted;
+ static QRegularExpression m_regexPointer;
+
+ static int m_refCount;
+
+ QList<Message> m_messages;
+ double m_previousTimestamp;
+
+ QHash<QString, QString> m_tracedObjectsById;
+ QHash<QString, void*> m_recentObjectsByClass;
+ QHash<void*, QObject*> m_objects; // both QObjects and QObjectPrivates have entries here
+
+ bool m_enabled;
+ QByteArray m_category;
+ QString m_outputPrefix;
+};
+
+QT_END_NAMESPACE
+
+#endif // QMLMESSAGETRACE_H
diff --git a/src/trace/trace.pro b/src/trace/trace.pro
new file mode 100644
index 0000000..fac422e
--- /dev/null
+++ b/src/trace/trace.pro
@@ -0,0 +1,24 @@
+TEMPLATE = lib
+TARGET = trace
+
+QT += quick core_private
+
+CONFIG += qt plugin
+
+SOURCES += \
+ plugin.cpp \
+ qmlmessagetrace.cpp
+
+HEADERS += \
+ qmlmessagetrace_p.h
+
+TARGETPATH = Qt/labs/UmlQuick/Trace
+
+OTHER_FILES = qmldir
+
+target.path = $$[QT_INSTALL_QML]/$$TARGETPATH
+
+qmldir.files = qmldir
+qmldir.path = $$[QT_INSTALL_QML]/$$TARGETPATH
+
+INSTALLS = target qmldir