blob: 67518de48cf0aa62b1ac37e888445a4e52c3c864 [file] [log] [blame]
TRM-coding85e5c322025-06-18 19:49:21 +08001import React, { useState, useEffect } from 'react';
2import {
3 ResponsiveContainer, LineChart, Line,
4 XAxis, YAxis, Tooltip, CartesianGrid, Legend
5} from 'recharts';
TRM-coding106204b2025-06-28 00:37:46 +08006import { fetchSysCost, fetchGpuUsage } from '../api/posts_trm';
TRM-coding85e5c322025-06-18 19:49:21 +08007
8function PerformanceLogs({ userId }) {
9 const [data, setData] = useState([]);
TRM-coding106204b2025-06-28 00:37:46 +080010 const [gpuData, setGpuData] = useState([]);
TRM-coding2a8fd602025-06-19 19:33:16 +080011 const [loading, setLoading] = useState(true);
trm9984ee52025-06-20 15:16:56 +000012 const [unauthorized, setUnauthorized] = useState(false);
TRM-coding85e5c322025-06-18 19:49:21 +080013
14 useEffect(() => {
TRM-coding2a8fd602025-06-19 19:33:16 +080015 setLoading(true);
TRM-coding106204b2025-06-28 00:37:46 +080016
17 // 获取系统性能数据
18 const fetchSystemData = fetchSysCost(userId)
trm9984ee52025-06-20 15:16:56 +000019 .then(result => {
20 // 检查是否是权限错误
21 if (result && result.status === 'error' && result.message === 'Unauthorized') {
22 setUnauthorized(true);
23 setData([]);
24 return;
25 }
26
27 // 确保数据是数组格式
28 let list = [];
29 if (Array.isArray(result)) {
30 list = result;
31 } else if (Array.isArray(result.data)) {
32 list = result.data;
33 } else if (Array.isArray(result.syscost)) {
34 list = result.syscost;
35 }
36
TRM-coding882dc442025-06-18 20:13:21 +080037 const msList = list.map(item => ({
38 ...item,
39 elapsed_time: item.elapsed_time * 1000,
40 cpu_user: item.cpu_user * 1000,
41 cpu_system: item.cpu_system * 1000,
42 memory_rss: item.memory_rss / (1024 * 1024*8), // convert bytes to MB
43 record_time_ts: new Date(item.record_time).getTime() // add numeric timestamp
44 }));
45 console.log('Converted data:', msList[0]); // debug first item
46 setData(msList);
trm9984ee52025-06-20 15:16:56 +000047 setUnauthorized(false);
TRM-coding882dc442025-06-18 20:13:21 +080048 })
trm9984ee52025-06-20 15:16:56 +000049 .catch(err => {
50 console.error('fetchSysCost error:', err);
51 if (err.message === 'Unauthorized') {
52 setUnauthorized(true);
53 setData([]);
54 }
TRM-coding106204b2025-06-28 00:37:46 +080055 });
56
57 // 获取GPU数据
58 const fetchGpuData = fetchGpuUsage(100)
59 .then(result => {
60 console.log('GPU data:', result);
61 const processedData = processGpuData(result);
62 setGpuData(processedData);
trm9984ee52025-06-20 15:16:56 +000063 })
TRM-coding106204b2025-06-28 00:37:46 +080064 .catch(err => {
65 console.error('fetchGpuUsage error:', err);
66 });
67
68 Promise.all([fetchSystemData, fetchGpuData])
TRM-coding2a8fd602025-06-19 19:33:16 +080069 .finally(() => setLoading(false));
TRM-coding85e5c322025-06-18 19:49:21 +080070 }, [userId]);
71
TRM-coding106204b2025-06-28 00:37:46 +080072 // 处理GPU数据,按时间戳分组
73 const processGpuData = (rawData) => {
74 const timeMap = new Map();
75
76 rawData.forEach(record => {
77 const timestamp = new Date(record.timestamp).getTime();
78 if (!timeMap.has(timestamp)) {
79 timeMap.set(timestamp, { timestamp });
80 }
81 const timeEntry = timeMap.get(timestamp);
82 timeEntry[`gpu${record.gpu_id}_usage`] = record.gpu_usage;
83 timeEntry[`gpu${record.gpu_id}_memory`] = record.gpu_memory_usage;
84 });
85
86 return Array.from(timeMap.values()).sort((a, b) => a.timestamp - b.timestamp);
87 };
88
89 // 获取GPU颜色
90 const getGpuColors = () => {
91 return ['#8884d8', '#82ca9d', '#ffc658', '#ff7300', '#8dd1e1', '#d084d0', '#ffb347', '#87ceeb'];
92 };
93
94 // 获取唯一的GPU ID列表
95 const getGpuIds = () => {
96 const ids = new Set();
97 gpuData.forEach(entry => {
98 Object.keys(entry).forEach(key => {
99 if (key.includes('gpu') && key.includes('_usage')) {
100 const gpuId = key.replace('_usage', '').replace('gpu', '');
101 ids.add(gpuId);
102 }
103 });
104 });
105 return Array.from(ids).sort((a, b) => parseInt(a) - parseInt(b));
106 };
107
TRM-coding2a8fd602025-06-19 19:33:16 +0800108 if (loading) {
109 return (
110 <section className="dashboard-performance">
111 <div style={{
112 display: 'flex',
113 justifyContent: 'center',
114 alignItems: 'center',
115 height: '400px',
116 flexDirection: 'column'
117 }}>
118 <div style={{
119 border: '4px solid #f3f3f3',
120 borderTop: '4px solid #3498db',
121 borderRadius: '50%',
122 width: '50px',
123 height: '50px',
124 animation: 'spin 1s linear infinite'
125 }}></div>
126 <p style={{ marginTop: '20px', color: '#666' }}>加载中...</p>
127 <style>{`
128 @keyframes spin {
129 0% { transform: rotate(0deg); }
130 100% { transform: rotate(360deg); }
131 }
132 `}</style>
133 </div>
134 </section>
135 );
136 }
137
trm9984ee52025-06-20 15:16:56 +0000138 if (unauthorized) {
139 return (
140 <section className="dashboard-performance">
141 <div style={{
142 display: 'flex',
143 justifyContent: 'center',
144 alignItems: 'center',
145 height: '400px',
146 flexDirection: 'column',
147 textAlign: 'center'
148 }}>
149 <div style={{
150 fontSize: '18px',
151 color: '#ff4d4f',
152 marginBottom: '10px'
153 }}>
154 权限不足,无法访问性能监控数据
155 </div>
156 <div style={{
157 fontSize: '14px',
158 color: '#666'
159 }}>
160 请联系管理员获取相应权限
161 </div>
162 </div>
163 </section>
164 );
165 }
166
TRM-coding85e5c322025-06-18 19:49:21 +0800167 return (
168 <section className="dashboard-performance">
TRM-coding106204b2025-06-28 00:37:46 +0800169 {/* GPU使用率图表 */}
170 <div style={{ marginBottom: '30px' }}>
171 <h3>GPU 使用率</h3>
172 <ResponsiveContainer width="100%" height={300}>
173 <LineChart data={gpuData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
174 <CartesianGrid strokeDasharray="3 3" />
175 <XAxis
176 dataKey="timestamp"
177 type="number"
178 domain={['dataMin', 'dataMax']}
179 tickFormatter={ts => new Date(ts).toLocaleTimeString()}
180 />
181 <YAxis domain={[0, 100]} label={{ value: '使用率 (%)', angle: -90, position: 'insideLeft' }} />
182 <Tooltip
183 formatter={(val, name) => [`${val?.toFixed(2)}%`, name]}
184 labelFormatter={ts => new Date(ts).toLocaleString()}
185 />
186 <Legend />
187 {getGpuIds().map((gpuId, index) => (
188 <Line
189 key={`gpu${gpuId}_usage`}
190 type="monotone"
191 dataKey={`gpu${gpuId}_usage`}
192 stroke={getGpuColors()[index % getGpuColors().length]}
193 name={`GPU ${gpuId} 使用率`}
194 connectNulls={false}
195 />
196 ))}
197 </LineChart>
198 </ResponsiveContainer>
199 </div>
200
201 {/* GPU内存占用图表 */}
202 <div style={{ marginBottom: '30px' }}>
203 <h3>GPU 内存占用</h3>
204 <ResponsiveContainer width="100%" height={300}>
205 <LineChart data={gpuData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
206 <CartesianGrid strokeDasharray="3 3" />
207 <XAxis
208 dataKey="timestamp"
209 type="number"
210 domain={['dataMin', 'dataMax']}
211 tickFormatter={ts => new Date(ts).toLocaleTimeString()}
212 />
213 <YAxis domain={[0, 'auto']} label={{ value: '内存 (MB)', angle: -90, position: 'insideLeft' }} />
214 <Tooltip
215 formatter={(val, name) => [`${val?.toFixed(2)} MB`, name]}
216 labelFormatter={ts => new Date(ts).toLocaleString()}
217 />
218 <Legend />
219 {getGpuIds().map((gpuId, index) => (
220 <Line
221 key={`gpu${gpuId}_memory`}
222 type="monotone"
223 dataKey={`gpu${gpuId}_memory`}
224 stroke={getGpuColors()[index % getGpuColors().length]}
225 name={`GPU ${gpuId} 内存`}
226 connectNulls={false}
227 />
228 ))}
229 </LineChart>
230 </ResponsiveContainer>
231 </div>
232
TRM-coding882dc442025-06-18 20:13:21 +0800233 {/* 响应时间图表 */}
234 <div style={{ marginBottom: '30px' }}>
235 <h3>响应时间</h3>
236 <ResponsiveContainer width="100%" height={300}>
237 <LineChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
238 <CartesianGrid strokeDasharray="3 3" />
239 <XAxis
240 dataKey="record_time_ts"
241 type="number"
242 domain={['dataMin', 'dataMax']}
243 tickFormatter={ts => new Date(ts).toLocaleTimeString()}
244 />
245 <YAxis domain={[0, 'auto']} label={{ value: '时间 (ms)', angle: -90, position: 'insideLeft' }} />
246 <Tooltip formatter={(val) => typeof val === 'number' ? `${val.toFixed(2)} ms` : val} />
247 <Legend />
248 <Line type="monotone" dataKey="elapsed_time" stroke="#8884d8" name="响应时间 (ms)" />
249 </LineChart>
250 </ResponsiveContainer>
251 </div>
252
253 {/* CPU时间图表 */}
254 <div style={{ marginBottom: '30px' }}>
255 <h3>CPU 使用时间</h3>
256 <ResponsiveContainer width="100%" height={300}>
257 <LineChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
258 <CartesianGrid strokeDasharray="3 3" />
259 <XAxis
260 dataKey="record_time_ts"
261 type="number"
262 domain={['dataMin', 'dataMax']}
263 tickFormatter={ts => new Date(ts).toLocaleTimeString()}
264 />
265 <YAxis domain={[0, 'auto']} label={{ value: '时间 (ms)', angle: -90, position: 'insideLeft' }} />
266 <Tooltip formatter={(val) => typeof val === 'number' ? `${val.toFixed(2)} ms` : val} />
267 <Legend />
268 <Line type="monotone" dataKey="cpu_user" stroke="#82ca9d" name="CPU 用户时间 (ms)" />
269 <Line type="monotone" dataKey="cpu_system" stroke="#ffc658" name="CPU 系统时间 (ms)" />
270 </LineChart>
271 </ResponsiveContainer>
272 </div>
273
274 {/* 内存图表 */}
275 <div style={{ marginBottom: '30px' }}>
276 <h3>内存使用</h3>
277 <ResponsiveContainer width="100%" height={300}>
278 <LineChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
279 <CartesianGrid strokeDasharray="3 3" />
280 <XAxis
281 dataKey="record_time_ts"
282 type="number"
283 domain={['dataMin', 'dataMax']}
284 tickFormatter={ts => new Date(ts).toLocaleTimeString()}
285 />
286 <YAxis domain={[0, 'auto']} label={{ value: '内存 (MB)', angle: -90, position: 'insideLeft' }} />
287 <Tooltip formatter={(val) => typeof val === 'number' ? `${val.toFixed(2)} MB` : val} />
288 <Legend />
289 <Line type="monotone" dataKey="memory_rss" stroke="#ff7300" name="内存 RSS" />
290 </LineChart>
291 </ResponsiveContainer>
292 </div>
TRM-coding85e5c322025-06-18 19:49:21 +0800293 </section>
294 );
295}
296
297export default PerformanceLogs;