| 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; |