1use std::sync::Arc;
2
3use saluki_context::{
4 origin::{OriginTagCardinality, OriginTagsResolver, RawOrigin},
5 tags::SharedTagSet,
6};
7use saluki_env::{
8 workload::{origin::ResolvedOrigin, EntityId},
9 WorkloadProvider,
10};
11use saluki_io::deser::codec::dogstatsd::{EventPacket, MetricPacket, ServiceCheckPacket};
12use tracing::trace;
13
14use super::{replay::CapturedTaggerHandle, tags::WellKnownTags};
15
16const REPLAY_PROCESS_ID_MARKER: u32 = 1u32 << 31;
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub(super) enum ProcessOrigin {
20 Pinned(Option<EntityId>),
21 Unpinned(u32),
22 Replay(u32),
23}
24
25impl ProcessOrigin {
26 pub(super) fn container_entity_id(&self) -> Option<&EntityId> {
27 match self {
28 Self::Pinned(entity_id) => entity_id.as_ref(),
29 Self::Unpinned(_) | Self::Replay(_) => None,
30 }
31 }
32}
33
34pub(super) fn mark_replay_process_id(process_id: u32) -> u32 {
35 process_id | REPLAY_PROCESS_ID_MARKER
36}
37
38fn captured_process_id_from_replay(process_id: u32) -> Option<u32> {
39 if process_id & REPLAY_PROCESS_ID_MARKER != 0 {
40 Some(process_id & !REPLAY_PROCESS_ID_MARKER)
41 } else {
42 None
43 }
44}
45
46#[derive(Clone)]
52#[cfg_attr(test, derive(Debug, PartialEq))]
53pub struct OriginEnrichmentConfiguration {
54 pub enabled: bool,
58
59 pub entity_id_precedence: bool,
65
66 pub tag_cardinality: OriginTagCardinality,
68
69 pub origin_detection_unified: bool,
79
80 pub origin_detection_optout: bool,
85
86 pub origin_detection_client: bool,
91}
92
93#[cfg(test)]
94impl OriginEnrichmentConfiguration {
95 pub(super) fn for_test() -> Self {
98 Self {
99 enabled: false,
100 entity_id_precedence: false,
101 tag_cardinality: OriginTagCardinality::Low,
102 origin_detection_unified: false,
103 origin_detection_optout: true,
104 origin_detection_client: false,
105 }
106 }
107}
108
109#[derive(Clone)]
110pub(super) struct DogStatsDOriginTagResolver {
111 config: OriginEnrichmentConfiguration,
112 workload_provider: Arc<dyn WorkloadProvider + Send + Sync>,
113 captured_tagger: CapturedTaggerHandle,
114}
115
116impl DogStatsDOriginTagResolver {
117 pub fn new(
118 config: OriginEnrichmentConfiguration, workload_provider: Arc<dyn WorkloadProvider + Send + Sync>,
119 captured_tagger: CapturedTaggerHandle,
120 ) -> Self {
121 Self {
122 config,
123 workload_provider,
124 captured_tagger,
125 }
126 }
127
128 fn collect_origin_tags_with_process_entity(
129 &self, origin: &ResolvedOrigin, process_entity_id: Option<&EntityId>,
130 ) -> SharedTagSet {
131 let mut collected_tags = SharedTagSet::default();
132
133 if !self.config.enabled {
134 return collected_tags;
135 }
136
137 let maybe_process_entity_id = process_entity_id;
146 let maybe_local_container_id = origin.local_data();
147 let maybe_local_pod_uid = origin.pod_uid();
148 let maybe_external_container_id = origin.resolved_external_data().map(|red| red.container_entity_id());
149 let maybe_external_pod_uid = origin.resolved_external_data().map(|red| red.pod_entity_id());
150
151 let tag_cardinality = origin.cardinality().unwrap_or(self.config.tag_cardinality);
152
153 if !self.config.origin_detection_unified {
154 if self.config.origin_detection_optout && tag_cardinality == OriginTagCardinality::None {
155 trace!("Skipping origin enrichment for DogStatsD metric with cardinality 'none'.");
156 return collected_tags;
157 }
158
159 if let Some(entity_id) = maybe_process_entity_id {
162 if maybe_local_pod_uid.is_none() || !self.config.entity_id_precedence {
163 if let Some(tags) = self.workload_provider.get_tags_for_entity(entity_id, tag_cardinality) {
164 collected_tags.extend_from_shared(&tags);
165 } else {
166 trace!(
167 ?entity_id,
168 cardinality = tag_cardinality.as_str(),
169 "No tags found for entity."
170 );
171 }
172 }
173 }
174
175 let maybe_entity_id = maybe_local_pod_uid.or(maybe_local_container_id);
178 if let Some(entity_id) = maybe_entity_id {
179 if let Some(tags) = self.workload_provider.get_tags_for_entity(entity_id, tag_cardinality) {
180 collected_tags.extend_from_shared(&tags);
181 } else {
182 trace!(
183 ?entity_id,
184 cardinality = tag_cardinality.as_str(),
185 "No tags found for entity."
186 );
187 }
188 }
189 } else {
190 if tag_cardinality == OriginTagCardinality::None {
191 trace!("Skipping origin enrichment for metric with cardinality 'none'.");
192 return collected_tags;
193 }
194
195 let maybe_entity_ids = &[
200 maybe_local_container_id,
201 maybe_process_entity_id,
202 maybe_external_container_id,
203 maybe_local_pod_uid,
204 maybe_external_pod_uid,
205 ];
206 for entity_id in maybe_entity_ids.iter().flatten() {
207 if let Some(tags) = self.workload_provider.get_tags_for_entity(entity_id, tag_cardinality) {
208 if !tags.is_empty() {
209 collected_tags.extend_from_shared(&tags);
210 break;
211 }
212 } else {
213 trace!(
214 ?entity_id,
215 cardinality = tag_cardinality.as_str(),
216 "No tags found for entity."
217 );
218 }
219 }
220 }
221
222 collected_tags
223 }
224
225 fn collect_origin_tags(&self, origin: &ResolvedOrigin) -> SharedTagSet {
226 self.collect_origin_tags_with_process_entity(origin, origin.process_id())
227 }
228
229 pub(super) fn resolve_origin_tags_with_process_origin(
230 &self, mut origin: RawOrigin<'_>, process_origin: Option<&ProcessOrigin>,
231 ) -> SharedTagSet {
232 match process_origin {
233 Some(ProcessOrigin::Replay(process_id)) => {
234 origin.set_process_id(mark_replay_process_id(*process_id));
235 self.resolve_origin_tags(origin)
236 }
237 Some(ProcessOrigin::Unpinned(process_id)) => {
238 origin.set_process_id(*process_id);
239 self.resolve_origin_tags(origin)
240 }
241 Some(ProcessOrigin::Pinned(process_entity_id)) => {
242 let resolved_origin = self
243 .workload_provider
244 .get_resolved_origin(origin.clone())
245 .unwrap_or_else(|| ResolvedOrigin::from_parts(origin.cardinality(), None, None, None, None));
246 self.collect_origin_tags_with_process_entity(&resolved_origin, process_entity_id.as_ref())
247 }
248 None => self.resolve_origin_tags(origin),
249 }
250 }
251}
252
253impl OriginTagsResolver for DogStatsDOriginTagResolver {
254 fn resolve_origin_tags(&self, origin: RawOrigin<'_>) -> SharedTagSet {
255 if let Some(captured_process_id) = origin.process_id().and_then(captured_process_id_from_replay) {
259 if let Some(store) = self.captured_tagger.current() {
260 let cardinality = origin.cardinality().unwrap_or(self.config.tag_cardinality);
261 return store
262 .lookup(captured_process_id as i32, cardinality)
263 .unwrap_or_default();
264 }
265 trace!(?origin, "Replay-flagged origin but no captured tagger available.");
266 return SharedTagSet::default();
267 }
268
269 match self.workload_provider.get_resolved_origin(origin.clone()) {
270 Some(resolved_origin) => self.collect_origin_tags(&resolved_origin),
271 None => {
272 trace!(?origin, "No resolved origin found for origin.");
273 SharedTagSet::default()
274 }
275 }
276 }
277}
278
279pub fn origin_from_metric_packet<'packet, 'tags>(
281 packet: &'tags MetricPacket<'packet>, well_known_tags: &'tags WellKnownTags<'tags>,
282) -> RawOrigin<'tags>
283where
284 'packet: 'tags,
285{
286 let cardinality = packet.cardinality.or(well_known_tags.cardinality);
287
288 let mut origin = RawOrigin::default();
289 origin.set_pod_uid(well_known_tags.pod_uid);
290 origin.set_local_data(packet.local_data);
291 origin.set_external_data(packet.external_data);
292 origin.set_cardinality(cardinality);
293 origin
294}
295
296pub fn origin_from_event_packet<'packet, 'tags>(
298 packet: &'tags EventPacket<'packet>, well_known_tags: &'tags WellKnownTags<'tags>,
299) -> RawOrigin<'tags>
300where
301 'packet: 'tags,
302{
303 let cardinality = packet.cardinality.or(well_known_tags.cardinality);
304
305 let mut origin = RawOrigin::default();
306 origin.set_pod_uid(well_known_tags.pod_uid);
307 origin.set_local_data(packet.local_data);
308 origin.set_external_data(packet.external_data);
309 origin.set_cardinality(cardinality);
310 origin
311}
312
313pub fn origin_from_service_check_packet<'packet, 'tags>(
315 packet: &'tags ServiceCheckPacket<'packet>, well_known_tags: &'tags WellKnownTags<'tags>,
316) -> RawOrigin<'tags>
317where
318 'packet: 'tags,
319{
320 let cardinality = packet.cardinality.or(well_known_tags.cardinality);
321
322 let mut origin = RawOrigin::default();
323 origin.set_pod_uid(well_known_tags.pod_uid);
324 origin.set_local_data(packet.local_data);
325 origin.set_external_data(packet.external_data);
326 origin.set_cardinality(cardinality);
327 origin
328}
329
330#[cfg(test)]
331mod tests {
332 use std::collections::HashMap;
333
334 use saluki_context::tags::{RawTags, TagSet};
335 use saluki_core::data_model::event::{metric::MetricValues, service_check::CheckStatus};
336 use saluki_env::workload::{origin::ResolvedExternalData, providers::TestWorkloadProvider, EntityId};
337 use stringtheory::MetaString;
338
339 use super::*;
340
341 static EID_PID: EntityId = EntityId::ContainerPid(12345);
342 static EID_LOCAL_CID: EntityId = EntityId::Container(MetaString::from_static("local-cid"));
343 static EID_EXTERNAL_CID_VALID: EntityId = EntityId::Container(MetaString::from_static("external-cid"));
344 static EID_EXTERNAL_CID_INVALID: EntityId = EntityId::Container(MetaString::from_static("invalid-external-cid"));
345 static EID_LOCAL_POD: EntityId = EntityId::PodUid(MetaString::from_static("local-pod-uid"));
346 static EID_EXTERNAL_POD: EntityId = EntityId::PodUid(MetaString::from_static("external-pod-uid"));
347
348 fn single_tag(tag: &str) -> SharedTagSet {
349 let mut tag_set = TagSet::default();
350 tag_set.insert_tag(tag);
351 tag_set.into_shared()
352 }
353
354 fn tags_for_entity(entity_id: &EntityId) -> SharedTagSet {
355 if entity_id == &EID_PID {
356 single_tag("tag_source:pid")
357 } else if entity_id == &EID_LOCAL_CID {
358 single_tag("tag_source:local-cid")
359 } else if entity_id == &EID_EXTERNAL_CID_VALID {
360 single_tag("tag_source:external-cid")
361 } else if entity_id == &EID_LOCAL_POD {
362 single_tag("tag_source:local-pod")
363 } else if entity_id == &EID_EXTERNAL_POD {
364 single_tag("tag_source:external-pod")
365 } else {
366 SharedTagSet::default()
367 }
368 }
369
370 fn origin(
371 maybe_process_id: Option<&EntityId>, maybe_local_container_id: Option<&EntityId>,
372 maybe_local_pod_uid: Option<&EntityId>, maybe_external_data: Option<&ResolvedExternalData>,
373 ) -> ResolvedOrigin {
374 ResolvedOrigin::from_parts(
375 None,
376 maybe_process_id.cloned(),
377 maybe_local_container_id.cloned(),
378 maybe_local_pod_uid.cloned(),
379 maybe_external_data.cloned(),
380 )
381 }
382
383 fn build_tags_resolver_with_default_tags(config: OriginEnrichmentConfiguration) -> DogStatsDOriginTagResolver {
384 let mut workload_provider = TestWorkloadProvider::new();
385 workload_provider.add_entity_shared_tags(EID_PID.clone(), tags_for_entity(&EID_PID));
386 workload_provider.add_entity_shared_tags(EID_LOCAL_CID.clone(), tags_for_entity(&EID_LOCAL_CID));
387 workload_provider
388 .add_entity_shared_tags(EID_EXTERNAL_CID_VALID.clone(), tags_for_entity(&EID_EXTERNAL_CID_VALID));
389 workload_provider.add_entity_shared_tags(EID_LOCAL_POD.clone(), tags_for_entity(&EID_LOCAL_POD));
390 workload_provider.add_entity_shared_tags(EID_EXTERNAL_POD.clone(), tags_for_entity(&EID_EXTERNAL_POD));
391
392 let erased_workload_provider = Arc::new(workload_provider);
393
394 DogStatsDOriginTagResolver::new(config, erased_workload_provider, CapturedTaggerHandle::new())
395 }
396
397 #[test]
398 fn metric_cardinality_precedence() {
399 let raw_tags_input = "dd.internal.card:high";
402 let raw_tags = RawTags::new(raw_tags_input, usize::MAX, usize::MAX);
403
404 let well_known_tags = WellKnownTags::from_raw_tags(&raw_tags);
405 assert_eq!(well_known_tags.cardinality, Some(OriginTagCardinality::High));
406
407 let packet_with_card = MetricPacket {
408 metric_name: "test_metric",
409 tags: raw_tags.clone(),
410 values: MetricValues::counter(1.0),
411 num_points: 1,
412 timestamp: None,
413 local_data: None,
414 external_data: None,
415 cardinality: Some(OriginTagCardinality::Low),
416 unit: None,
417 };
418
419 let packet_without_card = MetricPacket {
420 metric_name: "test_metric",
421 tags: raw_tags.clone(),
422 values: MetricValues::counter(1.0),
423 num_points: 1,
424 timestamp: None,
425 local_data: None,
426 external_data: None,
427 cardinality: None,
428 unit: None,
429 };
430
431 let with_card_origin = origin_from_metric_packet(&packet_with_card, &well_known_tags);
432 assert_ne!(packet_with_card.cardinality, well_known_tags.cardinality);
433 assert_eq!(with_card_origin.cardinality(), packet_with_card.cardinality);
434
435 let without_card_origin = origin_from_metric_packet(&packet_without_card, &well_known_tags);
436 assert_ne!(packet_without_card.cardinality, well_known_tags.cardinality);
437 assert_eq!(without_card_origin.cardinality(), well_known_tags.cardinality);
438 }
439
440 #[test]
441 fn event_cardinality_precedence() {
442 let raw_tags_input = "dd.internal.card:low";
445 let raw_tags = RawTags::new(raw_tags_input, usize::MAX, usize::MAX);
446
447 let well_known_tags = WellKnownTags::from_raw_tags(&raw_tags);
448 assert_eq!(well_known_tags.cardinality, Some(OriginTagCardinality::Low));
449
450 let packet_with_card = EventPacket {
451 title: MetaString::empty(),
452 text: MetaString::empty(),
453 timestamp: None,
454 hostname: None,
455 aggregation_key: None,
456 priority: None,
457 alert_type: None,
458 source_type_name: None,
459 tags: raw_tags.clone(),
460 local_data: None,
461 external_data: None,
462 cardinality: Some(OriginTagCardinality::Orchestrator),
463 };
464
465 let packet_without_card = EventPacket {
466 title: MetaString::empty(),
467 text: MetaString::empty(),
468 timestamp: None,
469 hostname: None,
470 aggregation_key: None,
471 priority: None,
472 alert_type: None,
473 source_type_name: None,
474 tags: raw_tags.clone(),
475 local_data: None,
476 external_data: None,
477 cardinality: None,
478 };
479
480 let with_card_origin = origin_from_event_packet(&packet_with_card, &well_known_tags);
481 assert_ne!(packet_with_card.cardinality, well_known_tags.cardinality);
482 assert_eq!(with_card_origin.cardinality(), packet_with_card.cardinality);
483
484 let without_card_origin = origin_from_event_packet(&packet_without_card, &well_known_tags);
485 assert_ne!(packet_without_card.cardinality, well_known_tags.cardinality);
486 assert_eq!(without_card_origin.cardinality(), well_known_tags.cardinality);
487 }
488
489 #[test]
490 fn service_check_cardinality_precedence() {
491 let raw_tags_input = "dd.internal.card:orchestrator";
494 let raw_tags = RawTags::new(raw_tags_input, usize::MAX, usize::MAX);
495
496 let well_known_tags = WellKnownTags::from_raw_tags(&raw_tags);
497 assert_eq!(well_known_tags.cardinality, Some(OriginTagCardinality::Orchestrator));
498
499 let packet_with_card = ServiceCheckPacket {
500 name: MetaString::empty(),
501 status: CheckStatus::Ok,
502 timestamp: None,
503 hostname: None,
504 message: None,
505 tags: raw_tags.clone(),
506 local_data: None,
507 external_data: None,
508 cardinality: Some(OriginTagCardinality::Low),
509 };
510
511 let packet_without_card = ServiceCheckPacket {
512 name: MetaString::empty(),
513 status: CheckStatus::Ok,
514 timestamp: None,
515 hostname: None,
516 message: None,
517 tags: raw_tags.clone(),
518 local_data: None,
519 external_data: None,
520 cardinality: None,
521 };
522
523 let with_card_origin = origin_from_service_check_packet(&packet_with_card, &well_known_tags);
524 assert_ne!(packet_with_card.cardinality, well_known_tags.cardinality);
525 assert_eq!(with_card_origin.cardinality(), packet_with_card.cardinality);
526
527 let without_card_origin = origin_from_service_check_packet(&packet_without_card, &well_known_tags);
528 assert_ne!(packet_without_card.cardinality, well_known_tags.cardinality);
529 assert_eq!(without_card_origin.cardinality(), well_known_tags.cardinality);
530 }
531
532 #[test]
533 fn origin_detection_legacy_precedence() {
534 let mut pid_plus_local_pod_tags = tags_for_entity(&EID_PID);
535 pid_plus_local_pod_tags.extend_from_shared(&tags_for_entity(&EID_LOCAL_POD));
536
537 let mut pid_plus_local_cid_tags = tags_for_entity(&EID_PID);
538 pid_plus_local_cid_tags.extend_from_shared(&tags_for_entity(&EID_LOCAL_CID));
539
540 let cases = [
541 (
543 false,
544 origin(Some(&EID_PID), None, None, None),
545 tags_for_entity(&EID_PID),
546 ),
547 (
548 true,
549 origin(Some(&EID_PID), None, None, None),
550 tags_for_entity(&EID_PID),
551 ),
552 (
555 false,
556 origin(Some(&EID_PID), None, Some(&EID_LOCAL_POD), None),
557 pid_plus_local_pod_tags.clone(),
558 ),
559 (
562 true,
563 origin(Some(&EID_PID), None, Some(&EID_LOCAL_POD), None),
564 tags_for_entity(&EID_LOCAL_POD),
565 ),
566 (
569 false,
570 origin(Some(&EID_PID), Some(&EID_LOCAL_CID), Some(&EID_LOCAL_POD), None),
571 pid_plus_local_pod_tags,
572 ),
573 (
576 true,
577 origin(Some(&EID_PID), Some(&EID_LOCAL_CID), Some(&EID_LOCAL_POD), None),
578 tags_for_entity(&EID_LOCAL_POD),
579 ),
580 (
583 false,
584 origin(Some(&EID_PID), Some(&EID_LOCAL_CID), None, None),
585 pid_plus_local_cid_tags.clone(),
586 ),
587 (
588 true,
589 origin(Some(&EID_PID), Some(&EID_LOCAL_CID), None, None),
590 pid_plus_local_cid_tags,
591 ),
592 ];
593
594 for (entity_id_precedence, resolved_origin, expected_tags) in cases {
595 let tag_resolver_config = OriginEnrichmentConfiguration {
596 enabled: true,
597 entity_id_precedence,
598 tag_cardinality: OriginTagCardinality::High,
599 origin_detection_unified: false,
600 origin_detection_optout: false,
601 origin_detection_client: false,
602 };
603
604 let origin_tags_resolver = build_tags_resolver_with_default_tags(tag_resolver_config);
605
606 let actual_tags = origin_tags_resolver.collect_origin_tags(&resolved_origin);
607 assert_eq!(
608 actual_tags, expected_tags,
609 "failed to resolve the expected tags for origin {:?}",
610 resolved_origin
611 );
612 }
613 }
614
615 #[test]
616 fn origin_detection_unified_precedence() {
617 let ext_data_valid = ResolvedExternalData::new(EID_EXTERNAL_POD.clone(), EID_EXTERNAL_CID_VALID.clone());
624 let ext_data_invalid = ResolvedExternalData::new(EID_EXTERNAL_POD.clone(), EID_EXTERNAL_CID_INVALID.clone());
625
626 let tag_resolver_config = OriginEnrichmentConfiguration {
627 enabled: true,
628 entity_id_precedence: false,
629 tag_cardinality: OriginTagCardinality::High,
630 origin_detection_unified: true,
631 origin_detection_optout: false,
632 origin_detection_client: false,
633 };
634
635 let origin_tags_resolver = build_tags_resolver_with_default_tags(tag_resolver_config);
636
637 let cases = [
640 (origin(Some(&EID_PID), None, None, None), tags_for_entity(&EID_PID)),
642 (
643 origin(None, Some(&EID_LOCAL_CID), None, None),
644 tags_for_entity(&EID_LOCAL_CID),
645 ),
646 (
647 origin(None, None, Some(&EID_LOCAL_POD), None),
648 tags_for_entity(&EID_LOCAL_POD),
649 ),
650 (
651 origin(None, None, None, Some(&ext_data_valid)),
652 tags_for_entity(&EID_EXTERNAL_CID_VALID),
653 ),
654 (
655 origin(None, None, None, Some(&ext_data_invalid)),
656 tags_for_entity(&EID_EXTERNAL_POD),
657 ),
658 (
660 origin(
661 Some(&EID_PID),
662 Some(&EID_LOCAL_CID),
663 Some(&EID_LOCAL_POD),
664 Some(&ext_data_valid),
665 ),
666 tags_for_entity(&EID_LOCAL_CID),
667 ),
668 (
669 origin(Some(&EID_PID), None, Some(&EID_LOCAL_POD), Some(&ext_data_valid)),
670 tags_for_entity(&EID_PID),
671 ),
672 (
673 origin(None, None, Some(&EID_LOCAL_POD), Some(&ext_data_valid)),
674 tags_for_entity(&EID_EXTERNAL_CID_VALID),
675 ),
676 (
677 origin(None, None, Some(&EID_LOCAL_POD), Some(&ext_data_invalid)),
678 tags_for_entity(&EID_LOCAL_POD),
679 ),
680 (
681 origin(None, None, None, Some(&ext_data_invalid)),
682 tags_for_entity(&EID_EXTERNAL_POD),
683 ),
684 ];
685
686 for (resolved_origin, expected_tags) in cases {
687 let actual_tags = origin_tags_resolver.collect_origin_tags(&resolved_origin);
688 assert_eq!(
689 actual_tags, expected_tags,
690 "failed to resolve the expected tags for origin {:?}",
691 resolved_origin
692 );
693 }
694 }
695
696 #[test]
697 fn origin_detection_disabled() {
698 let tag_resolver_config = OriginEnrichmentConfiguration::for_test();
701 assert!(!tag_resolver_config.enabled);
702
703 let origin_tags_resolver = build_tags_resolver_with_default_tags(tag_resolver_config);
704
705 let resolved_origin = origin(Some(&EID_PID), None, None, None);
706 let actual_tags = origin_tags_resolver.collect_origin_tags(&resolved_origin);
707 assert!(actual_tags.is_empty());
708 }
709
710 #[test]
711 fn resolve_origin_tags_dispatches_to_captured_store_when_replay_flag_set() {
712 use datadog_protos::agent::{Entity as ProtoEntity, TaggerState};
713
714 let mut entities = HashMap::new();
716 entities.insert(
717 "container_id://captured-container".to_string(),
718 ProtoEntity {
719 low_cardinality_tags: vec!["env:captured".into(), "service:replayed".into()],
720 ..Default::default()
721 },
722 );
723 let mut pid_map = HashMap::new();
724 pid_map.insert(7777, "container_id://captured-container".to_string());
725 let state = TaggerState {
726 state: entities,
727 pid_map,
728 duration: 0,
729 };
730 let captured_tagger = CapturedTaggerHandle::new();
731 captured_tagger.set_current(Some(super::super::replay::CapturedTaggerStore::from_tagger_state(
732 state,
733 )));
734
735 let config = OriginEnrichmentConfiguration {
738 enabled: true,
739 tag_cardinality: OriginTagCardinality::Low,
740 ..OriginEnrichmentConfiguration::for_test()
741 };
742 let live = Arc::new(TestWorkloadProvider::new());
743 let resolver = DogStatsDOriginTagResolver::new(config, live, captured_tagger);
744
745 let mut origin = RawOrigin::default();
746 origin.set_process_id(mark_replay_process_id(7777));
747 origin.set_cardinality(OriginTagCardinality::Low);
748
749 let tags = resolver.resolve_origin_tags(origin);
750 let tag_strs: Vec<String> = tags.into_iter().map(|t| t.as_str().to_string()).collect();
751 assert!(tag_strs.contains(&"env:captured".to_string()));
752 assert!(tag_strs.contains(&"service:replayed".to_string()));
753 }
754
755 #[test]
756 fn resolve_origin_tags_returns_empty_when_replay_flag_set_but_no_captured_store() {
757 let config = OriginEnrichmentConfiguration {
760 enabled: true,
761 tag_cardinality: OriginTagCardinality::Low,
762 ..OriginEnrichmentConfiguration::for_test()
763 };
764 let live = Arc::new(TestWorkloadProvider::new());
765 let resolver = DogStatsDOriginTagResolver::new(config, live, CapturedTaggerHandle::new());
766
767 let mut origin = RawOrigin::default();
768 origin.set_process_id(mark_replay_process_id(7777));
769
770 let tags = resolver.resolve_origin_tags(origin);
771 assert!(
772 tags.is_empty(),
773 "replay path with no captured store must return empty tags"
774 );
775 }
776
777 #[test]
778 fn resolve_origin_tags_live_path_resolves_tags_via_workload_provider() {
779 let config = OriginEnrichmentConfiguration {
784 enabled: true,
785 tag_cardinality: OriginTagCardinality::High,
786 ..OriginEnrichmentConfiguration::for_test()
787 };
788
789 let mut workload_provider = TestWorkloadProvider::new();
790 workload_provider.add_entity_shared_tags(EntityId::ContainerPid(4242), single_tag("tag_source:live-pid"));
791 let resolver =
792 DogStatsDOriginTagResolver::new(config, Arc::new(workload_provider), CapturedTaggerHandle::new());
793
794 let mut origin = RawOrigin::default();
796 origin.set_process_id(4242);
797
798 let tags = resolver.resolve_origin_tags(origin);
799 assert_eq!(tags, single_tag("tag_source:live-pid"));
800 }
801
802 #[test]
803 fn resolve_origin_tags_uses_pinned_process_entity() {
804 let pinned_entity = EntityId::Container(MetaString::from_static("original-container"));
805 let config = OriginEnrichmentConfiguration {
806 enabled: true,
807 tag_cardinality: OriginTagCardinality::High,
808 ..OriginEnrichmentConfiguration::for_test()
809 };
810 let workload_provider =
811 TestWorkloadProvider::with_entity(pinned_entity.clone(), &["tag_source:pinned-container"]);
812 let resolver =
813 DogStatsDOriginTagResolver::new(config, Arc::new(workload_provider), CapturedTaggerHandle::new());
814 let process_origin = ProcessOrigin::Pinned(Some(pinned_entity));
815
816 let tags = resolver.resolve_origin_tags_with_process_origin(RawOrigin::default(), Some(&process_origin));
817
818 assert_eq!(tags, single_tag("tag_source:pinned-container"));
819 }
820
821 #[test]
822 fn resolve_origin_tags_live_path_returns_empty_when_origin_unresolved() {
823 let config = OriginEnrichmentConfiguration {
826 enabled: true,
827 tag_cardinality: OriginTagCardinality::High,
828 ..OriginEnrichmentConfiguration::for_test()
829 };
830 let resolver = DogStatsDOriginTagResolver::new(
831 config,
832 Arc::new(TestWorkloadProvider::new()),
833 CapturedTaggerHandle::new(),
834 );
835
836 let tags = resolver.resolve_origin_tags(RawOrigin::default());
837 assert!(tags.is_empty(), "unresolved origin must produce no tags");
838 }
839}