Android发热监控实践

时间: 2024-02-01 00:06:04   来源: 佛像

  相信移动端高度普及的现在,大家或多或少都会存在电量焦虑,拥有过手机发热发烫的糟糕体验。而发热问题是一个长时间、多场景的指标存在,且涉及到端侧应用层、手机 ROM 厂商系统、外界环境等多方面的影响。如何有效衡量发热场景、定位发热现场、以及归因发热问题成为了端侧应用层发热监控的面前的三座大山。本文通过得物 Android 端侧现有的一些监控实践,不深入功耗计算场景不能自己,优先聚焦于发热场景本身,希望能给大家一些参考。

  温度是最直观能反映发热问题的指标,当前 Android 侧,我们以体感温度 37° 以上作为分界线° 作为一个发热温度区间,区间细分上限温度 49° ,即划分出 37-40,40-43,43-46,46-49,49+ 五个等级。

  以手机温度、CPU 使用率作为第一、第二要素来判断用户是否发热的同时,获取其他参数来支撑发热现场情况。

  系统 BatteryManger 已经提供了一系列自带的接口和粘性广播获取电池信息。

  BatteryManager 除支持电池温度的系统广播外,也包含电量、充电状态等额外信息的读取,均定义在其源码中。

  以一加 9 为例,共存在 105 个温度传感器 or 温度分区,以及 48 个冷却设备。

  每个温度分区下记录下具体的参数类型,我们着重关注的是 type 文件和 temp 文件,分别记录了该传感器设备的名称,以及当前的传感器温度。以 thermal_zone29 为例,代表了 CPU 第一核心的 第五处理单元的温度值为 33.2 摄氏度。而对单一设备来说分区对应的名称是固定的,从而我们大家可以通过读取 thermal_zone 文件的方式来记录当前第一个 type 文件名称包含 CPU 的传感器作为 CPU 温度。

  Android 10 Google 官方推出了 热缓解框架 ,通过 HAL2.0 框架监听底层硬件传感器(主要为 USB 传感器、Skin 传感器)提供 USB、壳温的热信号等级变更监听, 系统 PowerManager 源码提供了对应发热等级变更的回调和发热等级的获取,共 7 个等级,提供给开发者主动或被动获取。

  但对于发热等级来说,壳温无疑是最为能够反应手机的发热情况的。能够正常的看到 Android 系统的 API 其实就是提供了 AIDL 接口,可以直接注册 Thermal 变更事件的监听,获取到 Temperature 对象。但由于标识了 Hide API 。常规应用层是无法获取到的,在考虑好 Android 版本兼容性前提下,通过反射代理 ThermalManagerService 方式来进行读取。

  但事与愿违,国内厂商并没有完全适配官方热缓解框架,热状态回调时常不够准确,而是需要单独接入每个厂商的热缓解 SDK 去直接获取到壳温,具体 API 则以各应用厂商的内部接入文档为准。

  CPU 使用率的采集通过读取解析 Proc stat 文件的方式来进行计算。

  我们着重关注 14.15 位的信息,分别代表进程/线程的用户态运行的时间和内核态运行的时间。

  联发科芯片的设备,我们大家可以直接通过读取/d/ged/hal/gpu_utilization下的使用率数值。

  同样的通过指定周期(每秒 1 次)的采样间隔,即可获取到每秒的当前 GPU 使用率。

  与市面上常规的监控手段差异不大,都是通过系统 Hook ServiceManager 的方式,监听系统服务的 Binder 通信,匹配对应的调用方法名,做对应中间层监控的回调记录处理。

  同理 我们获取在固定采样周期内 各系统服务对应 申请次数、计算间隔时长等进行记录。

  源码 Power_profile 文件中定义了每个系统服务状态下的电流量定义。

  我们在需要记录每个元器件在不同状态的上班时间之后,通过以下计算方式,能得出元器件的发热贡献排行,即:

  元器件 电量消耗(发热贡献) ~~ 电流量 * 运行时长 * 电压(一般为固定值,可忽略)

  由于发热问题是一个综合性的问题,并不像 Crash 问题一样,在发生现场我们就不难得知是哪个线程触发的。如果将所有线程的堆栈都进行 Dump 记录的话,得物当前运行时的子线+,全部进行存储的话无疑是不合理的。问题就转变为 如何较为准确的找到发热代码的线程堆栈?

  上文说到 在计算 CPU 使用率的时读取进程下所有线程的 Stat 文件,我们大家可以获取到子线程的 CPU 使用率,对其使用率进行倒排,筛选超过阈值(当前定义 50% ) 或 占用 Top N 的线程进行存储。由于堆栈频繁采集时机上是有性能折损的,故牺牲了部分的堆栈采样精度和准确性,在温度、CPU 使用率等指标超过阈值定义后,才开始采集 指定下发时间的堆栈信息。

  我们还要明确一个概念,线程 Stat 文件的文件名即为线程标识名,Thread.id 是指线程ID。

  其两者并不等价,但 Native 方法中给咱们提供了对应的方式去建立两者的映射关系。

  在方法中,将 Java 中的 Thread 对象转换成 C++ 中的 Thread 对象,调用 ShortDump 打印线程的相关信息,我们通过字符串匹配到核心的 Tid= 的信息,即可获取到线程的 Tid。

  了解核心指标数据是如何获取的前提下,其实监控方案的核心思路无非是通过远端 APM 配置中心下发的采样阈值、采样周期、各模块数据开关等限定采样配置,子线程 Handler 定时发消息,采集各个模块的数据来进行组装,在合适的时机进行数据上报即可,具体的数据拆解、分析工作则由发热平台进一步处理。

  由于所有子线程的 CPU 采集、堆栈采集其实就是会对性能有折损的,200+ 的线ms 左右,采样子线程的 CPU 使用率在 10%,考虑到线上用户体验问题,并不能全量开启高频率采样。

  故整体方案来说: 线下场景以重点侧重发现、排查、治理全量问题,上报全量日志,以 CPU、GPU 使用率为第一衡量指标;

  线上场景以重点侧重观察整体发热大盘趋势、分析潜在问题场景,上报核心日志,以电池温度为第一衡量指标。

  在平台侧同学的支持下,发热现场数据经过平台侧进行消费,将核心的发热堆栈经过 Android 堆栈反混淆服务进行聚合,补齐充电状态、主线程 CPU 使用率、问题类型、电池温度等基础字段,平台侧就具备发现、分析、解决的流程化监控推进的能力。

  由于电池温度、CPU 使用率是针对运行时发热场景最直观的指标,且我们一期着重关注发热场景的治理,不针对元器件 Hook 等耗电场景进行持续深入分析,故当前得物侧是以电池温度、CPU 使用率为第一第二指标 建立核心的发热问题四象限,优先关注高温、高 CPU 的问题场景。

  在数据分析过程中,我们遇到了数据上的效率排查效率不够高、问题精度不够准的情况。

  如何定位是高温场景是发生在 App 内部,且在使用的过程中明显上升的? 通过过滤从启动开始即高温、后台切换回来即高温的场景,着重关注在 App 内部温度上升的场景。

  线上的采样后仍旧单日有 6w+ 数据的上报,我们如何筛选出更为核心的数据?当前的做法是定义了温度跨度的概念,优先看在 App 内部温度跨度较大的 Case。

  线程存在调用 Wait 等方法阻塞的堆栈,消耗内核态的时间分配,但实际不消耗整体 CPU 的误报数据。 补充了线程的运作时的状态和 Proc 文件中记录的 State,方便优先处理 RUNNABLE线程的 CPU 高温高占用问题。

  手机温度上升作为渐进式的场景,怎么来实现温度上升场景下的页面精确归因?增加温度采样频率的同时,汇总 CPU 使用率和实时堆栈等瞬时数据作为数据支撑,但考虑到数据体量的情况,数据上报聚合裁剪方式仍在逐步探索更为合理的方式,力求在两者之间找到一个平衡点。

  Android 端侧发热监控自上线以来,背靠平台侧的支撑,陆续发现了一些问题并联合开发同学做了对应场景的治理优化工作,如:

  这无疑给未来体验工作的场景技术选型、技术实现沉淀了一些有价值的经验,符合对 App 体验追求极致的高标准、高要求。

  手机发热作为渐进式的体验场景,涉及手机硬件、系统服务、软件使用、外界环境多方位因素。对于端侧的排查上来说,当前优先级聚焦于应用层的不合理使用上,对于排查工具链路增强、问题业务归因、低电量、低功耗模式下的动态策略降低、自动化诊断报告等环节仍旧有很多值得深入挖掘的点,例如:

  在此也只是粗略介绍当前已经做的针对发热治理的一些初步工作,以及对未来发热功耗相关开展的思路,希望能让 App 带来更好的体验,给用户所带来更对美好事物的向往的感受。

上一篇:配置Ubuntu系统环境和安装的开发工具
下一篇:美国一设施突发丙烷罐爆炸!如何防控丙烷安全风险
微信/电话同号 135-8216-1651 返回顶部
导航 电话咨询