在移动端开发者中最重要的KPI应该是崩溃率。当崩溃率稳定下来后,工作的重心就应该转移到性能优化上。那么问题来了,如果你的项目也没有接入任何性能监测SDK,没有量化的指标来衡量,那你说你优化了性能领导信么?

虽然现在市面上第三方性能检测平台已经很成熟,但笔者还是比较建议公司自己写自己的sdk,原因如下

1. 数据安全

2. 避开费用,有的平台是MAU三万以下不收费,超出后费用极高。

3. 可以自定义指标没有无用代码,一接别人SDK包体积增大不少,其实你只用到了个别几个功能。

本文主要内容是给小项目团队自己写性能sdk的建议,也会提到当前的第三方平台。

1.页面的打开速度

关于页面的加载速度的测量有两个指标,一个是页面渲染速度,一个是页面加载速度。

页面渲染速度就是计算一个viewcontroller从viewdidload的第一行到viewdidappear的最后一行所用的时间。 这种方法可以适用于大多数相对静态的页面,一打开就直接加载的。

页面加载速度相对比较麻烦,适用于有些页面并不是一进入就加载而是先发个请求,请求回调后才继续搭建页面的。加载时间应该是用户点开页面到用户能完全的看到内容才算加载完毕,这里就需要算进去请求消耗的时间了,对于源码级别的sdk,我们可以通过每发出一个请求就记到栈里,回调一个消除一个,当栈里的每一个请求都已经回调后才能认定是界面加载完毕。 业界也有一些黑盒的云测平台是录屏解析视频流,认定一个页面从打开到页面稳定后为加载时间。 个人认为肯定没有源码级别的准啊。 (董铂然博客园)

关于这种接入一个sdk直接hook每个页面生命周期方法也简单提一下吧,就是写一个类作为UIViewController的分类,增加几个方法如XXXviewdidload , XXXviewdidappear等,然后使用这种swizzle的做法替换方法的实现

    Method viewDidAppear = class_getInstanceMethod([UIViewController class], @selector(viewDidAppear:));
Method XXXViewDidAppear = class_getInstanceMethod([UIViewController class], @selector(XXXViewDidAppear:));
method_exchangeImplementations(viewDidAppear, XXXViewDidAppear);

对于一些新增的计量属性就使用运行时关联对象的那一套吧。 如果觉得要hook的方法太多或是太麻烦或是怕和别的hook冲突,可以使用埋点的方式,直接埋上你需要计算的开始和结束时间的采集点,这种更加灵活只关心自己关心的页面。

2.内存使用值

关于内存和cpu的获取方法业内基本都有统一的代码了,大致如下。

+ (unsigned long)memoryUsage
{
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
if (kr != KERN_SUCCESS) {
return -1;
}
unsigned long memorySize = info.resident_size >> 10; return memorySize;
}

需要先引入这两个头文件

#include <mach/task_info.h>
#include <mach/mach.h>

获取到了数据存入数组那接下来的事情就是上报策略的制定了。没必要每次获取数据都上报,可以设置每次启动上报上一次session的全部记录就好,启动后隔个10秒或20秒错开请求高峰期。上报时的数据结构也要尽可能的精简,因为不能对用户的流量造成太大的损失,也可以选择先压缩后再上传。

3.CPU占用率

同样需要内存的那两个头文件

+ (CGFloat)cpuUsage
{
thread_array_t thread_list;
mach_msg_type_number_t thread_count;
thread_info_data_t thinfo;
mach_msg_type_number_t thread_info_count;
thread_basic_info_t basic_info_th; // get threads in the task
kern_return_t kr = task_threads(mach_task_self(), &thread_list, &thread_count);
if (kr != KERN_SUCCESS) {
return -1;
} CGFloat tot_cpu = 0; for (int j = 0; j < thread_count; j++) {
thread_info_count = THREAD_INFO_MAX;
kr = thread_info(thread_list[j], THREAD_BASIC_INFO,(thread_info_t)thinfo, &thread_info_count);
if (kr != KERN_SUCCESS) {
return -1;
} basic_info_th = (thread_basic_info_t)thinfo; if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
tot_cpu = tot_cpu + basic_info_th->cpu_usage / (CGFloat)TH_USAGE_SCALE * 100.0;
} } // for each thread
//free mem
kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
assert(kr == KERN_SUCCESS);
return tot_cpu;
}

关于采集数据的频率完全是自己制定的,而且大部分app都是在刚启动不久内cpu占用较大 之后就渐渐区域稳定,所以建议在刚开始采集间隔短一点比如1s,之后采集间隔逐渐加大最后稳定到5分钟获取一次。

4.页面的帧率

_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; - (void)tick:(CADisplayLink *)link {
if (_lastTime == 0) {
_lastTime = link.timestamp;
return;
}
_count++;
NSTimeInterval delta = link.timestamp - _lastTime;
if (delta < 1) return;
_lastTime = link.timestamp;
float fps = _count / delta;
_count = 0;
}  

这个测量页面帧率的做法就是通过这个CADisplayLink刷帧方法的调用次数计算的,一般这个方法最快能每秒调用60次,如果是CPU或是GPU某个步骤耗时导致渲染错过了一次垂直信号,那这个方法就不会被调用了,之后统计的帧数也就随之降低了。 上面代码中的fps就是求出的这一时刻的帧率可以塞入数组再稍作处理上传。这里需要注意的是 性能监测平台自己对性能的影响,这个统计帧率的方法可能相对来说要耗性能一些,所以需要控制在某些时刻采集一定的样本就及时暂停。  这个数据建议hook每个页面加载时的方法如viewwillappear,或是tableview滑动时的代理方法,因为卡顿大多发生在这两个场景。 对帧率有兴趣的可以做一个悬浮窗测试帧率,我之前写过一个,但是还没有抽离成组件有兴趣的可以交流下。

5.url响应时间监控

这里普通的做法就是继承NSURLProtocol 这个类写一个子类,然后在子类中实现NSURLConnectionDelegate 的那五个代理方法。

- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
// 这个方法里可以做计时的开始 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
// 这里可以得到返回包的总大小 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
// 这里将每次的data累加起来,可以做加载进度圆环之类的 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
// 这里作为结束的时间 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
// 错误的收集 

(经过调研connection在升级ipv6后还是可以使用的)你可以得到请求的url,建议只记录host和path,因为吧拼上的参数也上传第一不好快速分类,第二服务端还要做截取处理增加压力,第三浪费流量。 也可以得到response包的length。 响应时间可以根据结束方法中的时间减去开始方法中的时间得到,然后以key-value的方式上传。个人觉得请求没必要与viewcontroller关联,要考虑到管理页面栈还要考虑present和dismiss,收效甚微。并且想知道哪个url属于哪个页面还有很多可执行的方案。

关于webView的url响应时间监控,比普通的native内url要复杂一些。 虽然上面说的统计方法也可以统计到webview的url响应时间,但是只能统计到完整url时间。webView的打开时可能会伴随一些302跳转之类的url,最后才到目标url。 如果一个url经历了三次跳转,响应时间很长,用上面说的统计方法只能统计总时间并不能定位到具体是哪一个子url异常。 这时就需要webView内特殊的时间统计了。

有的一个url会调多个不同团队维护的服务导致了重定向(图中ABC都是完整url的子url),分别在webView的两个代理方法中埋点计算差值,从A走了didstart到B走了didstart这个时间的差值为①就是A所消耗的时间,①+②+③是完整的时间。 通过这种分离统计的方法,可以把慢url明确定位到每一个服务,不对拖累好人。

6.请求错误码统计

如果没有做错误码统计的话,你服务器有问题或者某些地区被运营商劫持之类的,你app的界面可能显示的就是一直转菊花,这时是无法精确定位问题的。 所以将自己的请求都进行编号并收集错误码是非常有必要的。 我们所关注的错误码有三种:

1. 正常的HTTP Response code 比如200 ,404,500

2. AFN(现在ASI用不了了,ipv6强制升级后 应该大部分人都是用AFN3.0了)指定的code 如 -1101, 1102等

3. 你自己请求制定的errorcode。 如 errorcode : 7, message : "您还没有登录" 或 errorcode:10,message:"签名验证失败"

还有很重要一点,就是将自己的所有接口做成一整套映射的编号, 举个例子67对应的是persondetail接口。出现了请求错误,上传的请求错误码应该包含两个部分:接口编号+错误编号。 错误编号建议优先使用自己制定的errcode,其次是AFNcode,最后才是HTTPresponsecode。

“67-10” “35-404” 如果以后上报了类似的错误码,当然比只看到转菊花要好定位的多。

最后提当下的第三方性能监控平台,有听云,OneApm,博睿。 这里就不一一对比每一个平台的优缺点了,因为大体上都是差不多的。

就拿oneapm来说吧,首先ui做的比其他今个平台都要好,让人感觉更专业一点。  接入的方式也是对代码侵入性较小,接一个SDK大小6M左右,加几个framework,然后main函数加一行代码,上传的token是注册后生成的。

我在自己github的一个库里接了这个sdk,后来发现用的人还不少。里面有demo和一些性能监测平台的截图有兴趣可以去看看

https://github.com/dsxNiubility/SXNews

核心功能(这里说一下,我只用了这些平台的移动端功能)也就是包括:UI交互,url响应时间,用户的版本地域系统分布,及crash。 其中崩溃率如果需要统计的话需要手动上传dsym文件。  并且用着用着就遇到了我前面说的问题,有一些我们不需要的功能,并且也有一些我们需要他却没有的功能,比如帧率和webView中的子url响应时间。 优点内就是FE展示时指标内的图表很清晰,并且可以自定义定制仪表盘。这是一个小公司自己写sdk前期难以达到的。  还有一点就是可配置,在设置页面可以自行选择自己所关注的指标。 然后程序每次启动都会先有一个GET请求获取配置,哪些打开哪些关闭,然后测量和上传我们需要的(这里有一点可疑的是,他是真没测量还是全都测量上传了,只是在控制台里不给你展示罢了),我们自写的sdk做个可配置也是必要的。

本文是作者一段时间内的经验,希望能对你有所帮助,观点不同欢迎讨论。

谈谈iOS app的线上性能监测的更多相关文章

  1. 在iOS App的图标上显示版本信息

    最近读到一篇文章(http://www.merowing.info/2013/03/overlaying-application-version-on-top-of-your-icon/)介绍了一种非 ...

  2. 当app出现线上奔溃,该如何办?

    1.如何追踪app崩溃率,如何解决线上闪退 当iOS设备上的App应用闪退时,操作系统会生成一个crash日志,保存在设备上.crash日志上有很多有用的信息,比如每个正在执行线程的完整堆栈跟踪信息和 ...

  3. 记Booking.com iOS开发岗位线上笔试

    今晚参加了Booking的iOS职位线上笔试,结束后方能简单归纳一下. 关于测试内容: Booking采用了HackerRank作为测试平台,测试总时长为75分钟,总计4道题. 测试之前我很紧张,因为 ...

  4. 无需编译app切换线上、测试环境

    在咱们测试过程中,经常需要切换测试环境和线上环境.大致有如下几个方案. 一.服务器地址编译到app中 此种方式需要在代码里保存两套配置,一套指向线上,一套指向测试.通过编译参数分别生成测试包.线上包. ...

  5. redis中keys命令带来的线上性能问题

    起因 下午接到运维反馈,生产redis有个执行keys的命令请求太慢了,要两三秒才能响应 涉及命令如下: KEYS ttl_600::findHeadFootData-15349232-*-head ...

  6. IOS APP开发UI上的尺寸注意问题(屏幕、适配、分辨率)

  7. [转]iOS hybrid App 的实现原理及性能监测

    转自:http://www.cocoachina.com/ios/20151118/14270.html iOS hybrid App 的实现原理及性能监测 2015-11-18 11:39 编辑:  ...

  8. 火山引擎MARS-APM Plus x 飞书 |降低线上OOM,提高App性能稳定性

    通过使用火山引擎MARS-APM Plus的memory graph功能,飞书研发团队有效分析定位问题线上case多达30例,线上OOM率降低到了0.8‰,降幅达到60%.大幅提升了用户体验,为飞书的 ...

  9. iOS App稳定性指标及监测

    一个App的稳定性,主要决定于整体的系统架构设计,同时也不可忽略编程的细节,正所谓"千里之堤,溃于蚁穴",一旦考虑不周,看似无关紧要的代码片段可能会带来整体软件系统的崩溃.尤其因为 ...

随机推荐

  1. 判断checkbox的checked状态(jQuery写法)

    $('#checkboxInput').click(function(){ if (this.checked){ $('.questionContainer').css({ "opacity ...

  2. lintcode Permutation Index

    题目:http://www.lintcode.com/zh-cn/problem/permutation-index/ 排列序号 给出一个不含重复数字的排列,求这些数字的所有排列按字典序排序后该排列的 ...

  3. php的基础

    js是前段脚本语言 php是后端脚本语言 一.所建的文件都要存在wap下的www里面 二.所有的文件名都不能包含中文 三.通过输入 localhost/www下的文件名称,可以浏览 四.在DW内新建站 ...

  4. Form authentication(表单认证)问题

    前言 最近在做ASP.NET MVC中表单认证时出了一些问题,特此记录. 问题 进行表单认证时,在 PostAuthenticateRequest 事件中从Cookie值中解密票据.如下: prote ...

  5. Hawk 数据抓取工具 使用说明(二)

    1. 调试模式和执行模式 1.1.调试模式 系统能够通过拖拽构造工作流.在编辑流的过程中,处于调试模式,为了保证快速地计算和显示当前结果(只显示前20个数据,可在调试的采样量中修改),此时,所有执行器 ...

  6. GROUP函数-GROUP_ID,GROUPING,GROUPING_ID

    GROUP_ID 首先我们看看官方的解释: 大意是GROUP_ID用于区分相同分组标准的分组统计结果. 解释起来比较抽象,下面我们来看看具体的案例. 例1:单一分组 SQL> select gr ...

  7. java 中多线程之间的通讯之生产者和消费者 (多个线程之间的通讯)

    在真实开发 中关于多线程的通讯的问题用到下边的例子是比较多的 不同的地方时if 和while 的区别 如果只是两个线程之间的通讯,使用if是没有问题的. 但是在多个线程之间就会有问题 /* * 这个例 ...

  8. Java服务器对外提供接口以及Android端向服务器请求数据

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/5056780.html 讲解下java服务器是如何对移动终端提供接口的,以什么数据格式提供出去,移动端又是怎么 ...

  9. 【SQLServer】DBHelper即C#数据库底层封装

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.C ...

  10. IE6 IE7 ‘JSON’ 未定义

    今天在调试javascript程序,在FireFox和Chrome没有问题,但是在IE中,一些可以,就会出现如标题的错误:‘JSON’ 未定义: 在IE6,IE7一定有此错误,以及IE能设置兼容性视图 ...