543 lines
20 KiB
C++
543 lines
20 KiB
C++
/**
|
||
* @file HistoryWriter.cpp
|
||
* @brief Disk-backed circular history storage implementation.
|
||
*/
|
||
|
||
#include "HistoryWriter.h"
|
||
#include "AdvancedErrorManagement.h"
|
||
#include "UDPSProtocol.h"
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdarg.h>
|
||
#include <math.h>
|
||
#include <errno.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/statvfs.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
|
||
namespace StreamHub {
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Constructor / Destructor */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
HistoryWriter::HistoryWriter()
|
||
: scratchT_(static_cast<float64 *>(0)),
|
||
scratchV_(static_cast<float64 *>(0)),
|
||
files_(static_cast<SignalFile (*)[kHistMaxSignals]>(0)),
|
||
fileActive_(static_cast<bool (*)[kHistMaxSignals]>(0)),
|
||
enabled_(false),
|
||
durationHours_(1.0),
|
||
decimation_(1u),
|
||
flushIntervalSec_(5u),
|
||
minDiskFreeMB_(500u),
|
||
diskLow_(false) {
|
||
directory_[0] = '\0';
|
||
files_ = new SignalFile[kHistMaxSessions][kHistMaxSignals];
|
||
fileActive_ = new bool[kHistMaxSessions][kHistMaxSignals];
|
||
for (uint32 i = 0u; i < kHistMaxSessions; i++) {
|
||
for (uint32 s = 0u; s < kHistMaxSignals; s++) {
|
||
files_[i][s].fd = -1;
|
||
files_[i][s].capacity = 0u;
|
||
files_[i][s].head = 0u;
|
||
files_[i][s].count = 0u;
|
||
files_[i][s].tOldest = 0.0;
|
||
files_[i][s].tNewest = 0.0;
|
||
files_[i][s].readCursor = 0u;
|
||
files_[i][s].decimCounter = 0u;
|
||
files_[i][s].headerDirty = false;
|
||
files_[i][s].sourceId[0] = '\0';
|
||
files_[i][s].signalName[0] = '\0';
|
||
fileActive_[i][s] = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
HistoryWriter::~HistoryWriter() {
|
||
/* Flush and close all open files */
|
||
for (uint32 i = 0u; i < kHistMaxSessions; i++) {
|
||
for (uint32 s = 0u; s < kHistMaxSignals; s++) {
|
||
if (files_[i][s].fd >= 0) {
|
||
FlushHeader(files_[i][s]);
|
||
(void) close(files_[i][s].fd);
|
||
files_[i][s].fd = -1;
|
||
}
|
||
}
|
||
}
|
||
delete[] scratchT_;
|
||
delete[] scratchV_;
|
||
delete[] files_;
|
||
delete[] fileActive_;
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Initialise */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
bool HistoryWriter::Initialise(StructuredDataI &cfg) {
|
||
StreamString dir;
|
||
if (!cfg.Read("Directory", dir) || dir.Size() == 0u) {
|
||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Warning,
|
||
"HistoryWriter: no Directory specified, history disabled.");
|
||
return false;
|
||
}
|
||
strncpy(directory_, dir.Buffer(), sizeof(directory_) - 1u);
|
||
directory_[sizeof(directory_) - 1u] = '\0';
|
||
|
||
/* Remove trailing slash */
|
||
size_t dLen = strlen(directory_);
|
||
if (dLen > 1u && directory_[dLen - 1u] == '/') {
|
||
directory_[dLen - 1u] = '\0';
|
||
}
|
||
|
||
float64 durF = 1.0;
|
||
if (cfg.Read("DurationHours", durF)) {
|
||
durationHours_ = (durF > 0.0) ? durF : 1.0;
|
||
}
|
||
|
||
uint32 tmp = 0u;
|
||
if (cfg.Read("Decimation", tmp)) { decimation_ = (tmp > 0u) ? tmp : 1u; }
|
||
if (cfg.Read("FlushIntervalSec", tmp)) { flushIntervalSec_ = (tmp > 0u) ? tmp : 5u; }
|
||
if (cfg.Read("MinDiskFreeMB", tmp)) { minDiskFreeMB_ = tmp; }
|
||
|
||
/* Create root directory */
|
||
(void) mkdir(directory_, 0755);
|
||
|
||
/* Allocate scratch buffers */
|
||
scratchT_ = new float64[kReadScratch];
|
||
scratchV_ = new float64[kReadScratch];
|
||
|
||
enabled_ = true;
|
||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Information,
|
||
"HistoryWriter: enabled, dir=%s, duration=%.1fh, decimation=%u, flush=%us, minDisk=%uMB.",
|
||
directory_, durationHours_, decimation_, flushIntervalSec_, minDiskFreeMB_);
|
||
return true;
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* OnSourceConfigured */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
void HistoryWriter::OnSourceConfigured(uint32 sessionIdx, const char *sourceId,
|
||
UDPSourceSession &sess) {
|
||
if (!enabled_ || sessionIdx >= kHistMaxSessions) { return; }
|
||
|
||
const uint32 numSigs = sess.GetNumSignals();
|
||
for (uint32 s = 0u; s < numSigs && s < kHistMaxSignals; s++) {
|
||
MARTe::UDPSSignalDescriptor desc;
|
||
if (!sess.GetSignalDescriptor(s, desc)) { continue; }
|
||
|
||
/* Skip time-reference signals (uint64 type, typically TimeArray) */
|
||
if (desc.typeCode == MARTe::UDPS_TYPECODE_UINT64) { continue; }
|
||
|
||
float64 estRate = static_cast<float64>(desc.samplingRate);
|
||
if (estRate <= 0.0) { estRate = 1000.0; } /* fallback */
|
||
|
||
if (OpenSignalFile(sessionIdx, s, sourceId, desc.name, estRate)) {
|
||
fileActive_[sessionIdx][s] = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* OpenSignalFile */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
bool HistoryWriter::OpenSignalFile(uint32 sessionIdx, uint32 sigIdx,
|
||
const char *sourceId, const char *signalName,
|
||
float64 estimatedRateHz) {
|
||
/* Create source subdirectory */
|
||
char srcDir[640];
|
||
snprintf(srcDir, sizeof(srcDir), "%s/%s", directory_, sourceId);
|
||
(void) mkdir(srcDir, 0755);
|
||
|
||
char filePath[768];
|
||
snprintf(filePath, sizeof(filePath), "%s/%s.shist", srcDir, signalName);
|
||
|
||
/* Calculate capacity: duration × rate / decimation */
|
||
float64 totalSamples = durationHours_ * 3600.0 * estimatedRateHz
|
||
/ static_cast<float64>(decimation_);
|
||
uint32 capacity = static_cast<uint32>(ceil(totalSamples));
|
||
if (capacity < 1000u) { capacity = 1000u; }
|
||
|
||
SignalFile &sf = files_[sessionIdx][sigIdx];
|
||
|
||
/* Try to reopen an existing file */
|
||
int fd = open(filePath, O_RDWR);
|
||
if (fd >= 0) {
|
||
uint8 hdr[kHistHeaderSize];
|
||
ssize_t nr = pread(fd, hdr, kHistHeaderSize, 0);
|
||
if (nr == static_cast<ssize_t>(kHistHeaderSize) &&
|
||
memcmp(hdr, kHistMagic, 4) == 0) {
|
||
/* Read existing header */
|
||
uint32 ver = 0u, fileCap = 0u;
|
||
memcpy(&ver, &hdr[4], 4);
|
||
memcpy(&fileCap, &hdr[8], 4);
|
||
if (ver == 1u && fileCap == capacity) {
|
||
/* Reuse existing file */
|
||
memcpy(&sf.head, &hdr[12], 4);
|
||
memcpy(&sf.count, &hdr[16], 4);
|
||
memcpy(&sf.tOldest, &hdr[24], 8);
|
||
memcpy(&sf.tNewest, &hdr[32], 8);
|
||
sf.fd = fd;
|
||
sf.capacity = fileCap;
|
||
sf.decimCounter = 0u;
|
||
sf.readCursor = 0u;
|
||
sf.headerDirty = false;
|
||
strncpy(sf.sourceId, sourceId, sizeof(sf.sourceId) - 1u);
|
||
sf.sourceId[sizeof(sf.sourceId) - 1u] = '\0';
|
||
strncpy(sf.signalName, signalName, sizeof(sf.signalName) - 1u);
|
||
sf.signalName[sizeof(sf.signalName) - 1u] = '\0';
|
||
|
||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Information,
|
||
"HistoryWriter: reopened %s (cap=%u, count=%u).",
|
||
filePath, fileCap, sf.count);
|
||
return true;
|
||
}
|
||
}
|
||
/* Capacity changed or corrupt — recreate */
|
||
(void) close(fd);
|
||
fd = -1;
|
||
}
|
||
|
||
/* Create new file */
|
||
fd = open(filePath, O_RDWR | O_CREAT | O_TRUNC, 0644);
|
||
if (fd < 0) {
|
||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Warning,
|
||
"HistoryWriter: cannot create %s: %s", filePath, strerror(errno));
|
||
return false;
|
||
}
|
||
|
||
/* Pre-allocate: header + capacity × 16 bytes */
|
||
off_t fileSize = static_cast<off_t>(kHistHeaderSize)
|
||
+ static_cast<off_t>(capacity) * 16;
|
||
if (ftruncate(fd, fileSize) != 0) {
|
||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Warning,
|
||
"HistoryWriter: ftruncate %s failed: %s", filePath, strerror(errno));
|
||
(void) close(fd);
|
||
return false;
|
||
}
|
||
|
||
/* Write header */
|
||
uint8 hdr[kHistHeaderSize];
|
||
memset(hdr, 0, kHistHeaderSize);
|
||
memcpy(&hdr[0], kHistMagic, 4);
|
||
uint32 ver = 1u;
|
||
memcpy(&hdr[4], &ver, 4);
|
||
memcpy(&hdr[8], &capacity, 4);
|
||
/* head=0, count=0, decimation, tOldest=0, tNewest=0 */
|
||
memcpy(&hdr[20], &decimation_, 4);
|
||
(void) pwrite(fd, hdr, kHistHeaderSize, 0);
|
||
|
||
sf.fd = fd;
|
||
sf.capacity = capacity;
|
||
sf.head = 0u;
|
||
sf.count = 0u;
|
||
sf.tOldest = 0.0;
|
||
sf.tNewest = 0.0;
|
||
sf.readCursor = 0u;
|
||
sf.decimCounter = 0u;
|
||
sf.headerDirty = false;
|
||
strncpy(sf.sourceId, sourceId, sizeof(sf.sourceId) - 1u);
|
||
sf.sourceId[sizeof(sf.sourceId) - 1u] = '\0';
|
||
strncpy(sf.signalName, signalName, sizeof(sf.signalName) - 1u);
|
||
sf.signalName[sizeof(sf.signalName) - 1u] = '\0';
|
||
|
||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Information,
|
||
"HistoryWriter: created %s (cap=%u, %.1f MB).",
|
||
filePath, capacity,
|
||
static_cast<double>(fileSize) / (1024.0 * 1024.0));
|
||
return true;
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* WriteTick */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
void HistoryWriter::WriteTick(uint32 sessionIdx, UDPSourceSession &sess) {
|
||
if (!enabled_ || sessionIdx >= kHistMaxSessions || diskLow_) { return; }
|
||
|
||
const uint32 numSigs = sess.GetNumSignals();
|
||
for (uint32 s = 0u; s < numSigs && s < kHistMaxSignals; s++) {
|
||
if (!fileActive_[sessionIdx][s]) { continue; }
|
||
SignalFile &sf = files_[sessionIdx][s];
|
||
if (sf.fd < 0) { continue; }
|
||
|
||
/* Read new samples from the in-memory ring since last tick */
|
||
uint32 nRaw = sess.ReadSignalSince(s, sf.readCursor,
|
||
scratchT_, scratchV_, kReadScratch);
|
||
if (nRaw == 0u) { continue; }
|
||
|
||
if (decimation_ <= 1u) {
|
||
WritePairs(sf, scratchT_, scratchV_, nRaw);
|
||
} else {
|
||
/* Apply decimation: take every Nth sample */
|
||
uint32 nOut = 0u;
|
||
for (uint32 i = 0u; i < nRaw; i++) {
|
||
sf.decimCounter++;
|
||
if (sf.decimCounter >= static_cast<uint64>(decimation_)) {
|
||
scratchT_[nOut] = scratchT_[i];
|
||
scratchV_[nOut] = scratchV_[i];
|
||
nOut++;
|
||
sf.decimCounter = 0u;
|
||
}
|
||
}
|
||
if (nOut > 0u) {
|
||
WritePairs(sf, scratchT_, scratchV_, nOut);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* WritePairs */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
void HistoryWriter::WritePairs(SignalFile &sf, const float64 *t,
|
||
const float64 *v, uint32 n) {
|
||
for (uint32 i = 0u; i < n; i++) {
|
||
/* Write one (t,v) pair at the current head position */
|
||
off_t offset = static_cast<off_t>(kHistHeaderSize)
|
||
+ static_cast<off_t>(sf.head) * 16;
|
||
float64 pair[2] = { t[i], v[i] };
|
||
(void) pwrite(sf.fd, pair, 16, offset);
|
||
|
||
sf.head = (sf.head + 1u) % sf.capacity;
|
||
if (sf.count < sf.capacity) {
|
||
sf.count++;
|
||
}
|
||
}
|
||
|
||
/* Update time bounds */
|
||
sf.tNewest = t[n - 1u];
|
||
if (sf.count <= n) {
|
||
sf.tOldest = t[0];
|
||
} else {
|
||
/* Read the oldest entry's time from disk */
|
||
uint32 oldestIdx = (sf.head + sf.capacity - sf.count) % sf.capacity;
|
||
off_t offset = static_cast<off_t>(kHistHeaderSize)
|
||
+ static_cast<off_t>(oldestIdx) * 16;
|
||
float64 oldestT = 0.0;
|
||
(void) pread(sf.fd, &oldestT, 8, offset);
|
||
sf.tOldest = oldestT;
|
||
}
|
||
sf.headerDirty = true;
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* FlushHeaders */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
void HistoryWriter::FlushHeaders() {
|
||
if (!enabled_) { return; }
|
||
|
||
/* Periodic disk space check */
|
||
diskLow_ = !HasSufficientDisk();
|
||
if (diskLow_) {
|
||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Warning,
|
||
"HistoryWriter: disk space below %u MB, writing paused.", minDiskFreeMB_);
|
||
}
|
||
|
||
for (uint32 i = 0u; i < kHistMaxSessions; i++) {
|
||
for (uint32 s = 0u; s < kHistMaxSignals; s++) {
|
||
if (!fileActive_[i][s]) { continue; }
|
||
SignalFile &sf = files_[i][s];
|
||
if (sf.fd >= 0 && sf.headerDirty) {
|
||
FlushHeader(sf);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void HistoryWriter::FlushHeader(SignalFile &sf) {
|
||
uint8 hdr[kHistHeaderSize];
|
||
memset(hdr, 0, kHistHeaderSize);
|
||
memcpy(&hdr[0], kHistMagic, 4);
|
||
uint32 ver = 1u;
|
||
memcpy(&hdr[4], &ver, 4);
|
||
memcpy(&hdr[8], &sf.capacity, 4);
|
||
memcpy(&hdr[12], &sf.head, 4);
|
||
memcpy(&hdr[16], &sf.count, 4);
|
||
memcpy(&hdr[20], &decimation_, 4);
|
||
memcpy(&hdr[24], &sf.tOldest, 8);
|
||
memcpy(&hdr[32], &sf.tNewest, 8);
|
||
(void) pwrite(sf.fd, hdr, kHistHeaderSize, 0);
|
||
(void) fdatasync(sf.fd);
|
||
sf.headerDirty = false;
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* ReadRange */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
uint32 HistoryWriter::ReadRange(const char *sourceId, const char *signalName,
|
||
float64 t0, float64 t1,
|
||
float64 *tOut, float64 *vOut,
|
||
uint32 maxOut) const {
|
||
const SignalFile *sf = FindFile(sourceId, signalName);
|
||
if (sf == static_cast<const SignalFile *>(0) || sf->fd < 0 ||
|
||
sf->count == 0u || t1 < t0) {
|
||
return 0u;
|
||
}
|
||
|
||
const uint32 fileCap = sf->capacity;
|
||
const uint32 cnt = sf->count;
|
||
const uint32 oldest = (sf->head + fileCap - cnt) % fileCap;
|
||
const int fd = sf->fd;
|
||
|
||
/* Binary search over the logical order (same algorithm as SignalRingBuffer).
|
||
* We read individual timestamps from disk using pread. */
|
||
|
||
/* lo = first logical index with t >= t0 */
|
||
uint32 lo = 0u;
|
||
{
|
||
uint32 a = 0u, b = cnt;
|
||
while (a < b) {
|
||
const uint32 mid = a + ((b - a) >> 1);
|
||
const uint32 phys = (oldest + mid) % fileCap;
|
||
float64 tv = 0.0;
|
||
(void) pread(fd, &tv, 8,
|
||
static_cast<off_t>(kHistHeaderSize) + static_cast<off_t>(phys) * 16);
|
||
if (tv < t0) { a = mid + 1u; } else { b = mid; }
|
||
}
|
||
lo = a;
|
||
}
|
||
/* hi = first logical index with t > t1 */
|
||
uint32 hi = lo;
|
||
{
|
||
uint32 a = lo, b = cnt;
|
||
while (a < b) {
|
||
const uint32 mid = a + ((b - a) >> 1);
|
||
const uint32 phys = (oldest + mid) % fileCap;
|
||
float64 tv = 0.0;
|
||
(void) pread(fd, &tv, 8,
|
||
static_cast<off_t>(kHistHeaderSize) + static_cast<off_t>(phys) * 16);
|
||
if (tv <= t1) { a = mid + 1u; } else { b = mid; }
|
||
}
|
||
hi = a;
|
||
}
|
||
|
||
uint32 nOut = hi - lo;
|
||
if (nOut > maxOut) { nOut = maxOut; }
|
||
|
||
/* Read pairs from disk */
|
||
for (uint32 i = 0u; i < nOut; i++) {
|
||
uint32 physIdx = (oldest + lo + i) % fileCap;
|
||
off_t fileOff = static_cast<off_t>(kHistHeaderSize)
|
||
+ static_cast<off_t>(physIdx) * 16;
|
||
float64 pair[2];
|
||
(void) pread(fd, pair, 16, fileOff);
|
||
tOut[i] = pair[0];
|
||
vOut[i] = pair[1];
|
||
}
|
||
|
||
return nOut;
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* GetTimeRange */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
bool HistoryWriter::GetTimeRange(const char *sourceId, const char *signalName,
|
||
float64 &t0, float64 &t1) const {
|
||
const SignalFile *sf = FindFile(sourceId, signalName);
|
||
if (sf == static_cast<const SignalFile *>(0) || sf->count == 0u) {
|
||
return false;
|
||
}
|
||
t0 = sf->tOldest;
|
||
t1 = sf->tNewest;
|
||
return true;
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* AppendInfoJSON */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/* Forward-declare JsonAppendf from StreamHub.cpp — we use the same pattern */
|
||
static bool HistJsonAppendf(char *&buf, uint32 &len, uint32 &cap,
|
||
const char *fmt, ...) {
|
||
for (;;) {
|
||
va_list ap;
|
||
va_start(ap, fmt);
|
||
const int wrote = vsnprintf(buf + len, cap - len, fmt, ap);
|
||
va_end(ap);
|
||
if (wrote < 0) { return false; }
|
||
if (static_cast<uint32>(wrote) < (cap - len)) {
|
||
len += static_cast<uint32>(wrote);
|
||
return true;
|
||
}
|
||
uint32 newCap = cap * 2u;
|
||
while ((newCap - len) <= static_cast<uint32>(wrote)) {
|
||
newCap *= 2u;
|
||
}
|
||
char *nb = new char[newCap];
|
||
memcpy(nb, buf, len);
|
||
delete[] buf;
|
||
buf = nb;
|
||
cap = newCap;
|
||
}
|
||
}
|
||
|
||
void HistoryWriter::AppendInfoJSON(char *&buf, uint32 &off, uint32 &cap) const {
|
||
HistJsonAppendf(buf, off, cap,
|
||
"\"enabled\":%s,\"durationHours\":%.2f,\"decimation\":%u,\"signals\":{",
|
||
enabled_ ? "true" : "false", durationHours_, decimation_);
|
||
|
||
bool first = true;
|
||
for (uint32 i = 0u; i < kHistMaxSessions; i++) {
|
||
for (uint32 s = 0u; s < kHistMaxSignals; s++) {
|
||
if (!fileActive_[i][s]) { continue; }
|
||
const SignalFile &sf = files_[i][s];
|
||
/* Report even when count==0 — the file exists but hasn't received
|
||
* data yet (or was just created). Clients need the entry to enable
|
||
* history UI; t0/t1 == 0 signals "still priming". */
|
||
HistJsonAppendf(buf, off, cap,
|
||
"%s\"%s:%s\":{\"t0\":%.17g,\"t1\":%.17g,\"count\":%u,\"capacity\":%u}",
|
||
(first ? "" : ","),
|
||
sf.sourceId, sf.signalName,
|
||
sf.tOldest, sf.tNewest, sf.count, sf.capacity);
|
||
first = false;
|
||
}
|
||
}
|
||
HistJsonAppendf(buf, off, cap, "}");
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* HasSufficientDisk */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
bool HistoryWriter::HasSufficientDisk() const {
|
||
if (minDiskFreeMB_ == 0u) { return true; }
|
||
|
||
struct statvfs st;
|
||
if (statvfs(directory_, &st) != 0) { return true; /* assume OK */ }
|
||
|
||
uint64 freeMB = (static_cast<uint64>(st.f_bavail) * st.f_frsize)
|
||
/ (1024u * 1024u);
|
||
return freeMB >= static_cast<uint64>(minDiskFreeMB_);
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* FindFile */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
const HistoryWriter::SignalFile* HistoryWriter::FindFile(
|
||
const char *sourceId, const char *signalName) const {
|
||
for (uint32 i = 0u; i < kHistMaxSessions; i++) {
|
||
for (uint32 s = 0u; s < kHistMaxSignals; s++) {
|
||
if (!fileActive_[i][s]) { continue; }
|
||
const SignalFile &sf = files_[i][s];
|
||
if (strcmp(sf.sourceId, sourceId) == 0 &&
|
||
strcmp(sf.signalName, signalName) == 0) {
|
||
return &sf;
|
||
}
|
||
}
|
||
}
|
||
return static_cast<const SignalFile *>(0);
|
||
}
|
||
|
||
} /* namespace StreamHub */
|