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
132 changes: 86 additions & 46 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ inherits = "release"
# These are to satistfy the build with libdatadog as a path dependency inside
# components-rs. There may be a better way to fix this, but I'm alreday two
# tasks removed from what I'm trying to do, so pushing forward.
#
# Every dependency that a libdatadog crate inherits via `{ workspace = true }`
# must be declared here, because those crates resolve their workspace
# inheritance against this manifest when built as path dependencies. Keep this
# list in sync with libdatadog's own `[workspace.dependencies]`; libdatadog
# #2253 consolidated `anyhow`, `serde`, `tokio` and `tracing` to the workspace
# level, so they are mirrored here too.
[workspace.dependencies]
anyhow = { version = "1.0", default-features = false }
arc-swap = "1.7.1"
hyper = { version = "1.6", features = [
"http1",
Expand All @@ -44,6 +52,9 @@ hyper-util = { version = "0.1.10", features = [
"client",
"client-legacy",
] }
serde = { version = "1.0", default-features = false }
tokio = { version = "1.36", default-features = false }
tracing = { version = "0.1", default-features = false }

[workspace.lints]
# empty for compat with libdatadog
1 change: 1 addition & 0 deletions cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ rename_types = "PascalCase"
"ZendString" = "_zend_string"
"FILE" = "FILE"
"EndpointConfig" = "ddog_crasht_EndpointConfig"
"Metadata" = "ddog_crasht_Metadata"
"MaybeOwnedZendString" = "_zend_string *"

[enum]
Expand Down
2 changes: 2 additions & 0 deletions components-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ libdd-data-pipeline = { path = "../libdatadog/libdd-data-pipeline" }
libdd-tinybytes = { path = "../libdatadog/libdd-tinybytes" }
libdd-trace-utils = { path = "../libdatadog/libdd-trace-utils" }
libdd-trace-stats = { path = "../libdatadog/libdd-trace-stats" }
libdd-trace-protobuf = { path = "../libdatadog/libdd-trace-protobuf" }
libdd-crashtracker = { path = "../libdatadog/libdd-crashtracker", default-features = false, features = ["collector"] }
libdd-crashtracker-ffi = { path = "../libdatadog/libdd-crashtracker-ffi", default-features = false, features = ["collector"] }
libdd-library-config-ffi = { path = "../libdatadog/libdd-library-config-ffi", default-features = false }
spawn_worker = { path = "../libdatadog/spawn_worker" }
Expand Down
17 changes: 13 additions & 4 deletions components-rs/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@

typedef struct ddog_Endpoint ddog_Endpoint;

typedef struct ddog_Tag ddog_Tag;

/**
* Holds the raw parts of a Rust Vec; it should only be created from Rust,
* never from C.
Expand Down Expand Up @@ -412,8 +410,11 @@ typedef enum ddog_RemoteConfigCapabilities {
DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_ENABLE_LIVE_DEBUGGING = 41,
DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_DD_MULTICONFIG = 42,
DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_TRACE_TAGGING_RULES = 43,
DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_EXTENDED_DATA_COLLECTION = 44,
DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_MULTICONFIG = 45,
DDOG_REMOTE_CONFIG_CAPABILITIES_FFE_FLAG_CONFIGURATION_RULES = 46,
DDOG_REMOTE_CONFIG_CAPABILITIES_DD_DATA_STREAMS_TRANSACTION_EXTRACTORS = 47,
DDOG_REMOTE_CONFIG_CAPABILITIES_LLM_OBS_ACTIVATION = 48,
} ddog_RemoteConfigCapabilities;

typedef enum ddog_RemoteConfigProduct {
Expand All @@ -426,6 +427,7 @@ typedef enum ddog_RemoteConfigProduct {
DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES,
DDOG_REMOTE_CONFIG_PRODUCT_FFE_FLAGS,
DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER,
DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER_SYMBOL_DB,
} ddog_RemoteConfigProduct;

typedef enum ddog_SpanProbeTarget {
Expand Down Expand Up @@ -1110,9 +1112,10 @@ typedef struct ddog_TelemetryWorkerBuilder ddog_TelemetryWorkerBuilder;
* The worker won't send data to the agent until you call `TelemetryWorkerHandle::send_start`
*
* To stop the worker, call `TelemetryWorkerHandle::send_stop` which trigger flush asynchronously
* then `TelemetryWorkerHandle::wait_for_shutdown`
* then `TelemetryWorkerHandle::wait_for_shutdown` (native only — wasm callers rely on the
* SharedRuntime worker JoinHandle instead).
*/
typedef struct ddog_TelemetryWorkerHandle ddog_TelemetryWorkerHandle;
typedef struct ddog_TelemetryWorkerHandle_NativeCapabilities ddog_TelemetryWorkerHandle_NativeCapabilities;

typedef enum ddog_Option_U64_Tag {
DDOG_OPTION_U64_SOME_U64,
Expand All @@ -1128,6 +1131,12 @@ typedef struct ddog_Option_U64 {
};
} ddog_Option_U64;

/**
* FFI-facing alias: the C ABI surface is native-only, so the worker handle is
* always pinned to [`NativeCapabilities`].
*/
typedef struct ddog_TelemetryWorkerHandle_NativeCapabilities ddog_TelemetryWorkerHandle;

typedef enum ddog_Option_Bool_Tag {
DDOG_OPTION_BOOL_SOME_BOOL,
DDOG_OPTION_BOOL_NONE_BOOL,
Expand Down
30 changes: 26 additions & 4 deletions components-rs/datadog.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,26 @@ struct ddog_Endpoint *datadog_otel_metrics_endpoint_from_url(ddog_CharSlice url)

struct ddog_Endpoint *datadog_otel_metrics_endpoint_from_agent_url(ddog_CharSlice url);

void datadog_endpoint_as_crashtracker_config(const struct ddog_Endpoint *endpoint,
void (*callback)(ddog_crasht_EndpointConfig, void*),
void *userdata);
/**
* Initialize crashtracking, selecting the receiver strategy for this process:
* - Linux, sidecar host (`master_pid == getpid()`): the in-process thread-mode sidecar can't
* serve its own crash, so spawn a fork+exec subprocess receiver (like the standalone daemon),
* resolving frames there since a crashing process can't reliably symbolize itself.
* - Linux, worker/collector: connect to the sidecar IPC socket and upgrade it to a crashtracker
* receiver on crash (`SOCK_SEQPACKET` + `enter_crashtracker_receiver`), streaming the report
* over that single socket and resolving frames in-process.
* - other unix (macOS): no sidecar upgrade; the default connector reaches the socket path.
*
* `master_pid` is the thread-mode master listener PID (0 if none): it keys the IPC socket and, on
* Linux, distinguishes the host from a worker.
*
* # Safety
* `endpoint` must point to a valid `Endpoint`; `metadata`'s borrowed strings/tags must outlive the
* call (they are copied into owned storage before it returns).
*/
ddog_MaybeError datadog_crashtracker_init(const struct ddog_Endpoint *endpoint,
ddog_crasht_Metadata metadata,
int32_t master_pid);

ddog_Configurator *ddog_library_configurator_new_dummy(bool debug_logs, ddog_CharSlice language);

Expand Down Expand Up @@ -189,7 +206,12 @@ void ddog_rshutdown_remote_config(struct ddog_RemoteConfigState *remote_config);

void ddog_shutdown_remote_config(struct ddog_RemoteConfigState*);

void ddog_drop_probe(struct ddog_Probe probe);
/**
* Free the FFI-owned allocations in a `Probe` (the `tags` vec and the nested
* span-decoration / log allocations) by consuming it; borrowed `CharSlice`s are
* left untouched. Called from `dd_probe_dtor` when a probe is uninstalled.
*/
void ddog_drop_probe(struct ddog_Probe);

void ddog_log_debugger_data(const struct ddog_Vec_DebuggerPayload *payloads);

Expand Down
155 changes: 137 additions & 18 deletions components-rs/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use std::ffi::{c_char, OsStr};
use std::path::Path;
use std::ptr::null_mut;
use uuid::Uuid;

#[cfg(unix)]
use datadog_sidecar::crashtracker::crashtracker_receiver_request_bytes;
pub use libdd_crashtracker_ffi::*;
pub use libdd_library_config_ffi::*;
pub use datadog_sidecar_ffi::*;
Expand Down Expand Up @@ -196,26 +197,144 @@ pub unsafe extern "C" fn datadog_otel_metrics_endpoint_from_agent_url(url: CharS
}
}

#[no_mangle]
/// Initialize crashtracking, selecting the receiver strategy for this process:
/// - Linux, sidecar host (`master_pid == getpid()`): the in-process thread-mode sidecar can't
/// serve its own crash, so spawn a fork+exec subprocess receiver (like the standalone daemon),
/// resolving frames there since a crashing process can't reliably symbolize itself.
/// - Linux, worker/collector: connect to the sidecar IPC socket and upgrade it to a crashtracker
/// receiver on crash (`SOCK_SEQPACKET` + `enter_crashtracker_receiver`), streaming the report
/// over that single socket and resolving frames in-process.
/// - other unix (macOS): no sidecar upgrade; the default connector reaches the socket path.
///
/// `master_pid` is the thread-mode master listener PID (0 if none): it keys the IPC socket and, on
/// Linux, distinguishes the host from a worker.
///
/// # Safety
/// `endpoint` must point to a valid `Endpoint`; `metadata`'s borrowed strings/tags must outlive the
/// call (they are copied into owned storage before it returns).
#[cfg(unix)]
pub unsafe extern "C" fn datadog_endpoint_as_crashtracker_config(
#[no_mangle]
#[allow(clippy::missing_safety_doc)]
pub unsafe extern "C" fn datadog_crashtracker_init(
endpoint: &Endpoint,
callback: unsafe extern "C" fn(EndpointConfig<'_>, *mut std::ffi::c_void),
userdata: *mut std::ffi::c_void,
) {
let url_str = endpoint.url.to_string();
unsafe {
callback(
EndpointConfig {
url: CharSlice::from(url_str.as_str()),
api_key: CharSlice::from(endpoint.api_key.as_deref().unwrap_or("")),
test_token: CharSlice::from(endpoint.test_token.as_deref().unwrap_or("")),
timeout: endpoint.timeout_ms,
use_system_resolver: endpoint.use_system_resolver,
},
userdata,
);
metadata: Metadata,
master_pid: i32,
) -> MaybeError {
use libdd_crashtracker::{CrashtrackerConfiguration, StacktraceCollection};

let result = (|| -> anyhow::Result<()> {
let metadata: libdd_crashtracker::Metadata = metadata.try_into()?;

let mut builder = CrashtrackerConfiguration::builder()
.collect_all_threads(true)
.timeout(std::time::Duration::from_millis(5000))
.endpoint_use_system_resolver(endpoint.use_system_resolver)
.endpoint_url(&endpoint.url.to_string());
if let Some(api_key) = endpoint.api_key.as_deref() {
builder = builder.endpoint_api_key(api_key);
}
if let Some(test_token) = endpoint.test_token.as_deref() {
builder = builder.endpoint_test_token(test_token);
}
if endpoint.timeout_ms != 0 {
builder = builder.endpoint_timeout_ms(endpoint.timeout_ms);
}

#[cfg(target_os = "linux")]
{
// Worker/collector: open a fresh connection to the sidecar IPC socket, upgrade it with
// the SEQPACKET connector, and resolve frames in-process.
if master_pid == 0 || master_pid != std::process::id() as i32 {
let socket_path = datadog_sidecar::crashtracker::crashtracker_ipc_socket_path(
master_pid as u32,
datadog_sidecar::config::FromEnv::ipc_mode(),
);
// Prime the request bytes outside the crash handler so the connector never
// allocates in signal context.
let _ = crashtracker_receiver_request_bytes();
let config = builder
.resolve_frames(StacktraceCollection::EnabledWithInprocessSymbols)
.unix_socket_path(socket_path.to_string_lossy().into_owned())
.unix_socket_connector(
datadog_sidecar::crashtracker::connect_to_sidecar_receiver,
)
.build()?;
return libdd_crashtracker::init(
config,
libdd_crashtracker::CrashtrackerReceiverConfig::default(),
metadata,
);
}
// Thread-mode host: its in-process sidecar can't serve its own crash, so spawn a
// transient fork+exec subprocess receiver and resolve frames there.
let config = builder
.resolve_frames(StacktraceCollection::EnabledWithSymbolsInReceiver)
.build()?;
let receiver_config =
datadog_sidecar::build_crashtracker_receiver_config(None, None)?;
libdd_crashtracker::init(config, receiver_config, metadata)
}

// macOS can't open a fresh SOCK_SEQPACKET connection signal-safely, so reuse the
// already-open sidecar fd and upgrade it at crash time (no-op if there's no connection).
// The path is a placeholder the connector ignores; it just has to be non-empty so the
// crashtracker takes the connector path.
#[cfg(target_os = "macos")]
{
// Prime the request bytes outside the crash handler so the connector never
// allocates in signal context.
let _ = crashtracker_receiver_request_bytes();
let config = builder
.resolve_frames(StacktraceCollection::EnabledWithInprocessSymbols)
.unix_socket_path("datadog-sidecar-crashtracker".to_string())
.unix_socket_connector(reuse_sidecar_fd_connector)
.build()?;
libdd_crashtracker::init(
config,
libdd_crashtracker::CrashtrackerReceiverConfig::default(),
metadata,
)
}

#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
let _ = (master_pid, builder, metadata);
Ok(())
}
})();
match result {
Ok(()) => MaybeError::None,
Err(e) => {
MaybeError::Some(Error::from(format!("{e:?}")))
}
}
}

/// On macos we cannot easily create a new signal safe connection to the sidecar, so we reuse the
/// already open fd from datadog_sidecar_for_signal.
#[cfg(target_os = "macos")]
fn reuse_sidecar_fd_connector(_unix_socket_path: &str) -> std::os::fd::RawFd {
extern "C" {
// Set by the sidecar connect path (sidecar.c) to the live transport for best-effort
// signal-handler use; null when there is no connection. The transport pointer is stable
// across transparent reconnects (only its inner sender is swapped), so reading the fd
// through it stays current. Typed as an opaque pointer to keep the `extern` block FFI-safe;
// cast to the real type below.
static mut datadog_sidecar_for_signal: *mut std::ffi::c_void;
}

// Best-effort, signal context: read the transport pointer and get its current fd via
// SidecarTransport::signal_fd (which uses get_mut, never locking). Going through the raw
// pointer knowingly bypasses aliasing checks — the crashing thread is the only realistic
// accessor.
let transport = unsafe { datadog_sidecar_for_signal }
as *mut datadog_sidecar::service::blocking::SidecarTransport;
if transport.is_null() {
return -1;
}
let fd = unsafe { (*transport).as_raw_fd() };
let bytes = crashtracker_receiver_request_bytes();
let sent = unsafe { libc::send(dup, bytes.as_ptr() as *const libc::c_void, bytes.len(), 0) };
}

// Hack: Without this, the PECL build of the tracer does not contain the ddog_library_* functions
Expand Down
Binary file not shown.
16 changes: 8 additions & 8 deletions components-rs/remote_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,13 +650,13 @@ pub extern "C" fn ddog_remote_configs_service_env_change(
tags: &libdd_common_ffi::Vec<Tag>,
process_tags: &libdd_common_ffi::Vec<Tag>,
) -> bool {
let new_target = Target {
service: service.to_utf8_lossy().to_string(),
env: env.to_utf8_lossy().to_string(),
app_version: version.to_utf8_lossy().to_string(),
tags: tags.as_slice().to_vec(),
process_tags: process_tags.as_slice().to_vec(),
};
let new_target = Target::new(
service.to_utf8_lossy().to_string(),
env.to_utf8_lossy().to_string(),
version.to_utf8_lossy().to_string(),
tags.as_slice().iter().map(|t| t.to_string()).collect(),
process_tags.as_slice().iter().map(|t| t.to_string()).collect(),
);

if let Some(target) = remote_config.manager.get_target() {
if **target == new_target {
Expand Down Expand Up @@ -798,7 +798,7 @@ pub unsafe extern "C" fn ddog_send_debugger_diagnostics<'a>(
remote_config_state
.manager
.get_target()
.map_or("", |t| t.service.as_str()),
.map_or("", |t| t.service()),
);
let mut payload = ddog_debugger_diagnostics_create_unboxed(
probe,
Expand Down
5 changes: 0 additions & 5 deletions components-rs/sidecar.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,6 @@ ddog_MaybeError ddog_sidecar_set_test_session_token(struct ddog_SidecarTransport
void ddog_sidecar_reconnect(struct ddog_SidecarTransport **transport,
struct ddog_SidecarTransport *(*factory)(void));

/**
* Return the path of the crashtracker unix domain socket.
*/
ddog_CharSlice ddog_sidecar_get_crashtracker_unix_socket_path(void);

/**
* Gets an agent info reader.
*/
Expand Down
3 changes: 2 additions & 1 deletion components-rs/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::trace_filter;
use datadog_ipc::shm_stats::{OwnedShmSpanInput, ShmSpanConcentrator, ShmSpanInput, MAX_PEER_TAGS};
use datadog_sidecar::service::blocking::{add_span_to_concentrator, SidecarTransport};
use libdd_trace_stats::span_concentrator::FixedAggregationKey;
use libdd_trace_protobuf::pb::Trilean;
use libdd_common_ffi::slice::{AsBytes, CharSlice};
use std::collections::HashMap;
use std::ffi::{c_char, c_void};
Expand Down Expand Up @@ -148,7 +149,7 @@ fn build_fixed_key<'a>(span: &'a PhpSpanStats<'a>) -> FixedAggregationKey<&'a st
http_endpoint: extract_http_endpoint(span),
http_status_code: extract_http_status_code(span),
is_synthetics_request: is_synthetics_request(span),
is_trace_root: span.is_trace_root,
is_trace_root: if span.is_trace_root { Trilean::True } else { Trilean::False },
grpc_status_code: extract_grpc_status_code(span),
service_source: char_slice_str(span.service_source),
}
Expand Down
Loading
Loading