177 lines
5.9 KiB
C++
177 lines
5.9 KiB
C++
/**
|
||
* @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_ */
|