blob: f0f3d9f45353534367a545630d96e4965d9daa45 [file] [log] [blame]
import React, { useState, useEffect } from 'react';
import {
ResponsiveContainer, LineChart, Line,
XAxis, YAxis, Tooltip, CartesianGrid, Legend
} from 'recharts';
import { fetchSysCost, fetchGpuUsage } from '../api/posts_trm';
function PerformanceLogs({ userId }) {
const [data, setData] = useState([]);
const [gpuData, setGpuData] = useState([]);
const [loading, setLoading] = useState(true);
const [unauthorized, setUnauthorized] = useState(false);
useEffect(() => {
setLoading(true);
// 获取系统性能数据
const fetchSystemData = fetchSysCost(userId)
.then(result => {
// 检查是否是权限错误
if (typeof result === 'string' && result.startsWith('<!DOCTYPE')) {
// 返回的是 HTML,说明未登录或接口错误
setUnauthorized(true);
setData([]);
return;
}
if (result && result.status === 'error' && result.message === 'Unauthorized') {
setUnauthorized(true);
setData([]);
return;
}
// 确保数据是数组格式
let list = [];
if (Array.isArray(result)) {
list = result;
} else if (Array.isArray(result.data)) {
list = result.data;
} else if (Array.isArray(result.syscost)) {
list = result.syscost;
}
const msList = list.map(item => ({
...item,
elapsed_time: item.elapsed_time * 1000,
cpu_user: item.cpu_user * 1000,
cpu_system: item.cpu_system * 1000,
memory_rss: item.memory_rss / (1024 * 1024*8), // convert bytes to MB
record_time_ts: new Date(item.record_time).getTime() // add numeric timestamp
}));
console.log('Converted data:', msList[0]); // debug first item
setData(msList);
setUnauthorized(false);
})
.catch(err => {
console.error('fetchSysCost error:', err);
if (err.message === 'Unauthorized') {
setUnauthorized(true);
setData([]);
}
});
// 获取GPU数据
const fetchGpuData = fetchGpuUsage(100)
.then(result => {
if (typeof result === 'string' && result.startsWith('<!DOCTYPE')) {
// 返回的是 HTML,说明未登录或接口错误
setGpuData([]);
return;
}
console.log('GPU data:', result);
const processedData = processGpuData(result);
setGpuData(processedData);
})
.catch(err => {
console.error('fetchGpuUsage error:', err);
});
Promise.all([fetchSystemData, fetchGpuData])
.finally(() => setLoading(false));
}, [userId]);
// 处理GPU数据,按时间戳分组
const processGpuData = (rawData) => {
const timeMap = new Map();
rawData.forEach(record => {
const timestamp = new Date(record.timestamp).getTime();
if (!timeMap.has(timestamp)) {
timeMap.set(timestamp, { timestamp });
}
const timeEntry = timeMap.get(timestamp);
timeEntry[`gpu${record.gpu_id}_usage`] = record.gpu_usage;
timeEntry[`gpu${record.gpu_id}_memory`] = record.gpu_memory_usage;
});
return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp);
};
// 获取GPU颜色
const getGpuColors = () => {
return ['#8884d8', '#82ca9d', '#ffc658', '#ff7300', '#8dd1e1', '#d084d0', '#ffb347', '#87ceeb'];
};
// 获取唯一的GPU ID列表
const getGpuIds = () => {
const ids = new Set();
gpuData.forEach(entry => {
Object.keys(entry).forEach(key => {
if (key.includes('gpu') && key.includes('_usage')) {
const gpuId = key.replace('_usage', '').replace('gpu', '');
ids.add(gpuId);
}
});
});
return Array.from(ids).sort((a, b) => parseInt(a) - parseInt(b));
};
if (loading) {
return (
<section className="dashboard-performance">
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '400px',
flexDirection: 'column'
}}>
<div style={{
border: '4px solid #f3f3f3',
borderTop: '4px solid #3498db',
borderRadius: '50%',
width: '50px',
height: '50px',
animation: 'spin 1s linear infinite'
}}></div>
<p style={{ marginTop: '20px', color: '#666' }}>加载中...</p>
<style>{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}</style>
</div>
</section>
);
}
if (unauthorized) {
return (
<section className="dashboard-performance">
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '400px',
flexDirection: 'column',
textAlign: 'center'
}}>
<div style={{
fontSize: '18px',
color: '#ff4d4f',
marginBottom: '10px'
}}>
权限不足,无法访问性能监控数据
</div>
<div style={{
fontSize: '14px',
color: '#666'
}}>
请联系管理员获取相应权限
</div>
</div>
</section>
);
}
return (
<section className="dashboard-performance">
{/* GPU使用率图表 */}
<div style={{ marginBottom: '30px' }}>
<h3>GPU 使用率</h3>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={gpuData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="timestamp"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={ts => new Date(ts).toLocaleTimeString()}
/>
<YAxis domain={[0, 100]} label={{ value: '使用率 (%)', angle: -90, position: 'insideLeft' }} />
<Tooltip
formatter={(val, name) => [`${val != null && typeof val === 'number' ? val.toFixed(2) : val}%`, name]}
labelFormatter={ts => new Date(ts).toLocaleString()}
/>
<Legend />
{getGpuIds().map((gpuId, index) => (
<Line
key={`gpu${gpuId}_usage`}
type="monotone"
dataKey={`gpu${gpuId}_usage`}
stroke={getGpuColors()[index % getGpuColors().length]}
name={`GPU ${gpuId} 使用率`}
connectNulls={false}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
{/* GPU内存占用图表 */}
<div style={{ marginBottom: '30px' }}>
<h3>GPU 内存占用</h3>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={gpuData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="timestamp"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={ts => new Date(ts).toLocaleTimeString()}
/>
<YAxis domain={[0, 'auto']} label={{ value: '内存 (MB)', angle: -90, position: 'insideLeft' }} />
<Tooltip
formatter={(val, name) => [`${val != null && typeof val === 'number' ? val.toFixed(2) : val} MB`, name]}
labelFormatter={ts => new Date(ts).toLocaleString()}
/>
<Legend />
{getGpuIds().map((gpuId, index) => (
<Line
key={`gpu${gpuId}_memory`}
type="monotone"
dataKey={`gpu${gpuId}_memory`}
stroke={getGpuColors()[index % getGpuColors().length]}
name={`GPU ${gpuId} 内存`}
connectNulls={false}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
{/* 响应时间图表 */}
<div style={{ marginBottom: '30px' }}>
<h3>响应时间</h3>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="record_time_ts"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={ts => new Date(ts).toLocaleTimeString()}
/>
<YAxis domain={[0, 'auto']} label={{ value: '时间 (ms)', angle: -90, position: 'insideLeft' }} />
<Tooltip formatter={(val) => typeof val === 'number' ? `${val.toFixed(2)} ms` : val} />
<Legend />
<Line type="monotone" dataKey="elapsed_time" stroke="#8884d8" name="响应时间 (ms)" />
</LineChart>
</ResponsiveContainer>
</div>
{/* CPU时间图表 */}
<div style={{ marginBottom: '30px' }}>
<h3>CPU 使用时间</h3>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="record_time_ts"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={ts => new Date(ts).toLocaleTimeString()}
/>
<YAxis domain={[0, 'auto']} label={{ value: '时间 (ms)', angle: -90, position: 'insideLeft' }} />
<Tooltip formatter={(val) => typeof val === 'number' ? `${val.toFixed(2)} ms` : val} />
<Legend />
<Line type="monotone" dataKey="cpu_user" stroke="#82ca9d" name="CPU 用户时间 (ms)" />
<Line type="monotone" dataKey="cpu_system" stroke="#ffc658" name="CPU 系统时间 (ms)" />
</LineChart>
</ResponsiveContainer>
</div>
{/* 内存图表 */}
<div style={{ marginBottom: '30px' }}>
<h3>内存使用</h3>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="record_time_ts"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={ts => new Date(ts).toLocaleTimeString()}
/>
<YAxis domain={[0, 'auto']} label={{ value: '内存 (MB)', angle: -90, position: 'insideLeft' }} />
<Tooltip formatter={(val) => typeof val === 'number' ? `${val.toFixed(2)} MB` : val} />
<Legend />
<Line type="monotone" dataKey="memory_rss" stroke="#ff7300" name="内存 RSS" />
</LineChart>
</ResponsiveContainer>
</div>
</section>
);
}
export default PerformanceLogs;