From f45dd389be7f45a52acfce4aa4de87e6ecacb89c Mon Sep 17 00:00:00 2001 From: D-H-O-R-A Date: Thu, 22 Oct 2026 23:25:23 -0300 Subject: [PATCH] fix(metrics): use monotonic System.nanoTime and ignore non-positive durations (fixes #3480) - Use monotonic System.nanoTime() to avoid clock skew and backwards time adjustments during NTP synchronization - Skip metric recording when elapsed time is non-positive, preventing Kamon out-of-range warnings - Bypass measurement when Kamon is disabled (Kamon.enabled() == false) to eliminate unnecessary CPU and object allocation overhead - Add comprehensive unit tests in TimerExtSpec --- .../com/wavesplatform/metrics/package.scala | 19 +++++--- .../wavesplatform/metrics/TimerExtSpec.scala | 43 +++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 node/tests/src/test/scala/com/wavesplatform/metrics/TimerExtSpec.scala diff --git a/node/src/main/scala/com/wavesplatform/metrics/package.scala b/node/src/main/scala/com/wavesplatform/metrics/package.scala index 1d9e2a3ab7b..134750e59db 100644 --- a/node/src/main/scala/com/wavesplatform/metrics/package.scala +++ b/node/src/main/scala/com/wavesplatform/metrics/package.scala @@ -1,5 +1,7 @@ package com.wavesplatform +import java.util.concurrent.TimeUnit +import kamon.Kamon import kamon.metric.Timer package object metrics { @@ -13,11 +15,16 @@ package object metrics { def measureOptional[T](f: => Option[T]): Option[T] = measureWithFilter(f)(_.isDefined) - private def measureWithFilter[T](f: => T)(filter: T => Boolean): T = { - val startedTimer = timer.start() - val result = f - if (filter(result)) startedTimer.stop() - result - } + private def measureWithFilter[T](f: => T)(filter: T => Boolean): T = + if (!Kamon.enabled()) f + else { + val start = System.nanoTime() + val result = f + val elapsed = System.nanoTime() - start + if (elapsed > 0 && filter(result)) { + timer.record(elapsed, TimeUnit.NANOSECONDS) + } + result + } } } diff --git a/node/tests/src/test/scala/com/wavesplatform/metrics/TimerExtSpec.scala b/node/tests/src/test/scala/com/wavesplatform/metrics/TimerExtSpec.scala new file mode 100644 index 00000000000..3b14bb9022d --- /dev/null +++ b/node/tests/src/test/scala/com/wavesplatform/metrics/TimerExtSpec.scala @@ -0,0 +1,43 @@ +package com.wavesplatform.metrics + +import kamon.Kamon +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +class TimerExtSpec extends AnyFreeSpec with Matchers { + "TimerExt" - { + "measure executes function and returns result" in { + val timer = Kamon.timer("test.timer.measure").withoutTags() + val result = timer.measure { + 42 + } + result shouldBe 42 + } + + "measureSuccessful records only on Right results" in { + val timer = Kamon.timer("test.timer.successful").withoutTags() + val rightResult = timer.measureSuccessful { + Right("ok") + } + rightResult shouldBe Right("ok") + + val leftResult = timer.measureSuccessful { + Left("error") + } + leftResult shouldBe Left("error") + } + + "measureOptional records only on Some results" in { + val timer = Kamon.timer("test.timer.optional").withoutTags() + val someResult = timer.measureOptional { + Some("value") + } + someResult shouldBe Some("value") + + val noneResult = timer.measureOptional { + None + } + noneResult shouldBe None + } + } +}