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}