Android发热监控实践( 三 )


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

Android发热监控实践

文章插图
图片
线程堆栈由于发热问题是一个综合性的问题,并不像 Crash 问题一样,在发生现场我们就可以知道是哪个线程触发的 。如果将所有线程的堆栈都进行 Dump 记录的话,得物当前运行时的子线程数量在 200+ , 全部进行存储的话无疑是不合理的 。问题就转变为 如何较为准确的找到发热代码的线程堆栈?上文说到 在计算 CPU 使用率的时读取进程下所有线程的 Stat 文件,我们可以获取到子线程的 CPU 使用率 , 对其使用率进行倒排,筛选超过阈值(当前定义 50% ) 或 占用 Top N 的线程进行存储 。由于堆栈频繁采集时机上是有性能折损的,故牺牲了部分的堆栈采样精度和准确性 , 在温度、CPU 使用率等指标超过阈值定义后,才开始采集 指定下发时间的堆栈信息 。
我们还要明确一个概念,线程 Stat 文件的文件名即为线程标识名,Thread.id 是指线程ID 。
其两者并不等价,但 Native 方法中给我们提供了对应的方式去建立两者的映射关系 。
在 Art  Thread.cc 方法中,将 JAVA 中的 Thread 对象转换成 C++ 中的 Thread 对象,调用 ShortDump 打印线程的相关信息,我们通过字符串匹配到核心的 Tid= 的信息,即可获取到线程的 Tid 。
核心代码逻辑如下:
//获取队列中最近一次cpu采样的数据 val threadCpuUsageData = https://www.isolves.com/it/cxkf/ydd/Android/2023-10-24/cpuProfileStoreQueue.last().threadUsageDataListval hotStacks = mutableListOf()if (threadCpuUsageData != null) {val dataCount = if (threadCpuUsageData.size <= TOP_THREAD_COUNT) {threadCpuUsageData.size} else {TOP_THREAD_COUNT}val traces: MutableMap> = Thread.getAllStackTraces()//定义tid 和 thread的映射关系mapval tidMap: MutableMap = mutableMapOf()traces.keys.forEach { thread ->//调用native方法获取到tid信息val tidInfo = hotMonitorListener?.findTidInfoByThread(thread)tidInfo?.let {findTidByTidInfo(tidInfo).let { tid ->if (tid.isNotEmpty()) {tidMap[tid] = thread}}}}//采集topN的发热堆栈for (index in 1..dataCount) {val singleThreadData = threadCpuUsageData[index - 1]val isMainThread = singleThreadData.pid == singleThreadData.tidval thread = tidMap[singleThreadData.tid.toString()]thread?.let { findThread ->traces[findThread]?.let { findStackTrace ->//获取当前的线程堆栈val sb = StringBuilder()for (element in findStackTrace) {sb.append(element.toString()).append("n")}sb.append("n")if (findStackTrace.isNotEmpty()) {//是否为主线程//组装hotStackval hotStack = HotStack(//进程idsingleThreadData.pid,singleThreadData.tid,singleThreadData.name,singleThreadData.cpuUseRate,sb.toString(),thread.stateisMainThread)//Log.d("HotMonitor", sb.toString())hotStacks.add(hotStack)}}}}}四、监控方案了解核心指标数据是如何获取的前提下,其实监控方案的核心思路无非就是通过远端 APM 配置中心下发的采样阈值、采样周期、各模块数据开关等限定采样配置,子线程 Handler 定时发消息,采集各个模块的数据进行组装 , 在合适的时机进行数据上报即可,具体的数据拆解、分析工作则由发热平台进一步处理 。
模块整体架构
Android发热监控实践

文章插图
图片
上报时机
Android发热监控实践

文章插图
图片
核心采集流程
Android发热监控实践

文章插图
图片
线上线下区分由于所有子线程的 CPU 采集、堆栈采集实际上是会对性能有折损的,200+ 的线程的读取耗时整体在 200ms 左右,采样子线程的 CPU 使用率在 10%,考虑到线上用户体验问题,并不能全量开启高频率采样 。
Android发热监控实践

文章插图
图片
Android发热监控实践

文章插图
图片
故整体方案来说: 线下场景以重点侧重发现、排查、治理全量问题 , 上报全量日志,以 CPU、GPU 使用率为第一衡量指标;
线上场景以重点侧重观察整体发热大盘趋势、分析潜在问题场景,上报核心日志,以电池温度为第一衡量指标 。
发热平台在平台侧同学的支持下,发热现场数据经过平台侧进行消费 , 将核心的发热堆栈经过 Android 堆栈反混淆服务进行聚合,补齐充电状态、主线程 CPU 使用率、问题类型、电池温度等基础字段,平台侧就具备发现、分析、解决的流程化监控推进的能力 。


推荐阅读