@@ -1701,9 +1701,11 @@ async fn e2e_grouped_quantile_preserves_output_label_shape() {
17011701 ) ;
17021702}
17031703
1704- /// A grouped quantile served by a DDSketch aggregation: samples flow through
1705- /// remote write and precompute into one DDSketch per group, and each returned
1706- /// quantile stays within the sketch's relative-accuracy bound.
1704+ /// A grouped quantile served by a DDSketch aggregation: samples from two
1705+ /// instances per job flow through remote write and precompute into one
1706+ /// DDSketch per group, and each returned quantile stays within the sketch's
1707+ /// relative-accuracy bound. Zeros are part of the population, so a sketch
1708+ /// that dropped them would answer outside the bound.
17071709#[ tokio:: test]
17081710async fn e2e_grouped_quantile_over_ddsketch_is_within_alpha ( ) {
17091711 let port = 19421u16 ;
@@ -1721,30 +1723,38 @@ async fn e2e_grouped_quantile_over_ddsketch_is_within_alpha() {
17211723 ) ;
17221724 config. parameters . insert ( "alpha" . to_string ( ) , json ! ( alpha) ) ;
17231725
1724- // Each group gets 1..=100 (scaled per group) inside the (1s, 2s] window,
1725- // plus one later sample to close the window.
1726+ // Inside the (1s, 2s] window, instance "a" sends scale * 1..=50 and "b"
1727+ // sends scale * 51..=100, and each sends ten zeros. Samples go in time
1728+ // order across instances, since the watermark is per group, and one later
1729+ // sample per series closes the window.
17261730 let groups = [ ( "frontend" , 1.0 ) , ( "backend" , 10.0 ) ] ;
1727- let samples = groups
1728- . iter ( )
1729- . flat_map ( |& ( job, scale) | {
1730- ( 1 ..=100 )
1731- . map ( move |i| {
1732- make_timeseries ( metric, vec ! [ ( "job" , job) ] , 1_000 + 5 * i, scale * i as f64 )
1733- } )
1734- . chain ( std:: iter:: once ( make_timeseries (
1735- metric,
1736- vec ! [ ( "job" , job) ] ,
1737- 3_500 ,
1738- scale,
1739- ) ) )
1740- } )
1741- . collect ( ) ;
1731+ let instances = [ ( "a" , 1 ) , ( "b" , 51 ) ] ;
1732+ let mut samples = Vec :: new ( ) ;
1733+ for ( job, scale) in groups {
1734+ for i in 0 ..50 {
1735+ for ( instance, first) in instances {
1736+ let value = scale * ( first + i) as f64 ;
1737+ let labels = vec ! [ ( "job" , job) , ( "instance" , instance) ] ;
1738+ samples. push ( make_timeseries ( metric, labels, 1_005 + 5 * i, value) ) ;
1739+ }
1740+ }
1741+ for j in 0 ..10 {
1742+ for ( instance, _) in instances {
1743+ let labels = vec ! [ ( "job" , job) , ( "instance" , instance) ] ;
1744+ samples. push ( make_timeseries ( metric, labels, 1_600 + j, 0.0 ) ) ;
1745+ }
1746+ }
1747+ for ( instance, _) in instances {
1748+ let labels = vec ! [ ( "job" , job) , ( "instance" , instance) ] ;
1749+ samples. push ( make_timeseries ( metric, labels, 3_500 , scale) ) ;
1750+ }
1751+ }
17421752 let ( engine, query) = NativeDagScenario {
17431753 port,
17441754 metric,
17451755 query,
17461756 aggregation_configs : vec ! [ config] ,
1747- schema_labels : vec ! [ "job" . to_string( ) ] ,
1757+ schema_labels : vec ! [ "instance" . to_string ( ) , " job". to_string( ) ] ,
17481758 samples,
17491759 evaluation_time_seconds : 2.0 ,
17501760 base_interval_ms : 1_000 ,
@@ -1763,8 +1773,9 @@ async fn e2e_grouped_quantile_over_ddsketch_is_within_alpha() {
17631773 for element in vector. values {
17641774 let job = element. labels . labels [ 0 ] . as_str ( ) ;
17651775 let scale = groups. iter ( ) . find ( |( g, _) | * g == job) . unwrap ( ) . 1 ;
1766- // The 0.9 quantile of scale * {1..=100} is scale * 90.
1767- let truth = scale * 90.0 ;
1776+ // 120 values: 20 zeros, then scale * 1..=100. Rank ceil(0.9 * 120) = 108
1777+ // is scale * 88; without the zeros it would be scale * 90.
1778+ let truth = scale * 88.0 ;
17681779 assert ! (
17691780 ( element. value - truth) . abs( ) / truth <= alpha,
17701781 "{job}: estimate {} vs truth {truth}" ,
0 commit comments