diff --git a/olp-cpp-sdk-core/include/olp/core/http/NetworkResponse.h b/olp-cpp-sdk-core/include/olp/core/http/NetworkResponse.h index 7179ff326..bf76d4922 100644 --- a/olp-cpp-sdk-core/include/olp/core/http/NetworkResponse.h +++ b/olp-cpp-sdk-core/include/olp/core/http/NetworkResponse.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2020 HERE Europe B.V. + * Copyright (C) 2019-2025 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,13 +19,44 @@ #pragma once +#include +#include +#include #include +#include + #include #include namespace olp { namespace http { + +/** + * @brief Network request timings. + */ +struct Diagnostics { + using MicroSeconds = std::chrono::duration; + + enum Timings { + Queue = 0, // Delay until the request is processed + NameLookup, // Time taken for DNS name lookup + Connect, // Time taken to establish connection + SSL_Handshake, // Time taken to establish a secured connection + Send, // Time taken to send the request + Wait, // Time delay until server starts responding + Receive, // Time taken to receive the response + Total, // Total time taken for reqeust + Count + }; + + /// Timing values + std::array timings{}; + + /// Availability flag, specify which timing is available + std::bitset available_timings{}; +}; + /** * @brief A network response abstraction for the HTTP request. */ @@ -126,6 +157,22 @@ class CORE_API NetworkResponse final { */ NetworkResponse& WithBytesDownloaded(uint64_t bytes_downloaded); + /** + * @brief Gets the optional diagnostics if set. + * + * @return Diagnostic values. + */ + const boost::optional& GetDiagnostics() const; + + /** + * @brief Sets the request diagnostics. + * + * @param diagnostics Diagnostics values. + * + * @return A reference to *this. + */ + NetworkResponse& WithDiagnostics(Diagnostics diagnostics); + private: /// The associated request ID. RequestId request_id_{0}; @@ -134,9 +181,11 @@ class CORE_API NetworkResponse final { /// The human-readable error message if the associated request failed. std::string error_; /// The number of bytes uploaded during the network request. - uint64_t bytes_uploaded_; + uint64_t bytes_uploaded_{0}; /// The number of bytes downloaded during the network request. - uint64_t bytes_downloaded_; + uint64_t bytes_downloaded_{0}; + /// Diagnostics + boost::optional diagnostics_; }; } // namespace http diff --git a/olp-cpp-sdk-core/src/http/NetworkResponse.cpp b/olp-cpp-sdk-core/src/http/NetworkResponse.cpp index 6f03fc9f9..157b5b297 100644 --- a/olp-cpp-sdk-core/src/http/NetworkResponse.cpp +++ b/olp-cpp-sdk-core/src/http/NetworkResponse.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2020 HERE Europe B.V. + * Copyright (C) 2019-2025 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. @@ -65,5 +65,14 @@ NetworkResponse& NetworkResponse::WithBytesDownloaded( return *this; } +NetworkResponse& NetworkResponse::WithDiagnostics(Diagnostics diagnostics) { + diagnostics_ = diagnostics; + return *this; +} + +const boost::optional& NetworkResponse::GetDiagnostics() const { + return diagnostics_; +} + } // namespace http } // namespace olp diff --git a/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp b/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp index f809c73a6..9d28247cf 100644 --- a/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp +++ b/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp @@ -348,6 +348,49 @@ void SetupDns(CURL* curl_handle, const std::vector& dns_servers) { #endif } +Diagnostics GetDiagnostics(CURL* handle) { + Diagnostics diagnostics; + + static const std::pair available_timings[] = { +#if CURL_AT_LEAST_VERSION(8, 6, 0) + {Diagnostics::Queue, CURLINFO_QUEUE_TIME_T}, +#endif + {Diagnostics::NameLookup, CURLINFO_NAMELOOKUP_TIME_T}, + {Diagnostics::Connect, CURLINFO_CONNECT_TIME_T}, + {Diagnostics::SSL_Handshake, CURLINFO_APPCONNECT_TIME_T}, +#if CURL_AT_LEAST_VERSION(8, 10, 0) + {Diagnostics::Send, CURLINFO_POSTTRANSFER_TIME_T}, + {Diagnostics::Wait, CURLINFO_STARTTRANSFER_TIME_T}, +#else + {Diagnostics::Send, CURLINFO_STARTTRANSFER_TIME_T}, +#endif + {Diagnostics::Receive, CURLINFO_TOTAL_TIME_T}, + }; + + curl_off_t last_time_point{0}; + + auto add_timing = [&](Diagnostics::Timings timing, + Diagnostics::MicroSeconds time) { + diagnostics.timings[timing] = time; + diagnostics.available_timings.set(timing); + }; + + for (const auto& available_timing : available_timings) { + curl_off_t time_point_us = 0; + if (curl_easy_getinfo(handle, available_timing.second, &time_point_us) == + CURLE_OK && + time_point_us > 0) { + add_timing(available_timing.first, + Diagnostics::MicroSeconds(time_point_us - last_time_point)); + last_time_point = time_point_us; + } + } + + add_timing(Diagnostics::Total, Diagnostics::MicroSeconds(last_time_point)); + + return diagnostics; +} + } // anonymous namespace NetworkCurl::NetworkCurl(NetworkInitializationSettings settings) @@ -1004,7 +1047,8 @@ void NetworkCurl::CompleteMessage(CURL* curl_handle, CURLcode result) { auto response = NetworkResponse() .WithRequestId(request_handle->id) .WithBytesDownloaded(download_bytes) - .WithBytesUploaded(upload_bytes); + .WithBytesUploaded(upload_bytes) + .WithDiagnostics(GetDiagnostics(curl_handle)); if (request_handle->is_cancelled) { response.WithStatus(static_cast(ErrorCode::CANCELLED_ERROR)) @@ -1199,7 +1243,9 @@ void NetworkCurl::Run() { .WithStatus(static_cast(ErrorCode::IO_ERROR)) .WithError("CURL error") .WithBytesDownloaded(download_bytes) - .WithBytesUploaded(upload_bytes); + .WithBytesUploaded(upload_bytes) + .WithDiagnostics(GetDiagnostics(curl_handle)); + callback(response); lock.lock(); }