Add Channel viewer and selector and support for Ox messages

This commit is contained in:
Gary Talent 2023-01-15 16:52:33 -06:00
parent 021a7b0561
commit d269864c70
19 changed files with 867 additions and 203 deletions

View File

@ -1,16 +1,14 @@
cmake_minimum_required(VERSION 3.8.8)
project(bullock)
project(Bullock)
set(QTDIR "" CACHE STRING "Path to Qt Libraries")
include(deps/buildcore/base.cmake)
set(CMAKE_PREFIX_PATH ${QTDIR})
find_package(Qt5 COMPONENTS Core Network Widgets REQUIRED)
find_package(Qt6 COMPONENTS Core Network Widgets REQUIRED)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules)
include(address_sanitizer)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
@ -23,28 +21,23 @@ endif()
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare")
if (CMAKE_BUILD_TYPE STREQUAL "Release")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color")
endif()
if(APPLE)
set(CMAKE_MACOSX_RPATH OFF)
set(CMAKE_INSTALL_NAME_DIR "@executable_path/../Library/bullock")
set(BULLOCK_DIST_BIN bullock.app/Contents/MacOS)
set(BULLOCK_DIST_LIB bullock.app/Contents/Library)
set(BULLOCK_DIST_PLUGIN bullock.app/Contents/Plugins)
set(BULLOCK_DIST_RESOURCES bullock.app/Contents/Resources)
set(BULLOCK_DIST_MAC_APP_CONTENTS bullock.app/Contents)
set(CMAKE_INSTALL_NAME_DIR "@executable_path/../Library/Bullock")
set(BULLOCK_DIST_BIN Bullock.app/Contents/MacOS)
set(BULLOCK_DIST_LIB Bullock.app/Contents/Library)
set(BULLOCK_DIST_MODULE Bullock.app/Contents/Plugins)
set(BULLOCK_DIST_RESOURCES Bullock.app/Contents/Resources)
set(BULLOCK_DIST_MAC_APP_CONTENTS Bullock.app/Contents)
else()
set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../lib/bullock")
if(NOT ${QTDIR} STREQUAL "")
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} "${QTDIR}/lib")
endif()
set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../lib/Bullock")
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(BULLOCK_DIST_BIN bin)
set(BULLOCK_DIST_LIB lib)
@ -53,6 +46,11 @@ endif()
enable_testing()
add_subdirectory($ENV{OX_PATH} $ENV{OX_PATH})
include_directories(
SYSTEM
$ENV{OX_PATH}/src
)
add_subdirectory(src)
install(

View File

@ -1,85 +1,17 @@
OS=$(shell uname | tr [:upper:] [:lower:])
HOST_ENV=${OS}-$(shell uname -m)
DEVENV=devenv$(shell pwd | sed 's/\//-/g')
DEVENV_IMAGE=bullock-devenv
ifneq ($(shell which gmake 2> /dev/null),)
MAKE=gmake -s
PROJECT_NAME=Bullock
BUILDCORE_PATH=deps/buildcore
VCPKG_PKGS=
include ${BUILDCORE_PATH}/base.mk
ifeq ($(OS),darwin)
PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/${PROJECT_NAME}.app/Contents/MacOS/${PROJECT_NAME}
else
MAKE=make -s
endif
ifneq ($(shell which docker 2> /dev/null),)
ifeq ($(shell docker inspect --format="{{.State.Status}}" ${DEVENV} 2>&1),running)
ENV_RUN=docker exec -i -t --user $(shell id -u ${USER}) ${DEVENV}
endif
PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/bin/${PROJECT_NAME}
endif
make:
${ENV_RUN} ./scripts/run-make build
gba-pkg:
${ENV_RUN} ./scripts/run-make build install
${ENV_RUN} ./scripts/run-make build
${ENV_RUN} ./scripts/gba-pkg
preinstall:
${ENV_RUN} ./scripts/run-make build preinstall
install:
${ENV_RUN} ./scripts/run-make build install
clean:
${ENV_RUN} ./scripts/run-make build clean
purge:
${ENV_RUN} rm -rf build
test:
${ENV_RUN} ./scripts/run-make build test
.PHONY: run
run: install
./dist/current/bin/bullock
gdb: install
gdb --args ./dist/current/bin/bullock
devenv-image:
docker build . -t ${DEVENV_IMAGE}
devenv-create:
docker run -d \
-e LOCAL_USER_ID=$(shell id -u ${USER}) \
-e DISPLAY=$(DISPLAY) \
-e QT_AUTO_SCREEN_SCALE_FACTOR=1 \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v /run/dbus/:/run/dbus/ \
-v $(shell pwd):/usr/src/project \
-v /dev/shm:/dev/shm \
--restart=always \
--name ${DEVENV} \
-t ${DEVENV_IMAGE} bash
devenv-destroy:
docker rm -f ${DEVENV}
shell:
${ENV_RUN} bash
release:
${ENV_RUN} rm -rf build/${HOST_ENV}-release
${ENV_RUN} ./scripts/setup-build ${HOST_ENV} release
debug:
${ENV_RUN} rm -rf build/${HOST_ENV}-debug
${ENV_RUN} ./scripts/setup-build ${HOST_ENV} debug
asan:
${ENV_RUN} rm -rf build/${HOST_ENV}-asan
${ENV_RUN} ./scripts/setup-build ${HOST_ENV} asan
windows:
${ENV_RUN} rm -rf build/windows
${ENV_RUN} ./scripts/setup-build windows
windows-debug:
${ENV_RUN} rm -rf build/windows
${ENV_RUN} ./scripts/setup-build windows debug
gba:
${ENV_RUN} rm -rf build/gba-release
${ENV_RUN} ./scripts/setup-build gba release
gba-debug:
${ENV_RUN} rm -rf build/gba-debug
${ENV_RUN} ./scripts/setup-build gba debug
${PROJECT_EXECUTABLE}
.PHONY: debug
debug: install
${DEBUGGER} ${PROJECT_EXECUTABLE}

View File

@ -1,13 +1,15 @@
project(bullock)
project(Bullock)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKe_INCLUDE_CURRENT_DIR ON)
find_package(Qt5Widgets)
find_pacKage(Qt6Widgets)
set(CMAKE_AUTOMOC ON)
add_executable(
bullock MACOSX_BUNDLE
Bullock MACOSX_BUNDLE
callstackmodel.cpp
channelview.cpp
channelmodel.cpp
main.cpp
mainwindow.cpp
processdata.cpp
@ -18,19 +20,21 @@ add_executable(
)
target_link_libraries(
bullock
Qt5::Core
Qt5::Network
Qt5::Widgets
Bullock
OxMetalClaw
OxStd
Qt6::Core
Qt6::Network
Qt6::Widgets
)
if(APPLE)
set_target_properties(bullock PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
set_target_properties(Bullock PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
endif()
install(
TARGETS
bullock
Bullock
RUNTIME DESTINATION
${BULLOCK_DIST_BIN}
BUNDLE DESTINATION .

View File

@ -10,11 +10,11 @@
#include "callstackmodel.hpp"
int CallStackModel::rowCount(const QModelIndex &parent) const {
int CallStackModel::rowCount(const QModelIndex&) const {
return m_frames.size();
}
int CallStackModel::columnCount(const QModelIndex &parent) const {
int CallStackModel::columnCount(const QModelIndex&) const {
return Column::End;
}

192
src/channelmodel.cpp Normal file
View File

@ -0,0 +1,192 @@
/*
* Copyright 2018 - 2023 gary@drinkingtea.net
*
* 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 <algorithm>
#include "channelmodel.hpp"
QVariant ChannelModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case ColEnabled:
return tr("On");
case ColName:
return tr("Name");
case ColCount:
return tr("Messages");
case ColChildCount:
return tr("Child Messages");
}
}
return {};
}
Qt::ItemFlags ChannelModel::flags(const QModelIndex &index) const {
if (!index.isValid()) {
return {};
}
auto out = QAbstractItemModel::flags(index);
switch (index.column()) {
case ColEnabled:
out |= Qt::ItemIsUserCheckable | Qt::ItemIsEditable;
break;
}
return out;
}
bool ChannelModel::setData(const QModelIndex &index, const QVariant &val, int role) {
const auto item = static_cast<Channel*>(index.internalPointer());
switch (role) {
case Qt::EditRole:
return false;
case Qt::CheckStateRole:
switch (index.column()) {
case ColEnabled: {
item->setEnabled(val.toBool());
emit dataChanged(index, index);
emit m_procData->channelToggled();
return item->on;
}
}
break;
}
return QAbstractItemModel::setData(index, val, role);
}
QVariant ChannelModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) {
return {};
}
const auto item = static_cast<Channel*>(index.internalPointer());
switch (role) {
case Qt::CheckStateRole:
switch (index.column()) {
case ColEnabled:
return item->on ? Qt::Checked : Qt::Unchecked;
}
break;
case Qt::DisplayRole:
switch (index.column()) {
case ColName:
return item->name();
case ColCount:
return item->msgCnt;
case ColChildCount:
return item->msgCnt + item->childrenMsgCnt();
}
}
return {};
}
QModelIndex ChannelModel::index(int row, int column, const QModelIndex &parent) const {
if (!hasIndex(row, column, parent)) {
return {};
}
const auto parentItem = parent.isValid() ?
static_cast<Channel*>(parent.internalPointer()) :
m_rootCh;
const auto childItem = parentItem->children[row].get();
if (childItem) {
return createIndex(row, column, childItem);
}
return {};
}
QModelIndex ChannelModel::parent(const QModelIndex &index) const {
if (!index.isValid()) {
return {};
}
const auto item = static_cast<Channel*>(index.internalPointer());
const auto parentItem = item->parent;
if (parentItem == m_rootCh) {
return {};
}
return createIndex(parentItem->row(), 0, parentItem);
}
int ChannelModel::rowCount(const QModelIndex &parent) const {
Channel *ch = nullptr;
if (parent.isValid()) {
ch = static_cast<Channel*>(parent.internalPointer());
} else {
ch = m_rootCh;
}
if (!ch) {
return 0;
}
return ch->children.size();
}
int ChannelModel::columnCount(const QModelIndex&) const {
return ChannelColumnCnt;
}
Channel *ChannelModel::createChannel(Channel *ch, const QStringList &name, int it) {
if (it == name.size()) {
return ch;
}
const auto lvlName = name.sliced(0, it + 1).join(ChannelSplitter);
auto childIt = std::find_if(ch->children.begin(), ch->children.end(), [&](auto &val) {
return val->fullName() == lvlName;
});
Channel *child = nullptr;
if (childIt != ch->children.end()) {
child = (*childIt).get();
} else {
const auto childId = getChannelId(lvlName);
const auto insertPt = std::find_if(ch->children.begin(), ch->children.end(), [lvlName](const auto &b) {
return b->name() >= lvlName;
}).offset();
const auto chRow = ch->row();
const auto parIdx = m_rootCh == ch ? QModelIndex{} : createIndex(chRow, 0, ch);
beginInsertRows(parIdx, insertPt, 0);
child = (*ch->children.emplace(insertPt, ox::make_unique<Channel>(childId, ch))).get();
m_procData->channels[childId].channel = child;
endInsertRows();
//ch->children.empty
//std::sort(ch->children.begin(), ch->children.end(), [](const auto &a, const auto &b) {
// return a->name() < b->name();
//});
}
return createChannel(child, name, it + 1);
}
void ChannelModel::addChannel(ChId chId) {
addChannelInternal(chId);
}
void ChannelModel::incChannel(ChId chId) {
++m_procData->channels[chId].channel->msgCnt;
}
void ChannelModel::setProcessData(ProcessData *data) {
if (m_procData) {
disconnect(m_procData, &ProcessData::channelAdded, this, &ChannelModel::addChannel);
disconnect(m_procData, &ProcessData::channelInc, this, &ChannelModel::incChannel);
}
m_procData = data;
beginResetModel();
m_rootCh = &m_procData->rootChannel;
if (m_procData) {
for (auto i = 0l; i < m_procData->traceEvents.size(); ++i) {
addChannelInternal(m_procData->traceEvents[i]._channel);
}
connect(m_procData, &ProcessData::channelAdded, this, &ChannelModel::addChannel);
connect(m_procData, &ProcessData::channelInc, this, &ChannelModel::incChannel);
}
endResetModel();
}
Channel *ChannelModel::addChannelInternal(ChId chId) {
const auto &chName = getChannelFullName(chId);
auto namePath = chName.split(ChannelSplitter);
if (namePath.size() == 0) {
namePath = {chName};
}
return createChannel(m_rootCh, namePath);
}

69
src/channelmodel.hpp Normal file
View File

@ -0,0 +1,69 @@
/*
* Copyright 2018 - 2023 gary@drinkingtea.net
*
* 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 <QAbstractItemModel>
#include "processdata.hpp"
enum ChannelColumn {
ColEnabled = 0,
ColName,
ColCount,
ColChildCount,
ChannelColumnCnt,
};
class ChannelModel: public QAbstractItemModel {
Q_OBJECT
private:
Channel *m_rootCh = nullptr;
ProcessData *m_procData = nullptr;
public:
ChannelModel() = default;
[[nodiscard]]
bool setData(const QModelIndex &index, const QVariant &val, int role) override;
[[nodiscard]]
QVariant data(const QModelIndex &index, int role) const override;
[[nodiscard]]
Qt::ItemFlags flags(const QModelIndex &index) const override;
[[nodiscard]]
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
[[nodiscard]]
QModelIndex index(int row, int column,
const QModelIndex &parent) const override;
[[nodiscard]]
QModelIndex parent(const QModelIndex &index) const override;
[[nodiscard]]
int rowCount(const QModelIndex &parent) const override;
[[nodiscard]]
int columnCount(const QModelIndex &parent) const override;
void addChannel(ChId chId);
void incChannel(ChId chId);
void setProcessData(ProcessData *data);
private:
Channel *createChannel(Channel *ch, const QStringList &name, int it = 0);
Channel *addChannelInternal(ChId chId);
};

76
src/channelview.cpp Normal file
View File

@ -0,0 +1,76 @@
/*
* Copyright 2018 - 2021 gary@drinkingtea.net
*
* 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 <QHeaderView>
#include <QLabel>
#include <QTreeView>
#include <QSettings>
#include <QVBoxLayout>
#include "channelview.hpp"
ChannelView::ChannelView(QWidget *parent): QWidget(parent) {
const auto lyt = new QVBoxLayout(this);
m_tree = new QTreeView(this);
m_model = new ChannelModel;
m_tree->setModel(m_model);
lyt->addWidget(new QLabel(tr("Channels"), this));
lyt->addWidget(m_tree);
m_tree->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
m_tree->setTreePosition(ChannelColumn::ColName);
readState();
}
ChannelView::~ChannelView() {
writeState();
}
void ChannelView::logChEntry(ChId chId) {
++m_channels[chId]->msgCnt;
}
bool ChannelView::channelOn(ChId chId) const {
return m_channels[chId]->on;
}
Channel *ChannelView::findChannel(const QStringList &path, const ox::Vector<ox::UPtr<Channel>> &channels, int it) {
if (it >= path.size()) {
return nullptr;
}
const auto &name = path[it];
for (const auto &ch : channels) {
if (name == ch->fullName()) {
if (it == path.size() - 1) {
return ch.get();
} else {
return findChannel(path, ch->children, it + 1);
}
}
}
return nullptr;
}
void ChannelView::setProcessData(ProcessData *data) {
m_model->setProcessData(data);
//m_frameTableModel->clear();
//m_fieldView->clear();
}
void ChannelView::readState() {
QSettings settings;
settings.beginGroup("Logger.ChannelView");
m_tree->header()->restoreState(settings.value("headers").toByteArray());
settings.endGroup();
}
void ChannelView::writeState() {
QSettings settings;
settings.beginGroup("Logger.ChannelView");
settings.setValue("headers", m_tree->header()->saveState());
settings.endGroup();
}

54
src/channelview.hpp Normal file
View File

@ -0,0 +1,54 @@
/*
* Copyright 2018 - 2021 gary@drinkingtea.net
*
* 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 <QVector>
#include <QWidget>
#include "callstackmodel.hpp"
#include "channelmodel.hpp"
#include "traceeventmodel.hpp"
#pragma once
class ChannelView: public QWidget {
Q_OBJECT
private:
int m_selectedChannel = 0;
class QTreeView *m_tree = nullptr;
// maps channel ids to the channels
QVector<Channel*> m_channels;
QVector<Channel*> m_channelModelRoot;
QHash<QString, Channel*> m_channelMap;
ChannelModel *m_model = nullptr;
public:
explicit ChannelView(QWidget *parent = nullptr);
~ChannelView() override;
void addChannel(ChId chId);
void logChEntry(ChId chId);
[[nodiscard]]
bool channelOn(ChId chId) const;
[[nodiscard]]
Channel *findChannel(const QStringList &path, const ox::Vector<ox::UPtr<Channel>> &channels, int it = 0);
void setProcessData(ProcessData *data);
private:
void readState();
void writeState();
};

View File

@ -13,9 +13,9 @@
int main(int argc, char **args) {
QApplication app(argc, args);
app.setApplicationName("Bullock");
app.setOrganizationName("DrinkingTea");
app.setOrganizationDomain("drinkingtea.net");
QApplication::setApplicationName("Bullock");
QApplication::setOrganizationName("DrinkingTea");
QApplication::setOrganizationDomain("drinkingtea.net");
LogServer server;
@ -24,5 +24,5 @@ int main(int argc, char **args) {
QObject::connect(&server, &LogServer::newDataFeed, &w, &MainWindow::addDataFeed);
return app.exec();
return QApplication::exec();
}

View File

@ -9,13 +9,14 @@
#include <QAction>
#include <QApplication>
#include <QDateTime>
#include <QDesktopWidget>
#include <QHBoxLayout>
#include <QMenuBar>
#include <QScreen>
#include <QSettings>
#include <QSplitter>
#include "channelview.hpp"
#include "mainwindow.hpp"
MainWindow::MainWindow() {
@ -32,12 +33,14 @@ MainWindow::MainWindow() {
auto leftPane = new QWidget(this);
auto leftPaneSplitter = new QSplitter(Qt::Vertical, leftPane);
m_channelView = new ChannelView(this);
leftPaneSplitter->addWidget(m_channelView);
m_procSelector = new ProcessSelector(leftPaneSplitter);
connect(m_procSelector, &ProcessSelector::selectionChanged, this, &MainWindow::setProcess);
leftPaneSplitter->addWidget(m_procSelector);
m_splitter->addWidget(leftPaneSplitter);
m_traceView = new TraceView(m_splitter);
m_traceView = new TraceView(m_channelView, m_splitter);
m_splitter->addWidget(m_traceView);
m_splitter->setStretchFactor(1, 3);
@ -64,6 +67,8 @@ void MainWindow::readState() {
QSettings settings;
settings.beginGroup("MainWindow");
m_splitter->restoreState(settings.value("splitterState").toByteArray());
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("state").toByteArray());
settings.endGroup();
}
@ -71,6 +76,8 @@ void MainWindow::writeState() {
QSettings settings;
settings.beginGroup("MainWindow");
settings.setValue("splitterState", m_splitter->saveState());
settings.setValue("geometry", saveGeometry());
settings.setValue("state", saveState());
settings.endGroup();
}
@ -81,6 +88,7 @@ void MainWindow::setProcess(QString procKey) {
m_currentProc = nullptr;
}
m_traceView->setProcessData(m_currentProc);
m_channelView->setProcessData(m_currentProc);
}
void MainWindow::setupMenu() {

View File

@ -23,6 +23,7 @@ class MainWindow: public QMainWindow {
ProcessData *m_currentProc = nullptr;
ProcessSelector *m_procSelector = nullptr;
TraceView *m_traceView = nullptr;
ChannelView *m_channelView = nullptr;
public:
MainWindow();

View File

@ -1,53 +1,193 @@
/*
* Copyright 2018 - 2021 gary@drinkingtea.net
* Copyright 2018 - 2023 gary@drinkingtea.net
*
* 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 <ox/std/assert.hpp>
#include <ox/std/algorithm.hpp>
#include <QHash>
#include <QJsonArray>
#include <QSettings>
#include "processdata.hpp"
Field::Field(QJsonObject field) {
this->name = field["name"].toString();
this->type = field["type"].toString();
this->value = field["value"].toString();
auto fields = field["fields"].toArray();
for (auto field : fields) {
this->fields.push_back(field.toObject());
static ChId channelIdIt = 0;
static QHash<QString, ChId> channelToId;
struct ChannelData {
QString fullName;
QString name;
ChId parent = -1;
ChannelData(const QString &pFullName, ChId pParent) {
fullName = pFullName;
parent = pParent;
const auto lastSep = fullName.lastIndexOf(ChannelSplitter);
if (lastSep < 0) {
name = fullName;
} else {
name = fullName.mid(lastSep + ox_strlen(ChannelSplitter));
}
}
};
static QVector<ChannelData> idToChannel = {{"", -1}};
static void addChannel(const QString &ch, ChId parent = -1) {
const auto chPath = ch.split(ChannelSplitter);
if (chPath.empty()) {
return;
}
const auto add = [](const QString &c, ChId parent) {
if (!channelToId.contains(c)) {
channelToId.emplace(c, ++channelIdIt);
idToChannel.emplace_back(c, parent);
}
return channelToId[c];
};
auto current = chPath.first();
add(current, parent);
for (auto i = 1; i < chPath.size(); ++i) {
const auto &segment = chPath[i];
current += ChannelSplitter + segment;
parent = add(current, parent);
}
}
Frame::Frame(QJsonObject frame) {
[[nodiscard]]
const QString &getChannelFullName(ChId id) {
return idToChannel[id].fullName;
}
[[nodiscard]]
const QString &getChannelName(ChId id) {
return idToChannel[id].name;
}
[[nodiscard]]
ChId getChannelId(const QString &ch) {
if (!channelToId.contains(ch)) [[unlikely]] {
addChannel(ch);
return channelIdIt;
}
return channelToId.value(ch);
}
[[nodiscard]]
ChId getChannelParentId(ChId id) {
return idToChannel[id].parent;
}
Channel::Channel(ChId pId, Channel *pParent) {
id = pId;
parent = pParent;
QSettings settings;
if (!fullName().isEmpty()) {
settings.beginGroup("Bullock.Channel.Enabled");
on = settings.value(fullName(), true).toBool();
settings.endGroup();
}
}
int Channel::childrenMsgCnt() const noexcept {
auto out = 0;
for (const auto &c : children) {
out += c->msgCnt + c->childrenMsgCnt();
}
return out;
}
bool Channel::showMsgs() const noexcept {
return this->on && (!parent || parent->showMsgs());
}
const QString &Channel::name() const {
return getChannelName(id);
}
const QString &Channel::fullName() const {
return getChannelFullName(id);
}
int Channel::row() const noexcept {
if (!parent) [[unlikely]] {
return 0;
}
for (auto i = 0; const auto &c : parent->children) {
if (c.get() == this) {
return i;
}
++i;
}
return -1;
}
void Channel::setEnabled(bool val) noexcept {
on = val;
QSettings settings;
settings.beginGroup("Bullock.Channel.Enabled");
settings.setValue(fullName(), val);
settings.endGroup();
}
[[nodiscard]]
bool Channel::enabled() const noexcept {
return (parent == nullptr || parent->enabled()) && on;
}
Field::Field(const QJsonObject &field) {
this->name = field["name"].toString();
this->type = field["type"].toString();
this->value = field["value"].toString();
const auto fields = field["fields"].toArray();
for (const auto &field : fields) {
this->fields.emplace_back(field.toObject());
}
}
Frame::Frame(const 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();
for (auto field : fields) {
this->fields.push_back(field.toObject());
const auto fields = frame["fields"].toArray();
for (const auto &field : fields) {
this->fields.emplace_back(field.toObject());
}
}
TraceEvent::TraceEvent(QJsonObject tp) {
this->channel = tp["channel"].toString();
TraceEvent::TraceEvent(const QJsonObject &tp) {
this->_channel = getChannelId(tp["channel"].toString());
this->logMsg = tp["log_msg"].toString();
this->_file = tp["file"].toString();
this->_line = tp["line"].toInt();
auto frames = tp["frames"].toArray();
for (auto frame : frames) {
this->frames.push_back(frame.toObject());
const auto frames = tp["frames"].toArray();
for (const auto &frame : frames) {
this->frames.emplace_back(frame.toObject());
}
}
QString TraceEvent::file() const {
TraceEvent::TraceEvent(const ox::trace::TraceMsgRcv &tm) {
this->_channel = getChannelId(tm.ch.c_str());
this->logMsg = tm.msg.c_str();
this->_file = tm.file.c_str();
this->_line = tm.line;
this->time = tm.time;
}
const QString &TraceEvent::channel() const noexcept {
return idToChannel[_channel].fullName;
}
const QString &TraceEvent::file() const noexcept {
return this->_file;
}
int TraceEvent::line() const {
int TraceEvent::line() const noexcept {
return this->_line;
}

View File

@ -8,18 +8,36 @@
#pragma once
#include <ox/std/trace.hpp>
#include <QJsonObject>
#include <QObject>
#include <QStringList>
#include <QVector>
using ChId = int;
constexpr auto ChannelSplitter = "::";
[[nodiscard]]
const QString &getChannelFullName(ChId id);
[[nodiscard]]
const QString &getChannelName(ChId id);
[[nodiscard]]
ChId getChannelId(const QString &ch);
[[nodiscard]]
ChId getChannelParentId(ChId id);
struct Field {
QString name;
QString type;
QString value;
QVector<Field> fields;
Field(QJsonObject field = {});
Field(const QJsonObject &field = {});
};
@ -30,35 +48,89 @@ struct Frame {
int line = 0;
QVector<Field> fields;
Frame(QJsonObject frame = {});
Frame(const QJsonObject &field = {});
};
struct TraceEvent {
QString channel;
uint64_t time = 0;
int _channel = 0;
QString logMsg;
QVector<Frame> frames;
QString _file;
int _line;
int _line = 0;
TraceEvent(QJsonObject tp = {});
TraceEvent() = default;
QString file() const;
TraceEvent(const QJsonObject &tp);
int line() const;
TraceEvent(const ox::trace::TraceMsgRcv &tm);
[[nodiscard]]
const QString &channel() const noexcept;
[[nodiscard]]
const QString &file() const noexcept;
[[nodiscard]]
int line() const noexcept;
};
struct Channel {
Channel *parent = nullptr;
ChId id = -1;
int msgCnt = 0;
bool on = true;
ox::Vector<ox::UPtr<Channel>> children;
Channel(ChId pId, Channel *pParent);
[[nodiscard]]
int childrenMsgCnt() const noexcept;
[[nodiscard]]
bool showMsgs() const noexcept;
[[nodiscard]]
const QString &name() const;
[[nodiscard]]
const QString &fullName() const;
[[nodiscard]]
int row() const noexcept;
void setEnabled(bool val) noexcept;
[[nodiscard]]
bool enabled() const noexcept;
};
struct ProcessData: public QObject {
Q_OBJECT
public:
struct ChannelEntry {
// the initialization of channel should not be used to track whether
// or not a process has yet received a given channel
bool present = false;
Channel *channel = nullptr;
};
QString procKey;
QVector<TraceEvent> traceEvents;
QVector<ChannelEntry> channels;
Channel rootChannel = Channel(0, nullptr);
signals:
/**
* Emitted whenever a new TraceEvent is added.
* Emits the index of the TraceEvent
*/
void traceEvent(const TraceEvent&);
void traceEvent(std::size_t);
void channelAdded(int);
void channelInc(int);
void channelToggled();
};

View File

@ -8,14 +8,17 @@
#include <QDebug>
#include <ox/mc/read.hpp>
#include <ox/std/trace.hpp>
#include "server.hpp"
DataFeed::DataFeed(QIODevice *dev, bool skipInit): QObject(dev) {
m_dev = std::unique_ptr<QIODevice>(dev);
m_dev = dev;
if (!skipInit) {
connect(m_dev.get(), &QIODevice::readyRead, this, &DataFeed::handleInit);
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
} else {
connect(m_dev.get(), &QIODevice::readyRead, this, &DataFeed::read);
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
}
}
@ -24,35 +27,100 @@ const QSharedPointer<ProcessData> &DataFeed::procData() {
}
void DataFeed::handleInit() {
auto doc = QJsonDocument::fromJson(m_dev->readLine());
if (doc.isObject()) {
auto msg = doc.object();
if (msg["type"].toString() == "Init") {
disconnect(m_dev.get(), &QIODevice::readyRead, this, &DataFeed::handleInit);
connect(m_dev.get(), &QIODevice::readyRead, this, &DataFeed::read);
const auto init = [&] {
disconnect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
};
ox::trace::MsgId peekChar;
m_dev->peek(reinterpret_cast<char*>(&peekChar), 1);
if (peekChar == ox::trace::MsgId::Json) {
auto doc = QJsonDocument::fromJson(m_dev->readLine());
if (doc.isObject()) {
auto msg = doc.object();
if (msg["type"].toString() == "Init") {
init();
}
}
} else if (peekChar == ox::trace::MsgId::Init) {
m_dev->skip(1);
init();
}
}
void DataFeed::read() {
while (m_dev && m_dev->bytesAvailable()) {
const auto json = m_dev->readLine();
const auto doc = QJsonDocument::fromJson(json);
if (m_procData) {
const auto msg = doc.object();
if (msg["type"] == "TraceEvent") {
m_procData->traceEvents.push_back(msg["data"].toObject());
emit m_procData->traceEvent(m_procData->traceEvents.last());
} else if (msg["type"] == "Init") {
emit feedEnd(m_dev.get());
m_dev.release();
} else {
qDebug().noquote() << "Bad message:" << json;
ox::trace::MsgId msgId;
m_dev->peek(reinterpret_cast<char*>(&msgId), 1);
if (msgId == ox::trace::MsgId::Json) {
const auto json = m_dev->readLine();
const auto doc = QJsonDocument::fromJson(json);
if (m_procData) {
const auto msg = doc.object();
if (msg["type"] == "TraceEvent") {
addTraceEvent(msg["data"].toObject());
} else if (msg["type"] == "Init") {
endFeed();
} else {
qDebug().noquote() << "Bad message:" << json;
}
}
} else if (msgId == ox::trace::MsgId::TraceEvent) {
if (m_dev->bytesAvailable() > 5) {
handleMcTraceEvent();
}
} else if (msgId == ox::trace::MsgId::Init) {
endFeed();
} else {
qDebug().noquote() << "Bad message id:" << static_cast<int>(msgId);
qDebug() << "Connection is in invalid state, ending.";
m_dev->close();
m_dev->deleteLater();
}
}
}
void DataFeed::handleMcTraceEvent() {
ox::Array<char, 5> hdrBuff;
m_dev->peek(hdrBuff.data(), hdrBuff.size());
const auto msgSize = *reinterpret_cast<uint32_t*>(&hdrBuff[1]);
if (m_dev->bytesAvailable() < msgSize) {
return;
}
m_dev->skip(5);
auto msgBuff = ox_malloca(msgSize, char);
m_dev->read(msgBuff.get(), msgSize);
const auto [msg, err] = ox::readMC<ox::trace::TraceMsgRcv>(msgBuff.get(), msgSize);
if (err) [[unlikely]] {
qDebug().noquote() << "Bad message";
return;
}
addTraceEvent(msg);
}
void DataFeed::endFeed() {
disconnect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
emit feedEnd(m_dev);
m_dev = nullptr;
}
static ProcessData::ChannelEntry &chEntry(auto *list, ChId id) {
if (id >= list->size()) {
list->resize(id + 1);
}
return (*list)[id];
}
void DataFeed::addTraceEvent(TraceEvent teSrc) {
const auto &te = m_procData->traceEvents.emplace_back(std::move(teSrc));
auto &ce = chEntry(&m_procData->channels, te._channel);
if (!ce.present) {
ce.present = true;
emit m_procData->channelAdded(te._channel);
}
emit m_procData->channelInc(te._channel);
emit m_procData->traceEvent(m_procData->traceEvents.size() - 1);
}
LogServer::LogServer() {
m_server->listen(QHostAddress::Any, 5590);

View File

@ -23,18 +23,29 @@ class DataFeed: public QObject {
private:
QSharedPointer<ProcessData> m_procData = QSharedPointer<ProcessData>(new ProcessData);
std::unique_ptr<QIODevice> m_dev;
QIODevice *m_dev = nullptr;
public:
/**
* Constructor
* @param dev typically a TCP connection, but could be any QIODevice
* @param skipInit indicates that the feed should not expect an init message
*/
explicit DataFeed(QIODevice *dev, bool skipInit = false);
const QSharedPointer<ProcessData> &procData();
public slots:
void handleInit();
void read();
private:
void handleMcTraceEvent();
void endFeed();
void addTraceEvent(TraceEvent teSrc);
signals:
void feedEnd(QIODevice*);
@ -49,7 +60,6 @@ class LogServer: public QObject {
public:
LogServer();
public slots:
void handleConnection();
void setupDataFeed(QIODevice *conn);

View File

@ -8,13 +8,18 @@
#include <QDebug>
#include "channelview.hpp"
#include "traceeventmodel.hpp"
int TraceEventModel::rowCount(const QModelIndex &parent) const {
return m_traceEvents.size();
TraceEventModel::TraceEventModel(ChannelView *cv) {
m_channelView = cv;
}
int TraceEventModel::columnCount(const QModelIndex &parent) const {
int TraceEventModel::rowCount(const QModelIndex&) const {
return m_visibleTraceEvents.size();
}
int TraceEventModel::columnCount(const QModelIndex&) const {
return Column::End;
}
@ -43,16 +48,16 @@ QVariant TraceEventModel::data(const QModelIndex &index, int role) const {
return QVariant();
}
if (index.row() >= m_traceEvents.size() || index.row() < 0) {
if (index.row() >= m_visibleTraceEvents.size() || index.row() < 0) {
return QVariant();
}
if (role == Qt::DisplayRole) {
const auto &te = m_traceEvents[index.row()];
const auto eventId = m_visibleTraceEvents[index.row()];
const auto &te = m_procData->traceEvents[eventId];
switch (index.column()) {
case Column::Channel:
return te.channel;
return te.channel();
case Column::Source:
return QString("%1:%2").arg(te.file()).arg(te.line());
case Column::Message:
@ -65,31 +70,51 @@ QVariant TraceEventModel::data(const QModelIndex &index, int role) const {
}
void TraceEventModel::setProcessData(ProcessData *data) {
beginResetModel();
m_traceEvents.clear();
if (m_procData) {
disconnect(m_procData, &ProcessData::traceEvent, this, &TraceEventModel::addEvent);
disconnect(m_procData, &ProcessData::channelToggled, this, &TraceEventModel::resetChannels);
}
m_procData = data;
if (m_procData) {
for (const auto &te : m_procData->traceEvents) {
m_traceEvents.push_back(te);
}
connect(m_procData, &ProcessData::traceEvent, this, &TraceEventModel::addEvent);
connect(m_procData, &ProcessData::channelToggled, this, &TraceEventModel::resetChannels);
resetChannels();
}
}
const TraceEvent &TraceEventModel::traceEvent(int row) {
return m_procData->traceEvents[m_visibleTraceEvents[row]];
}
void TraceEventModel::addEvent(std::size_t idx) {
const auto &te = m_procData->traceEvents[idx];
const auto &ch = m_procData->channels[te._channel];
if (ch.channel->enabled()) {
addVisibleEvent(idx);
}
}
void TraceEventModel::addVisibleEvent(std::size_t idx) {
auto index = m_procData->traceEvents.size();
beginInsertRows(QModelIndex(), index, index);
m_visibleTraceEvents.push_back(idx);
endInsertRows();
}
void TraceEventModel::clearVisibleEvents() {
m_visibleTraceEvents.clear();
}
void TraceEventModel::resetChannels() {
beginResetModel();
m_visibleTraceEvents.clear();
for (auto i = 0l; i < m_procData->traceEvents.size(); ++i) {
const auto &te = m_procData->traceEvents[i];
const auto &ch = m_procData->channels[te._channel];
if (ch.channel->enabled()) {
m_visibleTraceEvents.push_back(i);
}
}
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);
m_traceEvents.push_back(event);
endInsertRows();
}

View File

@ -25,10 +25,14 @@ class TraceEventModel: public QAbstractTableModel {
};
private:
QVector<TraceEvent> m_traceEvents;
using TraceEventIdx = int;
QVector<TraceEventIdx> m_visibleTraceEvents;
ProcessData *m_procData = nullptr;
class ChannelView *m_channelView = nullptr;
public:
explicit TraceEventModel(class ChannelView *cv);
int rowCount(const QModelIndex &parent = QModelIndex()) const override ;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
@ -39,9 +43,17 @@ class TraceEventModel: public QAbstractTableModel {
void setProcessData(ProcessData *data);
TraceEvent traceEvent(int row);
[[nodiscard]]
const TraceEvent &traceEvent(int row);
public slots:
void addEvent(const TraceEvent &event);
void addEvent(std::size_t idx);
private:
void addVisibleEvent(std::size_t idx);
void clearVisibleEvents();
void resetChannels();
};

View File

@ -33,7 +33,8 @@ static QTreeWidgetItem *treeItem(const Field &field, QTreeWidget *treeWidget, QT
return item;
}
TraceView::TraceView(QWidget *parent): QWidget(parent) {
TraceView::TraceView(class ChannelView *cv, QWidget *parent): QWidget(parent) {
m_channelView = cv;
auto lyt = new QHBoxLayout;
setLayout(lyt);
@ -41,14 +42,14 @@ TraceView::TraceView(QWidget *parent): QWidget(parent) {
lyt->addWidget(m_splitter);
m_eventTable = new QTableView(this);
m_model = new TraceEventModel;
m_model = new TraceEventModel(cv);
m_eventTable->setModel(m_model);
m_eventTable->horizontalHeader()->setStretchLastSection(true);
m_eventTable->verticalHeader()->hide();
m_eventTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_eventTable->setSelectionMode(QAbstractItemView::SingleSelection);
connect(m_eventTable->selectionModel(), &QItemSelectionModel::selectionChanged,
[this](const QItemSelection &selected, const QItemSelection &deselected) {
[this](const QItemSelection &selected, const QItemSelection&) {
m_frameTableModel->clear();
m_fieldView->clear();
auto indexes = selected.indexes();
@ -100,6 +101,7 @@ void TraceView::readState() {
m_eventTable->horizontalHeader()->restoreGeometry(settings.value("eventTableGeometry").toByteArray());
m_frameTable->horizontalHeader()->restoreState(settings.value("frameTableState").toByteArray());
m_frameTable->horizontalHeader()->restoreGeometry(settings.value("frameTableGeometry").toByteArray());
m_frameTable->setAlternatingRowColors(true);
m_fieldView->header()->restoreState(settings.value("fieldViewState").toByteArray());
m_fieldView->header()->restoreGeometry(settings.value("fieldViewGeometry").toByteArray());
m_splitter->restoreState(settings.value("splitterState").toByteArray());
@ -127,7 +129,7 @@ void TraceView::setProcessData(ProcessData *data) {
m_fieldView->clear();
}
void TraceView::handleFrameSelection(const QItemSelection &selected, const QItemSelection &deselected) {
void TraceView::handleFrameSelection(const QItemSelection &selected, const QItemSelection&) {
auto indexes = selected.indexes();
m_fieldView->clear();
if (indexes.size()) {

View File

@ -30,9 +30,10 @@ class TraceView: public QWidget {
QSplitter *m_lowerSplitter = nullptr;
CallStackModel *m_frameTableModel = nullptr;
TraceEventModel *m_model = nullptr;
class ChannelView *m_channelView = nullptr;
public:
explicit TraceView(QWidget *parent = nullptr);
explicit TraceView(class ChannelView *cv, QWidget *parent = nullptr);
~TraceView() override;