GrabBag/AppUtils/AppCommon/Src/DebugDataSaver.cpp

302 lines
9.9 KiB
C++
Raw 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.

#include "DebugDataSaver.h"
#include "LaserDataLoader.h"
#include "VrLog.h"
#include "VrError.h"
#include <QCoreApplication>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QDateTime>
#include <QString>
#include <cstring>
namespace {
// 获取当前应用名(不含扩展名),用于构造 debug 子目录前缀
QString AppName()
{
QString name = QFileInfo(QCoreApplication::applicationFilePath()).baseName();
if (name.isEmpty()) name = QStringLiteral("app");
return name;
}
// 子目录前缀:{appName}_PointCloud_仅匹配此前缀的子目录会被统计/清理
QString SubDirPrefix()
{
return AppName() + QStringLiteral("_PointCloud_");
}
}
DebugDataSaver::DebugDataSaver(int maxThreads)
: m_maxThreads(std::max(1, std::min(maxThreads, 4)))
, m_running(true)
{
for (int i = 0; i < m_maxThreads; ++i) {
m_workers.emplace_back(&DebugDataSaver::WorkerThread, this);
}
LOG_INFO("[DebugDataSaver] 已启动 %d 个存储线程\n", m_maxThreads);
}
DebugDataSaver::~DebugDataSaver()
{
Stop();
}
void DebugDataSaver::Stop()
{
if (!m_running.exchange(false)) {
return;
}
m_queueCondition.notify_all();
for (auto& worker : m_workers) {
if (worker.joinable()) {
worker.join();
}
}
m_workers.clear();
// 释放队列中未处理的任务
while (!m_taskQueue.empty()) {
FreeData(m_taskQueue.front().data);
m_taskQueue.pop();
}
LOG_INFO("[DebugDataSaver] 已停止\n");
}
void DebugDataSaver::SaveAsync(const std::string& basePath, const std::string& timestamp, const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data, const std::string& deviceName)
{
if (data.empty() || !m_running) return;
SaveTask task;
task.deviceName = deviceName;
task.filePath = GenerateSavePath(basePath, timestamp, deviceName);
task.data = DeepCopyData(data);
// 在入队前清理超限文件,避免任务堆积时磁盘瞬间打满
EnsureStorageLimit(basePath);
{
std::lock_guard<std::mutex> lock(m_queueMutex);
m_taskQueue.push(std::move(task));
}
m_queueCondition.notify_one();
}
void DebugDataSaver::WorkerThread()
{
LaserDataLoader dataLoader;
while (true) {
SaveTask task;
{
std::unique_lock<std::mutex> lock(m_queueMutex);
m_queueCondition.wait(lock, [this] {
return !m_taskQueue.empty() || !m_running;
});
if (!m_running && m_taskQueue.empty()) {
break;
}
if (m_taskQueue.empty()) {
continue;
}
task = std::move(m_taskQueue.front());
m_taskQueue.pop();
}
int lineNum = static_cast<int>(task.data.size());
float scanSpeed = 0.0f;
int maxTimeStamp = 0;
int clockPerSecond = 0;
int result = dataLoader.SaveLaserScanData(task.filePath, task.data, lineNum, scanSpeed, maxTimeStamp, clockPerSecond);
if (result == SUCCESS) {
LOG_INFO("[DebugDataSaver] 保存成功: %s (%d行)\n", task.filePath.c_str(), lineNum);
} else {
LOG_ERROR("[DebugDataSaver] 保存失败: %s, 错误: %s\n", task.filePath.c_str(), dataLoader.GetLastError().c_str());
}
FreeData(task.data);
}
}
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>> DebugDataSaver::DeepCopyData(
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& src)
{
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>> dst;
dst.reserve(src.size());
for (const auto& item : src) {
EVzResultDataType dataType = item.first;
const SVzLaserLineData& srcLine = item.second;
SVzLaserLineData dstLine;
memset(&dstLine, 0, sizeof(SVzLaserLineData));
if (srcLine.nPointCount > 0) {
if (dataType == keResultDataType_Position) {
dstLine.p3DPoint = new SVzNL3DPosition[srcLine.nPointCount];
if (srcLine.p3DPoint) {
memcpy(dstLine.p3DPoint, srcLine.p3DPoint, sizeof(SVzNL3DPosition) * srcLine.nPointCount);
}
dstLine.p2DPoint = new SVzNL2DPosition[srcLine.nPointCount];
if (srcLine.p2DPoint) {
memcpy(dstLine.p2DPoint, srcLine.p2DPoint, sizeof(SVzNL2DPosition) * srcLine.nPointCount);
}
} else if (dataType == keResultDataType_PointXYZRGBA) {
dstLine.p3DPoint = new SVzNLPointXYZRGBA[srcLine.nPointCount];
if (srcLine.p3DPoint) {
memcpy(dstLine.p3DPoint, srcLine.p3DPoint, sizeof(SVzNLPointXYZRGBA) * srcLine.nPointCount);
}
dstLine.p2DPoint = new SVzNL2DLRPoint[srcLine.nPointCount];
if (srcLine.p2DPoint) {
memcpy(dstLine.p2DPoint, srcLine.p2DPoint, sizeof(SVzNL2DLRPoint) * srcLine.nPointCount);
}
}
}
dstLine.nPointCount = srcLine.nPointCount;
dstLine.llTimeStamp = srcLine.llTimeStamp;
dstLine.llFrameIdx = srcLine.llFrameIdx;
dstLine.nEncodeNo = srcLine.nEncodeNo;
dstLine.fSwingAngle = srcLine.fSwingAngle;
dstLine.bEndOnceScan = srcLine.bEndOnceScan;
dst.push_back(std::make_pair(dataType, dstLine));
}
return dst;
}
void DebugDataSaver::FreeData(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data)
{
LaserDataLoader loader;
loader.FreeLaserScanData(data);
data.clear();
}
std::string DebugDataSaver::GenerateSavePath(const std::string& basePath, const std::string& timestamp, const std::string& deviceName)
{
// 子目录命名:{appName}_PointCloud_YYYYMMDD时间戳前 8 位为日期)
QString subDir = SubDirPrefix() + QString::fromStdString(timestamp.substr(0, 8));
QString fullPath = QString::fromStdString(basePath) + "/" + subDir;
QDir dir(fullPath);
if (!dir.exists()) {
dir.mkpath(".");
}
// 文件名:{设备名}_{日期}_{时间}.txttimestamp 形如 YYYYMMDD_HHmmsszzz
// 设备名缺省时退化为 pointcloud
QString name = deviceName.empty() ? QStringLiteral("pointcloud")
: QString::fromStdString(deviceName);
QString fileName = QString("%1/%2_%3.txt").arg(fullPath)
.arg(name)
.arg(QString::fromStdString(timestamp));
return fileName.toStdString();
}
void DebugDataSaver::EnsureStorageLimit(const std::string& basePath)
{
std::lock_guard<std::mutex> lock(m_diskSpaceMutex);
const int64_t limit = m_maxStorageSize.load();
if (limit <= 0) return;
int64_t totalSize = ComputeDirSize(basePath);
const QString prefix = SubDirPrefix();
// 循环删除最早的文件,直到目录总大小 <= limit
while (totalSize > limit) {
std::string oldest = FindOldestFile(basePath);
if (oldest.empty()) {
LOG_WARNING("[DebugDataSaver] 目录大小 %lld 超过上限 %lld但无可删除文件\n",
static_cast<long long>(totalSize), static_cast<long long>(limit));
break;
}
QFileInfo info(QString::fromStdString(oldest));
qint64 fileSize = info.size();
QString parentPath = info.absolutePath();
if (!QFile::remove(QString::fromStdString(oldest))) {
LOG_ERROR("[DebugDataSaver] 删除失败: %s\n", oldest.c_str());
break;
}
LOG_INFO("[DebugDataSaver] 磁盘超限,删除最早文件: %s (%lld 字节)\n",
oldest.c_str(), static_cast<long long>(fileSize));
totalSize -= fileSize;
// 父目录若已为空且名字匹配 {appName}_PointCloud_* 前缀,连同目录一起回收
QDir parentDir(parentPath);
const QString parentName = parentDir.dirName();
if (parentName.startsWith(prefix) && parentDir.isEmpty()) {
parentDir.cdUp();
if (parentDir.rmdir(parentName)) {
LOG_INFO("[DebugDataSaver] 已移除空目录: %s\n", parentPath.toStdString().c_str());
}
}
}
}
int64_t DebugDataSaver::ComputeDirSize(const std::string& rootPath)
{
QString root = QString::fromStdString(rootPath);
QDir rootDir(root);
if (!rootDir.exists()) return 0;
const QString prefix = SubDirPrefix();
int64_t total = 0;
// 只统计匹配 {appName}_debug_* 模式的子目录,避免误判 basePath 下其他内容
const QStringList subDirs = rootDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QString& name : subDirs) {
if (!name.startsWith(prefix)) continue;
QString subPath = rootDir.absoluteFilePath(name);
QDirIterator it(subPath, QStringList() << "*.txt", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
total += it.fileInfo().size();
}
}
return total;
}
std::string DebugDataSaver::FindOldestFile(const std::string& rootPath)
{
QString root = QString::fromStdString(rootPath);
QDir rootDir(root);
if (!rootDir.exists()) return std::string();
const QString prefix = SubDirPrefix();
QString oldestPath;
QDateTime oldestTime;
bool first = true;
const QStringList subDirs = rootDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QString& name : subDirs) {
if (!name.startsWith(prefix)) continue;
QString subPath = rootDir.absoluteFilePath(name);
QDirIterator it(subPath, QStringList() << "*.txt", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
QFileInfo info = it.fileInfo();
QDateTime mtime = info.lastModified();
if (first || mtime < oldestTime) {
oldestTime = mtime;
oldestPath = info.absoluteFilePath();
first = false;
}
}
}
return oldestPath.toStdString();
}