Source/NSURLSessionTaskMetrics+TNLAdditions.m (328 lines of code) (raw):
//
// NSURLSessionTaskMetrics+TNLAdditions.m
// TwitterNetworkLayer
//
// Created on 7/25/16.
// Copyright © 2020 Twitter. All rights reserved.
//
#import "NSURLSessionTaskMetrics+TNLAdditions.h"
#import "TNL_Project.h"
NS_ASSUME_NONNULL_BEGIN
@implementation NSURLSessionTaskMetrics (TNLAdditions)
- (NSString *)tnl_detailedDescription
{
NSMutableString *string = [[NSMutableString alloc] init];
[string appendFormat:@"<%@ %p: redirectCount=%tu, taskInterval=%.3fs, transactionMetrics=[", NSStringFromClass([self class]), self, self.redirectCount, self.taskInterval.duration];
for (NSURLSessionTaskTransactionMetrics *transactionMetrics in self.transactionMetrics) {
NSDictionary *d = [transactionMetrics tnl_dictionaryValue];
[string appendFormat:@"\n%@,", d];
}
[string appendString:@"\n]>"];
return string;
}
- (NSDictionary<NSString *, id> *)tnl_dictionaryValue
{
NSMutableDictionary<NSString *, id> *taskMetrics = [[NSMutableDictionary alloc] init];
taskMetrics[@"redirectCount"] = @(self.redirectCount);
taskMetrics[@"taskInterval"] = self.taskInterval;
taskMetrics[@"taskDuration"] = @(self.taskInterval.duration);
NSMutableArray<NSDictionary *> *transactionMetricsArray = [[NSMutableArray alloc] initWithCapacity:self.transactionMetrics.count];
for (NSURLSessionTaskTransactionMetrics *transactionMetrics in self.transactionMetrics) {
[transactionMetricsArray addObject:[transactionMetrics tnl_dictionaryValue]];
}
taskMetrics[@"transactionMetrics"] = transactionMetricsArray;
return taskMetrics;
}
@end
@implementation NSURLSessionTaskTransactionMetrics (TNLAdditions)
- (NSDictionary<NSString *, id> *)tnl_dictionaryValue
{
return [self _tnl_dictionaryValue:NO];
}
- (NSDictionary<NSString *, id> *)tnl_dictionaryDescription
{
return [self _tnl_dictionaryValue:YES];
}
- (NSMutableDictionary<NSString *, id> *)_tnl_dictionaryValue:(BOOL)sanitizeForSerialization TNL_OBJC_DIRECT
{
NSMutableDictionary<NSString *, id> *d = [self _tnl_metadata:sanitizeForSerialization];
#define APPLY_VALUE(key, value) \
do { \
id valueObj = (value); \
if (valueObj) { \
d[(key)] = valueObj; \
} \
} while (NO)
APPLY_VALUE(@"dns", [self tnl_domainLookupDuration]);
APPLY_VALUE(@"connect", [self tnl_connectDuration]);
APPLY_VALUE(@"tcp", [self tnl_transportConnectionDuration]);
APPLY_VALUE(@"secure", [self tnl_secureConnectionDuration]);
APPLY_VALUE(@"request", [self tnl_requestSendDuration]);
APPLY_VALUE(@"server", [self tnl_serverTimeDuration]);
APPLY_VALUE(@"response", [self tnl_responseReceiveDuration]);
APPLY_VALUE(@"total", [self tnl_totalDuration]);
d[@"statusCode"] = @([(NSHTTPURLResponse *)self.response statusCode]);
if (sanitizeForSerialization) {
APPLY_VALUE(@"URL", self.request.URL.absoluteString);
} else {
APPLY_VALUE(@"URL", self.request.URL);
}
#undef APPLY_VALUE
return d;
}
- (NSDictionary<NSString *, id> *)tnl_medadata
{
return [self _tnl_metadata:NO];
}
- (NSMutableDictionary<NSString *, id> *)_tnl_metadata:(BOOL)sanitizeForSerialization TNL_OBJC_DIRECT
{
NSMutableDictionary<NSString *, id> *d = [[NSMutableDictionary alloc] init];
#define APPLY_VALUE(key, value) \
do { \
id valueObj = (value); \
if (valueObj) { \
d[(key)] = valueObj; \
} \
} while (NO)
APPLY_VALUE(@"protocol", self.networkProtocolName);
d[@"load"] = self.tnl_resourceFetchTypeDebugString;
d[@"newConnection"] = @(!self.reusedConnection);
d[@"proxy"] = @(self.proxyConnection);
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
#define TNL_CAN_LOG_NEW_METRICS 1
#elif TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500
#define TNL_CAN_LOG_NEW_METRICS 1
#endif
#if TNL_CAN_LOG_NEW_METRICS
if (tnl_available_ios_13) {
d[@"header_tx"] = @(self.countOfRequestHeaderBytesSent);
d[@"body_tx"] = @(self.countOfRequestBodyBytesSent);
d[@"body_bytes"] = @(self.countOfRequestBodyBytesBeforeEncoding);
d[@"header_rx"] = @(self.countOfResponseHeaderBytesReceived);
d[@"body_rx"] = @(self.countOfResponseBodyBytesReceived);
d[@"body_bytes"] = @(self.countOfResponseBodyBytesAfterDecoding);
APPLY_VALUE(@"localAddress", self.localAddress);
APPLY_VALUE(@"localPort", self.localPort);
APPLY_VALUE(@"remoteAddress", self.remoteAddress);
APPLY_VALUE(@"remotePort", self.remotePort);
APPLY_VALUE(@"tlsVersion", self.negotiatedTLSProtocolVersion);
APPLY_VALUE(@"tlsCSuite", self.negotiatedTLSCipherSuite);
d[@"cellular"] = @(self.isCellular);
d[@"expensive"] = @(self.isExpensive);
d[@"constrained"] = @(self.isConstrained);
d[@"multipath"] = @(self.isMultipath);
}
#endif
#undef APPLY_VALUE
return d;
}
- (NSString *)tnl_resourceFetchTypeDebugString
{
switch (self.resourceFetchType) {
case NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad:
return @"network";
case NSURLSessionTaskMetricsResourceFetchTypeServerPush:
return @"push";
case NSURLSessionTaskMetricsResourceFetchTypeLocalCache:
return @"cache";
default:
return @"unknown";
}
}
- (NSString *)tnl_timingDescription
{
NSDate *earliestDate = self.tnl_earliestDate;
if (!earliestDate) {
return @"<>";
}
NSDate *previousDate = earliestDate;
NSDate *currentDate = nil;
NSTimeInterval delta = 0;
NSMutableString *timing = [[NSMutableString alloc] init];
[timing appendString:@"<"];
#define APPEND_DATE(dateName) \
do { \
currentDate = [self dateName##Date ]; \
if (currentDate) { \
if (timing.length > 1) { \
[timing appendString:@"|"]; \
} \
delta = [currentDate timeIntervalSinceDate:previousDate]; \
[timing appendFormat:@" (%c%li) %@ ", (delta < 0) ? '-' : '+', labs((long)(delta * 1000.0)), @"" #dateName ]; \
previousDate = currentDate; \
} \
} while (0)
APPEND_DATE(fetchStart);
APPEND_DATE(domainLookupStart);
APPEND_DATE(domainLookupEnd);
APPEND_DATE(connectStart);
APPEND_DATE(secureConnectionStart);
APPEND_DATE(secureConnectionEnd);
APPEND_DATE(connectEnd);
APPEND_DATE(requestStart);
APPEND_DATE(requestEnd);
APPEND_DATE(responseStart);
APPEND_DATE(responseEnd);
#undef APPEND_DATE
[timing appendString:@">"];
return [timing copy];
}
- (nullable NSDate *)tnl_earliestDate
{
NSDate *earliest = self.fetchStartDate;
#define EARLIER(date) \
do { \
NSDate *__d = [self date ]; \
if (__d) { \
earliest = (earliest) ? [earliest earlierDate:__d] : __d; \
} \
} while (0)
EARLIER(domainLookupStartDate);
EARLIER(connectStartDate);
EARLIER(secureConnectionStartDate);
EARLIER(requestStartDate);
EARLIER(responseStartDate);
#undef EARLIER
return earliest;
}
- (nullable NSDate *)tnl_latestDate
{
NSDate *latest = self.responseEndDate;
#define LATER(date) \
do { \
NSDate *__d = [self date ]; \
if (__d) { \
latest = (latest) ? [latest laterDate:__d] : __d; \
} \
} while (0)
LATER(responseStartDate);
LATER(requestEndDate);
LATER(requestStartDate);
LATER(connectEndDate);
LATER(secureConnectionEndDate);
LATER(secureConnectionStartDate);
LATER(connectStartDate);
LATER(domainLookupEndDate);
LATER(domainLookupStartDate);
LATER(fetchStartDate);
#undef LATER
return latest;
}
- (NSTimeInterval)tnl_knownDuration
{
NSDate *earliestDate = self.tnl_earliestDate;
NSDate *latestDate = self.tnl_latestDate;
if (!earliestDate || !latestDate) {
return 0;
}
return [latestDate timeIntervalSinceDate:earliestDate];
}
- (nullable NSDate *)tnl_transportConnectionStartDate
{
return self.connectStartDate;
}
- (nullable NSDate *)tnl_transportConnectionEndDate
{
return self.secureConnectionStartDate ?: self.connectEndDate;
}
- (nullable NSNumber *)tnl_domainLookupDuration
{
NSDate *endDate = self.domainLookupEndDate;
if (!endDate) {
return nil;
}
NSDate *startDate = self.domainLookupStartDate;
TNLAssert(startDate != nil);
return @([endDate timeIntervalSinceDate:startDate]);
}
- (nullable NSNumber *)tnl_connectDuration
{
NSDate *endDate = self.connectEndDate;
if (!endDate) {
return nil;
}
NSDate *startDate = self.connectStartDate;
TNLAssert(startDate != nil);
return @([endDate timeIntervalSinceDate:startDate]);
}
- (nullable NSNumber *)tnl_transportConnectionDuration
{
NSDate *startDate = self.tnl_transportConnectionStartDate;
if (!startDate) {
return nil;
}
NSDate *endDate = self.tnl_transportConnectionEndDate;
if (!endDate) {
return nil;
}
return @([endDate timeIntervalSinceDate:startDate]);
}
- (nullable NSNumber *)tnl_secureConnectionDuration
{
NSDate *endDate = self.secureConnectionEndDate;
if (!endDate) {
return nil;
}
NSDate *startDate = self.secureConnectionStartDate;
TNLAssert(startDate != nil);
return @([endDate timeIntervalSinceDate:startDate]);
}
- (nullable NSNumber *)tnl_requestSendDuration
{
NSDate *endDate = self.requestEndDate;
if (!endDate) {
return nil;
}
NSDate *startDate = self.requestStartDate;
TNLAssert(startDate != nil);
return @([endDate timeIntervalSinceDate:startDate]);
}
- (nullable NSNumber *)tnl_serverTimeDuration
{
NSDate *requestEndDate = self.requestEndDate;
if (!requestEndDate) {
return nil;
}
NSDate *responseStartDate = self.responseStartDate;
if (!responseStartDate) {
return nil;
}
const NSTimeInterval duration = [responseStartDate timeIntervalSinceDate:requestEndDate];
if (duration < 0.0) {
// response started BEFORE the request ended...strange...
return nil;
}
return @(duration);
}
- (nullable NSNumber *)tnl_responseReceiveDuration
{
NSDate *endDate = self.responseEndDate;
if (!endDate) {
return nil;
}
NSDate *startDate = self.responseStartDate;
TNLAssert(startDate != nil);
return @([endDate timeIntervalSinceDate:startDate]);
}
- (nullable NSNumber *)tnl_totalDuration
{
NSDate *fetchStartDate = self.fetchStartDate;
if (!fetchStartDate) {
return nil;
}
NSDate *responseEndDate = self.responseEndDate;
if (!responseEndDate) {
return nil;
}
return @([responseEndDate timeIntervalSinceDate:fetchStartDate]);
}
- (nullable NSNumber *)tnl_secureConnectionDurationExt
{
NSDate *connectEndDate = self.connectEndDate;
if (!connectEndDate) {
return self.tnl_secureConnectionDuration;
}
NSDate *startDate = self.secureConnectionStartDate;
if (!startDate) {
return nil;
}
NSDate *endDate = [self.secureConnectionEndDate laterDate:connectEndDate];
if (!endDate) {
return nil;
}
return @([endDate timeIntervalSinceDate:startDate]);
}
@end
NS_ASSUME_NONNULL_END