@@ -2,6 +2,8 @@ use std::borrow::{Borrow, Cow};
2
2
use std:: sync:: Arc ;
3
3
use std:: { fmt, hash} ;
4
4
5
+ use std:: hash:: { Hash , Hasher } ;
6
+
5
7
/// The key part of attribute [KeyValue] pairs.
6
8
///
7
9
/// See the [attribute naming] spec for guidelines.
@@ -399,6 +401,42 @@ impl KeyValue {
399
401
}
400
402
}
401
403
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
+
402
440
/// Information about a library or crate providing instrumentation.
403
441
///
404
442
/// An instrumentation scope should be named to follow any naming conventions
@@ -427,22 +465,33 @@ pub struct InstrumentationScope {
427
465
attributes : Vec < KeyValue > ,
428
466
}
429
467
430
- // Uniqueness for InstrumentationScope does not depend on attributes
431
- impl Eq for InstrumentationScope { }
432
-
433
468
impl PartialEq for InstrumentationScope {
434
469
fn eq ( & self , other : & Self ) -> bool {
435
470
self . name == other. name
436
471
&& self . version == other. version
437
472
&& 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
+ }
438
480
}
439
481
}
440
482
483
+ impl Eq for InstrumentationScope { }
484
+
441
485
impl hash:: Hash for InstrumentationScope {
442
486
fn hash < H : hash:: Hasher > ( & self , state : & mut H ) {
443
487
self . name . hash ( state) ;
444
488
self . version . hash ( state) ;
445
489
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
+ }
446
495
}
447
496
}
448
497
@@ -561,3 +610,140 @@ impl InstrumentationScopeBuilder {
561
610
}
562
611
}
563
612
}
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