Files
2026-06-14 14:09:51 +02:00

177 lines
5.9 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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_ */