11package io .prometheus .metrics .core .metrics ;
22
33import static io .prometheus .metrics .core .metrics .TestUtil .assertExemplarEquals ;
4+ import static java .util .concurrent .TimeUnit .SECONDS ;
45import static org .assertj .core .api .Assertions .assertThat ;
56import static org .assertj .core .api .Assertions .assertThatExceptionOfType ;
67import static org .assertj .core .data .Offset .offset ;
8+ import static org .awaitility .Awaitility .await ;
79
810import io .prometheus .metrics .config .EscapingScheme ;
911import io .prometheus .metrics .config .MetricsProperties ;
@@ -1020,10 +1022,17 @@ public void markCurrentSpanAsExemplar() {}
10201022 assertThat (getExemplar (snapshot , Double .POSITIVE_INFINITY , "path" , "/hello" )).isNull ();
10211023 assertThat (getExemplar (snapshot , Double .POSITIVE_INFINITY , "path" , "/world" )).isNull ();
10221024
1023- Thread . sleep (sampleIntervalMillis + 1 );
1025+ waitForSampleInterval (sampleIntervalMillis );
10241026 histogram .labelValues ("/hello" ).observe (4.5 );
10251027 histogram .labelValues ("/world" ).observe (4.5 );
10261028
1029+ await ()
1030+ .atMost (2 , SECONDS )
1031+ .until (
1032+ () ->
1033+ getExemplar (histogram .collect (), Double .POSITIVE_INFINITY , "path" , "/hello" ) != null
1034+ && getExemplar (histogram .collect (), Double .POSITIVE_INFINITY , "path" , "/world" )
1035+ != null );
10271036 snapshot = histogram .collect ();
10281037 assertExemplarEquals (ex1a , getExemplar (snapshot , 1.0 , "path" , "/hello" ));
10291038 assertExemplarEquals (ex1b , getExemplar (snapshot , 1.0 , "path" , "/world" ));
@@ -1036,16 +1045,28 @@ public void markCurrentSpanAsExemplar() {}
10361045 assertExemplarEquals (ex2a , getExemplar (snapshot , Double .POSITIVE_INFINITY , "path" , "/hello" ));
10371046 assertExemplarEquals (ex2b , getExemplar (snapshot , Double .POSITIVE_INFINITY , "path" , "/world" ));
10381047
1039- Thread . sleep (sampleIntervalMillis + 1 );
1048+ waitForSampleInterval (sampleIntervalMillis );
10401049 histogram .labelValues ("/hello" ).observe (1.5 );
10411050 histogram .labelValues ("/world" ).observe (1.5 );
1042- Thread . sleep (sampleIntervalMillis + 1 );
1051+ waitForSampleInterval (sampleIntervalMillis );
10431052 histogram .labelValues ("/hello" ).observe (2.5 );
10441053 histogram .labelValues ("/world" ).observe (2.5 );
1045- Thread . sleep (sampleIntervalMillis + 1 );
1054+ waitForSampleInterval (sampleIntervalMillis );
10461055 histogram .labelValues ("/hello" ).observe (3.5 );
10471056 histogram .labelValues ("/world" ).observe (3.5 );
10481057
1058+ await ()
1059+ .atMost (2 , SECONDS )
1060+ .until (
1061+ () -> {
1062+ HistogramSnapshot s = histogram .collect ();
1063+ return getExemplar (s , 2.0 , "path" , "/hello" ) != null
1064+ && getExemplar (s , 2.0 , "path" , "/world" ) != null
1065+ && getExemplar (s , 3.0 , "path" , "/hello" ) != null
1066+ && getExemplar (s , 3.0 , "path" , "/world" ) != null
1067+ && getExemplar (s , 4.0 , "path" , "/hello" ) != null
1068+ && getExemplar (s , 4.0 , "path" , "/world" ) != null ;
1069+ });
10491070 snapshot = histogram .collect ();
10501071 assertExemplarEquals (ex1a , getExemplar (snapshot , 1.0 , "path" , "/hello" ));
10511072 assertExemplarEquals (ex1b , getExemplar (snapshot , 1.0 , "path" , "/world" ));
@@ -1072,15 +1093,35 @@ public void markCurrentSpanAsExemplar() {}
10721093 "span_id" ,
10731094 "spanId-11" ))
10741095 .build ();
1075- Thread . sleep (sampleIntervalMillis + 1 );
1096+ waitForSampleInterval (sampleIntervalMillis );
10761097 histogram
10771098 .labelValues ("/hello" )
10781099 .observeWithExemplar (3.4 , Labels .of ("key1" , "value1" , "key2" , "value2" ));
1100+ await ()
1101+ .atMost (2 , SECONDS )
1102+ .until (
1103+ () -> {
1104+ Exemplar actual = getExemplar (histogram .collect (), 4.0 , "path" , "/hello" );
1105+ return actual != null && Math .abs (actual .getValue () - 3.4 ) < 0.0001 ;
1106+ });
10791107 snapshot = histogram .collect ();
10801108 // custom exemplars have preference, so the automatic exemplar is replaced
10811109 assertExemplarEquals (custom , getExemplar (snapshot , 4.0 , "path" , "/hello" ));
10821110 }
10831111
1112+ /**
1113+ * Waits for the exemplar sampler's rate limit window so the next observation is accepted. Uses a
1114+ * deterministic delay (2x sample interval) so the scheduler is ready.
1115+ */
1116+ private static void waitForSampleInterval (long sampleIntervalMillis ) {
1117+ try {
1118+ Thread .sleep (2 * sampleIntervalMillis );
1119+ } catch (InterruptedException e ) {
1120+ Thread .currentThread ().interrupt ();
1121+ throw new AssertionError ("Interrupted while waiting for sample interval" , e );
1122+ }
1123+ }
1124+
10841125 private Exemplar getExemplar (HistogramSnapshot snapshot , double le , String ... labels ) {
10851126 HistogramSnapshot .HistogramDataPointSnapshot data =
10861127 snapshot .getDataPoints ().stream ()
0 commit comments