Skip to content

Commit b39a6cf

Browse files
committed
First batch
1 parent f8c3913 commit b39a6cf

6 files changed

Lines changed: 227 additions & 45 deletions

File tree

include/datadog/environment.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ namespace environment {
6969
MACRO(DD_VERSION, STRING, "") \
7070
MACRO(DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED, BOOLEAN, true) \
7171
MACRO(DD_TELEMETRY_HEARTBEAT_INTERVAL, DECIMAL, 10) \
72+
MACRO(DD_TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL, DECIMAL, 86400) \
7273
MACRO(DD_TELEMETRY_METRICS_ENABLED, BOOLEAN, true) \
7374
MACRO(DD_TELEMETRY_METRICS_INTERVAL_SECONDS, DECIMAL, 60) \
7475
MACRO(DD_TELEMETRY_DEBUG, BOOLEAN, false) \

include/datadog/telemetry/configuration.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ struct Configuration {
2929
// Interval at which the heartbeat payload will be sent.
3030
// Can be overriden by `DD_TELEMETRY_HEARTBEAT_INTERVAL` environment variable.
3131
tracing::Optional<double> heartbeat_interval_seconds;
32+
// Interval at which the extended heartbeat payload will be sent.
33+
// Default: 86400s (24h).
34+
// Can be overriden by `DD_TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL` environment
35+
// variable.
36+
tracing::Optional<double> extended_heartbeat_interval_seconds;
3237
// `integration_name` is the name of the product integrating this library.
3338
// Example: "nginx", "envoy" or "istio".
3439
tracing::Optional<std::string> integration_name;
@@ -52,6 +57,7 @@ struct FinalizedConfiguration {
5257
bool report_logs;
5358
std::chrono::steady_clock::duration metrics_interval;
5459
std::chrono::steady_clock::duration heartbeat_interval;
60+
std::chrono::steady_clock::duration extended_heartbeat_interval;
5561
std::string integration_name;
5662
std::string integration_version;
5763
std::vector<Product> products;

src/datadog/telemetry/configuration.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ tracing::Expected<Configuration> load_telemetry_env_config() {
4848
env_cfg.heartbeat_interval_seconds = *maybe_value;
4949
}
5050

51+
if (auto extended_heartbeat_interval_seconds =
52+
lookup(environment::DD_TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL)) {
53+
auto maybe_value = parse_double(*extended_heartbeat_interval_seconds);
54+
if (auto error = maybe_value.if_error()) {
55+
return *error;
56+
}
57+
58+
env_cfg.extended_heartbeat_interval_seconds = *maybe_value;
59+
}
60+
5161
return env_cfg;
5262
}
5363

@@ -112,6 +122,19 @@ tracing::Expected<FinalizedConfiguration> finalize_config(
112122
std::chrono::duration_cast<std::chrono::milliseconds>(
113123
std::chrono::duration<double>(heartbeat_interval.second));
114124

125+
// extended_heartbeat_interval_seconds
126+
auto extended_heartbeat_interval =
127+
pick(env_config->extended_heartbeat_interval_seconds,
128+
user_config.extended_heartbeat_interval_seconds, 86400);
129+
if (extended_heartbeat_interval.second <= 0.) {
130+
return Error{Error::Code::OUT_OF_RANGE_INTEGER,
131+
"Telemetry extended heartbeat polling interval must be a "
132+
"positive value"};
133+
}
134+
result.extended_heartbeat_interval =
135+
std::chrono::duration_cast<std::chrono::milliseconds>(
136+
std::chrono::duration<double>(extended_heartbeat_interval.second));
137+
115138
// integration_name
116139
std::tie(origin, result.integration_name) =
117140
pick(env_config->integration_name, user_config.integration_name,

src/datadog/telemetry/telemetry_impl.cpp

Lines changed: 93 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,39 @@ nlohmann::json encode_distributions(
198198
return j;
199199
}
200200

201+
nlohmann::json encode_configuration_field(const ConfigMetadata& config_metadata,
202+
std::size_t seq_id) {
203+
auto j = nlohmann::json{{"name", to_string(config_metadata.name)},
204+
{"value", config_metadata.value},
205+
{"seq_id", seq_id}};
206+
207+
switch (config_metadata.origin) {
208+
case ConfigMetadata::Origin::ENVIRONMENT_VARIABLE:
209+
j["origin"] = "env_var";
210+
break;
211+
case ConfigMetadata::Origin::CODE:
212+
j["origin"] = "code";
213+
break;
214+
case ConfigMetadata::Origin::REMOTE_CONFIG:
215+
j["origin"] = "remote_config";
216+
break;
217+
case ConfigMetadata::Origin::DEFAULT:
218+
j["origin"] = "default";
219+
break;
220+
}
221+
222+
if (config_metadata.error) {
223+
// clang-format off
224+
j["error"] = {
225+
{"code", config_metadata.error->code},
226+
{"message", config_metadata.error->message}
227+
};
228+
// clang-format on
229+
}
230+
231+
return j;
232+
}
233+
201234
} // namespace
202235

203236
Telemetry::Telemetry(FinalizedConfiguration config,
@@ -214,6 +247,16 @@ Telemetry::Telemetry(FinalizedConfiguration config,
214247
clock_(std::move(clock)),
215248
scheduler_(event_scheduler),
216249
host_info_(get_host_info()) {
250+
// Initialize current_configuration_ from the product configurations provided
251+
// at startup. Each config name maps to a vector ordered by precedence
252+
// (lowest to highest), so we take the last entry as the effective value.
253+
for (const auto& product : config_.products) {
254+
for (const auto& [config_name, config_metadatas] : product.configurations) {
255+
if (!config_metadatas.empty()) {
256+
current_configuration_[config_name] = config_metadatas.back();
257+
}
258+
}
259+
}
217260
app_started();
218261
schedule_tasks();
219262
}
@@ -227,6 +270,12 @@ void Telemetry::schedule_tasks() {
227270
tasks_.emplace_back(scheduler_->schedule_recurring_event(
228271
config_.metrics_interval, [this]() mutable { capture_metrics(); }));
229272
}
273+
274+
tasks_.emplace_back(scheduler_->schedule_recurring_event(
275+
config_.extended_heartbeat_interval,
276+
[this]() {
277+
send_payload("app-extended-heartbeat", extended_heartbeat_payload());
278+
}));
230279
}
231280

232281
Telemetry::~Telemetry() {
@@ -249,6 +298,7 @@ Telemetry::Telemetry(Telemetry&& rhs)
249298
rates_(std::move(rhs.rates_)),
250299
rates_snapshot_(std::move(rhs.rates_snapshot_)),
251300
distributions_(std::move(rhs.distributions_)),
301+
current_configuration_(std::move(rhs.current_configuration_)),
252302
seq_id_(rhs.seq_id_),
253303
config_seq_ids_(rhs.config_seq_ids_),
254304
host_info_(rhs.host_info_) {
@@ -274,6 +324,7 @@ Telemetry& Telemetry::operator=(Telemetry&& rhs) {
274324
std::swap(distributions_, rhs.distributions_);
275325
std::swap(seq_id_, rhs.seq_id_);
276326
std::swap(config_seq_ids_, rhs.config_seq_ids_);
327+
std::swap(current_configuration_, rhs.current_configuration_);
277328
std::swap(host_info_, rhs.host_info_);
278329
schedule_tasks();
279330
}
@@ -425,13 +476,13 @@ void Telemetry::send_payload(StringView request_type, std::string payload) {
425476
void Telemetry::send_configuration_change() {
426477
if (configuration_snapshot_.empty()) return;
427478

428-
std::vector<ConfigMetadata> current_configuration;
429-
std::swap(current_configuration, configuration_snapshot_);
479+
std::vector<ConfigMetadata> pending;
480+
std::swap(pending, configuration_snapshot_);
430481

431482
auto configuration_json = nlohmann::json::array();
432-
for (const auto& config_metadata : current_configuration) {
483+
for (const auto& config_metadata : pending) {
433484
configuration_json.emplace_back(
434-
generate_configuration_field(config_metadata));
485+
report_new_config_field(config_metadata));
435486
}
436487

437488
auto telemetry_body =
@@ -591,7 +642,7 @@ std::string Telemetry::app_started_payload() {
591642
// if (config_metadata.value.empty()) continue;
592643
for (const auto& config_metadata : config_metadatas) {
593644
configuration_json.emplace_back(
594-
generate_configuration_field(config_metadata));
645+
report_new_config_field(config_metadata));
595646
}
596647
}
597648

@@ -678,6 +729,36 @@ std::string Telemetry::app_started_payload() {
678729
return batch.dump();
679730
}
680731

732+
std::string Telemetry::extended_heartbeat_payload() {
733+
auto configuration_json = nlohmann::json::array();
734+
for (const auto& [_, config_metadata] : current_configuration_) {
735+
// Use the current seq_id without incrementing — this is a snapshot
736+
// refresh, not a new change event.
737+
auto seq_id = config_seq_ids_[config_metadata.name];
738+
configuration_json.emplace_back(
739+
encode_configuration_field(config_metadata, seq_id));
740+
}
741+
742+
auto payload = nlohmann::json{{"configuration", configuration_json}};
743+
744+
if (!config_.integration_name.empty()) {
745+
payload["integrations"] = nlohmann::json::array({
746+
nlohmann::json{{"name", config_.integration_name},
747+
{"version", config_.integration_version},
748+
{"enabled", true}},
749+
});
750+
}
751+
752+
auto extended_heartbeat = nlohmann::json{
753+
{"request_type", "app-extended-heartbeat"},
754+
{"payload", std::move(payload)},
755+
};
756+
757+
auto batch = generate_telemetry_body("message-batch");
758+
batch["payload"] = nlohmann::json::array({std::move(extended_heartbeat)});
759+
return batch.dump();
760+
}
761+
681762
nlohmann::json Telemetry::generate_telemetry_body(std::string request_type) {
682763
std::time_t tracer_time = std::chrono::duration_cast<std::chrono::seconds>(
683764
clock_().wall.time_since_epoch())
@@ -711,49 +792,21 @@ nlohmann::json Telemetry::generate_telemetry_body(std::string request_type) {
711792
});
712793
}
713794

714-
nlohmann::json Telemetry::generate_configuration_field(
795+
nlohmann::json Telemetry::report_new_config_field(
715796
const ConfigMetadata& config_metadata) {
716797
// NOTE(@dmehala): `seq_id` should start at 1 so that the go backend can
717798
// detect between non set fields.
718799
config_seq_ids_[config_metadata.name] += 1;
719-
auto seq_id = config_seq_ids_[config_metadata.name];
720-
721-
auto j = nlohmann::json{{"name", to_string(config_metadata.name)},
722-
{"value", config_metadata.value},
723-
{"seq_id", seq_id}};
724-
725-
switch (config_metadata.origin) {
726-
case ConfigMetadata::Origin::ENVIRONMENT_VARIABLE:
727-
j["origin"] = "env_var";
728-
break;
729-
case ConfigMetadata::Origin::CODE:
730-
j["origin"] = "code";
731-
break;
732-
case ConfigMetadata::Origin::REMOTE_CONFIG:
733-
j["origin"] = "remote_config";
734-
break;
735-
case ConfigMetadata::Origin::DEFAULT:
736-
j["origin"] = "default";
737-
break;
738-
}
739-
740-
if (config_metadata.error) {
741-
// clang-format off
742-
j["error"] = {
743-
{"code", config_metadata.error->code},
744-
{"message", config_metadata.error->message}
745-
};
746-
// clang-format on
747-
}
748-
749-
return j;
800+
return encode_configuration_field(config_metadata,
801+
config_seq_ids_[config_metadata.name]);
750802
}
751803

752804
void Telemetry::capture_configuration_change(
753805
const std::vector<tracing::ConfigMetadata>& new_configuration) {
754-
configuration_snapshot_.insert(configuration_snapshot_.begin(),
755-
new_configuration.begin(),
756-
new_configuration.end());
806+
for (const auto& config : new_configuration) {
807+
configuration_snapshot_.push_back(config);
808+
current_configuration_[config.name] = config;
809+
}
757810
}
758811

759812
void Telemetry::capture_metrics() {

src/datadog/telemetry/telemetry_impl.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ class Telemetry final {
5757
distributions_;
5858

5959
/// Configuration
60+
// Pending configuration changes to be sent in the next
61+
// `app-client-configuration-change` event.
6062
std::vector<tracing::ConfigMetadata> configuration_snapshot_;
63+
// Full current configuration state (initialized from app-started, updated on
64+
// every configuration change). Used for `app-extended-heartbeat`.
65+
std::unordered_map<tracing::ConfigName, tracing::ConfigMetadata>
66+
current_configuration_;
6167

6268
std::mutex log_mutex_;
6369
std::vector<telemetry::LogMessage> logs_;
@@ -143,7 +149,7 @@ class Telemetry final {
143149
tracing::Optional<std::string> stacktrace = tracing::nullopt);
144150

145151
nlohmann::json generate_telemetry_body(std::string request_type);
146-
nlohmann::json generate_configuration_field(
152+
nlohmann::json report_new_config_field(
147153
const tracing::ConfigMetadata& config_metadata);
148154

149155
// Constructs an `app-started` message using information provided when
@@ -155,6 +161,9 @@ class Telemetry final {
155161
// Constructs a message-batch containing `app-closing`, and if metrics have
156162
// been modified, a `generate-metrics` message.
157163
std::string app_closing_payload();
164+
// Constructs a message-batch containing `app-extended-heartbeat` with the
165+
// full current configuration state.
166+
std::string extended_heartbeat_payload();
158167
};
159168

160169
} // namespace datadog::telemetry

0 commit comments

Comments
 (0)