agent_data_plane_config/
control.rs

1//! Topology gates, orchestration decisions and application configuration.
2//!
3//! `ControlConfiguration` is read only by config-system and the topology builder, not by
4//! components. It carries pipeline activation gates, topology-shaping decisions, listen addresses,
5//! logging (read before topology exists), bootstrap IPC parameters, and process-lifecycle knobs.
6
7use std::{num::NonZeroUsize, path::PathBuf, time::Duration};
8
9use serde::Serialize;
10
11use crate::{defaults::DEFAULT_REMOTE_AGENT_STRING_INTERNER_SIZE_BYTES, ConfigValue};
12
13/// Topology gates and orchestration decisions. Most are static; `logging.level` is live.
14///
15/// The derived `Default` is all zeroes, empty, and `false`, and serves only as the starting point for translation. The
16/// effective default of each field is the one translation resolves, noted per field below.
17#[derive(Clone, Debug, Default, PartialEq, Serialize)]
18pub struct ControlConfiguration {
19    /// Master switch for the whole data plane; when false, no pipelines are built.
20    pub enabled: bool,
21
22    /// Whether the DogStatsD metrics pipeline is built.
23    pub dogstatsd: bool,
24
25    /// Whether the checks metrics pipeline is built. (not in Datadog Agent config schema)
26    pub checks: bool,
27
28    /// Whether the OTLP pipeline is built.
29    pub otlp: bool,
30
31    /// Whether the Datadog v1.0 (`idx`/ETP) APM trace pipeline is built. (not in Datadog Agent
32    /// config schema)
33    ///
34    /// Independent of the trace-agent, which keeps serving its own receiver on `8126`.
35    /// Defaults to `false`; the two run side by side, and each tracer points at exactly one of them.
36    pub apm: bool,
37
38    /// Whether standalone mode is active, running without a core Agent. (not in Datadog Agent
39    /// config schema)
40    pub standalone_mode: bool,
41
42    /// Whether the process registers itself with the core Agent as a remote agent.
43    pub remote_agent_enabled: bool,
44
45    /// Whether to subscribe to core Agent configuration updates over the newer config-stream
46    /// endpoint.
47    pub use_new_config_stream_endpoint: bool,
48
49    /// Address the unsecured control API listens on.
50    pub api_listen_address: String,
51
52    /// Address the mutually authenticated control API listens on. Every HTTP and gRPC client must
53    /// present the exact configured Agent IPC certificate during the TLS handshake.
54    pub secure_api_listen_address: String,
55
56    /// Logging configuration, read before runtime authority exists.
57    pub logging: Logging,
58
59    /// Bootstrap IPC and remote-agent connection parameters.
60    pub ipc: ControlIpc,
61
62    /// Grace period the aggregator is given to flush before shutdown.
63    pub aggregator_stop_timeout: Duration,
64
65    /// Override for the topology shutdown grace period.
66    ///
67    /// Defaults to `None`. When absent, the topology timeout is the sum of
68    /// `aggregator_stop_timeout` and `forwarder_stop_timeout`.
69    pub stop_timeout: Option<Duration>,
70
71    /// Process memory ceiling, in bytes, that bounds validation and the global limiter work against.
72    ///
73    /// Defaults to `None`. When absent, ADP reads the ceiling from the process cgroup, but only when `DOCKER_DD_AGENT`
74    /// is set to a non-empty value. When neither source supplies a value, bounds validation is skipped and the global
75    /// limiter never exerts backpressure, whatever `memory_mode` and `enable_global_limiter` say.
76    ///
77    /// `Some(0)` is a ceiling of zero bytes rather than "no ceiling": every component bound then exceeds it, which is
78    /// fatal under [`MemoryMode::Strict`]. A ceiling above 2^53 bytes is rejected during startup.
79    ///
80    /// Set this to the memory the process is allowed to use, and leave it unset only where cgroup detection supplies
81    /// that number.
82    pub memory_limit: Option<u64>,
83
84    /// Fraction of `memory_limit` held back as headroom for memory the component bounds do not account for.
85    ///
86    /// Defaults to [`DEFAULT_MEMORY_SLOP_FACTOR`](crate::defaults::DEFAULT_MEMORY_SLOP_FACTOR) (`0.25`), which
87    /// validates bounds against 75% of `memory_limit`. Valid values run from `0.0` up to but excluding `1.0`, where
88    /// `0.0` holds nothing back. A value outside that range, including `NaN`, fails startup once a memory ceiling
89    /// resolves, and goes unused when none does.
90    ///
91    /// Raise this for a workload whose real usage overshoots its validated bounds; lower it to hand more of a tight
92    /// ceiling to the components that do account for their usage.
93    pub memory_slop_factor: f64,
94
95    /// Whether the global memory limiter exerts backpressure as usage approaches the effective ceiling.
96    ///
97    /// Defaults to [`DEFAULT_ENABLE_GLOBAL_LIMITER`](crate::defaults::DEFAULT_ENABLE_GLOBAL_LIMITER) (`true`). When
98    /// `false`, the limiter is a no-op: it throttles nothing, and only the components' own bounds hold memory usage
99    /// down. Either way it does nothing unless a memory ceiling resolves and `memory_mode` is
100    /// [`MemoryMode::Permissive`] or [`MemoryMode::Strict`], because no other case installs a limiter.
101    ///
102    /// Turn this off to attribute a throughput drop to memory backpressure, accepting that the process can then run
103    /// past `memory_limit`.
104    pub enable_global_limiter: bool,
105
106    /// How the component memory bounds are reconciled with the effective memory ceiling during startup.
107    ///
108    /// Defaults to [`MemoryMode::Disabled`]. Validation runs only when a memory ceiling resolves; without one,
109    /// `Permissive` and `Strict` log that validation was skipped and startup continues.
110    ///
111    /// Run `Permissive` first to learn whether a ceiling fits the topology, then move to `Strict` where the platform
112    /// kills a process that exceeds its ceiling and refusing to start is the better failure.
113    pub memory_mode: MemoryMode,
114}
115
116/// Memory bounds validation and limiter behavior.
117#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
118#[serde(rename_all = "lowercase")]
119pub enum MemoryMode {
120    /// Bounds validation is skipped and no limiter is installed, whatever `enable_global_limiter` says.
121    #[default]
122    Disabled,
123
124    /// Bounds that do not fit the ceiling are logged as a warning and startup continues.
125    ///
126    /// Memory limiting is best effort: the limiter is installed when a ceiling resolves and
127    /// `enable_global_limiter` is `true`.
128    Permissive,
129
130    /// Bounds that do not fit the ceiling fail startup.
131    ///
132    /// The limiter is installed on the same terms as [`MemoryMode::Permissive`].
133    Strict,
134}
135
136impl ControlConfiguration {
137    /// Derived decision the topology builder reads. The outbound Datadog forwarder is needed only
138    /// if some pipeline that emits to Datadog is enabled.
139    pub fn requires_datadog_forwarder(&self) -> bool {
140        self.dogstatsd || self.checks || self.otlp
141    }
142}
143
144/// Logging configuration, read before runtime authority exists.
145#[derive(Clone, Debug, Default, PartialEq, Serialize)]
146pub struct Logging {
147    /// Minimum severity a record must reach to be emitted.
148    pub level: String,
149
150    /// Whether log timestamps are formatted as RFC 3339.
151    pub format_rfc3339: bool,
152
153    /// Whether log records are emitted as JSON.
154    pub format_json: bool,
155
156    /// Whether logs are written to the console.
157    pub to_console: bool,
158
159    /// Whether logs are forwarded to syslog.
160    pub to_syslog: bool,
161
162    /// Whether syslog messages use the RFC 5424 framing.
163    pub syslog_rfc: bool,
164
165    /// Destination URI for syslog forwarding.
166    pub syslog_uri: String,
167
168    /// Path of the log file.
169    ///
170    /// A defaulted or explicitly empty path selects the platform-specific ADP log file path.
171    pub file: ConfigValue<String>,
172
173    /// Whether file logging is turned off entirely.
174    pub disable_file_logging: bool,
175
176    /// Number of rotated log files retained.
177    ///
178    /// Defaults to `1`. The file writer retains one rotated file when this is `0`. A negative value is
179    /// rejected during translation.
180    pub file_max_rolls: usize,
181
182    /// Maximum size, in bytes, a log file reaches before it is rotated.
183    ///
184    /// When defaulted, the logging stack keeps its own 10 MiB threshold instead.
185    pub file_max_size: ConfigValue<u64>,
186}
187
188/// IPC and remote-agent connection parameters, read once at bootstrap before runtime authority
189/// exists and again from the authoritative configuration once it does.
190///
191/// Witnessed fields get their effective defaults during translation. The Saluki-only interner
192/// budget uses its Rust `Default`.
193#[derive(Clone, Debug, PartialEq, Serialize)]
194pub struct ControlIpc {
195    /// Path to the Agent authentication token file.
196    ///
197    /// ADP sends the file contents as a bearer token to the Core Agent. Override this path only when the Core Agent
198    /// uses a non-default token path, and configure both processes to use the same token.
199    ///
200    /// Defaults to an empty path, which selects the platform-specific Agent authentication token path.
201    pub auth_token_file_path: PathBuf,
202
203    /// Path to the shared Agent IPC mTLS identity file.
204    ///
205    /// The PEM file contains the certificate and private key used by ADP and its IPC peers. Every peer must use the
206    /// same identity because authentication requires an exact certificate match. Override this path only when the Core
207    /// Agent uses a non-default identity path.
208    ///
209    /// Defaults to an empty path, which selects `ipc_cert.pem` beside the resolved authentication token path.
210    pub ipc_cert_file_path: PathBuf,
211
212    /// TCP port the command API listens on.
213    ///
214    /// Defaults to `5001`.
215    pub cmd_port: u16,
216
217    /// vsock address used for guest/host IPC.
218    ///
219    /// Defaults to empty, which reaches the Core Agent over TCP on localhost at `cmd_port`.
220    pub vsock_addr: String,
221
222    /// Maximum gRPC message size, in bytes, accepted over the remote-agent IPC channel.
223    ///
224    /// Defaults to `134217728` (128 MiB).
225    pub grpc_max_message_size: usize,
226
227    /// Byte budget for the remote-agent workload metadata string interner. (not in Datadog Agent config schema)
228    ///
229    /// The workload provider interns entity IDs and tags into a single allocation of this size, taken at startup and
230    /// charged in full against the memory bounds ceiling. A workload whose tag cardinality outgrows the budget starts
231    /// failing to intern, which drops the affected tags and entity updates and increments the collectors'
232    /// `intern_failed_total` counters.
233    ///
234    /// Defaults to `524288` (512 KiB). An explicit `0` fails the configuration load. Change it when tuning ADP itself;
235    /// operators are not expected to.
236    pub remote_agent_string_interner_size_bytes: NonZeroUsize,
237}
238
239impl Default for ControlIpc {
240    fn default() -> Self {
241        Self {
242            // Written by the Datadog witness driver.
243            auth_token_file_path: PathBuf::new(),
244            ipc_cert_file_path: PathBuf::new(),
245            cmd_port: 0,
246            vsock_addr: String::new(),
247            grpc_max_message_size: 0,
248            remote_agent_string_interner_size_bytes: DEFAULT_REMOTE_AGENT_STRING_INTERNER_SIZE_BYTES,
249        }
250    }
251}