From 2b45eaa4427bacb43514a783384efcc9fbd32de0 Mon Sep 17 00:00:00 2001 From: Andrey Kashcheev Date: Thu, 18 Jun 2026 17:28:27 +0200 Subject: [PATCH] Network diagnostic statistic for iOS Previously, only curl-based network could collect it Relates-To: DATASDK-98 Signed-off-by: Andrey Kashcheev --- .../src/http/ios/OLPHttpClient.mm | 27 +++- .../src/http/ios/OLPHttpTask+Internal.h | 8 +- olp-cpp-sdk-core/src/http/ios/OLPHttpTask.mm | 119 +++++++++++++++++- .../src/http/ios/OLPNetworkIOS.mm | 23 ++-- 4 files changed, 166 insertions(+), 11 deletions(-) diff --git a/olp-cpp-sdk-core/src/http/ios/OLPHttpClient.mm b/olp-cpp-sdk-core/src/http/ios/OLPHttpClient.mm index 9e7a4bdb7..9388eaafe 100644 --- a/olp-cpp-sdk-core/src/http/ios/OLPHttpClient.mm +++ b/olp-cpp-sdk-core/src/http/ios/OLPHttpClient.mm @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2025 HERE Europe B.V. + * Copyright (C) 2019-2026 HERE Europe B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -442,6 +442,31 @@ - (void)URLSession:(NSURLSession*)session } } +- (void)URLSession:(NSURLSession*)session + task:(NSURLSessionTask*)task + didFinishCollectingMetrics:(NSURLSessionTaskMetrics*)metrics { + if (!self.sharedUrlSession) { + OLP_SDK_LOG_WARNING_F( + kLogTag, + "didFinishCollectingMetrics failed - invalid session, task_id=%u", + (unsigned int)task.taskIdentifier); + return; + } + + OLP_SDK_LOG_TRACE_F( + kLogTag, + "didFinishCollectingMetrics, session=%p, task_id=%u, dataTask=%p", + (__bridge void*)session, (unsigned int)task.taskIdentifier, + (__bridge void*)task); + + @autoreleasepool { + OLPHttpTask* httpTask = [self taskWithTaskDescription:task.taskDescription]; + if ([httpTask isValid]) { + [httpTask didCollectMetrics:metrics]; + } + } +} + - (void)URLSession:(NSURLSession*)session task:(NSURLSessionTask*)dataTask didReceiveChallenge:(NSURLAuthenticationChallenge*)challenge diff --git a/olp-cpp-sdk-core/src/http/ios/OLPHttpTask+Internal.h b/olp-cpp-sdk-core/src/http/ios/OLPHttpTask+Internal.h index d33ad0d7e..01f7251a8 100644 --- a/olp-cpp-sdk-core/src/http/ios/OLPHttpTask+Internal.h +++ b/olp-cpp-sdk-core/src/http/ios/OLPHttpTask+Internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2023 HERE Europe B.V. + * Copyright (C) 2019-2026 HERE Europe B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ #import +#include + /** @brief Internal category, which extends OLPHttpTask with internal methods, which shouldn't be exposed as public API. @@ -33,6 +35,10 @@ - (void)didReceiveData:(NSData*)data withWholeData:(bool)wholeData; +- (void)didCollectMetrics:(NSURLSessionTaskMetrics*)metrics; + +- (BOOL)getDiagnostics:(olp::http::Diagnostics&)diagnostics; + - (void)didCompleteWithError:(NSError*)error; - (NSString*)createTaskDescription; diff --git a/olp-cpp-sdk-core/src/http/ios/OLPHttpTask.mm b/olp-cpp-sdk-core/src/http/ios/OLPHttpTask.mm index 0bb9fa985..6fe1a057b 100644 --- a/olp-cpp-sdk-core/src/http/ios/OLPHttpTask.mm +++ b/olp-cpp-sdk-core/src/http/ios/OLPHttpTask.mm @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2024 HERE Europe B.V. + * Copyright (C) 2019-2026 HERE Europe B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,14 +19,106 @@ #import "OLPHttpTask+Internal.h" +#include + #include #include +#include #import "OLPHttpClient+Internal.h" #import "OLPNetworkConstants.h" namespace { constexpr auto kLogTag = "OLPHttpTask"; + +constexpr uint64_t kMicrosecondsInSecond = 1000000u; + +uint64_t DurationInMicroseconds(NSDate* start, NSDate* end) { + if (!start || !end) { + return 0u; + } + + const NSTimeInterval time_interval = [end timeIntervalSinceDate:start]; + if (time_interval <= 0.0) { + return 0u; + } + + return static_cast(time_interval * kMicrosecondsInSecond); +} + +void AddTiming(olp::http::Diagnostics& diagnostics, + olp::http::Diagnostics::Timings timing, + const uint64_t time_in_microseconds) { + if (time_in_microseconds == 0u) { + return; + } + + const auto previous_duration = + static_cast(diagnostics.timings[timing].count()); + const auto updated_duration = previous_duration + time_in_microseconds; + const auto clamped_duration = + std::min(updated_duration, + static_cast(std::numeric_limits::max())); + + diagnostics.timings[timing] = olp::http::Diagnostics::MicroSeconds( + static_cast(clamped_duration)); + diagnostics.available_timings.set(timing); +} + +olp::http::Diagnostics BuildDiagnostics(NSURLSessionTaskMetrics* metrics) { + olp::http::Diagnostics diagnostics; + if (!metrics) { + return diagnostics; + } + + NSDate* previous_response_end = metrics.taskInterval.startDate; + for (NSURLSessionTaskTransactionMetrics* transaction in metrics + .transactionMetrics) { + AddTiming(diagnostics, olp::http::Diagnostics::Queue, + DurationInMicroseconds(previous_response_end, + transaction.fetchStartDate)); + + AddTiming(diagnostics, olp::http::Diagnostics::NameLookup, + DurationInMicroseconds(transaction.domainLookupStartDate, + transaction.domainLookupEndDate)); + + const auto connect_duration = DurationInMicroseconds( + transaction.connectStartDate, transaction.connectEndDate); + const auto tls_duration = + DurationInMicroseconds(transaction.secureConnectionStartDate, + transaction.secureConnectionEndDate); + if (connect_duration >= tls_duration) { + AddTiming(diagnostics, olp::http::Diagnostics::Connect, + connect_duration - tls_duration); + } else { + AddTiming(diagnostics, olp::http::Diagnostics::Connect, connect_duration); + } + + AddTiming(diagnostics, olp::http::Diagnostics::SSL_Handshake, tls_duration); + + AddTiming(diagnostics, olp::http::Diagnostics::Send, + DurationInMicroseconds(transaction.requestStartDate, + transaction.requestEndDate)); + + AddTiming(diagnostics, olp::http::Diagnostics::Wait, + DurationInMicroseconds(transaction.requestEndDate, + transaction.responseStartDate)); + + AddTiming(diagnostics, olp::http::Diagnostics::Receive, + DurationInMicroseconds(transaction.responseStartDate, + transaction.responseEndDate)); + + if (transaction.responseEndDate) { + previous_response_end = transaction.responseEndDate; + } + } + + AddTiming(diagnostics, olp::http::Diagnostics::Total, + DurationInMicroseconds(metrics.taskInterval.startDate, + metrics.taskInterval.endDate)); + + return diagnostics; +} } // namespace #pragma mark - OLPHttpTaskResponseData @@ -59,6 +151,7 @@ @implementation OLPHttpTask { uint64_t _headersSizeReceived; uint64_t _headersSizeSent; uint64_t _contentLength; + olp::porting::optional _diagnostics; } - (instancetype)initWithHttpClient:(OLPHttpClient*)client @@ -73,6 +166,7 @@ - (instancetype)initWithHttpClient:(OLPHttpClient*)client _headersSizeReceived = 0; _headersSizeSent = 0; _contentLength = 0; + _diagnostics = {}; _backgroundMode = false; } return self; @@ -105,6 +199,8 @@ - (OLPHttpTaskStatus)restart { _dataTask = nil; } + _diagnostics = {}; + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]]; request.timeoutInterval = self.connectionTimeout; @@ -264,6 +360,27 @@ - (void)didReceiveData:(NSData*)data withWholeData:(bool)wholeData { } } +- (void)didCollectMetrics:(NSURLSessionTaskMetrics*)metrics { + const auto diagnostics = BuildDiagnostics(metrics); + if (!diagnostics.available_timings.any()) { + return; + } + + @synchronized(self) { + _diagnostics = diagnostics; + } +} + +- (BOOL)getDiagnostics:(olp::http::Diagnostics&)diagnostics { + @synchronized(self) { + if (!_diagnostics) { + return NO; + } + diagnostics = *_diagnostics; + return YES; + } +} + - (NSString*)createTaskDescription { return [NSString stringWithFormat:@"%llu", self.requestId]; } diff --git a/olp-cpp-sdk-core/src/http/ios/OLPNetworkIOS.mm b/olp-cpp-sdk-core/src/http/ios/OLPNetworkIOS.mm index 6c4fec670..533b21a1c 100644 --- a/olp-cpp-sdk-core/src/http/ios/OLPNetworkIOS.mm +++ b/olp-cpp-sdk-core/src/http/ios/OLPNetworkIOS.mm @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2025 HERE Europe B.V. + * Copyright (C) 2019-2026 HERE Europe B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ #include "olp/core/logging/Log.h" #import "OLPHttpClient+Internal.h" -#import "OLPHttpTask.h" +#import "OLPHttpTask+Internal.h" #import "OLPNetworkConstants.h" namespace olp { @@ -335,12 +335,19 @@ : response_data.status; error_str = HttpErrorToString(status); } - callback(olp::http::NetworkResponse() - .WithRequestId(strong_task.requestId) - .WithStatus(status) - .WithError(error_str) - .WithBytesDownloaded(bytesDownloaded) - .WithBytesUploaded(bytesUploaded)); + auto response = olp::http::NetworkResponse() + .WithRequestId(strong_task.requestId) + .WithStatus(status) + .WithError(error_str) + .WithBytesDownloaded(bytesDownloaded) + .WithBytesUploaded(bytesUploaded); + + olp::http::Diagnostics diagnostics; + if ([strong_task getDiagnostics:diagnostics]) { + response.WithDiagnostics(std::move(diagnostics)); + } + + callback(std::move(response)); } };