基本实现系统状态监控,待完善

This commit is contained in:
jinchao 2025-05-14 14:30:38 +08:00
parent 61947bb15f
commit df3b7f7853
17 changed files with 2561 additions and 22 deletions

74
XNMonitorServer/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,74 @@
{
"files.associations": {
"qsqlrecord": "cpp",
"*.tcc": "cpp",
"qsharedpointer": "cpp",
"optional": "cpp",
"variant": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"bitset": "cpp",
"chrono": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdint": "cpp",
"deque": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"ranges": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp"
}
}

View File

@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.16)
project(XNMonitorServer LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT fastcdr_FOUND)
find_package(fastcdr 2 REQUIRED)
endif()
if(NOT fastdds_FOUND)
find_package(fastdds 3 REQUIRED)
endif()
#
if(DEFINED ENV{XNCore})
set(XNCore_PATH $ENV{XNCore})
else()
message(FATAL_ERROR "Environment variable XNCore is not set.")
endif()
file(GLOB DDS_XNIDL_SOURCES_CXX "../XNCore/XNIDL/*.cxx")
find_package(OpenSSL REQUIRED)
find_package(nlohmann_json 3.9.1 REQUIRED)
add_library(XNMonitorServer SHARED
XNMonitorServer_global.h
TopicManager.h
TopicManager.cpp
DataReaderListenerImpl.h
TypeDefine.h
XNMonitorInterface.h
XNMonitorInterface.cpp
SystemInfoMonitor.h
SystemInfoMonitor.cpp
${DDS_XNIDL_SOURCES_CXX}
)
#
target_include_directories(XNMonitorServer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(XNMonitorServer PRIVATE
nlohmann_json::nlohmann_json
fastcdr
fastdds
OpenSSL::SSL
OpenSSL::Crypto
)
target_compile_definitions(XNMonitorServer PRIVATE LOGIN_LIBRARY)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${XNCore_PATH}" CACHE PATH "Install path prefix" FORCE)
endif()
include(GNUInstallDirs)
install(TARGETS XNMonitorServer
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION .
)

View File

@ -0,0 +1,51 @@
/**
* @file DataReaderListenerImpl.h
* @author jinchao
* @brief
* @version 1.0
* @date 2025-03-10
*
* @copyright Copyright (c) 2025 COMAC
*
*/
#pragma once
#include "TypeDefine.h"
/**
* @brief
* @tparam T
*/
template <typename T>
class DataReaderListenerImpl : public XNDataReaderListener
{
public:
/**
* @brief
* @param callback
*/
DataReaderListenerImpl(std::function<void(const T &)> callback) : callback_(callback) {}
/**
* @brief
* @param reader
*/
void on_data_available(XNDataReader *reader) override
{
XNSampleInfo info;
if (reader->take_next_sample(&data_, &info) == eprosima::fastdds::dds::RETCODE_OK
&& info.valid_data) {
callback_(data_);
}
}
private:
/**
* @brief
*/
T data_;
/**
* @brief
*/
std::function<void(const T &)> callback_;
};

View File

@ -0,0 +1,134 @@
#include "SystemInfoMonitor.h"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
SystemInfoMonitor::~SystemInfoMonitor()
{
//注销引擎状态订阅
TopicManager::Instance()->unregisterSubscriber("XNSim::XNSimStatus::XNEngineStatus");
//注销线程状态订阅
TopicManager::Instance()->unregisterSubscriber("XNSim::XNSimStatus::XNThreadStatus");
}
std::string SystemInfoMonitor::Initialize()
{
//注册引擎状态订阅
XNDDSErrorCode ret =
TopicManager::Instance()->registerSubscriber<XNSim::XNSimStatus::XNEngineStatusPubSubType>(
"XNSim::XNSimStatus::XNEngineStatus",
std::bind(&SystemInfoMonitor::EngineStatusListener, this, std::placeholders::_1));
if (ret != XNDDSErrorCode::SUCCESS) {
return "Failed to register engine status subscriber, error code: "
+ std::to_string(static_cast<int>(ret));
}
//注册线程状态订阅
ret =
TopicManager::Instance()->registerSubscriber<XNSim::XNSimStatus::XNThreadStatusPubSubType>(
"XNSim::XNSimStatus::XNThreadStatus",
std::bind(&SystemInfoMonitor::ThreadStatusListener, this, std::placeholders::_1));
if (ret != XNDDSErrorCode::SUCCESS) {
return "Failed to register thread status subscriber, error code: "
+ std::to_string(static_cast<int>(ret));
}
m_ThreadCycleCount.clear();
m_EngineStatusUpdate = false;
return "Success";
}
std::string SystemInfoMonitor::GetSystemInfo()
{
std::lock_guard<std::mutex> locker(m_EngineStatusMutex);
json jsonObj;
// 创建引擎状态JSON对象
json engineStatusObj;
engineStatusObj["engineName"] = m_EngineStatus.XNEngineName();
engineStatusObj["engineID"] = m_EngineStatus.XNEngineID();
engineStatusObj["engineStatus"] = m_EngineStatus.XNEngineSt();
std::string affinity;
for (int i = 0; i < sizeof(m_EngineStatus.XNEngineAff()); i++) {
if ((m_EngineStatus.XNEngineAff() >> i) & 1 == 1) {
affinity += std::to_string(i) + ",";
}
}
if (!affinity.empty()) {
affinity.pop_back(); // 移除最后一个逗号
}
engineStatusObj["engineAffinity"] = affinity;
engineStatusObj["threadCount"] = m_EngineStatus.XNThCnt();
// 创建核心状态JSON对象
json coreStatusObj;
const auto &coreStatus = m_EngineStatus.XNCoreSt();
coreStatusObj["fwStatus"] = coreStatus.XNFWStatus();
coreStatusObj["tmStatus"] = coreStatus.XNTMStatus();
coreStatusObj["emStatus"] = coreStatus.XNEMStatus();
coreStatusObj["sdStatus"] = coreStatus.XNSDStatus();
coreStatusObj["thmStatus"] = coreStatus.XNThMStatus();
coreStatusObj["mmStatus"] = coreStatus.XNMMStatus();
coreStatusObj["smStatus"] = coreStatus.XNSMStatus();
coreStatusObj["dmStatus"] = coreStatus.XNDMStatus();
// 将核心状态添加到引擎状态中
engineStatusObj["coreStatus"] = coreStatusObj;
// 将引擎状态添加到主JSON对象中
jsonObj["engineStatus"] = engineStatusObj;
m_EngineStatusUpdate = false;
return jsonObj.dump();
}
std::string SystemInfoMonitor::GetAllThreadInfo()
{
std::lock_guard<std::mutex> locker(m_ThreadStatusMutex);
json jsonObj;
// 创建线程状态JSON对象
json threadStatusObj;
for (const auto &status : m_ThreadStatus) {
json threadObj;
threadObj["threadName"] = status.second.XNThreadName();
std::string affinity;
for (int i = 0; i < sizeof(status.second.XNThreadAff()); i++) {
if ((status.second.XNThreadAff() >> i) & 1 == 1) {
affinity += std::to_string(i) + ",";
}
}
if (!affinity.empty()) {
affinity.pop_back(); // 移除最后一个逗号
}
threadObj["threadAffinity"] = affinity;
threadObj["threadPro"] = status.second.XNThreadPro();
uint64_t cycleCount = status.second.XNThRunCnt();
if (m_ThreadCycleCount.find(status.first) != m_ThreadCycleCount.end()) {
if (cycleCount <= m_ThreadCycleCount[status.first]) {
threadObj["threadStatus"] = 99;
continue;
}
}
threadObj["threadRunCount"] = cycleCount;
m_ThreadCycleCount[status.first] = cycleCount;
threadObj["threadStatus"] = status.second.XNThreadSt();
threadObj["threadCurrentFrequency"] = status.second.XNThCurFreq();
threadObj["threadSetFrequency"] = status.second.XNThSetFreq();
threadStatusObj[std::to_string(status.first)] = threadObj;
}
jsonObj["threadStatus"] = threadStatusObj;
return jsonObj.dump();
}
void SystemInfoMonitor::EngineStatusListener(const XNSim::XNSimStatus::XNEngineStatus &status)
{
std::lock_guard<std::mutex> locker(m_EngineStatusMutex);
m_EngineStatusUpdate = true;
m_EngineStatus = status;
}
void SystemInfoMonitor::ThreadStatusListener(const XNSim::XNSimStatus::XNThreadStatus &status)
{
std::lock_guard<std::mutex> locker(m_ThreadStatusMutex);
m_ThreadStatus[status.XNThreadID()] = status;
}

View File

@ -0,0 +1,59 @@
#ifndef SYSTEMINFOMONITOR_H
#define SYSTEMINFOMONITOR_H
#include "XNMonitorServer_global.h"
#include <map>
#include <mutex>
#include "TypeDefine.h"
#include "TopicManager.h"
#include "../XNCore/XNIDL/XNSimStatusPubSubTypes.hpp"
class XNMONITORSERVER_EXPORT SystemInfoMonitor
{
public:
explicit SystemInfoMonitor() {}
virtual ~SystemInfoMonitor();
public:
std::string Initialize();
std::string GetSystemInfo();
std::string GetAllThreadInfo();
private:
/**
* @brief
* @param status
*/
void EngineStatusListener(const XNSim::XNSimStatus::XNEngineStatus &status);
/**
* @brief 线
* @param status 线
*/
void ThreadStatusListener(const XNSim::XNSimStatus::XNThreadStatus &status);
private:
/**
* @brief
*/
std::mutex m_EngineStatusMutex;
std::mutex m_ThreadStatusMutex;
/**
* @brief
*/
XNSim::XNSimStatus::XNEngineStatus m_EngineStatus;
/**
* @brief
*/
bool m_EngineStatusUpdate = false;
/**
* @brief 线
*/
std::map<uint32_t, XNSim::XNSimStatus::XNThreadStatus> m_ThreadStatus;
/**
* @brief 线
*/
std::map<uint32_t, uint64_t> m_ThreadCycleCount;
};
#endif // SYSTEMINFOMONITOR_H

View File

@ -0,0 +1,16 @@
/**
* @file TopicManager.cpp
* @author jinchao
* @brief
* @version 1.0
* @date 2025-03-10
*
* @copyright Copyright (c) 2025 COMAC
*
*/
#include "TopicManager.h"
// 静态成员变量定义
TopicManager *TopicManager::instance = nullptr;
// 互斥锁
std::mutex TopicManager::instanceMutex;

299
XNMonitorServer/TopicManager.h Executable file
View File

@ -0,0 +1,299 @@
/**
* @file TopicManager.h
* @author jinchao
* @brief
* @version 1.0
* @date 2025-03-10
*
* @copyright Copyright (c) 2025 COMAC
*
*/
#pragma once
#include <map>
#include <mutex>
#include <memory>
#include <string>
#include "DataReaderListenerImpl.h"
#include <fastdds/dds/core/status/StatusMask.hpp>
/**
* @brief
*/
class TopicManager
{
public:
/**
* @brief
*/
TopicManager(const TopicManager &) = delete;
/**
* @brief
*/
TopicManager &operator=(const TopicManager &) = delete;
/**
* @brief
* @return TopicManager*:
*/
static TopicManager *Instance()
{
if (instance == nullptr) {
std::lock_guard<std::mutex> locker(instanceMutex);
if (instance == nullptr) { // 双重检查锁定
instance = new TopicManager();
}
}
return instance;
}
/**
* @brief
*/
static void cleanupParticipant()
{
std::lock_guard<std::mutex> locker(instanceMutex);
if (instance != nullptr) {
instance->clearAllTopic(); // 清理所有主题
if (instance->m_Participant != nullptr) {
eprosima::fastdds::dds::DomainParticipantFactory::get_instance()
->delete_participant(instance->m_Participant); // 删除参与者
instance->m_Participant = nullptr; // 设置参与者为空
}
delete instance; // 删除单例
instance = nullptr; // 设置单例为空
}
}
private:
/**
* @brief
*/
explicit TopicManager() {}
/**
* @brief
*/
virtual ~TopicManager() {}
public:
/**
* @brief
* @param domainId: ID
*/
XNDDSErrorCode initializeParticipant(int domainId)
{
XNParticipantQos participantQos = eprosima::fastdds::dds::PARTICIPANT_QOS_DEFAULT;
participantQos.name("XNMonitor"); // 设置参与者名称
m_Participant =
eprosima::fastdds::dds::DomainParticipantFactory::get_instance()->create_participant(
domainId, participantQos); // 创建参与者
if (m_Participant == nullptr) {
return XNDDSErrorCode::INIT_FAILED;
}
return XNDDSErrorCode::SUCCESS;
};
/**
* @brief
* @tparam T:
* @param topicName:
* @return XNDataWriter*:
*/
template <typename T>
XNDDSErrorCode registerPublisher(const std::string &topicName,
XNDataWriter *dataWriter = nullptr)
{
std::lock_guard<std::mutex> locker(m_Mutex);
if (topics_.find(topicName) == topics_.end()) {
topics_[topicName] = TopicInfo(); // 创建主题信息
TopicInfo &tmp = topics_[topicName]; // 获取主题信息
XNTypeSupport typeSupport(new T()); // 创建类型支持
typeSupport.register_type(m_Participant); // 注册类型
tmp.topic =
m_Participant->create_topic(topicName.c_str(), typeSupport.get_type_name(),
eprosima::fastdds::dds::TOPIC_QOS_DEFAULT); // 创建主题
if (tmp.topic == nullptr) {
topics_.erase(topicName); // 移除主题
return XNDDSErrorCode::TOPIC_CREATE_FAILED;
}
}
TopicInfo &topicInfo = topics_[topicName]; // 获取主题信息
if (topicInfo.publisher == nullptr) {
topicInfo.publisher = m_Participant->create_publisher(
eprosima::fastdds::dds::PUBLISHER_QOS_DEFAULT); // 创建发布者
if (topicInfo.publisher == nullptr) {
return XNDDSErrorCode::PUBLISHER_CREATE_FAILED;
}
}
if (topicInfo.dataWriter == nullptr) {
XNDataWriterQos dataWriterQos;
// 设置数据写入器的持久性策略, 使用瞬态本地持久性
dataWriterQos.durability().kind = eprosima::fastdds::dds::VOLATILE_DURABILITY_QOS;
// 设置数据写入器的生命周期策略, 设置为5秒
dataWriterQos.lifespan().duration = eprosima::fastdds::dds::Duration_t(5, 0);
topicInfo.dataWriter = topicInfo.publisher->create_datawriter(
topicInfo.topic, dataWriterQos); // 创建数据写入器
if (topicInfo.dataWriter == nullptr) {
return XNDDSErrorCode::DATAWRITER_CREATE_FAILED;
}
}
dataWriter = topicInfo.dataWriter;
return XNDDSErrorCode::SUCCESS;
}
/**
* @brief
* @param topicName:
*/
void unregisterPublisher(const std::string &topicName)
{
std::lock_guard<std::mutex> locker(m_Mutex);
auto it = topics_.find(topicName);
if (it != topics_.end()) {
TopicInfo &topicInfo = it->second; // 获取主题信息
if (topicInfo.dataWriter != nullptr) {
topicInfo.publisher->delete_datawriter(topicInfo.dataWriter); // 删除数据写入器
topicInfo.dataWriter = nullptr; // 设置数据写入器为空
}
if (topicInfo.publisher != nullptr) {
m_Participant->delete_publisher(topicInfo.publisher); // 删除发布者
topicInfo.publisher = nullptr; // 设置发布者为空
}
if (topicInfo.publisher == nullptr && topicInfo.subscriber == nullptr
&& topicInfo.topic != nullptr) {
m_Participant->delete_topic(topicInfo.topic); // 删除主题
topicInfo.topic = nullptr; // 设置主题为空
topics_.erase(it); // 移除主题
}
}
}
/**
* @brief
* @tparam T:
* @param topicName:
* @param fun:
*/
template <typename T>
XNDDSErrorCode registerSubscriber(const std::string &topicName,
std::function<void(const typename T::type &)> fun)
{
std::lock_guard<std::mutex> locker(m_Mutex);
if (topics_.find(topicName) == topics_.end()) {
topics_[topicName] = TopicInfo(); // 创建主题信息
TopicInfo &tmp = topics_[topicName]; // 获取主题信息
XNTypeSupport typeSupport(new T()); // 创建类型支持
typeSupport.register_type(m_Participant); // 注册类型
tmp.topic =
m_Participant->create_topic(topicName.c_str(), typeSupport.get_type_name(),
eprosima::fastdds::dds::TOPIC_QOS_DEFAULT); // 创建主题
if (tmp.topic == nullptr) {
topics_.erase(topicName); // 移除主题
return XNDDSErrorCode::TOPIC_CREATE_FAILED; // 返回
}
}
TopicInfo &topicInfo = topics_[topicName]; // 获取主题信息
if (topicInfo.subscriber == nullptr) {
topicInfo.subscriber = m_Participant->create_subscriber(
eprosima::fastdds::dds::SUBSCRIBER_QOS_DEFAULT); // 创建订阅者
if (topicInfo.subscriber == nullptr) {
return XNDDSErrorCode::SUBSCRIBER_CREATE_FAILED; // 返回
}
}
if (topicInfo.dataReader == nullptr) {
XNDataReaderQos dataReaderQos;
dataReaderQos.durability().kind =
eprosima::fastdds::dds::VOLATILE_DURABILITY_QOS; // 设置数据读取器的持久性策略
topicInfo.listener =
new DataReaderListenerImpl<typename T::type>(fun); // 创建数据读取器监听器
topicInfo.dataReader = topicInfo.subscriber->create_datareader(
topicInfo.topic, dataReaderQos, topicInfo.listener); // 创建数据读取器
if (topicInfo.dataReader == nullptr) {
return XNDDSErrorCode::DATAREADER_CREATE_FAILED; // 返回
}
} else {
auto oldListener = topicInfo.dataReader->get_listener(); // 获取旧的监听器
topicInfo.listener =
new DataReaderListenerImpl<typename T::type>(fun); // 创建新的监听器
topicInfo.dataReader->set_listener(
topicInfo.listener,
eprosima::fastdds::dds::StatusMask::all()); // 设置新的监听器
delete oldListener; // 删除旧的监听器
}
return XNDDSErrorCode::SUCCESS;
}
/**
* @brief
* @param topicName:
*/
void unregisterSubscriber(const std::string &topicName)
{
std::lock_guard<std::mutex> locker(m_Mutex);
auto it = topics_.find(topicName);
if (it != topics_.end()) {
TopicInfo &topicInfo = it->second; // 获取主题信息
if (topicInfo.dataReader != nullptr) {
topicInfo.subscriber->delete_datareader(topicInfo.dataReader); // 删除数据读取器
topicInfo.dataReader = nullptr; // 设置数据读取器为空
}
if (topicInfo.listener != nullptr) {
delete topicInfo.listener; // 删除监听器
topicInfo.listener = nullptr; // 设置监听器为空
}
if (topicInfo.subscriber != nullptr) {
m_Participant->delete_subscriber(topicInfo.subscriber); // 删除订阅者
topicInfo.subscriber = nullptr; // 设置订阅者为空
}
if (topicInfo.subscriber == nullptr && topicInfo.publisher == nullptr
&& topicInfo.topic != nullptr) {
m_Participant->delete_topic(topicInfo.topic); // 删除主题
topicInfo.topic = nullptr; // 设置主题为空
topics_.erase(it); // 移除主题
}
}
}
private:
/**
* @brief
*/
void clearAllTopic()
{
std::lock_guard<std::mutex> locker(m_Mutex);
if (m_Participant != nullptr) {
while (!topics_.empty()) {
unregisterPublisher(topics_.begin()->first); // 注销发布者
unregisterSubscriber(topics_.begin()->first); // 注销订阅者
}
}
}
private:
/**
* @brief
*/
static TopicManager *instance;
/**
* @brief
*/
static std::mutex instanceMutex;
/**
* @brief
*/
XNParticipant *m_Participant = nullptr;
/**
* @brief
*/
std::map<std::string, TopicInfo> topics_;
/**
* @brief 访
*/
std::mutex m_Mutex;
};

280
XNMonitorServer/TypeDefine.h Executable file
View File

@ -0,0 +1,280 @@
/**
* @file TypeDefine.h
* @brief
*/
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/topic/Topic.hpp>
#include <fastdds/dds/topic/TopicDataType.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/qos/PublisherQos.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/qos/DataWriterQos.hpp>
/**
* @brief
*/
using XNParticipant = eprosima::fastdds::dds::DomainParticipant;
/**
* @brief Qos
*/
using XNParticipantQos = eprosima::fastdds::dds::DomainParticipantQos;
/**
* @brief
*/
using XNParticipantFactory = eprosima::fastdds::dds::DomainParticipantFactory;
/**
* @brief
*/
using XNDataReaderListener = eprosima::fastdds::dds::DataReaderListener;
/**
* @brief
*/
using XNDataReader = eprosima::fastdds::dds::DataReader;
/**
* @brief Qos
*/
using XNDataReaderQos = eprosima::fastdds::dds::DataReaderQos;
/**
* @brief
*/
using XNSampleInfo = eprosima::fastdds::dds::SampleInfo;
/**
* @brief
*/
using XNSubscriber = eprosima::fastdds::dds::Subscriber;
/**
* @brief
*/
using XNTypeSupport = eprosima::fastdds::dds::TypeSupport;
/**
* @brief
*/
using XNDataWriter = eprosima::fastdds::dds::DataWriter;
/**
* @brief Qos
*/
using XNDataWriterQos = eprosima::fastdds::dds::DataWriterQos;
/**
* @brief
*/
using XNPublisher = eprosima::fastdds::dds::Publisher;
/**
* @brief Qos
*/
using XNPublisherQos = eprosima::fastdds::dds::PublisherQos;
/**
* @brief
*/
using XNTopic = eprosima::fastdds::dds::Topic;
/**
* @brief
*/
using XNTopicDataType = eprosima::fastdds::dds::TopicDataType;
/**
* @brief
*/
struct TopicInfo {
/**
* @brief
*/
XNTopic *topic;
/**
* @brief
*/
XNPublisher *publisher;
/**
* @brief
*/
XNDataWriter *dataWriter;
/**
* @brief
*/
XNSubscriber *subscriber;
/**
* @brief
*/
XNDataReader *dataReader;
/**
* @brief
*/
XNDataReaderListener *listener;
};
// 错误码定义
enum class XNDDSErrorCode {
SUCCESS = 0,
INIT_FAILED = -1,
TOPIC_CREATE_FAILED = -2,
PUBLISHER_CREATE_FAILED = -3,
SUBSCRIBER_CREATE_FAILED = -4,
DATAWRITER_CREATE_FAILED = -5,
DATAREADER_CREATE_FAILED = -6,
INVALID_PARAM = -7,
NOT_INITIALIZED = -8
};
/**
* @brief
*/
struct XNRuntimeData {
/**
* @brief
*/
std::string m_name;
/**
* @brief 线ID或模型ID
*/
unsigned int m_id;
/**
* @brief
*/
unsigned int m_RunningState;
/**
* @brief CPU亲和性掩码
* @note 线CPU亲和性掩码CPU 0~31
* @note 线
*/
unsigned int m_AffinityMask;
/**
* @brief ID
* @note
*/
unsigned int m_NodeID;
/**
* @brief
*/
unsigned int m_Priority;
/**
* @brief
*/
double m_SetFrequency;
/**
* @brief
*/
std::vector<double> m_CurrentFrequency;
/**
* @brief
*/
double m_MaxFrequency;
/**
* @brief
*/
double m_MinFrequency;
/**
* @brief
*/
double m_AvgFrequency;
/**
* @brief
*/
double m_CycleCount;
/**
* @brief
*/
double m_SetPeriod;
/**
* @brief
*/
std::vector<double> m_CurrentPeriod;
/**
* @brief
*/
double m_MaxPeriod;
/**
* @brief
*/
double m_MinPeriod;
/**
* @brief
*/
double m_AvgPeriod;
};
/**
* @brief
* @note
*/
struct MemberVariable {
/**
* @brief
*/
std::string dataType;
/**
* @brief
*/
std::string variableName;
/**
* @brief
*/
bool isArray;
/**
* @brief
*/
std::vector<int> arraySizes;
/**
* @brief
*/
std::string description;
};
/**
* @brief
* @note
*/
struct StructDefinition {
/**
* @brief
*/
std::string structName;
/**
* @brief
*/
std::vector<std::shared_ptr<MemberVariable>> memberVariables;
};
/**
* @brief
* @note
*/
struct NamespaceDefinition {
/**
* @brief
*/
std::string namespaceName;
/**
* @brief
*/
std::vector<std::shared_ptr<StructDefinition>> structDefinitions;
/**
* @brief
*/
std::vector<std::shared_ptr<NamespaceDefinition>> childNamespaces;
};
/**
* @brief
* @note
*/
struct ModelDefinition {
/**
* @brief
*/
std::string modelName;
/**
* @brief
*/
std::vector<std::shared_ptr<NamespaceDefinition>> namespaceDefinitions;
};

View File

@ -0,0 +1,372 @@
/**
* @file XNMonitorInterface.cpp
* @brief
*/
#include "XNMonitorInterface.h"
#include "TopicManager.h"
#include <string>
#include <mutex>
#include "SystemInfoMonitor.h"
// 全局变量
static std::string g_lastError;
static std::mutex g_errorMutex;
static bool g_initialized = false;
SystemInfoMonitor *systemInfoMonitor = nullptr;
bool g_systemInfoMonitorStarted = false;
// 设置错误信息
static void SetLastError(const char *error)
{
std::lock_guard<std::mutex> lock(g_errorMutex);
g_lastError = error;
}
// 初始化函数实现
int XN_Initialize(const char *domainId, int domainIdLen, char *errorMsg, int errorMsgSize)
{
if (g_initialized) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "DDSMonitor Initialized Successfully", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return 0;
}
try {
if (!domainId || domainIdLen <= 0) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "Invalid domainId", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return -1;
}
// 创建临时字符串确保以null结尾
std::string domainIdStr(domainId, domainIdLen);
int domainIdInt = std::stoi(domainIdStr);
if (domainIdInt < 0 || domainIdInt > 232) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "Invalid domainId", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return -1;
}
XNDDSErrorCode ret = TopicManager::Instance()->initializeParticipant(domainIdInt);
if (ret != XNDDSErrorCode::SUCCESS) {
if (errorMsg && errorMsgSize > 0) {
std::string error = "Failed to initialize DDSMonitor, error code: "
+ std::to_string(static_cast<int>(ret));
strncpy(errorMsg, error.c_str(), errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return -1;
}
g_initialized = true;
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "DDSMonitor Initialized Successfully", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return 0;
} catch (const std::exception &e) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, e.what(), errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return -1;
}
}
int XN_StartMonitorSystemInfo(char *errorMsg, int errorMsgSize)
{
if (!g_initialized) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "DDSMonitor Not Initialized", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return -1;
}
try {
systemInfoMonitor = new SystemInfoMonitor();
std::string ret = systemInfoMonitor->Initialize();
if (ret == "Success") {
g_systemInfoMonitorStarted = true;
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, ret.c_str(), errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return 0;
} else {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, ret.c_str(), errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return -1;
}
} catch (const std::exception &e) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, e.what(), errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return -1;
}
}
int XN_GetSystemInfo(char *infoMsg, int infoMsgSize)
{
if (!g_systemInfoMonitorStarted) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "System Info Monitor Not Started", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
try {
std::string info = systemInfoMonitor->GetSystemInfo();
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, info.c_str(), infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return 0;
} catch (const std::exception &e) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, e.what(), infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
}
int XN_GetAllThreadInfo(char *infoMsg, int infoMsgSize)
{
if (!g_systemInfoMonitorStarted) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "System Info Monitor Not Started", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
try {
std::string info = systemInfoMonitor->GetAllThreadInfo();
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, info.c_str(), infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return 0;
} catch (const std::exception &e) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, e.what(), infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
}
void XN_StopMonitorSystemInfo()
{
if (g_systemInfoMonitorStarted) {
delete systemInfoMonitor;
systemInfoMonitor = nullptr;
g_systemInfoMonitorStarted = false;
}
}
// 清理函数实现
void XN_Cleanup()
{
if (g_initialized) {
TopicManager::cleanupParticipant();
g_initialized = false;
}
}
// // 注册发布者实现
// XNDDSErrorCode XN_RegisterPublisher(const char *topicName, void **dataWriter)
// {
// if (!g_initialized) {
// SetLastError("Not initialized");
// return XNDDSErrorCode::NOT_INITIALIZED;
// }
// if (!topicName || !dataWriter) {
// SetLastError("Invalid parameters");
// return XNDDSErrorCode::INVALID_PARAM;
// }
// try {
// auto *writer =
// TopicManager::Instance()->registerPublisher<XNRuntimeData>(topicName);
// if (!writer) {
// SetLastError("Failed to create publisher");
// return XNDDSErrorCode::PUBLISHER_CREATE_FAILED;
// }
// *dataWriter = writer;
// return XNDDSErrorCode::SUCCESS;
// } catch (const std::exception &e) {
// SetLastError(e.what());
// return XNDDSErrorCode::PUBLISHER_CREATE_FAILED;
// }
// }
// // 注销发布者实现
// XNDDSErrorCode XN_UnregisterPublisher(const char *topicName)
// {
// if (!g_initialized) {
// SetLastError("Not initialized");
// return XNDDSErrorCode::NOT_INITIALIZED;
// }
// if (!topicName) {
// SetLastError("Invalid parameters");
// return XNDDSErrorCode::INVALID_PARAM;
// }
// try {
// TopicManager::Instance()->unregisterPublisher(topicName);
// return XNDDSErrorCode::SUCCESS;
// } catch (const std::exception &e) {
// SetLastError(e.what());
// return XNDDSErrorCode::INVALID_PARAM;
// }
// }
// // 数据写入实现
// XNDDSErrorCode XN_WriteData(void *dataWriter, const void *data, size_t dataSize)
// {
// if (!g_initialized) {
// SetLastError("Not initialized");
// return XNDDSErrorCode::NOT_INITIALIZED;
// }
// if (!dataWriter || !data || dataSize != sizeof(XNRuntimeData)) {
// SetLastError("Invalid parameters");
// return XNDDSErrorCode::INVALID_PARAM;
// }
// try {
// auto *writer = static_cast<XNDataWriter *>(dataWriter);
// if (writer->write(data) != eprosima::fastdds::dds::RETCODE_OK) {
// SetLastError("Failed to write data");
// return XNDDSErrorCode::INVALID_PARAM;
// }
// return XNDDSErrorCode::SUCCESS;
// } catch (const std::exception &e) {
// SetLastError(e.what());
// return XNDDSErrorCode::INVALID_PARAM;
// }
// }
// // 订阅回调包装器
// class SubscriberCallbackWrapper
// {
// public:
// SubscriberCallbackWrapper(XNDataCallback callback, void *userData)
// : callback_(callback), userData_(userData)
// {
// }
// void operator()(const XNRuntimeData &data)
// {
// if (callback_) {
// callback_(&data, sizeof(XNRuntimeData), userData_);
// }
// }
// private:
// XNDataCallback callback_;
// void *userData_;
// };
// // 注册订阅者实现
// XNDDSErrorCode XN_RegisterSubscriber(const char *topicName, XNDataCallback callback, void *userData)
// {
// if (!g_initialized) {
// SetLastError("Not initialized");
// return XNDDSErrorCode::NOT_INITIALIZED;
// }
// if (!topicName || !callback) {
// SetLastError("Invalid parameters");
// return XNDDSErrorCode::INVALID_PARAM;
// }
// try {
// // auto wrapper = std::make_shared<SubscriberCallbackWrapper>(callback, userData);
// // TopicManager::Instance()->registerSubscriber<XNRuntimeData>(
// // topicName, [wrapper](const XNRuntimeData &data) { (*wrapper)(data); });
// return XNDDSErrorCode::SUCCESS;
// } catch (const std::exception &e) {
// SetLastError(e.what());
// return XNDDSErrorCode::SUBSCRIBER_CREATE_FAILED;
// }
// }
// // 注销订阅者实现
// XNDDSErrorCode XN_UnregisterSubscriber(const char *topicName)
// {
// if (!g_initialized) {
// SetLastError("Not initialized");
// return XNDDSErrorCode::NOT_INITIALIZED;
// }
// if (!topicName) {
// SetLastError("Invalid parameters");
// return XNDDSErrorCode::INVALID_PARAM;
// }
// try {
// TopicManager::Instance()->unregisterSubscriber(topicName);
// return XNDDSErrorCode::SUCCESS;
// } catch (const std::exception &e) {
// SetLastError(e.what());
// return XNDDSErrorCode::INVALID_PARAM;
// }
// }
// // 获取运行时数据实现
// XNDDSErrorCode XN_GetRuntimeData(const char *name, XNRuntimeData *data)
// {
// if (!g_initialized) {
// SetLastError("Not initialized");
// return XNDDSErrorCode::NOT_INITIALIZED;
// }
// if (!name || !data) {
// SetLastError("Invalid parameters");
// return XNDDSErrorCode::INVALID_PARAM;
// }
// // TODO: 实现从数据存储中获取运行时数据
// return XNDDSErrorCode::SUCCESS;
// }
// // 设置运行时数据实现
// XNDDSErrorCode XN_SetRuntimeData(const char *name, const XNRuntimeData *data)
// {
// if (!g_initialized) {
// SetLastError("Not initialized");
// return XNDDSErrorCode::NOT_INITIALIZED;
// }
// if (!name || !data) {
// SetLastError("Invalid parameters");
// return XNDDSErrorCode::INVALID_PARAM;
// }
// // TODO: 实现将运行时数据保存到数据存储中
// return XNDDSErrorCode::SUCCESS;
// }
// // 获取最后错误信息实现
// const char *XN_GetLastError()
// {
// std::lock_guard<std::mutex> lock(g_errorMutex);
// return g_lastError.c_str();
// }

View File

@ -0,0 +1,57 @@
/**
* @file XNMonitorInterface.h
* @brief
*/
#pragma once
#include "TypeDefine.h"
#include "XNMonitorServer_global.h"
#include <cstdint>
#ifdef __cplusplus
extern "C"
{
#endif
// 初始化函数
int XNMONITORSERVER_EXPORT XN_Initialize(const char *domainId, int domainIdLen, char *errorMsg,
int errorMsgSize);
// 清理函数
void XNMONITORSERVER_EXPORT XN_Cleanup();
// 启动监控系统信息
int XNMONITORSERVER_EXPORT XN_StartMonitorSystemInfo(char *errorMsg, int errorMsgSize);
// 获取系统信息
int XNMONITORSERVER_EXPORT XN_GetSystemInfo(char *infoMsg, int infoMsgSize);
// 获取所有线程信息
int XNMONITORSERVER_EXPORT XN_GetAllThreadInfo(char *infoMsg, int infoMsgSize);
// 停止监控系统信息
void XNMONITORSERVER_EXPORT XN_StopMonitorSystemInfo();
// // 主题管理接口
// XNDDSErrorCode XN_RegisterPublisher(const char *topicName, void **dataWriter);
// XNDDSErrorCode XN_UnregisterPublisher(const char *topicName);
// // 数据写入接口
// XNDDSErrorCode XN_WriteData(void *dataWriter, const void *data, size_t dataSize);
// // 订阅回调函数类型
// typedef void (*XNDataCallback)(const void *data, size_t dataSize, void *userData);
// // 订阅接口
// XNDDSErrorCode XN_RegisterSubscriber(const char *topicName, XNDataCallback callback,
// void *userData);
// XNDDSErrorCode XN_UnregisterSubscriber(const char *topicName);
// // 运行时数据接口
// XNDDSErrorCode XN_GetRuntimeData(const char *name, XNRuntimeData *data);
// XNDDSErrorCode XN_SetRuntimeData(const char *name, const XNRuntimeData *data);
// 错误信息获取
//const char *MONITOR_EXPORT XN_GetLastError();
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,7 @@
#pragma once
#ifdef XNMonitorServer_EXPORTS
# define XNMONITORSERVER_EXPORT __attribute__((visibility("default")))
#else
# define XNMONITORSERVER_EXPORT __attribute__((visibility("default")))
#endif

View File

@ -2,10 +2,255 @@ class SimulationMonitor extends HTMLElement {
constructor() { constructor() {
super(); super();
this.attachShadow({ mode: 'open' }); this.attachShadow({ mode: 'open' });
this.monitorStatus = {
isMonitoring: false,
lastError: null
};
this.systemInfo = null;
this.threadInfo = null;
this.statusCheckInterval = null;
this.chart = null;
this.chartInitialized = false;
} }
connectedCallback() { connectedCallback() {
this.render(); this.render();
// 等待组件完全加载
setTimeout(() => {
this.initializeComponent();
}, 100);
}
initializeComponent() {
if (this.chartInitialized) return;
try {
this.initChart();
this.startStatusCheck();
this.chartInitialized = true;
} catch (error) {
console.error('初始化组件失败:', error);
}
}
disconnectedCallback() {
this.stopStatusCheck();
}
startStatusCheck() {
this.checkMonitorStatus();
this.statusCheckInterval = setInterval(() => {
this.checkMonitorStatus();
}, 1000);
}
stopStatusCheck() {
if (this.statusCheckInterval) {
clearInterval(this.statusCheckInterval);
this.statusCheckInterval = null;
}
}
async checkMonitorStatus() {
try {
// 获取监控状态
const statusResponse = await fetch('/api/system-monitor/status');
const statusData = await statusResponse.json();
this.monitorStatus = statusData;
if (this.monitorStatus.isMonitoring) {
// 获取系统信息
const systemResponse = await fetch('/api/system-monitor/system-info');
const systemData = await systemResponse.json();
this.systemInfo = systemData.data;
console.log('系统信息:', this.systemInfo);
// 获取线程信息
const threadResponse = await fetch('/api/system-monitor/thread-info');
const threadData = await threadResponse.json();
this.threadInfo = threadData.data;
}
this.updateUI();
} catch (error) {
console.error('获取监控状态失败:', error);
this.monitorStatus.lastError = error.message;
this.updateUI();
}
}
updateUI() {
const input = this.shadowRoot.querySelector('.domain-input');
const startButton = this.shadowRoot.querySelector('.start-button');
const stopButton = this.shadowRoot.querySelector('.stop-button');
const statusDisplay = this.shadowRoot.querySelector('.status-display');
const engineInfo = this.shadowRoot.querySelector('#engine-info');
const coreStatus = this.shadowRoot.querySelector('#core-status');
const threadTableBody = this.shadowRoot.querySelector('#thread-table-body');
if (this.monitorStatus.isMonitoring) {
input.disabled = true;
startButton.disabled = true;
stopButton.disabled = false;
} else {
input.disabled = false;
startButton.disabled = false;
stopButton.disabled = true;
}
// 更新状态显示
statusDisplay.textContent = `监控状态: ${this.monitorStatus.isMonitoring ? '运行中' : '已停止'}`;
if (this.monitorStatus.lastError) {
statusDisplay.textContent += ` (错误: ${this.monitorStatus.lastError})`;
}
// 更新引擎信息
const engineInfoFields = [
{ label: '引擎名称', key: 'name' },
{ label: '引擎ID', key: 'id' },
{ label: '引擎状态', key: 'status' },
{ label: '引擎亲和性', key: 'affinity' },
{ label: '线程数', key: 'threadCount' }
];
engineInfo.innerHTML = `
<div class="status-grid">
${engineInfoFields.map(field => `
<div class="status-item">
<div class="status-label">${field.label}</div>
<div class="status-value">${this.monitorStatus.isMonitoring && this.systemInfo?.engineInfo ?
(this.systemInfo.engineInfo[field.key] || '未知') :
(field.key === 'status' ? '未运行' : '未知')}</div>
</div>
`).join('')}
</div>
`;
// 更新核心状态
const coreStatusFields = [
{ label: '主框架状态', key: 'fwStatus' },
{ label: '时间管理器状态', key: 'tmStatus' },
{ label: '事件管理器状态', key: 'emStatus' },
{ label: '环境管理器状态', key: 'sdStatus' },
{ label: '线程管理器状态', key: 'thmStatus' },
{ label: '模型管理器状态', key: 'mmStatus' },
{ label: '服务管理器状态', key: 'smStatus' },
{ label: 'DDS管理器状态', key: 'dmStatus' }
];
// 确保coreStatus元素存在
if (!coreStatus) {
console.error('找不到核心状态元素');
return;
}
// 无论是否有数据,都显示状态项
coreStatus.innerHTML = `
<div class="status-grid">
${coreStatusFields.map(field => `
<div class="status-item">
<div class="status-label">${field.label}</div>
<div class="status-value">${this.monitorStatus.isMonitoring && this.systemInfo?.coreStatus ?
(this.systemInfo.coreStatus[field.key] || '未知') : '未知'}</div>
</div>
`).join('')}
</div>
`;
// 更新线程表格
if (this.monitorStatus.isMonitoring && this.threadInfo && Array.isArray(this.threadInfo)) {
threadTableBody.innerHTML = this.threadInfo.map(thread => `
<tr>
<td>${thread.name || '未知'}</td>
<td>${thread.id || '未知'}</td>
<td>${thread.status || '未知'}</td>
<td>${thread.priority || '未知'}</td>
<td>${thread.runCount || '0'}</td>
<td>${(thread.currentFrequency || 0).toFixed(2)}</td>
<td>${(thread.setFrequency || 0).toFixed(2)}</td>
<td>${(thread.currentPeriod || 0).toFixed(2)}</td>
</tr>
`).join('');
// 更新图表数据
this.updateChartData();
} else {
threadTableBody.innerHTML = '<tr><td colspan="8" style="text-align: center;">暂无线程信息</td></tr>';
// 清空图表数据
if (this.chart) {
this.chart.data.labels = [];
this.chart.data.datasets = [];
this.chart.update();
}
}
}
async startMonitoring() {
const domainId = this.shadowRoot.querySelector('.domain-input').value.trim();
// 验证域ID是否为有效的数字字符串
if (!/^\d+$/.test(domainId)) {
console.error('域ID必须是有效的数字');
return;
}
try {
// 首先检查DDS监控状态
const ddsStatusResponse = await fetch('/api/dds-monitor/status');
const ddsStatusData = await ddsStatusResponse.json();
// 如果DDS监控未初始化先初始化DDS监控
if (!ddsStatusData.isInitialized) {
const ddsInitResponse = await fetch('/api/dds-monitor/initialize', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ domainId })
});
if (!ddsInitResponse.ok) {
const errorData = await ddsInitResponse.json();
console.error('DDS监控初始化失败:', errorData.error);
return;
}
}
// 启动系统监控
const response = await fetch('/api/system-monitor/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ domainId })
});
const data = await response.json();
if (response.ok) {
this.monitorStatus = data.status;
this.updateUI();
} else {
console.error('启动监控失败:', data.error);
}
} catch (error) {
console.error('启动监控失败:', error);
}
}
async stopMonitoring() {
try {
const response = await fetch('/api/system-monitor/stop', {
method: 'POST'
});
const data = await response.json();
if (response.ok) {
this.monitorStatus = data.status;
this.updateUI();
} else {
console.error('停止监控失败:', data.error);
}
} catch (error) {
console.error('停止监控失败:', error);
}
} }
render() { render() {
@ -20,37 +265,464 @@ class SimulationMonitor extends HTMLElement {
} }
.monitor-container { .monitor-container {
background-color: white; display: flex;
border-radius: 8px; flex-direction: column;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); gap: 16px;
padding: 16px;
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
.monitor-header { .toolbar-section {
display: flex; background-color: white;
justify-content: space-between; border-radius: 8px;
align-items: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);
margin-bottom: 20px; padding: 16px;
padding-bottom: 10px;
border-bottom: 1px solid #e0e0e0;
} }
.monitor-title { .content-container {
font-size: 18px; display: grid;
font-weight: bold; grid-template-columns: 300px 1fr;
gap: 16px;
flex: 1;
}
.left-panel {
display: flex;
flex-direction: column;
gap: 16px;
}
.right-panel {
display: flex;
flex-direction: column;
gap: 16px;
}
.panel-section {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 16px;
}
.toolbar {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
}
.toolbar-left {
display: flex;
gap: 12px;
align-items: center;
}
.input-group {
display: flex;
align-items: center;
gap: 8px;
}
.input-label {
font-size: 14px;
color: #333; color: #333;
white-space: nowrap;
}
.domain-input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
width: 200px;
}
.domain-input:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
.control-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.control-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.start-button {
background-color: #4CAF50;
color: white;
}
.start-button:hover:not(:disabled) {
background-color: #45a049;
}
.stop-button {
background-color: #f44336;
color: white;
}
.stop-button:hover:not(:disabled) {
background-color: #da190b;
}
.status-display {
padding: 8px 12px;
background-color: #f5f5f5;
border-radius: 4px;
font-size: 14px;
color: #666;
}
.info-grid {
display: flex;
flex-direction: column;
gap: 8px;
}
.info-item {
background-color: #f8f9fa;
padding: 12px;
border-radius: 4px;
border: 1px solid #e9ecef;
}
.info-item .label {
color: #666;
font-size: 13px;
display: block;
margin-bottom: 4px;
}
.info-item .value {
color: #333;
font-size: 16px;
font-weight: 500;
}
h3 {
margin: 0 0 12px 0;
color: #333;
font-size: 16px;
}
.thread-table {
width: 100%;
border-collapse: collapse;
margin-top: 8px;
}
.thread-table th,
.thread-table td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #e9ecef;
}
.thread-table th {
background-color: #f8f9fa;
font-weight: 500;
color: #333;
}
.thread-table tr:hover {
background-color: #f8f9fa;
}
.chart-container {
position: relative;
width: 100%;
height: 300px;
margin: 0;
padding: 0;
}
#thread-chart {
width: 100% !important;
height: 100% !important;
display: block;
}
.chart-controls {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #2196F3;
}
input:checked + .slider:before {
transform: translateX(26px);
}
.status-grid {
display: flex;
flex-direction: column;
gap: 8px;
}
.status-item {
display: flex;
align-items: center;
padding: 8px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #e9ecef;
}
.status-label {
flex: 1;
color: #666;
font-size: 14px;
}
.status-value {
flex: 1;
color: #333;
font-size: 14px;
font-weight: 500;
text-align: right;
} }
</style> </style>
<div class="monitor-container"> <div class="monitor-container">
<div class="monitor-header"> <div class="toolbar-section">
<div class="monitor-title">仿真监控</div> <div class="toolbar">
<div class="toolbar-left">
<div class="input-group">
<label class="input-label">DDS通信域ID</label>
<input type="text" class="domain-input" value="10">
</div>
<button class="control-button start-button" onclick="this.getRootNode().host.startMonitoring()">开始监控</button>
<button class="control-button stop-button" onclick="this.getRootNode().host.stopMonitoring()">停止监控</button>
</div>
<div class="status-display">监控状态: 未启动</div>
</div>
</div>
<div class="content-container">
<div class="left-panel">
<div class="panel-section">
<h3>引擎信息</h3>
<div class="info-grid" id="engine-info"></div>
</div>
<div class="panel-section">
<h3>核心状态</h3>
<div class="info-grid" id="core-status"></div>
</div>
</div>
<div class="right-panel">
<div class="panel-section">
<h3>线程信息</h3>
<table class="thread-table">
<thead>
<tr>
<th>线程名称</th>
<th>线程ID</th>
<th>状态</th>
<th>优先级</th>
<th>运行次数</th>
<th>当前频率(Hz)</th>
<th>设置频率(Hz)</th>
<th>当前周期(ms)</th>
</tr>
</thead>
<tbody id="thread-table-body"></tbody>
</table>
</div>
<div class="panel-section">
<h3>线程监控</h3>
<div class="chart-container">
<canvas id="thread-chart"></canvas>
</div>
<div class="chart-controls">
<span>显示类型</span>
<label class="switch">
<input type="checkbox" id="display-type-switch">
<span class="slider"></span>
</label>
<span id="display-type-label">频率</span>
</div>
</div>
</div>
</div> </div>
<div>仿真监控组件内容待实现</div>
</div> </div>
`; `;
} }
initChart() {
const chartElement = this.shadowRoot.querySelector('#thread-chart');
if (!chartElement) {
console.error('找不到图表元素');
return;
}
// 确保 Chart.js 已加载
if (typeof Chart === 'undefined') {
console.error('Chart.js 未加载');
return;
}
try {
// 创建图表实例
const ctx = chartElement.getContext('2d');
if (!ctx) {
console.error('无法获取 canvas 上下文');
return;
}
// 销毁已存在的图表实例
if (this.chart) {
this.chart.destroy();
}
// 创建新的图表实例
this.chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
},
animation: false,
plugins: {
legend: {
display: true
},
tooltip: {
enabled: false
}
}
}
});
// 监听显示类型切换
const switchInput = this.shadowRoot.querySelector('#display-type-switch');
const displayTypeLabel = this.shadowRoot.querySelector('#display-type-label');
if (switchInput && displayTypeLabel) {
switchInput.addEventListener('change', (e) => {
const isFrequency = e.target.checked;
displayTypeLabel.textContent = isFrequency ? '频率' : '周期';
this.updateChartDisplayType(isFrequency);
});
}
} catch (error) {
console.error('初始化图表失败:', error);
this.chart = null;
}
}
updateChartData() {
if (!this.chart || !this.threadInfo || !Array.isArray(this.threadInfo)) return;
const isFrequency = this.shadowRoot.querySelector('#display-type-switch').checked;
const currentTime = new Date().toLocaleTimeString();
// 确保图表数据集存在
if (!this.chart.data.datasets || this.chart.data.datasets.length === 0) {
this.updateChartDisplayType(isFrequency);
}
// 更新数据
const newData = {
labels: [...this.chart.data.labels, currentTime].slice(-30),
datasets: this.threadInfo.map((thread, index) => {
const dataset = this.chart.data.datasets[index] || {
label: thread.name,
data: [],
borderColor: this.getRandomColor(),
fill: false
};
const value = isFrequency ? (thread.currentFrequency || 0) : (thread.currentPeriod || 0);
dataset.data = [...dataset.data, value].slice(-30);
return dataset;
})
};
// 使用新数据更新图表
this.chart.data = newData;
this.chart.update('none'); // 使用 'none' 模式更新,避免触发动画和事件
}
updateChartDisplayType(isFrequency) {
if (!this.threadInfo) return;
const datasets = this.threadInfo.map(thread => ({
label: thread.name,
data: [],
borderColor: this.getRandomColor(),
fill: false
}));
this.chart.data.datasets = datasets;
this.chart.update('none'); // 使用 'none' 模式更新
}
getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
} }
customElements.define('simulation-monitor', SimulationMonitor); customElements.define('simulation-monitor', SimulationMonitor);

View File

@ -6,6 +6,7 @@
<title>XNSim - 主页面</title> <title>XNSim - 主页面</title>
<link rel="icon" type="image/png" href="assets/icons/XNSim.png"> <link rel="icon" type="image/png" href="assets/icons/XNSim.png">
<link rel="shortcut icon" type="image/png" href="assets/icons/XNSim.png"> <link rel="shortcut icon" type="image/png" href="assets/icons/XNSim.png">
<script src="chart.min.js"></script>
<script src="components/auth-component.js"></script> <script src="components/auth-component.js"></script>
<script src="components/main-toolbar.js"></script> <script src="components/main-toolbar.js"></script>
<script src="components/sub-toolbar.js"></script> <script src="components/sub-toolbar.js"></script>

View File

@ -10,24 +10,46 @@ const xnCorePath = process.env.XNCore || '';
const isWindows = os.platform() === 'win32'; const isWindows = os.platform() === 'win32';
const libExtension = isWindows ? '.dll' : '.so'; const libExtension = isWindows ? '.dll' : '.so';
const libPrefix = isWindows ? '' : 'lib'; const libPrefix = isWindows ? '' : 'lib';
const libName = `${libPrefix}Login${libExtension}`;
const libPath = path.join(xnCorePath, 'lib', libName); // Login库配置
const loginLibName = `${libPrefix}Login${libExtension}`;
const loginLibPath = path.join(xnCorePath, 'lib', loginLibName);
// MonitorServer库配置
const monitorLibName = `${libPrefix}XNMonitorServer${libExtension}`;
const monitorLibPath = path.join(xnCorePath, 'lib', monitorLibName);
// 定义Buffer类型 // 定义Buffer类型
const BufferType = ref.refType(ref.types.void); const BufferType = ref.refType(ref.types.void);
const StringType = ref.types.CString; const StringType = ref.types.CString;
const IntType = ref.types.int;
// 定义动态库函数接口 // 定义动态库函数接口
let loginLib; let loginLib;
let monitorLib;
try { try {
loginLib = ffi.Library(libPath, { loginLib = ffi.Library(loginLibPath, {
'validateUser': ['int', [BufferType, 'size_t', BufferType, 'size_t']], 'validateUser': ['int', [BufferType, 'size_t', BufferType, 'size_t']],
'getUserInfo': [StringType, ['int']], 'getUserInfo': [StringType, ['int']],
'cleanup': ['void', []], 'cleanup': ['void', []],
'registerUser': ['int', [BufferType, 'size_t', BufferType, 'size_t', BufferType, 'size_t']] 'registerUser': ['int', [BufferType, 'size_t', BufferType, 'size_t', BufferType, 'size_t']]
}); });
} catch (error) { } catch (error) {
console.error(`加载 ${libName} 失败:`, error); console.error(`加载 ${loginLibName} 失败:`, error);
}
try {
monitorLib = ffi.Library(monitorLibPath, {
'XN_Initialize': ['int', [StringType, 'int', StringType, 'int']],
'XN_Cleanup': ['void', []],
'XN_StartMonitorSystemInfo': ['int', [StringType, 'int']],
'XN_GetSystemInfo': ['int', [StringType, 'int']],
'XN_GetAllThreadInfo': ['int', [StringType, 'int']],
'XN_StopMonitorSystemInfo': ['void', []]
});
} catch (error) {
console.error(`加载 ${monitorLibName} 失败:`, error);
} }
// 注册进程退出时的清理函数 // 注册进程退出时的清理函数
@ -57,8 +79,111 @@ function stringToBuffer(str) {
} }
} }
// 初始化监控服务器
function initializeMonitor(domainId) {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
// 创建错误消息缓冲区
const errorMsg = Buffer.alloc(1024);
const result = monitorLib.XN_Initialize(domainId, domainId.length, errorMsg, errorMsg.length);
if (result !== 0) {
return `初始化失败: ${errorMsg.toString('utf8').replace(/\0/g, '')}`;
}
return '初始化成功';
} catch (error) {
return `初始化失败: ${error.message}`;
}
}
// 清理监控服务器资源
function cleanupMonitor() {
if (!monitorLib) {
return;
}
try {
monitorLib.XN_Cleanup();
} catch (error) {
return;
}
}
// 启动监控系统信息
function startMonitorSystemInfo() {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const errorMsg = Buffer.alloc(1024);
const result = monitorLib.XN_StartMonitorSystemInfo(errorMsg, errorMsg.length);
if (result !== 0) {
return `启动失败: ${errorMsg.toString('utf8').replace(/\0/g, '')}`;
}
return '启动成功';
} catch (error) {
return `启动失败: ${error.message}`;
}
}
// 获取系统信息
function getSystemInfo() {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const infoMsg = Buffer.alloc(4096);
const result = monitorLib.XN_GetSystemInfo(infoMsg, infoMsg.length);
if (result !== 0) {
return `获取失败: ${infoMsg.toString('utf8').replace(/\0/g, '')}`;
}
return infoMsg.toString('utf8').replace(/\0/g, '');
} catch (error) {
return `获取失败: ${error.message}`;
}
}
// 获取所有线程信息
function getAllThreadInfo() {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const infoMsg = Buffer.alloc(4096);
const result = monitorLib.XN_GetAllThreadInfo(infoMsg, infoMsg.length);
if (result !== 0) {
return `获取失败: ${infoMsg.toString('utf8').replace(/\0/g, '')}`;
}
return infoMsg.toString('utf8').replace(/\0/g, '');
} catch (error) {
return `获取失败: ${error.message}`;
}
}
// 停止监控系统信息
function stopMonitorSystemInfo() {
if (!monitorLib) {
return;
}
try {
monitorLib.XN_StopMonitorSystemInfo();
} catch (error) {
return;
}
}
module.exports = { module.exports = {
loginLib, loginLib,
monitorLib,
performCleanup, performCleanup,
stringToBuffer stringToBuffer,
initializeMonitor,
cleanupMonitor,
startMonitorSystemInfo,
getSystemInfo,
getAllThreadInfo,
stopMonitorSystemInfo
}; };

View File

@ -0,0 +1,76 @@
const express = require('express');
const router = express.Router();
const { initializeMonitor, cleanupMonitor } = require('../modules/cleanup');
// 存储监控服务的状态
let monitorStatus = {
isInitialized: false,
domainId: null,
lastError: null
};
// 初始化监控服务
router.post('/initialize', async (req, res) => {
try {
const { domainId } = req.body;
if (!domainId) {
return res.status(400).json({ error: '缺少必要的domainId参数' });
}
if (monitorStatus.isInitialized) {
return res.status(400).json({ error: '监控服务已经初始化' });
}
const result = initializeMonitor(domainId);
if (result && result.includes('失败')) {
monitorStatus.lastError = result;
return res.status(500).json({ error: result });
}
monitorStatus.isInitialized = true;
monitorStatus.domainId = domainId;
monitorStatus.lastError = null;
res.json({
message: '监控服务初始化成功',
status: monitorStatus
});
} catch (error) {
console.error('初始化监控服务失败:', error);
monitorStatus.lastError = error.message;
res.status(500).json({ error: '初始化监控服务失败', message: error.message });
}
});
// 清理监控服务
router.post('/cleanup', async (req, res) => {
try {
if (!monitorStatus.isInitialized) {
return res.status(400).json({ error: '监控服务未初始化' });
}
cleanupMonitor();
monitorStatus = {
isInitialized: false,
domainId: null,
lastError: null
};
res.json({
message: '监控服务清理成功',
status: monitorStatus
});
} catch (error) {
console.error('清理监控服务失败:', error);
monitorStatus.lastError = error.message;
res.status(500).json({ error: '清理监控服务失败', message: error.message });
}
});
// 获取监控服务状态
router.get('/status', (req, res) => {
res.json(monitorStatus);
});
module.exports = router;

View File

@ -0,0 +1,249 @@
const express = require('express');
const router = express.Router();
const { startMonitorSystemInfo, stopMonitorSystemInfo, getSystemInfo, getAllThreadInfo } = require('../modules/cleanup');
// 存储监控服务的状态
let monitorStatus = {
isMonitoring: false,
lastError: null
};
// 存储线程频率历史数据
const threadFrequencyHistory = new Map(); // Map<threadId, number[]>
const MAX_HISTORY_SIZE = 100;
// 存储线程频率统计
const threadFrequencyStats = new Map(); // Map<threadId, {max: number, min: number, avg: number}>
// 频率转周期单位ms
function frequencyToPeriod(frequency) {
return frequency === 0 ? 0 : (1000 / frequency);
}
// 更新线程频率统计
function updateThreadFrequencyStats(threadId, currentFrequency, setFrequency) {
// 初始化历史数据
if (!threadFrequencyHistory.has(threadId)) {
threadFrequencyHistory.set(threadId, []);
}
// 初始化统计数据
if (!threadFrequencyStats.has(threadId)) {
threadFrequencyStats.set(threadId, {
max: setFrequency,
min: setFrequency,
avg: setFrequency
});
}
const history = threadFrequencyHistory.get(threadId);
const stats = threadFrequencyStats.get(threadId);
// 更新最大最小值
stats.max = Math.max(stats.max, currentFrequency);
stats.min = Math.min(stats.min, currentFrequency);
// 更新历史数据
history.push(currentFrequency);
if (history.length > MAX_HISTORY_SIZE) {
history.shift(); // 移除最旧的数据
}
// 计算平均值
stats.avg = history.reduce((sum, freq) => sum + freq, 0) / history.length;
}
// 启动系统监控
router.post('/start', async (req, res) => {
try {
if (monitorStatus.isMonitoring) {
return res.status(400).json({ error: '系统监控已经在运行' });
}
const result = startMonitorSystemInfo();
if (result && result.includes('失败')) {
monitorStatus.lastError = result;
return res.status(500).json({ error: result });
}
// 清空历史数据和统计数据
threadFrequencyHistory.clear();
threadFrequencyStats.clear();
monitorStatus.isMonitoring = true;
monitorStatus.lastError = null;
res.json({
message: '系统监控启动成功',
status: monitorStatus
});
} catch (error) {
console.error('启动系统监控失败:', error);
monitorStatus.lastError = error.message;
res.status(500).json({ error: '启动系统监控失败', message: error.message });
}
});
// 停止系统监控
router.post('/stop', async (req, res) => {
try {
if (!monitorStatus.isMonitoring) {
return res.status(400).json({ error: '系统监控未在运行' });
}
stopMonitorSystemInfo();
monitorStatus.isMonitoring = false;
monitorStatus.lastError = null;
// 清空历史数据和统计数据
threadFrequencyHistory.clear();
threadFrequencyStats.clear();
res.json({
message: '系统监控停止成功',
status: monitorStatus
});
} catch (error) {
console.error('停止系统监控失败:', error);
monitorStatus.lastError = error.message;
res.status(500).json({ error: '停止系统监控失败', message: error.message });
}
});
// 获取系统信息
router.get('/system-info', async (req, res) => {
try {
if (!monitorStatus.isMonitoring) {
return res.status(400).json({ error: '系统监控未在运行' });
}
const result = getSystemInfo();
if (result && result.includes('失败')) {
monitorStatus.lastError = result;
return res.status(500).json({ error: result });
}
// 解析JSON字符串
const data = JSON.parse(result);
const engineStatus = data.engineStatus;
// 构造响应数据
const responseData = {
engineInfo: {
name: engineStatus.engineName,
id: engineStatus.engineID,
status: engineStatus.engineStatus,
affinity: engineStatus.engineAffinity,
threadCount: engineStatus.threadCount
},
coreStatus: {
fw: engineStatus.coreStatus.fwStatus,
tm: engineStatus.coreStatus.tmStatus,
em: engineStatus.coreStatus.emStatus,
sd: engineStatus.coreStatus.sdStatus,
thm: engineStatus.coreStatus.thmStatus,
mm: engineStatus.coreStatus.mmStatus,
sm: engineStatus.coreStatus.smStatus,
dm: engineStatus.coreStatus.dmStatus
}
};
res.json({
data: responseData,
status: monitorStatus
});
} catch (error) {
console.error('获取系统信息失败:', error);
monitorStatus.lastError = error.message;
res.status(500).json({ error: '获取系统信息失败', message: error.message });
}
});
// 获取所有线程信息
router.get('/thread-info', async (req, res) => {
try {
if (!monitorStatus.isMonitoring) {
return res.status(400).json({ error: '系统监控未在运行' });
}
const result = getAllThreadInfo();
if (result && result.includes('失败')) {
monitorStatus.lastError = result;
return res.status(500).json({ error: result });
}
// 解析JSON字符串
const data = JSON.parse(result);
if (!data.threadStatus) {
return res.status(500).json({ error: '线程状态数据缺失' });
}
const threadStatus = data.threadStatus;
// 构造响应数据
const responseData = Object.entries(threadStatus).map(([threadId, thread]) => {
if (!thread.threadCurrentFrequency || !thread.threadSetFrequency ||
!thread.threadName || !thread.threadAffinity ||
!thread.threadPro || !thread.threadRunCount ||
!thread.threadStatus) {
throw new Error('线程数据不完整');
}
// 更新频率统计
updateThreadFrequencyStats(
parseInt(threadId),
thread.threadCurrentFrequency,
thread.threadSetFrequency
);
const stats = threadFrequencyStats.get(parseInt(threadId));
if (!stats) {
throw new Error('线程频率统计数据缺失');
}
// 计算周期值
const currentPeriod = frequencyToPeriod(thread.threadCurrentFrequency);
const setPeriod = frequencyToPeriod(thread.threadSetFrequency);
const maxPeriod = frequencyToPeriod(stats.min);
const minPeriod = frequencyToPeriod(stats.max);
const avgPeriod = frequencyToPeriod(stats.avg);
return {
id: parseInt(threadId),
name: thread.threadName,
affinity: thread.threadAffinity,
priority: thread.threadPro,
runCount: thread.threadRunCount,
status: thread.threadStatus,
// 频率值
currentFrequency: thread.threadCurrentFrequency,
setFrequency: thread.threadSetFrequency,
maxFrequency: stats.max,
minFrequency: stats.min,
avgFrequency: stats.avg,
// 周期值单位ms
currentPeriod: currentPeriod,
setPeriod: setPeriod,
maxPeriod: maxPeriod,
minPeriod: minPeriod,
avgPeriod: avgPeriod
};
});
res.json({
data: responseData,
status: monitorStatus
});
} catch (error) {
console.error('获取线程信息失败:', error);
monitorStatus.lastError = error.message;
res.status(500).json({ error: '获取线程信息失败', message: error.message });
}
});
// 获取监控状态
router.get('/status', (req, res) => {
res.json(monitorStatus);
});
module.exports = router;

View File

@ -27,6 +27,8 @@ const qaRoutes = require('./routes/qa');
const todoRoutes = require('./routes/todos'); const todoRoutes = require('./routes/todos');
const userRoutes = require('./routes/users'); const userRoutes = require('./routes/users');
const systemLogRoutes = require('./routes/system-log'); const systemLogRoutes = require('./routes/system-log');
const ddsMonitorRoutes = require('./routes/DDSMonitor');
const systemMonitorRoutes = require('./routes/SystemMonitor');
const app = express(); const app = express();
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
@ -95,6 +97,8 @@ app.use('/api/qa', qaRoutes);
app.use('/api/todos', todoRoutes); app.use('/api/todos', todoRoutes);
app.use('/api', userRoutes); app.use('/api', userRoutes);
app.use('/api/system-log', systemLogRoutes); app.use('/api/system-log', systemLogRoutes);
app.use('/api/dds-monitor', ddsMonitorRoutes);
app.use('/api/system-monitor', systemMonitorRoutes);
// 接口配置页面路由 // 接口配置页面路由
app.get('/interface-config', (req, res) => { app.get('/interface-config', (req, res) => {