From 955eb02924e3f651b38cf93bd44ab5a3af7b8cb4 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Sat, 21 Feb 2026 20:20:08 +0100 Subject: [PATCH] Updated with scheduler --- API.md | 61 +++++++++ ARCHITECTURE.md | 41 ++++++ CMakeLists.txt | 1 + DEMO.md | 32 +++++ Headers/DebugBrokerWrapper.h | 4 +- Headers/DebugFastScheduler.h | 33 +++++ Headers/DebugService.h | 7 +- README.md | 75 +++++------ SPECS.md | 24 ++++ Source/DebugFastScheduler.cpp | 153 +++++++++++++++++++++ Source/DebugService.cpp | 24 +++- Test/Integration/CMakeLists.txt | 3 + Test/Integration/SchedulerTest.cpp | 202 ++++++++++++++++++++++++++++ Test/Integration/ValidationTest.cpp | 83 +++++++----- run_test.sh | 23 ++-- 15 files changed, 664 insertions(+), 102 deletions(-) create mode 100644 API.md create mode 100644 ARCHITECTURE.md create mode 100644 DEMO.md create mode 100644 Headers/DebugFastScheduler.h create mode 100644 SPECS.md create mode 100644 Source/DebugFastScheduler.cpp create mode 100644 Test/Integration/SchedulerTest.cpp diff --git a/API.md b/API.md new file mode 100644 index 0000000..f0e5533 --- /dev/null +++ b/API.md @@ -0,0 +1,61 @@ +# API Documentation + +## 1. TCP Control Interface (Port 8080) + +### 1.1 `TREE` +Retrieves the full object hierarchy. +- **Request:** `TREE +` +- **Response:** `JSON_OBJECT +OK TREE +` + +### 1.2 `DISCOVER` +Lists all registrable signals and their metadata. +- **Request:** `DISCOVER +` +- **Response:** `{"Signals": [...]} +OK DISCOVER +` + +### 1.3 `TRACE ` +Enables/disables telemetry for a signal. +- **Example:** `TRACE App.Data.Timer.Counter 1 +` +- **Response:** `OK TRACE +` + +### 1.4 `FORCE ` +Overrides a signal value in memory. +- **Example:** `FORCE App.Data.DDB.Signal 123.4 +` +- **Response:** `OK FORCE +` + +### 1.5 `PAUSE` / `RESUME` +Controls global execution state via the Scheduler. +- **Request:** `PAUSE +` +- **Response:** `OK +` + +--- + +## 2. UDP Telemetry Format (Port 8081) + +Telemetry packets are Little-Endian and use `#pragma pack(1)`. + +### 2.1 TraceHeader (20 Bytes) +| Offset | Type | Name | Description | +| :--- | :--- | :--- | :--- | +| 0 | uint32 | magic | Always `0xDA7A57AD` | +| 4 | uint32 | seq | Incremental sequence number | +| 8 | uint64 | timestamp | High-resolution timestamp | +| 16 | uint32 | count | Number of samples in payload | + +### 2.2 Sample Entry +| Offset | Type | Name | Description | +| :--- | :--- | :--- | :--- | +| 0 | uint32 | id | Internal Signal ID (from `DISCOVER`) | +| 4 | uint32 | size | Data size in bytes | +| 8 | Bytes | data | Raw signal memory | diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..08936b4 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,41 @@ +# System Architecture + +## 1. Overview +The suite consists of a C++ server library (`libmarte_dev.so`) that instrument MARTe2 applications at runtime, and a native Rust client for visualization. + +## 2. Core Mechanism: Registry Patching +The "Zero-Code-Change" requirement is met by intercepting the MARTe2 `ClassRegistryDatabase`. When the `DebugService` initializes, it replaces the standard object builders for critical classes: + +1. **Broker Injection:** Standard Brokers (e.g., `MemoryMapInputBroker`) are replaced with `DebugBrokerWrapper`. This captures data every time a GAM reads or writes a signal. +2. **Scheduler Injection:** The `FastScheduler` is replaced with `DebugFastScheduler`. This provides hooks at the start/end of each real-time cycle for execution control (pausing). + +## 3. Communication Layer +The system uses three distinct channels: + +### 3.1 Command & Control (TCP Port 8080) +- **Protocol:** Text-based over TCP. +- **Role:** Object tree discovery (`TREE`), signal metadata (`DISCOVER`), and trace activation (`TRACE`). +- **Response Format:** JSON for complex data, `OK/ERROR` for status. + +### 3.2 High-Speed Telemetry (UDP Port 8081) +- **Protocol:** Binary over UDP. +- **Format:** Packed C-structs. +- **Header:** `[Magic:4][Seq:4][TS:8][Count:4]` (Packed, 20 bytes). +- **Payload:** `[ID:4][Size:4][Data:N]` (Repeated for each traced signal). + +### 3.3 Log Streaming (TCP Port 8082) +- **Protocol:** Real-time event streaming. +- **Role:** Forwards global `REPORT_ERROR` calls from the framework to the GUI client. + +## 4. Component Diagram +```text +[ MARTe2 App ] <--- [ DebugFastScheduler ] (Registry Patch) + | | + + <--- [ DebugBrokerWrapper ] (Registry Patch) + | | +[ DebugService ] <----------+ + | + +---- (TCP 8080) ----> [ Rust GUI Client ] + +---- (UDP 8081) ----> [ (Oscilloscope) ] + +---- (TCP 8082) ----> [ (Log Terminal) ] +``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 75a34dd..e342380 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ include_directories( ${MARTe2_DIR}/Source/Core/Scheduler/L4LoggerService ${MARTe2_DIR}/Source/Core/FileSystem/L1Portability ${MARTe2_DIR}/Source/Core/FileSystem/L3Streams + ${MARTe2_DIR}/Source/Core/Scheduler/L5GAMs ${MARTe2_Components_DIR}/Source/Components/DataSources/EpicsDataSource ${MARTe2_Components_DIR}/Source/Components/DataSources/FileDataSource ${MARTe2_Components_DIR}/Source/Components/GAMs/IOGAM diff --git a/DEMO.md b/DEMO.md new file mode 100644 index 0000000..aab81f7 --- /dev/null +++ b/DEMO.md @@ -0,0 +1,32 @@ +# Demo Walkthrough: High-Speed Tracing + +This demo demonstrates tracing a `Timer.Counter` signal at 100Hz and verifying its consistency. + +### 1. Launch the Test Environment +Start the validation environment which simulates a real-time app: +```bash +./Build/Test/Integration/ValidationTest +``` +*Note: The test will wait for a trace command before finishing.* + +### 2. Connect the GUI +In another terminal: +```bash +cd Tools/gui_client +cargo run --release +``` + +### 3. Explore the Tree +1. On the left panel (**Application Tree**), expand `Root.App.Data.Timer`. +2. Click the `ℹ Info` button on the `Timer` node to see its configuration (e.g., `SleepTime: 10000`). + +### 4. Activate Trace +1. Locate the `Counter` signal under the `Timer` node. +2. Click the **📈 Trace** button. +3. The **Oscilloscope** in the center will immediately begin plotting the incremental counter. +4. Verify the **UDP Packets** counter in the top bar is increasing rapidly. + +### 5. Test Execution Control +1. Click the **⏸ Pause** button in the top bar. +2. Observe that the plot stops updating and the counter value holds steady. +3. Click **▶ Resume** to continue execution. diff --git a/Headers/DebugBrokerWrapper.h b/Headers/DebugBrokerWrapper.h index ed913cd..2d0b375 100644 --- a/Headers/DebugBrokerWrapper.h +++ b/Headers/DebugBrokerWrapper.h @@ -72,7 +72,7 @@ public: StreamString signalName; if (!dataSourceIn.GetSignalName(dsIdx, signalName)) signalName = "Unknown"; - // 1. Register canonical DataSource name (Absolute) + // 1. Register canonical DataSource name (Absolute, No Root prefix) StreamString dsFullName; dsFullName.Printf("%s.%s", dsPath.Buffer(), signalName.Buffer()); service->RegisterSignal(addr, type, dsFullName.Buffer()); @@ -87,7 +87,7 @@ public: DebugService::GetFullObjectName(*(gamRef.operator->()), absGamPath); gamFullName.Printf("%s.%s.%s", absGamPath.Buffer(), dirStr, signalName.Buffer()); } else { - gamFullName.Printf("Root.%s.%s.%s", functionName, dirStr, signalName.Buffer()); + gamFullName.Printf("%s.%s.%s", functionName, dirStr, signalName.Buffer()); } signalInfoPointers[i] = service->RegisterSignal(addr, type, gamFullName.Buffer()); } else { diff --git a/Headers/DebugFastScheduler.h b/Headers/DebugFastScheduler.h new file mode 100644 index 0000000..c7533ba --- /dev/null +++ b/Headers/DebugFastScheduler.h @@ -0,0 +1,33 @@ +#ifndef DEBUGFASTSCHEDULER_H +#define DEBUGFASTSCHEDULER_H + +#include "FastScheduler.h" +#include "DebugService.h" +#include "ObjectRegistryDatabase.h" + +namespace MARTe { + +class DebugFastScheduler : public FastScheduler { +public: + CLASS_REGISTER_DECLARATION() + + DebugFastScheduler(); + virtual ~DebugFastScheduler(); + + virtual bool Initialise(StructuredDataI & data); + + ErrorManagement::ErrorType Execute(ExecutionInfo &information); + +protected: + virtual void CustomPrepareNextState(); + +private: + ErrorManagement::ErrorType DebugSetupThreadMap(); + + EmbeddedServiceMethodBinderT debugBinder; + DebugService *debugService; +}; + +} + +#endif diff --git a/Headers/DebugService.h b/Headers/DebugService.h index 04bde20..7d66c94 100644 --- a/Headers/DebugService.h +++ b/Headers/DebugService.h @@ -55,14 +55,17 @@ public: static bool GetFullObjectName(const Object &obj, StreamString &fullPath); -private: - void HandleCommand(StreamString cmd, BasicTCPSocket *client); + // Made public for integration tests and debug access uint32 ForceSignal(const char8* name, const char8* valueStr); uint32 UnforceSignal(const char8* name); uint32 TraceSignal(const char8* name, bool enable, uint32 decimation = 1); void Discover(BasicTCPSocket *client); void ListNodes(const char8* path, BasicTCPSocket *client); void InfoNode(const char8* path, BasicTCPSocket *client); + +private: + void HandleCommand(StreamString cmd, BasicTCPSocket *client); + uint32 ExportTree(ReferenceContainer *container, StreamString &json); void PatchRegistry(); diff --git a/README.md b/README.md index 1e38c79..9b009c3 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,43 @@ -# MARTe2 Universal Debugging & Observability Suite +# MARTe2 Debug Suite -A professional-grade, zero-code-change debugging suite for the MARTe2 real-time framework. - -## Features - -- **Runtime Registry Patching**: Instruments all MARTe2 Brokers automatically at startup. -- **Hierarchical Tree Explorer**: Recursive visualization of the `ObjectRegistryDatabase`, including GAMs, DataSources, and Signals. -- **Real-Time Execution Control**: Pause and Resume application logic globally to perform static inspection. -- **High-Speed Telemetry**: Visual oscilloscope with sub-millisecond precision via UDP. -- **Persistent Forcing**: Type-aware signal overrides (Last-Writer-Wins) with persistent re-application. -- **Isolated Log Streaming**: Dedicated TCP channel for real-time framework logs to ensure command responsiveness. - -## Components - -### 1. C++ Core (`libmarte_dev.so`) -The core service that handles registry patching, TCP/UDP communication, and real-time safe data capture. - -### 2. Rust GUI Client (`marte_debug_gui`) -A native, multi-threaded dashboard built with `egui`. -- **Side Panel**: Collapsible application tree and signal navigator. -- **Bottom Panel**: Advanced log terminal with Regex filtering and priority levels. -- **Right Panel**: Active Trace and Force management. -- **Central Pane**: High-frequency oscilloscope. +An interactive observability and debugging suite for the MARTe2 real-time framework. ## Quick Start -### Build +### 1. Build the project ```bash -# Build C++ Core -cd Build && cmake .. && make -j$(nproc) - -# Build GUI Client -cd Tools/gui_client -cargo build --release +. ./env.sh +cd Build +cmake .. +make -j$(nproc) ``` -### Run -1. Start your MARTe2 application with the `DebugService` enabled. -2. Launch the GUI: - ```bash - ./Tools/gui_client/target/release/marte_debug_gui - ``` +### 2. Run Integration Tests +```bash +./Test/Integration/ValidationTest # Verifies 100Hz tracing +./Test/Integration/SchedulerTest # Verifies execution control +``` -## Communication Ports -- **8080 (TCP)**: Commands (TREE, FORCE, TRACE, PAUSE). -- **8082 (TCP)**: Real-time framework logs. -- **8081 (UDP)**: Signal telemetry data. +### 3. Launch GUI +```bash +cd Tools/gui_client +cargo run --release +``` + +## Features +- **Live Tree:** Explore the MARTe2 object database in real-time. +- **Oscilloscope:** Trace any signal at high frequency (100Hz+) with automatic scaling. +- **Signal Forcing:** Inject values directly into the real-time memory map. +- **Log Forwarding:** Integrated framework log viewer with regex filtering. +- **Execution Control:** Global pause/resume via scheduler-level hooks. + +## Usage +To enable debugging in your application, add the following to your `.cfg`: +```text ++DebugService = { + Class = DebugService + ControlPort = 8080 + UdpPort = 8081 +} +``` +The suite automatically patches the registry to instrument your existing Brokers and Schedulers. diff --git a/SPECS.md b/SPECS.md new file mode 100644 index 0000000..25f5708 --- /dev/null +++ b/SPECS.md @@ -0,0 +1,24 @@ +# MARTe2 Debug Suite Specifications + +## 1. Goal +Implement a "Zero-Code-Change" observability layer for the MARTe2 real-time framework, providing live telemetry, signal forcing, and execution control without modifying existing application source code. + +## 2. Requirements +### 2.1 Functional Requirements (FR) +- **FR-01 (Discovery):** Discover the full MARTe2 object hierarchy at runtime. +- **FR-02 (Telemetry):** Stream high-frequency signal data (verified up to 100Hz) to a remote client. +- **FR-03 (Forcing):** Allow manual override of signal values in memory during execution. +- **FR-04 (Logs):** Stream global framework logs to a dedicated terminal. +- **FR-05 (Execution Control):** Pause and resume the real-time execution threads via scheduler injection. +- **FR-06 (UI):** Provide a native, immediate-mode GUI for visualization (Oscilloscope). + +### 2.2 Technical Constraints (TC) +- **TC-01:** No modifications allowed to the MARTe2 core library or component source code. +- **TC-02:** Instrumentation must use Runtime Class Registry Patching. +- **TC-03:** Real-time threads must remain lock-free; use `FastPollingMutexSem` or atomic operations for synchronization. +- **TC-04:** Telemetry must be delivered via UDP to minimize impact on real-time jitter. + +## 3. Performance Metrics +- **Latency:** Telemetry dispatch overhead < 5 microseconds per signal. +- **Throughput:** Support for 100Hz+ sampling rates with zero packet loss on local networks. +- **Scalability:** Handle up to 4096 unique signals and 16 simultaneous client connections. diff --git a/Source/DebugFastScheduler.cpp b/Source/DebugFastScheduler.cpp new file mode 100644 index 0000000..b0b452a --- /dev/null +++ b/Source/DebugFastScheduler.cpp @@ -0,0 +1,153 @@ +#include "DebugFastScheduler.h" +#include "AdvancedErrorManagement.h" +#include "ExecutionInfo.h" +#include "MultiThreadService.h" +#include "RealTimeApplication.h" +#include "Threads.h" +#include "MemoryOperationsHelper.h" + +namespace MARTe { + +const uint64 ALL_CPUS = 0xFFFFFFFFFFFFFFFFull; + +DebugFastScheduler::DebugFastScheduler() : + FastScheduler(), + debugBinder(*this, &DebugFastScheduler::Execute) +{ + debugService = NULL_PTR(DebugService*); +} + +DebugFastScheduler::~DebugFastScheduler() { +} + +bool DebugFastScheduler::Initialise(StructuredDataI & data) { + bool ret = FastScheduler::Initialise(data); + if (ret) { + ReferenceContainer *root = ObjectRegistryDatabase::Instance(); + Reference serviceRef = root->Find("DebugService"); + if (serviceRef.IsValid()) { + debugService = dynamic_cast(serviceRef.operator->()); + } + } + return ret; +} + +ErrorManagement::ErrorType DebugFastScheduler::DebugSetupThreadMap() { + ErrorManagement::ErrorType err; + ComputeMaxNThreads(); + REPORT_ERROR(ErrorManagement::Information, "DebugFastScheduler: Max Threads=%!", maxNThreads); + + multiThreadService = new (NULL) MultiThreadService(debugBinder); + multiThreadService->SetNumberOfPoolThreads(maxNThreads); + err = multiThreadService->CreateThreads(); + if (err.ErrorsCleared()) { + rtThreadInfo[0] = new RTThreadParam[maxNThreads]; + rtThreadInfo[1] = new RTThreadParam[maxNThreads]; + + for (uint32 i = 0u; i < numberOfStates; i++) { + cpuMap[i] = new uint64[maxNThreads]; + for (uint32 j = 0u; j < maxNThreads; j++) { + cpuMap[i][j] = ALL_CPUS; + } + } + + if (countingSem.Create(maxNThreads)) { + for (uint32 i = 0u; i < numberOfStates; i++) { + uint32 nThreads = states[i].numberOfThreads; + cpuThreadMap[i] = new uint32[nThreads]; + for (uint32 j = 0u; j < nThreads; j++) { + uint64 cpu = static_cast(states[i].threads[j].cpu.GetProcessorMask()); + CreateThreadMap(cpu, i, j); + } + } + } + } + return err; +} + +void DebugFastScheduler::CustomPrepareNextState() { + ErrorManagement::ErrorType err; + + err = !realTimeApplicationT.IsValid(); + if (err.ErrorsCleared()) { + uint8 nextBuffer = static_cast(realTimeApplicationT->GetIndex()); + nextBuffer++; + nextBuffer &= 0x1u; + + if (!initialised) { + cpuMap = new uint64*[numberOfStates]; + cpuThreadMap = new uint32*[numberOfStates]; + err = DebugSetupThreadMap(); + } + if (err.ErrorsCleared()) { + for (uint32 j = 0u; j < maxNThreads; j++) { + rtThreadInfo[nextBuffer][j].executables = NULL_PTR(ExecutableI **); + rtThreadInfo[nextBuffer][j].numberOfExecutables = 0u; + rtThreadInfo[nextBuffer][j].cycleTime = NULL_PTR(uint32 *); + rtThreadInfo[nextBuffer][j].lastCycleTimeStamp = 0u; + } + + ScheduledState *nextState = GetSchedulableStates()[nextBuffer]; + uint32 numberOfThreads = nextState->numberOfThreads; + for (uint32 i = 0u; i < numberOfThreads; i++) { + rtThreadInfo[nextBuffer][cpuThreadMap[nextStateIdentifier][i]].executables = nextState->threads[i].executables; + rtThreadInfo[nextBuffer][cpuThreadMap[nextStateIdentifier][i]].numberOfExecutables = nextState->threads[i].numberOfExecutables; + rtThreadInfo[nextBuffer][cpuThreadMap[nextStateIdentifier][i]].cycleTime = nextState->threads[i].cycleTime; + rtThreadInfo[nextBuffer][cpuThreadMap[nextStateIdentifier][i]].lastCycleTimeStamp = 0u; + } + } + } +} + +ErrorManagement::ErrorType DebugFastScheduler::Execute(ExecutionInfo & information) { + ErrorManagement::ErrorType ret; + + if (information.GetStage() == MARTe::ExecutionInfo::StartupStage) { + } + else if (information.GetStage() == MARTe::ExecutionInfo::MainStage) { + uint32 threadNumber = information.GetThreadNumber(); + (void) eventSem.Wait(TTInfiniteWait); + if (superFast == 0u) { + (void) countingSem.WaitForAll(TTInfiniteWait); + } + + uint32 idx = static_cast(realTimeApplicationT->GetIndex()); + + if (rtThreadInfo[idx] != NULL_PTR(RTThreadParam *)) { + if (rtThreadInfo[idx][threadNumber].numberOfExecutables > 0u) { + + // EXECUTION CONTROL HOOK + if (debugService != NULL_PTR(DebugService*)) { + while (debugService->IsPaused()) { + Sleep::MSec(1); + } + } + + bool ok = ExecuteSingleCycle(rtThreadInfo[idx][threadNumber].executables, rtThreadInfo[idx][threadNumber].numberOfExecutables); + if (!ok) { + if (errorMessage.IsValid()) { + (void)MessageI::SendMessage(errorMessage, this); + } + } + + uint32 absTime = 0u; + if (rtThreadInfo[idx][threadNumber].lastCycleTimeStamp != 0u) { + uint64 tmp = (HighResolutionTimer::Counter() - rtThreadInfo[idx][threadNumber].lastCycleTimeStamp); + float64 ticksToTime = (static_cast(tmp) * clockPeriod) * 1e6; + absTime = static_cast(ticksToTime); + } + uint32 sizeToCopy = static_cast(sizeof(uint32)); + (void)MemoryOperationsHelper::Copy(rtThreadInfo[idx][threadNumber].cycleTime, &absTime, sizeToCopy); + rtThreadInfo[idx][threadNumber].lastCycleTimeStamp = HighResolutionTimer::Counter(); + } + else { + (void) unusedThreadsSem.Wait(TTInfiniteWait); + } + } + } + return ret; +} + +CLASS_REGISTER(DebugFastScheduler, "1.0") + +} diff --git a/Source/DebugService.cpp b/Source/DebugService.cpp index 272262c..ec1b2b6 100644 --- a/Source/DebugService.cpp +++ b/Source/DebugService.cpp @@ -3,6 +3,7 @@ #include "StreamString.h" #include "BasicSocket.h" #include "DebugBrokerWrapper.h" +#include "DebugFastScheduler.h" #include "ObjectRegistryDatabase.h" #include "ClassRegistryItem.h" #include "ObjectBuilder.h" @@ -119,7 +120,12 @@ bool DebugService::Initialise(StructuredDataI & data) { (void)data.Read("TcpLogPort", logPort); } - (void)data.Read("StreamIP", streamIP); + StreamString tempIP; + if (data.Read("StreamIP", tempIP)) { + streamIP = tempIP; + } else { + streamIP = "127.0.0.1"; + } uint32 suppress = 1; if (data.Read("SuppressTimeoutLogs", suppress)) { @@ -148,14 +154,21 @@ bool DebugService::Initialise(StructuredDataI & data) { REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to open TCP Server Socket"); return false; } + if (!tcpServer.Listen(controlPort)) { + REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to Listen on port %u", controlPort); + return false; + } + if (!udpSocket.Open()) { REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to open UDP Socket"); return false; } + if (!logServer.Open()) { REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to open Log Server Socket"); return false; } + (void)logServer.Listen(logPort); if (threadService.Start() != ErrorManagement::NoError) { REPORT_ERROR(ErrorManagement::FatalError, "DebugService: Failed to start Server thread"); @@ -201,6 +214,10 @@ void DebugService::PatchRegistry() { PatchItemInternal("MemoryMapSynchronisedMultiBufferInputBroker", &b8); static DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder b9; PatchItemInternal("MemoryMapSynchronisedMultiBufferOutputBroker", &b9); + + // Patch Scheduler + static ObjectBuilderT schedBuilder; + PatchItemInternal("FastScheduler", &schedBuilder); } void DebugService::ProcessSignal(DebugSignalInfo* s, uint32 size) { @@ -295,9 +312,6 @@ static bool RecursiveGetFullObjectName(ReferenceContainer *container, const Obje bool DebugService::GetFullObjectName(const Object &obj, StreamString &fullPath) { fullPath = ""; if (RecursiveGetFullObjectName(ObjectRegistryDatabase::Instance(), obj, fullPath)) { - StreamString abs = "Root."; - abs += fullPath; - fullPath = abs; return true; } return false; @@ -311,7 +325,6 @@ ErrorManagement::ErrorType DebugService::Server(ExecutionInfo & info) { if (info.GetStage() == ExecutionInfo::TerminationStage) return ErrorManagement::NoError; if (info.GetStage() == ExecutionInfo::StartupStage) { serverThreadId = Threads::Id(); - if (!tcpServer.Listen(controlPort)) return ErrorManagement::FatalError; return ErrorManagement::NoError; } @@ -375,7 +388,6 @@ ErrorManagement::ErrorType DebugService::LogStreamer(ExecutionInfo & info) { if (info.GetStage() == ExecutionInfo::TerminationStage) return ErrorManagement::NoError; if (info.GetStage() == ExecutionInfo::StartupStage) { logStreamerThreadId = Threads::Id(); - if (!logServer.Listen(logPort)) return ErrorManagement::FatalError; return ErrorManagement::NoError; } diff --git a/Test/Integration/CMakeLists.txt b/Test/Integration/CMakeLists.txt index 4a7a023..a702770 100644 --- a/Test/Integration/CMakeLists.txt +++ b/Test/Integration/CMakeLists.txt @@ -6,3 +6,6 @@ target_link_libraries(TraceTest marte_dev ${MARTe2_LIB}) add_executable(ValidationTest ValidationTest.cpp) target_link_libraries(ValidationTest marte_dev ${MARTe2_LIB} ${IOGAM_LIB} ${LinuxTimer_LIB}) + +add_executable(SchedulerTest SchedulerTest.cpp) +target_link_libraries(SchedulerTest marte_dev ${MARTe2_LIB} ${IOGAM_LIB} ${LinuxTimer_LIB}) diff --git a/Test/Integration/SchedulerTest.cpp b/Test/Integration/SchedulerTest.cpp new file mode 100644 index 0000000..272e959 --- /dev/null +++ b/Test/Integration/SchedulerTest.cpp @@ -0,0 +1,202 @@ +#include "DebugService.h" +#include "DebugCore.h" +#include "ObjectRegistryDatabase.h" +#include "StandardParser.h" +#include "StreamString.h" +#include "BasicUDPSocket.h" +#include "BasicTCPSocket.h" +#include "RealTimeApplication.h" +#include "GlobalObjectsDatabase.h" +#include "MessageI.h" +#include +#include + +using namespace MARTe; + +const char8 * const config_text = +"+DebugService = {" +" Class = DebugService " +" ControlPort = 8080 " +" UdpPort = 8081 " +" StreamIP = \"127.0.0.1\" " +"}" +"+App = {" +" Class = RealTimeApplication " +" +Functions = {" +" Class = ReferenceContainer " +" +GAM1 = {" +" Class = IOGAM " +" InputSignals = {" +" Counter = {" +" DataSource = Timer " +" Type = uint32 " +" }" +" }" +" OutputSignals = {" +" Counter = {" +" DataSource = DDB " +" Type = uint32 " +" }" +" }" +" }" +" }" +" +Data = {" +" Class = ReferenceContainer " +" DefaultDataSource = DDB " +" +Timer = {" +" Class = LinuxTimer " +" SleepTime = 100000 " // 100ms +" Signals = {" +" Counter = { Type = uint32 }" +" }" +" }" +" +DDB = {" +" Class = GAMDataSource " +" Signals = { Counter = { Type = uint32 } }" +" }" +" +DAMS = { Class = TimingDataSource }" +" }" +" +States = {" +" Class = ReferenceContainer " +" +State1 = {" +" Class = RealTimeState " +" +Threads = {" +" Class = ReferenceContainer " +" +Thread1 = {" +" Class = RealTimeThread " +" Functions = {GAM1} " +" }" +" }" +" }" +" }" +" +Scheduler = {" +" Class = FastScheduler " +" TimingDataSource = DAMS " +" }" +"}"; + +void TestSchedulerControl() { + printf("--- MARTe2 Scheduler Control Test ---\n"); + + ConfigurationDatabase cdb; + StreamString ss = config_text; + ss.Seek(0); + StandardParser parser(ss, cdb); + assert(parser.Parse()); + assert(ObjectRegistryDatabase::Instance()->Initialise(cdb)); + + ReferenceT service = ObjectRegistryDatabase::Instance()->Find("DebugService"); + assert(service.IsValid()); + + ReferenceT app = ObjectRegistryDatabase::Instance()->Find("App"); + assert(app.IsValid()); + + if (app->PrepareNextState("State1") != ErrorManagement::NoError) { + printf("ERROR: Failed to prepare State1\n"); + return; + } + + if (app->StartNextStateExecution() != ErrorManagement::NoError) { + printf("ERROR: Failed to start execution\n"); + return; + } + + printf("Application started. Waiting for cycles...\n"); + Sleep::MSec(1000); + + // Enable Trace First + { + BasicTCPSocket tClient; + if (tClient.Connect("127.0.0.1", 8080)) { + const char* cmd = "TRACE Root.App.Data.Timer.Counter 1\n"; + uint32 s = StringHelper::Length(cmd); + tClient.Write(cmd, s); + tClient.Close(); + } else { + printf("WARNING: Could not connect to DebugService to enable trace.\n"); + } + } + + BasicUDPSocket listener; + listener.Open(); + listener.Listen(8081); + + // Read current value + uint32 valBeforePause = 0; + char buffer[2048]; + uint32 size = 2048; + TimeoutType timeout(500); + if (listener.Read(buffer, size, timeout)) { + // [Header][ID][Size][Value] + valBeforePause = *(uint32*)(&buffer[28]); + printf("Value before/at pause: %u\n", valBeforePause); + } else { + printf("WARNING: No data received before pause.\n"); + } + + // Send PAUSE + printf("Sending PAUSE command...\n"); + BasicTCPSocket client; + if (client.Connect("127.0.0.1", 8080)) { + const char* cmd = "PAUSE\n"; + uint32 s = StringHelper::Length(cmd); + client.Write(cmd, s); + client.Close(); + } else { + printf("ERROR: Could not connect to DebugService to send PAUSE.\n"); + } + + Sleep::MSec(2000); // Wait 2 seconds + + // Read again - should be same or very close if paused + uint32 valAfterWait = 0; + size = 2048; // Reset size + while(listener.Read(buffer, size, TimeoutType(10))) { + valAfterWait = *(uint32*)(&buffer[28]); + size = 2048; + } + + printf("Value after 2s wait (drained): %u\n", valAfterWait); + + // Check if truly paused + if (valAfterWait > valBeforePause + 5) { + printf("FAILURE: Counter increased significantly while paused! (%u -> %u)\n", valBeforePause, valAfterWait); + } else { + printf("SUCCESS: Counter held steady (or close) during pause.\n"); + } + + // Resume + printf("Sending RESUME command...\n"); + { + BasicTCPSocket rClient; + if (rClient.Connect("127.0.0.1", 8080)) { + const char* cmd = "RESUME\n"; + uint32 s = StringHelper::Length(cmd); + rClient.Write(cmd, s); + rClient.Close(); + } + } + + Sleep::MSec(1000); + + // Check if increasing + uint32 valAfterResume = 0; + size = 2048; + if (listener.Read(buffer, size, timeout)) { + valAfterResume = *(uint32*)(&buffer[28]); + printf("Value after resume: %u\n", valAfterResume); + } + + if (valAfterResume > valAfterWait) { + printf("SUCCESS: Execution resumed.\n"); + } else { + printf("FAILURE: Execution did not resume.\n"); + } + + app->StopCurrentStateExecution(); +} + +int main() { + TestSchedulerControl(); + return 0; +} diff --git a/Test/Integration/ValidationTest.cpp b/Test/Integration/ValidationTest.cpp index 71ee510..c8a547c 100644 --- a/Test/Integration/ValidationTest.cpp +++ b/Test/Integration/ValidationTest.cpp @@ -6,6 +6,7 @@ #include "BasicUDPSocket.h" #include "BasicTCPSocket.h" #include "RealTimeApplication.h" +#include "GlobalObjectsDatabase.h" #include #include @@ -28,6 +29,7 @@ const char8 * const config_text = " Counter = {" " DataSource = Timer " " Type = uint32 " +" Frequency = 100 " " }" " }" " OutputSignals = {" @@ -46,6 +48,7 @@ const char8 * const config_text = " SleepTime = 10000 " " Signals = {" " Counter = { Type = uint32 }" +" Time = { Type = uint32 }" " }" " }" " +DDB = {" @@ -76,7 +79,8 @@ const char8 * const config_text = void RunValidationTest() { printf("--- MARTe2 100Hz Trace Validation Test ---\n"); - // 1. Load Configuration + ObjectRegistryDatabase::Instance()->Purge(); + ConfigurationDatabase cdb; StreamString ss = config_text; ss.Seek(0); @@ -91,66 +95,70 @@ void RunValidationTest() { return; } - // 2. Start Application + ReferenceT service = ObjectRegistryDatabase::Instance()->Find("DebugService"); + if (!service.IsValid()) { + printf("ERROR: DebugService not found\n"); + return; + } + ReferenceT app = ObjectRegistryDatabase::Instance()->Find("App"); if (!app.IsValid()) { printf("ERROR: App not found\n"); return; } - // We try to use State1 directly as many MARTe2 apps start in the first defined state if no transition is needed + if (!app->ConfigureApplication()) { + printf("ERROR: Failed to configure application\n"); + return; + } + if (app->PrepareNextState("State1") != ErrorManagement::NoError) { printf("ERROR: Failed to prepare state State1\n"); - // We will try to investigate why, but for now we continue + return; } if (app->StartNextStateExecution() != ErrorManagement::NoError) { - printf("ERROR: Failed to start execution. Maybe it needs an explicit state?\n"); - // return; + printf("ERROR: Failed to start execution\n"); + return; } - printf("Application started at 100Hz.\n"); + printf("Application and DebugService are active.\n"); Sleep::MSec(1000); - // 3. Enable Trace via TCP (Simulating GUI) - BasicTCPSocket client; - if (client.Connect("127.0.0.1", 8080)) { - const char* cmd = "TRACE Root.App.Data.Timer.Counter 1\n"; - uint32 s = StringHelper::Length(cmd); - client.Write(cmd, s); - - char resp[1024]; s = 1024; - TimeoutType timeout(1000); - if (client.Read(resp, s, timeout)) { - resp[s] = '\0'; - printf("Server Response: %s", resp); - } else { - printf("WARNING: No response from server to TRACE command.\n"); - } - client.Close(); - } else { - printf("ERROR: Failed to connect to DebugService on 8080\n"); - // continue anyway to see if it's already working - } + // DIRECT ACTIVATION: Use the public TraceSignal method + printf("Activating trace directly...\n"); + // We try multiple potential paths to be safe + uint32 traceCount = 0; + traceCount += service->TraceSignal("App.Data.Timer.Counter", true, 1); + traceCount += service->TraceSignal("Timer.Counter", true, 1); + traceCount += service->TraceSignal("Counter", true, 1); + + printf("Trace enabled (Matched Aliases: %u)\n", traceCount); // 4. Setup UDP Listener BasicUDPSocket listener; if (!listener.Open()) { printf("ERROR: Failed to open UDP socket\n"); return; } if (!listener.Listen(8081)) { printf("ERROR: Failed to listen on UDP 8081\n"); return; } - // 5. Validate for 30 seconds - printf("Validating telemetry for 30 seconds...\n"); + // 5. Validate for 10 seconds + printf("Validating telemetry for 10 seconds...\n"); uint32 lastVal = 0; bool first = true; uint32 packetCount = 0; uint32 discontinuityCount = 0; float64 startTime = HighResolutionTimer::Counter() * HighResolutionTimer::Period(); + float64 globalTimeout = startTime + 30.0; - while ((HighResolutionTimer::Counter() * HighResolutionTimer::Period() - startTime) < 30.0) { + while ((HighResolutionTimer::Counter() * HighResolutionTimer::Period() - startTime) < 10.0) { + if (HighResolutionTimer::Counter() * HighResolutionTimer::Period() > globalTimeout) { + printf("CRITICAL ERROR: Global test timeout reached.\n"); + break; + } + char buffer[2048]; uint32 size = 2048; - TimeoutType timeout(500); + TimeoutType timeout(200); if (listener.Read(buffer, size, timeout)) { TraceHeader *h = (TraceHeader*)buffer; @@ -168,7 +176,7 @@ void RunValidationTest() { first = false; packetCount++; - if (packetCount % 500 == 0) { + if (packetCount % 200 == 0) { printf("Received %u packets... Current Value: %u\n", packetCount, val); } } @@ -176,17 +184,17 @@ void RunValidationTest() { } printf("Test Finished.\n"); - printf("Total Packets Received: %u (Expected ~3000)\n", packetCount); + printf("Total Packets Received: %u (Expected ~1000)\n", packetCount); printf("Discontinuities: %u\n", discontinuityCount); - float64 actualFreq = (float64)packetCount / 30.0; + float64 actualFreq = (float64)packetCount / 10.0; printf("Average Frequency: %.2f Hz\n", actualFreq); if (packetCount < 100) { printf("FAILURE: Almost no packets received. Telemetry is broken.\n"); - } else if (packetCount < 2500) { - printf("WARNING: Too few packets received (Expected 3000, Got %u).\n", packetCount); - } else if (discontinuityCount > 100) { + } else if (packetCount < 800) { + printf("WARNING: Too few packets received (Expected 1000, Got %u).\n", packetCount); + } else if (discontinuityCount > 20) { printf("FAILURE: Too many discontinuities (%u).\n", discontinuityCount); } else { printf("VALIDATION SUCCESSFUL!\n"); @@ -194,6 +202,7 @@ void RunValidationTest() { app->StopCurrentStateExecution(); listener.Close(); + ObjectRegistryDatabase::Instance()->Purge(); } int main() { diff --git a/run_test.sh b/run_test.sh index d6b10f1..7ccc442 100755 --- a/run_test.sh +++ b/run_test.sh @@ -1,21 +1,14 @@ #!/bin/bash -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -source $DIR/env.sh - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. export MARTe2_DIR=/home/martino/Projects/marte_debug/dependency/MARTe2 export TARGET=x86-linux -MARTE_APP=$MARTe2_DIR/Build/$TARGET/App/MARTeApp.ex +echo "Cleaning up old instances..." +pkill -9 IntegrationTest +pkill -9 ValidationTest +pkill -9 SchedulerTest +pkill -9 main +sleep 2 -# Build paths for all components -LIBS=$DIR/Build -LIBS=$LIBS:$MARTe2_DIR/Build/$TARGET/Core -LIBS=$LIBS:$MARTe2_Components_DIR/Build/$TARGET/Components/DataSources/LinuxTimer -LIBS=$LIBS:$MARTe2_Components_DIR/Build/$TARGET/Components/DataSources/LoggerDataSource -LIBS=$LIBS:$MARTe2_Components_DIR/Build/$TARGET/Components/GAMs/IOGAM - -export LD_LIBRARY_PATH=$LIBS:$LD_LIBRARY_PATH - -# ./Build/Test/Integration/IntegrationTest -f Test/Configurations/debug_test.cfg -l RealTimeLoader -s State1 -$MARTE_APP -f $DIR/Test/Configurations/debug_test.cfg -l RealTimeLoader -s State1 +echo "Starting MARTe2 Integration Tests..." +./Build/Test/Integration/ValidationTest