/** * @file HistoryWriter.h * @brief Disk-backed circular history storage for StreamHub signals. * * Each signal is stored in a separate binary file (.shist) with a fixed header * and a pre-allocated circular data region of (float64 time, float64 value) pairs. * The file size is determined at creation and never grows. * * File layout (per signal): * Offset Size Field * 0 4 Magic: "SHR1" * 4 4 uint32 version (1) * 8 4 uint32 capacity (max pairs) * 12 4 uint32 head (next write position, 0-based, wraps) * 16 4 uint32 count (valid entries, <= capacity) * 20 4 uint32 decimation * 24 8 float64 tOldest * 32 8 float64 tNewest * 40 24 reserved (pad to 64 bytes) * 64 ... data: capacity × 16 bytes (8 time + 8 value) * * Thread safety: WriteTick() is called from the push loop only. * ReadRange() uses pread() and is safe for concurrent reads from WS threads. */ #ifndef STREAMHUB_HISTORY_WRITER_H_ #define STREAMHUB_HISTORY_WRITER_H_ #include "CompilerTypes.h" #include "StreamString.h" #include "StructuredDataI.h" #include "UDPSourceSession.h" namespace StreamHub { using MARTe::uint8; using MARTe::uint16; using MARTe::uint32; using MARTe::uint64; using MARTe::float64; using MARTe::StreamString; using MARTe::StructuredDataI; /** Maximum number of sessions (matches kMaxSessions). */ static const uint32 kHistMaxSessions = 32u; /** Header size in bytes. */ static const uint32 kHistHeaderSize = 64u; /** Magic bytes. */ static const char kHistMagic[4] = {'S','H','R','1'}; class HistoryWriter { public: HistoryWriter(); ~HistoryWriter(); /** * @brief Parse +History block from StreamHub config. * * Expected keys inside a +History node: * Directory (string, required) * DurationHours (float64, default 1) * Decimation (uint32, default 1) * FlushIntervalSec (uint32, default 5) * MinDiskFreeMB (uint32, default 500) */ bool Initialise(StructuredDataI &cfg); /** @return true if history storage is enabled and initialised. */ bool IsEnabled() const { return enabled_; } /** * @brief Called when a source becomes configured. * Creates / reopens per-signal .shist files. */ void OnSourceConfigured(uint32 sessionIdx, const char *sourceId, UDPSourceSession &sess); /** * @brief Called from the push loop to write new samples for one session. * Uses per-signal ReadSince cursors. Applies decimation. */ void WriteTick(uint32 sessionIdx, UDPSourceSession &sess); /** * @brief Flush in-memory header state to disk (periodic). */ void FlushHeaders(); /** * @brief Read a time range for one signal from disk. * @return Number of (t,v) pairs written to tOut/vOut. */ uint32 ReadRange(const char *sourceId, const char *signalName, float64 t0, float64 t1, float64 *tOut, float64 *vOut, uint32 maxOut) const; /** * @brief Get the time range stored on disk for a signal. * @return true if the signal has history data. */ bool GetTimeRange(const char *sourceId, const char *signalName, float64 &t0, float64 &t1) const; /** * @brief Build a JSON fragment for historyInfo response. * Writes into buf at offset, growing via the provided append function. */ void AppendInfoJSON(char *&buf, uint32 &off, uint32 &cap) const; /** @brief Check available disk space. */ bool HasSufficientDisk() const; /** @brief Duration in hours. */ float64 DurationHours() const { return durationHours_; } /** @brief Decimation factor. */ uint32 Decimation() const { return decimation_; } private: struct SignalFile { int fd; /**< File descriptor (-1 = not open) */ uint32 capacity; /**< Total capacity in pairs */ uint32 head; /**< Next write position */ uint32 count; /**< Valid entries */ float64 tOldest; float64 tNewest; uint64 readCursor; /**< For ReadSince from ring buffer */ uint64 decimCounter; /**< Decimation sample counter */ bool headerDirty; /**< True if header needs flushing */ char sourceId[64]; /**< Source ID for lookup */ char signalName[64]; /**< Signal name for lookup */ }; /** Per-signal scratch for reading from rings. */ static const uint32 kReadScratch = 65536u; float64 *scratchT_; float64 *scratchV_; /** Maximum signals tracked per session for history. */ static const uint32 kHistMaxSignals = 64u; /** Heap-allocated to keep StreamHub object size manageable. */ SignalFile (*files_)[kHistMaxSignals]; /**< [kHistMaxSessions][kHistMaxSignals] */ bool (*fileActive_)[kHistMaxSignals]; /**< [kHistMaxSessions][kHistMaxSignals] */ bool enabled_; char directory_[512]; float64 durationHours_; uint32 decimation_; uint32 flushIntervalSec_; uint32 minDiskFreeMB_; bool diskLow_; /**< True when disk space is below threshold */ /** Create or reopen a .shist file for one signal. */ bool OpenSignalFile(uint32 sessionIdx, uint32 sigIdx, const char *sourceId, const char *signalName, float64 estimatedRateHz); /** Write a batch of (t,v) pairs to a signal file. */ void WritePairs(SignalFile &sf, const float64 *t, const float64 *v, uint32 n); /** Flush one signal file header to disk. */ void FlushHeader(SignalFile &sf); /** Find a signal file by sourceId:signalName. */ const SignalFile* FindFile(const char *sourceId, const char *signalName) const; }; } /* namespace StreamHub */ #endif /* STREAMHUB_HISTORY_WRITER_H_ */