Skip to content

Commit b1ce4a4

Browse files
Network diagnostic statistic for iOS (#1704)
Previously, only curl-based network could collect it Relates-To: DATASDK-98 Signed-off-by: Andrey Kashcheev <ext-andrey.kashcheev@here.com>
1 parent 9ba2a00 commit b1ce4a4

4 files changed

Lines changed: 166 additions & 11 deletions

File tree

olp-cpp-sdk-core/src/http/ios/OLPHttpClient.mm

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2025 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -442,6 +442,31 @@ - (void)URLSession:(NSURLSession*)session
442442
}
443443
}
444444

445+
- (void)URLSession:(NSURLSession*)session
446+
task:(NSURLSessionTask*)task
447+
didFinishCollectingMetrics:(NSURLSessionTaskMetrics*)metrics {
448+
if (!self.sharedUrlSession) {
449+
OLP_SDK_LOG_WARNING_F(
450+
kLogTag,
451+
"didFinishCollectingMetrics failed - invalid session, task_id=%u",
452+
(unsigned int)task.taskIdentifier);
453+
return;
454+
}
455+
456+
OLP_SDK_LOG_TRACE_F(
457+
kLogTag,
458+
"didFinishCollectingMetrics, session=%p, task_id=%u, dataTask=%p",
459+
(__bridge void*)session, (unsigned int)task.taskIdentifier,
460+
(__bridge void*)task);
461+
462+
@autoreleasepool {
463+
OLPHttpTask* httpTask = [self taskWithTaskDescription:task.taskDescription];
464+
if ([httpTask isValid]) {
465+
[httpTask didCollectMetrics:metrics];
466+
}
467+
}
468+
}
469+
445470
- (void)URLSession:(NSURLSession*)session
446471
task:(NSURLSessionTask*)dataTask
447472
didReceiveChallenge:(NSURLAuthenticationChallenge*)challenge

olp-cpp-sdk-core/src/http/ios/OLPHttpTask+Internal.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2023 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,8 @@
2323

2424
#import <Foundation/Foundation.h>
2525

26+
#include <olp/core/http/NetworkResponse.h>
27+
2628
/**
2729
@brief Internal category, which extends OLPHttpTask with internal methods,
2830
which shouldn't be exposed as public API.
@@ -33,6 +35,10 @@
3335

3436
- (void)didReceiveData:(NSData*)data withWholeData:(bool)wholeData;
3537

38+
- (void)didCollectMetrics:(NSURLSessionTaskMetrics*)metrics;
39+
40+
- (BOOL)getDiagnostics:(olp::http::Diagnostics&)diagnostics;
41+
3642
- (void)didCompleteWithError:(NSError*)error;
3743

3844
- (NSString*)createTaskDescription;

olp-cpp-sdk-core/src/http/ios/OLPHttpTask.mm

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2024 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,14 +19,106 @@
1919

2020
#import "OLPHttpTask+Internal.h"
2121

22+
#include <limits>
23+
2224
#include <olp/core/http/HttpStatusCode.h>
2325
#include <olp/core/logging/Log.h>
26+
#include <olp/core/porting/optional.h>
2427

2528
#import "OLPHttpClient+Internal.h"
2629
#import "OLPNetworkConstants.h"
2730

2831
namespace {
2932
constexpr auto kLogTag = "OLPHttpTask";
33+
34+
constexpr uint64_t kMicrosecondsInSecond = 1000000u;
35+
36+
uint64_t DurationInMicroseconds(NSDate* start, NSDate* end) {
37+
if (!start || !end) {
38+
return 0u;
39+
}
40+
41+
const NSTimeInterval time_interval = [end timeIntervalSinceDate:start];
42+
if (time_interval <= 0.0) {
43+
return 0u;
44+
}
45+
46+
return static_cast<uint64_t>(time_interval * kMicrosecondsInSecond);
47+
}
48+
49+
void AddTiming(olp::http::Diagnostics& diagnostics,
50+
olp::http::Diagnostics::Timings timing,
51+
const uint64_t time_in_microseconds) {
52+
if (time_in_microseconds == 0u) {
53+
return;
54+
}
55+
56+
const auto previous_duration =
57+
static_cast<uint64_t>(diagnostics.timings[timing].count());
58+
const auto updated_duration = previous_duration + time_in_microseconds;
59+
const auto clamped_duration =
60+
std::min(updated_duration,
61+
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()));
62+
63+
diagnostics.timings[timing] = olp::http::Diagnostics::MicroSeconds(
64+
static_cast<uint32_t>(clamped_duration));
65+
diagnostics.available_timings.set(timing);
66+
}
67+
68+
olp::http::Diagnostics BuildDiagnostics(NSURLSessionTaskMetrics* metrics) {
69+
olp::http::Diagnostics diagnostics;
70+
if (!metrics) {
71+
return diagnostics;
72+
}
73+
74+
NSDate* previous_response_end = metrics.taskInterval.startDate;
75+
for (NSURLSessionTaskTransactionMetrics* transaction in metrics
76+
.transactionMetrics) {
77+
AddTiming(diagnostics, olp::http::Diagnostics::Queue,
78+
DurationInMicroseconds(previous_response_end,
79+
transaction.fetchStartDate));
80+
81+
AddTiming(diagnostics, olp::http::Diagnostics::NameLookup,
82+
DurationInMicroseconds(transaction.domainLookupStartDate,
83+
transaction.domainLookupEndDate));
84+
85+
const auto connect_duration = DurationInMicroseconds(
86+
transaction.connectStartDate, transaction.connectEndDate);
87+
const auto tls_duration =
88+
DurationInMicroseconds(transaction.secureConnectionStartDate,
89+
transaction.secureConnectionEndDate);
90+
if (connect_duration >= tls_duration) {
91+
AddTiming(diagnostics, olp::http::Diagnostics::Connect,
92+
connect_duration - tls_duration);
93+
} else {
94+
AddTiming(diagnostics, olp::http::Diagnostics::Connect, connect_duration);
95+
}
96+
97+
AddTiming(diagnostics, olp::http::Diagnostics::SSL_Handshake, tls_duration);
98+
99+
AddTiming(diagnostics, olp::http::Diagnostics::Send,
100+
DurationInMicroseconds(transaction.requestStartDate,
101+
transaction.requestEndDate));
102+
103+
AddTiming(diagnostics, olp::http::Diagnostics::Wait,
104+
DurationInMicroseconds(transaction.requestEndDate,
105+
transaction.responseStartDate));
106+
107+
AddTiming(diagnostics, olp::http::Diagnostics::Receive,
108+
DurationInMicroseconds(transaction.responseStartDate,
109+
transaction.responseEndDate));
110+
111+
if (transaction.responseEndDate) {
112+
previous_response_end = transaction.responseEndDate;
113+
}
114+
}
115+
116+
AddTiming(diagnostics, olp::http::Diagnostics::Total,
117+
DurationInMicroseconds(metrics.taskInterval.startDate,
118+
metrics.taskInterval.endDate));
119+
120+
return diagnostics;
121+
}
30122
} // namespace
31123

32124
#pragma mark - OLPHttpTaskResponseData
@@ -59,6 +151,7 @@ @implementation OLPHttpTask {
59151
uint64_t _headersSizeReceived;
60152
uint64_t _headersSizeSent;
61153
uint64_t _contentLength;
154+
olp::porting::optional<olp::http::Diagnostics> _diagnostics;
62155
}
63156

64157
- (instancetype)initWithHttpClient:(OLPHttpClient*)client
@@ -73,6 +166,7 @@ - (instancetype)initWithHttpClient:(OLPHttpClient*)client
73166
_headersSizeReceived = 0;
74167
_headersSizeSent = 0;
75168
_contentLength = 0;
169+
_diagnostics = {};
76170
_backgroundMode = false;
77171
}
78172
return self;
@@ -105,6 +199,8 @@ - (OLPHttpTaskStatus)restart {
105199
_dataTask = nil;
106200
}
107201

202+
_diagnostics = {};
203+
108204
NSMutableURLRequest* request =
109205
[NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
110206
request.timeoutInterval = self.connectionTimeout;
@@ -264,6 +360,27 @@ - (void)didReceiveData:(NSData*)data withWholeData:(bool)wholeData {
264360
}
265361
}
266362

363+
- (void)didCollectMetrics:(NSURLSessionTaskMetrics*)metrics {
364+
const auto diagnostics = BuildDiagnostics(metrics);
365+
if (!diagnostics.available_timings.any()) {
366+
return;
367+
}
368+
369+
@synchronized(self) {
370+
_diagnostics = diagnostics;
371+
}
372+
}
373+
374+
- (BOOL)getDiagnostics:(olp::http::Diagnostics&)diagnostics {
375+
@synchronized(self) {
376+
if (!_diagnostics) {
377+
return NO;
378+
}
379+
diagnostics = *_diagnostics;
380+
return YES;
381+
}
382+
}
383+
267384
- (NSString*)createTaskDescription {
268385
return [NSString stringWithFormat:@"%llu", self.requestId];
269386
}

olp-cpp-sdk-core/src/http/ios/OLPNetworkIOS.mm

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2025 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@
2727
#include "olp/core/logging/Log.h"
2828

2929
#import "OLPHttpClient+Internal.h"
30-
#import "OLPHttpTask.h"
30+
#import "OLPHttpTask+Internal.h"
3131
#import "OLPNetworkConstants.h"
3232

3333
namespace olp {
@@ -335,12 +335,19 @@
335335
: response_data.status;
336336
error_str = HttpErrorToString(status);
337337
}
338-
callback(olp::http::NetworkResponse()
339-
.WithRequestId(strong_task.requestId)
340-
.WithStatus(status)
341-
.WithError(error_str)
342-
.WithBytesDownloaded(bytesDownloaded)
343-
.WithBytesUploaded(bytesUploaded));
338+
auto response = olp::http::NetworkResponse()
339+
.WithRequestId(strong_task.requestId)
340+
.WithStatus(status)
341+
.WithError(error_str)
342+
.WithBytesDownloaded(bytesDownloaded)
343+
.WithBytesUploaded(bytesUploaded);
344+
345+
olp::http::Diagnostics diagnostics;
346+
if ([strong_task getDiagnostics:diagnostics]) {
347+
response.WithDiagnostics(std::move(diagnostics));
348+
}
349+
350+
callback(std::move(response));
344351
}
345352
};
346353

0 commit comments

Comments
 (0)