基于iOS平台的性能检测方案

简介: 导语 在开发过程中,功能不仅要满足业务需求,也要关注功能对App性能带来的一些问题。开发人员在开发阶段检测性能比较容易,iOS端可以直接通过instruments工具进行检测。但是在测试阶段,测试人员要检测性能需要下载开发工具成本比较高。

导语

在开发过程中,功能不仅要满足业务需求,也要关注功能对App性能带来的一些问题。开发人员在开发阶段检测性能比较容易,iOS端可以直接通过instruments工具进行检测。但是在测试阶段,测试人员要检测性能需要下载开发工具成本比较高。如果客户端能够将性能数据上传到服务端并且通过一些界面进行展示,对测试人员来说是一种可以检测性能的比较好的方法。

本文章主要介绍iOS端如何通过代码采集性能数据,其中包括电池数据,CPU数据,内存数据,卡顿数据,流量数据以及冷启动时间等。

电池数据

首先来看一下电池数据,iOS电池数据采集方案主要有以下三种方案:UIDevice,IOKit,越狱。

1、UIDevice:提供了获取设备电池的相关信息,包括当前电池的状态以及电量。获取电池信息之前需要先将 batteryMonitoringEnabled 属性设置为 YES,然后就可以通过 batteryState 和 batteryLevel 获取电池信息。

优点:api简单,易于使用。
缺点:粗粒度,能够采集到的数据较少,不符合需求。

2、IOKit: 是一个iOS 系统的私有框架,它可以被用来获取硬件和设备的详细信息,也是与硬件和内核服务通信的底层框架。通过它可以获取设备电量信息,精确度达到1%。

优点:可以获取较多的电池相关的数据。
缺点:因为要访问私有api,不能通过苹果审核,只能在线下取值。获取到的值是设备的电池数据,无法达到应用级别的数据获取。

3、越狱方案:通过iOSDiagnosticsSupport 私有库,Runtime 拿到 MBSDevice 实例,获取电量日志信息表,日志信息表中包含了 iOS 系统采集的小时级别的耗电量。

优点:可以获取到应用的耗电量。
缺点:获取到的耗电量是以小时为单位的,时间间隔太长,不符合需求。

最后,为了能够采集更多的电池数据,我们选择的方案是通过访问IOKit的私有api获取数据,并且在提交到app store时将这部门代码从包里移除掉,以免影响app的审核结果。

核心代码如下:
1

通过以上方式可以获取到的数据包括但不限于:

当前充电状态
电量
是否连接USB(支持iOS10以下系统)
是否有电池
最大值
电压
温度(支持iOS10以下系统)

CPU数据

iOS的线程技术是基于Mach 线程技术实现的,在 Mach 层中thread_basic_info 结构体提供了线程的基本信息,并且每个线程中包含线程的cpu_usage。获取当前App的占用率就是所有线程的cpu_usage之和。
2

通常一个 task 包含多个线程,在内核提供了 task_threads API 调用获取指定 task 的线程列表以及线程个数,也就是target_task 任务中的所有线程保存在 act_list 数组中,数组中包含 act_listCnt 个条目。然后可以通过 thread_info API 调用来查询指定线程的信息。
3

因此,获取当前APP的CPU占用率需要遍历所有线程,将cpu_usage求和。

接下来就是获取当前设备的CPU总占用率。 iOS中CPU状态一般包括CPU_STATE_USER, CPU_STATE_SYSTEM, CPU_STATE_IDLE 和 CPU_STATE_NICE等四种。

1、CPU_STATE_USER:运行在用户态空间或者说是用户进程。
2、CPU_STATE_SYSTEM:在内核空间运行的分配内存、IO操作、创建子进程……等。
3、CPU_STATE_IDLE:空闲状态。
4、CPU_STATE_NICE:用户空间进程的CPU的调度优先级。

因此,除了空闲状态都属于CPU占用状态,因此当前CPU的总使用率为(用户+系统+调度)/(用户+系统+调度+空闲)。通过host_statistics获取host_cpu_load_info结构体数据,该结构体中 cpu_ticks 包含了 CPU 运行时四种不同该状态的时钟脉冲的数量,并且根据这四个不同状态的时间脉冲,计算出CPU的总占用率。

内存数据

获取内存数据同样也可以通过mach_task_basic_info结构获取resident_size值作为当前App已占用的内存大小。
4

但是在测试中发现,通过该结构体获取的值与Xcode中的内存数据对不上,往往差好几兆甚至好几十兆。因此通过查找资料,有一篇文章介绍通过逆向Xcode来获取Xcode计算内存方法以及结构体。该方法获取到的已占用内存大小与Xcode的值几乎一致,可以作为一个判断标准。具体参考代码如下:
5

接下来,如果要获取当前设备可以使用的空闲内存,首先要了解iOS系统的内存分配。
6

Free Memory:未使用的 RAM 容量,随时可以被应用分配使用。

Wired Memory:用来存放内核代码和数据结构,它主要为内核服务,如负责网络、文件系统之类的;对于应用、framework、一些用户级别的软件是没办法分配此内存的。但是应用程序也会对 Wired Memory 的分配有所影响。

Active Memory:活跃的内存,正在被使用或很短时间内被使用过。
Inactive Memory:最近被使用过,但是目前处于不活跃状态。
Purgeable Memory:可以理解为可释放的内存,主要是大对象或大内存块才可以使用的内存,此内存会在内存紧张的时候自动释放掉。

因此,空闲内存看成总内存大小减去 Wired Memory大小,Active Memory大小以及Inactive Memory大小。在32位系统通过这种方式获取空闲内存与Xcode数据作比较误差范围较小,而在64位系统上的数据与Xcode数据一比较误差较大,同样找到一个逆向Xcode获取Xcode的计算内存方法。64位系统获取空闲内存的具体代码如下:
7

卡顿数据

检测卡顿数据的方式通常有两种:一种是FPS卡顿检测,另一种是主线程卡顿检测。

FPS卡顿检测:检测当前页面的帧率,帧率越高意味着界面越流畅,通过计算丢帧率来检测当前页面的卡顿情况。
主线程卡顿检测:通过开辟一个子线程来监控主线程的RunLoop,当两个状态区域之间的耗时大于阈值时,就记为发生一次卡顿。

一、FPS卡顿检测

目前我们要采集的方主要是基于CADisplayLink以屏幕刷新频率同步绘图的特性,观察屏幕当前帧数的指示器,若帧率少于指定的帧率看成一个FPS卡顿。具体代码如下:
8

二、主线程卡顿检测

在主线程在Runloop的某个阶段进行长时间的耗时操作,因此主要思路就是开辟一个子线程去计算kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting两个状态区域之间的耗时是否超过某个阀值来断定主线程的卡顿情况。

那么,为什么要用kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting状态进行判定呢?首先要理清楚Runloop的运行机制,以下为RunLoop 顺序:
9

看完RunLoop顺序,就可以看到处理事件主要有两个时间段 — kCFRunLoopBeforeSources 发送之后与 kCFRunLoopAfterWaiting 发送之后。dispatch_semaphore_t 是一个信号量机制,信号量到达或者超时会继续向下进行。若超时则返回的结果必定不为0,若信号量到达返回的结果为0。利用这个特性我们判断卡顿出现的条件为在信号量发送 kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting后进行了大量的操作,在一段时间内没有再发送信号量,看成超时。也就是主线程长时间的停留在这两个状态上。转换为代码就是判断有没有超时,若超时了,再判断当前停留的状态是不是这两个状态,如果是,就判定为卡顿。具体参考代码如下:
10

流量数据

流量数据主要统计在当前App内发生的所有网络请求相应的数据大小。首先,先通过facebook提供的sonar框架捕捉app内的所有request和response。在实际的网络请求中 Request 和 Response 不一定是成对的,如果网络断开、或者突然关闭进程,都会导致不成对现象,如果将 Request 和 Response 记录在同一条数据,将会对统计造成偏差。因此request和response分开统计流量。若有对SonarKit框架感兴趣的同学可以直接访问官网进一步了解,官网:https://fbsonar.com/docs/getting-started.html

一、统计Request流量

首先需要了解请求报文的组成,如图:
11

那么,Request所花费的流量就是将把Line的大小,Header的大小,空格以及Body大小累加的合。

1、Line大小的统计

Line没有可以直接转换成CFNetwork相关数据的私有接口,但是我们很清楚 HTTP 请求报文 Line 部分的组成,因此可以手动计算Line的大小。
12

2、Header大小统计

通过request.allHTTPHeaderFields 拿到的头部数据是有很多缺失的,并不是完整的数据。同时由于无法直接转换到 CFNetwork 层,所以一直拿不到完整的 Header 数据。缺少的数包括但不限于以下几个字段:Accept,Connection,Host,当前Request的Cookie。由于基本上缺失的都是固定的几个字段,忽略这几个字段对统计的结果影响不大。因此主要针对cookie的数据并且手动大小进行补全。因此总Header的大小可以看成request.allHTTPHeaderFields数据大小加上cookie大小。

3、Body大小统计

最后是body部分,通过resquest.HTTPBody来计算Body大小。这里要注意的地方就是通过 NSURLConnection 发出的网络请求 resquest.HTTPBody 拿到的是 nil。需要通过 HTTPBodyStream 读取 stream 来获取 request 的 Body 大小。

最后,将Line大小,Header大小,Body大小相加就是当前request所话费的流量。

二、统计Response流量

请求报文的组成如下:
13

那么Response所花费的流量就是将把Status Line的大小,Header的大小,空格以及Body大小累加的合。

1、StatusLine大小

NSURLResponse没有接口能直接获取报文中的 Status Line。因此,最后通过转换到 CFNetwork 相关类拿到了Status Line 的数据后计算它的大小,这其中可能涉及到了读取私有 API,因此需要注意审核问题。

2、Header大小

通过 httpResponse.allHeaderFields拿到 Header 字典,转换成 NSData 计算大小。

3、Body大小

对于 Body 的计算,采用 expectedContentLength 或者去 NSURLResponse 对象的 allHeaderFields 中获取 Content-Length 值,其实都不够准确。Content-Length 只是表示 Body 部分的大小,因此采取直接获取body大小的方式。还有一个需要注意对 gzip 情况进行区别分析。我们知道 HTTP 请求中,客户端在发送请求的时候会带上 Accept-Encoding,这个字段的值将会告知服务器客户端能够理解的内容压缩算法。而服务器进行相应时,会在 Response 中添加 Content-Encoding 告知客户端选中的压缩算法。若Content-Encoding使用了 gzip,则模拟一次 gzip 压缩,再计算字节大小。

冷启动时间

App的冷启动就是,当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用, 这个启动方式就叫做冷启动(后台不存在该应用进程)。下面先看看苹果官方文档给的应用的启动时序图,图中可以看到冷启动是一个User taps app icon到Final initialization(applicationDidFinishLaunching: withOptions:)的过程,所以冷启动时间就是从用户唤醒App开始一直到App已启动所消耗的时间。
14

因此,冷启动时间 =DidLauching时间 - main()函数执行之前的时间。类的+ load方法在main函数执行之前调用,所以我们采取在+ load方法记录开始时间的方案。具体参考代码如下:
15

当applicationDidFinishLaunching:withOptions:方法执行完毕后,添加一个回调获取AppDidFinishLaunching后的时间。并且将开始时间与load开始时间相减作为应用冷启动时间。

总结

以上介绍了iOS中通过代码采集性能数据的方案,目前还在继续优化采集方案,希望本文章能够帮助大家对iOS性能数据采集的了解。

参考文章:

https://fbsonar.com/docs/getting-started.html

http://www.cocoachina.com/ios/20170629/19680.html

http://www.cocoachina.com/ios/20180606/23691.html

https://cloud.tencent.com/developer/article/1006222

https://www.jianshu.com/p/6c10ca55d343

http://ddrccw.github.io/2017/12/30/2017-12-30-reverse-xcode-with-lldb-and-hopper-disassembler/

https://www.jianshu.com/p/8e764d05275b?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

相关文章
|
1月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
112 1
|
2月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
14天前
|
监控 算法 iOS开发
深入探索iOS函数调用栈:符号化与性能调优实战
在iOS开发中,理解函数调用栈对于性能调优和问题排查至关重要。函数调用栈记录了程序执行过程中的函数调用顺序,通过分析调用栈,我们可以识别性能瓶颈和潜在的代码问题。本文将分享iOS函数调用栈的基本概念、符号化过程以及如何利用调用栈进行性能调优。
34 2
|
2月前
|
监控 Android开发 iOS开发
深入探索安卓与iOS的系统架构差异:理解两大移动平台的技术根基在移动技术日新月异的今天,安卓和iOS作为市场上最为流行的两个操作系统,各自拥有独特的技术特性和庞大的用户基础。本文将深入探讨这两个平台的系统架构差异,揭示它们如何支撑起各自的生态系统,并影响着全球数亿用户的使用体验。
本文通过对比分析安卓和iOS的系统架构,揭示了这两个平台在设计理念、安全性、用户体验和技术生态上的根本区别。不同于常规的技术综述,本文以深入浅出的方式,带领读者理解这些差异是如何影响应用开发、用户选择和市场趋势的。通过梳理历史脉络和未来展望,本文旨在为开发者、用户以及行业分析师提供有价值的见解,帮助大家更好地把握移动技术发展的脉络。
93 6
|
2月前
|
IDE 开发工具 Android开发
安卓与iOS开发对比:平台选择对项目成功的影响
【9月更文挑战第10天】在移动应用开发的世界中,选择正确的平台是至关重要的。本文将深入探讨安卓和iOS这两大主要移动操作系统的开发环境,通过比较它们的市场份额、开发工具、编程语言和用户群体等方面,为开发者提供一个清晰的指南。我们将分析这两个平台的优势和劣势,并讨论如何根据项目需求和目标受众来做出最佳选择。无论你是初学者还是有经验的开发者,这篇文章都将帮助你更好地理解每个平台的特性,并指导你做出明智的决策。
|
3月前
|
测试技术 Linux 虚拟化
iOS自动化测试方案(五):保姆级VMware虚拟机安装MacOS
详细的VMware虚拟机安装macOS Big Sur的保姆级教程,包括下载VMware和macOS镜像、图解安装步骤和遇到问题时的解决方案,旨在帮助读者顺利搭建macOS虚拟机环境。
142 3
iOS自动化测试方案(五):保姆级VMware虚拟机安装MacOS
|
2月前
|
安全 Android开发 数据安全/隐私保护
安卓与iOS的对决:移动操作系统的性能与创新
在当今智能手机市场,安卓和iOS两大操作系统一直处于竞争状态。本文将深入探讨它们在性能、安全性和用户体验方面的不同,并分析这些差异如何影响用户的选择。
56 3
|
2月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发:平台选择的艺术与科学
在移动应用开发的广阔天地中,安卓与iOS两大平台如同东西方哲学的碰撞,既有共通之处又各具特色。本文将深入探讨这两个平台的设计理念、开发工具和市场定位,旨在为开发者提供一份简明扼要的指南,帮助他们在这场技术与商业的博弈中找到自己的道路。通过比较分析,我们将揭示每个平台的优势与局限,以及它们如何影响应用的性能、用户体验和市场接受度。无论你是初涉江湖的新手,还是经验丰富的老手,这篇文章都将为你的选择提供新的视角和思考。
38 5
|
2月前
|
人工智能 Android开发 iOS开发
安卓与iOS开发:平台选择的艺术
在移动应用开发的广阔天地里,安卓和iOS两大操作系统各占半壁江山。本文将深入探讨这两个平台的开发环境、工具及市场趋势,帮助开发者在选择适合自己项目的平台时做出更明智的决策。通过比较各自的优势与局限,我们不仅能更好地理解每个系统的核心特性,还能洞察未来技术发展的脉络。无论你是刚入行的新手还是资深开发者,这篇文章都将为你提供有价值的参考和启示。
48 5
|
2月前
|
Linux Android开发 iOS开发
探索Android与iOS开发:平台之战还是互补共生?
在移动应用开发的浩瀚宇宙中,Android和iOS这两大星系始终吸引着无数开发者的目光。它们各自拥有独特的引力场,引领着技术潮流的方向。本文将穿梭于这两个平台的星际空间,揭示它们背后的力量对比,以及如何在这两者之间找到平衡点,共同推动移动应用开发的进步。
39 1