网络请求各个指标的度量
一,效果
比如我们向 https://github.com/
发起一个 http 的请求,我们看下可以拿到哪些指标。
通过下面的这些指标,我们可以分析这些数据,得知我们的网络请求为什么比较慢,哪个阶段比较慢,再逐步优化。
httpmetrics.png
二,一共有哪些指标?
下面这个类,在 >= iOS10 的操作系统才可以运作,部分属性在 >= iOS13 才可以使用,我分别解释下他们的意思,但是 有些属性,我是真的不知道是干什么的,经过尝试,没有变化,这里我就不做解释。
如果觉得我是在这瞎 BB,可以去 navigate to NSURLSessionTaskTransactionMetrics define 自己查看,上面备注写的也很清楚了。
API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))
@interface NSURLSessionTaskTransactionMetrics : NSObject
// URLRequest 请求的 Body 和 Header 都在这里面呢,包含请求的全部信息。
@property (copy, readonly) NSURLRequest *request;
// URLResponse 相应的信息都在这个对象里面
@property (nullable, copy, readonly) NSURLResponse *response;
// 请求任务开始前,可以理解为整个请求的最开始的时间点。
@property (nullable, copy, readonly) NSDate *fetchStartDate;
// 开始 DNS 解析的时间点
@property (nullable, copy, readonly) NSDate *domainLookupStartDate;
// 结束 DNS 解析的时间点
@property (nullable, copy, readonly) NSDate *domainLookupEndDate;
// 建立连接的起始点
@property (nullable, copy, readonly) NSDate *connectStartDate;
// 建立 SSL/TLS 对话密钥的起始时间
@property (nullable, copy, readonly) NSDate *secureConnectionStartDate;
// 建立 SSL/TLS 对话密钥的终点时间
@property (nullable, copy, readonly) NSDate *secureConnectionEndDate;
// 建立连接的终点
@property (nullable, copy, readonly) NSDate *connectEndDate;
// 建立好连接通道后,请求开始的时间点
@property (nullable, copy, readonly) NSDate *requestStartDate;
// 建立好连接通道后,请求结束的时间点
@property (nullable, copy, readonly) NSDate *requestEndDate;
// 开始得到响应的时间点
@property (nullable, copy, readonly) NSDate *responseStartDate;
// 接收完最后一字节的数据的响应结束时间点
@property (nullable, copy, readonly) NSDate *responseEndDate;
// 网络协议的名称
@property (nullable, copy, readonly) NSString *networkProtocolName;
// 是否使用了网络代理
@property (assign, readonly, getter=isProxyConnection) BOOL proxyConnection;
// 是否重用了连接
@property (assign, readonly, getter=isReusedConnection) BOOL reusedConnection;
// 资源获取的类型,有缓存,Network,不知道,等
@property (assign, readonly) NSURLSessionTaskMetricsResourceFetchType resourceFetchType;
// iOS13+ 发送的请求头占的字节
@property (readonly) int64_t countOfRequestHeaderBytesSent API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 发送的 Body 占的字节
@property (readonly) int64_t countOfRequestBodyBytesSent API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 发送的 Body 在 Encoding 之前占的字节
@property (readonly) int64_t countOfRequestBodyBytesBeforeEncoding API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 响应头占字节数
@property (readonly) int64_t countOfResponseHeaderBytesReceived API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 响应 Body 体占字节数
@property (readonly) int64_t countOfResponseBodyBytesReceived API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ Decoding 之后响应消息体占字节数
@property (readonly) int64_t countOfResponseBodyBytesAfterDecoding API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 本地 IP 地址
@property (nullable, copy, readonly) NSString *localAddress API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 本地端口
@property (nullable, copy, readonly) NSNumber *localPort API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 访问服务器机器的 IP 地址
@property (nullable, copy, readonly) NSString *remoteAddress API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 访问服务器机器的端口号
@property (nullable, copy, readonly) NSNumber *remotePort API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ TLS 协议的版本
@property (nullable, copy, readonly) NSNumber *negotiatedTLSProtocolVersion API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ TLS 选择的加密协议
@property (nullable, copy, readonly) NSNumber *negotiatedTLSCipherSuite API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 不知道,我不论怎么切换网络环境一直都是 NO
@property (readonly, getter=isCellular) BOOL cellular API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 不知道,我不论怎么切换网络环境一直都是 NO
@property (readonly, getter=isExpensive) BOOL expensive API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 不知道,我不论怎么切换网络环境一直都是 NO
@property (readonly, getter=isConstrained) BOOL constrained API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
// iOS13+ 不知道,我不论怎么切换网络环境一直都是 NO
@property (readonly, getter=isMultipath) BOOL multipath API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
兄弟别慌,我刚开始看也懵逼,我给你看一张图,你就不懵逼了。在下方:
红字: 描述请求的整个过程。 白字: 对应上面的属性名字,各个阶段的位置。
是不是懵逼症状好一些了。
WechatIMG1009.png
三,如何得到这些指标?
这里还是要感谢苹果爸爸,虽然 iOS10 才给出来,但是现在已经 9102 年了,以支持最近 3 个版本的原则,我们的用户也应该都是 iOS10+ 的了,所以这个官方提供的功能,用的安全放心,还简单,不用去 hook 这,hook 那,然后得到的数据还不准,还让工程复杂化。
首先 调用 session 的构造器的时候,传入 delegate,这里比如就是 self。
NSURLSession *session = [NSURLSession
sessionWithConfiguration: config
delegate:self delegateQueue:nil];
其次让 self 遵守 NSURLSessionTaskDelegate 协议 实现如下代理方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
if ([metrics.transactionMetrics count] > 0) {
[metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {
NSLog(@"========================================================================");
if (obj.domainLookupStartDate && obj.domainLookupEndDate) {
int dnsLookupTime = ceil([obj.domainLookupEndDate timeIntervalSinceDate:obj.domainLookupStartDate] * 1000);
NSLog(@"DNS 解析时长 单位 ms:%d",dnsLookupTime);
}
if (obj.connectStartDate && obj.connectEndDate) {
int tcpTime = ceil([obj.connectEndDate timeIntervalSinceDate:obj.connectStartDate] * 1000);
NSLog(@"TCP+SSL 整个连接建立的时间 单位 ms:%d",tcpTime);
}
if (obj.secureConnectionEndDate && obj.secureConnectionStartDate) {
int sslTime = ceil([obj.secureConnectionEndDate timeIntervalSinceDate:obj.secureConnectionStartDate] * 1000);
NSLog(@"SSL 四次握手时长 单位 ms:%d",sslTime);
}
if (obj.requestStartDate && obj.responseEndDate) {
int transmissionTime = ceil([obj.responseEndDate timeIntervalSinceDate:obj.requestStartDate] * 1000);
NSLog(@"传输时间 单位 ms:%d",transmissionTime);
}
if (obj.fetchStartDate && obj.responseEndDate) {
int requestTime = ceil([obj.responseEndDate timeIntervalSinceDate:obj.fetchStartDate] * 1000);
NSLog(@"完整请求时长 单位 ms:%d",requestTime);
}
if (obj.fetchStartDate) {
UInt64 requestDate = [obj.fetchStartDate timeIntervalSince1970] * 1000;
NSLog(@"开始发起请求的时间点:%llu",requestDate);
}
if (obj.responseEndDate) {
UInt64 responseEndDate = [obj.responseEndDate timeIntervalSince1970] * 1000;
NSLog(@"请求结束的时间点:%llu",responseEndDate);
}
NSLog(@"HTTP 请求类型:%@",obj.request.HTTPMethod);
NSLog(@"协议名称:%@",obj.networkProtocolName);
NSLog(@"请求 URL:%@",[obj.request.URL absoluteString]);
NSLog(@"是否使用代理:%d",obj.isProxyConnection);
NSLog(@"是否重用连接获取资源:%d",obj.reusedConnection);
// iOS13 或者 iOS13 以上才可以使用
NSLog(@"服务器 IP:%@",obj.remoteAddress);
NSLog(@"服务器端口:%@",obj.remotePort);
NSLog(@"请求的 Header 字节数:%lld bytes",obj.countOfRequestHeaderBytesSent);
NSLog(@"请求的 Body 字节数:%lld bytes",obj.countOfRequestBodyBytesSent);
NSLog(@"请求的 Encoding 之前的 Body 字节数:%lld bytes",obj.countOfRequestBodyBytesBeforeEncoding);
NSLog(@"响应的 Header 字节数:%lld bytes",obj.countOfResponseHeaderBytesReceived);
NSLog(@"响应的 Body 字节数:%lld bytes",obj.countOfResponseBodyBytesReceived);
NSLog(@"响应的 Decoding 之后的 Body 字节数:%lld bytes",obj.countOfResponseBodyBytesAfterDecoding);
// 网络类型
// NSURLSessionTaskMetricsResourceFetchTypeUnknown
// NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad
// NSURLSessionTaskMetricsResourceFetchTypeServerPush
// NSURLSessionTaskMetricsResourceFetchTypeLocalCache
}
}];
}
}
Run 一下,看看 delegate 方法都捕捉到了哪些指标参数 。
四,总结
但是我发现一个问题,requestStartDate 比 requestEndDate 晚, 例如打印如下: requestStartDate:1576467073.038733 requestEndDate:1576467071.590589 按照 WWDC 上面所描述的,应该 start 先于 end,但是实际测试结果并不是这样。
我也弄不清楚为什么,将这个问题放在了 stackoverflow 上面: https://stackoverflow.com/questions/59350261/ios-why-is-requestenddate-earlier-than-requeststartdate 如果有知道的小伙伴告诉我下哈!
补: 我自己回答了我的提问,在 OC 上面测试,我发现结果是正确的,但是在 swift 却是有问题的。
了解了一个完整的网路请求链路,以及每个链路的时间点,都是做什么,如何使用官方提供的 API 来进行收集数据,进行性能分析。
如果觉得自己写 Demo 测试验证麻烦,直接下载我上传的 Demo 没毒放心
Demo: https://github.com/zColdWater/iOSURLMetricsDemo/tree/master
2016 - 2019 WWDC 介绍这块的 PPT
https://github.com/zColdWater/Resources/blob/master/Images/713_advances_in_networking_part_2.pdf
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: FDLog App 客户端日志系统
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论