From 746bba958343b11314bb41000f77aacdc818a1b4 Mon Sep 17 00:00:00 2001 From: jinchao <383321154@qq.com> Date: Mon, 16 Jun 2025 16:19:12 +0800 Subject: [PATCH] =?UTF-8?q?V0.22.2.250616=5Falpha:=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=87=87=E9=9B=86=E9=A1=B5=E9=9D=A2=E7=9A=84?= =?UTF-8?q?CSV=E6=95=B0=E6=8D=AE=E9=87=87=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Release/database/XNSim.db | Bin 1224704 -> 1224704 bytes Release/include/XNCore/XNCore_global.h | 6 + Release/include/XNCore/XNDDSInterface.h | 18 +- XNCore/XNCore_Function.cpp | 37 +++ XNCore/XNCore_global.h | 6 + XNCore/XNDDSInterface.h | 18 +- XNMonitorServer/CMakeLists.txt | 2 + XNMonitorServer/CSVDataInjectThread.cpp | 31 ++- XNMonitorServer/CSVDataInjectThread.h | 9 +- XNMonitorServer/DataCollect.cpp | 326 ++++++++++++++++++++++++ XNMonitorServer/DataCollect.h | 52 ++++ XNMonitorServer/DataInjectThread.h | 4 - XNMonitorServer/TypeDefine.h | 10 +- XNMonitorServer/XNMonitorInterface.cpp | 147 ++++++++++- XNMonitorServer/XNMonitorInterface.h | 40 ++- XNSimHtml/components/data-collection.js | 90 ++++++- XNSimHtml/routes/DataCollect.js | 94 +++++++ XNSimHtml/server.js | 2 + XNSimHtml/utils/xnCoreService.js | 68 ++++- 19 files changed, 899 insertions(+), 61 deletions(-) create mode 100644 XNMonitorServer/DataCollect.cpp create mode 100644 XNMonitorServer/DataCollect.h create mode 100644 XNSimHtml/routes/DataCollect.js diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 4a97dc392c4cc632d43b5e53b155ceef95d0ec0f..fb097dc06935509a87b83d4add9100fb98483ebe 100644 GIT binary patch delta 573 zcmZp8;MMTJYl1Z6nu#*bjB6SbS`!$zCNM2|&uqh6Hl6(gQ!Ep2+4htVOai>zT+EV; z26{$DdPX+e8~-vjGchv=Fl=RKPUL59yx}ta3?H+lx~YMInSrULg{h^HUQT|xk%5t^ zu7R1Zp;?H5xs|Dzm9eFsnUQ(a^g?cC`RTIU%v|kX_?dy21&CRJm<@>8ftUk`Ij4W& z=MoU(;NWPJ_*gKpFj}BpXgiMRD207Ikrm(-1Z$<`iTk z8k$)dngY?&Mf09^Etp;?%_Umz931v+>V{{%>t43EzntA>s0X1TVo&zZeY$qW(~b>5 zRnKR&JnfqQqIvJr?VFzO?tj|9{`r&@@Au5+GSYjpf8En1y-#+v!3}%1dBxM#r9d@6 z^-p&6KG`!1BJynhgeSXZKkc9Kvb`T@;LGXt>s~IJ0W!rHVM>IbbAD0k(+PdgSFeAu zZ}QXjUC9}VMNfA0mF5*^=B1~mD1i9Srf+*TXB9;L$&Ot>2S07t12z+8q=_C-9N93S zY;j3`&f|tgAe929l=E|Po^9U^434MkI$tbqeX)1Vv))C|rtN&TdFtbaro@!o%)Iug eQd~gH4a7V^%nQVPK+F%s0zfRdeX5jD_EZ4M0|{0D delta 212 zcmZp8;MMTJYl1Z6vWYUzjLRAmS`!$zCNM2|&uq=xIGz0iQ|$J%4@?5Q69ag+H~wX6 zW@2XKPut4QoXF4I@x^TV89rufRSN?XOA`Y_V>7*+{B$D&BU4=iGhIXD5CaP(sqlite3_column_text(stmt, column)); diff --git a/Release/include/XNCore/XNDDSInterface.h b/Release/include/XNCore/XNDDSInterface.h index eb6103b..1c43e70 100644 --- a/Release/include/XNCore/XNDDSInterface.h +++ b/Release/include/XNCore/XNDDSInterface.h @@ -175,7 +175,7 @@ protected: if (data) { return std::to_string(data.value()); } else { - return "Unknown"; + return "0"; } } else if constexpr (is_std_array_v) { if (data) { @@ -201,9 +201,9 @@ protected: { if constexpr (std::is_arithmetic_v) { if constexpr (std::is_same_v || std::is_same_v) { - data = std::stod(value); + data = XNSim::safe_stod(value); } else { - data = std::stoll(value); + data = XNSim::safe_stoll(value); } } else if constexpr (is_std_array_v) { // 解析输入字符串 @@ -238,7 +238,13 @@ protected: if (i > 0) ss << ","; if constexpr (std::is_arithmetic_v) { - ss << data[i]; + if constexpr (std::is_same_v) { + ss << static_cast(data[i]); + } else if constexpr (std::is_same_v) { + ss << static_cast(data[i]); + } else { + ss << data[i]; + } } else if constexpr (is_std_array_v) { ss << getStringFromStdArray(data[i]); } else { @@ -267,9 +273,9 @@ protected: if constexpr (std::is_arithmetic_v) { // 对于基本类型,直接转换 if constexpr (std::is_same_v || std::is_same_v) { - data[i] = static_cast(std::stod(value[start_pos + i])); + data[i] = static_cast(XNSim::safe_stod(value[start_pos + i])); } else { - data[i] = static_cast(std::stoll(value[start_pos + i])); + data[i] = static_cast(XNSim::safe_stoll(value[start_pos + i])); } } else if constexpr (is_std_array_v) { // 对于嵌套数组,递归处理 diff --git a/XNCore/XNCore_Function.cpp b/XNCore/XNCore_Function.cpp index 77b3be8..a0968fc 100644 --- a/XNCore/XNCore_Function.cpp +++ b/XNCore/XNCore_Function.cpp @@ -55,4 +55,41 @@ int safe_stoi(const std::string &str, int defaultValue) return defaultValue; } } + +int safe_stol(const std::string &str, int defaultValue) +{ + if (str.empty()) { + return defaultValue; + } + try { + return std::stol(str); + } catch (const std::exception &) { + return defaultValue; + } +} + +double safe_stod(const std::string &str, double defaultValue) +{ + if (str.empty()) { + return defaultValue; + } + try { + return std::stod(str); + } catch (const std::exception &) { + return defaultValue; + } +} + +long long safe_stoll(const std::string &str, long long defaultValue) +{ + if (str.empty()) { + return defaultValue; + } + try { + return std::stoll(str); + } catch (const std::exception &) { + return defaultValue; + } +} + } // namespace XNSim \ No newline at end of file diff --git a/XNCore/XNCore_global.h b/XNCore/XNCore_global.h index e2cae8a..7d5e11a 100755 --- a/XNCore/XNCore_global.h +++ b/XNCore/XNCore_global.h @@ -280,6 +280,12 @@ extern "C" std::string XNCORE_EXPORT getFileNameWithoutExt(const std::string &pa extern "C" int XNCORE_EXPORT safe_stoi(const std::string &str, int defaultValue = 0); +extern "C" int XNCORE_EXPORT safe_stol(const std::string &str, int defaultValue = 0); + +extern "C" double XNCORE_EXPORT safe_stod(const std::string &str, double defaultValue = 0); + +extern "C" long long XNCORE_EXPORT safe_stoll(const std::string &str, long long defaultValue = 0); + inline std::string getStringFromSqlite3(sqlite3_stmt *stmt, int column) { const char *text = reinterpret_cast(sqlite3_column_text(stmt, column)); diff --git a/XNCore/XNDDSInterface.h b/XNCore/XNDDSInterface.h index eb6103b..1c43e70 100644 --- a/XNCore/XNDDSInterface.h +++ b/XNCore/XNDDSInterface.h @@ -175,7 +175,7 @@ protected: if (data) { return std::to_string(data.value()); } else { - return "Unknown"; + return "0"; } } else if constexpr (is_std_array_v) { if (data) { @@ -201,9 +201,9 @@ protected: { if constexpr (std::is_arithmetic_v) { if constexpr (std::is_same_v || std::is_same_v) { - data = std::stod(value); + data = XNSim::safe_stod(value); } else { - data = std::stoll(value); + data = XNSim::safe_stoll(value); } } else if constexpr (is_std_array_v) { // 解析输入字符串 @@ -238,7 +238,13 @@ protected: if (i > 0) ss << ","; if constexpr (std::is_arithmetic_v) { - ss << data[i]; + if constexpr (std::is_same_v) { + ss << static_cast(data[i]); + } else if constexpr (std::is_same_v) { + ss << static_cast(data[i]); + } else { + ss << data[i]; + } } else if constexpr (is_std_array_v) { ss << getStringFromStdArray(data[i]); } else { @@ -267,9 +273,9 @@ protected: if constexpr (std::is_arithmetic_v) { // 对于基本类型,直接转换 if constexpr (std::is_same_v || std::is_same_v) { - data[i] = static_cast(std::stod(value[start_pos + i])); + data[i] = static_cast(XNSim::safe_stod(value[start_pos + i])); } else { - data[i] = static_cast(std::stoll(value[start_pos + i])); + data[i] = static_cast(XNSim::safe_stoll(value[start_pos + i])); } } else if constexpr (is_std_array_v) { // 对于嵌套数组,递归处理 diff --git a/XNMonitorServer/CMakeLists.txt b/XNMonitorServer/CMakeLists.txt index 11dd228..2e8b4e3 100644 --- a/XNMonitorServer/CMakeLists.txt +++ b/XNMonitorServer/CMakeLists.txt @@ -48,6 +48,8 @@ add_library(XNMonitorServer SHARED DataInjectThread.cpp CSVDataInjectThread.h CSVDataInjectThread.cpp + DataCollect.h + DataCollect.cpp ) # 添加头文件搜索路径 diff --git a/XNMonitorServer/CSVDataInjectThread.cpp b/XNMonitorServer/CSVDataInjectThread.cpp index 1a875e9..89528ab 100644 --- a/XNMonitorServer/CSVDataInjectThread.cpp +++ b/XNMonitorServer/CSVDataInjectThread.cpp @@ -11,7 +11,7 @@ CSVDataInjectThread::~CSVDataInjectThread() stop(); } -bool CSVDataInjectThread::Initialize(std::vector injectDataInfos) +bool CSVDataInjectThread::Initialize(std::vector injectDataInfos) { m_csvFile.open(m_csvFilePath); if (!m_csvFile.is_open()) { @@ -49,6 +49,7 @@ bool CSVDataInjectThread::Initialize(std::vector injectDataInfos if (dataMonitor->isInitialized()) { m_alreadyStartedMonitors[injectDataInfo.structName] = dataMonitor; } else { + dataMonitor->Initialize(nullptr, 0, 0); m_notStartedMonitors[injectDataInfo.structName] = dataMonitor; } } @@ -96,9 +97,9 @@ void CSVDataInjectThread::parseHeaderField(const std::string &headerField) if (injectDataInfo.interfaceNames[i] == csvHeaderField.fieldName) { csvHeaderField.structName = injectDataInfo.structName; if (injectDataInfo.arraySizes[i].second > 1) { - csvHeaderField.arraySize1 = injectDataInfo.arraySizes[i].first; + csvHeaderField.arraySize2 = injectDataInfo.arraySizes[i].second; } else { - csvHeaderField.arraySize1 = 0; + csvHeaderField.arraySize2 = 0; } break; } @@ -161,7 +162,13 @@ void CSVDataInjectThread::updateData() std::stringstream ss(line); std::string field; std::getline(ss, field, ','); - double timeStamp = std::stod(field); + double timeStamp = 0; + try { + timeStamp = std::stod(field); + } catch (const std::exception &e) { + std::cout << "field: " << field << " is not a number" << std::endl; + return; + } m_nextExecuteTime = static_cast(timeStamp * 1000); // 转换为毫秒 // 为每个结构体初始化数据 @@ -197,7 +204,7 @@ void CSVDataInjectThread::updateData() [m_headerFields[i].arrayIndex1] = field; } else if (m_headerFields[i].arrayDim == 2) { tempDataMap[m_headerFields[i].structName][m_headerFields[i].fieldName] - [m_headerFields[i].arrayIndex1 * m_headerFields[i].arraySize1 + [m_headerFields[i].arrayIndex1 * m_headerFields[i].arraySize2 + m_headerFields[i].arrayIndex2] = field; } } @@ -248,9 +255,17 @@ void CSVDataInjectThread::threadFunc() } // 执行数据注入 - for (const auto &[structName, dataMonitor] : m_alreadyStartedMonitors) { - if (dataMonitor && m_data.find(structName) != m_data.end()) { - dataMonitor->setDataByString(m_data[structName]); + for (const auto &monitorDataInfo : m_injectDataInfos) { + DataMonitorBasePtr dataMonitor; + if (m_notStartedMonitors.find(monitorDataInfo.structName) + != m_notStartedMonitors.end()) { + dataMonitor = m_notStartedMonitors[monitorDataInfo.structName]; + } else if (m_alreadyStartedMonitors.find(monitorDataInfo.structName) + != m_alreadyStartedMonitors.end()) { + dataMonitor = m_alreadyStartedMonitors[monitorDataInfo.structName]; + } + if (dataMonitor && m_data.find(monitorDataInfo.structName) != m_data.end()) { + dataMonitor->setDataByString(m_data[monitorDataInfo.structName]); } } diff --git a/XNMonitorServer/CSVDataInjectThread.h b/XNMonitorServer/CSVDataInjectThread.h index a447992..8299d1e 100644 --- a/XNMonitorServer/CSVDataInjectThread.h +++ b/XNMonitorServer/CSVDataInjectThread.h @@ -1,11 +1,6 @@ #pragma once #include "DataMonitor.h" -#include -#include -#include -#include -#include /** * @brief 数据注入线程类,用于持续向数据监控器注入数据 @@ -24,7 +19,7 @@ public: */ ~CSVDataInjectThread(); - bool Initialize(std::vector injectDataInfos); + bool Initialize(std::vector injectDataInfos); /** * @brief 启动数据注入线程 @@ -55,7 +50,7 @@ private: private: std::string m_csvFilePath; std::ifstream m_csvFile; - std::vector m_injectDataInfos; + std::vector m_injectDataInfos; std::vector m_headerFields; std::thread m_thread; ///< 数据注入线程 std::atomic m_running; ///< 线程运行标志 diff --git a/XNMonitorServer/DataCollect.cpp b/XNMonitorServer/DataCollect.cpp new file mode 100644 index 0000000..81eccbc --- /dev/null +++ b/XNMonitorServer/DataCollect.cpp @@ -0,0 +1,326 @@ +#include "DataCollect.h" +#include "DataMonitorFactory.h" + +DataCollect::DataCollect() + : m_running(false), m_nextExecuteTime(0), m_collectDuration(0), m_collectFrequency(0) +{ +} + +DataCollect::~DataCollect() +{ + stop(); +} + +bool DataCollect::Initialize(std::vector collectDataInfos, std::string dcsFilePath) +{ + m_collectDataInfos = collectDataInfos; + + // 打开并读取 dcs 文件 + std::ifstream dcsFile(dcsFilePath); + if (!dcsFile.is_open()) { + return false; + } + + std::string line; + bool foundCollectList = false; + std::string collectListContent; + + // 查找 define collect_list 部分 + while (std::getline(dcsFile, line)) { + // 跳过注释行 + if (line.empty() || line[0] == '!') { + continue; + } + + if (line.find("define collect_list") != std::string::npos) { + foundCollectList = true; + continue; + } + + if (foundCollectList) { + // 检查是否到达结束引号 + if (line.find("\"") != std::string::npos) { + break; + } + collectListContent += line; + } + } + + // 继续读取文件,查找采集时长和频率以及输出文件名 + while (std::getline(dcsFile, line)) { + // 跳过注释行 + if (line.empty() || line[0] == '!') { + continue; + } + + // 查找 "for X at Y" 格式的行 + if (line.find("for") != std::string::npos && line.find("at") != std::string::npos) { + std::stringstream ss(line); + std::string token; + ss >> token; // 读取 "for" + ss >> m_collectDuration; // 读取时长 + ss >> token; // 读取 "at" + ss >> m_collectFrequency; // 读取频率 + } + + // 查找输出文件名 + if (line.find("put/extend/all result") != std::string::npos) { + std::stringstream ss(line); + std::string token; + ss >> token; // 读取 "put/extend/all" + ss >> token; // 读取 "result" + ss >> m_outputFileName; // 读取文件名 + m_outputFileName += ".csv"; // 添加.csv后缀 + } + } + + // 解析 collect_list 内容 + std::stringstream ss(collectListContent); + std::string field; + while (std::getline(ss, field, '-')) { + // 去除字段前后的空白字符 + field.erase(0, field.find_first_not_of(" \t\r\n")); + field.erase(field.find_last_not_of(" \t\r\n") + 1); + + if (!field.empty()) { + parseHeaderField(field); + } + } + + // 关闭 dcs 文件 + dcsFile.close(); + + for (const auto &collectDataInfo : m_collectDataInfos) { + auto dataMonitor = DataMonitorFactory::GetInstance(collectDataInfo.structName); + if (dataMonitor == nullptr) { + return false; + } + if (dataMonitor->isInitialized()) { + m_alreadyStartedMonitors[collectDataInfo.structName] = dataMonitor; + } else { + dataMonitor->Initialize(nullptr, 0, 0); + m_notStartedMonitors[collectDataInfo.structName] = dataMonitor; + } + } + + // 获取dcs文件所在目录 + std::filesystem::path dcsPath(dcsFilePath); + std::string dcsDir = dcsPath.parent_path().string(); + + // 将输出文件名设置为dcs文件同目录下的完整路径 + m_outputFileName = dcsDir + "/" + m_outputFileName; + if (std::filesystem::exists(m_outputFileName)) { + int suffix = 1; + std::filesystem::path originalPath(m_outputFileName); + std::string stem = originalPath.stem().string(); + std::string extension = originalPath.extension().string(); + std::string newFileName; + + do { + newFileName = stem + "_" + std::to_string(suffix) + extension; + suffix++; + } while (std::filesystem::exists(originalPath.parent_path() / newFileName)); + + std::filesystem::rename(m_outputFileName, + (originalPath.parent_path() / newFileName).string()); + } + m_outputFile.open(m_outputFileName); + if (!m_outputFile.is_open()) { + return false; + } + + // 写入表头 + m_outputFile << "Time"; + + for (const auto &headerField : m_headerFields) { + if (headerField.arrayDim == 0) { + m_outputFile << "," << headerField.fieldName; + } else if (headerField.arrayDim == 1) { + m_outputFile << "," << headerField.fieldName << "(" << headerField.arrayIndex1 + 1 + << ")"; + } else if (headerField.arrayDim == 2) { + m_outputFile << "," << headerField.fieldName << "(" << headerField.arrayIndex1 + 1 + << "_" << headerField.arrayIndex2 + 1 << ")"; + } + } + m_outputFile << std::endl; + + return true; +} + +void DataCollect::parseHeaderField(const std::string &headerField) +{ + CSVHeaderField csvHeaderField; + csvHeaderField.fieldName = headerField.substr(0, headerField.find('(')); + csvHeaderField.arrayDim = 0; + csvHeaderField.arrayIndex1 = 0; + csvHeaderField.arrayIndex2 = 0; + + if (headerField.find('(') != std::string::npos) { + // 处理一维或二维数组,格式为interfaceName(1)或interfaceName(1,2) + size_t firstParenPos = headerField.find('('); + size_t lastParenPos = headerField.find(')'); + std::string indexStr = + headerField.substr(firstParenPos + 1, lastParenPos - firstParenPos - 1); + if (indexStr.find('_') != std::string::npos) { + // 二维数组 + size_t commaPos = indexStr.find('_'); + csvHeaderField.arrayIndex1 = std::stoi(indexStr.substr(0, commaPos)) - 1; + if (csvHeaderField.arrayIndex1 < 0) { + return; + } + csvHeaderField.arrayIndex2 = std::stoi(indexStr.substr(commaPos + 1)) - 1; + if (csvHeaderField.arrayIndex2 < 0) { + return; + } + csvHeaderField.arrayDim = 2; + } else { + // 一维数组 + csvHeaderField.arrayIndex1 = std::stoi(indexStr) - 1; + if (csvHeaderField.arrayIndex1 < 0) { + return; + } + csvHeaderField.arrayDim = 1; + } + } + + for (const auto &collectDataInfo : m_collectDataInfos) { + for (int i = 0; i < collectDataInfo.interfaceNames.size(); i++) { + if (collectDataInfo.interfaceNames[i] == csvHeaderField.fieldName) { + csvHeaderField.structName = collectDataInfo.structName; + if (collectDataInfo.arraySizes[i].second > 1) { + csvHeaderField.arraySize2 = collectDataInfo.arraySizes[i].second; + } else { + csvHeaderField.arraySize2 = 0; + } + break; + } + } + } + if (csvHeaderField.structName.empty()) { + return; + } + + m_headerFields.push_back(csvHeaderField); +} + +void DataCollect::start() +{ + std::lock_guard lock(m_mutex); + if (!m_running) { + m_running = true; + m_thread = std::thread(&DataCollect::threadFunc, this); + } +} + +void DataCollect::stop() +{ + { + std::lock_guard lock(m_mutex); + if (m_running) { + m_running = false; + m_cv.notify_all(); + } + } + + if (m_thread.joinable()) { + m_thread.join(); + } + + // 关闭文件 + if (m_outputFile.is_open()) { + m_outputFile.close(); + } + + // 释放未启动的监控器 + for (const auto &[structName, dataMonitor] : m_notStartedMonitors) { + DataMonitorFactory::ReleaseInstance(structName); + } + m_notStartedMonitors.clear(); + m_alreadyStartedMonitors.clear(); +} + +void DataCollect::updateData() +{ + for (const auto &headerField : m_headerFields) { + if (m_data.find(headerField.structName) != m_data.end()) { + if (m_data[headerField.structName].find(headerField.fieldName) + != m_data[headerField.structName].end()) { + if (headerField.arrayDim == 0) { + m_outputFile << "," << m_data[headerField.structName][headerField.fieldName]; + } else { + std::string value = m_data[headerField.structName][headerField.fieldName]; + std::stringstream ss(value); + std::string item; + std::vector values; + while (std::getline(ss, item, ',')) { + values.push_back(item); + } + if (headerField.arrayDim == 1) { + m_outputFile << "," << values[headerField.arrayIndex1]; + } else if (headerField.arrayDim == 2) { + m_outputFile << "," + << values[headerField.arrayIndex1 * headerField.arraySize2 + + headerField.arrayIndex2]; + } + } + } else { + m_outputFile << "," << "0"; + } + } else { + m_outputFile << "," << "0"; + } + } + m_outputFile << std::endl; +} + +bool DataCollect::isRunning() const +{ + return m_running; +} + +void DataCollect::threadFunc() +{ + auto startTime = std::chrono::steady_clock::now(); + auto step = std::chrono::milliseconds(static_cast(1000 / m_collectFrequency)); + auto nextTime = step; + auto endTime = + startTime + std::chrono::milliseconds(static_cast(m_collectDuration * 1000)); + + while (m_running) { + // 等待直到到达执行时间 + auto now = std::chrono::steady_clock::now(); + auto targetTime = startTime + nextTime; + + if (now < targetTime) { + std::this_thread::sleep_until(targetTime); + } + m_data.clear(); + // 执行数据注入 + for (const auto &monitorDataInfo : m_collectDataInfos) { + DataMonitorBasePtr dataMonitor; + if (m_notStartedMonitors.find(monitorDataInfo.structName) + != m_notStartedMonitors.end()) { + dataMonitor = m_notStartedMonitors[monitorDataInfo.structName]; + } else if (m_alreadyStartedMonitors.find(monitorDataInfo.structName) + != m_alreadyStartedMonitors.end()) { + dataMonitor = m_alreadyStartedMonitors[monitorDataInfo.structName]; + } + if (dataMonitor && m_data.find(monitorDataInfo.structName) == m_data.end()) { + m_data[monitorDataInfo.structName] = + dataMonitor->getStringData(monitorDataInfo.interfaceNames); + } + } + + // 写入一行数据 + nextTime += step; + if (targetTime > endTime) { + m_running = false; + break; + } + double timeStamp = + std::chrono::duration_cast(now - startTime).count() / 1000.0; + m_outputFile << std::fixed << std::setprecision(3) << timeStamp; + updateData(); + } +} \ No newline at end of file diff --git a/XNMonitorServer/DataCollect.h b/XNMonitorServer/DataCollect.h new file mode 100644 index 0000000..4fcae71 --- /dev/null +++ b/XNMonitorServer/DataCollect.h @@ -0,0 +1,52 @@ +#pragma once + +#include "DataMonitor.h" + +class DataCollect +{ +public: + DataCollect(); + ~DataCollect(); + +public: + bool Initialize(std::vector collectDataInfos, std::string dcsFilePath); + + void start(); + + void stop(); + + /** + * @brief 从CSV文件读取下一行数据并更新执行时间 + * 如果文件读取完毕,将停止线程 + */ + void updateData(); + + bool isRunning() const; + +private: + /** + * @brief 线程执行函数 + */ + void threadFunc(); + + void parseHeaderField(const std::string &headerField); + +private: + std::string m_outputFileName; + std::ofstream m_outputFile; + std::vector m_collectDataInfos; + std::vector m_headerFields; + std::thread m_thread; ///< 数据采集线程 + std::atomic m_running; ///< 线程运行标志 + std::mutex m_mutex; ///< 互斥锁 + std::condition_variable m_cv; ///< 条件变量 + std::unordered_map + m_alreadyStartedMonitors; ///< 已经启动的数据监控器 + std::unordered_map + m_notStartedMonitors; ///< 未启动的数据监控器 + std::unordered_map> + m_data; ///< 采集到的数据 + std::atomic m_nextExecuteTime; ///< 下一次执行的时间点 + int m_collectDuration; ///< 采集时长(秒) + int m_collectFrequency; ///< 采集频率(Hz) +}; \ No newline at end of file diff --git a/XNMonitorServer/DataInjectThread.h b/XNMonitorServer/DataInjectThread.h index e592505..1e55fec 100644 --- a/XNMonitorServer/DataInjectThread.h +++ b/XNMonitorServer/DataInjectThread.h @@ -1,10 +1,6 @@ #pragma once #include "DataMonitor.h" -#include -#include -#include -#include /** * @brief 数据注入线程类,用于持续向数据监控器注入数据 diff --git a/XNMonitorServer/TypeDefine.h b/XNMonitorServer/TypeDefine.h index 39dd4f2..b7f51e7 100755 --- a/XNMonitorServer/TypeDefine.h +++ b/XNMonitorServer/TypeDefine.h @@ -12,6 +12,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -210,7 +214,7 @@ struct ModelDefinition { std::vector> namespaceDefinitions; }; -struct InjectDataInfo { +struct MonitorDataInfo { /** * @brief 结构体名称 */ @@ -253,7 +257,7 @@ struct CSVHeaderField { int arrayIndex2; /** - * @brief 一维数组大小 + * @brief 列大小 */ - int arraySize1; + int arraySize2; }; \ No newline at end of file diff --git a/XNMonitorServer/XNMonitorInterface.cpp b/XNMonitorServer/XNMonitorInterface.cpp index db4e49c..d6e111e 100644 --- a/XNMonitorServer/XNMonitorInterface.cpp +++ b/XNMonitorServer/XNMonitorInterface.cpp @@ -9,6 +9,7 @@ #include "DataMonitorFactory.h" #include "DataInjectThread.h" #include "CSVDataInjectThread.h" +#include "DataCollect.h" // 全局变量 static bool g_initialized = false; @@ -21,6 +22,7 @@ SystemControl *systemControl = nullptr; bool g_systemControlStarted = false; CSVDataInjectThread *g_csvDataInjectThread = nullptr; +DataCollect *g_dataCollect = nullptr; // 初始化函数实现 int XN_Initialize(const char *domainId, int domainIdLen, char *errorMsg, int errorMsgSize) @@ -602,12 +604,12 @@ int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *injectDataI delete g_csvDataInjectThread; g_csvDataInjectThread = nullptr; } - std::vector injectDataInfos; + std::vector injectDataInfos; std::string injectDataInfoStr(injectDataInfo, injectDataInfoLen); try { nlohmann::json injectDataInfoJson = nlohmann::json::parse(injectDataInfoStr); for (const auto &[structName, interfaceInfo] : injectDataInfoJson.items()) { - InjectDataInfo info; + MonitorDataInfo info; info.structName = structName; for (const auto &[interfaceName, size] : interfaceInfo.items()) { info.interfaceNames.push_back(interfaceName); @@ -722,12 +724,139 @@ int XN_StopCsvDataInject(char *infoMsg, int infoMsgSize) return 0; } -int XNMONITORSERVER_EXPORT XN_StartCollectData(const char *structName, const int structNameLen, - const char *interfaceName, - const int interfaceNameLen, const char *csvFilePath, - const int csvFilePathLen, char *infoMsg, - int infoMsgSize) +int XNMONITORSERVER_EXPORT XN_StartCollectData(const char *CollectDataInfo, + const int CollectDataInfoLen, + const char *dcsFilePath, const int dcsFilePathLen, + char *infoMsg, int infoMsgSize) { - // TODO: 持续采集数据并保存到csv文件接口 - return -1; + if (!g_initialized) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "DDSMonitor Not Initialized", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + if (g_dataCollect != nullptr) { + g_dataCollect->stop(); + delete g_dataCollect; + g_dataCollect = nullptr; + } + std::vector collectDataInfos; + std::string collectDataInfoStr(CollectDataInfo, CollectDataInfoLen); + try { + nlohmann::json collectDataInfoJson = nlohmann::json::parse(collectDataInfoStr); + for (const auto &[structName, interfaceInfo] : collectDataInfoJson.items()) { + MonitorDataInfo info; + info.structName = structName; + for (const auto &[interfaceName, size] : interfaceInfo.items()) { + info.interfaceNames.push_back(interfaceName); + if (size.is_array()) { + info.arraySizes.push_back({size[0].get(), size[1].get()}); + } else { + info.arraySizes.push_back({0, 0}); + } + } + collectDataInfos.push_back(info); + } + } catch (const nlohmann::json::parse_error &e) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "Invalid JSON format", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + + std::string dcsFilePathStr(dcsFilePath, dcsFilePathLen); + if (dcsFilePathStr.empty()) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "DCS 文件路径为空", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + if (!std::filesystem::exists(dcsFilePathStr)) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "DCS 文件不存在", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + try { + g_dataCollect = new DataCollect(); + if (!g_dataCollect->Initialize(collectDataInfos, dcsFilePathStr)) { + delete g_dataCollect; + g_dataCollect = nullptr; + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "数据采集线程初始化失败", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + g_dataCollect->start(); + } catch (const std::exception &e) { + if (g_dataCollect) { + delete g_dataCollect; + g_dataCollect = nullptr; + } + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, e.what(), infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + return 0; +} + +int XNMONITORSERVER_EXPORT XN_GetCollectDataStatus(char *infoMsg, int infoMsgSize) +{ + if (!g_initialized) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "DDSMonitor Not Initialized", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + + if (g_dataCollect == nullptr) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "数据采集线程已不存在", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + + return g_dataCollect->isRunning() ? 1 : 0; +} + +int XNMONITORSERVER_EXPORT XN_StopCollectData(char *infoMsg, int infoMsgSize) +{ + if (!g_initialized) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "DDSMonitor Not Initialized", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + + if (g_dataCollect == nullptr) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "数据采集线程已不存在", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + + try { + g_dataCollect->stop(); + delete g_dataCollect; + g_dataCollect = nullptr; + } catch (const std::exception &e) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, e.what(), infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + + return 0; } \ No newline at end of file diff --git a/XNMonitorServer/XNMonitorInterface.h b/XNMonitorServer/XNMonitorInterface.h index 7ae3f54..e9d9c9b 100644 --- a/XNMonitorServer/XNMonitorInterface.h +++ b/XNMonitorServer/XNMonitorInterface.h @@ -224,7 +224,7 @@ extern "C" * @brief 获取csv数据注入状态 * @param infoMsg 错误信息 * @param infoMsgSize 错误信息大小 - * @return 0: 成功, -1: 失败 + * @return 0: 成功, -1: 失败, 1: 正在注入 */ int XNMONITORSERVER_EXPORT XN_GetCsvDataInjectStatus(char *infoMsg, int infoMsgSize); @@ -239,25 +239,37 @@ extern "C" //******************** csv数据采集 ********************* /** - * @brief 持续采集数据并保存到csv文件接口 - * @param structName 结构体名称 - * @param structNameLen 结构体名称长度 - * @param interfaceName 接口名称JSON数组字符串 - * @param interfaceNameLen 接口名称JSON数组字符串长度 - * @param csvFilePath csv文件路径 - * @param csvFilePathLen csv文件路径长度 - * @param frequency 采集频率 + * @brief 读取dcs采集脚本并将数据保存到csv文件接口 + * @param CollectDataInfo 采集数据信息JSON数组字符串 + * @param CollectDataInfoLen 采集数据信息JSON数组字符串长度 + * @param dcsFilePath dcs文件路径 + * @param dcsFilePathLen dcs文件路径长度 * @param infoMsg 错误信息 * @param infoMsgSize 错误信息大小 * @return 0: 成功, -1: 失败 */ - int XNMONITORSERVER_EXPORT XN_StartCollectData(const char *structName, const int structNameLen, - const char *interfaceName, - const int interfaceNameLen, - const char *csvFilePath, - const int csvFilePathLen, char *infoMsg, + int XNMONITORSERVER_EXPORT XN_StartCollectData(const char *CollectDataInfo, + const int CollectDataInfoLen, + const char *dcsFilePath, + const int dcsFilePathLen, char *infoMsg, int infoMsgSize); + /** + * @brief 获取csv数据采集状态 + * @param infoMsg 错误信息 + * @param infoMsgSize 错误信息大小 + * @return 0: 成功, -1: 失败, 1: 正在采集 + */ + int XNMONITORSERVER_EXPORT XN_GetCollectDataStatus(char *infoMsg, int infoMsgSize); + + /** + * @brief 停止采集数据 + * @param infoMsg 错误信息 + * @param infoMsgSize 错误信息大小 + * @return 0: 成功, -1: 失败 + */ + int XNMONITORSERVER_EXPORT XN_StopCollectData(char *infoMsg, int infoMsgSize); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/XNSimHtml/components/data-collection.js b/XNSimHtml/components/data-collection.js index b002c7f..1ddfaa0 100644 --- a/XNSimHtml/components/data-collection.js +++ b/XNSimHtml/components/data-collection.js @@ -510,6 +510,20 @@ class DataCollection extends HTMLElement { background: #d9d9d9; cursor: not-allowed; } + /* 卸载脚本按钮样式 */ + .action-btn.unload { + background: #ff4d4f; + } + .action-btn.unload:hover { + background: #ff7875; + } + /* 停止采集按钮样式 */ + .action-btn.stop { + background: #faad14; + } + .action-btn.stop:hover { + background: #ffc53d; + } .input-row { display: flex; flex-direction: column; @@ -654,8 +668,8 @@ class DataCollection extends HTMLElement {
- - + +
@@ -713,6 +727,78 @@ class DataCollection extends HTMLElement { this.isActive = true; this.startStatusTimer(); } + + async handleStartCollect() { + // 检查监控状态 + if (this.monitorStatus !== 1) { + alert('请先启动监控'); + return; + } + + // 检查是否已加载脚本 + if (!this.scriptFile) { + alert('请先加载脚本'); + return; + } + + const startCollectBtn = this.shadowRoot.getElementById('startCollectBtn'); + + // 如果正在采集,则停止采集 + if (this.collectStatus === 2) { + try { + const response = await fetch('/api/data-collect/stop', { + method: 'POST' + }); + + const result = await response.json(); + + if (result.success) { + // 更新采集状态 + this.collectStatus = 1; // 改为已加载脚本状态 + // 更新按钮状态 + startCollectBtn.textContent = '开始采集'; + startCollectBtn.disabled = false; + startCollectBtn.classList.remove('stop'); + } else { + throw new Error(result.message); + } + } catch (error) { + console.error('停止采集失败:', error); + alert('停止采集失败: ' + error.message); + } + return; + } + + // 开始采集 + try { + // 调用后端接口启动采集 + const response = await fetch('/api/data-collect/start', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + collectDataInfo: JSON.stringify(this.structData), + dcsFilePath: this.scriptFile.name + }) + }); + + const result = await response.json(); + + if (result.success) { + // 更新采集状态 + this.collectStatus = 2; // 设置为采集中 + // 更新按钮状态 + startCollectBtn.textContent = '停止采集'; + startCollectBtn.classList.add('stop'); + } else { + throw new Error(result.message); + } + } catch (error) { + console.error('启动采集失败:', error); + alert('启动采集失败: ' + error.message); + } + } } customElements.define('data-collection', DataCollection); \ No newline at end of file diff --git a/XNSimHtml/routes/DataCollect.js b/XNSimHtml/routes/DataCollect.js new file mode 100644 index 0000000..6ec09d1 --- /dev/null +++ b/XNSimHtml/routes/DataCollect.js @@ -0,0 +1,94 @@ +const express = require('express'); +const router = express.Router(); +const { startCollectData, getCollectDataStatus, stopCollectData } = require('../utils/xnCoreService'); +const path = require('path'); + +// 启动数据采集 +router.post('/start', async (req, res) => { + try { + const { collectDataInfo, dcsFilePath } = req.body; + + if (!collectDataInfo || !dcsFilePath) { + return res.status(400).json({ + success: false, + message: '缺少必要参数' + }); + } + + // 构建上传文件的完整路径 + const fullPath = path.join(__dirname, '..', 'upload', dcsFilePath); + + const result = startCollectData(collectDataInfo, fullPath); + + if (result === '启动数据采集成功') { + res.json({ + success: true, + message: result + }); + } else { + res.status(500).json({ + success: false, + message: result + }); + } + } catch (error) { + console.error('启动数据采集失败:', error); + res.status(500).json({ + success: false, + message: '启动数据采集失败: ' + error.message + }); + } +}); + +// 获取数据采集状态 +router.get('/status', async (req, res) => { + try { + const status = getCollectDataStatus(); + + if (typeof status === 'number') { + res.json({ + success: true, + status: status, // 0-成功,1-正在采集 + message: status === 1 ? '正在采集' : '采集完成' + }); + } else { + res.status(500).json({ + success: false, + message: status + }); + } + } catch (error) { + console.error('获取数据采集状态失败:', error); + res.status(500).json({ + success: false, + message: '获取数据采集状态失败: ' + error.message + }); + } +}); + +// 停止数据采集 +router.post('/stop', async (req, res) => { + try { + const result = stopCollectData(); + + if (result === '停止数据采集成功') { + res.json({ + success: true, + message: result + }); + } else { + res.status(500).json({ + success: false, + message: result + }); + } + } catch (error) { + console.error('停止数据采集失败:', error); + res.status(500).json({ + success: false, + message: '停止数据采集失败: ' + error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/XNSimHtml/server.js b/XNSimHtml/server.js index 8feedab..076ef15 100644 --- a/XNSimHtml/server.js +++ b/XNSimHtml/server.js @@ -28,6 +28,7 @@ const systemMonitorRoutes = require('./routes/SystemMonitor'); const modelMonitorRoutes = require('./routes/ModelMonitor'); const systemControlRoutes = require('./routes/SystemControl'); const dataMonitorRoutes = require('./routes/DataMonitor'); +const dataCollectRoutes = require('./routes/DataCollect'); const app = express(); const PORT = process.env.PORT || 3000; @@ -97,6 +98,7 @@ app.use('/api/system-monitor', systemMonitorRoutes); app.use('/api/model-monitor', modelMonitorRoutes); app.use('/api/system-control', systemControlRoutes); app.use('/api/data-monitor', dataMonitorRoutes); +app.use('/api/data-collect', dataCollectRoutes); // 接口配置页面路由 app.get('/interface-config', (req, res) => { diff --git a/XNSimHtml/utils/xnCoreService.js b/XNSimHtml/utils/xnCoreService.js index b6d361a..c444f3c 100644 --- a/XNSimHtml/utils/xnCoreService.js +++ b/XNSimHtml/utils/xnCoreService.js @@ -62,7 +62,10 @@ try { 'XN_StopInjectContinuous': ['int', [StringType, 'int', StringType, 'int']], 'XN_InjectDataInterfaceFromCsv': ['int', [StringType, 'int', StringType, 'int', StringType, 'int']], 'XN_StopCsvDataInject': ['int', [StringType, 'int']], - 'XN_GetCsvDataInjectStatus': ['int', [StringType, 'int']] + 'XN_GetCsvDataInjectStatus': ['int', [StringType, 'int']], + 'XN_StartCollectData': ['int', [StringType, 'int', StringType, 'int', StringType, 'int']], + 'XN_GetCollectDataStatus': ['int', [StringType, 'int']], + 'XN_StopCollectData': ['int', [StringType, 'int']] }); } catch (error) { console.error(`加载 ${monitorLibName} 失败:`, error); @@ -501,6 +504,64 @@ function getCsvDataInjectStatus() { } } +// 启动数据采集 +function startCollectData(collectDataInfo, dcsFilePath) { + if (!monitorLib) { + return '监控服务器库未加载'; + } + try { + const errorMsg = Buffer.alloc(1024); + const result = monitorLib.XN_StartCollectData( + collectDataInfo, collectDataInfo.length, + dcsFilePath, dcsFilePath.length, + errorMsg, errorMsg.length + ); + + if (result !== 0) { + return `启动数据采集失败: ${errorMsg.toString('utf8').replace(/\0/g, '')}`; + } + return '启动数据采集成功'; + } catch (error) { + return `启动数据采集失败: ${error.message}`; + } +} + +// 获取数据采集状态 +function getCollectDataStatus() { + if (!monitorLib) { + return '监控服务器库未加载'; + } + try { + const infoMsg = Buffer.alloc(1024); + const result = monitorLib.XN_GetCollectDataStatus(infoMsg, infoMsg.length); + + if (result === -1) { + return `获取状态失败: ${infoMsg.toString('utf8').replace(/\0/g, '')}`; + } + return result; // 返回状态:0-成功,1-正在采集 + } catch (error) { + return `获取状态失败: ${error.message}`; + } +} + +// 停止数据采集 +function stopCollectData() { + if (!monitorLib) { + return '监控服务器库未加载'; + } + try { + const errorMsg = Buffer.alloc(1024); + const result = monitorLib.XN_StopCollectData(errorMsg, errorMsg.length); + + if (result !== 0) { + return `停止数据采集失败: ${errorMsg.toString('utf8').replace(/\0/g, '')}`; + } + return '停止数据采集成功'; + } catch (error) { + return `停止数据采集失败: ${error.message}`; + } +} + module.exports = { loginLib, monitorLib, @@ -527,5 +588,8 @@ module.exports = { stopInjectContinuous, injectDataInterfaceFromCsv, stopCsvDataInject, - getCsvDataInjectStatus + getCsvDataInjectStatus, + startCollectData, + getCollectDataStatus, + stopCollectData }; \ No newline at end of file