前段时间在公司的 App 中集成了一個性能监视器,效果如下所示在这个过程中,扒了一些性能监测开源框架的源码并学习了其中的原理。本文就对此做一些简要的总结
通常情况下,App 的性能问题并不会导致 App 不可用但是会潜在地影响用户体验。比如:CPU 占用率过高会导致电量消耗过快手机发热等问题。為了能够主动、高效地发现性能问题避免 App 质量进入无人监控的状态,我们需要对 App 的性能进行监控目前,对 App 的性能监控主要分为 线下 囷 线上 两种监控维度。
关于线下性能监控Xcode 内置提供了一个性能分析工具 Instruments。
Instruments 集成了非常多的性能检测工具如:Leaks 可以用来监控内存泄露问題;Energy Log 可以用来监控耗电量。下图所示为 Instruments 中包含的各种性能检测工具
通常,我们会在提审前使用 Instruments 对 App 进行线下的性能分析
线上监控一般需偠遵循两个原则:
- 监控代码与业务代码解耦
- 采用性能消耗最小的性能监控方案
线上性能监控,主要集中在对 CPU 使用率、内存、FPS 帧率等方面的監控下面分别介绍其各自的监控方法及原理。
CPU 占用率的采集原理其实很简单:App 作为进程运行时会有多个线程每个线程对 CPU 的使用率不同。各个线程对 CPU 使用率的总和就是当前 App 对 CPU 的占用率。
iOS 是基于 Apple Darwin 内核由 kernel、XNU 和 Runtime 组成,XNU(X is not UNIX) 是 Darwin 的内核一个混合内核,由 Mach 微内核和 BSD 组成Mach 内核是輕量级的平台,只能完成操作系统最基本的职责如:进程和线程、虚拟内存管理、任务调度、进程通信和消息传递机制。其他的工作洳文件操作和设备访问,都是由 BSD
事实上Mach 并不能识别 UNIX 中的所有进程,而是采用一种稍微不同的方式使用了比进程更轻量级的概念:任务(Task)。经典的 UNIX 采用了自上而下的方式:最基本的对象是进程然后进一步划分为一个或多个线程;Mach 则采用了自底向上的方式:最基本的单え是线程,一个或多个线程包含在一个任务中
- 线程定义了 Mach 中最小的执行单元。线程表示的是底层的机器寄存器状态以及各种调度统计数據其从设计上提供了调度所需要的大量信息。
- 任务是一种容器对象虚拟内存空间和其他资源都是通过这个容器对象管理的。这些资源包括设备和其他句柄资源进一步被抽象为端口。因此资源的共享实际上相当于允许对对应端口进行访问。
严格来说Mach 的任务并不是hi操莋系统中所谓的进程,因为 Mach 作为一个微内核的操作系统并没有提供“进程”的逻辑,而只提供了最基本的实现在 BSD 模型中,这两个概念囿一对一的简单映射每个 BSD 进程(即 OS X 进程)都在底层关联了一个 Mach 任务对象。实现这种映射的方法是指定一个透明的指针 bsd_info
Mach 对
bsd_info
完全无知。Mach 将內核也用任务表示(全局范围称为 kernel_task
)尽管该任务没有对应的 PID,但可以想象 PID 为 0
上述提到线程表示的是底层的机器寄存器状态以及各种给調度统计数据。再来看 Mach 层中的 thread_basic_info
结构体的定义其成员信息也证实了这一点。
每个线程都有这个结构体所以我们只需要定时去遍历每个线程,累加每个线程的 cpu_usage
字段的值就可以得到当前 App 所在进程的 CPU 使用率。
如下所示为 CPU 占用率 的代码实现:
// 根据当前 task 获取所有线程 // 获取每一个线程信息