@@ -175,14 +175,7 @@ void shortLivedPublish() {
175175 .register (registry );
176176
177177 // before step rollover
178- assertThat (counter .count ()).isZero ();
179- assertThat (timer .count ()).isZero ();
180- assertThat (timer .totalTime (MILLISECONDS )).isZero ();
181- assertThat (summary .count ()).isZero ();
182- assertThat (summary .totalAmount ()).isZero ();
183- assertThat (functionCounter .count ()).isZero ();
184- assertThat (functionTimer .count ()).isZero ();
185- assertThat (functionTimer .totalTime (MILLISECONDS )).isZero ();
178+ assertBeforeRollover (counter , timer , summary , functionCounter , functionTimer );
186179
187180 registry .close ();
188181
@@ -224,17 +217,10 @@ void finalPushHasPartialStep() {
224217 .register (registry );
225218
226219 // before step rollover
227- assertThat (counter .count ()).isZero ();
228- assertThat (timer .count ()).isZero ();
229- assertThat (timer .totalTime (MILLISECONDS )).isZero ();
230- assertThat (summary .count ()).isZero ();
231- assertThat (summary .totalAmount ()).isZero ();
232- assertThat (functionCounter .count ()).isZero ();
233- assertThat (functionTimer .count ()).isZero ();
234- assertThat (functionTimer .totalTime (MILLISECONDS )).isZero ();
220+ assertBeforeRollover (counter , timer , summary , functionCounter , functionTimer );
235221
236- clock . add ( config .step ());
237- registry .publish ();
222+ addTimeWithRolloverOnStepStart ( clock , registry , config , config .step ());
223+ registry .scheduledPublish ();
238224
239225 assertThat (registry .publishedCounterCounts ).hasSize (1 );
240226 assertThat (registry .publishedCounterCounts .pop ()).isOne ();
@@ -254,7 +240,7 @@ void finalPushHasPartialStep() {
254240 assertThat (registry .publishedFunctionTimerTotals .pop ()).isEqualTo (53 );
255241
256242 // set clock to middle of second step
257- clock . add ( config .step ().dividedBy (2 ));
243+ addTimeWithRolloverOnStepStart ( clock , registry , config , config .step ().dividedBy (2 ));
258244 // record some more values in new step interval
259245 counter .increment (2 );
260246 timer .record (6 , MILLISECONDS );
@@ -309,14 +295,7 @@ void publishOnCloseCrossesStepBoundary() {
309295 .register (registry );
310296
311297 // before rollover
312- assertThat (counter .count ()).isZero ();
313- assertThat (timer .count ()).isZero ();
314- assertThat (timer .totalTime (MILLISECONDS )).isZero ();
315- assertThat (summary .count ()).isZero ();
316- assertThat (summary .totalAmount ()).isZero ();
317- assertThat (functionCounter .count ()).isZero ();
318- assertThat (functionTimer .count ()).isZero ();
319- assertThat (functionTimer .totalTime (MILLISECONDS )).isZero ();
298+ assertBeforeRollover (counter , timer , summary , functionCounter , functionTimer );
320299
321300 // before publishing, simulate a step boundary being crossed after forced rollover
322301 // on close and before/during publishing
@@ -342,6 +321,57 @@ void publishOnCloseCrossesStepBoundary() {
342321 assertThat (registry .publishedFunctionTimerTotals .pop ()).isEqualTo (53 );
343322 }
344323
324+ @ Test
325+ @ Issue ("#3863" )
326+ void shouldPublishLastCompletedStepWhenClosingBeforeScheduledPublish () {
327+ Counter counter = Counter .builder ("counter_3863" ).register (registry );
328+ Timer timer = Timer .builder ("timer_3863" ).register (registry );
329+ DistributionSummary summary = DistributionSummary .builder ("summary_3863" ).register (registry );
330+
331+ AtomicLong functionValue = new AtomicLong (0 );
332+ FunctionCounter functionCounter = FunctionCounter
333+ .builder ("counter.function_3863" , functionValue , AtomicLong ::get )
334+ .register (registry );
335+ FunctionTimer functionTimer = FunctionTimer
336+ .builder ("timer.function_3863" , this , obj -> 3 , obj -> 53 , MILLISECONDS )
337+ .register (registry );
338+
339+ counter .increment ();
340+ timer .record (5 , MILLISECONDS );
341+ summary .record (5 );
342+ functionValue .set (1 );
343+
344+ // before rollover
345+ assertBeforeRollover (counter , timer , summary , functionCounter , functionTimer );
346+
347+ addTimeWithRolloverOnStepStart (clock , registry , config , Duration .ofSeconds (60 ));
348+
349+ // All new recordings now belong to next step.
350+ counter .increment (2 );
351+ timer .record (10 , MILLISECONDS );
352+ summary .record (10 );
353+ functionValue .incrementAndGet ();
354+
355+ // Simulating the application close behaviour before actual publishing happens.
356+ registry .close ();
357+
358+ assertThat (registry .publishedCounterCounts ).hasSize (2 );
359+ assertThat (registry .sumAllPublishedValues (registry .publishedCounterCounts )).isEqualTo (3 );
360+ assertThat (registry .publishedTimerCounts ).hasSize (2 );
361+ assertThat (registry .sumAllPublishedValues (registry .publishedTimerCounts )).isEqualTo (2 );
362+ assertThat (registry .sumAllPublishedValues (registry .publishedTimerSumMilliseconds )).isEqualTo (15 );
363+ assertThat (registry .publishedSummaryCounts ).hasSize (2 );
364+ assertThat (registry .sumAllPublishedValues (registry .publishedSummaryCounts )).isEqualTo (2 );
365+ assertThat (registry .sumAllPublishedValues (registry .publishedSummaryTotals )).isEqualTo (15 );
366+
367+ assertThat (registry .publishedFunctionCounterCounts ).hasSize (2 );
368+ assertThat (registry .sumAllPublishedValues (registry .publishedFunctionCounterCounts )).isEqualTo (2 );
369+
370+ assertThat (registry .publishedFunctionTimerCounts ).hasSize (2 );
371+ assertThat (registry .sumAllPublishedValues (registry .publishedFunctionTimerCounts )).isEqualTo (3 );
372+ assertThat (registry .sumAllPublishedValues (registry .publishedFunctionTimerTotals )).isEqualTo (53 );
373+ }
374+
345375 @ Test
346376 @ Issue ("#2818" )
347377 void scheduledRollOver () {
@@ -357,28 +387,17 @@ void scheduledRollOver() {
357387 .register (registry );
358388
359389 // before rollover
360- assertThat (counter .count ()).isZero ();
361- assertThat (timer .count ()).isZero ();
362- assertThat (timer .totalTime (MILLISECONDS )).isZero ();
363- assertThat (summary .count ()).isZero ();
364- assertThat (summary .totalAmount ()).isZero ();
365- assertThat (functionCounter .count ()).isZero ();
366- assertThat (functionTimer .count ()).isZero ();
367- assertThat (functionTimer .totalTime (MILLISECONDS )).isZero ();
368-
369- clock .addSeconds (60 );
370- // simulate this being scheduled at the start of the step
371- registry .pollMetersToRollover ();
390+ assertBeforeRollover (counter , timer , summary , functionCounter , functionTimer );
372391
373- clock . addSeconds ( 1 );
392+ addTimeWithRolloverOnStepStart ( clock , registry , config , Duration . ofSeconds ( 60 ) );
374393 // these recordings belong to the current step and should not be published
375394 counter .increment ();
376395 timer .record (5 , MILLISECONDS );
377396 summary .record (8 );
378- clock . addSeconds (10 );
397+ addTimeWithRolloverOnStepStart ( clock , registry , config , Duration . ofSeconds (10 ) );
379398
380399 // recordings that happened in the previous step should be published
381- registry .publish ();
400+ registry .scheduledPublish ();
382401 assertThat (registry .publishedCounterCounts ).hasSize (1 );
383402 assertThat (registry .publishedCounterCounts .pop ()).isOne ();
384403 assertThat (registry .publishedTimerCounts ).hasSize (1 );
@@ -415,6 +434,8 @@ private class MyStepMeterRegistry extends StepMeterRegistry {
415434
416435 Deque <Double > publishedFunctionTimerTotals = new ArrayDeque <>();
417436
437+ private long lastScheduledPublishStartTime = 0L ;
438+
418439 @ Nullable
419440 Runnable prePublishAction ;
420441
@@ -438,6 +459,16 @@ protected void publish() {
438459 .collect (Collectors .toList ());
439460 }
440461
462+ private void scheduledPublish () {
463+ this .lastScheduledPublishStartTime = clock .wallTime ();
464+ this .publish ();
465+ }
466+
467+ @ Override
468+ protected long getLastScheduledPublishStartTime () {
469+ return lastScheduledPublishStartTime ;
470+ }
471+
441472 private Timer publishTimer (Timer timer ) {
442473 publishedTimerCounts .add (timer .count ());
443474 publishedTimerSumMilliseconds .add (timer .totalTime (MILLISECONDS ));
@@ -466,11 +497,52 @@ private DistributionSummary publishSummary(DistributionSummary summary) {
466497 return summary ;
467498 }
468499
500+ <T extends Number > double sumAllPublishedValues (Deque <T > deque ) {
501+ double sum = 0 ;
502+ while (!deque .isEmpty ()) {
503+ sum += deque .pop ().doubleValue ();
504+ }
505+ return sum ;
506+ }
507+
469508 @ Override
470509 protected TimeUnit getBaseTimeUnit () {
471510 return TimeUnit .SECONDS ;
472511 }
473512
474513 }
475514
515+ private static void assertBeforeRollover (final Counter counter , final Timer timer ,
516+ final DistributionSummary summary , final FunctionCounter functionCounter ,
517+ final FunctionTimer functionTimer ) {
518+ assertThat (counter .count ()).isZero ();
519+ assertThat (timer .count ()).isZero ();
520+ assertThat (timer .totalTime (MILLISECONDS )).isZero ();
521+ assertThat (summary .count ()).isZero ();
522+ assertThat (summary .totalAmount ()).isZero ();
523+ assertThat (functionCounter .count ()).isZero ();
524+ assertThat (functionTimer .count ()).isZero ();
525+ assertThat (functionTimer .totalTime (MILLISECONDS )).isZero ();
526+ }
527+
528+ /**
529+ * This method simulates the behaviour StepRegistry will exhibit when rollOver is
530+ * scheduled on a thread. This calls {@link StepMeterRegistry#pollMetersToRollover()}
531+ * as soon as the step is crossed.
532+ */
533+ private void addTimeWithRolloverOnStepStart (MockClock clock , StepMeterRegistry registry , StepRegistryConfig config ,
534+ Duration timeToAdd ) {
535+
536+ long currentTime = clock .wallTime ();
537+ long boundaryForNextStep = ((currentTime / config .step ().toMillis ()) + 1 ) * config .step ().toMillis ();
538+ long timeToNextStep = boundaryForNextStep - currentTime ;
539+ if (timeToAdd .toMillis () >= timeToNextStep ) {
540+ clock .add (timeToNextStep , MILLISECONDS );
541+ registry .pollMetersToRollover ();
542+ clock .add ((timeToAdd .toMillis () - timeToNextStep ), MILLISECONDS );
543+ return ;
544+ }
545+ clock .add (timeToAdd );
546+ }
547+
476548}
0 commit comments