Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ dist/
out/
MODULE.bazel.lock
.vscode
.cache/
.cursor/
.DS_Store
90 changes: 88 additions & 2 deletions include/datadog/config.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#pragma once

#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>

#include "error.h"
#include "optional.h"

Expand Down Expand Up @@ -56,14 +62,94 @@ struct ConfigMetadata {
: name(n), value(std::move(v)), origin(orig), error(std::move(err)) {}
};

// Returns the final configuration value using the following
// precedence order: environment > user code > default, and populates two maps:
// 1. `telemetry_configs`: Records ALL configuration sources that were provided,
// ordered from lowest to highest precedence.
// 2. `metadata`: Records ONLY the winning configuration value (highest
// precedence). Template Parameters:
// Value: The type of the configuration value
// Stringifier: Optional function type to convert Value to string
// (defaults to std::nullptr_t, which uses string construction)
// DefaultValue: Type of the fallback value (defaults to std::nullptr_t)
//
// Parameters:
// from_env: Optional value from environment variables (highest precedence)
// from_user: Optional value from user code (middle precedence)
// telemetry_configs: Output map that will be populated with all config
// sources found for this config_name, in precedence order
// metadata: Output map that will be populated with the winning config value
// for this config_name
// config_name: The configuration parameter name identifier
// fallback: Optional default value (lowest precedence). Pass nullptr to
// indicate no default.
// to_string_fn: Optional custom function to convert Value to string.
// Required for non-string types. For string-like types, uses
// default string construction if not provided.
//
// Returns:
// The chosen configuration value based on precedence, or Value{} if no value
// was provided.
template <typename Value, typename Stringifier = std::nullptr_t,
typename DefaultValue = std::nullptr_t>
Value resolve_and_record_config(
const Optional<Value>& from_env, const Optional<Value>& from_user,
std::unordered_map<ConfigName, std::vector<ConfigMetadata>>*
telemetry_configs,
std::unordered_map<ConfigName, ConfigMetadata>* metadata,
ConfigName config_name, DefaultValue fallback = nullptr,
Stringifier to_string_fn = nullptr) {
auto stringify = [&](const Value& v) -> std::string {
if constexpr (!std::is_same_v<Stringifier, std::nullptr_t>) {
return to_string_fn(v); // use provided function
} else if constexpr (std::is_constructible_v<std::string, Value>) {
return std::string(v); // default behaviour (works for string-like types)
} else {
static_assert(!std::is_same_v<Value, Value>,
"Non-string types require a stringifier function");
return ""; // unreachable
}
};

std::vector<ConfigMetadata> telemetry_entries;
Optional<Value> chosen_value;

auto add_entry = [&](ConfigMetadata::Origin origin, const Value& val) {
std::string val_str = stringify(val);
telemetry_entries.emplace_back(
ConfigMetadata{config_name, val_str, origin});
chosen_value = val;
};

// Add DEFAULT entry if fallback was provided (detected by type)
if constexpr (!std::is_same_v<DefaultValue, std::nullptr_t>) {
add_entry(ConfigMetadata::Origin::DEFAULT, fallback);
}

if (from_user) {
add_entry(ConfigMetadata::Origin::CODE, *from_user);
}

if (from_env) {
add_entry(ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, *from_env);
}

(*telemetry_configs)[config_name] = std::move(telemetry_entries);
if (!(*telemetry_configs)[config_name].empty()) {
(*metadata)[config_name] = (*telemetry_configs)[config_name].back();
}

return chosen_value.value_or(Value{});
}

// Return a pair containing the configuration origin and value of a
// configuration value chosen from one of the specified `from_env`,
// `from_config`, and `fallback`. This function defines the relative precedence
// among configuration values originating from the environment, programmatic
// configuration, and default configuration.
template <typename Value, typename DefaultValue>
std::pair<ConfigMetadata::Origin, Value> pick(const Optional<Value> &from_env,
const Optional<Value> &from_user,
std::pair<ConfigMetadata::Origin, Value> pick(const Optional<Value>& from_env,
const Optional<Value>& from_user,
DefaultValue fallback) {
if (from_env) {
return {ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, *from_env};
Expand Down
3 changes: 2 additions & 1 deletion include/datadog/telemetry/product.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <string>
#include <unordered_map>
#include <vector>

namespace datadog::telemetry {

Expand All @@ -30,7 +31,7 @@ struct Product final {
/// Optional error message related to the product status.
tracing::Optional<std::string> error_message;
/// Map of configuration settings for the product.
std::unordered_map<tracing::ConfigName, tracing::ConfigMetadata>
std::unordered_map<tracing::ConfigName, std::vector<tracing::ConfigMetadata>>
configurations;
};

Expand Down
9 changes: 5 additions & 4 deletions src/datadog/telemetry/telemetry_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -587,11 +587,12 @@ std::string Telemetry::app_started_payload() {

for (const auto& product : config_.products) {
auto& configurations = product.configurations;
for (const auto& [_, config_metadata] : configurations) {
for (const auto& [_, config_metadatas] : configurations) {
// if (config_metadata.value.empty()) continue;

configuration_json.emplace_back(
generate_configuration_field(config_metadata));
for (const auto& config_metadata : config_metadatas) {
configuration_json.emplace_back(
generate_configuration_field(config_metadata));
}
}

/// NOTE(@dmehala): Telemetry API is tightly related to APM tracing and
Expand Down
10 changes: 6 additions & 4 deletions src/datadog/trace_sampler_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,12 @@ Expected<FinalizedTraceSamplerConfig> finalize_config(
result.rules.emplace_back(std::move(finalized_rule));
}

const auto [origin, max_per_second] =
pick(env_config->max_per_second, config.max_per_second, 100);
result.metadata[ConfigName::TRACE_SAMPLING_LIMIT] = ConfigMetadata(
ConfigName::TRACE_SAMPLING_LIMIT, std::to_string(max_per_second), origin);
std::unordered_map<ConfigName, std::vector<ConfigMetadata>>
telemetry_configs_tmp;
double max_per_second = resolve_and_record_config(
env_config->max_per_second, config.max_per_second, &telemetry_configs_tmp,
&result.metadata, ConfigName::TRACE_SAMPLING_LIMIT, 100.0,
[](const double &d) { return std::to_string(d); });

const auto allowed_types = {FP_NORMAL, FP_SUBNORMAL};
if (!(max_per_second > 0) ||
Expand Down
Loading