add gpuusage的逻辑

Change-Id: Ie1634a7d58ab94b05a3ebd7de87d362ca9be2286
diff --git a/Merge/back_rhj/app.py b/Merge/back_rhj/app.py
index df7a598..967b921 100644
--- a/Merge/back_rhj/app.py
+++ b/Merge/back_rhj/app.py
@@ -1,6 +1,47 @@
 from app import create_app
+from flask_cors import CORS
+import os
+import psutil
+import time
+import GPUtil
+from flask import g, request
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from config import Config
+from app.utils.gpuwriter import GpuWriter
 
 app = create_app()
+CORS(app,
+     resources={r"/*": {"origins": "*"}},
+     supports_credentials=True,
+     allow_headers=["Content-Type", "Authorization"]
+)
+
+proc = psutil.Process(os.getpid())
+
+@app.before_request
+def before_request():
+    g.start_time = time.time()
+    g.start_cpu = proc.cpu_times()
+    g.start_mem = proc.memory_info()
+
+@app.after_request
+def after_request(response):
+    try:
+        # 记录GPU使用情况
+        gpu_writer = GpuWriter(Config.SQLURL)
+        gpus = GPUtil.getGPUs()
+        
+        for gpu in gpus:
+            gpu_writer.write_gpu_usage(
+                gpu_id=gpu.id,
+                gpu_usage=gpu.load * 100,  # 转换为百分比
+                gpu_memory_usage=gpu.memoryUsed  # MB
+            )
+    except Exception as e:
+        print(f"GPU使用情况记录失败: {e}")
+    
+    return response
 
 if __name__ == "__main__":
     app.run(debug=True,port=8082,host='0.0.0.0')
\ No newline at end of file
diff --git a/Merge/back_rhj/app/__pycache__/__init__.cpython-310.pyc b/Merge/back_rhj/app/__pycache__/__init__.cpython-310.pyc
index bc775fd..08f5017 100644
--- a/Merge/back_rhj/app/__pycache__/__init__.cpython-310.pyc
+++ b/Merge/back_rhj/app/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/__pycache__/routes.cpython-310.pyc b/Merge/back_rhj/app/__pycache__/routes.cpython-310.pyc
index f5aa9b3..46bb4b4 100644
--- a/Merge/back_rhj/app/__pycache__/routes.cpython-310.pyc
+++ b/Merge/back_rhj/app/__pycache__/routes.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/__pycache__/gpu_usage.cpython-310.pyc b/Merge/back_rhj/app/models/__pycache__/gpu_usage.cpython-310.pyc
new file mode 100644
index 0000000..79f092a
--- /dev/null
+++ b/Merge/back_rhj/app/models/__pycache__/gpu_usage.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/gpu_usage.py b/Merge/back_rhj/app/models/gpu_usage.py
new file mode 100644
index 0000000..0ac5cfa
--- /dev/null
+++ b/Merge/back_rhj/app/models/gpu_usage.py
@@ -0,0 +1,18 @@
+from sqlalchemy import Column, Integer, DECIMAL, TIMESTAMP, text
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.sql import func
+
+Base = declarative_base()
+
+class GpuUsage(Base):
+    __tablename__ = 'gpu_usage'
+    __table_args__ = {
+        'mysql_engine': 'InnoDB',
+        'mysql_charset': 'utf8mb4',
+        'comment': 'GPU 使用情况表'
+    }
+    
+    gpu_id = Column(Integer, primary_key=True, nullable=False, comment='GPU 编号')
+    gpu_usage = Column(DECIMAL(5, 2), nullable=False, comment='GPU 使用率,单位:百分比')
+    gpu_memory_usage = Column(Integer, nullable=False, comment='GPU 内存用量,单位:MB')
+    created_at = Column(TIMESTAMP, nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='记录时间戳')
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-310.pyc b/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-310.pyc
index 5cd2aee..f50ef90 100644
--- a/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-310.pyc
+++ b/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/routes.py b/Merge/back_rhj/app/routes.py
index 6f159ca..0b60624 100644
--- a/Merge/back_rhj/app/routes.py
+++ b/Merge/back_rhj/app/routes.py
@@ -2,6 +2,7 @@
 from .functions.FAuth import FAuth
 from app.services.recommendation_service import RecommendationService
 from app.utils.graph_build import build_user_post_graph
+from app.utils.gpuwriter import GpuWriter
 from sqlalchemy import create_engine
 from sqlalchemy.orm import sessionmaker
 from config import Config
@@ -409,3 +410,37 @@
             'success': False, 
             'message': f'推荐获取失败: {str(e)}'
         }), 500
+
+@main.route('/gpu-usage', methods=['GET'])
+@token_required
+def get_gpu_usage(current_user):
+    """获取最新GPU使用情况接口"""
+    try:
+        limit = request.args.get('limit', 100, type=int)
+        
+        # 限制最大获取条数
+        if limit > 1000:
+            limit = 1000
+            
+        print(f"获取最新 {limit} 条GPU使用记录")
+        
+        # 创建GpuWriter实例并获取数据
+        gpu_writer = GpuWriter(Config.SQLURL)
+        gpu_records = gpu_writer.get_latest_gpu_usage(limit)
+        
+        return jsonify({
+            'success': True,
+            'data': {
+                'records': gpu_records,
+                'count': len(gpu_records),
+                'limit': limit
+            },
+            'message': 'GPU使用情况获取成功'
+        }), 200
+        
+    except Exception as e:
+        print(f"获取GPU使用情况错误: {str(e)}")
+        return jsonify({
+            'success': False, 
+            'message': f'获取GPU使用情况失败: {str(e)}'
+        }), 500
diff --git a/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-310.pyc b/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-310.pyc
index 495528d..c98aaf5 100644
--- a/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-310.pyc
+++ b/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-310.pyc b/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-310.pyc
index 88ab960..1424b42 100644
--- a/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-310.pyc
+++ b/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/__pycache__/gpuwriter.cpython-310.pyc b/Merge/back_rhj/app/utils/__pycache__/gpuwriter.cpython-310.pyc
new file mode 100644
index 0000000..f86da67
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/gpuwriter.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/gpuwriter.py b/Merge/back_rhj/app/utils/gpuwriter.py
new file mode 100644
index 0000000..1e521f3
--- /dev/null
+++ b/Merge/back_rhj/app/utils/gpuwriter.py
@@ -0,0 +1,68 @@
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy import create_engine
+from app.models.gpu_usage import GpuUsage
+import logging
+
+class GpuWriter:
+    def __init__(self, database_url):
+        """
+        初始化 GpuWriter
+        :param database_url: 数据库连接URL
+        """
+        self.engine = create_engine(database_url)
+        self.Session = sessionmaker(bind=self.engine)
+    
+    def write_gpu_usage(self, gpu_id, gpu_usage, gpu_memory_usage):
+        """
+        写入GPU使用情况到数据库
+        :param gpu_id: GPU编号
+        :param gpu_usage: GPU使用率(百分比)
+        :param gpu_memory_usage: GPU内存用量(MB)
+        :return: bool 是否写入成功
+        """
+        session = self.Session()
+        try:
+            gpu_record = GpuUsage(
+                gpu_id=gpu_id,
+                gpu_usage=gpu_usage,
+                gpu_memory_usage=gpu_memory_usage
+            )
+            session.add(gpu_record)
+            session.commit()
+            return True
+        except Exception as e:
+            session.rollback()
+            logging.error(f"写入GPU使用情况失败: {e}")
+            return False
+        finally:
+            session.close()
+
+    def get_latest_gpu_usage(self, limit=100):
+        """
+        获取最新的GPU使用情况记录
+        :param limit: 获取记录条数,默认100条
+        :return: list 包含GPU使用记录的字典列表
+        """
+        session = self.Session()
+        try:
+            records = session.query(GpuUsage)\
+                           .order_by(GpuUsage.timestamp.desc())\
+                           .limit(limit)\
+                           .all()
+            
+            result = []
+            for record in records:
+                result.append({
+                    'id': record.id,
+                    'gpu_id': record.gpu_id,
+                    'gpu_usage': record.gpu_usage,
+                    'gpu_memory_usage': record.gpu_memory_usage,
+                    'timestamp': record.timestamp.isoformat() if record.timestamp else None
+                })
+            
+            return result
+        except Exception as e:
+            logging.error(f"获取GPU使用情况失败: {e}")
+            return []
+        finally:
+            session.close()
diff --git a/Merge/front/src/api/posts_trm.js b/Merge/front/src/api/posts_trm.js
index d6303a7..8f7e877 100644
--- a/Merge/front/src/api/posts_trm.js
+++ b/Merge/front/src/api/posts_trm.js
@@ -209,4 +209,30 @@
   }
   console.log('Normalized sys cost list:', list)
   return list
+}
+
+/**
+ * 获取GPU使用情况数据
+ * GET /gpu-usage
+ * @param {number} limit 获取记录条数,默认100条
+ * @returns Promise<[ {id, gpu_id, gpu_usage, gpu_memory_usage, timestamp}, … ]>
+ */
+export async function fetchGpuUsage(limit = 100) {
+  const token = localStorage.getItem('token')
+  const res = await fetch(`http://10.126.59.25:8082/gpu-usage?limit=${limit}`, {
+    method: 'GET',
+    headers: { 
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${token}`
+    }
+  })
+  if (!res.ok) throw new Error(`fetchGpuUsage: ${res.status}`)
+  const json = await res.json()
+  console.log('fetchGpuUsage response:', json)
+  
+  if (json.success === false) {
+    throw new Error(json.message || 'Failed to fetch GPU usage')
+  }
+  
+  return json.data?.records || []
 }
\ No newline at end of file
diff --git a/Merge/front/src/components/PerformanceLogs.js b/Merge/front/src/components/PerformanceLogs.js
index f87ef7a..67518de 100644
--- a/Merge/front/src/components/PerformanceLogs.js
+++ b/Merge/front/src/components/PerformanceLogs.js
@@ -3,16 +3,19 @@
   ResponsiveContainer, LineChart, Line,
   XAxis, YAxis, Tooltip, CartesianGrid, Legend
 } from 'recharts';
-import { fetchSysCost } from '../api/posts_trm';
+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);
-    fetchSysCost(userId)
+    
+    // 获取系统性能数据
+    const fetchSystemData = fetchSysCost(userId)
       .then(result => {
         // 检查是否是权限错误
         if (result && result.status === 'error' && result.message === 'Unauthorized') {
@@ -49,10 +52,59 @@
           setUnauthorized(true);
           setData([]);
         }
+      });
+
+    // 获取GPU数据
+    const fetchGpuData = fetchGpuUsage(100)
+      .then(result => {
+        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">
@@ -114,6 +166,70 @@
 
   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?.toFixed(2)}%`, 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?.toFixed(2)} 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>