Add stack trace viewer

This commit is contained in:
Gary Talent 2018-10-17 21:38:45 -05:00
parent 733350b143
commit df47a6a1bf
14 changed files with 210 additions and 13 deletions

View File

@ -7,6 +7,7 @@ set(CMAKE_AUTOMOC ON)
add_executable( add_executable(
bullock MACOSX_BUNDLE bullock MACOSX_BUNDLE
callstackmodel.cpp
main.cpp main.cpp
mainwindow.cpp mainwindow.cpp
processdata.cpp processdata.cpp

73
src/callstackmodel.cpp Normal file
View File

@ -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 <QDebug>
#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();
}

43
src/callstackmodel.hpp Normal file
View File

@ -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 <QAbstractTableModel>
class CallStackModel: public QAbstractTableModel {
Q_OBJECT
public:
enum Column {
Function = 0,
Source,
End
};
private:
QVector<Frame> 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);
};

View File

@ -12,6 +12,7 @@
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMenuBar> #include <QMenuBar>
#include <QSettings>
#include <QSplitter> #include <QSplitter>
#include "mainwindow.hpp" #include "mainwindow.hpp"
@ -26,7 +27,7 @@ MainWindow::MainWindow() {
move(screenSize.width() * (1 - sizePct) / 2, screenSize.height() * (1 - sizePct) / 2); move(screenSize.width() * (1 - sizePct) / 2, screenSize.height() * (1 - sizePct) / 2);
auto central = new QWidget(this); auto central = new QWidget(this);
auto splitter = new QSplitter(central); m_splitter = new QSplitter(central);
auto leftPane = new QWidget(this); auto leftPane = new QWidget(this);
auto leftPaneSplitter = new QSplitter(Qt::Vertical, leftPane); auto leftPaneSplitter = new QSplitter(Qt::Vertical, leftPane);
@ -34,25 +35,44 @@ MainWindow::MainWindow() {
connect(m_procSelector, &ProcessSelector::selectionChanged, this, &MainWindow::setProcess); connect(m_procSelector, &ProcessSelector::selectionChanged, this, &MainWindow::setProcess);
leftPaneSplitter->addWidget(m_procSelector); leftPaneSplitter->addWidget(m_procSelector);
splitter->addWidget(leftPaneSplitter); m_splitter->addWidget(leftPaneSplitter);
m_traceView = new TraceView(splitter); m_traceView = new TraceView(m_splitter);
splitter->addWidget(m_traceView); m_splitter->addWidget(m_traceView);
splitter->setStretchFactor(1, 3); m_splitter->setStretchFactor(1, 3);
setCentralWidget(splitter); setCentralWidget(m_splitter);
setupMenu(); setupMenu();
readState();
} }
MainWindow::~MainWindow() { MainWindow::~MainWindow() {
writeState();
} }
void MainWindow::addDataFeed(DataFeed *feed) { void MainWindow::addDataFeed(DataFeed *feed) {
auto time = QDateTime::currentDateTime(); auto &procKey = feed->procData()->procKey;
auto procKey = time.toString(); if (procKey == "") {
procKey = QDateTime::currentDateTime().toString();
}
m_procData[procKey] = feed->procData(); m_procData[procKey] = feed->procData();
m_procSelector->addProcess(procKey); 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) { void MainWindow::setProcess(QString procKey) {
if (m_procData.contains(procKey)) { if (m_procData.contains(procKey)) {
m_currentProc = m_procData[procKey].data(); m_currentProc = m_procData[procKey].data();

View File

@ -7,6 +7,7 @@
*/ */
#include <QMainWindow> #include <QMainWindow>
#include <QSplitter>
#include "processselector.hpp" #include "processselector.hpp"
#include "traceview.hpp" #include "traceview.hpp"
@ -18,6 +19,7 @@ class MainWindow: public QMainWindow {
private: private:
QHash<QString, QSharedPointer<ProcessData>> m_procData; QHash<QString, QSharedPointer<ProcessData>> m_procData;
QSplitter *m_splitter = nullptr;
ProcessData *m_currentProc = nullptr; ProcessData *m_currentProc = nullptr;
ProcessSelector *m_procSelector = nullptr; ProcessSelector *m_procSelector = nullptr;
TraceView *m_traceView = nullptr; TraceView *m_traceView = nullptr;
@ -30,6 +32,11 @@ class MainWindow: public QMainWindow {
public slots: public slots:
void addDataFeed(DataFeed*); void addDataFeed(DataFeed*);
private:
void readState();
void writeState();
private slots: private slots:
void setProcess(QString procKey); void setProcess(QString procKey);

View File

@ -23,6 +23,7 @@ Field::Field(QJsonObject field) {
Frame::Frame(QJsonObject frame) { Frame::Frame(QJsonObject frame) {
this->arch = frame["arch"].toString(); this->arch = frame["arch"].toString();
this->function = frame["function"].toString();
this->file = frame["file"].toString(); this->file = frame["file"].toString();
this->line = frame["line"].toDouble(); this->line = frame["line"].toDouble();
auto fields = frame["fields"].toArray(); auto fields = frame["fields"].toArray();

View File

@ -24,6 +24,7 @@ struct Field {
}; };
struct Frame { struct Frame {
QString function;
QString arch; QString arch;
QString file; QString file;
int line = 0; int line = 0;
@ -50,7 +51,7 @@ struct ProcessData: public QObject {
Q_OBJECT Q_OBJECT
public: public:
QString cmd; QString procKey;
QVector<TraceEvent> traceEvents; QVector<TraceEvent> traceEvents;
signals: signals:

View File

@ -8,6 +8,7 @@
#include <QDebug> #include <QDebug>
#include <QLabel> #include <QLabel>
#include <QListWidget>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "processselector.hpp" #include "processselector.hpp"

View File

@ -15,7 +15,7 @@ DataFeed::DataFeed(QIODevice *dev): QObject(dev) {
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit); connect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
} }
QSharedPointer<ProcessData> DataFeed::procData() { const QSharedPointer<ProcessData> &DataFeed::procData() {
return m_procData; return m_procData;
} }

View File

@ -28,7 +28,7 @@ class DataFeed: public QObject {
public: public:
DataFeed(QIODevice *dev); DataFeed(QIODevice *dev);
QSharedPointer<ProcessData> procData(); const QSharedPointer<ProcessData> &procData();
public slots: public slots:
void handleInit(); void handleInit();

View File

@ -80,6 +80,13 @@ void TraceEventModel::setProcessData(ProcessData *data) {
endResetModel(); endResetModel();
} }
TraceEvent TraceEventModel::traceEvent(int row) {
if (m_procData) {
return m_procData->traceEvents[row];
}
return {};
}
void TraceEventModel::addEvent(const TraceEvent &event) { void TraceEventModel::addEvent(const TraceEvent &event) {
auto index = m_traceEvents.size(); auto index = m_traceEvents.size();
beginInsertRows(QModelIndex(), index, index); beginInsertRows(QModelIndex(), index, index);

View File

@ -39,6 +39,9 @@ class TraceEventModel: public QAbstractTableModel {
void setProcessData(ProcessData *data); void setProcessData(ProcessData *data);
TraceEvent traceEvent(int row);
public slots: public slots:
void addEvent(const TraceEvent &event); void addEvent(const TraceEvent &event);
}; };

View File

@ -9,20 +9,48 @@
#include <QDebug> #include <QDebug>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QHeaderView> #include <QHeaderView>
#include <QListWidget>
#include <QSettings> #include <QSettings>
#include <QTableView>
#include "traceview.hpp" #include "traceview.hpp"
TraceView::TraceView(QWidget *parent): QWidget(parent) { TraceView::TraceView(QWidget *parent): QWidget(parent) {
auto lyt = new QHBoxLayout; auto lyt = new QHBoxLayout;
setLayout(lyt); setLayout(lyt);
m_splitter = new QSplitter(Qt::Vertical, this);
lyt->addWidget(m_splitter);
m_eventTable = new QTableView(this); m_eventTable = new QTableView(this);
m_model = new TraceEventModel; m_model = new TraceEventModel;
m_eventTable->setModel(m_model); m_eventTable->setModel(m_model);
m_eventTable->horizontalHeader()->setStretchLastSection(true); m_eventTable->horizontalHeader()->setStretchLastSection(true);
m_eventTable->verticalHeader()->hide(); m_eventTable->verticalHeader()->hide();
m_eventTable->setSelectionBehavior(QAbstractItemView::SelectRows); 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(); readState();
} }
@ -35,6 +63,9 @@ void TraceView::readState() {
settings.beginGroup("TraceView"); settings.beginGroup("TraceView");
m_eventTable->horizontalHeader()->restoreState(settings.value("eventTableState").toByteArray()); m_eventTable->horizontalHeader()->restoreState(settings.value("eventTableState").toByteArray());
m_eventTable->horizontalHeader()->restoreGeometry(settings.value("eventTableGeometry").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(); settings.endGroup();
} }
@ -43,9 +74,13 @@ void TraceView::writeState() {
settings.beginGroup("TraceView"); settings.beginGroup("TraceView");
settings.setValue("eventTableState", m_eventTable->horizontalHeader()->saveState()); settings.setValue("eventTableState", m_eventTable->horizontalHeader()->saveState());
settings.setValue("eventTableGeometry", m_eventTable->horizontalHeader()->saveGeometry()); 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(); settings.endGroup();
} }
void TraceView::setProcessData(ProcessData *data) { void TraceView::setProcessData(ProcessData *data) {
m_model->setProcessData(data); m_model->setProcessData(data);
m_callStackModel->clear();
} }

View File

@ -8,9 +8,11 @@
#pragma once #pragma once
#include <QSplitter>
#include <QTableView> #include <QTableView>
#include <QWidget> #include <QWidget>
#include "callstackmodel.hpp"
#include "traceeventmodel.hpp" #include "traceeventmodel.hpp"
#include "processdata.hpp" #include "processdata.hpp"
@ -19,7 +21,10 @@ class TraceView: public QWidget {
Q_OBJECT Q_OBJECT
private: private:
QTableView *m_eventTable; QTableView *m_eventTable = nullptr;
QTableView *m_callStack = nullptr;
QSplitter *m_splitter = nullptr;
CallStackModel *m_callStackModel = nullptr;
TraceEventModel *m_model = nullptr; TraceEventModel *m_model = nullptr;
public: public: