Implemented history writer

This commit is contained in:
Martino Ferrari
2026-06-14 14:09:51 +02:00
parent f25bd7f08e
commit dd7dd22cb0
27 changed files with 1537 additions and 104 deletions
@@ -0,0 +1,176 @@
/**
* @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_ */