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