File indexing completed on 2024-05-12 05:12:48
0001 /* 0002 * SPDX-FileCopyrightText: 2013 Daniel Vrátil <dvratil@redhat.com> 0003 * SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 * 0007 */ 0008 0009 #include "querydebugger.h" 0010 #include "storagedebuggerinterface.h" 0011 #include "ui_querydebugger.h" 0012 #include "ui_queryviewdialog.h" 0013 0014 #include <KLocalizedString> 0015 0016 #include <QAbstractListModel> 0017 #include <QDateTime> 0018 #include <QDialog> 0019 #include <QDialogButtonBox> 0020 #include <QFileDialog> 0021 #include <QHeaderView> 0022 #include <QSortFilterProxyModel> 0023 #include <QTableWidget> 0024 0025 #include <QDBusArgument> 0026 #include <QDBusConnection> 0027 0028 #include <Akonadi/ControlGui> 0029 #include <Akonadi/ServerManager> 0030 0031 #include <KColorScheme> 0032 0033 #include <algorithm> 0034 0035 Q_DECLARE_METATYPE(QList<QList<QVariant>>) 0036 0037 QDBusArgument &operator<<(QDBusArgument &arg, const DbConnection &con) 0038 { 0039 arg.beginStructure(); 0040 arg << con.id << con.name << con.start << con.trxName << con.transactionStart; 0041 arg.endStructure(); 0042 return arg; 0043 } 0044 0045 const QDBusArgument &operator>>(const QDBusArgument &arg, DbConnection &con) 0046 { 0047 arg.beginStructure(); 0048 arg >> con.id >> con.name >> con.start >> con.trxName >> con.transactionStart; 0049 arg.endStructure(); 0050 return arg; 0051 } 0052 0053 struct QueryInfo { 0054 QString query; 0055 quint64 duration; 0056 quint64 calls; 0057 0058 bool operator<(const QString &other) const 0059 { 0060 return query < other; 0061 } 0062 }; 0063 0064 Q_DECLARE_TYPEINFO(QueryInfo, Q_RELOCATABLE_TYPE); 0065 0066 class QueryTreeModel : public QAbstractItemModel 0067 { 0068 Q_OBJECT 0069 0070 public: 0071 enum RowType { Connection, Transaction, Query }; 0072 0073 private: 0074 class Node 0075 { 0076 public: 0077 virtual ~Node() = default; 0078 0079 Node *parent; 0080 RowType type; 0081 qint64 start; 0082 uint duration; 0083 }; 0084 0085 class QueryNode : public Node 0086 { 0087 public: 0088 QString query; 0089 QString error; 0090 QMap<QString, QVariant> values; 0091 QList<QList<QVariant>> results; 0092 int resultsCount; 0093 }; 0094 0095 class TransactionNode : public QueryNode 0096 { 0097 public: 0098 ~TransactionNode() override 0099 { 0100 qDeleteAll(queries); 0101 } 0102 0103 enum TransactionType { Begin, Commit, Rollback }; 0104 TransactionType transactionType; 0105 QList<QueryNode *> queries; 0106 }; 0107 0108 class ConnectionNode : public Node 0109 { 0110 public: 0111 ~ConnectionNode() override 0112 { 0113 qDeleteAll(queries); 0114 } 0115 0116 QString name; 0117 QList<Node *> queries; // FIXME: Why can' I use QList<Query*> here?? 0118 }; 0119 0120 public: 0121 enum { RowTypeRole = Qt::UserRole + 1, QueryRole, QueryResultsCountRole, QueryResultsRole, QueryValuesRole }; 0122 0123 QueryTreeModel(QObject *parent) 0124 : QAbstractItemModel(parent) 0125 { 0126 } 0127 0128 ~QueryTreeModel() override 0129 { 0130 qDeleteAll(mConnections); 0131 } 0132 0133 void clear() 0134 { 0135 beginResetModel(); 0136 qDeleteAll(mConnections); 0137 mConnections.clear(); 0138 endResetModel(); 0139 } 0140 0141 void addConnection(qint64 id, const QString &name, qint64 timestamp) 0142 { 0143 auto con = new ConnectionNode; 0144 con->parent = nullptr; 0145 con->type = Connection; 0146 con->name = name; 0147 con->start = timestamp; 0148 beginInsertRows(QModelIndex(), mConnections.count(), mConnections.count()); 0149 mConnections << con; 0150 mConnectionById.insert(id, con); 0151 endInsertRows(); 0152 } 0153 0154 void updateConnection(qint64 id, const QString &name) 0155 { 0156 auto con = mConnectionById.value(id); 0157 if (!con) { 0158 return; 0159 } 0160 0161 con->name = name; 0162 const QModelIndex index = createIndex(mConnections.indexOf(con), columnCount() - 1, con); 0163 Q_EMIT dataChanged(index, index.sibling(index.row(), 5)); 0164 } 0165 0166 void addTransaction(qint64 connectionId, const QString &name, qint64 timestamp, uint duration, const QString &error) 0167 { 0168 auto con = mConnectionById.value(connectionId); 0169 if (!con) { 0170 return; 0171 } 0172 0173 auto trx = new TransactionNode; 0174 trx->query = name; 0175 trx->parent = con; 0176 trx->type = Transaction; 0177 trx->start = timestamp; 0178 trx->duration = duration; 0179 trx->transactionType = TransactionNode::Begin; 0180 trx->error = error.trimmed(); 0181 const QModelIndex conIdx = createIndex(mConnections.indexOf(con), 0, con); 0182 beginInsertRows(conIdx, con->queries.count(), con->queries.count()); 0183 con->queries << trx; 0184 endInsertRows(); 0185 } 0186 0187 void closeTransaction(qint64 connectionId, bool commit, qint64 timestamp, uint, const QString &error) 0188 { 0189 auto con = mConnectionById.value(connectionId); 0190 if (!con) { 0191 return; 0192 } 0193 0194 // Find the last open transaction and change it to closed 0195 for (int i = con->queries.count() - 1; i >= 0; i--) { 0196 Node *node = con->queries[i]; 0197 if (node->type == Transaction) { 0198 auto trx = static_cast<TransactionNode *>(node); 0199 if (trx->transactionType != TransactionNode::Begin) { 0200 continue; 0201 } 0202 0203 trx->transactionType = commit ? TransactionNode::Commit : TransactionNode::Rollback; 0204 trx->duration = timestamp - trx->start; 0205 trx->error = error.trimmed(); 0206 0207 const QModelIndex trxIdx = createIndex(i, 0, trx); 0208 Q_EMIT dataChanged(trxIdx, trxIdx.sibling(trxIdx.row(), columnCount() - 1)); 0209 return; 0210 } 0211 } 0212 } 0213 0214 void addQuery(qint64 connectionId, 0215 qint64 timestamp, 0216 uint duration, 0217 const QString &queryStr, 0218 const QMap<QString, QVariant> &values, 0219 int resultsCount, 0220 const QList<QList<QVariant>> &results, 0221 const QString &error) 0222 { 0223 auto con = mConnectionById.value(connectionId); 0224 if (!con) { 0225 return; 0226 } 0227 0228 auto query = new QueryNode; 0229 query->type = Query; 0230 query->start = timestamp; 0231 query->duration = duration; 0232 query->query = queryStr; 0233 query->values = values; 0234 query->resultsCount = resultsCount; 0235 query->results = results; 0236 query->error = error.trimmed(); 0237 0238 if (!con->queries.isEmpty() && con->queries.last()->type == Transaction 0239 && static_cast<TransactionNode *>(con->queries.last())->transactionType == TransactionNode::Begin) { 0240 auto trx = static_cast<TransactionNode *>(con->queries.last()); 0241 query->parent = trx; 0242 beginInsertRows(createIndex(con->queries.indexOf(trx), 0, trx), trx->queries.count(), trx->queries.count()); 0243 trx->queries << query; 0244 endInsertRows(); 0245 } else { 0246 query->parent = con; 0247 beginInsertRows(createIndex(mConnections.indexOf(con), 0, con), con->queries.count(), con->queries.count()); 0248 con->queries << query; 0249 endInsertRows(); 0250 } 0251 } 0252 0253 [[nodiscard]] int rowCount(const QModelIndex &parent) const override 0254 { 0255 if (!parent.isValid()) { 0256 return mConnections.count(); 0257 } 0258 0259 Node *node = reinterpret_cast<Node *>(parent.internalPointer()); 0260 switch (node->type) { 0261 case Connection: 0262 return static_cast<ConnectionNode *>(node)->queries.count(); 0263 case Transaction: 0264 return static_cast<TransactionNode *>(node)->queries.count(); 0265 case Query: 0266 return 0; 0267 } 0268 0269 Q_UNREACHABLE(); 0270 } 0271 0272 [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override 0273 { 0274 Q_UNUSED(parent) 0275 return 5; 0276 } 0277 0278 [[nodiscard]] QModelIndex parent(const QModelIndex &child) const override 0279 { 0280 if (!child.isValid() || !child.internalPointer()) { 0281 return {}; 0282 } 0283 0284 Node *childNode = reinterpret_cast<Node *>(child.internalPointer()); 0285 // childNode is a Connection 0286 if (!childNode->parent) { 0287 return {}; 0288 } 0289 0290 // childNode is a query in transaction 0291 if (childNode->parent->parent) { 0292 auto connection = static_cast<ConnectionNode *>(childNode->parent->parent); 0293 const int trxIdx = connection->queries.indexOf(childNode->parent); 0294 return createIndex(trxIdx, 0, childNode->parent); 0295 } else { 0296 // childNode is a query without transaction or a transaction 0297 return createIndex(mConnections.indexOf(static_cast<ConnectionNode *>(childNode->parent)), 0, childNode->parent); 0298 } 0299 } 0300 0301 [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent) const override 0302 { 0303 if (!parent.isValid()) { 0304 if (row < mConnections.count()) { 0305 return createIndex(row, column, mConnections.at(row)); 0306 } else { 0307 return {}; 0308 } 0309 } 0310 0311 Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer()); 0312 switch (parentNode->type) { 0313 case Connection: 0314 if (row < static_cast<ConnectionNode *>(parentNode)->queries.count()) { 0315 return createIndex(row, column, static_cast<ConnectionNode *>(parentNode)->queries.at(row)); 0316 } else { 0317 return {}; 0318 } 0319 case Transaction: 0320 if (row < static_cast<TransactionNode *>(parentNode)->queries.count()) { 0321 return createIndex(row, column, static_cast<TransactionNode *>(parentNode)->queries.at(row)); 0322 } else { 0323 return {}; 0324 } 0325 case Query: 0326 // Query can never have children 0327 return {}; 0328 } 0329 0330 Q_UNREACHABLE(); 0331 } 0332 0333 [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override 0334 { 0335 if (orientation != Qt::Horizontal || role != Qt::DisplayRole) { 0336 return {}; 0337 } 0338 0339 switch (section) { 0340 case 0: 0341 return i18n("Name / Query"); 0342 case 1: 0343 return i18n("Started"); 0344 case 2: 0345 return i18n("Ended"); 0346 case 3: 0347 return i18n("Duration"); 0348 case 4: 0349 return i18n("Error"); 0350 } 0351 0352 return {}; 0353 } 0354 0355 [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override 0356 { 0357 if (!index.isValid()) { 0358 return {}; 0359 } 0360 0361 Node *node = reinterpret_cast<Node *>(index.internalPointer()); 0362 if (role == RowTypeRole) { 0363 return node->type; 0364 } else { 0365 switch (node->type) { 0366 case Connection: 0367 return connectionData(static_cast<ConnectionNode *>(node), index.column(), role); 0368 case Transaction: 0369 return transactionData(static_cast<TransactionNode *>(node), index.column(), role); 0370 case Query: 0371 return queryData(static_cast<QueryNode *>(node), index.column(), role); 0372 } 0373 } 0374 0375 Q_UNREACHABLE(); 0376 } 0377 0378 void dumpRow(QFile &file, const QModelIndex &idx, int depth) 0379 { 0380 if (idx.isValid()) { 0381 QTextStream stream(&file); 0382 stream << QStringLiteral(" |").repeated(depth) << QLatin1StringView("- "); 0383 0384 Node *node = reinterpret_cast<Node *>(idx.internalPointer()); 0385 switch (node->type) { 0386 case Connection: { 0387 auto con = static_cast<ConnectionNode *>(node); 0388 stream << con->name << " " << fromMSecsSinceEpoch(con->start); 0389 break; 0390 } 0391 case Transaction: { 0392 auto trx = static_cast<TransactionNode *>(node); 0393 stream << idx.data(Qt::DisplayRole).toString() << " " << fromMSecsSinceEpoch(trx->start); 0394 if (trx->transactionType > TransactionNode::Begin) { 0395 stream << " - " << fromMSecsSinceEpoch(trx->start + trx->duration); 0396 } 0397 break; 0398 } 0399 case Query: { 0400 auto query = static_cast<QueryNode *>(node); 0401 stream << query->query << " " << fromMSecsSinceEpoch(query->start) << ", took " << query->duration << " ms"; 0402 break; 0403 } 0404 } 0405 0406 if (node->type >= Transaction) { 0407 auto query = static_cast<QueryNode *>(node); 0408 if (!query->error.isEmpty()) { 0409 stream << '\n' << QStringLiteral(" |").repeated(depth) << QStringLiteral(" Error: ") << query->error; 0410 } 0411 } 0412 0413 stream << '\n'; 0414 } 0415 0416 for (int i = 0, c = rowCount(idx); i < c; ++i) { 0417 dumpRow(file, index(i, 0, idx), depth + 1); 0418 } 0419 } 0420 0421 private: 0422 [[nodiscard]] QString fromMSecsSinceEpoch(qint64 msecs) const 0423 { 0424 return QDateTime::fromMSecsSinceEpoch(msecs).toString(QStringLiteral("dd.MM.yyyy HH:mm:ss.zzz")); 0425 } 0426 0427 QVariant connectionData(ConnectionNode *connection, int column, int role) const 0428 { 0429 if (role != Qt::DisplayRole) { 0430 return {}; 0431 } 0432 0433 switch (column) { 0434 case 0: 0435 return connection->name; 0436 case 1: 0437 return fromMSecsSinceEpoch(connection->start); 0438 } 0439 0440 return {}; 0441 } 0442 0443 QVariant transactionData(TransactionNode *transaction, int column, int role) const 0444 { 0445 if (role == Qt::DisplayRole && column == 0) { 0446 QString mode; 0447 switch (transaction->transactionType) { 0448 case TransactionNode::Begin: 0449 mode = QStringLiteral("BEGIN"); 0450 break; 0451 case TransactionNode::Commit: 0452 mode = QStringLiteral("COMMIT"); 0453 break; 0454 case TransactionNode::Rollback: 0455 mode = QStringLiteral("ROLLBACK"); 0456 break; 0457 } 0458 return QStringLiteral("%1 %2").arg(mode, transaction->query); 0459 } else { 0460 return queryData(transaction, column, role); 0461 } 0462 } 0463 0464 QVariant queryData(QueryNode *query, int column, int role) const 0465 { 0466 switch (role) { 0467 case Qt::BackgroundRole: 0468 if (!query->error.isEmpty()) { 0469 return KColorScheme(QPalette::Normal).background(KColorScheme::NegativeBackground).color(); 0470 } 0471 break; 0472 case Qt::DisplayRole: 0473 switch (column) { 0474 case 0: 0475 return query->query; 0476 case 1: 0477 return fromMSecsSinceEpoch(query->start); 0478 case 2: 0479 return fromMSecsSinceEpoch(query->start + query->duration); 0480 case 3: 0481 return QTime(0, 0, 0).addMSecs(query->duration).toString(QStringLiteral("HH:mm:ss.zzz")); 0482 case 4: 0483 return query->error; 0484 } 0485 break; 0486 case QueryRole: 0487 return query->query; 0488 case QueryResultsCountRole: 0489 return query->resultsCount; 0490 case QueryResultsRole: 0491 return QVariant::fromValue(query->results); 0492 case QueryValuesRole: 0493 return query->values; 0494 } 0495 0496 return {}; 0497 } 0498 0499 QList<ConnectionNode *> mConnections; 0500 QHash<qint64, ConnectionNode *> mConnectionById; 0501 }; 0502 0503 class QueryDebuggerModel : public QAbstractListModel 0504 { 0505 Q_OBJECT 0506 public: 0507 QueryDebuggerModel(QObject *parent) 0508 : QAbstractListModel(parent) 0509 { 0510 mSpecialRows[TOTAL].query = QStringLiteral("TOTAL"); 0511 mSpecialRows[TOTAL].duration = 0; 0512 mSpecialRows[TOTAL].calls = 0; 0513 } 0514 0515 enum SPECIAL_ROWS { TOTAL, NUM_SPECIAL_ROWS }; 0516 enum COLUMNS { DurationColumn, CallsColumn, AvgDurationColumn, QueryColumn, NUM_COLUMNS }; 0517 0518 [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override 0519 { 0520 if (orientation == Qt::Vertical || section < 0 || section >= NUM_COLUMNS || (role != Qt::DisplayRole && role != Qt::ToolTipRole)) { 0521 return {}; 0522 } 0523 0524 if (section == QueryColumn) { 0525 return i18n("Query"); 0526 } else if (section == DurationColumn) { 0527 return i18n("Duration [ms]"); 0528 } else if (section == CallsColumn) { 0529 return i18n("Calls"); 0530 } else if (section == AvgDurationColumn) { 0531 return i18n("Avg. Duration [ms]"); 0532 } 0533 0534 return {}; 0535 } 0536 0537 [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override 0538 { 0539 if (role != Qt::DisplayRole && role != Qt::ToolTipRole) { 0540 return {}; 0541 } 0542 0543 const int row = index.row(); 0544 if (row < 0 || row >= rowCount(index.parent())) { 0545 return {}; 0546 } 0547 const int column = index.column(); 0548 if (column < 0 || column >= NUM_COLUMNS) { 0549 return {}; 0550 } 0551 0552 const QueryInfo &info = (row < NUM_SPECIAL_ROWS) ? mSpecialRows[row] : mQueries.at(row - NUM_SPECIAL_ROWS); 0553 0554 if (role == Qt::ToolTipRole) { 0555 return QString(QLatin1StringView("<qt>") + info.query + QLatin1StringView("</qt>")); 0556 } 0557 0558 if (column == QueryColumn) { 0559 return info.query; 0560 } else if (column == DurationColumn) { 0561 return info.duration; 0562 } else if (column == CallsColumn) { 0563 return info.calls; 0564 } else if (column == AvgDurationColumn) { 0565 return float(info.duration) / info.calls; 0566 } 0567 0568 return {}; 0569 } 0570 0571 [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override 0572 { 0573 if (!parent.isValid()) { 0574 return mQueries.size() + NUM_SPECIAL_ROWS; 0575 } else { 0576 return 0; 0577 } 0578 } 0579 0580 [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override 0581 { 0582 if (!parent.isValid()) { 0583 return NUM_COLUMNS; 0584 } else { 0585 return 0; 0586 } 0587 } 0588 0589 void addQuery(const QString &query, uint duration) 0590 { 0591 QList<QueryInfo>::iterator it = std::lower_bound(mQueries.begin(), mQueries.end(), query); 0592 0593 const int row = std::distance(mQueries.begin(), it) + NUM_SPECIAL_ROWS; 0594 0595 if (it != mQueries.end() && it->query == query) { 0596 ++(it->calls); 0597 it->duration += duration; 0598 0599 Q_EMIT dataChanged(index(row, DurationColumn), index(row, AvgDurationColumn)); 0600 } else { 0601 beginInsertRows(QModelIndex(), row, row); 0602 QueryInfo info; 0603 info.query = query; 0604 info.duration = duration; 0605 info.calls = 1; 0606 mQueries.insert(it, info); 0607 endInsertRows(); 0608 } 0609 0610 mSpecialRows[TOTAL].duration += duration; 0611 ++mSpecialRows[TOTAL].calls; 0612 Q_EMIT dataChanged(index(TOTAL, DurationColumn), index(TOTAL, AvgDurationColumn)); 0613 } 0614 0615 void clear() 0616 { 0617 beginResetModel(); 0618 mQueries.clear(); 0619 mSpecialRows[TOTAL].duration = 0; 0620 mSpecialRows[TOTAL].calls = 0; 0621 endResetModel(); 0622 } 0623 0624 private: 0625 QList<QueryInfo> mQueries; 0626 QueryInfo mSpecialRows[NUM_SPECIAL_ROWS]; 0627 }; 0628 0629 class QueryViewDialog : public QDialog 0630 { 0631 Q_OBJECT 0632 public: 0633 QueryViewDialog(const QString &query, 0634 const QMap<QString, QVariant> &values, 0635 int resultsCount, 0636 const QList<QList<QVariant>> &results, 0637 QWidget *parent = nullptr) 0638 : QDialog(parent) 0639 , mUi(new Ui::QueryViewDialog) 0640 { 0641 mUi->setupUi(this); 0642 0643 QString q = query; 0644 for (int i = 0; i < values.count(); ++i) { 0645 const int pos = q.indexOf(QLatin1Char('?')); 0646 if (pos == -1) { 0647 break; 0648 } 0649 q.replace(pos, 1, values.value(QStringLiteral(":%1").arg(i)).toString()); 0650 } 0651 mUi->queryLbl->setText(q); 0652 if (!q.startsWith(QLatin1StringView("SELECT"))) { 0653 mUi->resultsLabelLbl->setText(i18n("Affected Rows:")); 0654 } 0655 mUi->resultsLbl->setText(QString::number(resultsCount)); 0656 0657 if (!results.isEmpty()) { 0658 mUi->tableWidget->setRowCount(resultsCount); 0659 const auto &headers = results[0]; 0660 mUi->tableWidget->setColumnCount(headers.count()); 0661 for (int c = 0; c < headers.count(); ++c) { 0662 mUi->tableWidget->setHorizontalHeaderItem(c, new QTableWidgetItem(headers[c].toString())); 0663 } 0664 for (int r = 1; r <= resultsCount; ++r) { 0665 const auto &row = results[r]; 0666 for (int c = 0; c < row.size(); ++c) { 0667 mUi->tableWidget->setItem(r - 1, c, new QTableWidgetItem(row[c].toString())); 0668 } 0669 } 0670 } 0671 0672 connect(mUi->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::accept); 0673 } 0674 0675 private: 0676 QScopedPointer<Ui::QueryViewDialog> mUi; 0677 }; 0678 0679 QueryDebugger::QueryDebugger(QWidget *parent) 0680 : QWidget(parent) 0681 , mUi(new Ui::QueryDebugger) 0682 { 0683 qDBusRegisterMetaType<QList<QList<QVariant>>>(); 0684 qDBusRegisterMetaType<DbConnection>(); 0685 qDBusRegisterMetaType<QList<DbConnection>>(); 0686 0687 QString service = QStringLiteral("org.freedesktop.Akonadi"); 0688 if (Akonadi::ServerManager::hasInstanceIdentifier()) { 0689 service += QLatin1Char('.') + Akonadi::ServerManager::instanceIdentifier(); 0690 } 0691 mDebugger = new org::freedesktop::Akonadi::StorageDebugger(service, QStringLiteral("/storageDebug"), QDBusConnection::sessionBus(), this); 0692 0693 connect(mDebugger, &OrgFreedesktopAkonadiStorageDebuggerInterface::queryExecuted, this, &QueryDebugger::addQuery); 0694 0695 mUi->setupUi(this); 0696 connect(mUi->enableDebuggingChkBox, &QAbstractButton::toggled, this, &QueryDebugger::debuggerToggled); 0697 0698 mQueryList = new QueryDebuggerModel(this); 0699 auto proxy = new QSortFilterProxyModel(this); 0700 proxy->setSourceModel(mQueryList); 0701 proxy->setDynamicSortFilter(true); 0702 mUi->queryListView->setModel(proxy); 0703 mUi->queryListView->header()->setSectionResizeMode(QueryDebuggerModel::CallsColumn, QHeaderView::Fixed); 0704 mUi->queryListView->header()->setSectionResizeMode(QueryDebuggerModel::DurationColumn, QHeaderView::Fixed); 0705 mUi->queryListView->header()->setSectionResizeMode(QueryDebuggerModel::AvgDurationColumn, QHeaderView::Fixed); 0706 mUi->queryListView->header()->setSectionResizeMode(QueryDebuggerModel::QueryColumn, QHeaderView::ResizeToContents); 0707 0708 connect(mUi->queryTreeView, &QTreeView::doubleClicked, this, &QueryDebugger::queryTreeDoubleClicked); 0709 connect(mUi->saveToFileBtn, &QPushButton::clicked, this, &QueryDebugger::saveTreeToFile); 0710 mQueryTree = new QueryTreeModel(this); 0711 mUi->queryTreeView->setModel(mQueryTree); 0712 connect(mDebugger, &org::freedesktop::Akonadi::StorageDebugger::connectionOpened, mQueryTree, &QueryTreeModel::addConnection); 0713 connect(mDebugger, &org::freedesktop::Akonadi::StorageDebugger::connectionChanged, mQueryTree, &QueryTreeModel::updateConnection); 0714 connect(mDebugger, &org::freedesktop::Akonadi::StorageDebugger::transactionStarted, mQueryTree, &QueryTreeModel::addTransaction); 0715 connect(mDebugger, &org::freedesktop::Akonadi::StorageDebugger::transactionFinished, mQueryTree, &QueryTreeModel::closeTransaction); 0716 0717 Akonadi::ControlGui::widgetNeedsAkonadi(this); 0718 } 0719 0720 QueryDebugger::~QueryDebugger() 0721 { 0722 // Disable debugging when turning off Akonadi Console so that we don't waste 0723 // resources on server 0724 mDebugger->enableSQLDebugging(false); 0725 } 0726 0727 void QueryDebugger::clear() 0728 { 0729 mQueryList->clear(); 0730 } 0731 0732 void QueryDebugger::debuggerToggled(bool on) 0733 { 0734 mDebugger->enableSQLDebugging(on); 0735 if (on) { 0736 mQueryTree->clear(); 0737 0738 const QList<DbConnection> conns = mDebugger->connections(); 0739 for (const auto &con : conns) { 0740 mQueryTree->addConnection(con.id, con.name, con.start); 0741 if (con.transactionStart > 0) { 0742 mQueryTree->addTransaction(con.id, con.trxName, con.transactionStart, 0, QString()); 0743 } 0744 } 0745 } 0746 } 0747 0748 void QueryDebugger::addQuery(double sequence, 0749 qint64 connectionId, 0750 qint64 timestamp, 0751 uint duration, 0752 const QString &query, 0753 const QMap<QString, QVariant> &values, 0754 int resultsCount, 0755 const QList<QList<QVariant>> &result, 0756 const QString &error) 0757 { 0758 Q_UNUSED(sequence) 0759 mQueryList->addQuery(query, duration); 0760 mQueryTree->addQuery(connectionId, timestamp, duration, query, values, resultsCount, result, error); 0761 } 0762 0763 void QueryDebugger::queryTreeDoubleClicked(const QModelIndex &index) 0764 { 0765 if (static_cast<QueryTreeModel::RowType>(index.data(QueryTreeModel::RowTypeRole).toInt()) != QueryTreeModel::Query) { 0766 return; 0767 } 0768 0769 auto dlg = new QueryViewDialog(index.data(QueryTreeModel::QueryRole).toString(), 0770 index.data(QueryTreeModel::QueryValuesRole).value<QMap<QString, QVariant>>(), 0771 index.data(QueryTreeModel::QueryResultsCountRole).toInt(), 0772 index.data(QueryTreeModel::QueryResultsRole).value<QList<QList<QVariant>>>(), 0773 this); 0774 connect(dlg, &QDialog::finished, dlg, &QObject::deleteLater); 0775 dlg->show(); 0776 } 0777 0778 void QueryDebugger::saveTreeToFile() 0779 { 0780 const QString fileName = QFileDialog::getSaveFileName(this); 0781 if (fileName.isEmpty()) { 0782 return; 0783 } 0784 QFile file(fileName); 0785 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { 0786 // show error 0787 return; 0788 } 0789 0790 mQueryTree->dumpRow(file, QModelIndex(), 0); 0791 0792 file.close(); 0793 } 0794 0795 #include "querydebugger.moc" 0796 0797 #include "moc_querydebugger.cpp"