Files
Martino Ferrari 617b5bd712 Initial release
2026-05-29 13:29:59 +02:00

242 lines
7.7 KiB
Markdown

# DebugService
`DebugService` is a MARTe2 `Object` that instruments a running MARTe2 application for
real-time signal tracing, forcing, and breakpoints — **without modifying the application
source code**.
It lives at `Source/Components/Interfaces/DebugService/` in this repository and is built
alongside `TCPLogger` at `Source/Components/Interfaces/TCPLogger/`.
---
## Architecture
### Core Mechanism: Registry Patching
On `Initialise()`, `PatchRegistry()` replaces the `ObjectBuilder` for every standard
`MemoryMap*Broker` class in the `ClassRegistryDatabase`. 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>` |
### DebugServiceI Abstraction
`DebugServiceI.h` defines an abstract singleton interface decoupling the broker injection
layer from the transport:
- **RT-path API** (called every RT cycle): `RegisterSignal`, `ProcessSignal`, `RegisterBroker`,
`IsPaused`/`SetPaused`, `ConsumeStepIfNeeded`
- **Control-path API**: `ForceSignal`/`UnforceSignal`, `TraceSignal`, `SetBreak`/`ClearBreak`,
`RegisterMonitorSignal`/`UnmonitorSignal`
### Signal Registration and Aliases
Each signal is registered with two names:
1. **Canonical**: `<DataSourcePath>.<SignalName>` (e.g. `App.Data.DDB.Counter`)
2. **GAM alias**: `<GAMPath>.In.<SignalName>` or `<GAMPath>.Out.<SignalName>`
Both map to the same `DebugSignalInfo*`. Alias lookup is bidirectional — a short unqualified
name matches any longer canonical or alias path.
### Communication Channels
| Port | Protocol | Purpose |
|------|----------|---------|
| 8080 (default) | TCP text | Command and control |
| 8081 (default) | UDP binary | High-speed telemetry stream |
| 8082 (default) | TCP stream | Log forwarding via `TcpLogger` |
### TraceRingBuffer
Single-producer/single-consumer circular byte buffer (4 MB default) 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).
`Pop()` is called exclusively by the Streamer thread.
---
## Configuration
Add `DebugService` as a **sibling** of `+App` (not inside it):
```text
+DebugService = {
Class = DebugService
ControlPort = 8080
UdpPort = 8081
LogPort = 8082
}
+Logger = {
Class = TcpLogger
Port = 8082
}
+App = {
Class = RealTimeApplication
...
}
```
Call `SetFullConfig(cdb)` after `ConfigureApplication()` to enable the `CONFIG` and `INFO`
commands.
---
## TCP Command Interface (port 8080)
Commands are newline-terminated (`\n`) UTF-8 text strings. One client is served at a time.
Rate limit: 100 commands/second. Idle timeout: 30 seconds.
### `DISCOVER`
List all registered signals.
```
Request: DISCOVER\n
Response: {"Signals":[{"id":<n>,"name":"…","alias":"…","type":"…","elements":<n>},...]}
OK DISCOVER
```
### `TREE`
Return the live `ObjectRegistryDatabase` hierarchy as JSON.
```
Request: TREE\n
Response: {"name":"Root","children":[…]}
OK TREE
```
### `INFO <path>`
Return metadata for a specific ORD node, enriched with config fields.
### `LS [path]`
List the immediate children of an ORD node.
### `TRACE <name> <0|1> [decimation]`
Enable or disable high-speed tracing. Optional decimation controls how many RT cycles are
skipped between samples (default 1 = every cycle).
```
Request: TRACE App.Data.DDB.Counter 1\n
Response: OK TRACE <match_count>\n
```
### `FORCE <name> <value>`
Inject a persistent value into a signal's memory location every RT cycle.
```
Request: FORCE App.Data.DDB.Counter 9999\n
Response: OK FORCE <match_count>\n
```
### `UNFORCE <name>`
Remove a forced value; the signal resumes normal data-flow.
### `BREAK <name> <op> <threshold>`
Set a conditional breakpoint. Supported operators: `>`, `<`, `==`, `>=`, `<=`, `!=`, `OFF`.
```
Request: BREAK App.Data.DDB.Counter > 1000\n
Response: OK BREAK <match_count>\n
```
### `PAUSE` / `RESUME`
Halt or release all patched RT threads.
### `STEP <n> [thread]`
Resume for exactly `n` RT output-broker cycles, then pause again.
### `VALUE <name>`
Read the current raw value of a signal. Arrays capped at 256 elements.
### `CONFIG`
Return the full application configuration as JSON (requires `SetFullConfig()`).
### `MSG <object> <function> [key=value …]`
Send a MARTe2 `Message` to any object in the ORD.
### `SERVICE_INFO`
Return metadata about the debug service (ports, transport type).
---
## UDP Telemetry Format (port 8081)
Packets are little-endian and packed.
### `TraceHeader` (20 bytes)
| Offset | Type | Field | Description |
|---|---|---|---|
| 0 | uint32 | `magic` | Always `0xDA7A57AD` |
| 4 | uint32 | `seq` | Monotonically incrementing sequence number |
| 8 | uint64 | `timestamp` | High-resolution hardware timestamp |
| 16 | uint32 | `count` | Number of samples in this datagram |
### Sample Entry (per signal)
| Offset | Type | Field | Description |
|---|---|---|---|
| 0 | uint32 | `id` | Signal ID from `DISCOVER` |
| 4 | uint64 | `timestamp` | Per-sample RT timestamp |
| 12 | uint32 | `size` | Payload size in bytes |
| 16 | bytes | `data` | Raw signal memory |
Multiple samples are packed into one datagram up to `STREAMER_MTU = 1400` bytes.
---
## Log Forwarding (TcpLogger, port 8082)
`TcpLogger` connects to the MARTe2 global `LoggerI` and forwards every `REPORT_ERROR` call:
```
<level>|<object>|<function>|<message>\n
```
Up to 8 simultaneous log clients supported.
---
## Performance Hardening
| Fix | Change |
|---|---|
| Multi-producer ring | `tracePushMutex` serialises `TraceRingBuffer::Push()` |
| Break evaluation | Break indices copied to stack-local array before evaluating outside the lock |
| Decimation counter | `Atomic::Add` for lock-free per-signal decimation in multi-producer `ProcessSignal` |
| Ring corruption | `Pop()` discards all entries only when `size >= bufferSize` |
| TCP rate limit | Disconnect clients sending > 100 commands/second |
| Idle timeout | Disconnect active TCP client after 30 s of silence |
| TCP framing | `inputBuffer` accumulates partial commands across `Read()` calls; bounded at 8 KiB |
| VALUE output size | Caps output at 256 elements; includes `"Truncated"` flag |
---
## Key Source Files
| File | Purpose |
|---|---|
| `DebugCore.h` | `DebugSignalInfo` struct, `TraceRingBuffer`, `BreakOp` enum |
| `DebugServiceI.h` | Abstract singleton interface, `SignalAlias`, `BrokerInfo` |
| `DebugBrokerWrapper.h` | `DebugBrokerHelper`, wrapper templates, `DebugBrokerBuilder` |
| `DebugService.h/.cpp` | TCP/UDP transport implementation; `Server()` and `Streamer()` threads |
| `DebugServiceBase.h/.cpp` | Shared signal registry and alias matching logic |
| `TcpLogger.h/.cpp` | `LoggerConsumerI` forwarding `REPORT_ERROR` events to TCP clients |