Add Auto publishing mode to UDPStreamer; optimise WebUI hub
UDPStreamer: - Add PublishingMode = "Strict" | "Auto" config parameter - Add MinRefreshRate (Hz) for Auto mode; uses HRT phase-locked tick counting to rate-limit sends without accumulation buffers - Fix high-frequency integration test configs to use newline-separated key-value pairs (MARTe2 StandardParser does not treat ';' as delimiter) - Add 3 new unit tests (AutoMode_Valid, AutoMode_MissingRefreshRate, UnknownPublishingMode); all 33 tests passing WebUI hub: - Skip ring-buffer LTTB writes when zoom has not been accessed in 10 s, reducing idle CPU usage - Use unsafe float64→bytes reinterpretation to eliminate per-element encoding overhead in the hot broadcast path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -101,20 +101,23 @@ UDPStreamer::UDPStreamer() :
|
||||
MemoryDataSourceI(),
|
||||
EmbeddedServiceMethodBinderI(),
|
||||
executor(*this) {
|
||||
port = UDPS_DEFAULT_PORT;
|
||||
maxPayloadSize = UDPS_DEFAULT_MAX_PAYLOAD;
|
||||
cpuMask = 0xFFFFFFFFu;
|
||||
stackSize = THREADS_DEFAULT_STACKSIZE;
|
||||
numSigs = 0u;
|
||||
signalInfos = NULL_PTR(UDPStreamerSignalInfo *);
|
||||
readyBuffer = NULL_PTR(uint8 *);
|
||||
scratchBuffer = NULL_PTR(uint8 *);
|
||||
wireBuffer = NULL_PTR(uint8 *);
|
||||
totalSrcBytes = 0u;
|
||||
totalWireBytes = 0u;
|
||||
syncTimestamp = 0u;
|
||||
clientConnected = false;
|
||||
packetCounter = 0u;
|
||||
port = UDPS_DEFAULT_PORT;
|
||||
maxPayloadSize = UDPS_DEFAULT_MAX_PAYLOAD;
|
||||
cpuMask = 0xFFFFFFFFu;
|
||||
stackSize = THREADS_DEFAULT_STACKSIZE;
|
||||
publishMode = UDPStreamerPublishStrict;
|
||||
minRefreshRate = 0.0;
|
||||
flushPeriodTicks = 0u;
|
||||
numSigs = 0u;
|
||||
signalInfos = NULL_PTR(UDPStreamerSignalInfo *);
|
||||
readyBuffer = NULL_PTR(uint8 *);
|
||||
scratchBuffer = NULL_PTR(uint8 *);
|
||||
wireBuffer = NULL_PTR(uint8 *);
|
||||
totalSrcBytes = 0u;
|
||||
totalWireBytes = 0u;
|
||||
syncTimestamp = 0u;
|
||||
clientConnected = false;
|
||||
packetCounter = 0u;
|
||||
|
||||
if (!dataSem.Create()) {
|
||||
REPORT_ERROR(ErrorManagement::FatalError, "Could not create EventSem.");
|
||||
@@ -218,6 +221,40 @@ bool UDPStreamer::Initialise(StructuredDataI &data) {
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
StreamString publishStr = "";
|
||||
(void) data.Read("PublishingMode", publishStr);
|
||||
if ((publishStr.Size() == 0u) || (publishStr == "Strict")) {
|
||||
publishMode = UDPStreamerPublishStrict;
|
||||
}
|
||||
else if (publishStr == "Auto") {
|
||||
publishMode = UDPStreamerPublishAuto;
|
||||
}
|
||||
else {
|
||||
REPORT_ERROR(ErrorManagement::ParametersError,
|
||||
"Unknown PublishingMode '%s'. Allowed: Strict|Auto.",
|
||||
publishStr.Buffer());
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok && (publishMode == UDPStreamerPublishAuto)) {
|
||||
if (!data.Read("MinRefreshRate", minRefreshRate) || (minRefreshRate <= 0.0)) {
|
||||
REPORT_ERROR(ErrorManagement::ParametersError,
|
||||
"MinRefreshRate > 0 is required when PublishingMode = Auto.");
|
||||
ok = false;
|
||||
}
|
||||
else {
|
||||
/* Pre-compute the HRT tick count for one flush interval */
|
||||
float64 hrtFreq = static_cast<float64>(HighResolutionTimer::Frequency());
|
||||
flushPeriodTicks = static_cast<uint64>(hrtFreq / minRefreshRate);
|
||||
REPORT_ERROR(ErrorManagement::Information,
|
||||
"Auto mode: MinRefreshRate=%.1f Hz, flushPeriodTicks=%llu.",
|
||||
minRefreshRate,
|
||||
static_cast<unsigned long long>(flushPeriodTicks));
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -611,10 +648,17 @@ bool UDPStreamer::Synchronise() {
|
||||
ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
|
||||
ErrorManagement::ErrorType ret = ErrorManagement::NoError;
|
||||
|
||||
/* nextFlushTick: HRT counter target for the next Auto-mode flush.
|
||||
* Declared static so it persists across Execute() calls (the framework
|
||||
* calls Execute() in a tight loop for the MainStage). */
|
||||
static uint64 nextFlushTick = 0u;
|
||||
|
||||
if (info.GetStage() == ExecutionInfo::StartupStage) {
|
||||
nextFlushTick = HighResolutionTimer::Counter();
|
||||
REPORT_ERROR(ErrorManagement::Information,
|
||||
"UDPStreamer background thread started (port %u).",
|
||||
static_cast<uint32>(port));
|
||||
"UDPStreamer background thread started (port %u, mode %s).",
|
||||
static_cast<uint32>(port),
|
||||
(publishMode == UDPStreamerPublishAuto) ? "Auto" : "Strict");
|
||||
}
|
||||
|
||||
if (info.GetStage() == ExecutionInfo::MainStage) {
|
||||
@@ -646,21 +690,44 @@ ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
|
||||
}
|
||||
|
||||
if (dataReady && clientConnected) {
|
||||
/* Copy readyBuffer → scratchBuffer under brief spinlock */
|
||||
uint64 ts = 0u;
|
||||
bufMutex.FastLock(TTInfiniteWait);
|
||||
(void) MemoryOperationsHelper::Copy(scratchBuffer, readyBuffer, totalSrcBytes);
|
||||
ts = syncTimestamp;
|
||||
bufMutex.FastUnLock();
|
||||
/* In Auto mode, only flush when the HRT flush interval has elapsed.
|
||||
* This reduces send syscalls and network load by (rtHz / minRefreshRate)x
|
||||
* without any changes to the RT thread or the wire protocol.
|
||||
* The most-recent signal values (captured in readyBuffer) are used. */
|
||||
bool shouldSend = true;
|
||||
if (publishMode == UDPStreamerPublishAuto) {
|
||||
uint64 now = HighResolutionTimer::Counter();
|
||||
if (now < nextFlushTick) {
|
||||
shouldSend = false;
|
||||
}
|
||||
else {
|
||||
/* Advance deadline by one full period (keeps phase-locked). */
|
||||
nextFlushTick += flushPeriodTicks;
|
||||
/* Guard against clock drift: if we're already more than one
|
||||
* period behind, reset to avoid a burst of back-to-back sends. */
|
||||
if (nextFlushTick < now) {
|
||||
nextFlushTick = now + flushPeriodTicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Serialize signal data into wireBuffer */
|
||||
QuantizeAndSerialize(scratchBuffer, ts);
|
||||
if (shouldSend) {
|
||||
/* Copy readyBuffer → scratchBuffer under brief spinlock */
|
||||
uint64 ts = 0u;
|
||||
bufMutex.FastLock(TTInfiniteWait);
|
||||
(void) MemoryOperationsHelper::Copy(scratchBuffer, readyBuffer, totalSrcBytes);
|
||||
ts = syncTimestamp;
|
||||
bufMutex.FastUnLock();
|
||||
|
||||
/* Send (fragmented if needed) */
|
||||
packetCounter++;
|
||||
if (!SendFragmented(UDPS_TYPE_DATA, packetCounter, wireBuffer, totalWireBytes)) {
|
||||
REPORT_ERROR(ErrorManagement::Warning,
|
||||
"Failed to send DATA packet (counter=%u).", packetCounter);
|
||||
/* Serialize signal data into wireBuffer */
|
||||
QuantizeAndSerialize(scratchBuffer, ts);
|
||||
|
||||
/* Send (fragmented if needed) */
|
||||
packetCounter++;
|
||||
if (!SendFragmented(UDPS_TYPE_DATA, packetCounter, wireBuffer, totalWireBytes)) {
|
||||
REPORT_ERROR(ErrorManagement::Warning,
|
||||
"Failed to send DATA packet (counter=%u).", packetCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,18 @@
|
||||
|
||||
namespace MARTe {
|
||||
|
||||
/**
|
||||
* @brief Publishing mode for the background sender thread.
|
||||
*
|
||||
* - Strict: send one UDP packet on every Synchronise() call (legacy behaviour).
|
||||
* - Auto: buffer successive ticks and only flush when the HRT-based flush
|
||||
* interval has elapsed. Controlled by MinRefreshRate (Hz).
|
||||
*/
|
||||
typedef enum {
|
||||
UDPStreamerPublishStrict = 0u, /**< Send on every RT cycle (default) */
|
||||
UDPStreamerPublishAuto = 1u /**< Rate-limited: flush at MinRefreshRate Hz */
|
||||
} UDPStreamerPublishMode;
|
||||
|
||||
/**
|
||||
* @brief Quantization types for float signals.
|
||||
*/
|
||||
@@ -153,6 +165,8 @@ static const uint8 UDPS_TYPECODE_UNKNOWN = 255u;
|
||||
* MaxPayloadSize = 1400 // Optional (default 1400); max payload bytes per UDP datagram
|
||||
* CPUMask = 0x2 // Optional, affinity for background thread
|
||||
* StackSize = 1048576 // Optional, stack size for background thread
|
||||
* PublishingMode = "Auto" // Optional: "Strict" (default) | "Auto"
|
||||
* MinRefreshRate = 120 // Optional (Hz); only used when PublishingMode = "Auto"
|
||||
* Signals = {
|
||||
* Time = {
|
||||
* Type = uint64
|
||||
@@ -292,10 +306,13 @@ private:
|
||||
static uint8 TypeDescriptorToCode(TypeDescriptor td);
|
||||
|
||||
/* Configuration parameters */
|
||||
uint16 port; /**< UDP server port */
|
||||
uint32 maxPayloadSize; /**< Max payload bytes per UDP packet (excluding header) */
|
||||
uint32 cpuMask; /**< Background thread CPU affinity */
|
||||
uint32 stackSize; /**< Background thread stack size */
|
||||
uint16 port; /**< UDP server port */
|
||||
uint32 maxPayloadSize; /**< Max payload bytes per UDP packet (excluding header) */
|
||||
uint32 cpuMask; /**< Background thread CPU affinity */
|
||||
uint32 stackSize; /**< Background thread stack size */
|
||||
UDPStreamerPublishMode publishMode; /**< Strict or Auto publishing mode */
|
||||
float64 minRefreshRate; /**< Minimum flush rate (Hz) for Auto mode */
|
||||
uint64 flushPeriodTicks; /**< HRT ticks per flush interval (computed from minRefreshRate) */
|
||||
|
||||
/* Signal metadata */
|
||||
uint32 numSigs; /**< Number of signals */
|
||||
|
||||
Reference in New Issue
Block a user