Sophie

Sophie

distrib > Mageia > 5 > i586 > by-pkgid > cf746698214707f972e669b661d0ae59 > files > 72

kdepim4-4.14.10-1.3.mga5.src.rpm

From 5f552cb453b6bd4e98d637d40e05282a63598af5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Vr=C3=A1til?= <daniel.vratil@kdab.com>
Date: Thu, 2 Feb 2017 11:58:51 +0100
Subject: [PATCH 72/74] Query debugger: show connections, transactions and
 queries

Uses the new methods from the org.freedesktop.Akonadi.StorageDebugger
interface.

Idea for future: use a graph to better visualize parallel connections
and transactions.
---
 CMakeLists.txt                   |   1 -
 akonadiconsole/CMakeLists.txt    |   4 +
 akonadiconsole/querydebugger.cpp | 655 ++++++++++++++++++++++++++++++---------
 akonadiconsole/querydebugger.h   |  43 ++-
 akonadiconsole/querydebugger.ui  |  89 ++++++
 5 files changed, 642 insertions(+), 150 deletions(-)
 create mode 100644 akonadiconsole/querydebugger.ui

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 49ff815a47..f93e500a9d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -338,4 +338,3 @@ feature_summary(WHAT ALL
                      INCLUDE_QUIET_PACKAGES
                      FATAL_ON_MISSING_REQUIRED_PACKAGES
                )
-
diff --git a/akonadiconsole/CMakeLists.txt b/akonadiconsole/CMakeLists.txt
index ec48731154..ddc88910aa 100644
--- a/akonadiconsole/CMakeLists.txt
+++ b/akonadiconsole/CMakeLists.txt
@@ -72,6 +72,9 @@ qt4_add_dbus_interface(akonadiconsole_bin_SRCS
   notificationsourceinterface
 )
 
+set_source_files_properties(${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.StorageDebugger.xml
+    PROPERTIES INCLUDE querydebugger.h
+)
 qt4_add_dbus_interface(akonadiconsole_bin_SRCS
   ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.StorageDebugger.xml
   storagedebuggerinterface
@@ -89,6 +92,7 @@ kde4_add_ui_files(akonadiconsole_bin_SRCS
   rawsocketconsole.ui
   browserwidget_contentview.ui
   collectioninternalspage.ui
+  querydebugger.ui
 )
 
 kde4_add_app_icon(akonadiconsole_bin_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/hi*-app-akonadiconsole.png")
diff --git a/akonadiconsole/querydebugger.cpp b/akonadiconsole/querydebugger.cpp
index 040543873d..cc5a72d443 100644
--- a/akonadiconsole/querydebugger.cpp
+++ b/akonadiconsole/querydebugger.cpp
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2013  Daniel Vrátil <dvratil@redhat.com>
+ * Copyright (C) 2017  Daniel Vrátil <dvratil@kde.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,16 +19,19 @@
  */
 
 #include "querydebugger.h"
+#include "ui_querydebugger.h"
+#include "storagedebuggerinterface.h"
 
-#include <QtGui/QVBoxLayout>
-#include <QtGui/QCheckBox>
 #include <QtGui/QMenu>
+#include <QtGui/QApplication>
 
-#include <QToolButton>
 #include <QAbstractListModel>
 #include <QSortFilterProxyModel>
-#include <QTreeView>
 #include <QHeaderView>
+#include <QDateTime>
+#include <QIcon>
+#include <QAbstractItemModel>
+#include <QFileDialog>
 
 #include <QtDBus/QDBusConnection>
 #include <QtDBus/QDBusArgument>
@@ -40,30 +44,460 @@
 #include <akonadi/servermanager.h>
 #include <akonadi/control.h>
 
-#include <KTextEdit>
-#include <KGlobalSettings>
 #include <KLocalizedString>
-#include <KDebug>
-#include <KIcon>
+#include <KColorScheme>
 
 #include <algorithm>
 
 Q_DECLARE_METATYPE(QList< QList<QVariant> >)
 
-struct QueryInfo
+QDBusArgument &operator<<(QDBusArgument &arg, const DbConnection &con)
 {
-  QString query;
-  quint64 duration;
-  quint64 calls;
+    arg.beginStructure();
+    arg << con.id
+        << con.name
+        << con.start
+        << con.transactionStart;
+    arg.endStructure();
+    return arg;
+}
 
-  bool operator<(const QString& other) const
-  {
-    return query < other;
-  }
+const QDBusArgument &operator>>(const QDBusArgument &arg, DbConnection &con)
+{
+    arg.beginStructure();
+    arg >> con.id
+        >> con.name
+        >> con.start
+        >> con.transactionStart;
+    arg.endStructure();
+    return arg;
+}
+
+struct QueryInfo {
+    QString query;
+    quint64 duration;
+    quint64 calls;
+
+    bool operator<(const QString &other) const
+    {
+        return query < other;
+    }
 };
 
 Q_DECLARE_TYPEINFO(QueryInfo, Q_MOVABLE_TYPE);
 
+class QueryTreeModel : public QAbstractItemModel
+{
+    Q_OBJECT
+
+    class Node
+    {
+    public:
+        virtual ~Node() {}
+
+        Node *parent;
+        enum Type {
+            Connection, Transaction, Query
+        };
+        Type type;
+        qint64 start;
+        uint duration;
+    };
+
+    class Query : public Node
+    {
+    public:
+        QString query;
+        QString error;
+    };
+
+    class Transaction : public Query
+    {
+    public:
+        ~Transaction()
+        {
+            qDeleteAll(queries);
+        }
+
+        enum TransactionType {
+            Begin, Commit, Rollback
+        };
+        TransactionType transactionType;
+        QVector<Query*> queries;
+    };
+
+    class Connection : public Node
+    {
+    public:
+        ~Connection()
+        {
+            qDeleteAll(queries);
+        }
+
+        QString name;
+        QVector<Node*> queries; // FIXME: Why can' I use QVector<Query*> here??
+    };
+
+public:
+    QueryTreeModel(QObject *parent)
+        : QAbstractItemModel(parent)
+    {}
+
+    ~QueryTreeModel()
+    {
+        qDeleteAll(mConnections);
+    }
+
+public Q_SLOTS:
+    void clear()
+    {
+        beginResetModel();
+        qDeleteAll(mConnections);
+        mConnections.clear();
+        endResetModel();
+    }
+
+    void addConnection(qlonglong id, const QString &name, qlonglong timestamp)
+    {
+        auto con = new Connection;
+        con->parent = nullptr;
+        con->type = Node::Connection;
+        con->name = name.isEmpty() ? QLatin1String("<unnamed connection>") : name;
+        con->start = timestamp;
+        beginInsertRows(QModelIndex(), mConnections.count(), mConnections.count());
+        mConnections << con;
+        mConnectionById.insert(id, con);
+        endInsertRows();
+    }
+
+    void updateConnection(qlonglong id, const QString &name)
+    {
+        auto con = mConnectionById.value(id);
+        if (!con) {
+            return;
+        }
+
+        con->name = name;
+        const QModelIndex index = createIndex(mConnections.indexOf(con), columnCount() - 1, con);
+        Q_EMIT dataChanged(index, index.sibling(index.row(), 5));
+    }
+
+    void addTransaction(qlonglong connectionId, qlonglong timestamp, uint duration, const QString &error)
+    {
+        auto con = mConnectionById.value(connectionId);
+        if (!con) {
+            return;
+        }
+
+        auto trx = new Transaction;
+        trx->parent = con;
+        trx->type = Node::Transaction;
+        trx->start = timestamp;
+        trx->duration = duration;
+        trx->transactionType = Transaction::Begin;
+        trx->error = error.trimmed();
+        const QModelIndex conIdx = createIndex(mConnections.indexOf(con), 0, con);
+        beginInsertRows(conIdx, con->queries.count(), con->queries.count());
+        con->queries << trx;
+        endInsertRows();
+    }
+
+    void closeTransaction(qlonglong connectionId, bool commit, qlonglong timestamp, uint,
+                          const QString &error)
+    {
+        auto con = mConnectionById.value(connectionId);
+        if (!con) {
+            return;
+        }
+
+        // Find the last open transaction and change it to closed
+        for (int i = con->queries.count() - 1; i >= 0; i--) {
+            Node *node = con->queries[i];
+            if (node->type == Node::Transaction) {
+                Transaction *trx = static_cast<Transaction*>(node);
+                if (trx->transactionType != Transaction::Begin) {
+                    continue;
+                }
+
+                trx->transactionType = commit ? Transaction::Commit : Transaction::Rollback;
+                trx->duration = timestamp - trx->start;
+                trx->error = error.trimmed();
+
+                const QModelIndex trxIdx = createIndex(i, 0, trx);
+                Q_EMIT dataChanged(trxIdx, trxIdx.sibling(trxIdx.row(), columnCount() - 1));
+                return;
+            }
+        }
+    }
+
+    void addQuery(qlonglong connectionId, const QString &queryStr, qlonglong timestamp,
+                  uint duration, const QString &error)
+    {
+        auto con = mConnectionById.value(connectionId);
+        if (!con) {
+            return;
+        }
+
+        auto query = new Query;
+        query->type = Node::Query;
+        query->query = queryStr;
+        query->start = timestamp;
+        query->duration = duration;
+        query->error = error.trimmed();
+
+        if (con->queries.isEmpty() || con->queries.last()->type == Node::Query) {
+            query->parent = con;
+            beginInsertRows(createIndex(mConnections.indexOf(con), 0, con),
+                            con->queries.count(), con->queries.count());
+            con->queries << query;
+            endInsertRows();
+        } else {
+            auto trx = static_cast<Transaction*>(con->queries.last());
+            query->parent = trx;
+            beginInsertRows(createIndex(con->queries.indexOf(trx), 0, trx),
+                            trx->queries.count(), trx->queries.count());
+            trx->queries << query;
+            endInsertRows();
+        }
+    }
+
+    void dumpRow(QFile &file, const QModelIndex &idx, int depth)
+    {
+        if (idx.isValid()) {
+            QTextStream stream(&file);
+            for (int i = 0; i < depth; ++i) {
+                stream << QLatin1String("  |");
+            }
+            stream << QLatin1String("- ");
+
+            Node *node = reinterpret_cast<Node*>(idx.internalPointer());
+            switch (node->type) {
+            case Node::Connection: {
+                Connection *con = static_cast<Connection*>(node);
+                stream << con->name << "    " << fromMSecsSinceEpoch(con->start);
+                break;
+            }
+            case Node::Transaction: {
+                Transaction *trx = static_cast<Transaction*>(node);
+                switch (trx->transactionType) {
+                case Transaction::Begin: stream << QLatin1String("BEGIN"); break;
+                case Transaction::Commit: stream << QLatin1String("COMMIT"); break;
+                case Transaction::Rollback: stream << QLatin1String("ROLLBACK"); break;
+                }
+                stream << "    " << fromMSecsSinceEpoch(trx->start);
+                if (trx->transactionType > Transaction::Begin) {
+                    stream << " - " << fromMSecsSinceEpoch(trx->start + trx->duration);
+                }
+                break;
+            }
+            case Node::Query: {
+                Query *query = static_cast<Query*>(node);
+                stream << query->query << "    " << fromMSecsSinceEpoch(query->start) << ", took " << query->duration << " ms";
+                break;
+            }
+            }
+
+            if (node->type >= Node::Transaction) {
+                Query *query = static_cast<Query*>(node);
+                if (!query->error.isEmpty()) {
+                    stream << '\n';
+                    for (int i = 0; i < depth; ++i) {
+                        stream << QLatin1String("  |");
+                    }
+                    stream << QLatin1String("  Error: ") << query->error;
+                }
+            }
+
+            stream << '\n';
+        }
+
+        for (int i = 0, c = rowCount(idx); i < c; ++i) {
+            dumpRow(file, index(i, 0, idx), depth + 1);
+        }
+    }
+
+public:
+    int rowCount(const QModelIndex &parent) const
+    {
+        if (!parent.isValid()) {
+            return mConnections.count();
+        }
+
+        Node *node = reinterpret_cast<Node*>(parent.internalPointer());
+        switch (node->type) {
+        case Node::Connection:
+            return static_cast<Connection*>(node)->queries.count();
+        case Node::Transaction:
+            return static_cast<Transaction*>(node)->queries.count();
+        case Node::Query:
+            return 0;
+        }
+
+        Q_ASSERT(false);
+        return 0;
+    }
+
+    int columnCount(const QModelIndex &parent = QModelIndex()) const
+    {
+        Q_UNUSED(parent);
+        return 5;
+    }
+
+    QModelIndex parent(const QModelIndex &child) const
+    {
+        if (!child.isValid() || !child.internalPointer()) {
+            return QModelIndex();
+        }
+
+        Node *childNode = reinterpret_cast<Node*>(child.internalPointer());
+        // childNode is a Connection
+        if (!childNode->parent) {
+            return QModelIndex();
+        }
+
+        // childNode is a query in transaction
+        if (childNode->parent->parent) {
+            Connection *connection = static_cast<Connection*>(childNode->parent->parent);
+            const int trxIdx = connection->queries.indexOf(childNode->parent);
+            return createIndex(trxIdx, 0, childNode->parent);
+        } else {
+            // childNode is a query without transaction or a transaction
+            return createIndex(mConnections.indexOf(static_cast<Connection*>(childNode->parent)),
+                               0, childNode->parent);
+        }
+    }
+
+    QModelIndex index(int row, int column, const QModelIndex &parent) const
+    {
+        if (!parent.isValid()) {
+            if (row < mConnections.count()) {
+                return createIndex(row, column, mConnections.at(row));
+            } else {
+                return QModelIndex();
+            }
+        }
+
+        Node *parentNode = reinterpret_cast<Node*>(parent.internalPointer());
+        switch (parentNode->type) {
+        case Node::Connection:
+            if (row < static_cast<Connection*>(parentNode)->queries.count()) {
+                return createIndex(row, column, static_cast<Connection*>(parentNode)->queries.at(row));
+            } else {
+                return QModelIndex();
+            }
+        case Node::Transaction:
+            if (row < static_cast<Transaction*>(parentNode)->queries.count()) {
+                return createIndex(row, column, static_cast<Transaction*>(parentNode)->queries.at(row));
+            } else {
+                return QModelIndex();
+            }
+        case Node::Query:
+            // Query can never have children
+            return QModelIndex();
+        }
+
+        Q_ASSERT(false);
+        return QModelIndex();
+    }
+
+    QVariant headerData(int section, Qt::Orientation orientation, int role) const
+    {
+        if (orientation != Qt::Horizontal || role != Qt::DisplayRole) {
+            return QVariant();
+        }
+
+        switch (section) {
+        case 0: return QLatin1String("Name / Query");
+        case 1: return QLatin1String("Started");
+        case 2: return QLatin1String("Ended");
+        case 3: return QLatin1String("Duration");
+        case 4: return QLatin1String("Error");
+        }
+
+        return QVariant();
+    }
+
+    QVariant data(const QModelIndex &index, int role) const
+    {
+        if (!index.isValid()) {
+            return QVariant();
+        }
+
+        Node *node = reinterpret_cast<Node*>(index.internalPointer());
+        switch (node->type) {
+        case Node::Connection:
+            return connectionData(static_cast<Connection*>(node), index.column(), role);
+        case Node::Transaction:
+            return transactionData(static_cast<Transaction*>(node), index.column(), role);
+        case Node::Query:
+            return queryData(static_cast<Query*>(node), index.column(), role);
+        }
+
+        Q_ASSERT(false);
+        return QVariant();
+    }
+
+private:
+    QString fromMSecsSinceEpoch(qint64 msecs) const
+    {
+        return QDateTime::fromMSecsSinceEpoch(msecs).toString(QLatin1String("dd-MM-yyyy HH:mm:ss.zzz"));
+    }
+
+
+    QVariant connectionData(Connection *connection, int column, int role) const
+    {
+        if (role != Qt::DisplayRole) {
+            return QVariant();
+        }
+
+        switch (column) {
+        case 0: return connection->name;
+        case 1: return fromMSecsSinceEpoch(connection->start);
+        }
+
+        return QVariant();
+    }
+
+    QVariant transactionData(Transaction *transaction, int column, int role) const
+    {
+        if (role == Qt::DisplayRole && column == 0) {
+            switch (transaction->transactionType) {
+            case Transaction::Begin: return QLatin1String("BEGIN");
+            case Transaction::Commit: return QLatin1String("COMMIT");
+            case Transaction::Rollback: return QLatin1String("ROLLBACK");
+            }
+            Q_ASSERT(false);
+        } else {
+            return queryData(transaction, column, role);
+        }
+        return QVariant();
+    }
+
+    QVariant queryData(Query *query, int column, int role) const
+    {
+        if (role == Qt::BackgroundRole) {
+            if (!query->error.isEmpty()) {
+                return KColorScheme(QPalette::Normal).background(KColorScheme::NegativeBackground).color();
+            }
+        } else if (role == Qt::DisplayRole) {
+            switch (column) {
+            case 0: return query->query;
+            case 1: return fromMSecsSinceEpoch(query->start);
+            case 2: return fromMSecsSinceEpoch(query->start + query->duration);
+            case 3: return QTime(0, 0, 0).addMSecs(query->duration).toString(QLatin1String("HH:mm:ss.zzz"));
+            case 4: return query->error;
+            }
+            Q_ASSERT(false);
+        }
+
+        return QVariant();
+    }
+
+    QVector<Connection *> mConnections;
+    QHash<qint64, Connection *> mConnectionById;
+};
+
+
 class QueryDebuggerModel : public QAbstractListModel
 {
   Q_OBJECT
@@ -201,65 +635,51 @@ private:
   QueryInfo mSpecialRows[NUM_SPECIAL_ROWS];
 };
 
-QueryDebugger::QueryDebugger( QWidget* parent ):
-  QWidget( parent )
+QueryDebugger::QueryDebugger( QWidget *parent ):
+  QWidget( parent ),
+  mUi( new Ui::QueryDebugger )
 {
   qDBusRegisterMetaType< QList< QList<QVariant> > >();
+  qDBusRegisterMetaType<DbConnection>();
+  qDBusRegisterMetaType<QVector<DbConnection>>();
 
-  QString service = QLatin1String( "org.freedesktop.Akonadi" );
+  QString service = QLatin1String("org.freedesktop.Akonadi");
   if ( Akonadi::ServerManager::hasInstanceIdentifier() ) {
-    service += "." + Akonadi::ServerManager::instanceIdentifier();
+    service += QLatin1String(".") + Akonadi::ServerManager::instanceIdentifier();
   }
   mDebugger = new org::freedesktop::Akonadi::StorageDebugger( service,
-                QLatin1String( "/storageDebug" ), QDBusConnection::sessionBus(), this );
-
-  connect( mDebugger, SIGNAL(queryExecuted(double,uint,QString,QMap<QString,QVariant>,int,QList<QList<QVariant> >,QString)),
-           this, SLOT(addQuery(double,uint,QString,QMap<QString,QVariant>,int,QList<QList<QVariant> >,QString)) );
-
-  QVBoxLayout *layout = new QVBoxLayout( this );
-
-  QHBoxLayout *checkBoxLayout = new QHBoxLayout( this );
-
-  QCheckBox* enableCB = new QCheckBox( this );
-  enableCB->setText( "Enable query debugger (slows down server!)");
-  enableCB->setChecked( mDebugger->isSQLDebuggingEnabled() );
-  connect( enableCB, SIGNAL(toggled(bool)), mDebugger, SLOT(enableSQLDebugging(bool)) );
-  checkBoxLayout->addWidget( enableCB );
-
-  mOnlyAggregate = new QCheckBox( this );
-  mOnlyAggregate->setText( "Only Aggregate data");
-  mOnlyAggregate->setChecked( true );
-  checkBoxLayout->addWidget( mOnlyAggregate );
-
-  QToolButton *clearButton = new QToolButton;
-  clearButton->setText( "clear" );
-  clearButton->setIcon( KIcon( "edit-clear-list" ) );
-  clearButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon  );
-  connect( clearButton, SIGNAL(clicked()), SLOT(clear()) );
-  checkBoxLayout->addWidget( clearButton );
-
-  layout->addLayout( checkBoxLayout );
-
-  QTreeView* queryList = new QTreeView( this );
-  mModel = new QueryDebuggerModel( this );
-  QSortFilterProxyModel* proxy = new QSortFilterProxyModel( this );
-  proxy->setSourceModel( mModel );
-  proxy->setDynamicSortFilter( true );
-  queryList->setModel( proxy );
-  queryList->setRootIsDecorated( false );
-  queryList->setSortingEnabled( true );
-  queryList->setUniformRowHeights( true );
-  queryList->header()->setResizeMode( QueryDebuggerModel::CallsColumn, QHeaderView::Fixed );
-  queryList->header()->setResizeMode( QueryDebuggerModel::DurationColumn, QHeaderView::Fixed );
-  queryList->header()->setResizeMode( QueryDebuggerModel::AvgDurationColumn, QHeaderView::Fixed );
-  queryList->header()->setResizeMode( QueryDebuggerModel::QueryColumn, QHeaderView::ResizeToContents );
-
-  layout->addWidget( queryList );
-
-  mView = new KTextEdit( this );
-  mView->setReadOnly( true );
-  mView->setFont( KGlobalSettings::fixedFont() );
-  layout->addWidget( mView );
+            QLatin1String("/storageDebug"), QDBusConnection::sessionBus(), this );
+
+  connect( mDebugger, SIGNAL( queryExecuted(double,qlonglong,qlonglong,uint,QString,QMap<QString,QVariant>,int,QList<QList<QVariant> >,QString) ),
+           this, SLOT( addQuery(double,qlonglong,qlonglong,uint,QString,QMap<QString,QVariant>,int,QList<QList<QVariant> >,QString) ) );
+
+  mUi->setupUi( this );
+  connect( mUi->enableDebuggingChkBox, SIGNAL( toggled(bool) ),
+           this, SLOT( debuggerToggled(bool) ) );
+
+  mQueryList = new QueryDebuggerModel(this);
+  QSortFilterProxyModel *proxy = new QSortFilterProxyModel(this);
+  proxy->setSourceModel(mQueryList);
+  proxy->setDynamicSortFilter(true);
+  mUi->queryListView->setModel(proxy);
+  mUi->queryListView->header()->setResizeMode(QueryDebuggerModel::CallsColumn, QHeaderView::Fixed);
+  mUi->queryListView->header()->setResizeMode(QueryDebuggerModel::DurationColumn, QHeaderView::Fixed);
+  mUi->queryListView->header()->setResizeMode(QueryDebuggerModel::AvgDurationColumn, QHeaderView::Fixed);
+  mUi->queryListView->header()->setResizeMode(QueryDebuggerModel::QueryColumn, QHeaderView::ResizeToContents);
+
+  mQueryTree = new QueryTreeModel(this);
+  mUi->queryTreeView->setModel(mQueryTree);
+  connect( mDebugger, SIGNAL( connectionOpened(qlonglong,QString,qlonglong) ),
+           mQueryTree, SLOT( addConnection(qlonglong,QString,qlonglong) ) );
+  connect( mDebugger, SIGNAL( connectionChanged(qlonglong,QString) ),
+           mQueryTree, SLOT( updateConnection(qlonglong,QString) ) );
+  connect( mDebugger, SIGNAL( transactionStarted(qlonglong,qlonglong,uint,QString) ),
+           mQueryTree, SLOT( addTransaction(qlonglong,qlonglong,uint,QString) ) );
+  connect( mDebugger, SIGNAL( transactionFinished(qlonglong,bool,qlonglong,uint,QString) ),
+           mQueryTree, SLOT( closeTransaction(qlonglong,bool,qlonglong,uint,QString) ) );
+
+  connect( mUi->saveTreeBtn, SIGNAL( clicked(bool) ),
+           this, SLOT( saveTreeToFile() ) );
 
   Akonadi::Control::widgetNeedsAkonadi( this );
 }
@@ -273,90 +693,49 @@ QueryDebugger::~QueryDebugger()
 
 void QueryDebugger::clear()
 {
-  mView->clear();
-  mModel->clear();
+  mQueryList->clear();
 }
 
-void QueryDebugger::addQuery( double sequence, uint duration, const QString &query,
-                              const QMap<QString, QVariant> &values,
+void QueryDebugger::addQuery( double sequence, qlonglong connectionId, qlonglong timestamp,
+                              uint duration, const QString &query, const QMap<QString, QVariant> &values,
                               int resultsCount, const QList<QList<QVariant> > &result,
                               const QString &error )
 {
-  mModel->addQuery(query, duration);
-
-  if (mOnlyAggregate->isChecked()) {
-    return;
-  }
-
-  QString q = query;
-  const QStringList keys = values.uniqueKeys();
-  Q_FOREACH ( const QString &key, keys ) {
-    int pos = q.indexOf( QLatin1String( "?" ) );
-    const QVariant val = values.value( key );
-    q.replace( pos, 1, variantToString( val ) );
-  }
-
-  mView->append( QString::fromLatin1( "%1: <font color=\"blue\">%2</font>") .arg( sequence ).arg( q ) );
-
-  if ( !error.isEmpty() ) {
-    mView->append( QString::fromLatin1( "<font color=\"red\">Error: %1</font>\n").arg( error ) );
-    return;
-  }
-
-  mView->append( QString::fromLatin1( "<font color=\"green\">Success</font>: Query took %1 msecs ").arg( duration ) );
-  if ( query.startsWith( QLatin1String( "SELECT") ) ) {
-    mView->append( QString::fromLatin1( "Fetched %1 results").arg( resultsCount ) );
-  } else {
-    mView->append( QString::fromLatin1( "Affected %1 rows").arg( resultsCount ) );
-  }
-
-  if ( !result.isEmpty() ) {
-    const QVariantList headerRow = result.first();
-    QString header;
-    for ( int i = 0; i < headerRow.size(); ++i ) {
-      if ( i > 0 ) {
-        header += QLatin1String( " | " );
-      }
-      header += headerRow.at( i ).toString();
-    }
-    mView->append( header );
-
-    QString sep;
-    mView->append( sep.fill( QLatin1Char('-'), header.length() ) );
+  mQueryList->addQuery( query, duration );
+  mQueryTree->addQuery( connectionId, query, timestamp, duration, error );
+}
 
-    for ( int row = 1; row < result.count(); ++row ) {
-      const QVariantList columns = result.at( row );
-      QString rowStr;
-      for ( int column = 0; column < columns.count(); ++column ) {
-        if ( column > 0 ) {
-          rowStr += QLatin1String( " | " );
-        }
-        rowStr += variantToString( columns.at( column ) );
+void QueryDebugger::debuggerToggled( bool on )
+{
+  mDebugger->enableSQLDebugging(on);
+  if ( on ) {
+    mQueryTree->clear();
+
+    const QVector<DbConnection> conns = mDebugger->connections();
+    Q_FOREACH ( const DbConnection &con, conns ) {
+      mQueryTree->addConnection( con.id, con.name, con.start );
+      if ( con.transactionStart > 0 ) {
+        mQueryTree->addTransaction( con.id, con.transactionStart,
+                                    QDateTime::currentMSecsSinceEpoch() - con.transactionStart,
+                                    QString() );
       }
-      mView->append( rowStr );
     }
   }
-
-  mView->append( QLatin1String( "\n" ) );
 }
 
-QString QueryDebugger::variantToString( const QVariant &val )
+void QueryDebugger::saveTreeToFile()
 {
-  if ( val.canConvert( QVariant::String ) ) {
-    return val.toString();
-  } else if ( val.canConvert( QVariant::QVariant::DateTime ) ) {
-    return val.toDateTime().toString( Qt::ISODate );
+  const QString fileName = QFileDialog::getSaveFileName(this);
+  QFile file(fileName);
+  if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+    // show error
+    return;
   }
 
-  QDBusArgument arg = val.value<QDBusArgument>();
-  if ( arg.currentType() == QDBusArgument::StructureType ) {
-    QDateTime t = qdbus_cast<QDateTime>( arg );
-    if ( t.isValid() ) {
-      return t.toString( Qt::ISODate );
-    }
-  }
+  mQueryTree->dumpRow(file, QModelIndex(), 0);
 
-  return QString();
+  file.close();
 }
 
+
 #include "querydebugger.moc"
diff --git a/akonadiconsole/querydebugger.h b/akonadiconsole/querydebugger.h
index 9163ffa4e8..8f3496f4c7 100644
--- a/akonadiconsole/querydebugger.h
+++ b/akonadiconsole/querydebugger.h
@@ -23,12 +23,31 @@
 #include <QtGui/QWidget>
 #include <QtCore/QMap>
 #include <QtCore/QVariant>
+#include <QScopedPointer>
 
-#include "storagedebuggerinterface.h"
+class QDBusArgument;
+
+namespace Ui
+{
+class QueryDebugger;
+}
 
-class KTextEdit;
-class QCheckBox;
 class QueryDebuggerModel;
+class QueryTreeModel;
+class OrgFreedesktopAkonadiStorageDebuggerInterface;
+
+struct DbConnection {
+    qint64 id;
+    QString name;
+    qint64 start;
+    qint64 transactionStart;
+};
+
+Q_DECLARE_METATYPE(DbConnection)
+Q_DECLARE_METATYPE(QVector<DbConnection>)
+
+QDBusArgument &operator<<(QDBusArgument &arg, const DbConnection &con);
+const QDBusArgument &operator>>(const QDBusArgument &arg, DbConnection &con);
 
 class QueryDebugger : public QWidget
 {
@@ -38,20 +57,22 @@ class QueryDebugger : public QWidget
     explicit QueryDebugger( QWidget* parent = 0 );
     virtual ~QueryDebugger();
 
-  private Q_SLOTS:
-    void addQuery( double sequence, uint duration, const QString &query,
-                   const QMap<QString,QVariant> & values, int resultsCount,
-                   const QList<QList<QVariant> > & result, const QString &error );
+private Q_SLOTS:
+    void debuggerToggled( bool on );
+    void addQuery( double sequence, qlonglong connectionId, qlonglong timestamp,
+                   uint duration, const QString &query, const QMap<QString, QVariant> &values,
+                   int resultsCount, const QList<QList<QVariant> > &result, const QString &error );
     void clear();
+    void saveTreeToFile();
 
   private:
     QString variantToString( const QVariant &val );
 
-    org::freedesktop::Akonadi::StorageDebugger *mDebugger;
+    QScopedPointer<Ui::QueryDebugger> mUi;
+    OrgFreedesktopAkonadiStorageDebuggerInterface *mDebugger;
 
-    KTextEdit *mView;
-    QueryDebuggerModel *mModel;
-    QCheckBox* mOnlyAggregate;
+    QueryDebuggerModel *mQueryList;
+    QueryTreeModel *mQueryTree;
 };
 
 #endif // QUERYDEBUGGER_H
diff --git a/akonadiconsole/querydebugger.ui b/akonadiconsole/querydebugger.ui
new file mode 100644
index 0000000000..6f417d8f93
--- /dev/null
+++ b/akonadiconsole/querydebugger.ui
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QueryDebugger</class>
+ <widget class="QWidget" name="QueryDebugger">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>645</width>
+    <height>508</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QCheckBox" name="enableDebuggingChkBox">
+     <property name="text">
+      <string>Enable query debugging (slows down server!)</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="currentIndex">
+      <number>1</number>
+     </property>
+     <widget class="QWidget" name="querySequenceTab">
+      <attribute name="title">
+       <string>Query Sequence</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <item>
+        <widget class="QTreeView" name="queryListView">
+         <property name="rootIsDecorated">
+          <bool>false</bool>
+         </property>
+         <property name="uniformRowHeights">
+          <bool>true</bool>
+         </property>
+         <property name="sortingEnabled">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="queryTreeTab">
+      <attribute name="title">
+       <string>Query Tree</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_3">
+       <item>
+        <widget class="QTreeView" name="queryTreeView"/>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <item>
+          <spacer name="horizontalSpacer">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QPushButton" name="saveTreeBtn">
+           <property name="text">
+            <string>Save to file</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
-- 
2.14.1