万里鹏—字节跳动线上性能监控体系的建设

前端狗

2019/07/09 发布于 编程 分类

GMTC2019 

文字内容
1. 字节跳动线上性能监控体系的建设 万里鹏 性能监控体系负责人
2. 自我介绍
3. 自我介绍
4. 自我介绍 万里鹏 字节跳动Android基础技术监控体系 性能优化与监控体系,包括稳定性、异常、性能,事件等上报和监控
5. 目录 • 研发流程和现状 • 性能监控体系的建立 • 单点问题追查
6. 研发流程和现状
7. 研发现状和痛点 庞大的亿级用户群体 Android机型碎片化、性能差异较大 直接影响产品体验、核心指标 用户反馈描述不容易定位问题 本地测试,线上偶现异常
8. 研发流程中的问题 在研发流程的不同阶段中,用户量级的不同,导致不同概率的问题遗漏到下一阶段 研发阶段 更更多关注功能 QA测试 覆盖机型有限 内测版本 版本灰度 正式发布 ⽤用户量量级较⼩小 量量级和正式版有差距 最终全量量级发布 研发流程中,每个阶段均能发现不不同的问题,但是最终漏漏掉的问题,在线上造成不不可预知,情况复杂多变, 所以线上监控变得⾮非常重要
9. 问题发现 • 量量级少,暴暴露露 • 不不容易易复现 问题不不明显 内测 • 易易复现 ⽤用户反馈 • 难复现 • 量量级⼩小 • 量量级⼤大 RD/QA • 情况理理想 • 场景覆盖不不⾜足 线上监控 • 真实情况 • 需要健全的监 控体系
10. 线上监控流程 发版/热修复 定位问题,需要监控平台满⾜足 监控数据采集 • 覆盖更更全⾯面的性能类型 • 性能问题的归因能⼒力力 • 不不同性能监控的采集技术 定位问题 监控数据上报 • 监控功耗 • 多维度过滤性能问题 • 低内存占⽤用 • 交互更更加易易⽤用的监控平台 • 低CPU占⽤用 报警/主动发现 数据聚合分析
11. 性能监控体系的建⽴立
12. 监控体系
13. 性能监控体系 内存监控 卡顿指标 缓存监控 电量监控 异常流量
14. 内存监控 监控系统发生OOM、内存触顶时的内存分配
15. 线上内存的痛点 01 内存溢出 线上发⽣生OOM之后,堆栈并不不⼀一定是 真正造成内存溢出的原因 内存 问题 内存泄露露 OOM率逐渐增多,内存泄露露也是导致 恶化的原因之⼀一 03 02 内存上涨 观察⼤大盘数据,某个版本开始 突然内存逐渐上涨。很难快速 定位到具体原因
16. 内存监控功能点 内存溢出 OOM发⽣生时分析内存占⽤用 的详细⽐比例例,分析原因 内存触顶 内存使⽤用⽐比例例达到阈值, 触发内存占⽤用分析 OOM ⼤大对象 Object Warn 分析内存中⼤大对象的占⽐比 内存泄露露 Leak Activity和Fragment的内 存泄露露检测
17. 内存分析流程 触发 上报 dump内存镜像 • 镜像⽂文件较⼤大 • 与当时内存⼤大⼩小 有直接关系 • dump耗时较多 剪裁Hprof⽂文件 • 针对⽂文件格式,剪 裁掉⽆无⽤用部分 分析镜像 • 独⽴立进程分析 • 优化分析内存占⽤用 • 间接优化分析速度 • 内存泄露露 • ⽐比Hook⽅方式更更安全 • ⼤大内存对象
18. 卡顿指标的建⽴立 基于FPS,建立卡顿指标,配合慢函数解决卡顿问题
19. FPS的感知度弊端 场景⼀一:平均滑动帧率57,期间出现2次丢帧 20帧(320ms) 10帧(160ms) 快速滑动过程中突然停顿320ms,⼜又继续滑动, 再次卡顿160ms 场景⼆二:平均滑动帧率56,期间出现多次丢帧 4帧(64ms) 3帧(48ms) 5帧(80ms) 2帧(32ms) 2帧(32ms) 3帧(48ms) 3帧(48ms) 2帧(32ms) 2帧(32ms) 3帧(48ms) 2帧(32ms) 1帧(16ms) 多次丢帧,累计32帧,但是每次时间较短,卡顿感受不不 明显 卡顿明显 平稳滑动 57帧并不不⼀一定⽐比56帧体验好
20. 卡顿指标和慢函数 慢函数 丢帧 FPS •展示大盘趋势 •数据波动较小 •不容易发现卡顿问题 •非真实用户体验 •卡顿衡量指标 •丢帧分布和趋势 •真实的用户体验 •多维度展示 •定位到函数级 •聚合函数调用链 •解决具体卡顿问题
21. 丢帧分布和趋势 丢帧分布 丢帧趋势
22. 流畅度分布 针对4.x系统上的流畅度分布
23. 流畅度、丢帧统计 针对4.x系统上的数据统计
24. 慢函数 监控函数级执行速度,将执行较慢的函数调用链聚合,定位到具体原因,解决导致卡顿的问题
25. 缓存监控 监控缓存中存在的大文件、大文件夹和过期文件
26. 缓存空间恶化原因 • 开发人员多 • 多团队协同 • 沟通同步成本大 人员 代码 • 历史代码 • 缓存失效bug • 兼容问题 • 需求快速迭代 • 功能下线未处理缓存 • 业务复杂,收敛困难 功能 测试 • 不容易复现 • 非真实用户场景 • 导致空间占用过大 缓存
27. 线上缓存监控 针对App私有⽬目录中缓存空间过⼤大的监控 ⼤大⽂文件监控 • 单⽂文件⼤大⼩小超过阈值 ⼤大⽂文件夹监控 • ⽂文件夹占⽤用过多 • 包含⽂文件数量量过多 过期⽂文件监控 • 过期⽂文件 ⻓长时间没执⾏行行过更更新 ⼤大⽂文件夹 ⼤大⽂文件
28. ⼤大⽂文件缓存监控 App缓存文件大小超过阈值,上报聚合统计
29. ⼤大⽂文件夹缓存监控 App缓存文件夹大小或者包含文件数量超过阈值,上报聚合统计
30. 电量量监控 监控多进程,前后台,不同方式的耗电情况
31. 应⽤用耗电 计算耗电⽅方式 ⼿手机系统估算 Screen CPU Bluetooth • ⾮非真实数据 • 相对指标可参考 电流计 电量 GPS WiFi • 硬件设备,真实电量量 • 系统和应⽤用混合 • 环境不不好搭建 Battery Historian Radio Video/ Audio • 线下测试
32. ⼿手机系统耗电估算 Screen DisplayManagerService DisplayController 采⽤用估算⽅方式计算耗电量量 power_profile.xml Wakelock • 按照不不同类型分别估算 PowerManagerService • 系数参照power_profile.xml Notifier WiFi … 的声明 BatteryStatsImpl 耗电量 • 不不同机型⼚厂商参数不不同 • 应⽤用获取不不到电量量细节数据 … … 总耗电 = CPU毫秒 * 系数1 + 流量量Bytes * 系数2 + ...
33. power_profile.xml -- 耗电估算参数 0 100 142 0.3 35690 160 4 120 220 88 88 300 170 1390 70 3 3 …… • 编译在/system/framework/frameworkres.apk中 • 分别声明了了不不同类型设备发⽣生不不同动作时的耗电 量量系数 • 包括Bluetooth,WiFi,Radio,CPU等
34. BatteryStatsHelper.java中关键⽅方法 private void processAppUsage(SparseArray asUsers) { final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null); mStatsPeriod = mTypeBatteryRealtime; BatterySipper osSipper = null; final SparseArray uidStats = mStats.getUidStats(); final int NU = uidStats.size(); for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0); mCpuPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mWakelockPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mWifiPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mSensorPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mCameraPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType); final double totalPower = app.sumPower(); if (DEBUG && totalPower != 0) { Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(), makemAh(totalPower))); } } } ... // code 按照不同类型分别估算
35. 耗电模块 关注的主要耗电模块,然后根 Location 据自定义的全局指数估算 Alarm Wakelock • Location:计时 • Alarm:计次 耗电模块 • Net:计算值 Net Sensor • Wakelock:计时 • CPU:计算值 CPU
36. 耗电监控架构 进程1 采 集 进程2 前台 CPU 后台 流量量 存 储 Wakelock Location Alarm 后台 前台 … … … 多进程收集信息传递给 ContentProvider(主进程收集存储) 主进程,⽽而后继续具体 计 算 上 报 前台总时⻓长 前台总耗电 前台模块耗电 前台模块单位时间耗电 前台单位时⻓长总耗电 上报接⼝口 … … 耗电评分,⽽而后上报
37. 监控系统展示
38. 异常流量量监控 监控不同的业务消耗流量的情况
39. 异常流量量监控
40. 监控流量量范围 HTTP 系统中所有的HTTP请求 WebView 详情⻚页等使⽤用WebView场景 Socket ⻓长链接等场景 视频 观看⼩小视频,⻄西⽠瓜视频流量量 直播 游戏直播等
41. 流量量监控实现 线上监控上报和服务端日志组合 Phone Server 结合上报异常流量量数据,业务后端下游数据,在监控平台聚合,展示
42. 监控平台展示 异常流量中,播放视频流量的消耗
43. 单点问题追查
44. ⽤用户反馈单点问题 配合度低 环境复杂 • 描述不不清楚 交流不不便便 • 反复沟通成本⾼高 • 不不容易易复现 个例例偶现 单点问题 描述差异 • 机型、环境差异
45. ⽇日志库 为了了定位线上的问题,在⽤用户反馈时,经⽤用户授权⽇日志回 崩溃日志 传,然后上传。⽽而⽇日志存储是⼀一个频繁且数据量量⼤大的操作, 且⽂文件IO和db⼜又不不能满⾜足性能的需求,基于这样的背景,⾃自 研了了⼀一套基于mmap的⽇日志库ALog,主要特性: 调试日志 网络日志 业务日志 ⽇日志分类 • 格式化输出 • ⾼高速写⼊入 • ⽇日志压缩 • ⽇日志加密 • 前端可视化
46. ⽇日志库架构 App 接入层 业务 组件 崩溃 … ALog中间件 Java层 格式化 名单过滤 ALog库分Java层和Native层两部分, 基本压缩、加密下沉到Native层,便便 于跨平台使⽤用。 ⾃自动⾏行行号 App中多个业务、组件都接⼊入ALog, 抽象出来中间件的概念,其它组件和 compress ALog Native层 业务接⼊入中间件,调⽤用接⼝口写⼊入⽇日志。 encrypt 具体实现类的注⼊入,在宿主App中实 Sharding Gzip AES AutoClean mmap … 现。
47. ⽇日志流 用户反馈时根据用户授权,回传日志 解决问题 • 调试日志重定向到日志流 排查问题 ⽤用户反馈 • 形成线上和线下一体化 • 用户反馈,研发解决问题联动 • 监控平台权限收敛 单点查询 ⽇日志回传
48. ⽇日志流 • ⽇日志⽅方便便输出 • 不不必关⼼心展现存储细节 • 上报与展示完整闭环 ALog上报 • ⽤用户反馈时授权上报 聚合数据 业务,组件写⼊入⽇日志,反 馈时授权上传 存储服务将数据存储、聚 合 服务端接收 服务接收后⾃自动解 压,解密 监控平台 前端展示,⽤用户维度查询
49. 监控系统查看ALog
50. 未来畅想 更更健全的功能 覆盖更更加健全的性 能专项,稳定性、 不不合理理代码的线上 监控。 更更准确的定位问题 更更深层次的监控⼒力力 度,能够直接定位 到具体问题的原因 线上问题主动发现 多⽅方⾯面,多维度,多技 术的监控、更更强⼤大的报 警和归因能⼒力力,主动发 现线上问题