Initial release
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user