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
评论(0)