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
17 changes: 17 additions & 0 deletions binding/c/include/datadog/c/tracer.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <stddef.h>
#include <stdint.h>

#if defined(_WIN32)
#if defined(DD_TRACE_C_BUILDING)
Expand Down Expand Up @@ -43,6 +44,9 @@ typedef enum {
DD_OPT_INTEGRATION_VERSION = 5
} dd_tracer_option;

// Sentinel for start_time_ns meaning "use the current time."
static const int64_t DD_TRACE_CURRENT_TIME = -1;
Comment thread
adeshkumar1 marked this conversation as resolved.

// Options for creating a span. Unset fields default to NULL.
typedef struct {
const char* name;
Expand All @@ -51,6 +55,7 @@ typedef struct {
const char* service_type;
const char* environment;
const char* version;
int64_t start_time_ns; // Unix-epoch nanoseconds or DD_TRACE_CURRENT_TIME.
} dd_span_options_t;

// Error codes returned by the C binding.
Expand Down Expand Up @@ -179,6 +184,18 @@ DD_TRACE_C_API dd_span_t* dd_span_create_child(dd_span_t* span_handle,
// @param span_handle Span handle
DD_TRACE_C_API void dd_span_finish(dd_span_t* span_handle);

// Set the end time of a span from an explicit wall-clock timestamp. The
// span itself is finished when dd_span_free destroys it. To use the
// current time, call dd_span_finish instead. If end_time_ns is earlier
// than the span's start time, the resulting duration is clamped to zero.
// No-op if span_handle is NULL.
//
Comment thread
adeshkumar1 marked this conversation as resolved.
// @param span_handle Span handle
// @param end_time_ns Wall-clock end time (Unix-epoch nanoseconds).
// DD_TRACE_CURRENT_TIME does not apply here.
DD_TRACE_C_API void dd_span_set_end_time(dd_span_t* span_handle,
int64_t end_time_ns);

// Get the trace ID as a zero-padded hex string.
//
// @param span_handle Span handle
Expand Down
38 changes: 38 additions & 0 deletions binding/c/src/tracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ class ContextWriter : public dd::DictWriter {
}
};

std::chrono::system_clock::time_point wall_ns_to_system_time(int64_t wall_ns) {
return std::chrono::system_clock::time_point(
std::chrono::round<std::chrono::system_clock::duration>(
std::chrono::nanoseconds(wall_ns)));
}

// Create a TimePoint from a wall-clock timestamp in nanoseconds. The tick is
// derived from the wall clock using the current offset between the system and
// the steady clocks. This keeps duration computed from ticks accurate when
// the start time is supplied via FFI.
dd::TimePoint wall_ns_to_timepoint(int64_t wall_ns) {
const auto wall = wall_ns_to_system_time(wall_ns);
const auto now_wall = std::chrono::system_clock::now();
const auto now_tick = std::chrono::steady_clock::now();
return {wall,
now_tick - std::chrono::duration_cast<dd::Duration>(now_wall - wall)};
}

dd::SpanConfig make_span_config(dd_span_options_t options) {
dd::SpanConfig span_config;
if (options.name != nullptr) {
Expand All @@ -64,6 +82,9 @@ dd::SpanConfig make_span_config(dd_span_options_t options) {
if (options.version != nullptr) {
span_config.version = options.version;
}
if (options.start_time_ns != DD_TRACE_CURRENT_TIME) {
span_config.start = wall_ns_to_timepoint(options.start_time_ns);
}
return span_config;
}

Expand Down Expand Up @@ -257,6 +278,23 @@ void dd_span_finish(dd_span_t *span_handle) {
->set_end_time(std::chrono::steady_clock::now());
}

void dd_span_set_end_time(dd_span_t *span_handle, int64_t end_time_ns) {
if (span_handle == nullptr) {
return;
}
auto *span = reinterpret_cast<dd::Span *>(span_handle);
// Anchor end.tick to start.tick + wall delta so NTP adjustments during the
// span's lifetime don't skew duration. Clamp to start.tick on negative
// deltas — serialization casts duration to uint64_t.
const auto start = span->start_time();
const auto end_wall = wall_ns_to_system_time(end_time_ns);
const auto duration =
end_wall > start.wall
? std::chrono::duration_cast<dd::Duration>(end_wall - start.wall)
: dd::Duration::zero();
span->set_end_time(start.tick + duration);
}

int dd_span_get_trace_id(dd_span_t *span_handle, char *buffer,
size_t buffer_size) {
if (span_handle == nullptr || buffer == nullptr || buffer_size == 0) {
Expand Down
29 changes: 27 additions & 2 deletions binding/c/test/test_c_binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ TEST_CASE("tracer new propagates error", "[c_binding]") {
TEST_CASE("span create, tag, finish, free", "[c_binding]") {
auto ctx = make_tracer();

auto *span = dd_tracer_create_span(ctx.tracer, {.name = "test.op"});
const int64_t start_ns = 1'700'000'000'000'000'000LL;
const int64_t end_ns = start_ns + 42'000'000LL;

auto *span = dd_tracer_create_span(
ctx.tracer, {.name = "test.op", .start_time_ns = start_ns});
REQUIRE(span != nullptr);

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

dd_span_finish(span);
dd_span_set_end_time(span, end_ns);
dd_span_free(span);

const auto &sd = ctx.collector->first_span();
Expand All @@ -109,6 +113,26 @@ TEST_CASE("span create, tag, finish, free", "[c_binding]") {
CHECK(sd.service == "user-service");
CHECK(sd.error == true);
CHECK(sd.tags.at("error.message") == "something broke");

CHECK(std::chrono::duration_cast<std::chrono::nanoseconds>(
sd.start.wall.time_since_epoch())
.count() == start_ns);
CHECK(std::chrono::abs(sd.duration -
std::chrono::nanoseconds(end_ns - start_ns)) <
std::chrono::milliseconds(1));
}

TEST_CASE("set_end_time before start clamps to zero duration", "[c_binding]") {
auto ctx = make_tracer();

const int64_t past_ns = 1'700'000'000'000'000'000LL;
auto *span = dd_tracer_create_span(
ctx.tracer, {.name = "reversed", .start_time_ns = past_ns});
REQUIRE(span != nullptr);
dd_span_set_end_time(span, past_ns - 1'000'000LL);
dd_span_free(span);

CHECK(ctx.collector->first_span().duration >= std::chrono::nanoseconds(0));
}

TEST_CASE("create span with resource", "[c_binding]") {
Expand Down Expand Up @@ -253,6 +277,7 @@ TEST_CASE("null arguments do not crash", "[c_binding]") {
dd_span_set_error_message(nullptr, "msg");
dd_span_inject(nullptr, test_header_writer);
dd_span_finish(nullptr);
dd_span_set_end_time(nullptr, 0);
dd_span_set_resource(nullptr, "res");
dd_span_set_service(nullptr, "svc");
}
Loading