# System Architecture ## 1. Overview The MARTe2 Debug Suite is a C++ server library that instruments running MARTe2 applications without modifying their source code. It exposes an abstract `DebugServiceI` interface that decouples the broker injection layer from the communication transport, allowing multiple transport backends to coexist (currently TCP/UDP and HTTP/SSE). ``` ┌────────────────────────────────────────────────────────────────────┐ │ MARTe2 Application │ │ ┌──────────┐ ┌───────────────────────┐ ┌──────────────────┐ │ │ │ GAM(s) │ │ DebugBrokerWrapper │ │ FastScheduler │ │ │ │ │◄──│ (Registry-patched) │ │ (unmodified) │ │ │ └──────────┘ └──────────┬────────────┘ └──────────────────┘ │ │ │ RT-path API │ └────────────────────────────┼───────────────────────────────────────┘ │ ┌────────▼────────┐ │ DebugServiceI │ ← Abstract interface (singleton) └────────┬────────┘ ┌─────────────┴──────────────┐ │ │ ┌────────▼────────┐ ┌─────────▼────────┐ │ DebugService │ │ WebDebugService │ │ TCP/UDP │ │ HTTP/SSE │ └────────┬────────┘ └─────────┬─────────┘ │ │ ┌──────────┼──────────┐ ┌──────────┼──────────┐ │TCP 8080 │UDP 8081 │ │HTTP 8090 │SSE stream │ │(commands)│(telemetry│ │(commands)│(telemetry)│ └────┬─────┴────┬─────┘ └─────┬────┴────┬──────┘ │ │ │ │ ┌────▼──────────▼─────┐ ┌─────────▼─────────▼──────┐ │ Rust/egui GUI │ │ Web Browser (built-in UI)│ │ (Tools/gui_client) │ │ http://localhost:8090 │ └─────────────────────┘ └───────────────────────────┘ ``` --- ## 2. Core Mechanism: Registry Patching The "Zero-Code-Change" requirement is met by intercepting the MARTe2 `ClassRegistryDatabase`. When `DebugService` or `WebDebugService` initialises, `PatchRegistry()` replaces the `ObjectBuilder` for every standard `MemoryMap*Broker` class in the registry. When `RealTimeApplication::ConfigureApplication()` runs afterward it instantiates wrapped broker objects instead of the originals — the application sees no difference. Patched broker types: | Class | Wrapper | |---|---| | `MemoryMapInputBroker` | `DebugBrokerWrapper` | | `MemoryMapOutputBroker` | `DebugBrokerWrapper` | | `MemoryMapSynchronisedInputBroker` | `DebugBrokerWrapper` | | `MemoryMapSynchronisedOutputBroker` | `DebugBrokerWrapper` | | `MemoryMapMultiBufferBroker` | `DebugBrokerWrapper` | | `MemoryMapMultiBufferOutputBroker` | `DebugBrokerWrapper` | | `MemoryMapAsynchronousInputBroker` | `DebugBrokerWrapperAsync` | | `MemoryMapAsynchronousOutputBroker` | `DebugBrokerWrapperAsync` | | `MemoryMapInterpolatedInputBroker` | `DebugBrokerWrapper` | | `MemoryMapStatefulOutputBroker` | `DebugBrokerWrapper` | | `MemoryMapStatefulInputBroker` | `DebugBrokerWrapper` | --- ## 3. DebugServiceI Abstraction Layer `Source/Components/Interfaces/DebugService/DebugServiceI.h` defines the abstract singleton interface. It has two logical groups: ### 3.1 RT-path API Called from broker threads on every RT cycle. Must be as cheap as possible; the only permissible synchronisation primitive is `FastPollingMutexSem`. | Method | Purpose | |---|---| | `RegisterSignal(addr, type, name, …)` | Called once per signal during broker `Init()` | | `ProcessSignal(info, size, timestamp)` | Apply forcing, check break, push trace sample | | `RegisterBroker(…)` | Cache broker metadata for active/break index updates | | `IsPaused() / SetPaused(bool)` | Query / set global pause state | | `ConsumeStepIfNeeded(gamName, thread)` | Decrement step counter on output-broker Execute | ### 3.2 Control-path API Called from server/command-handler threads; latency acceptable. | Method | Purpose | |---|---| | `ForceSignal / UnforceSignal` | Persistent value injection | | `TraceSignal(name, enable, decimation)` | Enable/disable ring-buffer tracing | | `SetBreak / ClearBreak` | Conditional breakpoints | | `IsInstrumented(path, …)` | Query whether a signal is reachable | | `RegisterMonitorSignal / UnmonitorSignal` | Slow-rate signal polling | ### 3.3 Static singleton pattern ```cpp // Concrete implementation registers itself: DebugServiceI::SetInstance(this); // in Initialise() DebugServiceI::SetInstance(NULL_PTR(…)); // in destructor // Broker wrappers retrieve it (no ORD search on the hot path): DebugServiceI *svc = DebugServiceI::GetInstance(); if (svc != NULL_PTR(DebugServiceI*)) { ... } ``` Shared data structures (`SignalAlias`, `BrokerInfo`) are also defined in `DebugServiceI.h` so both transport implementations can use them without a circular dependency. --- ## 4. Transport Implementations ### 4.1 DebugService (TCP/UDP) `Source/Components/Interfaces/DebugService/DebugService.h/.cpp` Runs two `SingleThreadService` threads: | Thread | Role | |---|---| | `Server()` | Accepts one TCP client at a time; reads text commands; writes JSON/text responses | | `Streamer()` | Drains `TraceRingBuffer`; assembles and sends UDP datagrams; polls monitored signals | Communication ports (all configurable): | Port | Protocol | Purpose | |---|---|---| | `ControlPort` (default 8080) | TCP text | Commands and responses | | `UdpPort` (default 8081) | UDP binary | High-speed telemetry (`TraceHeader` + samples) | | `LogPort` (default 8082) | TCP stream | Log forwarding via `TcpLogger` sidecar | ### 4.2 WebDebugService (HTTP/SSE) `Source/Components/Interfaces/WebDebugService/WebDebugService.h/.cpp` Serves a single HTTP port (default 8090) with an embedded browser UI. Runs two `SingleThreadService` threads: | Thread | Role | |---|---| | `HttpServer()` | Accepts TCP connections; routes GET/POST/OPTIONS; upgrades SSE clients | | `Streamer()` | Drains `TraceRingBuffer` → SSE events; polls monitored signals; heartbeat | HTTP endpoints: | Endpoint | Method | Purpose | |---|---|---| | `/` | GET | Serves embedded single-page application (HTML/CSS/JS) | | `/api/command` | POST | Execute a debug command; returns text response | | `/api/events` | GET | Server-Sent Events stream for real-time telemetry (up to 8 clients) | The embedded web UI (`Source/Components/Interfaces/WebDebugService/WebUI.h`) is a self-contained single-page application using Chart.js for real-time signal plots and a Catppuccin Mocha dark theme. No install or server-side tooling required. --- ## 5. Signal Flow ### 5.1 Registration (startup, single-threaded) ``` ConfigureApplication() └─► DebugBrokerWrapper::Init() └─► DebugBrokerHelper::InitSignals() ├─► DebugServiceI::RegisterSignal() (canonical name + GAM alias) └─► DebugServiceI::RegisterBroker() ``` ### 5.2 RT loop (hot path, multi-threaded) ``` RealTimeThread::Execute() └─► DebugBrokerWrapper::Execute() ├─► Base::Execute() (data moved in/out of GAM memory) └─► DebugBrokerHelper::Process() ├─► For each active signal: │ └─► DebugServiceI::ProcessSignal() │ ├─► if isForcing: memcpy forcedValue → signal memory │ ├─► if isTracing & decimation fires: │ │ └─► tracePushMutex → TraceRingBuffer::Push() │ └─► if breakOp set: evaluate condition → SetPaused(true) └─► (output brokers only) DebugServiceI::ConsumeStepIfNeeded() ``` ### 5.3 Telemetry delivery **DebugService (UDP)** ``` Streamer thread └─► TraceRingBuffer::Pop() └─► assemble into streamerPacketBuffer (MTU=1400) └─► BasicUDPSocket::Write() → client ``` **WebDebugService (SSE)** ``` Streamer thread └─► TraceRingBuffer::Pop() └─► BroadcastSse("message", {"type":"trace","name":…,"value":…}) └─► write "event: message\ndata: {…}\n\n" to each SSE socket ``` --- ## 6. TraceRingBuffer Single-producer / single-consumer circular byte buffer defined in `DebugCore.h`. ``` Buffer layout per entry: [ID:4][Timestamp:8][Size:4][Data:N] ``` - `Push()` is serialised by `tracePushMutex` (multiple RT threads write; one Streamer reads). - `Pop()` is called exclusively by the Streamer thread. - Ring is initialised to 4 MB in both transport implementations. - On corrupt `size` field (`size >= bufferSize`): discards all entries to prevent cascading bad reads (Fix #5). --- ## 7. Performance Hardening The following fixes were applied after initial implementation to improve correctness and real-time safety. | Fix | Area | Change | |---|---|---| | **#1** | UDP streamer | Named constants `STREAMER_MTU = 1400` and `STREAMER_BUFFER_SIZE = 4096`; removed magic numbers | | **#2** | Multi-producer ring | `tracePushMutex` serialises `TraceRingBuffer::Push()` from concurrent RT threads | | **#3** | Break evaluation | Break indices copied to stack-local array while holding `activeMutex`; condition evaluated outside the lock | | **#4** | Decimation counter | `Atomic::Add` used for lock-free per-signal decimation in multi-producer `ProcessSignal` | | **#5** | Ring corruption | `Pop()` skips only the current corrupt entry; falls back to full discard only when `size >= bufferSize` | | **#6** | TCP rate limit | Server disconnects clients sending > 100 commands/second (`CMD_RATE_LIMIT = 100`) | | **#7** | Broker index swap | `Vec::Swap` inside spinlock followed by free outside; avoids holding the lock during deallocation | | **#8** | Idle timeout | Active TCP client disconnected after 30 s of silence (`CLIENT_IDLE_TIMEOUT_MS = 30000`) | | **#9** | VALUE output size | `GetSignalValue` caps output at 256 elements (`GET_VALUE_MAX_ELEMENTS`); includes `"Truncated"` flag | | **#10** | TCP framing | `inputBuffer` accumulates partial commands across `Read()` calls; bounded at 8 KiB (`INPUT_BUFFER_MAX`) | | **#11** | CDB cursor | `Initialise()` never calls `MoveToRoot()` on the CDB passed by the framework; avoids corrupting `ReferenceContainer` traversal | --- ## 8. Repository Layout ``` Source/ Core/Types/ Vec/ — RT-safe dynamic array (no STL) Result/ — Error-carrying return type Components/Interfaces/ DebugService/ DebugCore.h — DebugSignalInfo, TraceRingBuffer, BreakOp DebugServiceI.h — Abstract interface + SignalAlias, BrokerInfo DebugBrokerWrapper.h — DebugBrokerHelper, wrapper templates, registry builder DebugService.h/.cpp — TCP/UDP transport implementation WebDebugService/ WebUI.h — Embedded single-page HTML/CSS/JS application WebDebugService.h/.cpp — HTTP/SSE transport implementation TCPLogger/ TcpLogger.h/.cpp — LoggerConsumerI forwarding logs to TCP clients Test/ Integration/ — Integration test suite (180 s SIGALRM guard) UnitTests/ — Unit test suite Tools/ gui_client/ — Rust/egui native GUI (DebugService TCP/UDP transport) ```