# 性能预算与监控体系

从"救火队员"变成"防火队员"!建立主动监控,而不是被动排查

前置知识:第01~09章全部内容


阅读指南(初学者必看)

为什么你需要学习性能预算?

如果你是一名 H5 游戏/前端开发者,可能会问:"我会改Bug就够了,为什么还要学性能预算?"

答案是:上线前有标准,上线后有监控!从被动查问题变成主动防问题!

具体场景:

  • 每到周末游戏用户多了就卡,但平时没监控
  • 上线后才发现加载慢,但已经晚了
  • 面试问你有没有性能监控体系,能说出来吗?

学完本章,你能回答:

  • 性能预算怎么设定?
  • 三维监控体系怎么搭建?
  • 告警机制怎么设计?
  • Performance 面板怎么深度使用?

本文结构

第一部分:性能预算概念与设定
第二部分:Chrome DevTools Performance 面板深度使用
第三部分:性能监控体系(三维监控)
第四部分:告警机制与落地步骤
第五部分:真实用户监控(RUM)实战

一、什么是性能预算?

预算比喻

想象你是公司财务:

  • 财务预算 = 每个部门每月最多花多少钱
  • 性能预算 = 你的页面/游戏,性能指标最多多少

性能预算定义

性能预算是性能指标的上限,超过就视为不合格!

例子:
✅ JS体积 < 300KB
✅ Game加载 < 5s
✅ 首帧渲染 < 2s
✅ GC频率 < 1次/分钟

二、怎么设性能预算?

Step1:看行业标准

指标 需改进
FP(First Paint) <1.8s >3s
FCP(First Contentful Paint) <2s >4s
LCP(Largest Contentful Paint) <2.5s >4s
TTI(Time To Interactive) <3s >7s

Step2:看自己项目现状

步骤:
1.测当前性能
2.看哪项不达标
3.设一个可达到的目标

Step3:H5游戏常见性能预算

// 前端指标
const frontendBudget = {
  // 加载指标
  loadTime: '< 5s',
  firstFrame: '< 2s',
  
  // 内存指标
  heapMB: '< 200MB',
  gcPerMinute: '< 2',
  
  // 渲染指标
  fps: '> 55',
  jankFrame: '< 10',
  
  // 包大小
  jsSize: '< 300KB',
  imageSize: '< 500KB',
};

// 服务端指标
const serverBudget = {
  heapMB: '< 800MB',
  responseTime: '< 100ms',
  roomMemMB: '< 10',
};

三、Chrome DevTools Performance 面板深度使用

为什么需要 Performance 面板?

你已经理解了浏览器渲染管线的理论,但理论需要结合实践。Performance 面板是浏览器性能分析的瑞士军刀,能让你看到页面运行时每个毫秒发生了什么。

能解决的问题

问题 Performance 面板的答案
页面为什么卡顿? 看 Main 线程的火焰图,找到长任务
帧率为什么不稳定? 看 FPS 图表,找到掉帧的时刻
内存为什么增长? 看 Memory 曲线,找到泄漏点
JS 执行慢在哪里? 看火焰图的黄色条,找到耗时函数
渲染慢在哪里? 看紫色(样式)、蓝色(布局)、绿色(绘制)条

录制方法

  1. 打开 Chrome DevTools -> Performance 面板
  2. 点击左上角的录制按钮(圆形)
  3. 在页面上执行操作(如点击按钮、滚动页面)
  4. 点击停止按钮(方形)
  5. 等待分析完成

时间轴区域

录制完成后,看到的时间轴从上到下分为:

+-------------------------------+
| FPS(帧率)                    |  <- 绿色越高越好
+-------------------------------+
| CPU(CPU占用)                 |  <- 各种颜色的堆叠
+-------------------------------+
| NET(网络请求)                |  <- 每个请求的瀑布条
+-------------------------------+
| Main(主线程)                 |  <- 火焰图,最重要的区域
+-------------------------------+
| Raster(光栅化)               |  <- GPU光栅化活动
+-------------------------------+
| GPU(GPU活动)                 |  <- GPU合成和渲染
+-------------------------------+
| Memory(内存)                 |  <- JS堆、DOM节点、GPU内存
+-------------------------------+

火焰图(Flame Chart)解读

火焰图是 Performance 面板中Main 线程的可视化表示。每个条形代表一个函数调用,条形的长度代表执行时间。

Main线程火焰图示例:

|████████████████████████████████|  任务A (100ms)
|████████████|██████████████████|  子任务A1 (40ms) + 子任务A2 (60ms)
|███|███████|  |████████|██████|  更细的子任务...

颜色含义:
黄色 = JavaScript执行
紫色 = 样式计算(Style)
蓝色 = 布局(Layout)
绿色 = 绘制(Paint)
灰色 = 系统/其他

Step 1:找到长任务(Long Task)

长任务是指执行时间超过 50ms 的任务(会阻塞主线程,导致掉帧)。

Main线程:
|████████████████████████████████████████|  <- 红色角标 = 长任务(80ms)
|                                        |
|████████████████████|███████████████████|  <- 普通任务(30ms)

Step 2:展开长任务,找到耗时函数

点击长任务的条形,展开它的调用栈:

Long Task (80ms)
  |
  +-- updateGame() (78ms)
        |
        +-- updatePhysics() (40ms)  <- 最耗时的子函数!
        |     |
        |     +-- calculateCollision() (35ms)
        |
        +-- renderUI() (30ms)
              |
              +-- measureText() (25ms)  <- 强制同步布局!

Step 3:定位问题

上例中:

  1. calculateCollision() 占用 35ms -> 物理计算优化
  2. measureText() 占用 25ms -> 强制同步布局,应该缓存测量结果

帧率(FPS)分析

FPS:
|████████████████████                    |  <- 60fps(完美)
|████████████████████                    |
|█████████████████                       |  <- 45fps(轻微掉帧)
|█████████████                           |  <- 30fps(明显卡顿)
|████████                                |  <- 15fps(严重卡顿)

解读

  • 绿色条越高,帧率越高
  • 如果看到红色条,表示掉帧(帧率低于 60fps)

内存(Memory)分析

Memory:
JS Heap     /\    /\    /\
           /  \  /  \  /  \
          /    \/    \/    \
         /                     \____
        /                          
       
       时间 ->

       锯齿状 = GC在不断回收内存
       如果整体趋势向上 = 可能存在内存泄漏

三条曲线

  • JS Heap(黄色):V8 堆内存
  • DOM Nodes(蓝色):DOM 节点数量
  • GPU Memory(绿色):GPU 显存占用

网络(Network)瀑布图

Network:
请求1 |====SSL===|-----------TTFB----------|====下载====|
请求2           |====SSL===|---TTFB--|==下载==|
请求3                      |====SSL===|----TTFB----|==下载==|

颜色含义:
灰色 = 排队等待
橙色 = SSL握手
绿色 = 连接建立
蓝色 = 等待服务器响应(TTFB)
深灰 = 内容下载

关键指标

指标 含义 优化目标
TTFB 首字节时间 < 200ms
下载时间 内容传输时间 < 1s(关键资源)
总时间 请求开始到结束 < 2s

Performance 面板 vs Memory 面板

  • Performance 面板:关注时间维度(一段时间内发生了什么),适合分析卡顿、掉帧、长任务
  • Memory 面板:关注空间维度(内存中有什么),适合分析内存泄漏、对象分布

四、性能监控体系

三维监控图

┌─────────────────────────────────────────┐
│              监控体系三维度               │
├─────────────────────────────────────────┤
│ 1.开发期监控(CI阶段)                    │
│   -打包前就检查体积、性能                  │
├─────────────────────────────────────────┤
│ 2.真实用户监控(RUM)                     │
│   -真实用户手机上的性能数据                │
├─────────────────────────────────────────┤
│ 3.服务端监控                             │
│   -Node.js内存、响应时间                  │
└─────────────────────────────────────────┘

1. 开发期监控

在 CI 阶段就做检查:

// package.json
{
  "scripts": {
    "check-bundle": "webpack --profile --json > stats.json && webpack-bundle-analyzer stats.json",
    "build": "webpack"
  }
}

// CI配置(GitHub Actions)
// 每次PR自动检查,超过预算不让合并

2. 真实用户监控(RUM)

// 简单的性能埋点
const metrics = {
  loadTime: performance.now(),
  fps: 0,
  gcCount: 0,
};

// FP/FCP
const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.name === 'first-paint') {
      metrics.fp = entry.startTime;
    } else if (entry.name === 'first-contentful-paint') {
      metrics.fcp = entry.startTime;
    }
  }
  // 上报
  sendToServer(metrics);
});
observer.observe({ entryTypes: ['paint'] });

// FPS 监控
let frameCount = 0;
let lastTime = performance.now();

function checkFPS() {
  frameCount++;
  const now = performance.now();
  if (now - lastTime >= 1000) {
    metrics.fps = frameCount;
    frameCount = 0;
    lastTime = now;
  }
  requestAnimationFrame(checkFPS);
}
requestAnimationFrame(checkFPS);

3. 服务端监控

// Node.js监控
setInterval(() => {
  const usage = process.memoryUsage();
  const stats = {
    heapMB: Math.round(usage.heapUsed / 1024 / 1024),
    uptime: process.uptime(),
    timestamp: Date.now(),
  };
  
  // 上报到监控系统
  sendToServer(stats);
}, 10000);  // 每10s上报一次

五、告警机制

告警阈值

const alertThreshold = {
  heapMB: 800,  // 超过800MB告警
  gcPerMinute: 5,  // 1分钟GC超过5次告警
  responseTime: 500,  // 响应超过500ms告警
  fps: 30,  // FPS低于30告警
};

告警方式

// 简单告警实现
function checkAndAlert(stats) {
  if (stats.heapMB > alertThreshold.heapMB) {
    sendAlert('Heap too high: ' + stats.heapMB + 'MB');
  }
  
  if (stats.gcPerMinute > alertThreshold.gcPerMinute) {
    sendAlert('GC too frequent: ' + stats.gcPerMinute + '/min');
  }
  
  if (stats.fps && stats.fps < alertThreshold.fps) {
    sendAlert('FPS too low: ' + stats.fps);
  }
}

function sendAlert(msg) {
  // 可以接:钉钉机器人、飞书机器人、邮件、短信
  console.warn('[ALERT]', msg);
  // 实际项目发钉钉
  // fetch('https://oapi.dingtalk.com/robot/send...');
}

告警级别

级别 说明 响应
P0(紧急) 服务挂了、OOM 立即处理
P1(严重) 内存超标100% 24小时内处理
P2(警告) 内存超标50% 本周内优化

六、性能预算落地步骤

Step1:测现状
-当前性能怎么样?

Step2:设预算
-达标值是多少?
-阈值是多少?

Step3:加监控
-埋点上报
-可视化看板

Step4:加告警
-超过阈值通知

Step5:迭代优化
-不达标就优化
-达标就维持

七、游戏开发性能 Checklist

检查项 目标值
JS体积 < 300KB (gzipped)
游戏加载 < 5s (4G网络)
首帧渲染 < 2s
内存峰值 < 200MB
FPS > 55fps
热更新下载 < 1s
GC频率 < 1次/分钟
服务端堆 < 800MB

自问自答

Q:性能预算设多少合适? A:看你的项目类型,小游戏可以要求高一点,3D大作可以放宽一点。建议先测现状,再设一个比现状好 20% 的目标。

Q:怎么说服产品经理留时间做性能优化? A:用数据说话!给 TA 看:加载慢导致多少用户流失、卡顿导致多少评论差评。性能是用户体验的一部分,直接影响留存和收入。

Q:监控数据上报会影响性能吗? A:会!所以要采样上报,比如 1/100 用户上报,或者 10s 上报一次,不要每帧上报!

Q:Performance 面板录制时页面更卡,正常吗? A:正常。Performance 面板本身有性能开销,录制时会额外消耗 CPU 和内存。建议在需要分析时再录制,录制时间不要太长。


实践任务

  • 任务1:为你的项目设定性能预算(加载时间、内存、FPS、包大小)
  • 任务2:用 Performance 面板录制游戏操作,分析长任务和掉帧原因
  • 任务3:实现 FPS 和内存的实时监控看板
  • 任务4:搭建告警系统(超过阈值发送通知)
  • 任务5:在 CI 中集成包体积检查(超过预算不允许合并)

与其他章节的关联

本章内容 关联章节 关联点
Performance 面板 第01~03章 渲染管线的实践验证工具
内存监控 第05~07章 全链路内存和泄漏排查的落地
RUM 监控 第09章 游戏内存优化的线上验证
服务端监控 第08章 Node.js 内存的线上监控

上一章:09-H5游戏内存优化实战


恭喜你完成了本项目的学习!

你已经掌握了:

  1. 浏览器渲染管线的 8 大阶段
  2. 合成层的创建条件和层爆炸的避免
  3. 重排与重绘的区别和优化策略
  4. 事件循环与渲染时序的配合
  5. 浏览器全链路内存的五大区域
  6. JS 堆与 GPU 显存的桥梁
  7. 内存泄漏排查 SOP
  8. Node.js 服务端内存管理
  9. H5 游戏内存优化实战
  10. 性能预算与监控体系

下一步

  • 在你的实际项目中应用这些知识
  • 继续学习 P1 优先级的内容(网络同步、Java 后端)