From df3b7f785393451d58a125e60f653455c6e08f9a Mon Sep 17 00:00:00 2001 From: jinchao <383321154@qq.com> Date: Wed, 14 May 2025 14:30:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=9E=E7=8E=B0=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=8A=B6=E6=80=81=E7=9B=91=E6=8E=A7=EF=BC=8C=E5=BE=85?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- XNMonitorServer/.vscode/settings.json | 74 +++ XNMonitorServer/CMakeLists.txt | 63 ++ XNMonitorServer/DataReaderListenerImpl.h | 51 ++ XNMonitorServer/SystemInfoMonitor.cpp | 134 ++++ XNMonitorServer/SystemInfoMonitor.h | 59 ++ XNMonitorServer/TopicManager.cpp | 16 + XNMonitorServer/TopicManager.h | 299 +++++++++ XNMonitorServer/TypeDefine.h | 280 ++++++++ XNMonitorServer/XNMonitorInterface.cpp | 372 +++++++++++ XNMonitorServer/XNMonitorInterface.h | 57 ++ XNMonitorServer/XNMonitorServer_global.h | 7 + XNSimHtml/components/simulation-monitor.js | 706 ++++++++++++++++++++- XNSimHtml/main.html | 1 + XNSimHtml/modules/cleanup.js | 135 +++- XNSimHtml/routes/DDSMonitor.js | 76 +++ XNSimHtml/routes/SystemMonitor.js | 249 ++++++++ XNSimHtml/server.js | 4 + 17 files changed, 2561 insertions(+), 22 deletions(-) create mode 100644 XNMonitorServer/.vscode/settings.json create mode 100644 XNMonitorServer/CMakeLists.txt create mode 100755 XNMonitorServer/DataReaderListenerImpl.h create mode 100644 XNMonitorServer/SystemInfoMonitor.cpp create mode 100644 XNMonitorServer/SystemInfoMonitor.h create mode 100755 XNMonitorServer/TopicManager.cpp create mode 100755 XNMonitorServer/TopicManager.h create mode 100755 XNMonitorServer/TypeDefine.h create mode 100644 XNMonitorServer/XNMonitorInterface.cpp create mode 100644 XNMonitorServer/XNMonitorInterface.h create mode 100644 XNMonitorServer/XNMonitorServer_global.h create mode 100644 XNSimHtml/routes/DDSMonitor.js create mode 100644 XNSimHtml/routes/SystemMonitor.js diff --git a/XNMonitorServer/.vscode/settings.json b/XNMonitorServer/.vscode/settings.json new file mode 100644 index 0000000..1f9ba80 --- /dev/null +++ b/XNMonitorServer/.vscode/settings.json @@ -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" + } +} diff --git a/XNMonitorServer/CMakeLists.txt b/XNMonitorServer/CMakeLists.txt new file mode 100644 index 0000000..095a1bb --- /dev/null +++ b/XNMonitorServer/CMakeLists.txt @@ -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 . +) diff --git a/XNMonitorServer/DataReaderListenerImpl.h b/XNMonitorServer/DataReaderListenerImpl.h new file mode 100755 index 0000000..5e3240e --- /dev/null +++ b/XNMonitorServer/DataReaderListenerImpl.h @@ -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 +class DataReaderListenerImpl : public XNDataReaderListener +{ +public: + /** + * @brief 构造函数 + * @param callback 回调函数 + */ + DataReaderListenerImpl(std::function 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 callback_; +}; \ No newline at end of file diff --git a/XNMonitorServer/SystemInfoMonitor.cpp b/XNMonitorServer/SystemInfoMonitor.cpp new file mode 100644 index 0000000..ee0c6f8 --- /dev/null +++ b/XNMonitorServer/SystemInfoMonitor.cpp @@ -0,0 +1,134 @@ +#include "SystemInfoMonitor.h" +#include + +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::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(ret)); + } + //注册线程状态订阅 + ret = + TopicManager::Instance()->registerSubscriber( + "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(ret)); + } + m_ThreadCycleCount.clear(); + m_EngineStatusUpdate = false; + return "Success"; +} + +std::string SystemInfoMonitor::GetSystemInfo() +{ + std::lock_guard 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 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 locker(m_EngineStatusMutex); + m_EngineStatusUpdate = true; + m_EngineStatus = status; +} + +void SystemInfoMonitor::ThreadStatusListener(const XNSim::XNSimStatus::XNThreadStatus &status) +{ + std::lock_guard locker(m_ThreadStatusMutex); + m_ThreadStatus[status.XNThreadID()] = status; +} \ No newline at end of file diff --git a/XNMonitorServer/SystemInfoMonitor.h b/XNMonitorServer/SystemInfoMonitor.h new file mode 100644 index 0000000..31ab863 --- /dev/null +++ b/XNMonitorServer/SystemInfoMonitor.h @@ -0,0 +1,59 @@ +#ifndef SYSTEMINFOMONITOR_H +#define SYSTEMINFOMONITOR_H + +#include "XNMonitorServer_global.h" +#include +#include +#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 m_ThreadStatus; + /** + * @brief 线程周期计数 + */ + std::map m_ThreadCycleCount; +}; + +#endif // SYSTEMINFOMONITOR_H diff --git a/XNMonitorServer/TopicManager.cpp b/XNMonitorServer/TopicManager.cpp new file mode 100755 index 0000000..9dc57fa --- /dev/null +++ b/XNMonitorServer/TopicManager.cpp @@ -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; \ No newline at end of file diff --git a/XNMonitorServer/TopicManager.h b/XNMonitorServer/TopicManager.h new file mode 100755 index 0000000..17eae46 --- /dev/null +++ b/XNMonitorServer/TopicManager.h @@ -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 +#include +#include +#include +#include "DataReaderListenerImpl.h" +#include + +/** + * @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 locker(instanceMutex); + if (instance == nullptr) { // 双重检查锁定 + instance = new TopicManager(); + } + } + return instance; + } + + /** + * @brief 清理参与者 + */ + static void cleanupParticipant() + { + std::lock_guard 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 + XNDDSErrorCode registerPublisher(const std::string &topicName, + XNDataWriter *dataWriter = nullptr) + { + std::lock_guard 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 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 + XNDDSErrorCode registerSubscriber(const std::string &topicName, + std::function fun) + { + std::lock_guard 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(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(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 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 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 topics_; + + /** + * @brief 主题访问互斥锁 + */ + std::mutex m_Mutex; +}; diff --git a/XNMonitorServer/TypeDefine.h b/XNMonitorServer/TypeDefine.h new file mode 100755 index 0000000..b049f37 --- /dev/null +++ b/XNMonitorServer/TypeDefine.h @@ -0,0 +1,280 @@ +/** + * @file TypeDefine.h + * @brief 类型定义头文件 + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @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 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 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 arraySizes; + /** + * @brief 描述 + */ + std::string description; +}; + +/** + * @brief 接口结构体定义结构体 + * @note 接口结构体定义包含结构体名称、成员变量 + */ +struct StructDefinition { + /** + * @brief 结构体名称 + */ + std::string structName; + /** + * @brief 成员变量 + */ + std::vector> memberVariables; +}; + +/** + * @brief 命名空间定义结构体 + * @note 命名空间定义包含命名空间名称、接口结构体定义、子命名空间定义 + */ +struct NamespaceDefinition { + /** + * @brief 命名空间名称 + */ + std::string namespaceName; + /** + * @brief 结构体定义 + */ + std::vector> structDefinitions; + /** + * @brief 子命名空间 + */ + std::vector> childNamespaces; +}; + +/** + * @brief 模型接口定义 + * @note 模型接口定义包含模型名称和接口命名空间定义 + */ +struct ModelDefinition { + /** + * @brief 模型名称 + */ + std::string modelName; + /** + * @brief 命名空间定义 + */ + std::vector> namespaceDefinitions; +}; diff --git a/XNMonitorServer/XNMonitorInterface.cpp b/XNMonitorServer/XNMonitorInterface.cpp new file mode 100644 index 0000000..26b82aa --- /dev/null +++ b/XNMonitorServer/XNMonitorInterface.cpp @@ -0,0 +1,372 @@ +/** + * @file XNMonitorInterface.cpp + * @brief 监控服务器接口实现 + */ +#include "XNMonitorInterface.h" +#include "TopicManager.h" +#include +#include +#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 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(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(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(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(callback, userData); +// // TopicManager::Instance()->registerSubscriber( +// // 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 lock(g_errorMutex); +// return g_lastError.c_str(); +// } \ No newline at end of file diff --git a/XNMonitorServer/XNMonitorInterface.h b/XNMonitorServer/XNMonitorInterface.h new file mode 100644 index 0000000..7af9905 --- /dev/null +++ b/XNMonitorServer/XNMonitorInterface.h @@ -0,0 +1,57 @@ +/** + * @file XNMonitorInterface.h + * @brief 监控服务器接口定义 + */ +#pragma once + +#include "TypeDefine.h" +#include "XNMonitorServer_global.h" +#include + +#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 \ No newline at end of file diff --git a/XNMonitorServer/XNMonitorServer_global.h b/XNMonitorServer/XNMonitorServer_global.h new file mode 100644 index 0000000..b8cd139 --- /dev/null +++ b/XNMonitorServer/XNMonitorServer_global.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef XNMonitorServer_EXPORTS +# define XNMONITORSERVER_EXPORT __attribute__((visibility("default"))) +#else +# define XNMONITORSERVER_EXPORT __attribute__((visibility("default"))) +#endif diff --git a/XNSimHtml/components/simulation-monitor.js b/XNSimHtml/components/simulation-monitor.js index 3196fe1..d7fcd0d 100644 --- a/XNSimHtml/components/simulation-monitor.js +++ b/XNSimHtml/components/simulation-monitor.js @@ -2,10 +2,255 @@ class SimulationMonitor extends HTMLElement { constructor() { super(); 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() { 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 = ` +
+ ${engineInfoFields.map(field => ` +
+
${field.label}
+
${this.monitorStatus.isMonitoring && this.systemInfo?.engineInfo ? + (this.systemInfo.engineInfo[field.key] || '未知') : + (field.key === 'status' ? '未运行' : '未知')}
+
+ `).join('')} +
+ `; + + // 更新核心状态 + 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 = ` +
+ ${coreStatusFields.map(field => ` +
+
${field.label}
+
${this.monitorStatus.isMonitoring && this.systemInfo?.coreStatus ? + (this.systemInfo.coreStatus[field.key] || '未知') : '未知'}
+
+ `).join('')} +
+ `; + + // 更新线程表格 + if (this.monitorStatus.isMonitoring && this.threadInfo && Array.isArray(this.threadInfo)) { + threadTableBody.innerHTML = this.threadInfo.map(thread => ` + + ${thread.name || '未知'} + ${thread.id || '未知'} + ${thread.status || '未知'} + ${thread.priority || '未知'} + ${thread.runCount || '0'} + ${(thread.currentFrequency || 0).toFixed(2)} + ${(thread.setFrequency || 0).toFixed(2)} + ${(thread.currentPeriod || 0).toFixed(2)} + + `).join(''); + + // 更新图表数据 + this.updateChartData(); + } else { + threadTableBody.innerHTML = '暂无线程信息'; + // 清空图表数据 + 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() { @@ -20,37 +265,464 @@ class SimulationMonitor extends HTMLElement { } .monitor-container { - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; height: 100%; box-sizing: border-box; } - .monitor-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20px; - padding-bottom: 10px; - border-bottom: 1px solid #e0e0e0; + .toolbar-section { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + padding: 16px; } - .monitor-title { - font-size: 18px; - font-weight: bold; + .content-container { + display: grid; + 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; + 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; }
-
-
仿真监控
+
+
+
+
+ + +
+ + +
+
监控状态: 未启动
+
+
+
+
+
+

引擎信息

+
+
+
+

核心状态

+
+
+
+
+
+

线程信息

+ + + + + + + + + + + + + + +
线程名称线程ID状态优先级运行次数当前频率(Hz)设置频率(Hz)当前周期(ms)
+
+
+

线程监控

+
+ +
+
+ 显示类型: + + 频率 +
+
+
-
仿真监控组件内容(待实现)
`; } + + 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); \ No newline at end of file diff --git a/XNSimHtml/main.html b/XNSimHtml/main.html index 6ecb4d3..d6e92b6 100644 --- a/XNSimHtml/main.html +++ b/XNSimHtml/main.html @@ -6,6 +6,7 @@ XNSim - 主页面 + diff --git a/XNSimHtml/modules/cleanup.js b/XNSimHtml/modules/cleanup.js index c591854..f2fa1a7 100644 --- a/XNSimHtml/modules/cleanup.js +++ b/XNSimHtml/modules/cleanup.js @@ -10,24 +10,46 @@ const xnCorePath = process.env.XNCore || ''; const isWindows = os.platform() === 'win32'; const libExtension = isWindows ? '.dll' : '.so'; 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类型 const BufferType = ref.refType(ref.types.void); const StringType = ref.types.CString; +const IntType = ref.types.int; // 定义动态库函数接口 let loginLib; +let monitorLib; + try { - loginLib = ffi.Library(libPath, { + loginLib = ffi.Library(loginLibPath, { 'validateUser': ['int', [BufferType, 'size_t', BufferType, 'size_t']], 'getUserInfo': [StringType, ['int']], 'cleanup': ['void', []], 'registerUser': ['int', [BufferType, 'size_t', BufferType, 'size_t', BufferType, 'size_t']] }); } 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 = { loginLib, + monitorLib, performCleanup, - stringToBuffer + stringToBuffer, + initializeMonitor, + cleanupMonitor, + startMonitorSystemInfo, + getSystemInfo, + getAllThreadInfo, + stopMonitorSystemInfo }; \ No newline at end of file diff --git a/XNSimHtml/routes/DDSMonitor.js b/XNSimHtml/routes/DDSMonitor.js new file mode 100644 index 0000000..8d2526e --- /dev/null +++ b/XNSimHtml/routes/DDSMonitor.js @@ -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; \ No newline at end of file diff --git a/XNSimHtml/routes/SystemMonitor.js b/XNSimHtml/routes/SystemMonitor.js new file mode 100644 index 0000000..488046f --- /dev/null +++ b/XNSimHtml/routes/SystemMonitor.js @@ -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 +const MAX_HISTORY_SIZE = 100; + +// 存储线程频率统计 +const threadFrequencyStats = new Map(); // Map + +// 频率转周期(单位: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; \ No newline at end of file diff --git a/XNSimHtml/server.js b/XNSimHtml/server.js index c1d195e..008c24e 100644 --- a/XNSimHtml/server.js +++ b/XNSimHtml/server.js @@ -27,6 +27,8 @@ const qaRoutes = require('./routes/qa'); const todoRoutes = require('./routes/todos'); const userRoutes = require('./routes/users'); const systemLogRoutes = require('./routes/system-log'); +const ddsMonitorRoutes = require('./routes/DDSMonitor'); +const systemMonitorRoutes = require('./routes/SystemMonitor'); const app = express(); const PORT = process.env.PORT || 3000; @@ -95,6 +97,8 @@ app.use('/api/qa', qaRoutes); app.use('/api/todos', todoRoutes); app.use('/api', userRoutes); app.use('/api/system-log', systemLogRoutes); +app.use('/api/dds-monitor', ddsMonitorRoutes); +app.use('/api/system-monitor', systemMonitorRoutes); // 接口配置页面路由 app.get('/interface-config', (req, res) => {