From da8f4d99267fd4cb39ccfef2efd4b6206ba97c5d Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 9 May 2026 08:59:19 +0200 Subject: [PATCH] Add debug graph to static network --- examples/CMakeLists.txt | 1 + include/kpn/channel.hpp | 16 ++++++ include/kpn/fanout.hpp | 1 + include/kpn/network.hpp | 15 ----- include/kpn/static_network.hpp | 100 ++++++++++++++++++++++++++++----- 5 files changed, 104 insertions(+), 29 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ce5a8b0..c25e732 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -35,6 +35,7 @@ if(OpenCV_FOUND) if(KPN_WEB_DEBUG) kpn_target_enable_web_debug(09_opencv_cellshade) + kpn_target_enable_web_debug(13_debug_cellshade) endif() message(STATUS "KPN++ example 09_opencv_cellshade: OpenCV ${OpenCV_VERSION} found — building") else() diff --git a/include/kpn/channel.hpp b/include/kpn/channel.hpp index 23f57f1..683cb04 100644 --- a/include/kpn/channel.hpp +++ b/include/kpn/channel.hpp @@ -172,4 +172,20 @@ private: ChannelStats stats_; }; +// ── Channel probe — type-erased snapshot accessor ───────────────────────────── +// Used by both Network and StaticNetwork for diagnostics. + +struct IChannelProbe { + virtual ~IChannelProbe() = default; + virtual ChannelSnapshot snapshot() const = 0; +}; + +template +struct ChannelProbe : IChannelProbe { + const Channel& ch; + std::string name; + ChannelProbe(const Channel& c, std::string n) : ch(c), name(std::move(n)) {} + ChannelSnapshot snapshot() const override { return ch.snapshot(name); } +}; + } // namespace kpn diff --git a/include/kpn/fanout.hpp b/include/kpn/fanout.hpp index 2afab26..c73b7f3 100644 --- a/include/kpn/fanout.hpp +++ b/include/kpn/fanout.hpp @@ -53,6 +53,7 @@ public: static constexpr std::size_t input_count = 1; static constexpr std::size_t output_count = N; + static constexpr std::size_t unique_tag = Id; explicit FanoutNode(std::size_t fifo_capacity = 5) : fifo_capacity_(fifo_capacity) diff --git a/include/kpn/network.hpp b/include/kpn/network.hpp index 7e563ef..41f6c3a 100644 --- a/include/kpn/network.hpp +++ b/include/kpn/network.hpp @@ -35,21 +35,6 @@ public: : std::runtime_error(std::move(msg)) {} }; -// ── Channel snapshot accessor — type-erased so Network can collect them ─────── - -struct IChannelProbe { - virtual ~IChannelProbe() = default; - virtual ChannelSnapshot snapshot() const = 0; -}; - -template -struct ChannelProbe : IChannelProbe { - const Channel& ch; - std::string name; - ChannelProbe(const Channel& c, std::string n) : ch(c), name(std::move(n)) {} - ChannelSnapshot snapshot() const override { return ch.snapshot(name); } -}; - // ── Network ─────────────────────────────────────────────────────────────────── class Network : public INode { diff --git a/include/kpn/static_network.hpp b/include/kpn/static_network.hpp index 4db1c13..bacdf54 100644 --- a/include/kpn/static_network.hpp +++ b/include/kpn/static_network.hpp @@ -7,11 +7,17 @@ #include "tmp/fanout_groups.hpp" #include "tmp/topo_sort.hpp" +#ifdef KPN_WEB_DEBUG +#include "web_debug.hpp" +#include +#endif + #include #include #include #include #include +#include namespace kpn { @@ -82,23 +88,41 @@ public: StaticNetwork(std::unique_ptr fanouts, std::vector user_nodes_topo, std::vector fanout_ptrs, - std::vector user_node_names) + std::vector user_node_names, + std::vector fanout_node_names, + std::vector> channel_probes) : fanouts_(std::move(fanouts)) , user_nodes_topo_(std::move(user_nodes_topo)) , fanout_nodes_ptr_(std::move(fanout_ptrs)) , user_node_names_(std::move(user_node_names)) + , fanout_node_names_(std::move(fanout_node_names)) + , channel_probes_(std::move(channel_probes)) {} ~StaticNetwork() override { stop(); } void start() override { stop_flag_ = false; + start_time_ = clock_t::now(); for (auto* n : user_nodes_topo_) n->start(); for (auto* n : fanout_nodes_ptr_) n->start(); +#ifdef KPN_WEB_DEBUG + web_server_ = std::make_unique( + web_debug_port_, + [this]() { + auto s = collect_snapshots(); + return web_debug::to_json(s.nodes, s.channels, s.elapsed_s); + }); + web_server_->start(); + std::cerr << "[kpn] web debug UI: http://localhost:" << web_debug_port_ << "\n"; +#endif } void stop() override { stop_flag_ = true; +#ifdef KPN_WEB_DEBUG + if (web_server_) web_server_->stop(); +#endif for (auto it = fanout_nodes_ptr_.rbegin(); it != fanout_nodes_ptr_.rend(); ++it) (*it)->stop(); for (auto it = user_nodes_topo_.rbegin(); it != user_nodes_topo_.rend(); ++it) @@ -113,6 +137,10 @@ public: return {n, 0, 0, 0, 0, 0, 0, 0}; } +#ifdef KPN_WEB_DEBUG + void set_web_debug_port(uint16_t port) { web_debug_port_ = port; } +#endif + // Print diagnostics using compile-time node labels void print_diagnostics(std::ostream& os = std::cerr) const { os << "\n┌─ KPN++ StaticNetwork diagnostics ─────────────────────────────\n"; @@ -128,12 +156,42 @@ public: FanoutStorage& fanouts_storage() { return *fanouts_; } private: - std::string name_; - bool stop_flag_{false}; - std::unique_ptr fanouts_; - std::vector user_nodes_topo_; - std::vector fanout_nodes_ptr_; - std::vector user_node_names_; // parallel to user_nodes_topo_ + struct Snapshots { + std::vector nodes; + std::vector channels; + double elapsed_s; + }; + + Snapshots collect_snapshots() const { + double elapsed_s = std::chrono::duration( + clock_t::now() - start_time_).count(); + + std::vector nodes; + for (std::size_t i = 0; i < user_nodes_topo_.size(); ++i) + nodes.push_back(user_nodes_topo_[i]->node_snapshot(user_node_names_[i], elapsed_s)); + for (std::size_t i = 0; i < fanout_nodes_ptr_.size(); ++i) + nodes.push_back(fanout_nodes_ptr_[i]->node_snapshot(fanout_node_names_[i], elapsed_s)); + + std::vector channels; + for (auto& probe : channel_probes_) + channels.push_back(probe->snapshot()); + + return {std::move(nodes), std::move(channels), elapsed_s}; + } + + std::string name_; + bool stop_flag_{false}; + std::unique_ptr fanouts_; + std::vector user_nodes_topo_; + std::vector fanout_nodes_ptr_; + std::vector user_node_names_; + std::vector fanout_node_names_; + std::vector> channel_probes_; + clock_t::time_point start_time_; +#ifdef KPN_WEB_DEBUG + uint16_t web_debug_port_{9090}; + std::unique_ptr web_server_; +#endif }; // ── make_network ────────────────────────────────────────────────────────────── @@ -203,27 +261,39 @@ auto make_network(Edges&&... edges) { return ptr; }; + std::vector> channel_probes; + auto wire_one = [&](SE) { using SrcNode = typename SE::src_node_t; using DstNode = typename SE::dst_node_t; + using out_t = std::tuple_element_t; constexpr std::size_t SrcIdx = SE::src_idx; constexpr std::size_t DstIdx = SE::dst_idx; auto* src = find_node.template operator()(); auto* dst = find_node.template operator()(); - if (src && dst) - src->template set_output_channel( - &dst->template input_channel()); + if (src && dst) { + auto& ch = dst->template input_channel(); + src->template set_output_channel(&ch); + std::string ch_name = node_display_name() + ":" + std::to_string(SrcIdx) + + " \xe2\x86\x92 " // UTF-8 → + + node_display_name() + ":" + std::to_string(DstIdx); + channel_probes.push_back(std::make_unique>(ch, ch_name)); + } }; [&](tmp::TypeList) { (wire_one(SEs{}), ...); }(ExpandedEdges{}); - // 6. Collect fanout node pointers - std::vector fanout_ptrs; + // 6. Collect fanout node pointers and names + std::vector fanout_ptrs; + std::vector fanout_node_names; std::apply([&](auto&... fn) { - (fanout_ptrs.push_back(static_cast(&fn)), ...); + ([&](auto& node) { + fanout_ptrs.push_back(static_cast(&node)); + fanout_node_names.push_back(node_display_name>()); + }(fn), ...); }, *fanout_storage); // 7. Construct and return the StaticNetwork @@ -231,7 +301,9 @@ auto make_network(Edges&&... edges) { return Net(std::move(fanout_storage), std::move(user_node_ptrs), std::move(fanout_ptrs), - std::move(user_node_names)); + std::move(user_node_names), + std::move(fanout_node_names), + std::move(channel_probes)); } } // namespace kpn