XNSim/XNSimHtml/components/simulation-monitor.js

967 lines
35 KiB
JavaScript
Raw Permalink Normal View History

2025-04-28 12:25:20 +08:00
class SimulationMonitor extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
2025-05-15 10:56:49 +08:00
this.monitorId = `simulation_monitor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.monitorStatus = {
isMonitoring: false,
lastError: null
};
this.systemInfo = null;
this.threadInfo = null;
this.statusCheckInterval = null;
this.chart = null;
this.chartInitialized = false;
2025-05-15 10:56:49 +08:00
this.domainId = '10'; // 默认值
this.isActive = false;
this.initializing = false;
2025-04-28 12:25:20 +08:00
}
connectedCallback() {
this.render();
2025-05-15 10:56:49 +08:00
this.isActive = true; // 设置初始状态为激活
// 等待组件完全加载
setTimeout(() => {
this.initializeComponent();
}, 100);
}
2025-05-15 10:56:49 +08:00
// 重新激活组件
reactivate() {
if (this.isActive) return; // 如果已经激活,直接返回
this.isActive = true;
// 强制重新初始化
this.chartInitialized = false;
this.chart = null;
this.initializeComponent();
}
2025-05-15 10:56:49 +08:00
// 修改 startStatusCheck 方法
startStatusCheck() {
2025-05-15 10:56:49 +08:00
if (!this.isActive) return;
// 清除可能存在的旧定时器
this.stopStatusCheck();
// 设置定时器,每秒执行一次
this.statusCheckInterval = setInterval(() => {
2025-05-15 10:56:49 +08:00
if (this.isActive && this.monitorStatus.isMonitoring) {
this.checkMonitorStatus();
} else {
// 如果监控已停止,清除定时器
this.stopStatusCheck();
}
}, 1000);
}
2025-05-15 10:56:49 +08:00
// 修改 checkMonitorStatus 方法
async checkMonitorStatus() {
if (!this.monitorStatus.isMonitoring) return;
try {
// 获取监控状态
const statusResponse = await fetch('/api/system-monitor/status');
const statusData = await statusResponse.json();
this.monitorStatus = statusData;
// 只有在监控状态为true时才获取详细信息
if (this.monitorStatus.isMonitoring) {
// 使用 Promise.all 并行获取系统信息和线程信息
const [systemResponse, threadResponse] = await Promise.all([
fetch('/api/system-monitor/system-info'),
fetch('/api/system-monitor/thread-info')
]);
if (systemResponse.ok) {
const systemData = await systemResponse.json();
this.systemInfo = systemData.data;
}
if (threadResponse.ok) {
const threadData = await threadResponse.json();
this.threadInfo = threadData.data;
// 确保图表存在并更新
if (!this.chart) {
this.initChart();
}
}
// 只在成功获取数据后更新UI
this.updateUI();
} else {
// 如果监控已停止,清空数据
this.systemInfo = null;
this.threadInfo = null;
// 停止状态检查
this.stopStatusCheck();
// 更新UI显示停止状态
this.updateUI();
}
} catch (error) {
console.error('获取监控状态失败:', error);
this.monitorStatus.lastError = error.message;
this.updateUI();
// 发生错误时也停止状态检查
this.stopStatusCheck();
}
}
// 修改 stopStatusCheck 方法
stopStatusCheck() {
if (this.statusCheckInterval) {
clearInterval(this.statusCheckInterval);
this.statusCheckInterval = null;
}
}
2025-05-15 10:56:49 +08:00
// 修改 startMonitoring 方法
async startMonitoring() {
2025-05-29 15:18:31 +08:00
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
const confID = selection.configurationId;
2025-05-15 10:56:49 +08:00
// 如果已经在监控中,直接返回
if (this.monitorStatus.isMonitoring) {
console.log('监控已经在运行中');
return;
}
try {
2025-05-29 15:18:31 +08:00
// 获取构型参数
const configResponse = await fetch(`/api/configurations/${confID}`);
if (!configResponse.ok) {
throw new Error('获取构型参数失败');
}
const configData = await configResponse.json();
// 从构型参数中提取域ID
const domainId = configData.DomainID;
if (!domainId) {
throw new Error('构型参数中未找到有效的域ID');
}
this.domainId = domainId;
2025-05-15 10:56:49 +08:00
// 首先检查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,
monitorId: this.monitorId
})
});
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();
// 只有在没有运行中的状态检查时才启动新的检查
if (!this.statusCheckInterval) {
this.startStatusCheck();
}
} else {
console.error('启动监控失败:', data.error);
}
} catch (error) {
console.error('启动监控失败:', error);
}
}
disconnectedCallback() {
this.isActive = false;
this.stopStatusCheck();
// 注销监控器
fetch('/api/dds-monitor/unregister', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ monitorId: this.monitorId })
}).catch(error => {
console.error('注销监控器失败:', error);
});
// 不要在这里销毁图表实例,让它在重新激活时重新创建
}
async initializeComponent() {
if (this.initializing) return; // 防止重复初始化
this.initializing = true;
try {
// 确保图表被正确初始化
if (this.chart) {
this.chart.destroy();
this.chart = null;
}
// 初始化图表
this.initChart();
// 检查当前监控状态
const statusResponse = await fetch('/api/system-monitor/status');
const statusData = await statusResponse.json();
this.monitorStatus = statusData;
2025-05-15 10:56:49 +08:00
// 如果已经在监控中,开始状态检查
if (this.monitorStatus.isMonitoring && !this.statusCheckInterval) {
this.startStatusCheck();
}
2025-05-15 10:56:49 +08:00
this.chartInitialized = true;
} catch (error) {
2025-05-15 10:56:49 +08:00
console.error('初始化组件失败:', error);
this.monitorStatus.lastError = error.message;
this.updateUI();
2025-05-15 10:56:49 +08:00
} finally {
this.initializing = false;
}
}
2025-05-14 16:42:09 +08:00
getStatusDisplay(status) {
switch (status) {
case 0:
return { text: '未运行', color: '#999999' };
case 1:
return { text: '运行中', color: '#4CAF50' };
case 2:
return { text: '暂停中', color: '#FF9800' };
case 3:
return { text: '错误', color: '#f44336' };
default:
return { text: '未知状态', color: '#f44336' };
}
}
getCoreStatusDisplay(status) {
switch (status) {
case 0:
return { text: '未加载', color: '#999999' };
case 1:
return { text: '初始化完成', color: '#FFC107' };
case 2:
return { text: '正常', color: '#4CAF50' };
case 3:
return { text: '异常', color: '#f44336' };
default:
return { text: '未知状态', color: '#f44336' };
}
}
getThreadStatusDisplay(status) {
switch (status) {
case 0:
return { text: '未运行', color: '#999999' };
case 1:
return { text: '运行中', color: '#4CAF50' };
case 2:
return { text: '暂停中', color: '#FF9800' };
case 3:
return { text: '错误', color: '#f44336' };
default:
return { text: '未知状态', color: '#f44336' };
}
}
updateUI() {
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) {
startButton.disabled = true;
stopButton.disabled = false;
} else {
startButton.disabled = false;
stopButton.disabled = true;
}
// 更新状态显示
statusDisplay.textContent = `监控状态: ${this.monitorStatus.isMonitoring ? '运行中' : '已停止'}`;
if (this.monitorStatus.lastError) {
statusDisplay.textContent += ` (错误: ${this.monitorStatus.lastError})`;
}
// 更新引擎信息
2025-05-29 15:18:31 +08:00
const engineInfoItems = engineInfo.querySelectorAll('.status-item');
engineInfoItems.forEach(item => {
const label = item.querySelector('.status-label').textContent;
const valueElement = item.querySelector('.status-value');
switch(label) {
case '引擎名称':
valueElement.textContent = this.systemInfo?.engineInfo?.name || 'XNSim';
valueElement.style.color = '#666';
break;
case '引擎ID':
valueElement.textContent = this.systemInfo?.engineInfo?.id || '未知';
valueElement.style.color = '#666';
break;
case '引擎状态':
const statusInfo = this.getStatusDisplay(this.systemInfo?.engineInfo?.status || 0);
valueElement.textContent = statusInfo.text;
valueElement.style.color = statusInfo.color;
break;
case '引擎亲和性':
valueElement.textContent = this.systemInfo?.engineInfo?.affinity || '未知';
valueElement.style.color = '#666';
break;
case '线程数':
valueElement.textContent = this.systemInfo?.engineInfo?.threadCount || '未知';
valueElement.style.color = '#666';
break;
}
});
// 更新核心状态
2025-05-29 15:18:31 +08:00
const coreStatusItems = coreStatus.querySelectorAll('.status-item');
coreStatusItems.forEach(item => {
const label = item.querySelector('.status-label').textContent;
const valueElement = item.querySelector('.status-value');
const key = this.getCoreStatusKey(label);
if (key) {
const status = this.systemInfo?.coreStatus?.[key] || 0;
const statusInfo = this.getCoreStatusDisplay(status);
valueElement.textContent = statusInfo.text;
valueElement.style.color = statusInfo.color;
}
});
// 更新线程表格
2025-05-29 15:18:31 +08:00
if (this.threadInfo && Array.isArray(this.threadInfo)) {
2025-05-14 16:42:09 +08:00
threadTableBody.innerHTML = this.threadInfo.map(thread => {
const statusInfo = this.getThreadStatusDisplay(thread.status);
return `
<tr>
<td>${thread.name || '未知'}</td>
<td>${thread.id || '未知'}</td>
<td style="color: ${statusInfo.color}">${statusInfo.text}</td>
<td>${thread.priority || '未知'}</td>
<td>${thread.runCount || '0'}</td>
<td>${(thread.setFrequency || 0).toFixed(2)}</td>
<td>${(thread.avgFrequency || 0).toFixed(2)}</td>
<td>${(thread.maxFrequency || 0).toFixed(2)}</td>
<td>${(thread.minFrequency || 0).toFixed(2)}</td>
<td>${(thread.setPeriod || 0).toFixed(2)}</td>
<td>${(thread.avgPeriod || 0).toFixed(2)}</td>
<td>${(thread.maxPeriod || 0).toFixed(2)}</td>
<td>${(thread.minPeriod || 0).toFixed(2)}</td>
</tr>
`;
}).join('');
// 更新图表数据
this.updateChartData();
} else {
2025-05-14 16:42:09 +08:00
threadTableBody.innerHTML = '<tr><td colspan="13" style="text-align: center;">暂无线程信息</td></tr>';
// 清空图表数据
if (this.chart) {
this.chart.data.labels = [];
this.chart.data.datasets = [];
this.chart.update();
}
}
}
2025-05-29 15:18:31 +08:00
getCoreStatusKey(label) {
const keyMap = {
'主框架状态': 'fw',
'时间管理器状态': 'tm',
'事件管理器状态': 'em',
'环境管理器状态': 'sd',
'线程管理器状态': 'thm',
'模型管理器状态': 'mm',
'服务管理器状态': 'sm',
'DDS管理器状态': 'dm'
};
return keyMap[label];
}
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;
2025-05-29 15:18:31 +08:00
// 立即停止状态检查
this.stopStatusCheck();
// 清空数据
this.systemInfo = {
engineInfo: {
name: 'XNSim',
id: '未知',
status: 0,
affinity: '未知',
threadCount: '未知'
},
coreStatus: {
fw: 0,
tm: 0,
em: 0,
sd: 0,
thm: 0,
mm: 0,
sm: 0,
dm: 0
}
};
this.threadInfo = null;
// 更新UI
this.updateUI();
} else {
console.error('停止监控失败:', data.error);
}
} catch (error) {
console.error('停止监控失败:', error);
}
2025-04-28 12:25:20 +08:00
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
height: 100%;
overflow: auto;
padding: 16px;
box-sizing: border-box;
}
.monitor-container {
display: flex;
flex-direction: column;
gap: 16px;
height: 100%;
box-sizing: border-box;
}
.toolbar-section {
2025-04-28 12:25:20 +08:00
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 16px;
}
.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 {
2025-04-28 12:25:20 +08:00
display: flex;
gap: 12px;
align-items: center;
2025-04-28 12:25:20 +08:00
justify-content: space-between;
}
.toolbar-left {
display: flex;
gap: 12px;
2025-04-28 12:25:20 +08:00
align-items: center;
}
.input-group {
display: flex;
align-items: center;
gap: 8px;
}
.input-label {
font-size: 14px;
2025-04-28 12:25:20 +08:00
color: #333;
white-space: nowrap;
}
.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;
2025-04-28 12:25:20 +08:00
}
</style>
<div class="monitor-container">
<div class="toolbar-section">
<div class="toolbar">
<div class="toolbar-left">
<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>
2025-05-29 15:18:31 +08:00
<div class="info-grid" id="engine-info">
<div class="status-grid">
<div class="status-item">
<div class="status-label">引擎名称</div>
<div class="status-value" style="color: #666">XNSim</div>
</div>
<div class="status-item">
<div class="status-label">引擎ID</div>
<div class="status-value" style="color: #666">未知</div>
</div>
<div class="status-item">
<div class="status-label">引擎状态</div>
<div class="status-value" style="color: #999999">未运行</div>
</div>
<div class="status-item">
<div class="status-label">引擎亲和性</div>
<div class="status-value" style="color: #666">未知</div>
</div>
<div class="status-item">
<div class="status-label">线程数</div>
<div class="status-value" style="color: #666">未知</div>
</div>
</div>
</div>
</div>
<div class="panel-section">
<h3>核心状态</h3>
2025-05-29 15:18:31 +08:00
<div class="info-grid" id="core-status">
<div class="status-grid">
<div class="status-item">
<div class="status-label">主框架状态</div>
<div class="status-value" style="color: #999999">未加载</div>
</div>
<div class="status-item">
<div class="status-label">时间管理器状态</div>
<div class="status-value" style="color: #999999">未加载</div>
</div>
<div class="status-item">
<div class="status-label">事件管理器状态</div>
<div class="status-value" style="color: #999999">未加载</div>
</div>
<div class="status-item">
<div class="status-label">环境管理器状态</div>
<div class="status-value" style="color: #999999">未加载</div>
</div>
<div class="status-item">
<div class="status-label">线程管理器状态</div>
<div class="status-value" style="color: #999999">未加载</div>
</div>
<div class="status-item">
<div class="status-label">模型管理器状态</div>
<div class="status-value" style="color: #999999">未加载</div>
</div>
<div class="status-item">
<div class="status-label">服务管理器状态</div>
<div class="status-value" style="color: #999999">未加载</div>
</div>
<div class="status-item">
<div class="status-label">DDS管理器状态</div>
<div class="status-value" style="color: #999999">未加载</div>
</div>
</div>
</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>
2025-05-14 16:42:09 +08:00
<th>设定频率(Hz)</th>
<th>平均频率(Hz)</th>
<th>最大频率(Hz)</th>
<th>最小频率(Hz)</th>
<th>设定周期(ms)</th>
<th>平均周期(ms)</th>
<th>最大周期(ms)</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>
2025-04-28 12:25:20 +08:00
</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();
2025-05-15 10:56:49 +08:00
this.chart = null;
}
// 创建新的图表实例
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() {
2025-05-15 10:56:49 +08:00
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;
}
2025-04-28 12:25:20 +08:00
}
customElements.define('simulation-monitor', SimulationMonitor);