写点什么

网易 NeteaseAPM iOS SDK 技术实现分享

2016 年 5 月 23 日

嘉宾介绍

朱志强,曾在创业团队中担任技术负责人,开发 OSX 下办公软件,独立完成的文字编辑工具包含图文混排、图表、输入动画、多媒体等功能。目前是网易 APM iOS 端负责人,热爱编程之美,享受创造的乐趣。

一. NeteaseAPM 是什么

Application Performance Management(APM),应用程序性能管理, 主要指对企业的关键业务应用进行监测、优化,提高企业应用的可靠性和质量,保证用户得到良好的服务。一个企业的关键业务应用的性能强大,可以提高竞争力,并取得商业成功。

NetsaseAPM 是网易性能数据分析平台,一个用户数据分析平台,支持非侵入式获取应用性能数据,实时展示多个维度分析结果。目前支持移动端和浏览器端,此次分享只介绍 iOS SDK 端。

NetsaseAPM 移动端支持的功能:

  1. 应用性能分析
    对当前应用请求的各项性能指标进行分析,如响应时间,吞吐量,下载速率等,帮助用户全面了解应用性能表现。
  2. 错误分析
    分析应用每个域名的网络错误率及响应码错误率,快速定位应用问题。
  3. 多维分析
    可以组合域名,地理位置,运营商,网络环境等参数,精确定位应用的性能问题。

下面是从移动端收集的数据在 NeteaseAPM Web 平台的展示:

(点击放大图像)

(点击放大图像)

(点击放大图像)

二. NeteaseAPM iOS SDK 的目标

  1. 最小侵入:
    只需要启动一次,就可以持续收集网络和交互数据,不需要手动收集数据。
    启动方式:


2. 启动之后,NeteaseAPM 会插入收集数据的代码到系统调用中,却不会影响用户的使用,在用户的网络消息中的位置如图:


3. 最大化自由配置:

用户可以看到收集到的数据,选择是否上传到 NeteaseAPM 服务器;

用户可以自己上传数据到 NeteaseAPM 服务器;
4. 更多功能:
首创监控底层网络库 CFNetwork,和竞品监控第三方网络库 ASIHttpRequest 的方案相比,更加干净,能监控更多数据;

三. 现状

目前实现的功能:

  • 网络请求的响应时间,下载速率,状态码,错误码,网络状态等数据的收集;
  • 页面加载时间的收集,检查出慢交互页面;

已经接入的应用: 二次元,秀品,网易新闻,考拉。

四. 整体设计

NeteaseAPM iOS SDK 分为四个部分:

  • Hooker
    Hooker 负责在用户感知不到的情况下替换程序原实现,转发消息回调,完成对系统消息的 hook 和数据的采集。
    Hooker 能否监控到更多更准确的数据,是衡量一个 APM 产品是否优秀的最重要的标准。
    一个 APM 产品监控的数据面越广,收集的数据越细,就越能准确地取得用户的性能数据,帮助产品优化性能。
  • DataBuilder
    收集监控数据;
  • Persistence
    缓存监控数据;
  • Poster
    上传监控数据到 NeteaseAPM;

线程模型: 监控数据的保存和发送都在后台队列中执行,不会影响用户线程。

数据上传规则:

  • 可设置允许数据上传的网络环境;
  • 数据支持批量发送,可自定义发送批量和等待间隔;

================================

五. 一些关键实现

APM SDK 使用方式和一般的 UI 库有很大不同,APM 只需要启动一次即可生效,不需要修改代码。

例如:启动 APM 之后,用户使用 NSURLConnection 执行的网络请求就会在开始连接,得到响应,获取数据等时机被 APM 监控到, 而不需要使用者手动添加任何监控代码。

要实现这样的目标,Hooker 需要使用一些比较特殊的的方式来实现,下面介绍 Hooker 使用到的解决方案:

1. 使用面向切面编程

APM 需要将监控代码插入到系统实现中,完成系统消息的拦截和数据收集。

比如我们需要知道 NSURLConnection 在什么时候开始发送请求,就会监控 -[NSURLConnection start] 方法的调用时机, 这就需要在这个方法中插入 APM 的逻辑,要做到这一点,需要理解面向切面编程的思想。

Aspect Oriented Programming(AOP),面向切面编程,面对的是处理过程中的某个步骤或阶段。 在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。 借助 AOP,甚至不用修改一行代码,就可以修改现有程序的行为,非常高效。

AOP 基本原理:将一个函数替换为一个新函数,新的函数中插入代码片段,然后执行原函数。

iOS 开发中用到的库按照实现语言可以分为 Objective-C 和 C 两种,下面介绍这两种语言能够支持 AOP 的原理。

Objective-C 对 AOP 的支持非常容易:

Runtime 支持方法名和方法实现的分离,Objective-C 的方法名类型是 SEL,方法实现类型是 IMP。

通过一个例子了解 SEL 和 IMP 的关系:

一个 Objective-C 方法 [self setFilled:YES] 完全可以用下面的代码代替:

所以通过修改 SEL 对应的 IMP,可以方便地 hook 原方法。

Objective-C 上的 AOP 有一个好听的名字: Method Swizzling 被灌醉的方法 ~~!

C 的 AOP 涉及到程序较底层,比较少见,简单了解一下。

C 函数指针的地址可以通过 dlsym 函数取得,如:

C 的 AOP 需要查找到这个函数指针的地址,再使用新的函数指针替换原函数指针。

基于 iOS 动态链接器的符号绑定,查找函数指针地址的过程见下图:

这里借助了 Facebook 的一个开源库:fishhook 。

2. 使用代理模式采集回调消息的数据

通过 AOP,可以监控指定类的指定方法了,我们可以取得方法调用的时机了, 但是程序中除了方法调用还存在方法回调,这是一种不适合用 AOP 监控的情况。

例如 NSURLConnection 的构造方法和 start 方法可以通过 Method Swizzling 监控到, 但是回调消息的接收者 delegate 的类名不固定,可能是任意一个页面实例, 如果还要使用 Method Swizzling 的方法来监控,会面对未知个数的页面的 delegate 方法,不是一个好办法。

解决方法是构造一个回调消息的转发者作为代理,在转发者中收集数据,再转发给用户。

下图演示对 NSURLConnection 的监控,MAM IMP 就是被替换过的新的 start 方法的实现, ProxyDelegate 就是消息转发者,负责将回调消息转发给 delegate 对象:

要实现一个 Objective-C 的代理,一定要注意以下问题:

  • 系统在向 delegate 发送消息时会调用 -[NSObject respondsToSelector:] 方法,所以 ProxyDelegate 需要重写此方法,才能正确地获取到回调消息;
  • ProxyDelegate 没必要实现的用户 delegate 方法,如鉴权请求,需要借助 Objective-C 的动态特性,使用 -[NSObject forwardInvocation:] 方法将 delegate 能够响应的方法直接转发给用户的 delegate;

ProxyDelegate 中的 forwardInvocation: 方法实现:

不止 NSURLConnection,CFNetwork 的监控也使用了代理模式:

Proxy Stream 拦截 read 方法,记录 stream 读取成功的数据长度,再转发给 Original Stream。

3. 借助桥接模式,从面向过程到面向对象的数据

CFNetwork 是一个 C 语言实现的网络系统框架,虽然使用起来比较麻烦,但是可配置的功能更多,仍然被一些产品看好。

但是由于面向过程编程难以扩展的缺点,没有办法选择性地监控和 http 有关的 CFReadStream,而不影响到来自文件或内存的 CFReadStream;

虽然面向过程编程难以扩展,但是 Objective-C 支持从面向过程到面向对象的桥接:

Toll-Free Bridging, 它允许某些 CoreFoundation 类与其对应的 Objective-C 类互换使用,使我们在面向过程编程和面向对象编程两种编程思想中自由切换。

NeteaseAPM 的策略:在系统构造 http stream 时,将一个 NSInputStream 的子类 ProxyStream 桥接为 CFReadStream,返回给用户, 达到单独监控 http stream 的效果。

使用 Toll-Free Bridging 时需要重点关注两种编程思想切换时内存管理机制的不同引起的内存问题;

例如 -[NSInputStream propertyForKey] 方法和 CFReadStreamCopyProperty 函数是桥接的,但是它们内存管理方法不同,前者是自动管理,后者是手动管理,后者在调用之后需要使用者在随后手动调用 CFRelease。

以 ProxyStream 举例,ProxyStream 是 CFReadStream 的代理,负责管理 http stream 的数据收集。

如果用户对一个 http stream 执行如下调用:

ProxyStream 的 propertyForKey: 方法会因为桥接而被调用,ProxyStream 需要从 original stream 中获取正确的 property,如果 ProxyStream 中这么写

那么这个方法返回的结果会被 CFReadStreamCopyProperty 随后的 CFRelease 函数错误地释放,引起内存异常。

正确的书写方式应该是:

ProxyStream 只需要”转发”CFReadStreamCopyProperty 函数给 original stream 就可以了。

4. 借助 NSURLProtocol 的 UIWebView 监控

NSURLProtocol 是监控 UIWebView 请求最普遍的解决方案。

这里用户会产生多个 URLProtocol 会不会冲突的疑问?

结论是不会,因为即使用户注册了 NSURLProtocol,拦截了 NeteaseAPM 的 URLProtocol 的请求, 但用户的 URLProtocol 发送请求时的 NSURLConnection 仍然会被 NeteaseAPM 监控到的;

UIWebView 的请求比较复杂,下面收集几个容易出错的问题:

  • 某些网页的验证码的请求可能是一个阻塞主线程的请求,在 URLProtocol 的网络请求需要放在后台执行,否则会被阻塞住而无法得到响应。
  • 重定向的网页请求需要转发给网页,APM 不能监控。

================================

六. 后续工作

  • 支持更多的性能数据的收集,如内存,CPU,帧率等;
  • 支持性能问题的自动诊断,如卡顿监控,Out of Memory 问题诊断等,同时提高性能问题诊断的能力。

延伸阅读:

Method Swizzling: http://blog.csdn.net/yiyaaixuexi/article/details/9374411

fishhook: https://github.com/facebook/fishhook

QA 环节

Q:可以细说一下验证码请求阻塞主线程的例子吗?

A:这个问题的现象是,UIWebView 访问 https://reg.163.com 时,点击注册邮箱,填写完成后点击注册,界面卡住一段时间后无响应,没有跳转。

调查发现:UIWebView 访问 https://reg.163.com/services/checkSsnAll?isret=1&username=XXX 时,会在主线程使用 ajax 向服务器发送验证码请求。由于这个请求阻塞了主线程,NeteaseAPM 的数据发送失败,导致这么问题。

Q:加入监控后性能影响多少?

A:这个没有具体测试过,不过 QA 曾经连续跑一个测试跑了两天,没有出现性能问题。

Q:监控数据上报,会缓存到本地,然后再上报么?会有流量问题么?

A:是的,数据上传使用了短码,并且压缩上传,一次上传的数据大小在 60 比特。并且可以设置上传的网络环境。流量问题可以通过各种方式解决,NeteaseAPM 支持自定义数据发送方式,有些产品有免流量的通道,NeteaseAPM 就把数据交给产品发送了。

Q:页面加载监控,hook 那些方法?

A:页面监控的方法是 viewDidLoad,viewDidAppear,viewDidDisappear 等方法。如果页面过于复杂渲染时间长,页面的生命周期方法会正常执行的,可以准确地取得页面的状态,慢加载也是通过页面加载时间诊断的。

Q:流媒体能监控吗? 我尝试过 oneAPM,用 AVPlayerLayer 实现的流媒体没法监控。

A:流媒体监控可以使用 NSURLProtocol 监控到,但是目前还没有针对这方面的测试。

Q:NSURLProtocol 不能吧, 我记得 oneAPM 就是这么做的,然后我在后台看没监控到…

A:AVPlayerLayer 的请求非常频繁,可能会被过滤掉,有同事反映,他们后来也选择了过滤掉 AVPlayerLayer 触发的请求。

Q:回调的信息数据,是程序退出后台的时候发送,还是回调以后就会发送统计服务器。

A:数据保存和数据发送是两个队列,数据保存在请求结束后执行,数据发送的规则是批量发送,支持自定义。

Q:开始介绍有说到可以 “支持页面加载时间的收集,检查出慢交互页面”。这里的加载时间只是请求发出数据到回来的时间,还是包括页面数据渲染时间在内,如果包括,是怎么无侵入的统计的,慢交互页面又是怎么统计的?

A:SDK 端负责将网络请求和交互的时间点交给服务器,具体的分析由 NeteaseAPM 平台诊断,现在使用的规则应该是页面从 didload 到打开超过 1 秒就算是慢交互了。

Q:与其他平台的 APM 服务兼容性如何?都是同样的实现原理吗?

A:目前使用到的监控方式如 AOP,代理都是兼容多次监控的,兼容性可以保证。CFNetwork 的监控是不一样的。

Q:上传到平台的 didload 统计时长应该有对应的页面唯一标识吧,这个标记是在业务页面手动加的,还是 hook 页面方法自动加的,如果是自动加的,页面唯一标识是怎么在 hook 里传递记录的?

A:页面唯一标识保存在一个容器中,这个容器维护着页面引用和页面标识的对应关系,hook 中需要根据页面查询页面标识。

Q:页面类字符串映射页面唯一标识么?

A:会使用页面的指针映射页面唯一标识,防止同时有多个同类页面。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 5 月 23 日 17:368791

评论

发布
暂无评论
发现更多内容

交易所画k线市值管理机器人,做市机器人开发

WX13823153201

阿里P8Java大神给迷茫的程序员一些中肯建议:“请不要再虚度光阴了!”

Java架构之路

Java 阿里巴巴 程序员 架构 编程语言

【原创】Spring Boot集成Redis的玩法

田维常

spring Boot Starter

【原创】Spring Boot 如何手写stater

田维常

spring Boot Starter

区块链数字货币商城系统开发技术

薇電13242772558

区块链 数字货币

Spring Boot 集成 Druid 监控数据源

田维常

spring Boot Starter

【原创】Spring Boot终极篇《下》

田维常

spring Boot Starter

想不通(关于人生的突发奇想)

干啥啥不行的赢

WebSocket连接错误Error during WebSocket handshake Unexpected response code 404

李浩宇/Alex

浅析一个较完整的SpringBoot项目

田维常

spring Boot Starter

一站式低延迟直播连麦解决方案

anyRTC开发者

音视频 WebRTC 直播 RTC sdk

MySQL-技术专题-创建临时表

李浩宇/Alex

如何快速构建Spring Boot基础项目?

田维常

spring Boot Starter

【原创】Spring Boot终极篇《上》

田维常

spring Boot Starter

为什么11·11物流一年比一年快?奥秘就在这里!

华为云开发者社区

物联网 物流 仓储

产品经理团队的管理秘法

马踏飞机747

管理 产品经理 团队

MySQL-技术专题-STRAIGHT_JOIN

李浩宇/Alex

用时半个月,终于把2020年各大公司的Java面试题精选整理成文档了

Java架构之路

Java 架构 面试 编程语言

【原创】Spring Boot 集成Spring Data JPA的玩法

田维常

spring Boot Starter

【原创】Spring Boot集成Mybatis的玩法

田维常

spring Boot Starter

【原创】SpringBoot快速整合Thymeleaf模板引擎

田维常

spring Boot Starter

国家超算深圳中心计划2年内提升计算能力至少1000倍;图神经网络的生成式预训练论文解读

京东智联云开发者

云计算

阿里P8架构师呕心沥血整理的【Docker实战】文档带你玩转Docker。

Java架构之路

Java 程序员 架构 面试 编程语言

【原创】SpringBoot 这几种配置文件方式,你都用过吗?

田维常

spring Boot Starter

华为云FusionInsight湖仓一体解决方案的前世今生

华为云开发者社区

数据库 华为 仓库

详解软件行业低代码开发平台以及敏捷开发方案

Marilyn

敏捷开发

华为20级工程师吐血整理出600页Spring微服务架构设计,绝了!

996小迁

Java spring 编程 架构 spring微服务

华为云“创原会”:40+技术精英论道云原生2.0

华为云开发者社区

k8s 华为云

【原创】Spring Boot一口气说自动装配与案例

田维常

spring Boot Starter

Spring Boot 如何快速实现定时任务

田维常

spring Boot Starter

【原创】Spring Boot 过滤器、监听器、拦截器的使用

田维常

spring Boot Starter

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

网易NeteaseAPM iOS SDK技术实现分享-InfoQ