diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e01069..8de1bef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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( diff --git a/Makefile b/Makefile index e5dcfe4..57a399f 100644 --- a/Makefile +++ b/Makefile @@ -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} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0724376..eb3a436 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 . diff --git a/src/callstackmodel.cpp b/src/callstackmodel.cpp index 6ef8224..cdd5a97 100644 --- a/src/callstackmodel.cpp +++ b/src/callstackmodel.cpp @@ -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; } diff --git a/src/channelmodel.cpp b/src/channelmodel.cpp new file mode 100644 index 0000000..1860933 --- /dev/null +++ b/src/channelmodel.cpp @@ -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 +#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(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(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(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(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(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(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); +} + diff --git a/src/channelmodel.hpp b/src/channelmodel.hpp new file mode 100644 index 0000000..7b01f7c --- /dev/null +++ b/src/channelmodel.hpp @@ -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 + +#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); + +}; diff --git a/src/channelview.cpp b/src/channelview.cpp new file mode 100644 index 0000000..405ce71 --- /dev/null +++ b/src/channelview.cpp @@ -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 +#include +#include +#include +#include + +#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> &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(); +} diff --git a/src/channelview.hpp b/src/channelview.hpp new file mode 100644 index 0000000..c732b5f --- /dev/null +++ b/src/channelview.hpp @@ -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 +#include + +#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 m_channels; + QVector m_channelModelRoot; + QHash 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> &channels, int it = 0); + + void setProcessData(ProcessData *data); + + private: + void readState(); + + void writeState(); + +}; diff --git a/src/main.cpp b/src/main.cpp index 41d9293..f7b52dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4482361..80c38e1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -9,13 +9,14 @@ #include #include #include -#include #include #include #include #include #include +#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() { diff --git a/src/mainwindow.hpp b/src/mainwindow.hpp index 0cbf6fc..81be115 100644 --- a/src/mainwindow.hpp +++ b/src/mainwindow.hpp @@ -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(); diff --git a/src/processdata.cpp b/src/processdata.cpp index e1ae427..aadae1c 100644 --- a/src/processdata.cpp +++ b/src/processdata.cpp @@ -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 +#include +#include + +#include #include +#include #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 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 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; } diff --git a/src/processdata.hpp b/src/processdata.hpp index bee6bb0..f3abc32 100644 --- a/src/processdata.hpp +++ b/src/processdata.hpp @@ -8,18 +8,36 @@ #pragma once +#include + #include #include #include #include +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 fields; - Field(QJsonObject field = {}); + Field(const QJsonObject &field = {}); }; @@ -30,35 +48,89 @@ struct Frame { int line = 0; QVector fields; - Frame(QJsonObject frame = {}); + Frame(const QJsonObject &field = {}); }; struct TraceEvent { - QString channel; + uint64_t time = 0; + int _channel = 0; QString logMsg; QVector 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> 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 traceEvents; + QVector 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(); }; diff --git a/src/server.cpp b/src/server.cpp index e683b4c..c9ba08b 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -8,14 +8,17 @@ #include +#include +#include + #include "server.hpp" DataFeed::DataFeed(QIODevice *dev, bool skipInit): QObject(dev) { - m_dev = std::unique_ptr(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 &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(&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(&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(msgId); + qDebug() << "Connection is in invalid state, ending."; + m_dev->close(); + m_dev->deleteLater(); } } } +void DataFeed::handleMcTraceEvent() { + ox::Array hdrBuff; + m_dev->peek(hdrBuff.data(), hdrBuff.size()); + const auto msgSize = *reinterpret_cast(&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(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); diff --git a/src/server.hpp b/src/server.hpp index 758b07f..d65e2ce 100644 --- a/src/server.hpp +++ b/src/server.hpp @@ -23,18 +23,29 @@ class DataFeed: public QObject { private: QSharedPointer m_procData = QSharedPointer(new ProcessData); - std::unique_ptr 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 &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); diff --git a/src/traceeventmodel.cpp b/src/traceeventmodel.cpp index 918febe..3dcdf6a 100644 --- a/src/traceeventmodel.cpp +++ b/src/traceeventmodel.cpp @@ -8,13 +8,18 @@ #include +#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(); -} diff --git a/src/traceeventmodel.hpp b/src/traceeventmodel.hpp index 6a2e360..a354baa 100644 --- a/src/traceeventmodel.hpp +++ b/src/traceeventmodel.hpp @@ -25,10 +25,14 @@ class TraceEventModel: public QAbstractTableModel { }; private: - QVector m_traceEvents; + using TraceEventIdx = int; + QVector 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(); }; diff --git a/src/traceview.cpp b/src/traceview.cpp index a0e6d77..1d10bd2 100644 --- a/src/traceview.cpp +++ b/src/traceview.cpp @@ -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()) { diff --git a/src/traceview.hpp b/src/traceview.hpp index 35cab19..390c1be 100644 --- a/src/traceview.hpp +++ b/src/traceview.hpp @@ -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;