Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions include/ada/url_aggregator.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ struct url_aggregator : url_base {
*/
[[nodiscard]] constexpr size_t get_href_size() const noexcept;

/**
* Estimates the bytes retained by the serialized URL buffer.
*
* The estimate is allocator-independent and reports the buffer capacity,
* not the current serialized URL length or allocator bookkeeping overhead.
* It is suitable for embedders that need to account for retained payload
* storage.
* @return The retained buffer capacity in bytes.
*/
[[nodiscard]] size_t estimated_memory_usage() const noexcept;

/**
* Returns the URL's username component.
* Does not allocate memory. The returned view becomes invalid if this
Expand Down
10 changes: 10 additions & 0 deletions include/ada/url_search_params.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ struct url_search_params {
url_search_params& operator=(const url_search_params& u) = default;
~url_search_params() = default;

/**
* Estimates the bytes retained by the parameter list and its strings.
*
* The estimate is allocator-independent and is the saturating sum of the
* parameter vector capacity times its element size and every live key and
* value string capacity. It excludes allocator bookkeeping overhead.
* @return The estimated retained payload capacity in bytes.
*/
[[nodiscard]] size_t estimated_memory_usage() const noexcept;

/**
* Returns the number of key-value pairs.
* @return The total count of parameters.
Expand Down
8 changes: 8 additions & 0 deletions include/ada_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ ada_string ada_get_hostname(ada_url result);
ada_string ada_get_pathname(ada_url result);
ada_string ada_get_search(ada_url result);
ada_string ada_get_protocol(ada_url result);
// Returns an allocator-independent estimate of the opaque URL object's
// retained storage, including its owning result block and URL buffer capacity.
// Invalid URL results still retain and report their owning result block.
size_t ada_url_estimated_memory_usage(ada_url result);
uint8_t ada_get_host_type(ada_url result);
uint8_t ada_get_scheme_type(ada_url result);

Expand Down Expand Up @@ -136,6 +140,10 @@ ada_url_search_params ada_parse_search_params(const char* input, size_t length);
void ada_free_search_params(ada_url_search_params result);

size_t ada_search_params_size(ada_url_search_params result);
// Returns an allocator-independent estimate of the opaque parameter object's
// retained storage, including its owning result block, parameter vector, and
// live string capacities.
size_t ada_search_params_estimated_memory_usage(ada_url_search_params result);
void ada_search_params_sort(ada_url_search_params result);
ada_owned_string ada_search_params_to_string(ada_url_search_params result);

Expand Down
1 change: 1 addition & 0 deletions src/ada.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "parser.cpp"
#include "url_components.cpp"
#include "url_aggregator.cpp"
#include "url_search_params.cpp"

#if ADA_INCLUDE_URL_PATTERN
#include "url_pattern.cpp"
Expand Down
32 changes: 32 additions & 0 deletions src/ada_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,31 @@
#include "ada/url_aggregator-inl.h"
#include "ada/url_search_params-inl.h"

#include <limits>

ada::result<ada::url_aggregator>& get_instance(void* result) noexcept {
return *(ada::result<ada::url_aggregator>*)result;
}

namespace {

template <typename T>
[[nodiscard]] size_t estimated_c_object_memory_usage(
const ada::result<T>& result) noexcept {
constexpr size_t owning_block_size = sizeof(ada::result<T>);
if (!result) {
return owning_block_size;
}

const size_t payload_size = result->estimated_memory_usage();
constexpr size_t max = std::numeric_limits<size_t>::max();
return payload_size > max - owning_block_size
? max
: owning_block_size + payload_size;
}

} // namespace

extern "C" {
typedef void* ada_url;
typedef void* ada_url_search_params;
Expand Down Expand Up @@ -220,6 +241,11 @@ ada_string ada_get_protocol(ada_url result) noexcept {
return ada_string_create(out.data(), out.length());
}

size_t ada_url_estimated_memory_usage(ada_url result) noexcept {
ada::result<ada::url_aggregator>& r = get_instance(result);
return estimated_c_object_memory_usage(r);
}

uint8_t ada_get_host_type(ada_url result) noexcept {
ada::result<ada::url_aggregator>& r = get_instance(result);
if (!r) {
Expand Down Expand Up @@ -498,6 +524,12 @@ size_t ada_search_params_size(ada_url_search_params result) {
return r->size();
}

size_t ada_search_params_estimated_memory_usage(ada_url_search_params result) {
ada::result<ada::url_search_params>& r =
*(ada::result<ada::url_search_params>*)result;
return estimated_c_object_memory_usage(r);
}

void ada_search_params_sort(ada_url_search_params result) {
ada::result<ada::url_search_params>& r =
*(ada::result<ada::url_search_params>*)result;
Expand Down
5 changes: 5 additions & 0 deletions src/url_aggregator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ ada_really_inline void apply_shifted_non_scheme_offsets(
} // namespace

namespace ada {

size_t url_aggregator::estimated_memory_usage() const noexcept {
return buffer.capacity();
}

template <bool has_state_override>
[[nodiscard]] ada_really_inline bool url_aggregator::parse_scheme_with_colon(
const std::string_view input_with_colon) {
Expand Down
38 changes: 38 additions & 0 deletions src/url_search_params.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "ada/url_search_params.h"

#include <limits>

namespace {

[[nodiscard]] constexpr size_t saturating_add(size_t left,
size_t right) noexcept {
constexpr size_t max = std::numeric_limits<size_t>::max();
return right > max - left ? max : left + right;
}

[[nodiscard]] constexpr size_t saturating_multiply(size_t left,
size_t right) noexcept {
constexpr size_t max = std::numeric_limits<size_t>::max();
return left != 0 && right > max / left ? max : left * right;
}

static_assert(saturating_add(std::numeric_limits<size_t>::max(), 1) ==
std::numeric_limits<size_t>::max());
static_assert(saturating_multiply(std::numeric_limits<size_t>::max(), 2) ==
std::numeric_limits<size_t>::max());

} // namespace

namespace ada {

size_t url_search_params::estimated_memory_usage() const noexcept {
size_t estimate =
saturating_multiply(params.capacity(), sizeof(key_value_pair));
for (const auto& [key, value] : params) {
estimate = saturating_add(estimate, key.capacity());
estimate = saturating_add(estimate, value.capacity());
}
return estimate;
}

} // namespace ada
35 changes: 35 additions & 0 deletions tests/ada_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,41 @@ TEST(ada_c, getters) {
SUCCEED();
}

TEST(ada_c, estimated_memory_usage) {
const std::string short_input = "https://example.com/";
ada_url url = ada_parse(short_input.data(), short_input.size());
ASSERT_TRUE(ada_is_valid(url));
const size_t initial_url_estimate = ada_url_estimated_memory_usage(url);
ASSERT_GT(initial_url_estimate, short_input.size());

const std::string large_input =
"https://example.com/" + std::string(8192, 'a');
ASSERT_TRUE(ada_set_href(url, large_input.data(), large_input.size()));
ASSERT_GE(ada_url_estimated_memory_usage(url), large_input.size());
ASSERT_GT(ada_url_estimated_memory_usage(url), initial_url_estimate);
ada_free(url);

const std::string invalid_input = ":";
url = ada_parse(invalid_input.data(), invalid_input.size());
ASSERT_FALSE(ada_is_valid(url));
ASSERT_GT(ada_url_estimated_memory_usage(url), 0);
ada_free(url);

ada_url_search_params params = ada_parse_search_params("", 0);
const size_t initial_params_estimate =
ada_search_params_estimated_memory_usage(params);
ASSERT_GT(initial_params_estimate, 0);
const std::string key(256, 'k');
const std::string value(256, 'v');
for (size_t index = 0; index < 64; ++index) {
ada_search_params_append(params, key.data(), key.size(), value.data(),
value.size());
}
ASSERT_GT(ada_search_params_estimated_memory_usage(params),
initial_params_estimate);
ada_free_search_params(params);
}

TEST(ada_c, setters) {
std::string input =
"https://username:password@www.google.com:8080/"
Expand Down
13 changes: 13 additions & 0 deletions tests/basic_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,19 @@ TYPED_TEST(basic_tests, get_href_size_matches_get_href) {
}
}

TEST(url_aggregator, estimated_memory_usage_tracks_retained_capacity) {
auto url = ada::parse<ada::url_aggregator>("https://example.com/");
ASSERT_TRUE(url);

const size_t initial_estimate = url->estimated_memory_usage();
ASSERT_GE(initial_estimate, url->get_href_size());

const std::string large_url = "https://example.com/" + std::string(8192, 'a');
ASSERT_TRUE(url->set_href(large_url));
ASSERT_GE(url->estimated_memory_usage(), url->get_href_size());
ASSERT_GT(url->estimated_memory_usage(), initial_estimate);
}

TYPED_TEST(basic_tests, get_href_size_after_setters) {
auto url =
ada::parse<TypeParam>("https://user:pass@example.com:8080/path?q=1#frag");
Expand Down
27 changes: 27 additions & 0 deletions tests/url_search_params.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,33 @@ TEST(url_search_params, append) {
SUCCEED();
}

TEST(url_search_params, estimated_memory_usage_tracks_retained_capacity) {
using key_value_pair = std::pair<std::string, std::string>;
constexpr size_t entry_count = 64;
constexpr size_t string_size = 256;

ada::url_search_params search_params;
const size_t initial_estimate = search_params.estimated_memory_usage();
const std::string key(string_size, 'k');
const std::string value(string_size, 'v');

for (size_t index = 0; index < entry_count; ++index) {
search_params.append(key, value);
}

const size_t populated_estimate = search_params.estimated_memory_usage();
const size_t populated_lower_bound =
entry_count * sizeof(key_value_pair) +
entry_count * (key.size() + value.size());
ASSERT_GE(populated_estimate, populated_lower_bound);
ASSERT_GT(populated_estimate, initial_estimate);

search_params.reset("");
ASSERT_GE(search_params.estimated_memory_usage(),
entry_count * sizeof(key_value_pair));
ASSERT_LE(search_params.estimated_memory_usage(), populated_estimate);
}

TEST(url_search_params, to_string) {
auto search_params = ada::url_search_params();
search_params.append("key1", "value1");
Expand Down
Loading