Files
marte-debug/ARCHITECTURE.md
T
2026-05-07 10:49:24 +02:00

13 KiB

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<MemoryMapInputBroker>
MemoryMapOutputBroker DebugBrokerWrapper<MemoryMapOutputBroker>
MemoryMapSynchronisedInputBroker DebugBrokerWrapper<MemoryMapSynchronisedInputBroker>
MemoryMapSynchronisedOutputBroker DebugBrokerWrapper<MemoryMapSynchronisedOutputBroker>
MemoryMapMultiBufferBroker DebugBrokerWrapper<MemoryMapMultiBufferBroker>
MemoryMapMultiBufferOutputBroker DebugBrokerWrapper<MemoryMapMultiBufferOutputBroker>
MemoryMapAsynchronousInputBroker DebugBrokerWrapperAsync<MemoryMapAsynchronousInputBroker>
MemoryMapAsynchronousOutputBroker DebugBrokerWrapperAsync<MemoryMapAsynchronousOutputBroker>
MemoryMapInterpolatedInputBroker DebugBrokerWrapper<MemoryMapInterpolatedInputBroker>
MemoryMapStatefulOutputBroker DebugBrokerWrapper<MemoryMapStatefulOutputBroker>
MemoryMapStatefulInputBroker DebugBrokerWrapper<MemoryMapStatefulInputBroker>

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

// 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)