From df47a6a1bf8194ed9c5f4ba0fc69d86fabef45bf Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Wed, 17 Oct 2018 21:38:45 -0500 Subject: [PATCH] Add stack trace viewer --- src/CMakeLists.txt | 1 + src/callstackmodel.cpp | 73 +++++++++++++++++++++++++++++++++++++++++ src/callstackmodel.hpp | 43 ++++++++++++++++++++++++ src/mainwindow.cpp | 36 +++++++++++++++----- src/mainwindow.hpp | 7 ++++ src/processdata.cpp | 1 + src/processdata.hpp | 3 +- src/processselector.cpp | 1 + src/server.cpp | 2 +- src/server.hpp | 2 +- src/traceeventmodel.cpp | 7 ++++ src/traceeventmodel.hpp | 3 ++ src/traceview.cpp | 37 ++++++++++++++++++++- src/traceview.hpp | 7 +++- 14 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 src/callstackmodel.cpp create mode 100644 src/callstackmodel.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2cd9534..0724376 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_AUTOMOC ON) add_executable( bullock MACOSX_BUNDLE + callstackmodel.cpp main.cpp mainwindow.cpp processdata.cpp diff --git a/src/callstackmodel.cpp b/src/callstackmodel.cpp new file mode 100644 index 0000000..2d58f0a --- /dev/null +++ b/src/callstackmodel.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2018 gtalent2@gmail.com + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include "callstackmodel.hpp" + +int CallStackModel::rowCount(const QModelIndex &parent) const { + return m_frames.size(); +} + +int CallStackModel::columnCount(const QModelIndex &parent) const { + return Column::End; +} + +QVariant CallStackModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole) { + return QVariant(); + } + + if (orientation == Qt::Horizontal) { + switch (section) { + case Column::Function: + return tr("Function"); + case Column::Source: + return tr("Source"); + default: + return QVariant(); + } + } + return QVariant(); +} + +QVariant CallStackModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) { + return QVariant(); + } + + if (index.row() >= m_frames.size() || index.row() < 0) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + const auto &f = m_frames[index.row()]; + + switch (index.column()) { + case Column::Function: + return f.function; + case Column::Source: + return QString("%1:%2").arg(f.file).arg(f.line); + default: + return {}; + } + } + return QVariant(); +} + +void CallStackModel::clear() { + beginResetModel(); + m_frames.clear(); + endResetModel(); +} + +void CallStackModel::setTraceEvent(const TraceEvent &event) { + beginResetModel(); + m_frames = event.frames; + endResetModel(); +} diff --git a/src/callstackmodel.hpp b/src/callstackmodel.hpp new file mode 100644 index 0000000..9c302ff --- /dev/null +++ b/src/callstackmodel.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2018 gtalent2@gmail.com + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "processdata.hpp" + +#include + + +class CallStackModel: public QAbstractTableModel { + Q_OBJECT + + public: + enum Column { + Function = 0, + Source, + End + }; + + private: + QVector m_frames; + + public: + int rowCount(const QModelIndex &parent = QModelIndex()) const override ; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + void clear(); + + public slots: + void setTraceEvent(const TraceEvent &event); + +}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index adda935..21c02ce 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "mainwindow.hpp" @@ -26,7 +27,7 @@ MainWindow::MainWindow() { move(screenSize.width() * (1 - sizePct) / 2, screenSize.height() * (1 - sizePct) / 2); auto central = new QWidget(this); - auto splitter = new QSplitter(central); + m_splitter = new QSplitter(central); auto leftPane = new QWidget(this); auto leftPaneSplitter = new QSplitter(Qt::Vertical, leftPane); @@ -34,25 +35,44 @@ MainWindow::MainWindow() { connect(m_procSelector, &ProcessSelector::selectionChanged, this, &MainWindow::setProcess); leftPaneSplitter->addWidget(m_procSelector); - splitter->addWidget(leftPaneSplitter); - m_traceView = new TraceView(splitter); - splitter->addWidget(m_traceView); - splitter->setStretchFactor(1, 3); + m_splitter->addWidget(leftPaneSplitter); + m_traceView = new TraceView(m_splitter); + m_splitter->addWidget(m_traceView); + m_splitter->setStretchFactor(1, 3); - setCentralWidget(splitter); + setCentralWidget(m_splitter); setupMenu(); + + readState(); } MainWindow::~MainWindow() { + writeState(); } void MainWindow::addDataFeed(DataFeed *feed) { - auto time = QDateTime::currentDateTime(); - auto procKey = time.toString(); + auto &procKey = feed->procData()->procKey; + if (procKey == "") { + procKey = QDateTime::currentDateTime().toString(); + } m_procData[procKey] = feed->procData(); m_procSelector->addProcess(procKey); } +void MainWindow::readState() { + QSettings settings; + settings.beginGroup("MainWindow"); + m_splitter->restoreState(settings.value("splitterState").toByteArray()); + settings.endGroup(); +} + +void MainWindow::writeState() { + QSettings settings; + settings.beginGroup("MainWindow"); + settings.setValue("splitterState", m_splitter->saveState()); + settings.endGroup(); +} + void MainWindow::setProcess(QString procKey) { if (m_procData.contains(procKey)) { m_currentProc = m_procData[procKey].data(); diff --git a/src/mainwindow.hpp b/src/mainwindow.hpp index 73bbe0a..09726b2 100644 --- a/src/mainwindow.hpp +++ b/src/mainwindow.hpp @@ -7,6 +7,7 @@ */ #include +#include #include "processselector.hpp" #include "traceview.hpp" @@ -18,6 +19,7 @@ class MainWindow: public QMainWindow { private: QHash> m_procData; + QSplitter *m_splitter = nullptr; ProcessData *m_currentProc = nullptr; ProcessSelector *m_procSelector = nullptr; TraceView *m_traceView = nullptr; @@ -30,6 +32,11 @@ class MainWindow: public QMainWindow { public slots: void addDataFeed(DataFeed*); + private: + void readState(); + + void writeState(); + private slots: void setProcess(QString procKey); diff --git a/src/processdata.cpp b/src/processdata.cpp index 8865bfd..becb913 100644 --- a/src/processdata.cpp +++ b/src/processdata.cpp @@ -23,6 +23,7 @@ Field::Field(QJsonObject field) { Frame::Frame(QJsonObject frame) { this->arch = frame["arch"].toString(); + this->function = frame["function"].toString(); this->file = frame["file"].toString(); this->line = frame["line"].toDouble(); auto fields = frame["fields"].toArray(); diff --git a/src/processdata.hpp b/src/processdata.hpp index 7bc0649..2fdcf09 100644 --- a/src/processdata.hpp +++ b/src/processdata.hpp @@ -24,6 +24,7 @@ struct Field { }; struct Frame { + QString function; QString arch; QString file; int line = 0; @@ -50,7 +51,7 @@ struct ProcessData: public QObject { Q_OBJECT public: - QString cmd; + QString procKey; QVector traceEvents; signals: diff --git a/src/processselector.cpp b/src/processselector.cpp index cd4b12b..14acd55 100644 --- a/src/processselector.cpp +++ b/src/processselector.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include "processselector.hpp" diff --git a/src/server.cpp b/src/server.cpp index 5261cf2..4193a63 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -15,7 +15,7 @@ DataFeed::DataFeed(QIODevice *dev): QObject(dev) { connect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit); } -QSharedPointer DataFeed::procData() { +const QSharedPointer &DataFeed::procData() { return m_procData; } diff --git a/src/server.hpp b/src/server.hpp index e2d0397..1c1d906 100644 --- a/src/server.hpp +++ b/src/server.hpp @@ -28,7 +28,7 @@ class DataFeed: public QObject { public: DataFeed(QIODevice *dev); - QSharedPointer procData(); + const QSharedPointer &procData(); public slots: void handleInit(); diff --git a/src/traceeventmodel.cpp b/src/traceeventmodel.cpp index 25d78b7..d836b55 100644 --- a/src/traceeventmodel.cpp +++ b/src/traceeventmodel.cpp @@ -80,6 +80,13 @@ void TraceEventModel::setProcessData(ProcessData *data) { endResetModel(); } +TraceEvent TraceEventModel::traceEvent(int row) { + if (m_procData) { + return m_procData->traceEvents[row]; + } + return {}; +} + void TraceEventModel::addEvent(const TraceEvent &event) { auto index = m_traceEvents.size(); beginInsertRows(QModelIndex(), index, index); diff --git a/src/traceeventmodel.hpp b/src/traceeventmodel.hpp index f52859c..8058be8 100644 --- a/src/traceeventmodel.hpp +++ b/src/traceeventmodel.hpp @@ -39,6 +39,9 @@ class TraceEventModel: public QAbstractTableModel { void setProcessData(ProcessData *data); + TraceEvent traceEvent(int row); + public slots: void addEvent(const TraceEvent &event); + }; diff --git a/src/traceview.cpp b/src/traceview.cpp index b0b9bf3..81091af 100644 --- a/src/traceview.cpp +++ b/src/traceview.cpp @@ -9,20 +9,48 @@ #include #include #include +#include #include +#include #include "traceview.hpp" TraceView::TraceView(QWidget *parent): QWidget(parent) { auto lyt = new QHBoxLayout; setLayout(lyt); + + m_splitter = new QSplitter(Qt::Vertical, this); + lyt->addWidget(m_splitter); + m_eventTable = new QTableView(this); m_model = new TraceEventModel; m_eventTable->setModel(m_model); m_eventTable->horizontalHeader()->setStretchLastSection(true); m_eventTable->verticalHeader()->hide(); m_eventTable->setSelectionBehavior(QAbstractItemView::SelectRows); - lyt->addWidget(m_eventTable); + m_eventTable->setSelectionMode(QAbstractItemView::SingleSelection); + connect(m_eventTable->selectionModel(), &QItemSelectionModel::selectionChanged, + [this](const QItemSelection &selected, const QItemSelection &deselected) { + auto indexes = selected.indexes(); + if (indexes.size()) { + auto row = indexes[0].row(); + const auto te = m_model->traceEvent(row); + m_callStackModel->setTraceEvent(te); + } + }); + m_splitter->addWidget(m_eventTable); + + m_callStack = new QTableView(this); + m_callStackModel = new CallStackModel; + m_callStack->setModel(m_callStackModel); + m_callStack->horizontalHeader()->setStretchLastSection(true); + m_callStack->verticalHeader()->hide(); + m_callStack->setSelectionBehavior(QAbstractItemView::SelectRows); + m_callStack->setSelectionMode(QAbstractItemView::SingleSelection); + m_splitter->addWidget(m_callStack); + + m_splitter->setStretchFactor(0, 2); + readState(); } @@ -35,6 +63,9 @@ void TraceView::readState() { settings.beginGroup("TraceView"); m_eventTable->horizontalHeader()->restoreState(settings.value("eventTableState").toByteArray()); m_eventTable->horizontalHeader()->restoreGeometry(settings.value("eventTableGeometry").toByteArray()); + m_callStack->horizontalHeader()->restoreState(settings.value("callStackTableState").toByteArray()); + m_callStack->horizontalHeader()->restoreGeometry(settings.value("callStackTableGeometry").toByteArray()); + m_splitter->restoreState(settings.value("splitterState").toByteArray()); settings.endGroup(); } @@ -43,9 +74,13 @@ void TraceView::writeState() { settings.beginGroup("TraceView"); settings.setValue("eventTableState", m_eventTable->horizontalHeader()->saveState()); settings.setValue("eventTableGeometry", m_eventTable->horizontalHeader()->saveGeometry()); + settings.setValue("callStackTableState", m_callStack->horizontalHeader()->saveState()); + settings.setValue("callStackTableGeometry", m_callStack->horizontalHeader()->saveGeometry()); + settings.setValue("splitterState", m_splitter->saveState()); settings.endGroup(); } void TraceView::setProcessData(ProcessData *data) { m_model->setProcessData(data); + m_callStackModel->clear(); } diff --git a/src/traceview.hpp b/src/traceview.hpp index bf2a346..4372511 100644 --- a/src/traceview.hpp +++ b/src/traceview.hpp @@ -8,9 +8,11 @@ #pragma once +#include #include #include +#include "callstackmodel.hpp" #include "traceeventmodel.hpp" #include "processdata.hpp" @@ -19,7 +21,10 @@ class TraceView: public QWidget { Q_OBJECT private: - QTableView *m_eventTable; + QTableView *m_eventTable = nullptr; + QTableView *m_callStack = nullptr; + QSplitter *m_splitter = nullptr; + CallStackModel *m_callStackModel = nullptr; TraceEventModel *m_model = nullptr; public: