为伏羲模型构建自动化监控面板Web技术栈实战最近在负责一个伏羲模型的生产服务最头疼的就是服务上线后的“黑盒”状态。模型跑得好不好GPU资源吃紧了吗用户请求有没有异常堆积以前全靠手动查日志既费时又容易遗漏关键问题。直到我们决定自己动手用最熟悉的Web技术栈给模型服务装上一个“仪表盘”。这个监控面板不是什么复杂的商业软件就是用HTML、CSS、JavaScript这些前端基本功加上一个轻量级的后端框架搭起来的。但它带来的价值是实实在在的服务健康状态一目了然关键指标实时刷新一旦有异常还能自动告警。今天我就把这个从零到一的搭建过程以及我们趟过的一些坑分享给你。如果你也在为AI服务的可观测性发愁希望这篇实战记录能给你带来一些直接的启发。1. 为什么需要一个专属的监控面板你可能觉得用现成的监控工具不就好了吗确实市面上有很多优秀的APM和监控平台。但在我们实际用伏羲模型的过程中发现了一些特殊需求是通用工具不太容易完美覆盖的。首先伏羲模型这类大模型服务有几个非常核心的指标需要特别关注。比如预测耗时Latency它不仅包含模型推理时间还可能包括数据预处理、后处理的时间波动一大用户体验就直线下降。再比如GPU利用率这直接关系到我们的算力成本花得值不值是模型有没有在“偷懒”的重要标志。还有API的调用量和成功率这反映了服务的可用性和业务流量。其次我们需要一个高度定制化的视图。开发、运维、甚至业务方他们关心的数据维度不一样。开发可能想看详细的错误堆栈和GPU内存波动运维更关注服务整体的SLA和资源水位业务方则只关心接口成功率。一个固定的仪表盘很难满足所有人。最后是成本和灵活性的权衡。引入一套完整的商业监控体系学习成本、接入成本和后续的定制化成本都不低。而我们用Web技术栈自己搭建前端可以随心所欲地设计图表和布局后端接口只采集我们真正关心的数据整个系统轻量、可控并且能与现有的运维流程比如告警通知到钉钉或企业微信无缝集成。所以我们决定自己动手目标很明确构建一个轻量、实时、专注伏羲模型服务的监控面板。2. 技术栈选型与整体设计在开始敲代码之前我们先花点时间把技术方案定下来。原则是用熟悉的、轻量的、能快速上手的。前端部分这是仪表盘的脸面核心是数据可视化。基础三件套HTML、CSS、JavaScript 是根本没得说。图表库我们选择了ECharts。原因很简单它足够强大能绘制折线图、柱状图、仪表盘、热力图等几乎所有我们需要的图表类型而且文档丰富社区活跃。它的配置项驱动方式用起来也很顺手。布局与样式为了快速搭建一个整洁的响应式界面我们引入了Bootstrap框架。它的栅格系统能让我们轻松排列各个监控卡片省去了大量调整CSS布局的麻烦。后端部分负责收集数据、提供API接口。框架我们用了Flask。Python写起来快Flask又足够轻量灵活非常适合这种小型、专用的服务。如果你更熟悉 Node.js 的 Express 或者 Go 的 Gin原理都是相通的。数据采集这是关键。我们需要从伏羲模型服务本身、以及服务器主机上抓取数据。模型服务指标如调用量、耗时通过在模型服务的API入口处埋点将日志写入一个中间件如Redis或直接由监控后端定时拉取。系统资源指标如GPU利用率、内存我们使用了Prometheus Client Library。在监控后端启动一个Prometheus的指标暴露端点再利用nvidia-smi的命令行工具或pynvml库来获取GPU信息转换成Prometheus格式的指标。数据存储与更新为了实时性我们没有用传统数据库。监控数据是典型的时序数据且需要快速读写。我们用了Redis来暂存最近一段时间比如24小时的聚合数据前端通过后端API来获取。这种方案简单高效适合监控场景。整个数据流是这样的伏羲模型服务在每次处理请求时会向Redis记录一条日志包含时间戳、耗时、状态码等。同时一个后台定时任务Cron Job会定期执行nvidia-smi将GPU数据也写入Redis。我们的Flask后端则提供几个简单的API前端定时调用这些API从Redis里取出最新数据交给ECharts渲染成图表。3. 一步步搭建监控后端Flask理论说完了我们来看代码。先从后端开始它是数据的枢纽。首先确保你的环境已经安装了必要的包flask,redis,prometheus-client。# app.py from flask import Flask, jsonify import redis import json from datetime import datetime, timedelta import threading import time import subprocess import psutil app Flask(__name__) # 连接Redis假设运行在本地 redis_client redis.Redis(hostlocalhost, port6379, decode_responsesTrue) # 定义存储数据的Key前缀 METRIC_KEY_PREFIX fuxi_monitor API_CALLS_KEY f{METRIC_KEY_PREFIX}:api_calls LATENCY_KEY f{METRIC_KEY_PREFIX}:latency GPU_UTIL_KEY f{METRIC_KEY_PREFIX}:gpu_util我们定义了几个Redis的Key来分别存储不同的指标。接下来编写一个后台线程函数用来定期采集GPU和系统信息。def collect_system_metrics(): 后台任务定期收集GPU和系统指标 while True: try: # 1. 收集GPU信息 (示例使用nvidia-smi) # 这里需要根据你的实际环境调整命令和解析逻辑 result subprocess.run([nvidia-smi, --query-gpuutilization.gpu,memory.used,memory.total, --formatcsv,noheader,nounits], capture_outputTrue, textTrue) if result.returncode 0: gpu_data result.stdout.strip().split(, ) if len(gpu_data) 3: gpu_util float(gpu_data[0]) # GPU利用率 % mem_used float(gpu_data[1]) # 已用显存 MB mem_total float(gpu_data[2]) # 总显存 MB mem_util (mem_used / mem_total) * 100 if mem_total 0 else 0 # 将当前时间点的数据存入一个有序集合ZSET用时间戳做分数 current_ts time.time() metric_data { gpu_util: gpu_util, mem_util: mem_util } # 存储最近1小时的数据并自动清理旧数据 redis_client.zadd(GPU_UTIL_KEY, {json.dumps(metric_data): current_ts}) # 移除1小时前的数据 one_hour_ago current_ts - 3600 redis_client.zremrangebyscore(GPU_UTIL_KEY, 0, one_hour_ago) # 2. 收集系统CPU/内存 (使用psutil) cpu_percent psutil.cpu_percent(intervalNone) mem psutil.virtual_memory() mem_percent mem.percent # ... 可以类似地存储系统指标 time.sleep(5) # 每5秒采集一次 except Exception as e: print(f采集指标时出错: {e}) time.sleep(30) # 启动后台采集线程 metric_thread threading.Thread(targetcollect_system_metrics, daemonTrue) metric_thread.start()然后我们需要在模型服务的API处理逻辑里埋点。假设你的伏羲模型服务有一个/predict接口。# 在你的模型服务代码中例如 model_server.py import time import redis import json redis_client redis.Redis(hostlocalhost, port6379, decode_responsesTrue) METRIC_KEY_PREFIX fuxi_monitor API_CALLS_KEY f{METRIC_KEY_PREFIX}:api_calls LATENCY_KEY f{METRIC_KEY_PREFIX}:latency def predict_endpoint(request_data): 模拟模型预测接口 start_time time.time() status success try: # 这里是你的模型推理逻辑 # result model.predict(request_data) result {prediction: sample_result} # 模拟处理时间 time.sleep(0.1) except Exception as e: status error result {error: str(e)} end_time time.time() latency_ms (end_time - start_time) * 1000 # 转换为毫秒 # 埋点记录本次调用 call_record { timestamp: end_time, latency: latency_ms, status: status } # 使用管道提高性能 pipe redis_client.pipeline() pipe.lpush(API_CALLS_KEY, json.dumps(call_record)) # 存储详细记录 pipe.ltrim(API_CALLS_KEY, 0, 10000) # 只保留最近10000条防止内存爆炸 pipe.execute() return result最后为前端提供数据查询的API。# app.py 继续 app.route(/api/metrics/summary) def get_metrics_summary(): 获取指标摘要用于仪表盘顶部的概览卡片 summary {} # 1. 计算最近1分钟的成功率、调用量、平均耗时 one_minute_ago time.time() - 60 recent_calls_raw redis_client.lrange(API_CALLS_KEY, 0, -1) # 获取全部实际应优化 recent_calls [] for item in recent_calls_raw: try: record json.loads(item) if record[timestamp] one_minute_ago: recent_calls.append(record) except: pass if recent_calls: total_calls len(recent_calls) success_calls len([r for r in recent_calls if r[status] success]) avg_latency sum([r[latency] for r in recent_calls]) / total_calls if total_calls 0 else 0 summary[qps] round(total_calls / 60, 2) # 估算QPS summary[success_rate] round((success_calls / total_calls) * 100, 2) if total_calls 0 else 100 summary[avg_latency_ms] round(avg_latency, 2) else: summary[qps] 0 summary[success_rate] 100 summary[avg_latency_ms] 0 # 2. 获取最新的GPU数据 latest_gpu_data redis_client.zrange(GPU_UTIL_KEY, -1, -1) # 获取最新的一条 if latest_gpu_data: gpu_info json.loads(latest_gpu_data[0]) summary[gpu_util] gpu_info.get(gpu_util, 0) summary[mem_util] gpu_info.get(mem_util, 0) else: summary[gpu_util] 0 summary[mem_util] 0 return jsonify(summary) app.route(/api/metrics/history/metric_name) def get_metric_history(metric_name): 获取某个指标的历史数据用于绘制趋势图 # 这里简化处理实际应根据metric_name从不同的Redis Key中获取数据 # 例如返回最近30分钟的GPU利用率数据点 thirty_min_ago time.time() - 1800 history_data [] if metric_name gpu_util: # 从有序集合中获取时间窗口内的数据 data_points redis_client.zrangebyscore(GPU_UTIL_KEY, thirty_min_ago, time.time()) for point in data_points: record json.loads(point) # 这里需要根据存储的数据结构调整 history_data.append(record.get(gpu_util, 0)) # ... 其他指标类似 # 补全时间戳这里简化返回数据点列表 return jsonify({ timestamps: [], # 实际应生成对应的时间戳列表 values: history_data[-100:] # 只返回最近100个点避免前端压力 })这样一个简单的监控后端就搭好了。它提供了两个核心接口一个返回当前状态的摘要一个返回历史趋势数据。4. 打造实时数据前端ECharts Bootstrap后端准备好了数据前端的工作就是把这些数据漂亮地展示出来。我们创建一个index.html文件。首先引入必要的库Bootstrap和ECharts。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title伏羲模型服务监控面板/title !-- Bootstrap 5 CSS -- link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/css/bootstrap.min.css relstylesheet !-- ECharts -- script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script style .metric-card { margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,.1); border-radius: 8px; padding: 15px; background: #fff; } .chart-container { height: 300px; width: 100%; } .status-healthy { color: #28a745; font-weight: bold; } .status-warning { color: #ffc107; font-weight: bold; } .status-danger { color: #dc3545; font-weight: bold; } /style /head body div classcontainer-fluid mt-3 h1 classmb-4伏羲模型服务监控面板/h1 !-- 第一行关键指标概览 -- div classrow idsummaryRow !-- 指标卡片将通过JS动态生成 -- /div !-- 第二行趋势图表 -- div classrow mt-4 div classcol-md-6 div classmetric-card h5API调用量 成功率趋势/h5 div idchartApiTrend classchart-container/div /div /div div classcol-md-6 div classmetric-card h5预测平均耗时趋势 (ms)/h5 div idchartLatencyTrend classchart-container/div /div /div /div !-- 第三行GPU/系统资源 -- div classrow mt-4 div classcol-md-6 div classmetric-card h5GPU利用率 (%)/h5 div idchartGpuUtil classchart-container/div /div /div div classcol-md-6 div classmetric-card h5显存使用率 (%)/h5 div idchartGpuMem classchart-container/div /div /div /div /div !-- Bootstrap JS Bundle -- script srchttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/js/bootstrap.bundle.min.js/script script srcdashboard.js/script !-- 我们的主逻辑JS -- /body /html接下来是核心的dashboard.js文件它负责定时拉取数据并更新图表。// dashboard.js document.addEventListener(DOMContentLoaded, function() { // 初始化ECharts实例 const apiTrendChart echarts.init(document.getElementById(chartApiTrend)); const latencyChart echarts.init(document.getElementById(chartLatencyTrend)); const gpuUtilChart echarts.init(document.getElementById(chartGpuUtil)); const gpuMemChart echarts.init(document.getElementById(chartGpuMem)); // 定义图表的基本配置 const commonChartOption { tooltip: { trigger: axis }, grid: { left: 3%, right: 4%, bottom: 3%, top: 10%, containLabel: true }, xAxis: { type: time, boundaryGap: false }, yAxis: { type: value }, series: [] }; // 初始化图表 apiTrendChart.setOption({ ...commonChartOption, legend: { data: [QPS, 成功率] }, yAxis: [{ type: value, name: QPS }, { type: value, name: %, max: 100 }] }); latencyChart.setOption({ ...commonChartOption }); gpuUtilChart.setOption({ ...commonChartOption }); gpuMemChart.setOption({ ...commonChartOption }); // 定义要监控的指标卡片 const metricCards [ { id: qps, title: 实时QPS, unit: , selector: #summaryRow, threshold: null }, { id: success_rate, title: 成功率, unit: %, selector: #summaryRow, threshold: { warn: 99, danger: 95 } }, { id: avg_latency_ms, title: 平均耗时, unit: ms, selector: #summaryRow, threshold: { warn: 500, danger: 1000 } }, { id: gpu_util, title: GPU利用率, unit: %, selector: #summaryRow, threshold: { warn: 80, danger: 95 } }, { id: mem_util, title: 显存使用率, unit: %, selector: #summaryRow, threshold: { warn: 85, danger: 95 } } ]; // 首次加载时创建卡片 renderMetricCards(metricCards); // 定时更新数据每3秒 setInterval(fetchAndUpdateData, 3000); fetchAndUpdateData(); // 立即执行一次 async function fetchAndUpdateData() { try { const response await fetch(/api/metrics/summary); const summary await response.json(); // 1. 更新指标卡片 updateMetricCards(summary); // 2. 更新图表这里简化实际应调用历史数据接口 // updateCharts(summary); // 3. 检查告警 checkAlerts(summary); } catch (error) { console.error(获取监控数据失败:, error); } } function renderMetricCards(cards) { const container document.querySelector(#summaryRow); container.innerHTML ; // 清空 cards.forEach(card { const col document.createElement(div); col.className col; col.innerHTML div classmetric-card text-center h6 classtext-muted${card.title}/h6 h2 idvalue-${card.id}--/h2 div${card.unit}/div small idstatus-${card.id} classstatus-healthy正常/small /div ; container.appendChild(col); }); } function updateMetricCards(data) { metricCards.forEach(card { const valueElement document.getElementById(value-${card.id}); const statusElement document.getElementById(status-${card.id}); const value data[card.id] ! undefined ? data[card.id] : --; if (valueElement) valueElement.textContent typeof value number ? value.toFixed(2) : value; // 根据阈值更新状态 if (card.threshold typeof value number) { let statusClass status-healthy; let statusText 正常; if (value card.threshold.danger) { statusClass status-danger; statusText 危险; } else if (value card.threshold.warn) { statusClass status-warning; statusText 警告; } statusElement.className statusClass; statusElement.textContent statusText; } }); } function checkAlerts(data) { // 这里可以实现告警逻辑比如当成功率低于95%时在控制台打印或发送通知 if (data.success_rate 95) { console.warn(告警服务成功率下降至 ${data.success_rate}%); // 在实际项目中这里可以调用后端接口发送钉钉/企业微信/邮件告警 } if (data.avg_latency_ms 1000) { console.warn(告警平均预测耗时过高 ${data.avg_latency_ms}ms); } } });现在启动你的Flask应用 (python app.py)然后在浏览器中打开index.html你可能需要一个简单的HTTP服务器比如python -m http.server就能看到一个实时刷新的监控面板了。顶部的卡片会显示当前关键指标和健康状态下面的图表区域预留了位置你可以根据/api/metrics/history/接口返回的数据用ECharts绘制出漂亮的历史趋势线。5. 从监控到告警让面板更智能一个只会展示数据的面板是被动的。真正的价值在于当指标异常时它能主动告诉我们。我们在前端JS里已经做了简单的控制台告警但这还不够。一个更健壮的告警系统应该在后端实现。我们可以在Flask后端增加一个定时检查的任务或者更优雅地使用Celery这样的异步任务队列。这里给出一个在Flask内实现简单定时检查的思路# 在app.py中增加 from apscheduler.schedulers.background import BackgroundScheduler scheduler BackgroundScheduler() def check_alert_rules(): 定时检查告警规则 try: summary get_current_summary() # 一个函数用于计算当前的指标摘要类似/api/metrics/summary的逻辑 # 规则1: 成功率低于阈值 if summary.get(success_rate, 100) 95: send_alert(f服务成功率告警: {summary[success_rate]}%, leveldanger) # 规则2: 平均耗时高于阈值 if summary.get(avg_latency_ms, 0) 1000: send_alert(f预测耗时告警: {summary[avg_latency_ms]}ms, levelwarning) # 规则3: GPU利用率持续过高 if summary.get(gpu_util, 0) 90: send_alert(fGPU利用率告警: {summary[gpu_util]}%, levelwarning) except Exception as e: print(f告警检查失败: {e}) def send_alert(message, levelwarning): 发送告警示例打印到日志实际可集成钉钉/微信/webhook print(f[ALERT-{level.upper()}] {message}) # TODO: 在这里调用你的告警通道API例如 # requests.post(你的钉钉机器人webhook, json{text: message}) # 或者发送邮件写入数据库等。 # 每30秒检查一次 scheduler.add_job(funccheck_alert_rules, triggerinterval, seconds30) scheduler.start()这样一个具备基本监控和告警能力的面板就完整了。你可以根据实际需求继续丰富它的功能比如增加更多图表错误类型分布、请求来源分布等。历史数据持久化将Redis中的数据定期归档到MySQL或时序数据库如InfluxDB中用于长期趋势分析和报表。多服务/多实例监控改造后端使其能监控多个伏羲模型服务实例并在前端进行聚合展示或切换。权限控制为面板增加简单的登录验证。6. 写在最后回过头看用Web技术栈搭建这么一个监控面板其实并没有想象中那么复杂。核心就是数据采集、存储、展示、告警这四个环节。我们用的技术Flask, Redis, ECharts可能不是性能最极致或功能最全面的但它们是很多开发者最熟悉、最能快速上手的组合。这套方案最大的好处是掌控感。从数据字段的定义、到图表样式的设计、再到告警规则的逻辑你都能完全自定义。当业务方提出“能不能加一个XX指标的对比图”时你完全可以快速响应而不是等待外部系统的排期。当然它更适合作为核心业务监控的补充或者在中小型项目、内部工具场景下作为主力监控。如果服务规模变得非常大你可能需要考虑更专业的可观测性体系。但无论如何亲手构建一次会让你对“监控”这件事有更深刻的理解。下次当你再使用那些成熟的监控产品时你会更清楚它们背后大概是怎么运转的。如果你正准备为你的AI服务添加监控不妨就从这样一个简单的面板开始。把它跑起来看着图表随着服务请求而跳动那种对服务状态了如指掌的感觉会让你觉得这一切的投入都是值得的。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
为伏羲模型构建自动化监控面板:Web技术栈实战
为伏羲模型构建自动化监控面板Web技术栈实战最近在负责一个伏羲模型的生产服务最头疼的就是服务上线后的“黑盒”状态。模型跑得好不好GPU资源吃紧了吗用户请求有没有异常堆积以前全靠手动查日志既费时又容易遗漏关键问题。直到我们决定自己动手用最熟悉的Web技术栈给模型服务装上一个“仪表盘”。这个监控面板不是什么复杂的商业软件就是用HTML、CSS、JavaScript这些前端基本功加上一个轻量级的后端框架搭起来的。但它带来的价值是实实在在的服务健康状态一目了然关键指标实时刷新一旦有异常还能自动告警。今天我就把这个从零到一的搭建过程以及我们趟过的一些坑分享给你。如果你也在为AI服务的可观测性发愁希望这篇实战记录能给你带来一些直接的启发。1. 为什么需要一个专属的监控面板你可能觉得用现成的监控工具不就好了吗确实市面上有很多优秀的APM和监控平台。但在我们实际用伏羲模型的过程中发现了一些特殊需求是通用工具不太容易完美覆盖的。首先伏羲模型这类大模型服务有几个非常核心的指标需要特别关注。比如预测耗时Latency它不仅包含模型推理时间还可能包括数据预处理、后处理的时间波动一大用户体验就直线下降。再比如GPU利用率这直接关系到我们的算力成本花得值不值是模型有没有在“偷懒”的重要标志。还有API的调用量和成功率这反映了服务的可用性和业务流量。其次我们需要一个高度定制化的视图。开发、运维、甚至业务方他们关心的数据维度不一样。开发可能想看详细的错误堆栈和GPU内存波动运维更关注服务整体的SLA和资源水位业务方则只关心接口成功率。一个固定的仪表盘很难满足所有人。最后是成本和灵活性的权衡。引入一套完整的商业监控体系学习成本、接入成本和后续的定制化成本都不低。而我们用Web技术栈自己搭建前端可以随心所欲地设计图表和布局后端接口只采集我们真正关心的数据整个系统轻量、可控并且能与现有的运维流程比如告警通知到钉钉或企业微信无缝集成。所以我们决定自己动手目标很明确构建一个轻量、实时、专注伏羲模型服务的监控面板。2. 技术栈选型与整体设计在开始敲代码之前我们先花点时间把技术方案定下来。原则是用熟悉的、轻量的、能快速上手的。前端部分这是仪表盘的脸面核心是数据可视化。基础三件套HTML、CSS、JavaScript 是根本没得说。图表库我们选择了ECharts。原因很简单它足够强大能绘制折线图、柱状图、仪表盘、热力图等几乎所有我们需要的图表类型而且文档丰富社区活跃。它的配置项驱动方式用起来也很顺手。布局与样式为了快速搭建一个整洁的响应式界面我们引入了Bootstrap框架。它的栅格系统能让我们轻松排列各个监控卡片省去了大量调整CSS布局的麻烦。后端部分负责收集数据、提供API接口。框架我们用了Flask。Python写起来快Flask又足够轻量灵活非常适合这种小型、专用的服务。如果你更熟悉 Node.js 的 Express 或者 Go 的 Gin原理都是相通的。数据采集这是关键。我们需要从伏羲模型服务本身、以及服务器主机上抓取数据。模型服务指标如调用量、耗时通过在模型服务的API入口处埋点将日志写入一个中间件如Redis或直接由监控后端定时拉取。系统资源指标如GPU利用率、内存我们使用了Prometheus Client Library。在监控后端启动一个Prometheus的指标暴露端点再利用nvidia-smi的命令行工具或pynvml库来获取GPU信息转换成Prometheus格式的指标。数据存储与更新为了实时性我们没有用传统数据库。监控数据是典型的时序数据且需要快速读写。我们用了Redis来暂存最近一段时间比如24小时的聚合数据前端通过后端API来获取。这种方案简单高效适合监控场景。整个数据流是这样的伏羲模型服务在每次处理请求时会向Redis记录一条日志包含时间戳、耗时、状态码等。同时一个后台定时任务Cron Job会定期执行nvidia-smi将GPU数据也写入Redis。我们的Flask后端则提供几个简单的API前端定时调用这些API从Redis里取出最新数据交给ECharts渲染成图表。3. 一步步搭建监控后端Flask理论说完了我们来看代码。先从后端开始它是数据的枢纽。首先确保你的环境已经安装了必要的包flask,redis,prometheus-client。# app.py from flask import Flask, jsonify import redis import json from datetime import datetime, timedelta import threading import time import subprocess import psutil app Flask(__name__) # 连接Redis假设运行在本地 redis_client redis.Redis(hostlocalhost, port6379, decode_responsesTrue) # 定义存储数据的Key前缀 METRIC_KEY_PREFIX fuxi_monitor API_CALLS_KEY f{METRIC_KEY_PREFIX}:api_calls LATENCY_KEY f{METRIC_KEY_PREFIX}:latency GPU_UTIL_KEY f{METRIC_KEY_PREFIX}:gpu_util我们定义了几个Redis的Key来分别存储不同的指标。接下来编写一个后台线程函数用来定期采集GPU和系统信息。def collect_system_metrics(): 后台任务定期收集GPU和系统指标 while True: try: # 1. 收集GPU信息 (示例使用nvidia-smi) # 这里需要根据你的实际环境调整命令和解析逻辑 result subprocess.run([nvidia-smi, --query-gpuutilization.gpu,memory.used,memory.total, --formatcsv,noheader,nounits], capture_outputTrue, textTrue) if result.returncode 0: gpu_data result.stdout.strip().split(, ) if len(gpu_data) 3: gpu_util float(gpu_data[0]) # GPU利用率 % mem_used float(gpu_data[1]) # 已用显存 MB mem_total float(gpu_data[2]) # 总显存 MB mem_util (mem_used / mem_total) * 100 if mem_total 0 else 0 # 将当前时间点的数据存入一个有序集合ZSET用时间戳做分数 current_ts time.time() metric_data { gpu_util: gpu_util, mem_util: mem_util } # 存储最近1小时的数据并自动清理旧数据 redis_client.zadd(GPU_UTIL_KEY, {json.dumps(metric_data): current_ts}) # 移除1小时前的数据 one_hour_ago current_ts - 3600 redis_client.zremrangebyscore(GPU_UTIL_KEY, 0, one_hour_ago) # 2. 收集系统CPU/内存 (使用psutil) cpu_percent psutil.cpu_percent(intervalNone) mem psutil.virtual_memory() mem_percent mem.percent # ... 可以类似地存储系统指标 time.sleep(5) # 每5秒采集一次 except Exception as e: print(f采集指标时出错: {e}) time.sleep(30) # 启动后台采集线程 metric_thread threading.Thread(targetcollect_system_metrics, daemonTrue) metric_thread.start()然后我们需要在模型服务的API处理逻辑里埋点。假设你的伏羲模型服务有一个/predict接口。# 在你的模型服务代码中例如 model_server.py import time import redis import json redis_client redis.Redis(hostlocalhost, port6379, decode_responsesTrue) METRIC_KEY_PREFIX fuxi_monitor API_CALLS_KEY f{METRIC_KEY_PREFIX}:api_calls LATENCY_KEY f{METRIC_KEY_PREFIX}:latency def predict_endpoint(request_data): 模拟模型预测接口 start_time time.time() status success try: # 这里是你的模型推理逻辑 # result model.predict(request_data) result {prediction: sample_result} # 模拟处理时间 time.sleep(0.1) except Exception as e: status error result {error: str(e)} end_time time.time() latency_ms (end_time - start_time) * 1000 # 转换为毫秒 # 埋点记录本次调用 call_record { timestamp: end_time, latency: latency_ms, status: status } # 使用管道提高性能 pipe redis_client.pipeline() pipe.lpush(API_CALLS_KEY, json.dumps(call_record)) # 存储详细记录 pipe.ltrim(API_CALLS_KEY, 0, 10000) # 只保留最近10000条防止内存爆炸 pipe.execute() return result最后为前端提供数据查询的API。# app.py 继续 app.route(/api/metrics/summary) def get_metrics_summary(): 获取指标摘要用于仪表盘顶部的概览卡片 summary {} # 1. 计算最近1分钟的成功率、调用量、平均耗时 one_minute_ago time.time() - 60 recent_calls_raw redis_client.lrange(API_CALLS_KEY, 0, -1) # 获取全部实际应优化 recent_calls [] for item in recent_calls_raw: try: record json.loads(item) if record[timestamp] one_minute_ago: recent_calls.append(record) except: pass if recent_calls: total_calls len(recent_calls) success_calls len([r for r in recent_calls if r[status] success]) avg_latency sum([r[latency] for r in recent_calls]) / total_calls if total_calls 0 else 0 summary[qps] round(total_calls / 60, 2) # 估算QPS summary[success_rate] round((success_calls / total_calls) * 100, 2) if total_calls 0 else 100 summary[avg_latency_ms] round(avg_latency, 2) else: summary[qps] 0 summary[success_rate] 100 summary[avg_latency_ms] 0 # 2. 获取最新的GPU数据 latest_gpu_data redis_client.zrange(GPU_UTIL_KEY, -1, -1) # 获取最新的一条 if latest_gpu_data: gpu_info json.loads(latest_gpu_data[0]) summary[gpu_util] gpu_info.get(gpu_util, 0) summary[mem_util] gpu_info.get(mem_util, 0) else: summary[gpu_util] 0 summary[mem_util] 0 return jsonify(summary) app.route(/api/metrics/history/metric_name) def get_metric_history(metric_name): 获取某个指标的历史数据用于绘制趋势图 # 这里简化处理实际应根据metric_name从不同的Redis Key中获取数据 # 例如返回最近30分钟的GPU利用率数据点 thirty_min_ago time.time() - 1800 history_data [] if metric_name gpu_util: # 从有序集合中获取时间窗口内的数据 data_points redis_client.zrangebyscore(GPU_UTIL_KEY, thirty_min_ago, time.time()) for point in data_points: record json.loads(point) # 这里需要根据存储的数据结构调整 history_data.append(record.get(gpu_util, 0)) # ... 其他指标类似 # 补全时间戳这里简化返回数据点列表 return jsonify({ timestamps: [], # 实际应生成对应的时间戳列表 values: history_data[-100:] # 只返回最近100个点避免前端压力 })这样一个简单的监控后端就搭好了。它提供了两个核心接口一个返回当前状态的摘要一个返回历史趋势数据。4. 打造实时数据前端ECharts Bootstrap后端准备好了数据前端的工作就是把这些数据漂亮地展示出来。我们创建一个index.html文件。首先引入必要的库Bootstrap和ECharts。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title伏羲模型服务监控面板/title !-- Bootstrap 5 CSS -- link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/css/bootstrap.min.css relstylesheet !-- ECharts -- script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script style .metric-card { margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,.1); border-radius: 8px; padding: 15px; background: #fff; } .chart-container { height: 300px; width: 100%; } .status-healthy { color: #28a745; font-weight: bold; } .status-warning { color: #ffc107; font-weight: bold; } .status-danger { color: #dc3545; font-weight: bold; } /style /head body div classcontainer-fluid mt-3 h1 classmb-4伏羲模型服务监控面板/h1 !-- 第一行关键指标概览 -- div classrow idsummaryRow !-- 指标卡片将通过JS动态生成 -- /div !-- 第二行趋势图表 -- div classrow mt-4 div classcol-md-6 div classmetric-card h5API调用量 成功率趋势/h5 div idchartApiTrend classchart-container/div /div /div div classcol-md-6 div classmetric-card h5预测平均耗时趋势 (ms)/h5 div idchartLatencyTrend classchart-container/div /div /div /div !-- 第三行GPU/系统资源 -- div classrow mt-4 div classcol-md-6 div classmetric-card h5GPU利用率 (%)/h5 div idchartGpuUtil classchart-container/div /div /div div classcol-md-6 div classmetric-card h5显存使用率 (%)/h5 div idchartGpuMem classchart-container/div /div /div /div /div !-- Bootstrap JS Bundle -- script srchttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/js/bootstrap.bundle.min.js/script script srcdashboard.js/script !-- 我们的主逻辑JS -- /body /html接下来是核心的dashboard.js文件它负责定时拉取数据并更新图表。// dashboard.js document.addEventListener(DOMContentLoaded, function() { // 初始化ECharts实例 const apiTrendChart echarts.init(document.getElementById(chartApiTrend)); const latencyChart echarts.init(document.getElementById(chartLatencyTrend)); const gpuUtilChart echarts.init(document.getElementById(chartGpuUtil)); const gpuMemChart echarts.init(document.getElementById(chartGpuMem)); // 定义图表的基本配置 const commonChartOption { tooltip: { trigger: axis }, grid: { left: 3%, right: 4%, bottom: 3%, top: 10%, containLabel: true }, xAxis: { type: time, boundaryGap: false }, yAxis: { type: value }, series: [] }; // 初始化图表 apiTrendChart.setOption({ ...commonChartOption, legend: { data: [QPS, 成功率] }, yAxis: [{ type: value, name: QPS }, { type: value, name: %, max: 100 }] }); latencyChart.setOption({ ...commonChartOption }); gpuUtilChart.setOption({ ...commonChartOption }); gpuMemChart.setOption({ ...commonChartOption }); // 定义要监控的指标卡片 const metricCards [ { id: qps, title: 实时QPS, unit: , selector: #summaryRow, threshold: null }, { id: success_rate, title: 成功率, unit: %, selector: #summaryRow, threshold: { warn: 99, danger: 95 } }, { id: avg_latency_ms, title: 平均耗时, unit: ms, selector: #summaryRow, threshold: { warn: 500, danger: 1000 } }, { id: gpu_util, title: GPU利用率, unit: %, selector: #summaryRow, threshold: { warn: 80, danger: 95 } }, { id: mem_util, title: 显存使用率, unit: %, selector: #summaryRow, threshold: { warn: 85, danger: 95 } } ]; // 首次加载时创建卡片 renderMetricCards(metricCards); // 定时更新数据每3秒 setInterval(fetchAndUpdateData, 3000); fetchAndUpdateData(); // 立即执行一次 async function fetchAndUpdateData() { try { const response await fetch(/api/metrics/summary); const summary await response.json(); // 1. 更新指标卡片 updateMetricCards(summary); // 2. 更新图表这里简化实际应调用历史数据接口 // updateCharts(summary); // 3. 检查告警 checkAlerts(summary); } catch (error) { console.error(获取监控数据失败:, error); } } function renderMetricCards(cards) { const container document.querySelector(#summaryRow); container.innerHTML ; // 清空 cards.forEach(card { const col document.createElement(div); col.className col; col.innerHTML div classmetric-card text-center h6 classtext-muted${card.title}/h6 h2 idvalue-${card.id}--/h2 div${card.unit}/div small idstatus-${card.id} classstatus-healthy正常/small /div ; container.appendChild(col); }); } function updateMetricCards(data) { metricCards.forEach(card { const valueElement document.getElementById(value-${card.id}); const statusElement document.getElementById(status-${card.id}); const value data[card.id] ! undefined ? data[card.id] : --; if (valueElement) valueElement.textContent typeof value number ? value.toFixed(2) : value; // 根据阈值更新状态 if (card.threshold typeof value number) { let statusClass status-healthy; let statusText 正常; if (value card.threshold.danger) { statusClass status-danger; statusText 危险; } else if (value card.threshold.warn) { statusClass status-warning; statusText 警告; } statusElement.className statusClass; statusElement.textContent statusText; } }); } function checkAlerts(data) { // 这里可以实现告警逻辑比如当成功率低于95%时在控制台打印或发送通知 if (data.success_rate 95) { console.warn(告警服务成功率下降至 ${data.success_rate}%); // 在实际项目中这里可以调用后端接口发送钉钉/企业微信/邮件告警 } if (data.avg_latency_ms 1000) { console.warn(告警平均预测耗时过高 ${data.avg_latency_ms}ms); } } });现在启动你的Flask应用 (python app.py)然后在浏览器中打开index.html你可能需要一个简单的HTTP服务器比如python -m http.server就能看到一个实时刷新的监控面板了。顶部的卡片会显示当前关键指标和健康状态下面的图表区域预留了位置你可以根据/api/metrics/history/接口返回的数据用ECharts绘制出漂亮的历史趋势线。5. 从监控到告警让面板更智能一个只会展示数据的面板是被动的。真正的价值在于当指标异常时它能主动告诉我们。我们在前端JS里已经做了简单的控制台告警但这还不够。一个更健壮的告警系统应该在后端实现。我们可以在Flask后端增加一个定时检查的任务或者更优雅地使用Celery这样的异步任务队列。这里给出一个在Flask内实现简单定时检查的思路# 在app.py中增加 from apscheduler.schedulers.background import BackgroundScheduler scheduler BackgroundScheduler() def check_alert_rules(): 定时检查告警规则 try: summary get_current_summary() # 一个函数用于计算当前的指标摘要类似/api/metrics/summary的逻辑 # 规则1: 成功率低于阈值 if summary.get(success_rate, 100) 95: send_alert(f服务成功率告警: {summary[success_rate]}%, leveldanger) # 规则2: 平均耗时高于阈值 if summary.get(avg_latency_ms, 0) 1000: send_alert(f预测耗时告警: {summary[avg_latency_ms]}ms, levelwarning) # 规则3: GPU利用率持续过高 if summary.get(gpu_util, 0) 90: send_alert(fGPU利用率告警: {summary[gpu_util]}%, levelwarning) except Exception as e: print(f告警检查失败: {e}) def send_alert(message, levelwarning): 发送告警示例打印到日志实际可集成钉钉/微信/webhook print(f[ALERT-{level.upper()}] {message}) # TODO: 在这里调用你的告警通道API例如 # requests.post(你的钉钉机器人webhook, json{text: message}) # 或者发送邮件写入数据库等。 # 每30秒检查一次 scheduler.add_job(funccheck_alert_rules, triggerinterval, seconds30) scheduler.start()这样一个具备基本监控和告警能力的面板就完整了。你可以根据实际需求继续丰富它的功能比如增加更多图表错误类型分布、请求来源分布等。历史数据持久化将Redis中的数据定期归档到MySQL或时序数据库如InfluxDB中用于长期趋势分析和报表。多服务/多实例监控改造后端使其能监控多个伏羲模型服务实例并在前端进行聚合展示或切换。权限控制为面板增加简单的登录验证。6. 写在最后回过头看用Web技术栈搭建这么一个监控面板其实并没有想象中那么复杂。核心就是数据采集、存储、展示、告警这四个环节。我们用的技术Flask, Redis, ECharts可能不是性能最极致或功能最全面的但它们是很多开发者最熟悉、最能快速上手的组合。这套方案最大的好处是掌控感。从数据字段的定义、到图表样式的设计、再到告警规则的逻辑你都能完全自定义。当业务方提出“能不能加一个XX指标的对比图”时你完全可以快速响应而不是等待外部系统的排期。当然它更适合作为核心业务监控的补充或者在中小型项目、内部工具场景下作为主力监控。如果服务规模变得非常大你可能需要考虑更专业的可观测性体系。但无论如何亲手构建一次会让你对“监控”这件事有更深刻的理解。下次当你再使用那些成熟的监控产品时你会更清楚它们背后大概是怎么运转的。如果你正准备为你的AI服务添加监控不妨就从这样一个简单的面板开始。把它跑起来看着图表随着服务请求而跳动那种对服务状态了如指掌的感觉会让你觉得这一切的投入都是值得的。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。