@@ -109,14 +109,18 @@ enum DistributionKind {
109109pub struct DistributionSummary < T : Copy + PartialEq + Default + std:: fmt:: Debug , const N : usize > {
110110 kind : DistributionKind ,
111111 buckets : [ T ; N ] ,
112+ /// How many samples landed in each bucket, in the same order as `buckets`
113+ counts : [ NumProfiles ; N ] ,
114+ /// How many samples did not fit in any bucket
115+ other : NumProfiles ,
112116 // TODO(max): Determine if we need some notion of stability
113117}
114118
115119const SKEW_THRESHOLD : f64 = 0.75 ;
116120
117121impl < T : Copy + PartialEq + Default + std:: fmt:: Debug , const N : usize > DistributionSummary < T , N > {
118122 pub fn empty ( ) -> Self {
119- Self { kind : DistributionKind :: Empty , buckets : [ Default :: default ( ) ; N ] }
123+ Self { kind : DistributionKind :: Empty , buckets : [ Default :: default ( ) ; N ] , counts : [ 0 ; N ] , other : 0 }
120124 }
121125
122126 pub fn new ( dist : & Distribution < T , N > ) -> Self {
@@ -147,7 +151,7 @@ impl<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> Distributi
147151 DistributionKind :: Megamorphic
148152 }
149153 } ;
150- Self { kind, buckets : dist. buckets }
154+ Self { kind, buckets : dist. buckets , counts : dist . counts , other : dist . other }
151155 }
152156
153157 pub fn is_monomorphic ( & self ) -> bool {
@@ -178,6 +182,31 @@ impl<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> Distributi
178182 pub fn buckets ( & self ) -> & [ T ] {
179183 & self . buckets
180184 }
185+
186+ /// How many samples landed in `buckets[idx]`. 0 means the bucket is unused.
187+ pub fn bucket_count ( & self , idx : usize ) -> NumProfiles {
188+ assert ! ( idx < N , "index {idx} out of bounds for buckets[{N}]" ) ;
189+ self . counts [ idx]
190+ }
191+
192+ /// Total number of samples this summary was built from, including the ones that did not fit
193+ /// in a bucket.
194+ pub fn num_seen ( & self ) -> u32 {
195+ self . counts . iter ( ) . map ( |& c| u32:: from ( c) ) . sum :: < u32 > ( ) + u32:: from ( self . other )
196+ }
197+
198+ /// Fraction of observed samples that landed in the buckets for which `keep` returns true.
199+ /// Used to decide whether a guard chain over those buckets is worth building: samples in
200+ /// other buckets, and samples that did not fit in any bucket, have to take the fallback.
201+ pub fn coverage ( & self , keep : impl Fn ( usize , T ) -> bool ) -> f64 {
202+ let num_seen = self . num_seen ( ) ;
203+ if num_seen == 0 { return 0.0 ; }
204+ let covered: u32 = self . counts . iter ( ) . enumerate ( )
205+ . filter ( |& ( idx, & count) | count > 0 && keep ( idx, self . buckets [ idx] ) )
206+ . map ( |( _, & count) | u32:: from ( count) )
207+ . sum ( ) ;
208+ ( covered as f64 ) / ( num_seen as f64 )
209+ }
181210}
182211
183212#[ cfg( test) ]
@@ -365,4 +394,38 @@ mod distribution_tests {
365394 assert_eq ! ( summary. kind, DistributionKind :: SkewedMegamorphic ) ;
366395 assert_eq ! ( summary. buckets[ 0 ] , 12 ) ;
367396 }
397+
398+ #[ test]
399+ fn summary_exposes_counts_and_num_seen ( ) {
400+ let mut dist = Distribution :: < usize , 2 > :: new ( ) ;
401+ dist. observe ( 10 ) ;
402+ dist. observe ( 10 ) ;
403+ dist. observe ( 11 ) ;
404+ dist. observe ( 12 ) ; // does not fit; counted in other
405+ let summary = DistributionSummary :: new ( & dist) ;
406+ assert_eq ! ( summary. bucket_count( 0 ) , 2 ) ;
407+ assert_eq ! ( summary. bucket_count( 1 ) , 1 ) ;
408+ assert_eq ! ( summary. num_seen( ) , 4 ) ;
409+ }
410+
411+ #[ test]
412+ fn coverage_counts_only_kept_buckets ( ) {
413+ let mut dist = Distribution :: < usize , 2 > :: new ( ) ;
414+ dist. observe ( 10 ) ;
415+ dist. observe ( 10 ) ;
416+ dist. observe ( 11 ) ;
417+ dist. observe ( 12 ) ; // does not fit; counted in other
418+ let summary = DistributionSummary :: new ( & dist) ;
419+ assert_eq ! ( summary. coverage( |_, _| true ) , 0.75 ) ;
420+ assert_eq ! ( summary. coverage( |idx, _| idx == 0 ) , 0.5 ) ;
421+ assert_eq ! ( summary. coverage( |_, item| item == 11 ) , 0.25 ) ;
422+ assert_eq ! ( summary. coverage( |_, _| false ) , 0.0 ) ;
423+ }
424+
425+ #[ test]
426+ fn empty_summary_has_no_coverage ( ) {
427+ let summary = DistributionSummary :: < usize , 4 > :: empty ( ) ;
428+ assert_eq ! ( summary. num_seen( ) , 0 ) ;
429+ assert_eq ! ( summary. coverage( |_, _| true ) , 0.0 ) ;
430+ }
368431}
0 commit comments