@@ -2,6 +2,8 @@ use std::borrow::{Borrow, Cow};
22use std:: sync:: Arc ;
33use std:: { fmt, hash} ;
44
5+ use std:: hash:: { Hash , Hasher } ;
6+
57/// The key part of attribute [KeyValue] pairs.
68///
79/// See the [attribute naming] spec for guidelines.
@@ -399,6 +401,42 @@ impl KeyValue {
399401 }
400402}
401403
404+ struct F64Hashable ( f64 ) ;
405+
406+ impl PartialEq for F64Hashable {
407+ fn eq ( & self , other : & Self ) -> bool {
408+ self . 0 . to_bits ( ) == other. 0 . to_bits ( )
409+ }
410+ }
411+
412+ impl Eq for F64Hashable { }
413+
414+ impl Hash for F64Hashable {
415+ fn hash < H : Hasher > ( & self , state : & mut H ) {
416+ self . 0 . to_bits ( ) . hash ( state) ;
417+ }
418+ }
419+
420+ impl Hash for KeyValue {
421+ fn hash < H : Hasher > ( & self , state : & mut H ) {
422+ self . key . hash ( state) ;
423+ match & self . value {
424+ Value :: F64 ( f) => F64Hashable ( * f) . hash ( state) ,
425+ Value :: Array ( a) => match a {
426+ Array :: Bool ( b) => b. hash ( state) ,
427+ Array :: I64 ( i) => i. hash ( state) ,
428+ Array :: F64 ( f) => f. iter ( ) . for_each ( |f| F64Hashable ( * f) . hash ( state) ) ,
429+ Array :: String ( s) => s. hash ( state) ,
430+ } ,
431+ Value :: Bool ( b) => b. hash ( state) ,
432+ Value :: I64 ( i) => i. hash ( state) ,
433+ Value :: String ( s) => s. hash ( state) ,
434+ } ;
435+ }
436+ }
437+
438+ impl Eq for KeyValue { }
439+
402440/// Information about a library or crate providing instrumentation.
403441///
404442/// An instrumentation scope should be named to follow any naming conventions
@@ -427,22 +465,33 @@ pub struct InstrumentationScope {
427465 attributes : Vec < KeyValue > ,
428466}
429467
430- // Uniqueness for InstrumentationScope does not depend on attributes
431- impl Eq for InstrumentationScope { }
432-
433468impl PartialEq for InstrumentationScope {
434469 fn eq ( & self , other : & Self ) -> bool {
435470 self . name == other. name
436471 && self . version == other. version
437472 && self . schema_url == other. schema_url
473+ && {
474+ let mut self_attrs = self . attributes . clone ( ) ;
475+ let mut other_attrs = other. attributes . clone ( ) ;
476+ self_attrs. sort_unstable_by ( |a, b| a. key . cmp ( & b. key ) ) ;
477+ other_attrs. sort_unstable_by ( |a, b| a. key . cmp ( & b. key ) ) ;
478+ self_attrs == other_attrs
479+ }
438480 }
439481}
440482
483+ impl Eq for InstrumentationScope { }
484+
441485impl hash:: Hash for InstrumentationScope {
442486 fn hash < H : hash:: Hasher > ( & self , state : & mut H ) {
443487 self . name . hash ( state) ;
444488 self . version . hash ( state) ;
445489 self . schema_url . hash ( state) ;
490+ let mut sorted_attrs = self . attributes . clone ( ) ;
491+ sorted_attrs. sort_unstable_by ( |a, b| a. key . cmp ( & b. key ) ) ;
492+ for attribute in sorted_attrs {
493+ attribute. hash ( state) ;
494+ }
446495 }
447496}
448497
@@ -561,3 +610,140 @@ impl InstrumentationScopeBuilder {
561610 }
562611 }
563612}
613+
614+ #[ cfg( test) ]
615+ mod tests {
616+ use std:: hash:: { Hash , Hasher } ;
617+
618+ use crate :: { InstrumentationScope , KeyValue } ;
619+
620+ use rand:: random;
621+ use std:: collections:: hash_map:: DefaultHasher ;
622+ use std:: f64;
623+
624+ #[ test]
625+ fn kv_float_equality ( ) {
626+ let kv1 = KeyValue :: new ( "key" , 1.0 ) ;
627+ let kv2 = KeyValue :: new ( "key" , 1.0 ) ;
628+ assert_eq ! ( kv1, kv2) ;
629+
630+ let kv1 = KeyValue :: new ( "key" , 1.0 ) ;
631+ let kv2 = KeyValue :: new ( "key" , 1.01 ) ;
632+ assert_ne ! ( kv1, kv2) ;
633+
634+ let kv1 = KeyValue :: new ( "key" , f64:: NAN ) ;
635+ let kv2 = KeyValue :: new ( "key" , f64:: NAN ) ;
636+ assert_ne ! ( kv1, kv2, "NAN is not equal to itself" ) ;
637+
638+ for float_val in [
639+ f64:: INFINITY ,
640+ f64:: NEG_INFINITY ,
641+ f64:: MAX ,
642+ f64:: MIN ,
643+ f64:: MIN_POSITIVE ,
644+ ]
645+ . iter ( )
646+ {
647+ let kv1 = KeyValue :: new ( "key" , * float_val) ;
648+ let kv2 = KeyValue :: new ( "key" , * float_val) ;
649+ assert_eq ! ( kv1, kv2) ;
650+ }
651+
652+ for _ in 0 ..100 {
653+ let random_value = random :: < f64 > ( ) ;
654+ let kv1 = KeyValue :: new ( "key" , random_value) ;
655+ let kv2 = KeyValue :: new ( "key" , random_value) ;
656+ assert_eq ! ( kv1, kv2) ;
657+ }
658+ }
659+
660+ #[ test]
661+ fn kv_float_hash ( ) {
662+ for float_val in [
663+ f64:: NAN ,
664+ f64:: INFINITY ,
665+ f64:: NEG_INFINITY ,
666+ f64:: MAX ,
667+ f64:: MIN ,
668+ f64:: MIN_POSITIVE ,
669+ ]
670+ . iter ( )
671+ {
672+ let kv1 = KeyValue :: new ( "key" , * float_val) ;
673+ let kv2 = KeyValue :: new ( "key" , * float_val) ;
674+ assert_eq ! ( hash_helper( & kv1) , hash_helper( & kv2) ) ;
675+ }
676+
677+ for _ in 0 ..100 {
678+ let random_value = random :: < f64 > ( ) ;
679+ let kv1 = KeyValue :: new ( "key" , random_value) ;
680+ let kv2 = KeyValue :: new ( "key" , random_value) ;
681+ assert_eq ! ( hash_helper( & kv1) , hash_helper( & kv2) ) ;
682+ }
683+ }
684+
685+ fn hash_helper < T : Hash > ( item : & T ) -> u64 {
686+ let mut hasher = DefaultHasher :: new ( ) ;
687+ item. hash ( & mut hasher) ;
688+ hasher. finish ( )
689+ }
690+
691+ #[ test]
692+ fn instrumentation_scope_equality ( ) {
693+ let scope1 = InstrumentationScope :: builder ( "my-crate" )
694+ . with_version ( "v0.1.0" )
695+ . with_schema_url ( "https://opentelemetry.io/schemas/1.17.0" )
696+ . with_attributes ( [ KeyValue :: new ( "k" , "v" ) ] )
697+ . build ( ) ;
698+ let scope2 = InstrumentationScope :: builder ( "my-crate" )
699+ . with_version ( "v0.1.0" )
700+ . with_schema_url ( "https://opentelemetry.io/schemas/1.17.0" )
701+ . with_attributes ( [ KeyValue :: new ( "k" , "v" ) ] )
702+ . build ( ) ;
703+ assert_eq ! ( scope1, scope2) ;
704+ }
705+
706+ #[ test]
707+ fn instrumentation_scope_equality_attributes_diff_order ( ) {
708+ let scope1 = InstrumentationScope :: builder ( "my-crate" )
709+ . with_version ( "v0.1.0" )
710+ . with_schema_url ( "https://opentelemetry.io/schemas/1.17.0" )
711+ . with_attributes ( [ KeyValue :: new ( "k1" , "v1" ) , KeyValue :: new ( "k2" , "v2" ) ] )
712+ . build ( ) ;
713+ let scope2 = InstrumentationScope :: builder ( "my-crate" )
714+ . with_version ( "v0.1.0" )
715+ . with_schema_url ( "https://opentelemetry.io/schemas/1.17.0" )
716+ . with_attributes ( [ KeyValue :: new ( "k2" , "v2" ) , KeyValue :: new ( "k1" , "v1" ) ] )
717+ . build ( ) ;
718+ assert_eq ! ( scope1, scope2) ;
719+
720+ // assert hash are same for both scopes
721+ let mut hasher1 = std:: collections:: hash_map:: DefaultHasher :: new ( ) ;
722+ scope1. hash ( & mut hasher1) ;
723+ let mut hasher2 = std:: collections:: hash_map:: DefaultHasher :: new ( ) ;
724+ scope2. hash ( & mut hasher2) ;
725+ assert_eq ! ( hasher1. finish( ) , hasher2. finish( ) ) ;
726+ }
727+
728+ #[ test]
729+ fn instrumentation_scope_equality_different_attributes ( ) {
730+ let scope1 = InstrumentationScope :: builder ( "my-crate" )
731+ . with_version ( "v0.1.0" )
732+ . with_schema_url ( "https://opentelemetry.io/schemas/1.17.0" )
733+ . with_attributes ( [ KeyValue :: new ( "k1" , "v1" ) , KeyValue :: new ( "k2" , "v2" ) ] )
734+ . build ( ) ;
735+ let scope2 = InstrumentationScope :: builder ( "my-crate" )
736+ . with_version ( "v0.1.0" )
737+ . with_schema_url ( "https://opentelemetry.io/schemas/1.17.0" )
738+ . with_attributes ( [ KeyValue :: new ( "k2" , "v3" ) , KeyValue :: new ( "k4" , "v5" ) ] )
739+ . build ( ) ;
740+ assert_ne ! ( scope1, scope2) ;
741+
742+ // assert hash are same for both scopes
743+ let mut hasher1 = std:: collections:: hash_map:: DefaultHasher :: new ( ) ;
744+ scope1. hash ( & mut hasher1) ;
745+ let mut hasher2 = std:: collections:: hash_map:: DefaultHasher :: new ( ) ;
746+ scope2. hash ( & mut hasher2) ;
747+ assert_ne ! ( hasher1. finish( ) , hasher2. finish( ) ) ;
748+ }
749+ }
0 commit comments