datadog_agent_runtime/env/workload/
mod.rs1use std::{future::Future, num::NonZeroUsize, time::Duration};
4
5use datadog_agent_commons::ipc::config::RemoteAgentClientConfiguration;
6use saluki_core::{
7 accounting::{ComponentRegistry, MemoryBounds, MemoryBoundsBuilder},
8 data_model::{
9 origin::{OriginTagCardinality, RawOrigin},
10 tags::SharedTagSet,
11 },
12 health::{Health, HealthRegistry},
13 runtime::{RestartStrategy, Supervisor},
14 support::SubsystemIdentifier,
15};
16#[cfg(unix)]
17use saluki_env::features::Feature;
18#[cfg(target_os = "linux")]
19use saluki_env::workload::{collectors::CgroupsMetadataCollector, CgroupsConfiguration};
20#[cfg(unix)]
21use saluki_env::workload::{collectors::ContainerdMetadataCollector, ContainerdConfiguration};
22use saluki_env::{
23 features::FeatureDetector,
24 workload::{
25 aggregator::MetadataAggregator,
26 collectors::MetadataCollectorWorker,
27 entity::EntityId,
28 origin::{OriginResolver, ResolvedOrigin},
29 stores::{ExternalDataStore, TagStore, TagStoreQuerier},
30 OnDemandPIDResolver,
31 },
32 CaptureEntityResolver, WorkloadProvider,
33};
34use saluki_error::{generic_error, GenericError};
35use stringtheory::interning::GenericMapInterner;
36
37mod api;
38use self::api::RemoteAgentWorkloadAPIWorker;
39use crate::env::{root_provider_id, WorkloadSettings};
40
41mod collectors;
42use self::collectors::{RemoteAgentTaggerMetadataCollector, RemoteAgentWorkloadMetadataCollector};
43
44const DEFAULT_TAG_STORE_ENTITY_LIMIT: NonZeroUsize = NonZeroUsize::new(2000).unwrap();
48
49const DEFAULT_EXTERNAL_DATA_STORE_ENTITY_LIMIT: NonZeroUsize = NonZeroUsize::new(2000).unwrap();
51
52#[derive(Clone)]
67pub struct RemoteAgentWorkloadProvider {
68 tags_querier: TagStoreQuerier,
69 origin_resolver: OriginResolver,
70 on_demand_pid_resolver: OnDemandPIDResolver,
71}
72
73impl RemoteAgentWorkloadProvider {
74 pub async fn new(
80 settings: &WorkloadSettings, client_config: &RemoteAgentClientConfiguration,
81 component_registry: &ComponentRegistry, health_registry: &HealthRegistry,
82 ) -> Result<(Self, Supervisor), GenericError> {
83 let string_interner_size_bytes = settings.string_interner_size_bytes;
84 let workload_provider_id = root_provider_id().child("workload").child("remote_agent");
85 let mut provider_bounds = component_registry.bounds_builder(&workload_provider_id);
86
87 let string_interner = GenericMapInterner::new(string_interner_size_bytes);
88
89 provider_bounds
90 .subcomponent("string_interner")
91 .firm()
92 .with_fixed_amount("string interner", string_interner_size_bytes.get());
93
94 let aggregator_id = workload_provider_id.clone().child("aggregator");
97 let aggregator_health = health_registry
98 .register_component(&aggregator_id)
99 .ok_or_else(|| generic_error!("Component '{aggregator_id}' already registered in health registry."))?;
100 let (mut aggregator, operations_tx) = MetadataAggregator::new(aggregator_health);
101
102 let collectors_root = workload_provider_id.child("collectors");
103 let mut collector_bounds = provider_bounds.subcomponent("collectors");
104 let mut collector_workers: Vec<MetadataCollectorWorker> = Vec::new();
105
106 let containerd_socket_path = settings.containerd_socket_path.clone();
107 let container_proc_root = settings.proc_root.clone();
108 let container_cgroup_root = settings.cgroup_root.clone();
109
110 let feature_detector = FeatureDetector::automatic(containerd_socket_path.clone());
111
112 #[cfg(unix)]
114 if feature_detector.is_feature_available(Feature::Containerd) {
115 let containerd_config = ContainerdConfiguration {
116 connection_timeout: settings.containerd_connection_timeout,
117 query_timeout: settings.containerd_query_timeout,
118 };
119 let cri_collector = build_collector(
120 &collectors_root,
121 "containerd",
122 health_registry,
123 &mut collector_bounds,
124 |health| {
125 ContainerdMetadataCollector::new(
126 containerd_socket_path.clone(),
127 &containerd_config,
128 health,
129 string_interner.clone(),
130 )
131 },
132 )
133 .await?;
134
135 collector_workers.push(MetadataCollectorWorker::new(cri_collector, operations_tx.clone()));
136 }
137
138 #[cfg(target_os = "linux")]
140 {
141 let cgroups_config = CgroupsConfiguration::new(
142 container_proc_root.clone(),
143 container_cgroup_root.clone(),
144 &feature_detector,
145 );
146 let cgroups_collector = build_collector(
147 &collectors_root,
148 "cgroups",
149 health_registry,
150 &mut collector_bounds,
151 |health| CgroupsMetadataCollector::new(&cgroups_config, health, string_interner.clone()),
152 )
153 .await?;
154
155 collector_workers.push(MetadataCollectorWorker::new(cgroups_collector, operations_tx.clone()));
156 }
157
158 let ra_tags_collector = build_collector(
160 &collectors_root,
161 "remote_agent_tags",
162 health_registry,
163 &mut collector_bounds,
164 |health| RemoteAgentTaggerMetadataCollector::new(client_config, health, string_interner.clone()),
165 )
166 .await?;
167
168 collector_workers.push(MetadataCollectorWorker::new(ra_tags_collector, operations_tx.clone()));
169
170 let ra_wmeta_collector = build_collector(
171 &collectors_root,
172 "remote_agent_wmeta",
173 health_registry,
174 &mut collector_bounds,
175 |health| RemoteAgentWorkloadMetadataCollector::new(client_config, health, string_interner.clone()),
176 )
177 .await?;
178
179 collector_workers.push(MetadataCollectorWorker::new(ra_wmeta_collector, operations_tx));
180
181 let tag_store = TagStore::with_entity_limit(DEFAULT_TAG_STORE_ENTITY_LIMIT);
183 let tags_querier = tag_store.querier();
184
185 aggregator.add_store(tag_store);
186
187 let external_data_store = ExternalDataStore::with_entity_limit(DEFAULT_EXTERNAL_DATA_STORE_ENTITY_LIMIT);
188 let eds_resolver = external_data_store.resolver();
189
190 aggregator.add_store(external_data_store);
191
192 let on_demand_pid_resolver = OnDemandPIDResolver::new(
193 container_proc_root,
194 container_cgroup_root,
195 &feature_detector,
196 string_interner,
197 )?;
198 let origin_resolver = OriginResolver::new(eds_resolver.clone());
199
200 provider_bounds.with_subcomponent("aggregator", &aggregator);
202
203 let api_worker = RemoteAgentWorkloadAPIWorker::from_state(tags_querier.clone(), eds_resolver.clone());
204
205 let mut supervisor = Supervisor::new("workload")?
207 .with_restart_strategy(RestartStrategy::one_to_one().with_intensity_and_period(5, Duration::from_secs(30)));
208 supervisor.add_worker(aggregator);
209 for worker in collector_workers {
210 supervisor.add_worker(worker);
211 }
212 supervisor.add_worker(api_worker);
213
214 let provider = Self {
215 tags_querier,
216 origin_resolver,
217 on_demand_pid_resolver,
218 };
219
220 Ok((provider, supervisor))
221 }
222}
223
224impl WorkloadProvider for RemoteAgentWorkloadProvider {
225 fn get_tags_for_entity(&self, entity_id: &EntityId, cardinality: OriginTagCardinality) -> Option<SharedTagSet> {
226 match self.tags_querier.get_entity_tags(entity_id, cardinality) {
228 Some(tags) => Some(tags),
229 None => {
230 if let EntityId::ContainerPid(pid) = entity_id {
233 if let Some(container_id) = self.on_demand_pid_resolver.resolve(*pid) {
234 return self.tags_querier.get_entity_tags(&container_id, cardinality);
236 }
237 }
238
239 None
240 }
241 }
242 }
243
244 fn get_self_container_tags(&self) -> Option<SharedTagSet> {
245 let self_container_entity = self.on_demand_pid_resolver.resolve_self_container()?;
246 self.tags_querier
247 .get_entity_tags(&self_container_entity, OriginTagCardinality::Low)
248 }
249
250 fn get_resolved_origin(&self, origin: RawOrigin<'_>) -> Option<ResolvedOrigin> {
251 self.origin_resolver.get_resolved_origin(origin)
252 }
253}
254
255impl CaptureEntityResolver for RemoteAgentWorkloadProvider {
256 fn resolve_container_entity_for_live_pid(&self, process_id: u32) -> Option<EntityId> {
257 self.on_demand_pid_resolver.resolve(process_id)
258 }
259}
260
261async fn build_collector<F, Fut, O>(
262 collectors_root: &SubsystemIdentifier, collector_name: &str, health_registry: &HealthRegistry,
263 bounds_builder: &mut MemoryBoundsBuilder<'_>, build: F,
264) -> Result<O, GenericError>
265where
266 F: FnOnce(Health) -> Fut,
267 Fut: Future<Output = Result<O, GenericError>>,
268 O: MemoryBounds,
269{
270 let collector_id = collectors_root.clone().child(collector_name);
271 let health = health_registry
272 .register_component(&collector_id)
273 .ok_or_else(|| generic_error!("Component '{collector_id}' already registered in health registry."))?;
274 let collector = build(health).await?;
275 bounds_builder.with_subcomponent(collector_name, &collector);
276
277 Ok(collector)
278}