66//! span.
77
88use hashbrown:: HashMap ;
9+ use libdd_trace_obfuscation:: ip_address:: quantize_peer_ip_addresses;
910use libdd_trace_protobuf:: pb;
1011use libdd_trace_utils:: span:: SpanText ;
1112use std:: borrow:: Borrow ;
@@ -79,19 +80,13 @@ impl<T> FixedAggregationKey<T> {
7980/// Represent a stats aggregation key borrowed from span data
8081pub ( super ) struct BorrowedAggregationKey < ' a > {
8182 fixed : FixedAggregationKey < & ' a str > ,
82- peer_tags : Vec < ( & ' a str , & ' a str ) > ,
83+ peer_tags : Vec < ( String , String ) > ,
8384}
8485
8586impl hashbrown:: Equivalent < OwnedAggregationKey > for BorrowedAggregationKey < ' _ > {
8687 #[ inline]
8788 fn equivalent ( & self , other : & OwnedAggregationKey ) -> bool {
88- self . fixed == other. fixed . convert ( |s| s)
89- && self . peer_tags . len ( ) == other. peer_tags . len ( )
90- && self
91- . peer_tags
92- . iter ( )
93- . zip ( other. peer_tags . iter ( ) )
94- . all ( |( ( k1, v1) , ( k2, v2) ) | k1 == k2 && v1 == v2)
89+ self . fixed == other. fixed . convert ( |s| s) && self . peer_tags == other. peer_tags
9590 }
9691}
9792
@@ -112,11 +107,7 @@ impl From<&BorrowedAggregationKey<'_>> for OwnedAggregationKey {
112107 fn from ( value : & BorrowedAggregationKey < ' _ > ) -> Self {
113108 OwnedAggregationKey {
114109 fixed : value. fixed . convert ( str:: to_owned) ,
115- peer_tags : value
116- . peer_tags
117- . iter ( )
118- . map ( |( k, v) | ( k. to_string ( ) , v. to_string ( ) ) )
119- . collect ( ) ,
110+ peer_tags : value. peer_tags . clone ( ) ,
120111 }
121112 }
122113}
@@ -218,14 +209,17 @@ impl<'a> BorrowedAggregationKey<'a> {
218209 let span_kind = span. get_meta ( TAG_SPANKIND ) . unwrap_or_default ( ) ;
219210 let peer_tags = if should_track_peer_tags ( span_kind) {
220211 // Parse the meta tags of the span and return a list of the peer tags based on the list
221- // of `peer_tag_keys`
212+ // of `peer_tag_keys`. IP address values are quantized to reduce cardinality.
222213 peer_tag_keys
223214 . iter ( )
224- . filter_map ( |key| Some ( ( ( key. as_str ( ) ) , ( span. get_meta ( key. as_str ( ) ) ?) ) ) )
215+ . filter_map ( |key| {
216+ let value = span. get_meta ( key. as_str ( ) ) ?;
217+ Some ( ( key. clone ( ) , quantize_peer_ip_addresses ( value) . into_owned ( ) ) )
218+ } )
225219 . collect ( )
226220 } else if let Some ( base_service) = span. get_meta ( "_dd.base_service" ) {
227221 // Internal spans with a base service override use only _dd.base_service as peer tag
228- vec ! [ ( "_dd.base_service" , base_service) ]
222+ vec ! [ ( "_dd.base_service" . to_string ( ) , base_service. to_string ( ) ) ]
229223 } else {
230224 vec ! [ ]
231225 } ;
@@ -974,6 +968,80 @@ mod tests {
974968 }
975969 }
976970
971+ #[ test]
972+ fn test_peer_tag_ip_quantization_in_aggregation_key ( ) {
973+ let peer_tag_keys = vec ! [ "peer.hostname" . to_string( ) , "db.instance" . to_string( ) ] ;
974+
975+ // IPv4 address peer tag gets replaced with blocked-ip-address
976+ let span_ipv4 = SpanSlice {
977+ service : "service" ,
978+ name : "op" ,
979+ resource : "res" ,
980+ span_id : 1 ,
981+ parent_id : 0 ,
982+ meta : HashMap :: from ( [
983+ ( "span.kind" , "client" ) ,
984+ ( "peer.hostname" , "10.1.2.3" ) ,
985+ ( "db.instance" , "my-db" ) ,
986+ ] ) ,
987+ ..Default :: default ( )
988+ } ;
989+ let key = BorrowedAggregationKey :: from_span ( & span_ipv4, & peer_tag_keys) ;
990+ let owned = OwnedAggregationKey :: from ( & key) ;
991+ assert_eq ! (
992+ owned. peer_tags,
993+ vec![
994+ (
995+ "peer.hostname" . to_string( ) ,
996+ "blocked-ip-address" . to_string( )
997+ ) ,
998+ ( "db.instance" . to_string( ) , "my-db" . to_string( ) ) ,
999+ ]
1000+ ) ;
1001+
1002+ // IPv6 address peer tag gets replaced with blocked-ip-address
1003+ let span_ipv6 = SpanSlice {
1004+ service : "service" ,
1005+ name : "op" ,
1006+ resource : "res" ,
1007+ span_id : 1 ,
1008+ parent_id : 0 ,
1009+ meta : HashMap :: from ( [
1010+ ( "span.kind" , "client" ) ,
1011+ ( "peer.hostname" , "2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF" ) ,
1012+ ] ) ,
1013+ ..Default :: default ( )
1014+ } ;
1015+ let ipv6_keys = vec ! [ "peer.hostname" . to_string( ) ] ;
1016+ let key = BorrowedAggregationKey :: from_span ( & span_ipv6, & ipv6_keys) ;
1017+ let owned = OwnedAggregationKey :: from ( & key) ;
1018+ assert_eq ! (
1019+ owned. peer_tags,
1020+ vec![ (
1021+ "peer.hostname" . to_string( ) ,
1022+ "blocked-ip-address" . to_string( )
1023+ ) ]
1024+ ) ;
1025+
1026+ // Non-IP peer tags pass through unchanged
1027+ let span_non_ip = SpanSlice {
1028+ service : "service" ,
1029+ name : "op" ,
1030+ resource : "res" ,
1031+ span_id : 1 ,
1032+ parent_id : 0 ,
1033+ meta : HashMap :: from ( [ ( "span.kind" , "client" ) , ( "db.instance" , "dynamo.test.us1" ) ] ) ,
1034+ ..Default :: default ( )
1035+ } ;
1036+ let non_ip_keys = vec ! [ "db.instance" . to_string( ) ] ;
1037+ let key = BorrowedAggregationKey :: from_span ( & span_non_ip, & non_ip_keys) ;
1038+ let owned = OwnedAggregationKey :: from ( & key) ;
1039+ assert_eq ! (
1040+ owned. peer_tags,
1041+ vec![ ( "db.instance" . to_string( ) , "dynamo.test.us1" . to_string( ) ) ]
1042+ ) ;
1043+ }
1044+
9771045 #[ test]
9781046 fn test_grpc_status_str_to_int_value ( ) {
9791047 // Numeric strings parse directly
0 commit comments