APM监控系统-启动监控
前言
iOS 网络库普遍用的开源库是AFNetworking和微信的Mars,大多数都是基于AFNetworkin苹果xrg实现自己的网络库。实现网络监控,挺简单的,但是身处于基础服务组,需要对业务侧无侵入监控,将会遇到一些困难。在此记录APM专项网络指标监控所遇到的问题。
App 网络请苹果x求过程
NSURLSehttp 500ssionTaskDelegate 新代理方法
iOS 10 之后,NSURLSessionTa监控怎么查看回放skDelegate 中增加了一个新的代理方法:
/*
* Sent when complete statistics information has been collected for the task.
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
不要想着 iOS苹果范冰冰 9 没有,就放弃苹果爸爸给的糖。别在意那么一丢丢用户量。
以下有三种方案,方案三是为实现业务无侵入https和http的区别而实现,若司内没有过多的项目且没有所谓的基础服务库,直接使用方案一和方案二。
方案一:NSURLProtocol 监控 App 网络请求
自定义CustomURLProtocol,实现网络监控。
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
}
- (void)startLoading {
}
#pragma NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
// 实现网络指标上报
}
缺点
需要实现 protocolClasses。 问题在于多个自定义URLProtocol,只响应一个。如果项目业务侧没有其http协议他自定义URhttpwatchLProtocol,可以用此方案实现。如果在基础服务组,而司内的项目又有自定义URLProtocol,会导致业务侧无法实现自定义URLProtocol逻辑代码。
NSURLSessionConfiguration *config=[NSURLSessionConfiguration defaultSessionConfiguration];
config.protocolClasses=@[[CustomURLProtocol class]];
方案二:实现 AFNetworkiAPPng 的 MetricsBlock
- (void)setTaskDidFinishCollectingMetricsBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSURLSessionTaskMetrics * _Nullable metrics))block AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
可以在司内apple苹果官网的基础服务网络库实现此block,进行网络指标上报。
缺点
- AFNetworking 4.0.0 才有此block,必须升级到此版本;
- 个别项目业务侧也实现此block,出现覆盖问题;
- 个别项目没有使用司内的网络库,独立封装AFNetworking,实监控系统现网络请求。因此没法监控到;
方案三:hook AFNetw监控apporking 的实现http 302 NSURLSessionTaskDelegate的代理方法
不管AFNetworking是 3.2.1及以下还是4.0.0及以上,不管项目组有没http 302有使用司内基础服务网络库,只有用AFNetworking库发起请求的。都将能实现网络指标监控。
优点
实现不必在基础服务网络库,可以在其他基础服务库实现,如司内的基础服务APM库,实现网络监控,并通过APM上报指标数苹果8plus据。
代码实现
AFHTTPSessionManager+APM.h
#import "AFHTTPSessionManager.h"
NS_ASSUME_NONNULL_BEGIN
@interface AFHTTPSessionManager (APM)
@end
NS_ASSUME_NONNULL_END
AFHTTPS监控眼essionManager+APM.m
#import "AFHTTPSessionManager+APM.h"
#import "APMNetworkMetricsModel.h"
#import <objc/runtime.h>
static inline void swizzling_exchangeMethod(Class clazz ,SEL originalSelector, SEL swizzledSelector){
Method originalMethod = class_getInstanceMethod(clazz, originalSelector);
Method swizzledMethod = class_getInstanceMethod(clazz, swizzledSelector);
BOOL success = class_addMethod(clazz, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(clazz, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@implementation AFHTTPSessionManager (APM)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
swizzling_exchangeMethod(self, @selector(URLSession:task:didFinishCollectingMetrics:), @selector(apm_URLSession:task:didFinishCollectingMetrics:));
});
}
- (void)apm_URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
{
[self apm_URLSession:session task:task didFinishCollectingMetrics:metrics];
NSLog(@"metrics: %@", metrics);
if (@available(iOS 10.0, *) &&
metrics.transactionMetrics.count)
{
[metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj == nil) return;
APMNetworkMetricsModel *model = [APMNetworkMetricsModel networkMetricsModelWithMetrics:obj];
// 对model进行处理,通过APM上报网络指标
// 这里可以通过业务侧动态设置采样率,不必全量上报
// 通过APM上报后也会执行到这里,需要有判断条件过滤,否则将无限循环
}];
}
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// AFNetworking 4.0.0 有实现didFinishCollectingMetrics
// 但是4.0.0以下没有实现,方法交换后会崩溃,所以在此动态添加方法
if (sel == @selector(URLSession:task:didFinishCollectingMetrics:)) {
Method method = class_getInstanceMethod(self, @selector(doMethod));
IMP imp = method_getImplementation(method);
const char *typeEncoding = method_getTypeEncoding(method);
return class_addMethod(self, sel, imp, typeEncoding);
}
return [super resolveInstanceMethod:sel];
}
- (void)doMethod
{
// 孤单的空实现
}
@end
APMNetworkMetricsModel.h
属性定义用下划线是为了方便映射表,忽略。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface APMNetworkMetricsModel : NSObject
/// 接口
@property (nonatomic, copy) NSString *path;
/// 请求的 URL 地址
@property (nonatomic, copy) NSString *req_url;
/// 请求参数
@property (nonatomic, copy) NSString *req_params;
/// 请求头
@property (nonatomic, strong) NSDictionary *req_headers;
/// 请求头流量
@property (nonatomic, assign) int64_t req_header_byte;
/// 请求体流量
@property (nonatomic, assign) int64_t req_body_byte;
/// 响应头
@property (nonatomic, strong) NSDictionary *res_headers;
/// 响应码
@property (nonatomic, assign) NSInteger status_code;
/// 响应头流量
@property (nonatomic, assign) int64_t res_header_byte;
/// 响应体流量
@property (nonatomic, assign) int64_t res_body_byte;
/// HTTP 方法
@property (nonatomic, copy) NSString *http_method;
/// 协议名
@property (nonatomic, copy) NSString *protocol_name;
/// 是否使用代理
@property (nonatomic, assign) BOOL proxy_connection;
/// 是否蜂窝连接
@property (nonatomic, assign) BOOL cellular;
/// 本地 ip
@property (nonatomic, copy) NSString *local_ip;
/// 本地端口
@property (nonatomic, assign) NSInteger local_port;
/// 远端 ip
@property (nonatomic, copy) NSString *remote_ip;
/// 远端端口
@property (nonatomic, assign) NSInteger remote_port;
#pragma mark - cost time
/// DNS 解析耗时
@property (nonatomic, assign) int64_t dns_time;
/// TCP 连接耗时
@property (nonatomic, assign) int64_t tcp_time;
/// SSL 握手耗时
@property (nonatomic, assign) int64_t ssl_time;
/// Request 请求耗时
@property (nonatomic, assign) int64_t req_time;
/// Response 响应耗时
@property (nonatomic, assign) int64_t res_time;
/// 请求到响应总耗时
@property (nonatomic, assign) int64_t req_total_time;
@end
@class NSURLSessionTaskTransactionMetrics;
@interface APMNetworkMetricsModel (Helper)
+ (instancetype)networkMetricsModelWithMetrics:(NSURLSessionTaskTransactionMetrics *)metrics;
@end
NS_ASSUME_NONNULL_END
APMNetworkMetricsModel.m
#import "APMNetworkMetricsModel.h"
@implementation APMNetworkMetricsModel
@end
@implementation APMNetworkMetricsModel (Helper)
+ (instancetype)networkMetricsModelWithMetrics:(NSURLSessionTaskTransactionMetrics *)metrics
{
if (metrics.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad)
{
APMNetworkMetricsModel *model = [[APMNetworkMetricsModel alloc] init];
// 需要在基础服务网络库对header设置接口名
model.path = metrics.request.allHTTPHeaderFields[@"path"];
model.req_url = [metrics.request.URL absoluteString];
model.req_params = [metrics.request.URL parameterString];
model.req_headers = metrics.request.allHTTPHeaderFields;
if (@available(iOS 13.0, *)) {
model.req_header_byte = metrics.countOfRequestHeaderBytesSent;
model.req_body_byte = metrics.countOfRequestBodyBytesSent;
model.res_header_byte = metrics.countOfResponseHeaderBytesReceived;
model.res_body_byte = metrics.countOfResponseBodyBytesReceived;
}
if (@available(iOS 13.0, *)) {
model.local_ip = metrics.localAddress;
model.local_port = metrics.localPort.integerValue;
model.remote_ip = metrics.remoteAddress;
model.remote_port = metrics.remotePort.integerValue;
}
if (@available(iOS 13.0, *)) {
model.cellular = metrics.cellular;
}
NSHTTPURLResponse *response = (NSHTTPURLResponse *)metrics.response;
if ([response isKindOfClass:NSHTTPURLResponse.class])
{
model.res_headers = response.allHeaderFields;
model.status_code = response.statusCode;
}
model.http_method = metrics.request.HTTPMethod;
model.protocol_name = metrics.networkProtocolName;
model.proxy_connection = metrics.proxyConnection;
if (metrics.domainLookupStartDate &&
metrics.domainLookupEndDate)
{
model.dns_time = ceil([metrics.domainLookupEndDate timeIntervalSinceDate:metrics.domainLookupStartDate] * 1000);
}
if (metrics.connectStartDate &&
metrics.connectEndDate)
{
model.tcp_time = ceil([metrics.connectEndDate timeIntervalSinceDate:metrics.connectStartDate] * 1000);
}
if (metrics.secureConnectionStartDate &&
metrics.secureConnectionEndDate)
{
model.ssl_time = ceil([metrics.secureConnectionEndDate timeIntervalSinceDate:metrics.secureConnectionStartDate] * 1000);
}
if (metrics.requestStartDate &&
metrics.requestEndDate)
{
model.req_time = ceil([metrics.requestEndDate timeIntervalSinceDate:metrics.requestStartDate] * 1000);
}
if (metrics.responseStartDate &&
metrics.responseEndDate)
{
model.res_time = ceil([metrics.responseEndDate timeIntervalSinceDate:metrics.responseStartDate] * 1000);
}
if (metrics.fetchStartDate &&
metrics.responseEndDate)
{
model.req_total_time = ceil([metrics.responseEndDate timeIntervalSinceDate:metrics.fetchStartDate] * 1000);
}
return model;
}
return nil;
}
@end