Add stack trace viewer
This commit is contained in:
parent
733350b143
commit
df47a6a1bf
@ -7,6 +7,7 @@ set(CMAKE_AUTOMOC ON)
|
||||
|
||||
add_executable(
|
||||
bullock MACOSX_BUNDLE
|
||||
callstackmodel.cpp
|
||||
main.cpp
|
||||
mainwindow.cpp
|
||||
processdata.cpp
|
||||
|
73
src/callstackmodel.cpp
Normal file
73
src/callstackmodel.cpp
Normal 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
43
src/callstackmodel.hpp
Normal 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);
|
||||
|
||||
};
|
@ -12,6 +12,7 @@
|
||||
#include <QDesktopWidget>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMenuBar>
|
||||
#include <QSettings>
|
||||
#include <QSplitter>
|
||||
|
||||
#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();
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSplitter>
|
||||
|
||||
#include "processselector.hpp"
|
||||
#include "traceview.hpp"
|
||||
@ -18,6 +19,7 @@ class MainWindow: public QMainWindow {
|
||||
|
||||
private:
|
||||
QHash<QString, QSharedPointer<ProcessData>> 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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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<TraceEvent> traceEvents;
|
||||
|
||||
signals:
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "processselector.hpp"
|
||||
|
@ -15,7 +15,7 @@ DataFeed::DataFeed(QIODevice *dev): QObject(dev) {
|
||||
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
|
||||
}
|
||||
|
||||
QSharedPointer<ProcessData> DataFeed::procData() {
|
||||
const QSharedPointer<ProcessData> &DataFeed::procData() {
|
||||
return m_procData;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ class DataFeed: public QObject {
|
||||
public:
|
||||
DataFeed(QIODevice *dev);
|
||||
|
||||
QSharedPointer<ProcessData> procData();
|
||||
const QSharedPointer<ProcessData> &procData();
|
||||
|
||||
public slots:
|
||||
void handleInit();
|
||||
|
@ -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);
|
||||
|
@ -39,6 +39,9 @@ class TraceEventModel: public QAbstractTableModel {
|
||||
|
||||
void setProcessData(ProcessData *data);
|
||||
|
||||
TraceEvent traceEvent(int row);
|
||||
|
||||
public slots:
|
||||
void addEvent(const TraceEvent &event);
|
||||
|
||||
};
|
||||
|
@ -9,20 +9,48 @@
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QListWidget>
|
||||
#include <QSettings>
|
||||
#include <QTableView>
|
||||
|
||||
#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();
|
||||
}
|
||||
|
@ -8,9 +8,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSplitter>
|
||||
#include <QTableView>
|
||||
#include <QWidget>
|
||||
|
||||
#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:
|
||||
|
Loading…
Reference in New Issue
Block a user