XNSim/XNSimHtml/components/model-monitor.js
2025-06-09 14:39:29 +08:00

686 lines
24 KiB
JavaScript
Raw 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 ModelMonitor extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.monitorId = `model_monitor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.monitorStatus = {
isMonitoring: false,
lastError: null
};
this.modelInfo = 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();
// 初始化完成后再启动定时器,给服务器一些准备时间
setTimeout(() => {
this.startStatusCheck();
}, 1000); // 延迟1秒启动定时器
}, 100);
}
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/model-monitor/status');
const statusData = await statusResponse.json();
this.monitorStatus = statusData;
// 如果监控未运行,尝试启动监控
if (!this.monitorStatus.isMonitoring) {
this.startMonitoring();
}
this.chartInitialized = true;
} catch (error) {
console.error('初始化组件失败:', error);
this.monitorStatus.lastError = error.message;
this.updateUI();
} finally {
this.initializing = false;
}
}
// 修改 reactivate 方法
reactivate() {
this.isActive = true;
// 如果已经在监控中,重新开始状态检查
if (this.monitorStatus.isMonitoring) {
this.startStatusCheck();
}
}
// 修改 startStatusCheck 方法
startStatusCheck() {
if (!this.isActive) return;
// 如果已经有定时器在运行,先停止它
this.stopStatusCheck();
let consecutiveErrors = 0;
const maxConsecutiveErrors = 3;
const checkStatus = async () => {
if (!this.isActive) return;
try {
await this.checkMonitorStatus();
consecutiveErrors = 0; // 重置错误计数
} catch (error) {
consecutiveErrors++;
console.error(`状态检查失败 (${consecutiveErrors}/${maxConsecutiveErrors}):`, error);
if (consecutiveErrors >= maxConsecutiveErrors) {
console.error('连续错误次数过多,停止状态检查');
this.stopStatusCheck();
this.monitorStatus.lastError = '监控服务异常,请重新启动监控';
this.updateUI();
return;
}
}
};
// 立即执行一次
//checkStatus();
// 设置定时器,每秒执行一次
this.statusCheckInterval = setInterval(checkStatus, 1000);
}
// 修改 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 {
// 首先检查DDS监控状态
const ddsStatusResponse = await fetch('/api/dds-monitor/status');
const ddsStatusData = await ddsStatusResponse.json();
// 如果DDS监控未初始化直接返回等待下次定时器运行时再检查
if (!ddsStatusData.isInitialized) {
//console.log('DDS监控未初始化等待下次检查');
return;
}
// 启动模型监控
const response = await fetch('/api/model-monitor/start', {
method: 'POST'
});
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();
// 自动停止监控
this.stopMonitoring();
}
async checkMonitorStatus() {
try {
// 获取监控状态
const statusResponse = await fetch('/api/model-monitor/status');
const statusData = await statusResponse.json();
this.monitorStatus = statusData;
// 只有在监控状态为true时才获取详细信息
if (this.monitorStatus.isMonitoring) {
try {
// 获取模型信息
const modelResponse = await fetch('/api/model-monitor/model-info');
const modelData = await modelResponse.json();
if (!modelResponse.ok) {
console.error('获取模型信息失败:', modelData.error || modelData.message);
this.monitorStatus.lastError = modelData.error || modelData.message;
// 保持原有数据不变
} else {
// 只有在数据发生变化时才更新
if (JSON.stringify(this.modelInfo) !== JSON.stringify(modelData.data)) {
this.modelInfo = modelData.data;
this.monitorStatus.lastError = null;
// 确保图表存在
if (!this.chart) {
this.initChart();
}
// 更新UI
this.updateUI();
}
}
} catch (error) {
console.warn('获取模型信息失败:', error);
// 保持原有数据不变
}
} else {
// 如果监控未运行,尝试启动监控
this.startMonitoring();
}
} catch (error) {
console.error('获取监控状态失败:', error);
this.monitorStatus.lastError = error.message;
this.updateUI();
}
}
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' };
}
}
updateUI() {
const statusIndicator = this.shadowRoot.querySelector('#statusIndicator');
const statusText = this.shadowRoot.querySelector('#statusText');
const modelTableBody = this.shadowRoot.querySelector('#model-table-body');
// 更新状态显示
if (statusIndicator && statusText) {
if (this.monitorStatus.isMonitoring) {
statusIndicator.classList.add('active');
statusIndicator.classList.remove('error');
statusText.textContent = '监控中';
} else if (this.monitorStatus.lastError) {
statusIndicator.classList.remove('active');
statusIndicator.classList.add('error');
statusText.textContent = `监控错误: ${this.monitorStatus.lastError}`;
} else {
statusIndicator.classList.remove('active', 'error');
statusText.textContent = '未监控';
}
}
// 更新模型表格
if (this.monitorStatus.isMonitoring && this.modelInfo && Array.isArray(this.modelInfo)) {
modelTableBody.innerHTML = this.modelInfo.map(model => {
const statusInfo = this.getStatusDisplay(model.status);
return `
<tr>
<td>${model.name || '未知'}</td>
<td>${model.id || '未知'}</td>
<td style="color: ${statusInfo.color}">${statusInfo.text}</td>
<td>${model.threadId || '未知'}</td>
<td>${model.node}</td>
<td>${model.priority || '未知'}</td>
<td>${model.runCount || '0'}</td>
<td>${(model.setFrequency || 0).toFixed(2)}</td>
<td>${(model.avgFrequency || 0).toFixed(2)}</td>
<td>${(model.maxFrequency || 0).toFixed(2)}</td>
<td>${(model.minFrequency || 0).toFixed(2)}</td>
<td>${(model.setPeriod || 0).toFixed(2)}</td>
<td>${(model.avgPeriod || 0).toFixed(2)}</td>
<td>${(model.maxPeriod || 0).toFixed(2)}</td>
<td>${(model.minPeriod || 0).toFixed(2)}</td>
</tr>
`;
}).join('');
// 更新图表数据
this.updateChartData();
} else {
modelTableBody.innerHTML = '<tr><td colspan="14" style="text-align: center;">暂无模型信息</td></tr>';
// 清空图表数据
if (this.chart) {
this.chart.data.labels = [];
this.chart.data.datasets = [];
this.chart.update();
}
}
}
async stopMonitoring() {
try {
// 首先检查监控状态
const statusResponse = await fetch('/api/model-monitor/status');
const statusData = await statusResponse.json();
// 如果监控未运行,直接返回
if (!statusData.isMonitoring) {
//console.log('监控未运行,无需关闭');
return;
}
// 执行关闭操作
const response = await fetch('/api/model-monitor/stop', {
method: 'POST'
});
const data = await response.json();
if (response.ok) {
this.monitorStatus = data.status;
// 立即停止状态检查
this.stopStatusCheck();
// 清空数据
this.modelInfo = 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 {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
margin-bottom: 16px;
}
.toolbar-left {
display: flex;
align-items: center;
gap: 12px;
}
.monitor-status {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #666;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: #ccc;
}
.status-indicator.active {
background: #52c41a;
}
.status-indicator.error {
background: #ff4d4f;
}
.content-container {
display: flex;
flex-direction: column;
gap: 16px;
flex: 1;
}
.panel-section {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 16px;
}
.status-display {
padding: 8px 12px;
background-color: #f5f5f5;
border-radius: 4px;
font-size: 14px;
color: #666;
}
h3 {
margin: 0 0 12px 0;
color: #333;
font-size: 16px;
}
.model-table {
width: 100%;
border-collapse: collapse;
margin-top: 8px;
}
.model-table th,
.model-table td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #e9ecef;
}
.model-table th {
background-color: #f8f9fa;
font-weight: 500;
color: #333;
}
.model-table tr:hover {
background-color: #f8f9fa;
}
.chart-container {
position: relative;
width: 100%;
height: 300px;
margin: 0;
padding: 0;
}
#model-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);
}
</style>
<div class="monitor-container">
<div class="toolbar">
<div class="toolbar-left">
<div class="monitor-status">
<div class="status-indicator" id="statusIndicator"></div>
<span id="statusText">未监控</span>
</div>
</div>
</div>
<div class="content-container">
<div class="panel-section">
<h3>模型信息</h3>
<table class="model-table">
<thead>
<tr>
<th>模型名称</th>
<th>模型ID</th>
<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="model-table-body"></tbody>
</table>
</div>
<div class="panel-section">
<h3>模型监控</h3>
<div class="chart-container">
<canvas id="model-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>
`;
}
// 修改 initChart 方法
initChart() {
const chartElement = this.shadowRoot.querySelector('#model-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.modelInfo || !Array.isArray(this.modelInfo)) {
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.modelInfo.map((model, index) => {
const dataset = this.chart.data.datasets[index] || {
label: model.name,
data: [],
borderColor: this.getRandomColor(),
fill: false
};
const value = isFrequency ? (model.currentFrequency || 0) : (model.currentPeriod || 0);
dataset.data = [...dataset.data, value].slice(-30);
return dataset;
})
};
// 使用新数据更新图表
this.chart.data = newData;
this.chart.update('none'); // 使用 'none' 模式更新,避免触发动画和事件
}
updateChartDisplayType(isFrequency) {
if (!this.modelInfo) return;
const datasets = this.modelInfo.map(model => ({
label: model.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('model-monitor', ModelMonitor);