diff --git a/Source/Components/Interfaces/DebugService/DebugService.cpp b/Source/Components/Interfaces/DebugService/DebugService.cpp index caef424..b296adf 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.cpp +++ b/Source/Components/Interfaces/DebugService/DebugService.cpp @@ -1,15 +1,18 @@ #include "BasicTCPSocket.h" #include "ClassRegistryItem.h" #include "ConfigurationDatabase.h" +#include "DataSourceI.h" #include "DebugBrokerWrapper.h" #include "DebugService.h" #include "GAM.h" +#include "GlobalObjectsDatabase.h" #include "HighResolutionTimer.h" #include "ObjectBuilder.h" #include "ObjectRegistryDatabase.h" #include "StreamString.h" #include "TimeoutType.h" #include "TypeConversion.h" +#include "ReferenceT.h" namespace MARTe { @@ -84,6 +87,7 @@ DebugService::DebugService() threadService(binderServer), streamerService(binderStreamer) { controlPort = 0; streamPort = 8081; + logPort = 8082; streamIP = "127.0.0.1"; isServer = false; suppressTimeoutLogs = true; @@ -106,6 +110,7 @@ DebugService::~DebugService() { for (uint32 i = 0; i < signals.Size(); i++) { delete signals[i]; } + this->Purge(); } bool DebugService::Initialise(StructuredDataI &data) { @@ -132,6 +137,15 @@ bool DebugService::Initialise(StructuredDataI &data) { (void)data.Read("UdpPort", port); streamPort = (uint16)port; } + + port = 8082; + if (data.Read("LogPort", port)) { + logPort = (uint16)port; + } else { + (void)data.Read("TcpLogPort", port); + logPort = (uint16)port; + } + StreamString tempIP; if (data.Read("StreamIP", tempIP)) { streamIP = tempIP; @@ -175,6 +189,34 @@ bool DebugService::Initialise(StructuredDataI &data) { return false; if (streamerService.Start() != ErrorManagement::NoError) return false; + + if (logPort > 0) { + Reference tcpLogger( + "TcpLogger", GlobalObjectsDatabase::Instance()->GetStandardHeap()); + if (tcpLogger.IsValid()) { + ConfigurationDatabase loggerConfig; + loggerConfig.Write("Port", (uint32)logPort); + if (tcpLogger->Initialise(loggerConfig)) { + this->Insert(tcpLogger); + + Reference loggerService( + "LoggerService", + GlobalObjectsDatabase::Instance()->GetStandardHeap()); + if (loggerService.IsValid()) { + ConfigurationDatabase serviceConfig; + serviceConfig.Write("CPUs", (uint32)1); + ReferenceContainer *lc = + dynamic_cast(loggerService.operator->()); + if (lc != NULL_PTR(ReferenceContainer *)) { + lc->Insert(tcpLogger); + } + if (loggerService->Initialise(serviceConfig)) { + this->Insert(loggerService); + } + } + } + } + } } return true; } @@ -430,6 +472,24 @@ ErrorManagement::ErrorType DebugService::Streamer(ExecutionInfo &info) { uint32 packetOffset = 0; uint32 sequenceNumber = 0; while (info.GetStage() == ExecutionInfo::MainStage) { + // Poll monitored signals + uint64 currentTimeMs = (uint64)((float64)HighResolutionTimer::Counter() * + HighResolutionTimer::Period() * 1000.0); + mutex.FastLock(); + for (uint32 i = 0; i < monitoredSignals.Size(); i++) { + if (currentTimeMs >= (monitoredSignals[i].lastPollTime + monitoredSignals[i].periodMs)) { + monitoredSignals[i].lastPollTime = currentTimeMs; + uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() * + HighResolutionTimer::Period() * 1000000.0); + + void *address = NULL_PTR(void *); + if (monitoredSignals[i].dataSource->GetSignalMemoryBuffer(monitoredSignals[i].signalIdx, 0, address)) { + traceBuffer.Push(monitoredSignals[i].internalID, ts, (uint8 *)address, monitoredSignals[i].size); + } + } + } + mutex.FastUnLock(); + uint32 id, size; uint64 ts; uint8 sampleData[1024]; @@ -536,7 +596,47 @@ void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) { } } else if (token == "DISCOVER") Discover(client); - else if (token == "CONFIG") + else if (token == "SERVICE_INFO") { + if (client) { + StreamString resp; + resp.Printf("OK SERVICE_INFO TCP_CTRL:%u UDP_STREAM:%u TCP_LOG:%u STATE:%s\n", + controlPort, streamPort, logPort, isPaused ? "PAUSED" : "RUNNING"); + uint32 s = resp.Size(); + (void)client->Write(resp.Buffer(), s); + } + } else if (token == "MONITOR") { + StreamString subToken; + if (cmd.GetToken(subToken, delims, term) && subToken == "SIGNAL") { + StreamString name, period; + if (cmd.GetToken(name, delims, term) && cmd.GetToken(period, delims, term)) { + uint32 p = 100; + AnyType pVal(UnsignedInteger32Bit, 0u, &p); + AnyType pStr(CharString, 0u, period.Buffer()); + (void)TypeConvert(pVal, pStr); + uint32 count = RegisterMonitorSignal(name.Buffer(), p); + if (client) { + StreamString resp; + resp.Printf("OK MONITOR %u\n", count); + uint32 s = resp.Size(); + (void)client->Write(resp.Buffer(), s); + } + } + } + } else if (token == "UNMONITOR") { + StreamString subToken; + if (cmd.GetToken(subToken, delims, term) && subToken == "SIGNAL") { + StreamString name; + if (cmd.GetToken(name, delims, term)) { + uint32 count = UnmonitorSignal(name.Buffer()); + if (client) { + StreamString resp; + resp.Printf("OK UNMONITOR %u\n", count); + uint32 s = resp.Size(); + (void)client->Write(resp.Buffer(), s); + } + } + } + } else if (token == "CONFIG") ServeConfig(client); else if (token == "PAUSE") { SetPaused(true); @@ -554,7 +654,7 @@ void DebugService::HandleCommand(StreamString cmd, BasicTCPSocket *client) { StreamString json; json = "{\"Name\": \"Root\", \"Class\": \"ObjectRegistryDatabase\", " "\"Children\": [\n"; - (void)ExportTree(ObjectRegistryDatabase::Instance(), json); + (void)ExportTree(ObjectRegistryDatabase::Instance(), json, NULL_PTR(const char8 *)); json += "\n]}\nOK TREE\n"; uint32 s = json.Size(); if (client) @@ -747,7 +847,7 @@ void DebugService::InfoNode(const char8 *path, BasicTCPSocket *client) { } uint32 DebugService::ExportTree(ReferenceContainer *container, - StreamString &json) { + StreamString &json, const char8 *pathPrefix) { if (container == NULL_PTR(ReferenceContainer *)) return 0; uint32 size = container->Size(); @@ -761,6 +861,14 @@ uint32 DebugService::ExportTree(ReferenceContainer *container, const char8 *cname = child->GetName(); if (cname == NULL_PTR(const char8 *)) cname = "unnamed"; + + StreamString currentPath; + if (pathPrefix != NULL_PTR(const char8 *)) { + currentPath.Printf("%s.%s", pathPrefix, cname); + } else { + currentPath = cname; + } + nodeJson += "{\"Name\": \""; EscapeJson(cname, nodeJson); nodeJson += "\", \"Class\": \""; @@ -775,7 +883,7 @@ uint32 DebugService::ExportTree(ReferenceContainer *container, nodeJson += ", \"Children\": [\n"; uint32 subCount = 0u; if (inner != NULL_PTR(ReferenceContainer *)) - subCount += ExportTree(inner, nodeJson); + subCount += ExportTree(inner, nodeJson, currentPath.Buffer()); if (ds != NULL_PTR(DataSourceI *)) { uint32 nSignals = ds->GetNumberOfSignals(); for (uint32 j = 0u; j < nSignals; j++) { @@ -790,12 +898,22 @@ uint32 DebugService::ExportTree(ReferenceContainer *container, (void)ds->GetSignalNumberOfDimensions(j, dims); uint32 elems = 0u; (void)ds->GetSignalNumberOfElements(j, elems); + + StreamString signalFullPath; + signalFullPath.Printf("%s.%s", currentPath.Buffer(), sname.Buffer()); + bool traceable = false; + bool forcable = false; + (void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable); + nodeJson += "{\"Name\": \""; EscapeJson(sname.Buffer(), nodeJson); nodeJson += "\", \"Class\": \"Signal\", \"Type\": \""; EscapeJson(stype ? stype : "Unknown", nodeJson); - nodeJson.Printf("\", \"Dimensions\": %d, \"Elements\": %u}", dims, + nodeJson.Printf("\", \"Dimensions\": %d, \"Elements\": %u", dims, elems); + nodeJson.Printf(", \"IsTraceable\": %s, \"IsForcable\": %s}", + traceable ? "true" : "false", + forcable ? "true" : "false"); } } if (gam != NULL_PTR(GAM *)) { @@ -812,12 +930,23 @@ uint32 DebugService::ExportTree(ReferenceContainer *container, (void)gam->GetSignalNumberOfDimensions(InputSignals, j, dims); uint32 elems = 0u; (void)gam->GetSignalNumberOfElements(InputSignals, j, elems); + + StreamString signalFullPath; + signalFullPath.Printf("%s.In.%s", currentPath.Buffer(), + sname.Buffer()); + bool traceable = false; + bool forcable = false; + (void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable); + nodeJson += "{\"Name\": \"In."; EscapeJson(sname.Buffer(), nodeJson); nodeJson += "\", \"Class\": \"InputSignal\", \"Type\": \""; EscapeJson(stype ? stype : "Unknown", nodeJson); - nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u}", dims, + nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u", dims, elems); + nodeJson.Printf(", \"IsTraceable\": %s, \"IsForcable\": %s}", + traceable ? "true" : "false", + forcable ? "true" : "false"); } uint32 nOut = gam->GetNumberOfOutputSignals(); for (uint32 j = 0u; j < nOut; j++) { @@ -832,12 +961,23 @@ uint32 DebugService::ExportTree(ReferenceContainer *container, (void)gam->GetSignalNumberOfDimensions(OutputSignals, j, dims); uint32 elems = 0u; (void)gam->GetSignalNumberOfElements(OutputSignals, j, elems); + + StreamString signalFullPath; + signalFullPath.Printf("%s.Out.%s", currentPath.Buffer(), + sname.Buffer()); + bool traceable = false; + bool forcable = false; + (void)IsInstrumented(signalFullPath.Buffer(), traceable, forcable); + nodeJson += "{\"Name\": \"Out."; EscapeJson(sname.Buffer(), nodeJson); nodeJson += "\", \"Class\": \"OutputSignal\", \"Type\": \""; EscapeJson(stype ? stype : "Unknown", nodeJson); - nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u}", dims, + nodeJson.Printf("\", \"Dimensions\": %u, \"Elements\": %u", dims, elems); + nodeJson.Printf(", \"IsTraceable\": %s, \"IsForcable\": %s}", + traceable ? "true" : "false", + forcable ? "true" : "false"); } } nodeJson += "\n]"; @@ -908,13 +1048,121 @@ uint32 DebugService::TraceSignal(const char8 *name, bool enable, return count; } +bool DebugService::IsInstrumented(const char8 *fullPath, bool &traceable, + bool &forcable) { + mutex.FastLock(); + bool found = false; + for (uint32 i = 0; i < aliases.Size(); i++) { + if (aliases[i].name == fullPath || + SuffixMatch(aliases[i].name.Buffer(), fullPath)) { + found = true; + break; + } + } + mutex.FastUnLock(); + traceable = found; + forcable = found; + return found; +} + +uint32 DebugService::RegisterMonitorSignal(const char8 *path, uint32 periodMs) { + mutex.FastLock(); + uint32 count = 0; + + // Check if already monitored + for (uint32 j = 0; j < monitoredSignals.Size(); j++) { + if (monitoredSignals[j].path == path) { + monitoredSignals[j].periodMs = periodMs; + mutex.FastUnLock(); + return 1; + } + } + + // Path resolution: find the DataSource object + StreamString fullPath = path; + fullPath.Seek(0); + char8 term; + Vec parts; + StreamString token; + while (fullPath.GetToken(token, ".", term)) { + parts.Push(token); + token = ""; + } + + if (parts.Size() >= 2) { + StreamString signalName = parts[parts.Size() - 1u]; + StreamString dsPath; + for (uint32 i = 0; i < parts.Size() - 1u; i++) { + dsPath += parts[i]; + if (i < parts.Size() - 2u) + dsPath += "."; + } + + ReferenceT ds = + ObjectRegistryDatabase::Instance()->Find(dsPath.Buffer()); + if (ds.IsValid()) { + uint32 idx = 0; + if (ds->GetSignalIndex(idx, signalName.Buffer())) { + MonitoredSignal m; + m.dataSource = ds; + m.signalIdx = idx; + m.path = path; + m.periodMs = periodMs; + m.lastPollTime = 0; + m.size = 0; + (void)ds->GetSignalByteSize(idx, m.size); + if (m.size == 0) + m.size = 4; + + // Use high-bit for polled signals to avoid conflict with brokered ones + m.internalID = 0x80000000 | monitoredSignals.Size(); + + // Re-use existing ID if signal is also instrumented via broker + for (uint32 i = 0; i < aliases.Size(); i++) { + if (aliases[i].name == path || + SuffixMatch(aliases[i].name.Buffer(), path)) { + m.internalID = signals[aliases[i].signalIndex]->internalID; + break; + } + } + + monitoredSignals.Push(m); + count = 1; + } + } + } + + mutex.FastUnLock(); + return count; +} + +uint32 DebugService::UnmonitorSignal(const char8 *path) { + mutex.FastLock(); + uint32 count = 0; + for (uint32 i = 0; i < monitoredSignals.Size(); i++) { + if (monitoredSignals[i].path == path || + SuffixMatch(monitoredSignals[i].path.Buffer(), path)) { + (void)monitoredSignals.Remove(i); + i--; + count++; + } + } + mutex.FastUnLock(); + return count; +} + void DebugService::Discover(BasicTCPSocket *client) { if (client) { StreamString header = "{\n \"Signals\": [\n"; uint32 s = header.Size(); (void)client->Write(header.Buffer(), s); mutex.FastLock(); + uint32 total = 0; for (uint32 i = 0; i < aliases.Size(); i++) { + if (total > 0) { + uint32 commaSize = 2; + (void)client->Write(",\n", commaSize); + } StreamString line; DebugSignalInfo *sig = signals[aliases[i].signalIndex]; const char8 *typeName = @@ -924,11 +1172,39 @@ void DebugService::Discover(BasicTCPSocket *client) { typeName ? typeName : "Unknown"); EnrichWithConfig(aliases[i].name.Buffer(), line); line += "}"; - if (i < aliases.Size() - 1) - line += ","; - line += "\n"; s = line.Size(); (void)client->Write(line.Buffer(), s); + total++; + } + + // Export monitored signals not already in aliases + for (uint32 i = 0; i < monitoredSignals.Size(); i++) { + bool found = false; + for (uint32 j = 0; j < aliases.Size(); j++) { + if (aliases[j].name == monitoredSignals[i].path) { + found = true; + break; + } + } + if (!found) { + if (total > 0) { + uint32 commaSize = 2; + (void)client->Write(",\n", commaSize); + } + StreamString line; + const char8 *typeName = TypeDescriptor::GetTypeNameFromTypeDescriptor( + monitoredSignals[i].dataSource->GetSignalType( + monitoredSignals[i].signalIdx)); + line.Printf(" {\"name\": \"%s\", \"id\": %u, \"type\": \"%s\"", + monitoredSignals[i].path.Buffer(), + monitoredSignals[i].internalID, + typeName ? typeName : "Unknown"); + EnrichWithConfig(monitoredSignals[i].path.Buffer(), line); + line += "}"; + s = line.Size(); + (void)client->Write(line.Buffer(), s); + total++; + } } mutex.FastUnLock(); StreamString footer = " ]\n}\nOK DISCOVER\n"; diff --git a/Source/Components/Interfaces/DebugService/DebugService.h b/Source/Components/Interfaces/DebugService/DebugService.h index cd34817..1ee2d7d 100644 --- a/Source/Components/Interfaces/DebugService/DebugService.h +++ b/Source/Components/Interfaces/DebugService/DebugService.h @@ -16,6 +16,7 @@ namespace MARTe { class MemoryMapBroker; +class DataSourceI; struct SignalAlias { StreamString name; @@ -64,17 +65,31 @@ public: uint32 ForceSignal(const char8 *name, const char8 *valueStr); uint32 UnforceSignal(const char8 *name); uint32 TraceSignal(const char8 *name, bool enable, uint32 decimation = 1); + bool IsInstrumented(const char8 *fullPath, bool &traceable, bool &forcable); void Discover(BasicTCPSocket *client); void InfoNode(const char8 *path, BasicTCPSocket *client); void ListNodes(const char8 *path, BasicTCPSocket *client); void ServeConfig(BasicTCPSocket *client); void SetFullConfig(ConfigurationDatabase &config); + struct MonitoredSignal { + ReferenceT dataSource; + uint32 signalIdx; + uint32 internalID; + uint32 periodMs; + uint64 lastPollTime; + uint32 size; + StreamString path; + }; + + uint32 RegisterMonitorSignal(const char8 *path, uint32 periodMs); + uint32 UnmonitorSignal(const char8 *path); + private: void HandleCommand(StreamString cmd, BasicTCPSocket *client); void UpdateBrokersActiveStatus(); - uint32 ExportTree(ReferenceContainer *container, StreamString &json); + uint32 ExportTree(ReferenceContainer *container, StreamString &json, const char8 *pathPrefix); void PatchRegistry(); void EnrichWithConfig(const char8 *path, StreamString &json); @@ -85,6 +100,7 @@ private: uint16 controlPort; uint16 streamPort; + uint16 logPort; StreamString streamIP; bool isServer; bool suppressTimeoutLogs; @@ -123,6 +139,7 @@ private: Vec signals; Vec aliases; Vec brokers; + Vec monitoredSignals; FastPollingMutexSem mutex; TraceRingBuffer traceBuffer; diff --git a/Source/Components/Interfaces/DebugService/Makefile.inc b/Source/Components/Interfaces/DebugService/Makefile.inc index 6fa6264..6083f09 100644 --- a/Source/Components/Interfaces/DebugService/Makefile.inc +++ b/Source/Components/Interfaces/DebugService/Makefile.inc @@ -33,6 +33,7 @@ include $(MAKEDEFAULTDIR)/MakeStdLibDefs.$(TARGET) INCLUDES += -I$(ROOT_DIR)/Source/Core/Types/Result INCLUDES += -I$(ROOT_DIR)/Source/Core/Types/Vec +INCLUDES += -I$(ROOT_DIR)/Source/Components/Interfaces/TCPLogger INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L0Types INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L1Portability INCLUDES += -I$(MARTe2_DIR)/Source/Core/BareMetal/L2Objects diff --git a/Test/Configurations/debug_test.cfg b/Test/Configurations/debug_test.cfg index dc760f6..14caf10 100644 --- a/Test/Configurations/debug_test.cfg +++ b/Test/Configurations/debug_test.cfg @@ -125,14 +125,6 @@ Class = DebugService ControlPort = 8080 UdpPort = 8081 + LogPort = 8082 StreamIP = "127.0.0.1" } - -+LoggerService = { - Class = LoggerService - CPUs = 0x1 - +DebugConsumer = { - Class = TcpLogger - Port = 8082 - } -} diff --git a/Test/Integration/IntegrationTests.cpp b/Test/Integration/IntegrationTests.cpp index baa1b77..7da9d4c 100644 --- a/Test/Integration/IntegrationTests.cpp +++ b/Test/Integration/IntegrationTests.cpp @@ -7,8 +7,7 @@ #include "BasicUDPSocket.h" #include "RealTimeApplication.h" #include "StandardParser.h" -#include "StreamString.h" -#include "GlobalObjectsDatabase.h" +#include "TestCommon.h" #include using namespace MARTe; @@ -31,6 +30,7 @@ void TestFullTracePipeline(); void RunValidationTest(); void TestConfigCommands(); void TestGAMSignalTracing(); +void TestTreeCommand(); int main() { signal(SIGALRM, timeout_handler); @@ -83,9 +83,9 @@ int main() { // TestConfigCommands(); // Skipping for now Sleep::MSec(1000); - // printf("\n--- Test 6: GAM Signal Tracing ---\n"); - // TestGAMSignalTracing(); - // Sleep::MSec(1000); + printf("\n--- Test 6: TREE Command Enhancement ---\n"); + TestTreeCommand(); + Sleep::MSec(1000); printf("\nAll Integration Tests Finished.\n"); @@ -94,72 +94,6 @@ int main() { // --- Test Implementation --- -const char8 * const debug_test_config = -"DebugService = {" -" Class = DebugService " -" ControlPort = 8095 " -" UdpPort = 8096 " -" StreamIP = \"127.0.0.1\" " -"}" -"App = {" -" Class = RealTimeApplication " -" +Functions = {" -" Class = ReferenceContainer " -" +GAM1 = {" -" Class = IOGAM " -" InputSignals = {" -" Counter = { DataSource = Timer Type = uint32 Frequency = 1000 }" -" }" -" OutputSignals = {" -" Counter = { DataSource = DDB Type = uint32 }" -" }" -" }" -" +GAM2 = {" -" Class = IOGAM " -" InputSignals = {" -" Counter = { DataSource = TimerSlow Type = uint32 Frequency = 10 }" -" }" -" OutputSignals = {" -" Counter = { DataSource = Logger Type = uint32 }" -" }" -" }" -" }" -" +Data = {" -" Class = ReferenceContainer " -" DefaultDataSource = DDB " -" +Timer = { Class = LinuxTimer SleepTime = 1000 Signals = { Counter = { Type = uint32 } } }" -" +TimerSlow = { Class = LinuxTimer SleepTime = 100000 Signals = { Counter = { Type = uint32 } } }" -" +Logger = { Class = LoggerDataSource 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 GAM2} } } }" -" }" -" +Scheduler = { Class = GAMScheduler TimingDataSource = DAMS }" -"}"; - -bool SendCommandGAM(uint16 port, const char8* cmd, StreamString &reply) { - BasicTCPSocket client; - if (!client.Open()) return false; - if (!client.Connect("127.0.0.1", port)) return false; - - uint32 s = StringHelper::Length(cmd); - if (!client.Write(cmd, s)) return false; - - char buffer[4096]; - uint32 size = 4096; - TimeoutType timeout(2000); - if (client.Read(buffer, size, timeout)) { - reply.Write(buffer, size); - client.Close(); - return true; - } - client.Close(); - return false; -} - void TestGAMSignalTracing() { printf("--- Test: GAM Signal Tracing Issue ---\n"); diff --git a/Test/Integration/Makefile.inc b/Test/Integration/Makefile.inc index 1574983..bfa0918 100644 --- a/Test/Integration/Makefile.inc +++ b/Test/Integration/Makefile.inc @@ -1,4 +1,4 @@ -OBJSX = SchedulerTest.x TraceTest.x ValidationTest.x ConfigCommandTest.x +OBJSX = SchedulerTest.x TraceTest.x ValidationTest.x ConfigCommandTest.x TreeCommandTest.x TestCommon.x PACKAGE = Test/Integration @@ -29,6 +29,8 @@ INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L4LoggerService INCLUDES += -I$(MARTe2_DIR)/Source/Core/Scheduler/L5GAMs INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L1Portability INCLUDES += -I$(MARTe2_DIR)/Source/Core/FileSystem/L3Streams +INCLUDES += -I$(MARTe2_Components_DIR)/Source/Components/GAMs/IOGAM +INCLUDES += -I$(MARTe2_Components_DIR)/Source/Components/DataSources/LinuxTimer LIBRARIES += -L$(MARTe2_DIR)/Build/$(TARGET)/Core -lMARTe2 LIBRARIES += -L$(MARTe2_Components_DIR)/Build/$(TARGET)/Components/DataSources/LinuxTimer -lLinuxTimer diff --git a/Test/Integration/TestCommon.cpp b/Test/Integration/TestCommon.cpp new file mode 100644 index 0000000..5a787ec --- /dev/null +++ b/Test/Integration/TestCommon.cpp @@ -0,0 +1,74 @@ +#include "TestCommon.h" +#include "BasicTCPSocket.h" +#include "StringHelper.h" +#include "TimeoutType.h" + +namespace MARTe { + +const char8 * const debug_test_config = +"DebugService = {" +" Class = DebugService " +" ControlPort = 8095 " +" UdpPort = 8096 " +" StreamIP = \"127.0.0.1\" " +"}" +"App = {" +" Class = RealTimeApplication " +" +Functions = {" +" Class = ReferenceContainer " +" +GAM1 = {" +" Class = IOGAM " +" InputSignals = {" +" Counter = { DataSource = Timer Type = uint32 Frequency = 1000 }" +" }" +" OutputSignals = {" +" Counter = { DataSource = DDB Type = uint32 }" +" }" +" }" +" +GAM2 = {" +" Class = IOGAM " +" InputSignals = {" +" Counter = { DataSource = TimerSlow Type = uint32 Frequency = 10 }" +" }" +" OutputSignals = {" +" Counter = { DataSource = Logger Type = uint32 }" +" }" +" }" +" }" +" +Data = {" +" Class = ReferenceContainer " +" DefaultDataSource = DDB " +" +Timer = { Class = LinuxTimer SleepTime = 1000 Signals = { Counter = { Type = uint32 } } }" +" +TimerSlow = { Class = LinuxTimer SleepTime = 100000 Signals = { Counter = { Type = uint32 } } }" +" +Logger = { Class = LoggerDataSource 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 GAM2} } } }" +" }" +" +Scheduler = { Class = GAMScheduler TimingDataSource = DAMS }" +"}"; + +bool SendCommandGAM(uint16 port, const char8* cmd, StreamString &reply) { + BasicTCPSocket client; + if (!client.Open()) return false; + if (!client.Connect("127.0.0.1", port)) return false; + + uint32 s = StringHelper::Length(cmd); + if (!client.Write(cmd, s)) return false; + + char buffer[16384]; + uint32 size = 16384; + TimeoutType timeout(5000); + if (client.Read(buffer, size, timeout)) { + reply.Write(buffer, size); + client.Close(); + return true; + } + client.Close(); + return false; +} + +} diff --git a/Test/Integration/TestCommon.h b/Test/Integration/TestCommon.h new file mode 100644 index 0000000..6c86ab7 --- /dev/null +++ b/Test/Integration/TestCommon.h @@ -0,0 +1,12 @@ +#ifndef TESTCOMMON_H +#define TESTCOMMON_H + +#include "CompilerTypes.h" +#include "StreamString.h" + +namespace MARTe { + extern const char8 * const debug_test_config; + bool SendCommandGAM(uint16 port, const char8* cmd, StreamString &reply); +} + +#endif diff --git a/Test/Integration/TreeCommandTest.cpp b/Test/Integration/TreeCommandTest.cpp new file mode 100644 index 0000000..8c9d716 --- /dev/null +++ b/Test/Integration/TreeCommandTest.cpp @@ -0,0 +1,176 @@ +#include "BasicTCPSocket.h" +#include "ConfigurationDatabase.h" +#include "DebugService.h" +#include "GlobalObjectsDatabase.h" +#include "ObjectRegistryDatabase.h" +#include "RealTimeApplication.h" +#include "StandardParser.h" +#include "StreamString.h" +#include "TestCommon.h" +#include "IOGAM.h" +#include "LinuxTimer.h" +#include "GAMDataSource.h" +#include "TimingDataSource.h" +#include "GAMScheduler.h" +#include + +using namespace MARTe; + +void TestTreeCommand() { + printf("--- Test: TREE Command Enhancement ---\n"); + + ObjectRegistryDatabase::Instance()->Purge(); + Sleep::MSec(2000); // Wait for sockets from previous tests to clear + + ConfigurationDatabase cdb; + // Use unique ports to avoid conflict with other tests + const char8 * const tree_test_config = + "DebugService = {" + " Class = DebugService " + " ControlPort = 8110 " + " UdpPort = 8111 " + " StreamIP = \"127.0.0.1\" " + "}" + "App = {" + " Class = RealTimeApplication " + " +Functions = {" + " Class = ReferenceContainer " + " +GAM1 = {" + " Class = IOGAM " + " InputSignals = {" + " Counter = { DataSource = Timer Type = uint32 Frequency = 1000 }" + " }" + " OutputSignals = {" + " Counter = { DataSource = DDB Type = uint32 }" + " }" + " }" + " }" + " +Data = {" + " Class = ReferenceContainer " + " DefaultDataSource = DDB " + " +Timer = { Class = LinuxTimer SleepTime = 1000 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 = GAMScheduler TimingDataSource = DAMS }" + "}"; + + StreamString ss = tree_test_config; + ss.Seek(0); + StandardParser parser(ss, cdb); + if (!parser.Parse()) { + printf("ERROR: Failed to parse config\n"); + return; + } + + cdb.MoveToRoot(); + uint32 n = cdb.GetNumberOfChildren(); + for (uint32 i = 0; i < n; i++) { + const char8 *name = cdb.GetChildName(i); + ConfigurationDatabase child; + cdb.MoveRelative(name); + cdb.Copy(child); + cdb.MoveToAncestor(1u); + + StreamString className; + child.Read("Class", className); + + Reference ref(className.Buffer(), + GlobalObjectsDatabase::Instance()->GetStandardHeap()); + if (!ref.IsValid()) { + printf("ERROR: Could not create object %s of class %s\n", name, + className.Buffer()); + continue; + } + ref->SetName(name); + if (!ref->Initialise(child)) { + printf("ERROR: Failed to initialise object %s\n", name); + continue; + } + ObjectRegistryDatabase::Instance()->Insert(ref); + } + + ReferenceT service = + ObjectRegistryDatabase::Instance()->Find("DebugService"); + if (!service.IsValid()) { + printf("ERROR: DebugService not found\n"); + return; + } + service->SetFullConfig(cdb); + + ReferenceT app = + ObjectRegistryDatabase::Instance()->Find("App"); + if (!app.IsValid()) { + printf("ERROR: App not found\n"); + return; + } + + if (!app->ConfigureApplication()) { + printf("ERROR: ConfigureApplication failed.\n"); + return; + } + + if (app->PrepareNextState("State1") != ErrorManagement::NoError) { + printf("ERROR: PrepareNextState failed.\n"); + return; + } + + if (app->StartNextStateExecution() != ErrorManagement::NoError) { + printf("ERROR: StartNextStateExecution failed.\n"); + return; + } + + printf("Application started.\n"); + Sleep::MSec(1000); + + // Step 1: Request TREE + StreamString reply; + if (SendCommandGAM(8110, "TREE\n", reply)) { + printf("TREE response received (len=%llu)\n", reply.Size()); + // ... + } + + // Step 2: SERVICE_INFO + printf("\n--- Step 2: SERVICE_INFO ---\n"); + reply = ""; + if (SendCommandGAM(8110, "SERVICE_INFO\n", reply)) { + printf("SERVICE_INFO response: %s", reply.Buffer()); + if (StringHelper::SearchString(reply.Buffer(), "TCP_CTRL:8110") != NULL_PTR(const char8 *) && + StringHelper::SearchString(reply.Buffer(), "UDP_STREAM:8111") != NULL_PTR(const char8 *)) { + printf("SUCCESS: SERVICE_INFO returned correct ports.\n"); + } else { + printf("FAILURE: SERVICE_INFO returned incorrect data.\n"); + } + } + + // Step 3: MONITOR + printf("\n--- Step 3: MONITOR SIGNAL ---\n"); + reply = ""; + if (SendCommandGAM(8110, "MONITOR SIGNAL App.Data.Timer.Counter 10\n", reply)) { + printf("MONITOR response: %s", reply.Buffer()); + if (StringHelper::SearchString(reply.Buffer(), "OK MONITOR 1") != NULL_PTR(const char8 *)) { + printf("SUCCESS: Signal monitored.\n"); + } else { + printf("FAILURE: Could not monitor signal.\n"); + } + } + + // Step 4: UNMONITOR + printf("\n--- Step 4: UNMONITOR SIGNAL ---\n"); + reply = ""; + if (SendCommandGAM(8110, "UNMONITOR SIGNAL App.Data.Timer.Counter\n", reply)) { + printf("UNMONITOR response: %s", reply.Buffer()); + if (StringHelper::SearchString(reply.Buffer(), "OK UNMONITOR 1") != NULL_PTR(const char8 *)) { + printf("SUCCESS: Signal unmonitored.\n"); + } else { + printf("FAILURE: Could not unmonitor signal.\n"); + } + } + + app->StopCurrentStateExecution(); + ObjectRegistryDatabase::Instance()->Purge(); +} diff --git a/Tools/gui_client/src/main.rs b/Tools/gui_client/src/main.rs index 0452619..b0607a4 100644 --- a/Tools/gui_client/src/main.rs +++ b/Tools/gui_client/src/main.rs @@ -51,6 +51,10 @@ struct TreeItem { dimensions: Option, #[serde(rename = "Elements")] elements: Option, + #[serde(rename = "IsTraceable")] + is_traceable: Option, + #[serde(rename = "IsForcable")] + is_forcable: Option, } #[derive(Clone)] @@ -65,6 +69,7 @@ struct TraceData { last_value: f64, recording_tx: Option>, recording_path: Option, + is_monitored: bool, } struct SignalMetadata { @@ -151,13 +156,14 @@ enum InternalEvent { Connected, Disconnected, InternalLog(String), - TraceRequested(String), + TraceRequested(String, bool), // Name, IsMonitored ClearTrace(String), UdpStats(u64), UdpDropped(u32), RecordPathChosen(String, String), // SignalName, FilePath RecordingError(String, String), // SignalName, ErrorMessage TelemMatched(u32), // Signal ID + ServiceConfig { udp_port: String, log_port: String }, } // --- App State --- @@ -167,6 +173,11 @@ struct ForcingDialog { value: String, } +struct MonitorDialog { + signal_path: String, + period_ms: String, +} + struct LogFilters { show_debug: bool, show_info: bool, @@ -212,6 +223,7 @@ struct MarteDebugApp { udp_dropped: u64, telem_match_count: HashMap, forcing_dialog: Option, + monitoring_dialog: Option, style_editor: Option<(usize, usize)>, tx_cmd: Sender, rx_events: Receiver, @@ -291,6 +303,7 @@ impl MarteDebugApp { udp_dropped: 0, telem_match_count: HashMap::new(), forcing_dialog: None, + monitoring_dialog: None, style_editor: None, tx_cmd, rx_events, @@ -430,13 +443,24 @@ impl MarteDebugApp { let _ = self.tx_cmd.send(format!("INFO {}", current_path)); } if item.class.contains("Signal") { - if ui.button("Trace").clicked() { + let traceable = item.is_traceable.unwrap_or(false); + let forcable = item.is_forcable.unwrap_or(false); + + if traceable && ui.button("Trace").clicked() { let _ = self.tx_cmd.send(format!("TRACE {} 1", current_path)); let _ = self .internal_tx - .send(InternalEvent::TraceRequested(current_path.clone())); + .send(InternalEvent::TraceRequested(current_path.clone(), false)); } - if ui.button("⚡ Force").clicked() { + if item.class == "Signal" { + if ui.button("Monitor").clicked() { + self.monitoring_dialog = Some(MonitorDialog { + signal_path: current_path.clone(), + period_ms: "100".to_string(), + }); + } + } + if forcable && ui.button("⚡ Force").clicked() { self.forcing_dialog = Some(ForcingDialog { signal_path: current_path.clone(), value: "".to_string(), @@ -535,6 +559,26 @@ fn tcp_command_worker( json_acc.clear(); } } else { + if trimmed.starts_with("OK SERVICE_INFO") { + // OK SERVICE_INFO TCP_CTRL:8110 UDP_STREAM:8111 TCP_LOG:8082 STATE:RUNNING + let parts: Vec<&str> = trimmed.split_whitespace().collect(); + let mut udp = String::new(); + let mut log = String::new(); + for p in parts { + if p.starts_with("UDP_STREAM:") { + udp = p.split(':').nth(1).unwrap_or("").to_string(); + } + if p.starts_with("TCP_LOG:") { + log = p.split(':').nth(1).unwrap_or("").to_string(); + } + } + if !udp.is_empty() || !log.is_empty() { + let _ = tx_events_inner.send(InternalEvent::ServiceConfig { + udp_port: udp, + log_port: log, + }); + } + } let _ = tx_events_inner .send(InternalEvent::CommandResponse(trimmed.to_string())); } @@ -906,14 +950,16 @@ impl eframe::App for MarteDebugApp { InternalEvent::NodeInfo(info) => { self.node_info = info; } - InternalEvent::TraceRequested(name) => { + InternalEvent::TraceRequested(name, is_monitored) => { let mut data_map = self.traced_signals.lock().unwrap(); - data_map.entry(name.clone()).or_insert_with(|| TraceData { + let entry = data_map.entry(name.clone()).or_insert_with(|| TraceData { values: VecDeque::with_capacity(10000), last_value: 0.0, recording_tx: None, recording_path: None, + is_monitored, }); + entry.is_monitored = is_monitored; self.logs.push_back(LogEntry { time: Local::now().format("%H:%M:%S").to_string(), level: "GUI_INFO".to_string(), @@ -937,11 +983,33 @@ impl eframe::App for MarteDebugApp { self.connected = true; // Wait for connection to stabilize before sending commands std::thread::sleep(std::time::Duration::from_millis(200)); + let _ = self.tx_cmd.send("SERVICE_INFO".to_string()); + std::thread::sleep(std::time::Duration::from_millis(100)); let _ = self.tx_cmd.send("TREE".to_string()); // Wait for TREE response before sending next command std::thread::sleep(std::time::Duration::from_millis(500)); let _ = self.tx_cmd.send("DISCOVER".to_string()); } + InternalEvent::ServiceConfig { udp_port, log_port } => { + let mut changed = false; + if !udp_port.is_empty() && self.config.udp_port != udp_port { + self.config.udp_port = udp_port; + changed = true; + } + if !log_port.is_empty() && self.config.log_port != log_port { + self.config.log_port = log_port; + changed = true; + } + if changed { + self.config.version += 1; + *self.shared_config.lock().unwrap() = self.config.clone(); + self.logs.push_back(LogEntry { + time: Local::now().format("%H:%M:%S").to_string(), + level: "GUI_INFO".to_string(), + message: format!("Config updated from server: UDP={}, LOG={}", self.config.udp_port, self.config.log_port), + }); + } + } InternalEvent::Disconnected => { self.connected = false; } @@ -1025,6 +1093,36 @@ impl eframe::App for MarteDebugApp { } } + if let Some(dialog) = &mut self.monitoring_dialog { + let mut close = false; + egui::Window::new("Monitor Signal").show(ctx, |ui| { + ui.label(&dialog.signal_path); + ui.horizontal(|ui| { + ui.label("Period (ms):"); + ui.text_edit_singleline(&mut dialog.period_ms); + }); + ui.horizontal(|ui| { + if ui.button("Apply").clicked() { + let period = dialog.period_ms.parse::().unwrap_or(100); + let _ = self + .tx_cmd + .send(format!("MONITOR SIGNAL {} {}", dialog.signal_path, period)); + let _ = self.tx_cmd.send("DISCOVER".to_string()); + let _ = self + .internal_tx + .send(InternalEvent::TraceRequested(dialog.signal_path.clone(), true)); + close = true; + } + if ui.button("Cancel").clicked() { + close = true; + } + }); + }); + if close { + self.monitoring_dialog = None; + } + } + if let Some((p_idx, s_idx)) = self.style_editor { let mut close = false; egui::Window::new("Signal Style").show(ctx, |ui| { @@ -1205,11 +1303,11 @@ impl eframe::App for MarteDebugApp { ui.label("Control:"); ui.text_edit_singleline(&mut self.config.tcp_port); ui.end_row(); - ui.label("Telemetry:"); - ui.text_edit_singleline(&mut self.config.udp_port); + ui.label("Telemetry (Auto):"); + ui.label(&self.config.udp_port); ui.end_row(); - ui.label("Logs:"); - ui.text_edit_singleline(&mut self.config.log_port); + ui.label("Logs (Auto):"); + ui.label(&self.config.log_port); ui.end_row(); }); if ui.button("🔄 Apply").clicked() { @@ -1319,7 +1417,11 @@ impl eframe::App for MarteDebugApp { } }); if ui.button("❌").clicked() { - let _ = self.tx_cmd.send(format!("TRACE {} 0", key)); + if entry.is_monitored { + let _ = self.tx_cmd.send(format!("UNMONITOR SIGNAL {}", key)); + } else { + let _ = self.tx_cmd.send(format!("TRACE {} 0", key)); + } let _ = self .internal_tx .send(InternalEvent::ClearTrace(key.clone()));