/** * @file BinaryRecorder.cpp * @brief Per-source binary recorder implementation. * * @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and * the Development of Fusion Energy ('Fusion for Energy'). * Licensed under the EUPL, Version 1.1 or - as soon they will be approved * by the European Commission - subsequent versions of the EUPL (the "Licence") * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl * * @warning Unless required by applicable law or agreed to in writing, * software distributed under the Licence is distributed on an "AS IS" * basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the Licence permissions and limitations under the Licence. */ #include "BinaryRecorder.h" #include "AdvancedErrorManagement.h" #include "TypeDescriptor.h" #include #include #include #include #include #include #include #include #include #include namespace StreamHub { /*---------------------------------------------------------------------------*/ /* Static helpers */ /*---------------------------------------------------------------------------*/ uint16 BinaryRecorder::TypeCodeToDescriptorAll(uint8 udpsTypeCode) { switch (udpsTypeCode) { case MARTe::UDPS_TYPECODE_UINT8: return MARTe::UnsignedInteger8Bit.all; case MARTe::UDPS_TYPECODE_INT8: return MARTe::SignedInteger8Bit.all; case MARTe::UDPS_TYPECODE_UINT16: return MARTe::UnsignedInteger16Bit.all; case MARTe::UDPS_TYPECODE_INT16: return MARTe::SignedInteger16Bit.all; case MARTe::UDPS_TYPECODE_UINT32: return MARTe::UnsignedInteger32Bit.all; case MARTe::UDPS_TYPECODE_INT32: return MARTe::SignedInteger32Bit.all; case MARTe::UDPS_TYPECODE_UINT64: return MARTe::UnsignedInteger64Bit.all; case MARTe::UDPS_TYPECODE_INT64: return MARTe::SignedInteger64Bit.all; case MARTe::UDPS_TYPECODE_FLOAT32: return MARTe::Float32Bit.all; case MARTe::UDPS_TYPECODE_FLOAT64: return MARTe::Float64Bit.all; default: return MARTe::Float64Bit.all; } } uint32 BinaryRecorder::EncodeNative(uint8 typeCode, float64 v, uint8 *dst) { switch (typeCode) { case MARTe::UDPS_TYPECODE_UINT8: { uint8 x = static_cast(v); memcpy(dst, &x, 1u); return 1u; } case MARTe::UDPS_TYPECODE_INT8: { MARTe::int8 x = static_cast(v); memcpy(dst, &x, 1u); return 1u; } case MARTe::UDPS_TYPECODE_UINT16: { uint16 x = static_cast(v); memcpy(dst, &x, 2u); return 2u; } case MARTe::UDPS_TYPECODE_INT16: { MARTe::int16 x = static_cast(v); memcpy(dst, &x, 2u); return 2u; } case MARTe::UDPS_TYPECODE_UINT32: { uint32 x = static_cast(v); memcpy(dst, &x, 4u); return 4u; } case MARTe::UDPS_TYPECODE_INT32: { MARTe::int32 x = static_cast(v); memcpy(dst, &x, 4u); return 4u; } case MARTe::UDPS_TYPECODE_UINT64: { uint64 x = static_cast(v); memcpy(dst, &x, 8u); return 8u; } case MARTe::UDPS_TYPECODE_INT64: { MARTe::int64 x = static_cast(v); memcpy(dst, &x, 8u); return 8u; } case MARTe::UDPS_TYPECODE_FLOAT32: { float x = static_cast(v); memcpy(dst, &x, 4u); return 4u; } case MARTe::UDPS_TYPECODE_FLOAT64: { float64 x = v; memcpy(dst, &x, 8u); return 8u; } default: return 0u; } } /** Local dequantization mirror of UDPSourceSession::DequantizeValue. */ static float64 RecDequantize(float64 q, uint8 quantType, float64 rangeMin, float64 rangeMax) { const float64 range = rangeMax - rangeMin; switch (quantType) { case MARTe::UDPS_QUANT_UINT8: return rangeMin + (q / 255.0) * range; case MARTe::UDPS_QUANT_INT8: return rangeMin + ((q + 127.0) / 254.0) * range; case MARTe::UDPS_QUANT_UINT16: return rangeMin + (q / 65535.0) * range; case MARTe::UDPS_QUANT_INT16: return rangeMin + ((q + 32767.0) / 65534.0) * range; default: return q; } } /** Read one quantized wire element as a float64 integer value. */ static float64 RecDecodeQuantWire(const uint8 *ptr, uint8 quantType) { switch (quantType) { case MARTe::UDPS_QUANT_UINT8: { uint8 v = 0u; memcpy(&v, ptr, 1u); return static_cast(v); } case MARTe::UDPS_QUANT_INT8: { MARTe::int8 v = 0; memcpy(&v, ptr, 1u); return static_cast(v); } case MARTe::UDPS_QUANT_UINT16: { uint16 v = 0u; memcpy(&v, ptr, 2u); return static_cast(v); } case MARTe::UDPS_QUANT_INT16: { MARTe::int16 v = 0; memcpy(&v, ptr, 2u); return static_cast(v); } default: return 0.0; } } static uint32 RecQuantWireBytes(uint8 quantType) { switch (quantType) { case MARTe::UDPS_QUANT_UINT8: return 1u; case MARTe::UDPS_QUANT_INT8: return 1u; case MARTe::UDPS_QUANT_UINT16: return 2u; case MARTe::UDPS_QUANT_INT16: return 2u; default: return 0u; } } /*---------------------------------------------------------------------------*/ /* Constructor / Destructor */ /*---------------------------------------------------------------------------*/ BinaryRecorder::BinaryRecorder() : nIncluded_(0u), rowBytes_(0u), headerLen_(0u), layoutReady_(false), pushHeaderLen_(0u), pushRowBytes_(0u), front_(static_cast(0)), back_(static_cast(0)), frontLen_(0u), backLen_(0u), pendingArm_(false), pendingDisarm_(false), pendingReopen_(false), fd_(-1), fileOffset_(0u), lastSyncSec_(0u), fileSeq_(0u), armed_(false), diskLow_(false), bytesWritten_(0u), rowsWritten_(0u), droppedRows_(0u) { memset(&config_, 0, sizeof(config_)); sourceId_[0] = '\0'; curFilePath_[0] = '\0'; stagingMutex_.Create(); } BinaryRecorder::~BinaryRecorder() { CloseFile(); if (front_ != static_cast(0)) { delete[] front_; front_ = static_cast(0); } if (back_ != static_cast(0)) { delete[] back_; back_ = static_cast(0); } } /*---------------------------------------------------------------------------*/ /* Init */ /*---------------------------------------------------------------------------*/ void BinaryRecorder::Init(const RecorderConfig &cfg, const char *sourceId) { config_ = cfg; if (config_.stagingBytes < 4096u) { config_.stagingBytes = 4096u; } strncpy(sourceId_, (sourceId != static_cast(0)) ? sourceId : "src", sizeof(sourceId_) - 1u); sourceId_[sizeof(sourceId_) - 1u] = '\0'; front_ = new uint8[config_.stagingBytes]; back_ = new uint8[config_.stagingBytes]; frontLen_ = 0u; backLen_ = 0u; /* Create output directory (best-effort). */ if (config_.directory[0] != '\0') { (void) mkdir(config_.directory, 0775); } if (config_.autoStart) { pendingArm_ = true; } } /*---------------------------------------------------------------------------*/ /* Configure (receive thread) */ /*---------------------------------------------------------------------------*/ void BinaryRecorder::Configure(const MARTe::UDPSSignalDescriptor *descs, uint32 nSigs, const bool *includeMask) { uint32 inc = 0u; for (uint32 s = 0u; (s < nSigs) && (inc < kRecMaxSig); s++) { if ((includeMask != static_cast(0)) && (!includeMask[s])) { continue; } const MARTe::UDPSSignalDescriptor &d = descs[s]; uint32 nElems = d.numRows * d.numCols; if (nElems == 0u) { nElems = 1u; } const uint32 native = MARTe::UDPSTypeCodeByteSize(d.typeCode); if (native == 0u) { continue; } const uint32 wire = (d.quantType != MARTe::UDPS_QUANT_NONE) ? RecQuantWireBytes(d.quantType) : native; srcIdx_[inc] = s; typeCode_[inc] = d.typeCode; quantType_[inc] = d.quantType; declaredElems_[inc] = nElems; nativeBytes_[inc] = native; wireBytes_[inc] = wire; rangeMin_[inc] = d.rangeMin; rangeMax_[inc] = d.rangeMax; inc++; } nIncluded_ = inc; /* Build the FileWriter header blob and compute the row size. */ uint32 pos = 0u; memcpy(&headerBlob_[pos], &nIncluded_, 4u); pos += 4u; uint32 row = 0u; for (uint32 i = 0u; i < nIncluded_; i++) { const uint16 tcAll = TypeCodeToDescriptorAll(typeCode_[i]); memcpy(&headerBlob_[pos], &tcAll, 2u); pos += 2u; /* 32-byte name, null-padded, truncated from the descriptor name. */ char name32[32]; memset(name32, 0, sizeof(name32)); strncpy(name32, descs[srcIdx_[i]].name, sizeof(name32) - 1u); memcpy(&headerBlob_[pos], name32, 32u); pos += 32u; memcpy(&headerBlob_[pos], &declaredElems_[i], 4u); pos += 4u; row += declaredElems_[i] * nativeBytes_[i]; } headerLen_ = pos; rowBytes_ = row; (void) stagingMutex_.FastLock(); layoutReady_ = true; pendingReopen_ = true; /* Discard any rows staged under the previous layout: they would belong * to the file being closed and must not be mixed into the new layout. */ frontLen_ = 0u; stagingMutex_.FastUnLock(); } /*---------------------------------------------------------------------------*/ /* CapturePacket (receive thread) */ /*---------------------------------------------------------------------------*/ void BinaryRecorder::CapturePacket(const uint8 *payload, const uint32 *sigOff, const uint32 *sigElems, uint8 publishMode, uint32 numSamples) { if (!layoutReady_ || (nIncluded_ == 0u) || (rowBytes_ == 0u)) { return; } const bool accumulate = (publishMode == MARTe::UDPS_PUBLISH_ACCUMULATE); uint32 numRows = accumulate ? numSamples : 1u; if (numRows == 0u) { numRows = 1u; } const uint32 rowSpan = numRows * rowBytes_; (void) stagingMutex_.FastLock(); if (frontLen_ + rowSpan > config_.stagingBytes) { droppedRows_ += static_cast(numRows); stagingMutex_.FastUnLock(); return; } for (uint32 r = 0u; r < numRows; r++) { for (uint32 i = 0u; i < nIncluded_; i++) { const uint32 s = srcIdx_[i]; const uint8 *base = payload + sigOff[s]; /* A per-sample (accumulated) scalar carries numSamples wire values; * a plain scalar carries one value that is repeated across rows. */ const bool isAccumScalar = accumulate && (declaredElems_[i] == 1u) && (sigElems[s] > 1u); const uint32 nElem = declaredElems_[i]; if (quantType_[i] == MARTe::UDPS_QUANT_NONE) { if (isAccumScalar) { /* Take wire element r for an accumulated scalar. */ memcpy(&front_[frontLen_], base + r * wireBytes_[i], nativeBytes_[i]); frontLen_ += nativeBytes_[i]; } else { /* Straight copy of all elements (wire == native size). */ const uint32 bytes = nElem * nativeBytes_[i]; memcpy(&front_[frontLen_], base, bytes); frontLen_ += bytes; } } else { /* Quantized: dequantize + re-encode per element. */ for (uint32 e = 0u; e < nElem; e++) { const uint32 wireIdx = isAccumScalar ? r : e; const float64 q = RecDecodeQuantWire( base + wireIdx * wireBytes_[i], quantType_[i]); const float64 phys = RecDequantize(q, quantType_[i], rangeMin_[i], rangeMax_[i]); frontLen_ += EncodeNative(typeCode_[i], phys, &front_[frontLen_]); } } } } stagingMutex_.FastUnLock(); } /*---------------------------------------------------------------------------*/ /* FlushTick (push thread) */ /*---------------------------------------------------------------------------*/ void BinaryRecorder::FlushTick(uint32 nowSec) { bool doArm = false; bool doDisarm = false; bool doReopen = false; (void) stagingMutex_.FastLock(); doArm = pendingArm_; pendingArm_ = false; doDisarm = pendingDisarm_; pendingDisarm_ = false; doReopen = pendingReopen_; pendingReopen_ = false; if (doReopen && layoutReady_) { memcpy(pushHeader_, headerBlob_, headerLen_); pushHeaderLen_ = headerLen_; pushRowBytes_ = rowBytes_; } /* Swap front -> back. */ uint8 *tmp = back_; back_ = front_; front_ = tmp; backLen_ = frontLen_; frontLen_ = 0u; stagingMutex_.FastUnLock(); if (doArm) { armed_ = true; } if (doReopen) { /* Layout changed: close the current file. The freshly swapped back * buffer holds new-layout rows and is written to the new file below. */ CloseFile(); } if (doDisarm) { if ((fd_ >= 0) && (backLen_ > 0u)) { (void) pwrite(fd_, back_, backLen_, static_cast(fileOffset_)); fileOffset_ += backLen_; bytesWritten_ += backLen_; } backLen_ = 0u; armed_ = false; CloseFile(); return; } if (!armed_) { backLen_ = 0u; return; } /* Disk-free guard. */ if (!HasSufficientDisk()) { if (!diskLow_) { diskLow_ = true; REPORT_ERROR_STATIC(MARTe::ErrorManagement::Warning, "BinaryRecorder: free disk below MinDiskFreeMB; recording stopped"); } armed_ = false; backLen_ = 0u; CloseFile(); return; } diskLow_ = false; /* Open a file if none is active. */ if ((fd_ < 0) && (pushHeaderLen_ > 0u)) { OpenNewFile(); } if (fd_ < 0) { backLen_ = 0u; return; } /* Flush staged rows. */ if (backLen_ > 0u) { const ssize_t w = pwrite(fd_, back_, backLen_, static_cast(fileOffset_)); if (w > 0) { fileOffset_ += static_cast(w); bytesWritten_ += static_cast(w); if (pushRowBytes_ > 0u) { rowsWritten_ += static_cast(w) / pushRowBytes_; } } backLen_ = 0u; } /* Rotate on size cap. */ if (fileOffset_ >= static_cast(config_.maxFileBytes)) { CloseFile(); OpenNewFile(); PruneOldFiles(); } /* Periodic durability. */ if ((fd_ >= 0) && ((nowSec - lastSyncSec_) >= config_.flushIntervalSec)) { (void) fdatasync(fd_); lastSyncSec_ = nowSec; } } /*---------------------------------------------------------------------------*/ /* File management (push thread) */ /*---------------------------------------------------------------------------*/ void BinaryRecorder::OpenNewFile() { struct timeval tv; (void) gettimeofday(&tv, static_cast(0)); struct tm tmv; time_t secs = static_cast(tv.tv_sec); (void) gmtime_r(&secs, &tmv); char stamp[32]; (void) strftime(stamp, sizeof(stamp), "%Y%m%dT%H%M%S", &tmv); fileSeq_++; (void) snprintf(curFilePath_, sizeof(curFilePath_), "%s/%s_%s_%08u.bin", config_.directory, sourceId_, stamp, fileSeq_); fd_ = open(curFilePath_, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (fd_ < 0) { REPORT_ERROR_STATIC(MARTe::ErrorManagement::Warning, "BinaryRecorder: failed to open recording file"); fileOffset_ = 0u; return; } (void) pwrite(fd_, pushHeader_, pushHeaderLen_, 0); fileOffset_ = pushHeaderLen_; } void BinaryRecorder::CloseFile() { if (fd_ >= 0) { (void) fdatasync(fd_); (void) close(fd_); fd_ = -1; } fileOffset_ = 0u; } void BinaryRecorder::PruneOldFiles() { DIR *dir = opendir(config_.directory); if (dir == static_cast(0)) { return; } char prefix[160]; (void) snprintf(prefix, sizeof(prefix), "%s_", sourceId_); const size_t prefixLen = strlen(prefix); static const uint32 kMaxList = 4096u; char (*names)[256] = new char[kMaxList][256]; uint32 count = 0u; struct dirent *ent; while (((ent = readdir(dir)) != static_cast(0)) && (count < kMaxList)) { const char *nm = ent->d_name; const size_t nlen = strlen(nm); if (nlen < prefixLen + 4u) { continue; } if (strncmp(nm, prefix, prefixLen) != 0) { continue; } if (strcmp(nm + (nlen - 4u), ".bin") != 0) { continue; } strncpy(names[count], nm, 255u); names[count][255] = '\0'; count++; } (void) closedir(dir); /* Sort ascending (lexicographic == chronological via timestamp+seq). */ for (uint32 a = 0u; a < count; a++) { for (uint32 b = a + 1u; b < count; b++) { if (strcmp(names[a], names[b]) > 0) { char t[256]; strncpy(t, names[a], 256u); strncpy(names[a], names[b], 256u); strncpy(names[b], t, 256u); } } } /* Unlink oldest until at most keepFiles remain. */ if (count > config_.keepFiles) { const uint32 toRemove = count - config_.keepFiles; for (uint32 i = 0u; i < toRemove; i++) { char full[768]; (void) snprintf(full, sizeof(full), "%s/%s", config_.directory, names[i]); (void) unlink(full); } } delete[] names; } bool BinaryRecorder::HasSufficientDisk() { if (config_.minDiskFreeMB == 0u) { return true; } struct statvfs st; if (statvfs(config_.directory, &st) != 0) { return true; } const uint64 freeBytes = static_cast(st.f_bavail) * static_cast(st.f_frsize); const uint64 freeMB = freeBytes / (1024u * 1024u); return freeMB >= static_cast(config_.minDiskFreeMB); } /*---------------------------------------------------------------------------*/ /* Control + info */ /*---------------------------------------------------------------------------*/ void BinaryRecorder::RequestArm() { (void) stagingMutex_.FastLock(); pendingArm_ = true; pendingDisarm_ = false; stagingMutex_.FastUnLock(); } void BinaryRecorder::RequestDisarm() { (void) stagingMutex_.FastLock(); pendingDisarm_ = true; pendingArm_ = false; stagingMutex_.FastUnLock(); } bool BinaryRecorder::IsEnabled() const { return config_.enabled; } void BinaryRecorder::GetInfo(bool &recording, char *file, uint32 fileSz, uint64 &bytesWritten, uint64 &rowsWritten, uint64 &droppedRows, uint64 &freeMB) const { recording = armed_ && (fd_ >= 0); if ((file != static_cast(0)) && (fileSz > 0u)) { strncpy(file, curFilePath_, fileSz - 1u); file[fileSz - 1u] = '\0'; } bytesWritten = bytesWritten_; rowsWritten = rowsWritten_; droppedRows = droppedRows_; freeMB = 0u; struct statvfs st; if (statvfs(config_.directory, &st) == 0) { const uint64 freeBytes = static_cast(st.f_bavail) * static_cast(st.f_frsize); freeMB = freeBytes / (1024u * 1024u); } } } /* namespace StreamHub */