Skip to main content

datadog_agent_runtime/env/
mod.rs

1//! Environment providers backed by the Datadog Agent.
2//!
3//! [`RemoteAgentEnvironmentProvider`] gives a remote agent its hostname, autodiscovery events, and workload metadata.
4//! In connected mode, each of those comes from the Datadog Agent over its own connection. In standalone mode, the
5//! hostname is fixed and the rest is unavailable.
6
7use std::{future::Future, num::NonZeroUsize, path::PathBuf, time::Duration};
8
9use datadog_agent_commons::ipc::config::RemoteAgentClientConfiguration;
10use saluki_core::accounting::ComponentRegistry;
11use saluki_core::health::HealthRegistry;
12use saluki_core::runtime::Supervisor;
13use saluki_core::support::SubsystemIdentifier;
14use saluki_env::{
15    autodiscovery::providers::BoxedAutodiscoveryProvider,
16    host::providers::{BoxedHostProvider, FixedHostProvider},
17    EnvironmentProvider,
18};
19use saluki_error::{generic_error, GenericError};
20use tracing::warn;
21
22use crate::facade::AuthoritativeConfiguration;
23
24mod autodiscovery;
25pub use self::autodiscovery::RemoteAgentAutodiscoveryProvider;
26
27mod host;
28pub use self::host::RemoteAgentHostProvider;
29
30mod workload;
31pub use self::workload::RemoteAgentWorkloadProvider;
32
33// SAFETY: The value is demonstrably not zero.
34const DEFAULT_STRING_INTERNER_SIZE_BYTES: NonZeroUsize = NonZeroUsize::new(512 * 1024).unwrap();
35const DEFAULT_CONTAINERD_CONNECTION_TIMEOUT: Duration = Duration::from_secs(1);
36const DEFAULT_CONTAINERD_QUERY_TIMEOUT: Duration = Duration::from_secs(5);
37
38pub(crate) fn root_provider_id() -> SubsystemIdentifier {
39    SubsystemIdentifier::from_segments(["env_provider"])
40}
41
42/// Settings for the host provider.
43#[derive(Clone, Debug, Default)]
44#[non_exhaustive]
45pub struct HostSettings {
46    /// Hostname to report, if one is configured explicitly.
47    ///
48    /// Required in standalone mode, where it's reported as-is. In connected mode, the hostname comes from the Datadog
49    /// Agent, which applies its own `hostname` setting, so this is unused.
50    pub hostname: Option<String>,
51}
52
53/// Settings for the workload provider.
54#[derive(Clone, Debug)]
55#[non_exhaustive]
56pub struct WorkloadSettings {
57    /// Size, in bytes, of the string interner shared by the workload metadata collectors.
58    ///
59    /// The interner holds the entity IDs and tags received from the Datadog Agent and container runtimes. Hosts with
60    /// many containers may need to increase it. Defaults to 512 KiB.
61    pub string_interner_size_bytes: NonZeroUsize,
62
63    /// Path to the containerd gRPC socket, if one is configured explicitly.
64    ///
65    /// Defaults to `None`, which probes the well-known socket paths.
66    pub containerd_socket_path: Option<PathBuf>,
67
68    /// Timeout for establishing a containerd gRPC connection. Defaults to 1 second.
69    pub containerd_connection_timeout: Duration,
70
71    /// Per-RPC timeout for containerd API calls. Defaults to 5 seconds.
72    pub containerd_query_timeout: Duration,
73
74    /// Root of the procfs filesystem to read container processes from, if one is configured explicitly.
75    ///
76    /// Defaults to `None`, which detects the root automatically.
77    pub proc_root: Option<PathBuf>,
78
79    /// Root of the cgroupfs filesystem to read container cgroups from, if one is configured explicitly.
80    ///
81    /// Defaults to `None`, which detects the root automatically.
82    pub cgroup_root: Option<PathBuf>,
83}
84
85impl WorkloadSettings {
86    /// Creates a new `WorkloadSettings` with the given string interner size, and every other setting at its default.
87    pub fn new(string_interner_size_bytes: NonZeroUsize) -> Self {
88        Self {
89            string_interner_size_bytes,
90            ..Self::default()
91        }
92    }
93}
94
95impl Default for WorkloadSettings {
96    fn default() -> Self {
97        Self {
98            string_interner_size_bytes: DEFAULT_STRING_INTERNER_SIZE_BYTES,
99            containerd_socket_path: None,
100            containerd_connection_timeout: DEFAULT_CONTAINERD_CONNECTION_TIMEOUT,
101            containerd_query_timeout: DEFAULT_CONTAINERD_QUERY_TIMEOUT,
102            proc_root: None,
103            cgroup_root: None,
104        }
105    }
106}
107
108/// Environment provider for a remote agent.
109///
110/// This environment provider is designed for a remote agent's normal deployment environment, which is running alongside
111/// the Datadog Agent. The underlying providers communicate directly with the Datadog Agent to receive information such
112/// as the hostname, entity tags, workload metadata events, and more.
113///
114/// # Opting out for testing/benchmarking
115///
116/// In order to facilitate testing/benchmarking where running the Datadog Agent isn't desirable, the provider runs in
117/// standalone mode when the configuration is standalone.
118///
119/// This effectively disables origin enrichment (no entity tags) and causes metrics to be tagged with a fixed hostname.
120#[derive(Clone)]
121pub struct RemoteAgentEnvironmentProvider {
122    client_config: Option<RemoteAgentClientConfiguration>,
123    host_provider: BoxedHostProvider,
124    workload_provider: Option<RemoteAgentWorkloadProvider>,
125    autodiscovery_provider: Option<BoxedAutodiscoveryProvider>,
126    health_registry: HealthRegistry,
127}
128
129impl RemoteAgentEnvironmentProvider {
130    /// Creates a `RemoteAgentEnvironmentProvider` from the given configuration, along with the [`Supervisor`] that
131    /// drives the provider's background work, if it has any.
132    ///
133    /// In connected mode, the host, workload, and autodiscovery providers each open their own connection to the Datadog
134    /// Agent, and the supervisor drives them. In standalone mode, the provider reports the configured hostname, everything
135    /// else is a no-op, and no supervisor is returned.
136    ///
137    /// # Errors
138    ///
139    /// If any of the underlying providers can't be created, or no hostname is configured in standalone mode, an error is
140    /// returned.
141    pub async fn from_authoritative<C>(
142        config: &C, component_registry: &ComponentRegistry, health_registry: &HealthRegistry,
143    ) -> Result<(Self, Option<Supervisor>), GenericError>
144    where
145        C: AuthoritativeConfiguration,
146    {
147        if config.is_standalone() {
148            let env = Self::standalone(&config.host(), health_registry)?;
149            Ok((env, None))
150        } else {
151            let client_config = config.client_configuration()?;
152            let (env, env_supervisor) =
153                Self::connected(&config.workload(), client_config, component_registry, health_registry).await?;
154            Ok((env, Some(env_supervisor)))
155        }
156    }
157
158    fn standalone(settings: &HostSettings, health_registry: &HealthRegistry) -> Result<Self, GenericError> {
159        warn!("Running in standalone mode. Origin detection/enrichment and other features dependent upon the Datadog Agent will not be available.");
160
161        let hostname = match &settings.hostname {
162            Some(hostname) if !hostname.is_empty() => hostname.clone(),
163            _ => {
164                return Err(generic_error!(
165                    "A non-empty hostname must be configured (`hostname`) when running in standalone mode."
166                ))
167            }
168        };
169
170        Ok(Self {
171            client_config: None,
172            host_provider: BoxedHostProvider::from_provider(FixedHostProvider::new(hostname)),
173            workload_provider: None,
174            autodiscovery_provider: None,
175            health_registry: health_registry.clone(),
176        })
177    }
178
179    async fn connected(
180        settings: &WorkloadSettings, client_config: RemoteAgentClientConfiguration,
181        component_registry: &ComponentRegistry, health_registry: &HealthRegistry,
182    ) -> Result<(Self, Supervisor), GenericError> {
183        let mut env_supervisor = Supervisor::new("env-provider")?;
184
185        let host_provider = RemoteAgentHostProvider::new(&client_config, component_registry).await?;
186
187        let (workload_provider, workload_supervisor) =
188            RemoteAgentWorkloadProvider::new(settings, &client_config, component_registry, health_registry).await?;
189        env_supervisor.add_worker(workload_supervisor);
190
191        let (autodiscovery_provider, autodiscovery_supervisor) =
192            RemoteAgentAutodiscoveryProvider::new(&client_config).await?;
193        env_supervisor.add_worker(autodiscovery_supervisor);
194
195        let env = Self {
196            client_config: Some(client_config),
197            host_provider: BoxedHostProvider::from_provider(host_provider),
198            workload_provider: Some(workload_provider),
199            autodiscovery_provider: Some(BoxedAutodiscoveryProvider::from_provider(autodiscovery_provider)),
200            health_registry: health_registry.clone(),
201        };
202
203        Ok((env, env_supervisor))
204    }
205
206    /// Returns the configuration for connecting to the Datadog Agent, or `None` in standalone mode.
207    pub fn client_configuration(&self) -> Option<&RemoteAgentClientConfiguration> {
208        self.client_config.as_ref()
209    }
210
211    /// Returns a future that resolves once the environment provider's background subsystems are ready.
212    ///
213    /// Specifically, this waits for the workload provider's metadata aggregator and collectors to become ready, which
214    /// ensures that origin detection and entity tagging are operational before the caller begins processing data. In
215    /// standalone mode -- where there is no workload provider -- the returned future resolves immediately.
216    pub fn wait_for_ready(&self) -> impl Future<Output = ()> + Send + 'static {
217        let health_registry = self.health_registry.clone();
218        let has_workload_provider = self.workload_provider.is_some();
219        async move {
220            if has_workload_provider {
221                health_registry.all_ready_under(root_provider_id()).await;
222            }
223        }
224    }
225}
226
227impl EnvironmentProvider for RemoteAgentEnvironmentProvider {
228    type Host = BoxedHostProvider;
229    type Workload = Option<RemoteAgentWorkloadProvider>;
230    type AutodiscoveryProvider = Option<BoxedAutodiscoveryProvider>;
231
232    fn host(&self) -> &Self::Host {
233        &self.host_provider
234    }
235
236    fn workload(&self) -> &Self::Workload {
237        &self.workload_provider
238    }
239
240    fn autodiscovery(&self) -> &Self::AutodiscoveryProvider {
241        &self.autodiscovery_provider
242    }
243}