Skip to content

Commit 3b12728

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 3b12728

3 files changed

Lines changed: 61 additions & 5 deletions

File tree

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

Lines changed: 13 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,9 @@ typedef struct {
5152
const char* service_type;
5253
const char* environment;
5354
const char* version;
55+
// Wall-clock span start time in nanoseconds since the UNIX epoch.
56+
// 0 means "use the current time".
57+
int64_t start_time_ns;
5458
} dd_span_options_t;
5559

5660
// Error codes returned by the C binding.
@@ -179,6 +183,15 @@ DD_TRACE_C_API dd_span_t* dd_span_create_child(dd_span_t* span_handle,
179183
// @param span_handle Span handle
180184
DD_TRACE_C_API void dd_span_finish(dd_span_t* span_handle);
181185

186+
// Finish a span using an explicit end time given as wall-clock nanoseconds
187+
// since the UNIX epoch. If end_time_ns is 0, behaves like dd_span_finish
188+
// (uses the current time). No-op if span_handle is NULL.
189+
//
190+
// @param span_handle Span handle
191+
// @param end_time_ns Wall-clock end time in nanoseconds since the UNIX epoch
192+
DD_TRACE_C_API void dd_span_finish_with_time(dd_span_t* span_handle,
193+
int64_t end_time_ns);
194+
182195
// Get the trace ID as a zero-padded hex string.
183196
//
184197
// @param span_handle Span handle

binding/c/src/tracer.cpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#include "datadog/c/tracer.h"
22

3+
#include <datadog/clock.h>
34
#include <datadog/hex.h>
45
#include <datadog/trace_segment.h>
56
#include <datadog/tracer.h>
67

8+
#include <chrono>
79
#include <cstddef>
810
#include <cstring>
911
#include <string>
@@ -44,6 +46,21 @@ class ContextWriter : public dd::DictWriter {
4446
}
4547
};
4648

49+
// Convert a wall-clock timestamp (nanoseconds since UNIX epoch) into a
50+
// TimePoint. The steady-clock tick is derived from the current offset
51+
// between system_clock::now() and steady_clock::now() so that duration
52+
// calculations (which are done in steady-clock space) remain meaningful.
53+
dd::TimePoint wall_ns_to_timepoint(int64_t wall_ns) {
54+
const auto wall = std::chrono::system_clock::time_point(
55+
std::chrono::duration_cast<std::chrono::system_clock::duration>(
56+
std::chrono::nanoseconds(wall_ns)));
57+
const auto now_wall = std::chrono::system_clock::now();
58+
const auto now_tick = std::chrono::steady_clock::now();
59+
const auto offset = now_wall - wall;
60+
return dd::TimePoint{
61+
wall, now_tick - std::chrono::duration_cast<dd::Duration>(offset)};
62+
}
63+
4764
dd::SpanConfig make_span_config(dd_span_options_t options) {
4865
dd::SpanConfig span_config;
4966
if (options.name != nullptr) {
@@ -64,6 +81,9 @@ dd::SpanConfig make_span_config(dd_span_options_t options) {
6481
if (options.version != nullptr) {
6582
span_config.version = options.version;
6683
}
84+
if (options.start_time_ns != 0) {
85+
span_config.start = wall_ns_to_timepoint(options.start_time_ns);
86+
}
6787
return span_config;
6888
}
6989

@@ -249,12 +269,20 @@ dd_span_t *dd_span_create_child(dd_span_t *span_handle,
249269
}
250270
}
251271

252-
void dd_span_finish(dd_span_t *span_handle) {
272+
void dd_span_finish_with_time(dd_span_t *span_handle, int64_t end_time_ns) {
253273
if (span_handle == nullptr) {
254274
return;
255275
}
256-
reinterpret_cast<dd::Span *>(span_handle)
257-
->set_end_time(std::chrono::steady_clock::now());
276+
auto *span = reinterpret_cast<dd::Span *>(span_handle);
277+
if (end_time_ns == 0) {
278+
span->set_end_time(std::chrono::steady_clock::now());
279+
} else {
280+
span->set_end_time(wall_ns_to_timepoint(end_time_ns).tick);
281+
}
282+
}
283+
284+
void dd_span_finish(dd_span_t *span_handle) {
285+
dd_span_finish_with_time(span_handle, 0);
258286
}
259287

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

binding/c/test/test_c_binding.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include <datadog/c/tracer.h>
22
#include <datadog/tracer.h>
33

4+
#include <chrono>
5+
46
#include "mocks/collectors.h"
57
#include "null_logger.h"
68
#include "test.h"
@@ -91,7 +93,11 @@ TEST_CASE("tracer new propagates error", "[c_binding]") {
9193
TEST_CASE("span create, tag, finish, free", "[c_binding]") {
9294
auto ctx = make_tracer();
9395

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

97103
dd_span_set_tag(span, "http.method", "GET");
@@ -100,7 +106,7 @@ TEST_CASE("span create, tag, finish, free", "[c_binding]") {
100106
dd_span_set_error(span, 1);
101107
dd_span_set_error_message(span, "something broke");
102108

103-
dd_span_finish(span);
109+
dd_span_finish_with_time(span, end_ns);
104110
dd_span_free(span);
105111

106112
const auto &sd = ctx.collector->first_span();
@@ -109,6 +115,15 @@ TEST_CASE("span create, tag, finish, free", "[c_binding]") {
109115
CHECK(sd.service == "user-service");
110116
CHECK(sd.error == true);
111117
CHECK(sd.tags.at("error.message") == "something broke");
118+
119+
const auto start_wall_ns =
120+
std::chrono::duration_cast<std::chrono::nanoseconds>(
121+
sd.start.wall.time_since_epoch())
122+
.count();
123+
CHECK(start_wall_ns == start_ns);
124+
const auto diff =
125+
sd.duration - std::chrono::nanoseconds(end_ns - start_ns);
126+
CHECK(std::chrono::abs(diff) < std::chrono::milliseconds(1));
112127
}
113128

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

0 commit comments

Comments
 (0)