XNSim/XNSimHtml/components/simulation-monitor.js

967 lines
35 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class SimulationMonitor extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
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;
this.domainId = '10'; // 默认值
this.isActive = false;
this.initializing = false;
}
connectedCallback() {
this.render();
this.isActive = true; // 设置初始状态为激活
// 等待组件完全加载
setTimeout(() => {
this.initializeComponent();
}, 100);
}
// 重新激活组件
reactivate() {
if (this.isActive) return; // 如果已经激活,直接返回
this.isActive = true;
// 强制重新初始化
this.chartInitialized = false;
this.chart = null;
this.initializeComponent();
}
// 修改 startStatusCheck 方法
startStatusCheck() {
if (!this.isActive) return;
// 清除可能存在的旧定时器
this.stopStatusCheck();
// 设置定时器,每秒执行一次
this.statusCheckInterval = setInterval(() => {
if (this.isActive && this.monitorStatus.isMonitoring) {
this.checkMonitorStatus();
} else {
// 如果监控已停止,清除定时器
this.stopStatusCheck();
}
}, 1000);
}
// 修改 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;
}
}
// 修改 startMonitoring 方法
async startMonitoring() {
const savedSelection = localStorage.getItem('xnsim-selection');
const selection = savedSelection ? JSON.parse(savedSelection) : {};
const confID = selection.configurationId;
// 如果已经在监控中,直接返回
if (this.monitorStatus.isMonitoring) {
console.log('监控已经在运行中');
return;
}
try {
// 获取构型参数
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;
// 首先检查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;
// 如果已经在监控中,开始状态检查
if (this.monitorStatus.isMonitoring && !this.statusCheckInterval) {
this.startStatusCheck();
}
this.chartInitialized = true;
} catch (error) {
console.error('初始化组件失败:', error);
this.monitorStatus.lastError = error.message;
this.updateUI();
} finally {
this.initializing = false;
}
}
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})`;
}
// 更新引擎信息
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;
}
});
// 更新核心状态
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;
}
});
// 更新线程表格
if (this.threadInfo && Array.isArray(this.threadInfo)) {
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 {
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();
}
}
}
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;
// 立即停止状态检查
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);
}
}
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 {
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 {
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;
}
.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>
<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>
<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>
<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>
<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>
</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 = 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() {
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);