From 488db71afc1ef35b42590884b3b377488b485649 Mon Sep 17 00:00:00 2001 From: Carl Suster Date: Fri, 13 Jun 2025 09:40:29 +1000 Subject: [PATCH 1/5] Expose request timing --- NAMESPACE | 1 + NEWS.md | 1 + R/resp-timing.R | 25 +++++++++++++++++++++++++ R/resp.R | 6 ++++++ man/resp_timing.Rd | 29 +++++++++++++++++++++++++++++ tests/testthat/test-resp-timing.R | 6 ++++++ 6 files changed, 68 insertions(+) create mode 100644 R/resp-timing.R create mode 100644 man/resp_timing.Rd create mode 100644 tests/testthat/test-resp-timing.R diff --git a/NAMESPACE b/NAMESPACE index a590c36e..cae5cace 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -131,6 +131,7 @@ export(resp_stream_is_complete) export(resp_stream_lines) export(resp_stream_raw) export(resp_stream_sse) +export(resp_timing) export(resp_url) export(resp_url_path) export(resp_url_queries) diff --git a/NEWS.md b/NEWS.md index 36646dd5..3e74a546 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # httr2 (development version) +* `resp_timing()` exposes timing information about the request measured by libcurl (@arcresu, #725). * `req_url_query()` now re-calculates n lengths when using `.multi = "explode"` to avoid select/recycling issues (@Kevanness, #719). # httr2 1.1.2 diff --git a/R/resp-timing.R b/R/resp-timing.R new file mode 100644 index 00000000..0e9d77ff --- /dev/null +++ b/R/resp-timing.R @@ -0,0 +1,25 @@ +#' Extract information about the timing of the response +#' +#' The underlying curl library measures how long different components of the +#' request take to complete. This function retrieves that information. +#' +#' The names of the elements in this vector correspond to the enum used +#' in libcurl's `curl_easy_getinfo()` API. For example, `"namelookup"` is the +#' time returned by requesting `CURLINFO_NAMELOOKUP_TIME`. Refer to [curl +#' documentation] for explanations of what these measure. The most useful +#' component is likely `"total"`, the overall time to complete the request. +#' +#' [curl documentation]: https://everything.curl.dev/transfers/getinfo.html#available-information +#' +#' @inheritParams resp_header +#' @returns named numeric vector of timing information +#' @export +#' @examples +#' req <- request(example_url()) +#' resp <- req_perform(req) +#' resp_timing(resp) +resp_timing <- function(resp) { + check_response(resp) + + resp$timing +} diff --git a/R/resp.R b/R/resp.R index 92f52ad8..f64e5a66 100644 --- a/R/resp.R +++ b/R/resp.R @@ -76,6 +76,7 @@ new_response <- function( status_code, headers, body, + timing = NULL, request = NULL, error_call = caller_env() ) { @@ -83,6 +84,9 @@ new_response <- function( check_string(url, call = error_call) check_number_whole(status_code, call = error_call) check_request(request, allow_null = TRUE) + if (!is.null(timing) && !is_bare_numeric(timing)) { + stop_input_type(timing, "a numeric vector", allow_null = TRUE) + } headers <- as_headers(headers, error_call = error_call) # ensure we always have a date field @@ -97,6 +101,7 @@ new_response <- function( status_code = status_code, headers = headers, body = body, + timing = timing, request = request, cache = new_environment() ), @@ -111,6 +116,7 @@ create_response <- function(req, curl_data, body) { status_code = curl_data$status_code, headers = as_headers(curl_data$headers), body = body, + timing = curl_data$times, request = req ) diff --git a/man/resp_timing.Rd b/man/resp_timing.Rd new file mode 100644 index 00000000..ad0388ee --- /dev/null +++ b/man/resp_timing.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp-timing.R +\name{resp_timing} +\alias{resp_timing} +\title{Extract information about the timing of the response} +\usage{ +resp_timing(resp) +} +\arguments{ +\item{resp}{A httr2 \link{response} object, created by \code{\link[=req_perform]{req_perform()}}.} +} +\value{ +named numeric vector of timing information +} +\description{ +The underlying curl library measures how long different components of the +request take to complete. This function retrieves that information. +} +\details{ +The names of the elements in this vector correspond to the enum used +in libcurl's \code{curl_easy_getinfo()} API. For example, \code{"namelookup"} is the +time returned by requesting \code{CURLINFO_NAMELOOKUP_TIME}. Refer to \href{https://everything.curl.dev/transfers/getinfo.html#available-information}{curl documentation} for explanations of what these measure. The most useful +component is likely \code{"total"}, the overall time to complete the request. +} +\examples{ +req <- request(example_url()) +resp <- req_perform(req) +resp_timing(resp) +} diff --git a/tests/testthat/test-resp-timing.R b/tests/testthat/test-resp-timing.R new file mode 100644 index 00000000..2515288d --- /dev/null +++ b/tests/testthat/test-resp-timing.R @@ -0,0 +1,6 @@ +test_that("can extract request", { + req <- request_test() + resp <- req_perform(req) + expect(is_bare_numeric(resp_timing(resp)), "timing vector is not numeric") + expect_contains(names(resp_timing(resp)), "total") +}) From e21b40791ec3219b36afcca8b9001273ecb0789a Mon Sep 17 00:00:00 2001 From: Carl Suster Date: Fri, 13 Jun 2025 14:58:53 +1000 Subject: [PATCH 2/5] Link to upstream curl documentation --- R/resp-timing.R | 7 ++++--- man/resp_timing.Rd | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/R/resp-timing.R b/R/resp-timing.R index 0e9d77ff..43bce0bd 100644 --- a/R/resp-timing.R +++ b/R/resp-timing.R @@ -3,13 +3,14 @@ #' The underlying curl library measures how long different components of the #' request take to complete. This function retrieves that information. #' -#' The names of the elements in this vector correspond to the enum used +#' The names of the elements in this vector correspond to the names used #' in libcurl's `curl_easy_getinfo()` API. For example, `"namelookup"` is the #' time returned by requesting `CURLINFO_NAMELOOKUP_TIME`. Refer to [curl #' documentation] for explanations of what these measure. The most useful -#' component is likely `"total"`, the overall time to complete the request. +#' component is likely `"total"`, the overall time in seconds to complete the +#' request including any redirects followed. #' -#' [curl documentation]: https://everything.curl.dev/transfers/getinfo.html#available-information +#' [curl documentation]: https://curl.se/libcurl/c/curl_easy_getinfo.html #' #' @inheritParams resp_header #' @returns named numeric vector of timing information diff --git a/man/resp_timing.Rd b/man/resp_timing.Rd index ad0388ee..11dac759 100644 --- a/man/resp_timing.Rd +++ b/man/resp_timing.Rd @@ -17,10 +17,11 @@ The underlying curl library measures how long different components of the request take to complete. This function retrieves that information. } \details{ -The names of the elements in this vector correspond to the enum used +The names of the elements in this vector correspond to the names used in libcurl's \code{curl_easy_getinfo()} API. For example, \code{"namelookup"} is the -time returned by requesting \code{CURLINFO_NAMELOOKUP_TIME}. Refer to \href{https://everything.curl.dev/transfers/getinfo.html#available-information}{curl documentation} for explanations of what these measure. The most useful -component is likely \code{"total"}, the overall time to complete the request. +time returned by requesting \code{CURLINFO_NAMELOOKUP_TIME}. Refer to \href{https://curl.se/libcurl/c/curl_easy_getinfo.html}{curl documentation} for explanations of what these measure. The most useful +component is likely \code{"total"}, the overall time in seconds to complete the +request including any redirects followed. } \examples{ req <- request(example_url()) From 2de29c0c9f84e2dd41fcc9af716daffe085aad3f Mon Sep 17 00:00:00 2001 From: Carl Suster Date: Mon, 16 Jun 2025 09:51:33 +1000 Subject: [PATCH 3/5] Improve resp_timing docs --- R/resp-timing.R | 20 +++++++++----------- man/resp_timing.Rd | 16 +++++++--------- tests/testthat/test-resp-timing.R | 2 +- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/R/resp-timing.R b/R/resp-timing.R index 43bce0bd..54f8f069 100644 --- a/R/resp-timing.R +++ b/R/resp-timing.R @@ -1,19 +1,17 @@ -#' Extract information about the timing of the response +#' Extract timing data #' #' The underlying curl library measures how long different components of the #' request take to complete. This function retrieves that information. #' -#' The names of the elements in this vector correspond to the names used -#' in libcurl's `curl_easy_getinfo()` API. For example, `"namelookup"` is the -#' time returned by requesting `CURLINFO_NAMELOOKUP_TIME`. Refer to [curl -#' documentation] for explanations of what these measure. The most useful -#' component is likely `"total"`, the overall time in seconds to complete the -#' request including any redirects followed. -#' -#' [curl documentation]: https://curl.se/libcurl/c/curl_easy_getinfo.html -#' #' @inheritParams resp_header -#' @returns named numeric vector of timing information +#' @returns Named numeric vector of timing information. +#' The names of the elements in this vector correspond to the names used +#' in [libcurl's `curl_easy_getinfo()` API][curl docs]. +#' The most useful component is likely `"total"` (corresponding to +#' `CURLINFO_TOTAL_TIME`), the overall time in seconds to complete the +#' request including any redirects followed. +#' +#' [curl docs]: https://curl.se/libcurl/c/curl_easy_getinfo.html #' @export #' @examples #' req <- request(example_url()) diff --git a/man/resp_timing.Rd b/man/resp_timing.Rd index 11dac759..f614cbe3 100644 --- a/man/resp_timing.Rd +++ b/man/resp_timing.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/resp-timing.R \name{resp_timing} \alias{resp_timing} -\title{Extract information about the timing of the response} +\title{Extract timing data} \usage{ resp_timing(resp) } @@ -10,19 +10,17 @@ resp_timing(resp) \item{resp}{A httr2 \link{response} object, created by \code{\link[=req_perform]{req_perform()}}.} } \value{ -named numeric vector of timing information +Named numeric vector of timing information. +The names of the elements in this vector correspond to the names used +in \href{https://curl.se/libcurl/c/curl_easy_getinfo.html}{libcurl's \code{curl_easy_getinfo()} API}. +The most useful component is likely \code{"total"} (corresponding to +\code{CURLINFO_TOTAL_TIME}), the overall time in seconds to complete the +request including any redirects followed. } \description{ The underlying curl library measures how long different components of the request take to complete. This function retrieves that information. } -\details{ -The names of the elements in this vector correspond to the names used -in libcurl's \code{curl_easy_getinfo()} API. For example, \code{"namelookup"} is the -time returned by requesting \code{CURLINFO_NAMELOOKUP_TIME}. Refer to \href{https://curl.se/libcurl/c/curl_easy_getinfo.html}{curl documentation} for explanations of what these measure. The most useful -component is likely \code{"total"}, the overall time in seconds to complete the -request including any redirects followed. -} \examples{ req <- request(example_url()) resp <- req_perform(req) diff --git a/tests/testthat/test-resp-timing.R b/tests/testthat/test-resp-timing.R index 2515288d..39cf76b7 100644 --- a/tests/testthat/test-resp-timing.R +++ b/tests/testthat/test-resp-timing.R @@ -1,4 +1,4 @@ -test_that("can extract request", { +test_that("can extract request timing", { req <- request_test() resp <- req_perform(req) expect(is_bare_numeric(resp_timing(resp)), "timing vector is not numeric") From f9d3ad5edefd9e756b07c6eb9059c2f7fd103a4e Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Mon, 16 Jun 2025 08:57:02 -0500 Subject: [PATCH 4/5] Partition test into setting and getting --- R/resp.R | 6 ++++-- tests/testthat/test-req-perform.R | 3 +++ tests/testthat/test-resp-timing.R | 6 ++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/R/resp.R b/R/resp.R index 024bacfa..fe765e84 100644 --- a/R/resp.R +++ b/R/resp.R @@ -30,7 +30,8 @@ response <- function( url = "https://example.com", method = "GET", headers = list(), - body = raw() + body = raw(), + timing = NULL ) { check_number_whole(status_code, min = 100, max = 700) check_string(url) @@ -43,7 +44,8 @@ response <- function( url = url, status_code = as.integer(status_code), headers = headers, - body = body + body = body, + timing = timing ) } diff --git a/tests/testthat/test-req-perform.R b/tests/testthat/test-req-perform.R index edb114e5..ca7d290c 100644 --- a/tests/testthat/test-req-perform.R +++ b/tests/testthat/test-req-perform.R @@ -9,6 +9,9 @@ test_that("successful request returns expected response", { expect_s3_class(resp$headers, "httr2_headers") expect_type(resp$body, "raw") expect_equal(resp$request, req) + + expect_type(resp$timing, "double") + expect_true(all(resp$timing >= 0)) }) test_that("request updates last_response()", { diff --git a/tests/testthat/test-resp-timing.R b/tests/testthat/test-resp-timing.R index 39cf76b7..85caa1c9 100644 --- a/tests/testthat/test-resp-timing.R +++ b/tests/testthat/test-resp-timing.R @@ -1,6 +1,4 @@ test_that("can extract request timing", { - req <- request_test() - resp <- req_perform(req) - expect(is_bare_numeric(resp_timing(resp)), "timing vector is not numeric") - expect_contains(names(resp_timing(resp)), "total") + req <- response(timing = c(total = 1)) + expect_equal(resp_timing(req), c(total = 1)) }) From 7a9175e036fd4acf324329968911657c8db2ac45 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Mon, 16 Jun 2025 09:00:53 -0500 Subject: [PATCH 5/5] Fix docs --- R/resp.R | 2 ++ man/response.Rd | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/R/resp.R b/R/resp.R index fe765e84..1c998103 100644 --- a/R/resp.R +++ b/R/resp.R @@ -19,6 +19,8 @@ #' which will be parsed using the standard rules, or a named list. #' @param body Response, if any, contained in the response body. #' For `response_json()`, a R data structure to serialize to JSON. +#' @param timing A named numeric vector giving the time taken by various +#' components. #' @returns An HTTP response: an S3 list with class `httr2_response`. #' @export #' @examples diff --git a/man/response.Rd b/man/response.Rd index c5c8b112..f0eb95a0 100644 --- a/man/response.Rd +++ b/man/response.Rd @@ -10,7 +10,8 @@ response( url = "https://example.com", method = "GET", headers = list(), - body = raw() + body = raw(), + timing = NULL ) response_json( @@ -34,6 +35,9 @@ which will be parsed using the standard rules, or a named list.} \item{body}{Response, if any, contained in the response body. For \code{response_json()}, a R data structure to serialize to JSON.} + +\item{timing}{A named numeric vector giving the time taken by various +components.} } \value{ An HTTP response: an S3 list with class \code{httr2_response}.