Skip to content

Commit c879a38

Browse files
authored
Support metrics collection. (#187) (#188)
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+.
1 parent 0cb2890 commit c879a38

File tree

6 files changed

+186
-0
lines changed

6 files changed

+186
-0
lines changed

Source/GTMSessionFetcher.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,9 @@ typedef void (^GTMSessionFetcherRetryBlock)(BOOL suggestedWillRetry,
560560
NSError * GTM_NULLABLE_TYPE error,
561561
GTMSessionFetcherRetryResponse response);
562562

563+
API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0))
564+
typedef void (^GTMSessionFetcherMetricsCollectionBlock)(NSURLSessionTaskMetrics *metrics);
565+
563566
typedef void (^GTMSessionFetcherTestResponse)(NSHTTPURLResponse * GTM_NULLABLE_TYPE response,
564567
NSData * GTM_NULLABLE_TYPE data,
565568
NSError * GTM_NULLABLE_TYPE error);
@@ -996,6 +999,13 @@ NSData * GTM_NULLABLE_TYPE GTMDataFromInputStream(NSInputStream *inputStream, NS
996999
// See comments at the top of this file.
9971000
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock;
9981001

1002+
// The optional block for collecting the metrics of the present session.
1003+
//
1004+
// This is called on the callback queue.
1005+
@property(atomic, copy, GTM_NULLABLE)
1006+
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE(
1007+
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0));
1008+
9991009
// Retry intervals must be strictly less than maxRetryInterval, else
10001010
// they will be limited to maxRetryInterval and no further retries will
10011011
// be attempted. Setting maxRetryInterval to 0.0 will reset it to the

Source/GTMSessionFetcher.m

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,6 +1795,9 @@ - (void)releaseCallbacks {
17951795
self.retryBlock = nil;
17961796
self.testBlock = nil;
17971797
self.resumeDataBlock = nil;
1798+
if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
1799+
self.metricsCollectionBlock = nil;
1800+
}
17981801
}
17991802

18001803
- (void)forgetSessionIdentifierForFetcher {
@@ -2853,6 +2856,21 @@ - (void)URLSession:(NSURLSession *)session
28532856
}];
28542857
}
28552858

2859+
- (void)URLSession:(NSURLSession *)session
2860+
task:(NSURLSessionTask *)task
2861+
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
2862+
API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
2863+
@synchronized(self) {
2864+
GTMSessionMonitorSynchronized(self);
2865+
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock = _metricsCollectionBlock;
2866+
if (metricsCollectionBlock) {
2867+
[self invokeOnCallbackQueueUnlessStopped:^{
2868+
metricsCollectionBlock(metrics);
2869+
}];
2870+
}
2871+
}
2872+
}
2873+
28562874
#if TARGET_OS_IPHONE
28572875
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
28582876
GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSessionDidFinishEventsForBackgroundURLSession:%@",
@@ -3462,6 +3480,7 @@ + (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler
34623480
sendProgressBlock = _sendProgressBlock,
34633481
willCacheURLResponseBlock = _willCacheURLResponseBlock,
34643482
retryBlock = _retryBlock,
3483+
metricsCollectionBlock = _metricsCollectionBlock,
34653484
retryFactor = _retryFactor,
34663485
allowedInsecureSchemes = _allowedInsecureSchemes,
34673486
allowLocalhostRequest = _allowLocalhostRequest,

Source/GTMSessionFetcherService.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ extern NSString *const kGTMSessionFetcherServiceSessionKey;
6363
@property(atomic, assign) NSTimeInterval maxRetryInterval;
6464
@property(atomic, assign) NSTimeInterval minRetryInterval;
6565
@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties;
66+
@property(atomic, copy, GTM_NULLABLE)
67+
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE(
68+
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0));
6669

6770
#if GTM_BACKGROUND_TASK_FETCHING
6871
@property(atomic, assign) BOOL skipBackgroundTask;

Source/GTMSessionFetcherService.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ @implementation GTMSessionFetcherService {
121121
retryBlock = _retryBlock,
122122
maxRetryInterval = _maxRetryInterval,
123123
minRetryInterval = _minRetryInterval,
124+
metricsCollectionBlock = _metricsCollectionBlock,
124125
properties = _properties,
125126
unusedSessionTimeout = _unusedSessionTimeout,
126127
testBlock = _testBlock;
@@ -186,6 +187,9 @@ - (id)fetcherWithRequest:(NSURLRequest *)request
186187
fetcher.retryBlock = self.retryBlock;
187188
fetcher.maxRetryInterval = self.maxRetryInterval;
188189
fetcher.minRetryInterval = self.minRetryInterval;
190+
if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
191+
fetcher.metricsCollectionBlock = self.metricsCollectionBlock;
192+
}
189193
fetcher.properties = self.properties;
190194
fetcher.service = self;
191195
if (self.cookieStorageMethod >= 0) {
@@ -1281,6 +1285,14 @@ - (void)URLSession:(NSURLSession *)session
12811285
didCompleteWithError:error];
12821286
}
12831287

1288+
- (void)URLSession:(NSURLSession *)session
1289+
task:(NSURLSessionTask *)task
1290+
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
1291+
API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
1292+
id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
1293+
[fetcher URLSession:session task:task didFinishCollectingMetrics:metrics];
1294+
}
1295+
12841296
// NSURLSessionDataDelegate protocol methods.
12851297

12861298
- (void)URLSession:(NSURLSession *)session

Source/UnitTests/GTMSessionFetcherFetchingTest.m

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ - (void)assertCallbacksReleasedForFetcher:(GTMSessionFetcher *)fetcher {
194194
XCTAssertNil(fetcher.downloadProgressBlock);
195195
XCTAssertNil(fetcher.willCacheURLResponseBlock);
196196
XCTAssertNil(fetcher.retryBlock);
197+
if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
198+
XCTAssertNil(fetcher.metricsCollectionBlock);
199+
}
197200
XCTAssertNil(fetcher.testBlock);
198201

199202
if ([fetcher isKindOfClass:[GTMSessionUploadFetcher class]]) {
@@ -1849,6 +1852,120 @@ - (void)testInsecureRequests_WithoutFetcherService {
18491852
[self testInsecureRequests];
18501853
}
18511854

1855+
- (void)testCollectingMetrics_WithSuccessfulFetch API_AVAILABLE(ios(10.0), macosx(10.12),
1856+
tvos(10.0), watchos(3.0)) {
1857+
if (!_isServerRunning) return;
1858+
1859+
NSString *localURLString = [self localURLStringToTestFileName:kGTMGettysburgFileName];
1860+
GTMSessionFetcher *fetcher = [self fetcherWithURLString:localURLString];
1861+
__block NSURLSessionTaskMetrics *collectedMetrics = nil;
1862+
1863+
fetcher.metricsCollectionBlock = ^(NSURLSessionTaskMetrics *_Nonnull metrics) {
1864+
collectedMetrics = metrics;
1865+
};
1866+
1867+
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
1868+
[self assertSuccessfulGettysburgFetchWithFetcher:fetcher data:data error:error];
1869+
}];
1870+
XCTAssertTrue([fetcher waitForCompletionWithTimeout:_timeoutInterval], @"timed out");
1871+
[self assertCallbacksReleasedForFetcher:fetcher];
1872+
1873+
XCTAssertNotNil(collectedMetrics);
1874+
XCTAssertEqual(collectedMetrics.transactionMetrics.count, 1);
1875+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].fetchStartDate);
1876+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].connectStartDate);
1877+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].connectEndDate);
1878+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].requestStartDate);
1879+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].requestEndDate);
1880+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].responseStartDate);
1881+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].responseEndDate);
1882+
}
1883+
1884+
- (void)testCollectingMetrics_WithSuccessfulFetch_WithoutFetcherService API_AVAILABLE(
1885+
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
1886+
_fetcherService = nil;
1887+
[self testCollectingMetrics_WithSuccessfulFetch];
1888+
}
1889+
1890+
- (void)testCollectingMetrics_WithWrongFetch_FaildToConnect API_AVAILABLE(ios(10.0), macosx(10.12),
1891+
tvos(10.0),
1892+
watchos(3.0)) {
1893+
if (!_isServerRunning) return;
1894+
1895+
// Fetch a live, invalid URL
1896+
NSString *badURLString = @"http://localhost:86/";
1897+
1898+
GTMSessionFetcher *fetcher = [self fetcherWithURLString:badURLString];
1899+
1900+
__block NSURLSessionTaskMetrics *collectedMetrics = nil;
1901+
fetcher.metricsCollectionBlock = ^(NSURLSessionTaskMetrics *_Nonnull metrics) {
1902+
collectedMetrics = metrics;
1903+
};
1904+
1905+
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
1906+
XCTAssertNotNil(error);
1907+
}];
1908+
XCTAssertTrue([fetcher waitForCompletionWithTimeout:_timeoutInterval], @"timed out");
1909+
[self assertCallbacksReleasedForFetcher:fetcher];
1910+
1911+
XCTAssertNotNil(collectedMetrics);
1912+
XCTAssertEqual(collectedMetrics.transactionMetrics.count, 1);
1913+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].fetchStartDate);
1914+
1915+
// Connetion not established, and therefore the following metrics do not exist.
1916+
XCTAssertNil(collectedMetrics.transactionMetrics[0].connectStartDate);
1917+
XCTAssertNil(collectedMetrics.transactionMetrics[0].connectEndDate);
1918+
XCTAssertNil(collectedMetrics.transactionMetrics[0].requestStartDate);
1919+
XCTAssertNil(collectedMetrics.transactionMetrics[0].requestEndDate);
1920+
XCTAssertNil(collectedMetrics.transactionMetrics[0].responseStartDate);
1921+
XCTAssertNil(collectedMetrics.transactionMetrics[0].responseEndDate);
1922+
}
1923+
1924+
- (void)testCollectingMetrics_WithWrongFetch_FaildToConnect_WithoutFetcherService API_AVAILABLE(
1925+
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
1926+
_fetcherService = nil;
1927+
[self testCollectingMetrics_WithWrongFetch_FaildToConnect];
1928+
}
1929+
1930+
- (void)testCollectingMetrics_WithWrongFetch_BadStatusCode API_AVAILABLE(ios(10.0), macosx(10.12),
1931+
tvos(10.0), watchos(3.0)) {
1932+
if (!_isServerRunning) return;
1933+
1934+
NSString *statusURLString = [self localURLStringToTestFileName:kGTMGettysburgFileName
1935+
parameters:@{@"status" : @"400"}];
1936+
1937+
GTMSessionFetcher *fetcher = [self fetcherWithURLString:statusURLString];
1938+
1939+
__block NSURLSessionTaskMetrics *collectedMetrics = nil;
1940+
fetcher.metricsCollectionBlock = ^(NSURLSessionTaskMetrics *_Nonnull metrics) {
1941+
collectedMetrics = metrics;
1942+
};
1943+
1944+
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
1945+
XCTAssertNotNil(error);
1946+
}];
1947+
XCTAssertTrue([fetcher waitForCompletionWithTimeout:_timeoutInterval], @"timed out");
1948+
[self assertCallbacksReleasedForFetcher:fetcher];
1949+
1950+
XCTAssertNotNil(collectedMetrics);
1951+
XCTAssertEqual(collectedMetrics.transactionMetrics.count, 1);
1952+
1953+
// A 400 HTTP response is still a complete response, and therefore these metrics exist.
1954+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].fetchStartDate);
1955+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].connectStartDate);
1956+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].connectEndDate);
1957+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].requestStartDate);
1958+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].requestEndDate);
1959+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].responseStartDate);
1960+
XCTAssertNotNil(collectedMetrics.transactionMetrics[0].responseEndDate);
1961+
}
1962+
1963+
- (void)testCollectingMetrics_WithWrongFetch_BadStatusCode_WithoutFetcherService API_AVAILABLE(
1964+
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
1965+
_fetcherService = nil;
1966+
[self testCollectingMetrics_WithWrongFetch_BadStatusCode];
1967+
}
1968+
18521969
#pragma mark - TestBlock Tests
18531970

18541971
- (void)testFetcherTestBlock {

Source/UnitTests/GTMSessionFetcherServiceTest.m

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,4 +820,29 @@ - (void)testDelegateDispatcherForFetcher {
820820
[session invalidateAndCancel];
821821
}
822822

823+
- (void)testFetcherUsingMetricsCollectionBlockFromFetcherService API_AVAILABLE(ios(10.0),
824+
macosx(10.12),
825+
tvos(10.0),
826+
watchos(3.0)) {
827+
if (!_isServerRunning) return;
828+
829+
__block NSURLSessionTaskMetrics *collectedMetrics = nil;
830+
831+
GTMSessionFetcherService *service = [[GTMSessionFetcherService alloc] init];
832+
service.metricsCollectionBlock = ^(NSURLSessionTaskMetrics *_Nonnull metrics) {
833+
collectedMetrics = metrics;
834+
};
835+
836+
NSURL *fetchURL = [_testServer localURLForFile:kValidFileName];
837+
GTMSessionFetcher *fetcher = [service fetcherWithURL:fetchURL];
838+
[fetcher beginFetchWithCompletionHandler:^(NSData *fetchData, NSError *fetchError) {
839+
XCTAssertNotNil(fetchData);
840+
XCTAssertNil(fetchError);
841+
}];
842+
843+
[service waitForCompletionOfAllFetchersWithTimeout:10];
844+
845+
XCTAssertNotNil(collectedMetrics);
846+
}
847+
823848
@end

0 commit comments

Comments
 (0)