Skip to content

Commit db1bf1b

Browse files
adeshkumar1claude
andcommitted
feat(c-binding): accept explicit span timestamps
Add `start_time_ns` to `dd_span_options_t` and introduce `dd_span_finish_with_time(end_time_ns)`. Both accept wall-clock nanoseconds since the UNIX epoch; 0 preserves today's "use current time" behaviour so existing callers are unaffected. The conversion from wall-ns to the tracer's internal `TimePoint` derives the steady_clock tick from the current system/steady offset, so duration math stays correct regardless of which endpoint is explicit and which is implicit. Motivated by FFI callers (e.g. the Kong plugin) that capture timestamps in their host runtime before crossing the FFI boundary, where using "now" inside the binding would lose the pre-FFI portion of the span and bake in call-overhead skew. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1f76c5c commit db1bf1b

3 files changed

Lines changed: 63 additions & 5 deletions

File tree

binding/c/include/datadog/c/tracer.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <stddef.h>
4+
#include <stdint.h>
45

56
#if defined(_WIN32)
67
#if defined(DD_TRACE_C_BUILDING)
@@ -51,6 +52,7 @@ typedef struct {
5152
const char* service_type;
5253
const char* environment;
5354
const char* version;
55+
int64_t start_time_ns; // UNIX-epoch nanoseconds; 0 = use current time
5456
} dd_span_options_t;
5557

5658
// Error codes returned by the C binding.
@@ -179,6 +181,13 @@ DD_TRACE_C_API dd_span_t* dd_span_create_child(dd_span_t* span_handle,
179181
// @param span_handle Span handle
180182
DD_TRACE_C_API void dd_span_finish(dd_span_t* span_handle);
181183

184+
// Finish a span using an explicit end time. No-op if span_handle is NULL.
185+
//
186+
// @param span_handle Span handle
187+
// @param end_time_ns Wall-clock end time (UNIX-epoch ns); 0 = current time
188+
DD_TRACE_C_API void dd_span_finish_with_time(dd_span_t* span_handle,
189+
int64_t end_time_ns);
190+
182191
// Get the trace ID as a zero-padded hex string.
183192
//
184193
// @param span_handle Span handle

binding/c/src/tracer.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ class ContextWriter : public dd::DictWriter {
4444
}
4545
};
4646

47+
// tick is derived from the current system/steady offset so that duration
48+
// (end.tick - start.tick) stays correct across FFI-supplied timestamps.
49+
dd::TimePoint wall_ns_to_timepoint(int64_t wall_ns) {
50+
const auto wall = std::chrono::system_clock::time_point(
51+
std::chrono::duration_cast<std::chrono::system_clock::duration>(
52+
std::chrono::nanoseconds(wall_ns)));
53+
const auto offset = std::chrono::system_clock::now() - wall;
54+
return {wall, std::chrono::steady_clock::now() -
55+
std::chrono::duration_cast<dd::Duration>(offset)};
56+
}
57+
4758
dd::SpanConfig make_span_config(dd_span_options_t options) {
4859
dd::SpanConfig span_config;
4960
if (options.name != nullptr) {
@@ -64,6 +75,9 @@ dd::SpanConfig make_span_config(dd_span_options_t options) {
6475
if (options.version != nullptr) {
6576
span_config.version = options.version;
6677
}
78+
if (options.start_time_ns != 0) {
79+
span_config.start = wall_ns_to_timepoint(options.start_time_ns);
80+
}
6781
return span_config;
6882
}
6983

@@ -249,12 +263,21 @@ dd_span_t *dd_span_create_child(dd_span_t *span_handle,
249263
}
250264
}
251265

252-
void dd_span_finish(dd_span_t *span_handle) {
266+
void dd_span_finish_with_time(dd_span_t *span_handle, int64_t end_time_ns) {
253267
if (span_handle == nullptr) {
254268
return;
255269
}
256-
reinterpret_cast<dd::Span *>(span_handle)
257-
->set_end_time(std::chrono::steady_clock::now());
270+
auto *span = reinterpret_cast<dd::Span *>(span_handle);
271+
const auto end_tick = end_time_ns == 0
272+
? std::chrono::steady_clock::now()
273+
: wall_ns_to_timepoint(end_time_ns).tick;
274+
// Clamp to start: serialization casts duration to uint64_t, so a
275+
// negative duration (end before start) would ship as a huge bogus value.
276+
span->set_end_time(std::max(end_tick, span->start_time().tick));
277+
}
278+
279+
void dd_span_finish(dd_span_t *span_handle) {
280+
dd_span_finish_with_time(span_handle, 0);
258281
}
259282

260283
int dd_span_get_trace_id(dd_span_t *span_handle, char *buffer,

binding/c/test/test_c_binding.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@ TEST_CASE("tracer new propagates error", "[c_binding]") {
9191
TEST_CASE("span create, tag, finish, free", "[c_binding]") {
9292
auto ctx = make_tracer();
9393

94-
auto *span = dd_tracer_create_span(ctx.tracer, {.name = "test.op"});
94+
const int64_t start_ns = 1'700'000'000'000'000'000LL;
95+
const int64_t end_ns = start_ns + 42'000'000LL;
96+
97+
auto *span = dd_tracer_create_span(
98+
ctx.tracer, {.name = "test.op", .start_time_ns = start_ns});
9599
REQUIRE(span != nullptr);
96100

97101
dd_span_set_tag(span, "http.method", "GET");
@@ -100,7 +104,7 @@ TEST_CASE("span create, tag, finish, free", "[c_binding]") {
100104
dd_span_set_error(span, 1);
101105
dd_span_set_error_message(span, "something broke");
102106

103-
dd_span_finish(span);
107+
dd_span_finish_with_time(span, end_ns);
104108
dd_span_free(span);
105109

106110
const auto &sd = ctx.collector->first_span();
@@ -109,6 +113,28 @@ TEST_CASE("span create, tag, finish, free", "[c_binding]") {
109113
CHECK(sd.service == "user-service");
110114
CHECK(sd.error == true);
111115
CHECK(sd.tags.at("error.message") == "something broke");
116+
117+
CHECK(std::chrono::duration_cast<std::chrono::nanoseconds>(
118+
sd.start.wall.time_since_epoch())
119+
.count() == start_ns);
120+
CHECK(std::chrono::abs(sd.duration -
121+
std::chrono::nanoseconds(end_ns - start_ns)) <
122+
std::chrono::milliseconds(1));
123+
}
124+
125+
TEST_CASE("finish with end before start clamps to zero duration",
126+
"[c_binding]") {
127+
auto ctx = make_tracer();
128+
129+
const int64_t start_ns = 1'700'000'000'000'000'000LL;
130+
auto *span = dd_tracer_create_span(
131+
ctx.tracer, {.name = "reversed", .start_time_ns = start_ns});
132+
REQUIRE(span != nullptr);
133+
134+
dd_span_finish_with_time(span, start_ns - 1'000'000LL);
135+
dd_span_free(span);
136+
137+
CHECK(ctx.collector->first_span().duration >= std::chrono::nanoseconds(0));
112138
}
113139

114140
TEST_CASE("create span with resource", "[c_binding]") {

0 commit comments

Comments
 (0)