Skip to content

Commit

Permalink
Support metrics collection. (#187) (#188)
Browse files Browse the repository at this point in the history
This adds support to handle the
`-URLSession:task:didFinishCollectingMetrics:` delegate method in the
`NSURLSessionTaskDelegate protocol`. A new callback block type,
`GTMSessionFetcherMetricsCollectionBlock`, is also added.

This feature is enabled when compiled for iOS 10+, macOS 10.12+, Mac
Catalyst 13.0+, tvOS 10.0+, or watchOS 3.0+.
  • Loading branch information
lukhnos authored Mar 25, 2020
1 parent 0cb2890 commit c879a38
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Source/GTMSessionFetcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,9 @@ typedef void (^GTMSessionFetcherRetryBlock)(BOOL suggestedWillRetry,
NSError * GTM_NULLABLE_TYPE error,
GTMSessionFetcherRetryResponse response);

API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0))
typedef void (^GTMSessionFetcherMetricsCollectionBlock)(NSURLSessionTaskMetrics *metrics);

typedef void (^GTMSessionFetcherTestResponse)(NSHTTPURLResponse * GTM_NULLABLE_TYPE response,
NSData * GTM_NULLABLE_TYPE data,
NSError * GTM_NULLABLE_TYPE error);
Expand Down Expand Up @@ -996,6 +999,13 @@ NSData * GTM_NULLABLE_TYPE GTMDataFromInputStream(NSInputStream *inputStream, NS
// See comments at the top of this file.
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock;

// The optional block for collecting the metrics of the present session.
//
// This is called on the callback queue.
@property(atomic, copy, GTM_NULLABLE)
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE(
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0));

// Retry intervals must be strictly less than maxRetryInterval, else
// they will be limited to maxRetryInterval and no further retries will
// be attempted. Setting maxRetryInterval to 0.0 will reset it to the
Expand Down
19 changes: 19 additions & 0 deletions Source/GTMSessionFetcher.m
Original file line number Diff line number Diff line change
Expand Up @@ -1795,6 +1795,9 @@ - (void)releaseCallbacks {
self.retryBlock = nil;
self.testBlock = nil;
self.resumeDataBlock = nil;
if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
self.metricsCollectionBlock = nil;
}
}

- (void)forgetSessionIdentifierForFetcher {
Expand Down Expand Up @@ -2853,6 +2856,21 @@ - (void)URLSession:(NSURLSession *)session
}];
}

- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock = _metricsCollectionBlock;
if (metricsCollectionBlock) {
[self invokeOnCallbackQueueUnlessStopped:^{
metricsCollectionBlock(metrics);
}];
}
}
}

#if TARGET_OS_IPHONE
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSessionDidFinishEventsForBackgroundURLSession:%@",
Expand Down Expand Up @@ -3462,6 +3480,7 @@ + (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler
sendProgressBlock = _sendProgressBlock,
willCacheURLResponseBlock = _willCacheURLResponseBlock,
retryBlock = _retryBlock,
metricsCollectionBlock = _metricsCollectionBlock,
retryFactor = _retryFactor,
allowedInsecureSchemes = _allowedInsecureSchemes,
allowLocalhostRequest = _allowLocalhostRequest,
Expand Down
3 changes: 3 additions & 0 deletions Source/GTMSessionFetcherService.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ extern NSString *const kGTMSessionFetcherServiceSessionKey;
@property(atomic, assign) NSTimeInterval maxRetryInterval;
@property(atomic, assign) NSTimeInterval minRetryInterval;
@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties;
@property(atomic, copy, GTM_NULLABLE)
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE(
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0));

#if GTM_BACKGROUND_TASK_FETCHING
@property(atomic, assign) BOOL skipBackgroundTask;
Expand Down
12 changes: 12 additions & 0 deletions Source/GTMSessionFetcherService.m
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ @implementation GTMSessionFetcherService {
retryBlock = _retryBlock,
maxRetryInterval = _maxRetryInterval,
minRetryInterval = _minRetryInterval,
metricsCollectionBlock = _metricsCollectionBlock,
properties = _properties,
unusedSessionTimeout = _unusedSessionTimeout,
testBlock = _testBlock;
Expand Down Expand Up @@ -186,6 +187,9 @@ - (id)fetcherWithRequest:(NSURLRequest *)request
fetcher.retryBlock = self.retryBlock;
fetcher.maxRetryInterval = self.maxRetryInterval;
fetcher.minRetryInterval = self.minRetryInterval;
if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
fetcher.metricsCollectionBlock = self.metricsCollectionBlock;
}
fetcher.properties = self.properties;
fetcher.service = self;
if (self.cookieStorageMethod >= 0) {
Expand Down Expand Up @@ -1281,6 +1285,14 @@ - (void)URLSession:(NSURLSession *)session
didCompleteWithError:error];
}

- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
[fetcher URLSession:session task:task didFinishCollectingMetrics:metrics];
}

// NSURLSessionDataDelegate protocol methods.

- (void)URLSession:(NSURLSession *)session
Expand Down
117 changes: 117 additions & 0 deletions Source/UnitTests/GTMSessionFetcherFetchingTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ - (void)assertCallbacksReleasedForFetcher:(GTMSessionFetcher *)fetcher {
XCTAssertNil(fetcher.downloadProgressBlock);
XCTAssertNil(fetcher.willCacheURLResponseBlock);
XCTAssertNil(fetcher.retryBlock);
if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
XCTAssertNil(fetcher.metricsCollectionBlock);
}
XCTAssertNil(fetcher.testBlock);

if ([fetcher isKindOfClass:[GTMSessionUploadFetcher class]]) {
Expand Down Expand Up @@ -1849,6 +1852,120 @@ - (void)testInsecureRequests_WithoutFetcherService {
[self testInsecureRequests];
}

- (void)testCollectingMetrics_WithSuccessfulFetch API_AVAILABLE(ios(10.0), macosx(10.12),
tvos(10.0), watchos(3.0)) {
if (!_isServerRunning) return;

NSString *localURLString = [self localURLStringToTestFileName:kGTMGettysburgFileName];
GTMSessionFetcher *fetcher = [self fetcherWithURLString:localURLString];
__block NSURLSessionTaskMetrics *collectedMetrics = nil;

fetcher.metricsCollectionBlock = ^(NSURLSessionTaskMetrics *_Nonnull metrics) {
collectedMetrics = metrics;
};

[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
[self assertSuccessfulGettysburgFetchWithFetcher:fetcher data:data error:error];
}];
XCTAssertTrue([fetcher waitForCompletionWithTimeout:_timeoutInterval], @"timed out");
[self assertCallbacksReleasedForFetcher:fetcher];

XCTAssertNotNil(collectedMetrics);
XCTAssertEqual(collectedMetrics.transactionMetrics.count, 1);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].fetchStartDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].connectStartDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].connectEndDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].requestStartDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].requestEndDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].responseStartDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].responseEndDate);
}

- (void)testCollectingMetrics_WithSuccessfulFetch_WithoutFetcherService API_AVAILABLE(
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
_fetcherService = nil;
[self testCollectingMetrics_WithSuccessfulFetch];
}

- (void)testCollectingMetrics_WithWrongFetch_FaildToConnect API_AVAILABLE(ios(10.0), macosx(10.12),
tvos(10.0),
watchos(3.0)) {
if (!_isServerRunning) return;

// Fetch a live, invalid URL
NSString *badURLString = @"http://localhost:86/";

GTMSessionFetcher *fetcher = [self fetcherWithURLString:badURLString];

__block NSURLSessionTaskMetrics *collectedMetrics = nil;
fetcher.metricsCollectionBlock = ^(NSURLSessionTaskMetrics *_Nonnull metrics) {
collectedMetrics = metrics;
};

[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
XCTAssertNotNil(error);
}];
XCTAssertTrue([fetcher waitForCompletionWithTimeout:_timeoutInterval], @"timed out");
[self assertCallbacksReleasedForFetcher:fetcher];

XCTAssertNotNil(collectedMetrics);
XCTAssertEqual(collectedMetrics.transactionMetrics.count, 1);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].fetchStartDate);

// Connetion not established, and therefore the following metrics do not exist.
XCTAssertNil(collectedMetrics.transactionMetrics[0].connectStartDate);
XCTAssertNil(collectedMetrics.transactionMetrics[0].connectEndDate);
XCTAssertNil(collectedMetrics.transactionMetrics[0].requestStartDate);
XCTAssertNil(collectedMetrics.transactionMetrics[0].requestEndDate);
XCTAssertNil(collectedMetrics.transactionMetrics[0].responseStartDate);
XCTAssertNil(collectedMetrics.transactionMetrics[0].responseEndDate);
}

- (void)testCollectingMetrics_WithWrongFetch_FaildToConnect_WithoutFetcherService API_AVAILABLE(
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
_fetcherService = nil;
[self testCollectingMetrics_WithWrongFetch_FaildToConnect];
}

- (void)testCollectingMetrics_WithWrongFetch_BadStatusCode API_AVAILABLE(ios(10.0), macosx(10.12),
tvos(10.0), watchos(3.0)) {
if (!_isServerRunning) return;

NSString *statusURLString = [self localURLStringToTestFileName:kGTMGettysburgFileName
parameters:@{@"status" : @"400"}];

GTMSessionFetcher *fetcher = [self fetcherWithURLString:statusURLString];

__block NSURLSessionTaskMetrics *collectedMetrics = nil;
fetcher.metricsCollectionBlock = ^(NSURLSessionTaskMetrics *_Nonnull metrics) {
collectedMetrics = metrics;
};

[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
XCTAssertNotNil(error);
}];
XCTAssertTrue([fetcher waitForCompletionWithTimeout:_timeoutInterval], @"timed out");
[self assertCallbacksReleasedForFetcher:fetcher];

XCTAssertNotNil(collectedMetrics);
XCTAssertEqual(collectedMetrics.transactionMetrics.count, 1);

// A 400 HTTP response is still a complete response, and therefore these metrics exist.
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].fetchStartDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].connectStartDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].connectEndDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].requestStartDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].requestEndDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].responseStartDate);
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].responseEndDate);
}

- (void)testCollectingMetrics_WithWrongFetch_BadStatusCode_WithoutFetcherService API_AVAILABLE(
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
_fetcherService = nil;
[self testCollectingMetrics_WithWrongFetch_BadStatusCode];
}

#pragma mark - TestBlock Tests

- (void)testFetcherTestBlock {
Expand Down
25 changes: 25 additions & 0 deletions Source/UnitTests/GTMSessionFetcherServiceTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -820,4 +820,29 @@ - (void)testDelegateDispatcherForFetcher {
[session invalidateAndCancel];
}

- (void)testFetcherUsingMetricsCollectionBlockFromFetcherService API_AVAILABLE(ios(10.0),
macosx(10.12),
tvos(10.0),
watchos(3.0)) {
if (!_isServerRunning) return;

__block NSURLSessionTaskMetrics *collectedMetrics = nil;

GTMSessionFetcherService *service = [[GTMSessionFetcherService alloc] init];
service.metricsCollectionBlock = ^(NSURLSessionTaskMetrics *_Nonnull metrics) {
collectedMetrics = metrics;
};

NSURL *fetchURL = [_testServer localURLForFile:kValidFileName];
GTMSessionFetcher *fetcher = [service fetcherWithURL:fetchURL];
[fetcher beginFetchWithCompletionHandler:^(NSData *fetchData, NSError *fetchError) {
XCTAssertNotNil(fetchData);
XCTAssertNil(fetchError);
}];

[service waitForCompletionOfAllFetchersWithTimeout:10];

XCTAssertNotNil(collectedMetrics);
}

@end

0 comments on commit c879a38

Please sign in to comment.