From 501f8e103f96b5544b65f20201fbdc95ba044c78 Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Thu, 9 Jun 2022 15:06:38 +0300 Subject: [PATCH 01/19] Measure metrics using `otel4s` --- build.sbt | 10 + .../typelevel/keypool/PoolMetricsSpec.scala | 258 ++++++++++++++++++ .../scala/org/typelevel/keypool/KeyPool.scala | 94 ++++--- .../typelevel/keypool/KeyPoolBuilder.scala | 12 +- .../scala/org/typelevel/keypool/Pool.scala | 20 +- .../typelevel/keypool/internal/Metrics.scala | 120 ++++++++ 6 files changed, 467 insertions(+), 47 deletions(-) create mode 100644 core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala create mode 100644 core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala diff --git a/build.sbt b/build.sbt index 6cab399a..58032cf7 100644 --- a/build.sbt +++ b/build.sbt @@ -23,6 +23,13 @@ lazy val core = crossProject(JVMPlatform, JSPlatform) .jsSettings( tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "0.4.6").toMap ) + .jvmSettings( + libraryDependencies ++= Seq( + "org.typelevel" %% "otel4s-java" % otel4sV % Test, + "io.opentelemetry" % "opentelemetry-sdk" % "1.14.0" % Test, + "io.opentelemetry" % "opentelemetry-sdk-testing" % "1.14.0" % Test + ) + ) .settings( mimaBinaryIssueFilters ++= Seq( ProblemFilters.exclude[DirectMissingMethodProblem]("org.typelevel.keypool.KeyPool.destroy"), @@ -48,6 +55,8 @@ lazy val docs = project val catsV = "2.7.0" val catsEffectV = "3.3.12" +val otel4sV = "0.0-c25617c-20220609T070516Z-SNAPSHOT" + val munitV = "0.7.29" val munitCatsEffectV = "1.0.7" @@ -60,6 +69,7 @@ lazy val commonSettings = Seq( libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, "org.typelevel" %%% "cats-effect-kernel" % catsEffectV, + "org.typelevel" %%% "otel4s-core" % otel4sV, "org.typelevel" %%% "cats-effect-std" % catsEffectV % Test, "org.scalameta" %%% "munit" % munitV % Test, "org.typelevel" %%% "munit-cats-effect-3" % munitCatsEffectV % Test diff --git a/core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala new file mode 100644 index 00000000..ce09818b --- /dev/null +++ b/core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -0,0 +1,258 @@ +package org.typelevel.keypool + +import java.util.concurrent.TimeUnit + +import cats.syntax.all._ +import cats.effect._ +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.metrics.{ + Aggregation, + InstrumentSelector, + InstrumentType, + SdkMeterProvider, + View +} +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader +import munit.CatsEffectSuite +import org.typelevel.otel4s.{MeterProvider, Otel4s} +import org.typelevel.otel4s.java.OtelJava +import scala.concurrent.duration._ +import scala.jdk.CollectionConverters._ +import scala.util.control.NoStackTrace + +class PoolMetricsSpec extends CatsEffectSuite { + + test("Metrics should be empty for unused pool") { + val expectedSnapshot = MetricsSnapshot(0, 0, Nil, 0, Nil) + + for { + snapshot <- setupSdk.use(sdk => mkPool(sdk.otel.meterProvider).use(_ => sdk.snapshot)) + } yield assertEquals(snapshot, expectedSnapshot) + } + + test("In use: increment on acquire and decrement on release") { + poolTest() { (sdk, pool) => + for { + inUse <- pool.take.use(_ => sdk.snapshot) + afterUse <- sdk.snapshot + } yield { + assertEquals(inUse.inUse, 1L) + assertEquals(afterUse.inUse, 0L) + } + } + } + + test("In use: increment on acquire and decrement on release (failure)") { + val exception = new RuntimeException("Something went wrong") with NoStackTrace + + poolTest() { (sdk, pool) => + for { + deferred <- IO.deferred[MetricsSnapshot] + _ <- pool.take + .use(_ => sdk.snapshot.flatMap(deferred.complete) >> IO.raiseError(exception)) + .attempt + inUse <- deferred.get + afterUse <- sdk.snapshot + } yield { + assertEquals(inUse.inUse, 1L) + assertEquals(afterUse.inUse, 0L) + } + } + } + + test("Idle: keep 0 when `maxIdle` is 0") { + // todo replace with `withMaxIdle` once https://github.com/typelevel/keypool/pull/394 gets merged + poolTest(_.withMaxTotal(-1)) { (sdk, pool) => + for { + inUse <- pool.take.use(_ => sdk.snapshot) + afterUse <- sdk.snapshot + } yield { + assertEquals(inUse.idle, 0L) + assertEquals(afterUse.idle, 0L) + } + } + } + + test("Idle: keep 1 when `maxIdle` is 1") { + // todo replace with `withMaxIdle` once https://github.com/typelevel/keypool/pull/394 gets merged + poolTest(_.withMaxTotal(1)) { (sdk, pool) => + for { + inUse <- pool.take.use(_ => sdk.snapshot) + afterUse <- sdk.snapshot + } yield { + assertEquals(inUse.idle, 0L) + assertEquals(afterUse.idle, 1L) + } + } + } + + test("Idle: decrement on reaper cleanup") { + // todo replace with `withMaxIdle` once https://github.com/typelevel/keypool/pull/394 gets merged + poolTest(_.withMaxTotal(1).withIdleTimeAllowedInPool(1.second)) { (sdk, pool) => + for { + inUse <- pool.take.use(_ => sdk.snapshot) + afterUse <- sdk.snapshot + afterSleep <- sdk.snapshot.delayBy(6.seconds) + } yield { + assertEquals(inUse.idle, 0L) + assertEquals(afterUse.idle, 1L) + assertEquals(afterSleep.idle, 0L) + } + } + } + + test("Generate valid metric snapshots") { + setupSdk.use { sdk => + mkPool(sdk.otel.meterProvider) + .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) + .map { case (inUse, afterUse) => + val acquireDuration = + List(HistogramSnapshot(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0))) + + val expectedInUse = MetricsSnapshot( + idle = 0, + inUse = 1, + inUseDuration = Nil, + acquiredTotal = 1, + acquireDuration = acquireDuration + ) + + val expectedAfterUser = MetricsSnapshot( + idle = 1, + inUse = 0, + inUseDuration = List(HistogramSnapshot(0, 1, HistogramBuckets, List(0, 0, 0, 1, 0))), + acquiredTotal = 1, + acquireDuration = acquireDuration + ) + + assertEquals(inUse, expectedInUse) + assertEquals( + afterUse.copy(inUseDuration = afterUse.inUseDuration.map(_.copy(sum = 0))), + expectedAfterUser + ) + assert(afterUse.inUseDuration.forall(r => r.sum >= 500 && r.sum <= 700)) + } + } + } + + private def poolTest( + customize: Pool.Builder[IO, Ref[IO, Int]] => Pool.Builder[IO, Ref[IO, Int]] = identity + )(scenario: (OtelSdk[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = + setupSdk.use { sdk => + val builder = + Pool.Builder(Ref.of[IO, Int](1), nothing).withMeterProvider(sdk.otel.meterProvider) + + customize(builder).build.use(pool => scenario(sdk, pool)) + } + + private def mkPool(meterProvider: MeterProvider[IO]) = + Pool + .Builder( + Ref.of[IO, Int](1), + nothing + ) + .withMeterProvider(meterProvider) + .withMaxTotal(10) + .build + + private def setupSdk: Resource[IO, OtelSdk[IO]] = { + val acquire = IO.delay { + val mr = InMemoryMetricReader.create() + + val mp = SdkMeterProvider + .builder() + .registerMetricReader(mr) + .registerView( + InstrumentSelector.builder().setType(InstrumentType.HISTOGRAM).build(), + View + .builder() + .setAggregation( + Aggregation.explicitBucketHistogram(HistogramBuckets.map(Double.box).asJava) + ) + .build() + ) + .build() + + val sdk = OpenTelemetrySdk + .builder() + .setMeterProvider(mp) + .build() + + new OtelSdk[IO] { + def metricReader: InMemoryMetricReader = mr + def otel: Otel4s[IO] = OtelJava.forSync[IO](sdk) + def flush: IO[Unit] = IO.blocking(mp.forceFlush().join(5, TimeUnit.SECONDS)) + + def snapshot: IO[MetricsSnapshot] = { + IO.delay { + val items = metricReader.collectAllMetrics().asScala.toList + + def counterValue(name: String): Long = + items + .find(_.getName === name) + .flatMap(_.getLongSumData.getPoints.asScala.headOption.map(_.getValue)) + .getOrElse(0L) + + def histogramSnapshot(name: String): List[HistogramSnapshot] = + items + .find(_.getName === name) + .map { metric => + val points = metric.getHistogramData.getPoints.asScala.toList + + points.map { hdp => + HistogramSnapshot( + hdp.getSum, + hdp.getCount, + hdp.getBoundaries.asScala.toList.map(Double.unbox), + hdp.getCounts.asScala.toList.map(Long.unbox) + ) + } + } + .getOrElse(Nil) + + MetricsSnapshot( + counterValue("idle"), + counterValue("in_use"), + histogramSnapshot("in_use_duration"), + counterValue("acquired_total"), + histogramSnapshot("acquire_duration") + ) + } + } + } + } + + def release(sdk: OtelSdk[IO]) = sdk.flush + + Resource.make(acquire)(release) + } + + final case class MetricsSnapshot( + idle: Long, + inUse: Long, + inUseDuration: List[HistogramSnapshot], + acquiredTotal: Long, + acquireDuration: List[HistogramSnapshot] + ) + + final case class HistogramSnapshot( + sum: Double, + count: Double, + boundaries: List[Double], + counts: List[Long] + ) + + private trait OtelSdk[F[_]] { + def metricReader: InMemoryMetricReader + def otel: Otel4s[F] + def flush: F[Unit] + def snapshot: F[MetricsSnapshot] + } + + private val HistogramBuckets: List[Double] = + List(0.01, 1, 100, 1000) + + private def nothing(ref: Ref[IO, Int]): IO[Unit] = + ref.get.void + +} diff --git a/core/src/main/scala/org/typelevel/keypool/KeyPool.scala b/core/src/main/scala/org/typelevel/keypool/KeyPool.scala index 261cfefd..3200ce29 100644 --- a/core/src/main/scala/org/typelevel/keypool/KeyPool.scala +++ b/core/src/main/scala/org/typelevel/keypool/KeyPool.scala @@ -23,9 +23,11 @@ package org.typelevel.keypool import cats._ import cats.effect.kernel._ +import cats.effect.syntax.spawn._ import cats.syntax.all._ import scala.concurrent.duration._ import org.typelevel.keypool.internal._ +import org.typelevel.otel4s.{MeterProvider} /** * This pools internal guarantees are that the max number of values are in the pool at any time, not @@ -64,7 +66,8 @@ object KeyPool { private[keypool] val kpDefaultReuseState: Reusable, private[keypool] val kpMaxPerKey: A => Int, private[keypool] val kpMaxTotal: Int, - private[keypool] val kpVar: Ref[F, PoolMap[A, (B, F[Unit])]] + private[keypool] val kpVar: Ref[F, PoolMap[A, (B, F[Unit])]], + private[keypool] val kpMetrics: Metrics[F] ) extends KeyPool[F, A, B] { def take(k: A): Resource[F, Managed[F, B]] = @@ -133,6 +136,7 @@ object KeyPool { private[keypool] def reap[F[_], A, B]( idleTimeAllowedInPoolNanos: FiniteDuration, kpVar: Ref[F, PoolMap[A, (B, F[Unit])]], + metrics: Metrics[F], onReaperException: Throwable => F[Unit] )(implicit F: Temporal[F]): F[Unit] = { // We are going to do non-referentially tranpsarent things as we may be waiting for our modification to go through @@ -194,7 +198,7 @@ object KeyPool { val (m_, toDestroy) = findStale(now, idleCount, m) ( m_, - toDestroy.traverse_(_._2._2).attempt.flatMap { + toDestroy.traverse_(r => metrics.decIdle >> r._2._2).attempt.flatMap { case Left(t) => onReaperException(t) // .handleErrorWith(t => F.delay(t.printStackTrace())) // CHEATING? case Right(()) => F.unit @@ -214,25 +218,26 @@ object KeyPool { private[keypool] def state[F[_]: Functor, A, B]( kpVar: Ref[F, PoolMap[A, (B, F[Unit])]] ): F[(Int, Map[A, Int])] = - kpVar.get.map(pm => - pm match { - case PoolClosed() => (0, Map.empty) - case PoolOpen(idleCount, m) => - val modified = m.map { case (k, pl) => - pl match { - case One(_, _) => (k, 1) - case Cons(_, length, _, _) => (k, length) - } - }.toMap - (idleCount, modified) - } - ) + kpVar.get.map { + case PoolClosed() => + (0, Map.empty) + + case PoolOpen(idleCount, m) => + val modified = m.map { case (k, pl) => + pl match { + case One(_, _) => (k, 1) + case Cons(_, length, _, _) => (k, length) + } + } + (idleCount, modified) + } private[keypool] def put[F[_]: Temporal, A, B]( kp: KeyPoolConcrete[F, A, B], k: A, r: B, - destroy: F[Unit] + destroy: F[Unit], + isFromPool: Boolean ): F[Unit] = { def addToList[Z]( now: FiniteDuration, @@ -249,22 +254,25 @@ object KeyPool { else (l, Some(x)) } } + + def decIdle = kp.kpMetrics.decIdle.whenA(isFromPool) + def go(now: FiniteDuration, pc: PoolMap[A, (B, F[Unit])]): (PoolMap[A, (B, F[Unit])], F[Unit]) = pc match { - case p @ PoolClosed() => (p, destroy) + case p @ PoolClosed() => (p, decIdle >> destroy) case p @ PoolOpen(idleCount, m) => - if (idleCount > kp.kpMaxTotal) (p, destroy) + if (idleCount > kp.kpMaxTotal) (p, decIdle >> destroy) else m.get(k) match { case None => val cnt_ = idleCount + 1 val m_ = PoolMap.open(cnt_, m + (k -> One((r, destroy), now))) - (m_, Applicative[F].pure(())) + (m_, kp.kpMetrics.incIdle) case Some(l) => val (l_, mx) = addToList(now, kp.kpMaxPerKey(k), (r, destroy), l) val cnt_ = idleCount + mx.fold(1)(_ => 0) val m_ = PoolMap.open(cnt_, m + (k -> l_)) - (m_, mx.fold(Applicative[F].unit)(_ => destroy)) + (m_, mx.fold(kp.kpMetrics.incIdle)(_ => decIdle >> destroy)) } } @@ -293,16 +301,20 @@ object KeyPool { for { optR <- Resource.eval(kp.kpVar.modify(go)) releasedState <- Resource.eval(Ref[F].of[Reusable](kp.kpDefaultReuseState)) - resource <- Resource.make(optR.fold(kp.kpRes(k).allocated)(r => Applicative[F].pure(r))) { - resource => - for { - reusable <- releasedState.get - out <- reusable match { - case Reusable.Reuse => put(kp, k, resource._1, resource._2).attempt.void - case Reusable.DontReuse => resource._2.attempt.void - } - } yield out + resource <- Resource.make( + optR.fold(kp.kpMetrics.recordAcquireDuration(kp.kpRes(k)))(r => Applicative[F].pure(r)) + ) { resource => + for { + reusable <- releasedState.get + out <- reusable match { + case Reusable.Reuse => put(kp, k, resource._1, resource._2, optR.nonEmpty).attempt.void + case Reusable.DontReuse => resource._2.attempt.void + } + } yield out } + _ <- Resource.eval(kp.kpMetrics.acquiredTotal.add(1).whenA(optR.isEmpty)) + _ <- Resource.make(kp.kpMetrics.incInUse)(_ => kp.kpMetrics.decInUse) + _ <- kp.kpMetrics.recordInUseDuration } yield new Managed(resource._1, optR.isDefined, releasedState) } @@ -312,7 +324,8 @@ object KeyPool { val idleTimeAllowedInPool: Duration, val kpMaxPerKey: A => Int, val kpMaxTotal: Int, - val onReaperException: Throwable => F[Unit] + val onReaperException: Throwable => F[Unit], + val meterProvider: MeterProvider[F] ) { private def copy( kpRes: A => Resource[F, B] = this.kpRes, @@ -320,14 +333,16 @@ object KeyPool { idleTimeAllowedInPool: Duration = this.idleTimeAllowedInPool, kpMaxPerKey: A => Int = this.kpMaxPerKey, kpMaxTotal: Int = this.kpMaxTotal, - onReaperException: Throwable => F[Unit] = this.onReaperException + onReaperException: Throwable => F[Unit] = this.onReaperException, + meterProvider: MeterProvider[F] = this.meterProvider ): Builder[F, A, B] = new Builder[F, A, B]( kpRes, kpDefaultReuseState, idleTimeAllowedInPool, kpMaxPerKey, kpMaxTotal, - onReaperException + onReaperException, + meterProvider ) def doOnCreate(f: B => F[Unit]): Builder[F, A, B] = @@ -353,6 +368,9 @@ object KeyPool { def withOnReaperException(f: Throwable => F[Unit]): Builder[F, A, B] = copy(onReaperException = f) + def withMeterProvider(meterProvider: MeterProvider[F]): Builder[F, A, B] = + copy(meterProvider = meterProvider) + def build: Resource[F, KeyPool[F, A, B]] = { def keepRunning[Z](fa: F[Z]): F[Z] = fa.onError { case e => onReaperException(e) }.attempt >> keepRunning(fa) @@ -360,12 +378,11 @@ object KeyPool { kpVar <- Resource.make( Ref[F].of[PoolMap[A, (B, F[Unit])]](PoolMap.open(0, Map.empty[A, PoolList[(B, F[Unit])]])) )(kpVar => KeyPool.destroy(kpVar)) + kpMetrics <- Resource.eval(Metrics.fromMeterProvider(meterProvider)) _ <- idleTimeAllowedInPool match { case fd: FiniteDuration => val nanos = 0.seconds.max(fd) - Resource.make( - Concurrent[F].start(keepRunning(KeyPool.reap(nanos, kpVar, onReaperException))) - )(_.cancel) + keepRunning(KeyPool.reap(nanos, kpVar, kpMetrics, onReaperException)).background.void case _ => Applicative[Resource[F, *]].unit } @@ -374,7 +391,8 @@ object KeyPool { kpDefaultReuseState, kpMaxPerKey, kpMaxTotal, - kpVar + kpVar, + kpMetrics ) } @@ -389,7 +407,8 @@ object KeyPool { Defaults.idleTimeAllowedInPool, Defaults.maxPerKey, Defaults.maxTotal, - Defaults.onReaperException[F] + Defaults.onReaperException[F], + Defaults.meterProvider ) def apply[F[_]: Temporal, A, B]( @@ -406,6 +425,7 @@ object KeyPool { def onReaperException[F[_]: Applicative] = { (t: Throwable) => Function.const(Applicative[F].unit)(t) } + def meterProvider[F[_]: Applicative]: MeterProvider[F] = MeterProvider.noop } } } diff --git a/core/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala b/core/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala index 8f914203..3170da45 100644 --- a/core/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala +++ b/core/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala @@ -21,10 +21,12 @@ package org.typelevel.keypool -import internal.{PoolList, PoolMap} +import internal.{Metrics, PoolList, PoolMap} import cats._ import cats.syntax.all._ import cats.effect.kernel._ +import cats.effect.syntax.spawn._ +import org.typelevel.otel4s.MeterProvider import scala.concurrent.duration._ @deprecated("use KeyPool.Builder", "0.4.7") @@ -83,12 +85,11 @@ final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( kpVar <- Resource.make( Ref[F].of[PoolMap[A, (B, F[Unit])]](PoolMap.open(0, Map.empty[A, PoolList[(B, F[Unit])]])) )(kpVar => KeyPool.destroy(kpVar)) + kpMetrics <- Resource.eval(Metrics.fromMeterProvider(MeterProvider.noop)) _ <- idleTimeAllowedInPool match { case fd: FiniteDuration => val nanos = 0.seconds.max(fd) - Resource.make( - Concurrent[F].start(keepRunning(KeyPool.reap(nanos, kpVar, onReaperException))) - )(_.cancel) + keepRunning(KeyPool.reap(nanos, kpVar, kpMetrics, onReaperException)).background.void case _ => Applicative[Resource[F, *]].unit } @@ -97,7 +98,8 @@ final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( kpDefaultReuseState, kpMaxPerKey, kpMaxTotal, - kpVar + kpVar, + kpMetrics ) } diff --git a/core/src/main/scala/org/typelevel/keypool/Pool.scala b/core/src/main/scala/org/typelevel/keypool/Pool.scala index 3d275ffd..36f857dc 100644 --- a/core/src/main/scala/org/typelevel/keypool/Pool.scala +++ b/core/src/main/scala/org/typelevel/keypool/Pool.scala @@ -24,6 +24,7 @@ package org.typelevel.keypool import cats._ import cats.effect.kernel._ import cats.syntax.all._ +import org.typelevel.otel4s.MeterProvider import scala.concurrent.duration._ /** @@ -75,20 +76,23 @@ object Pool { val kpDefaultReuseState: Reusable, val idleTimeAllowedInPool: Duration, val kpMaxTotal: Int, - val onReaperException: Throwable => F[Unit] + val onReaperException: Throwable => F[Unit], + val meterProvider: MeterProvider[F] ) { private def copy( kpRes: Resource[F, B] = this.kpRes, kpDefaultReuseState: Reusable = this.kpDefaultReuseState, idleTimeAllowedInPool: Duration = this.idleTimeAllowedInPool, kpMaxTotal: Int = this.kpMaxTotal, - onReaperException: Throwable => F[Unit] = this.onReaperException + onReaperException: Throwable => F[Unit] = this.onReaperException, + meterProvider: MeterProvider[F] = this.meterProvider ): Builder[F, B] = new Builder[F, B]( kpRes, kpDefaultReuseState, idleTimeAllowedInPool, kpMaxTotal, - onReaperException + onReaperException, + meterProvider ) def doOnCreate(f: B => F[Unit]): Builder[F, B] = @@ -111,6 +115,9 @@ object Pool { def withOnReaperException(f: Throwable => F[Unit]): Builder[F, B] = copy(onReaperException = f) + def withMeterProvider(meterProvider: MeterProvider[F]): Builder[F, B] = + copy(meterProvider = meterProvider) + private def toKeyPoolBuilder: KeyPool.Builder[F, Unit, B] = new KeyPool.Builder( kpRes = _ => kpRes, @@ -118,7 +125,8 @@ object Pool { idleTimeAllowedInPool = idleTimeAllowedInPool, kpMaxPerKey = _ => kpMaxTotal, kpMaxTotal = kpMaxTotal, - onReaperException = onReaperException + onReaperException = onReaperException, + meterProvider = meterProvider ) def build: Resource[F, Pool[F, B]] = { @@ -139,7 +147,8 @@ object Pool { Defaults.defaultReuseState, Defaults.idleTimeAllowedInPool, Defaults.maxTotal, - Defaults.onReaperException[F] + Defaults.onReaperException[F], + Defaults.meterProvider ) def apply[F[_]: Temporal, B]( @@ -155,6 +164,7 @@ object Pool { def onReaperException[F[_]: Applicative] = { (t: Throwable) => Function.const(Applicative[F].unit)(t) } + def meterProvider[F[_]: Applicative]: MeterProvider[F] = MeterProvider.noop } } } diff --git a/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala b/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala new file mode 100644 index 00000000..43ed53bd --- /dev/null +++ b/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala @@ -0,0 +1,120 @@ +package org.typelevel.keypool.internal + +import cats.Monad +import cats.effect.{Resource, Temporal} +import cats.syntax.functor._ +import cats.syntax.flatMap._ +import org.typelevel.otel4s._ + +private[keypool] trait Metrics[F[_]] { + + /** + * The '''current''' number of the '''idle''' resources + */ + def idle: UpDownCounter[F, Long] + + /** + * The '''current''' number of the '''borrowed (in use)''' resources + */ + def inUse: UpDownCounter[F, Long] + + def inUseDuration: Histogram[F, Double] + + /** + * The '''total''' number of the '''acquired''' resources. + * + * The value can be only incremented. + */ + def acquiredTotal: Counter[F, Long] + + def acquireDuration: Histogram[F, Double] + + private[keypool] final def incIdle: F[Unit] = idle.add(1L) + private[keypool] final def decIdle: F[Unit] = idle.add(-1L) + private[keypool] final def incInUse: F[Unit] = inUse.add(1L) + private[keypool] final def decInUse: F[Unit] = inUse.add(-1L) + + private[keypool] final def recordInUseDuration(implicit F: Temporal[F]): Resource[F, Unit] = + Metrics.recordInUseDuration(this) + + private[keypool] final def recordAcquireDuration[A]( + resource: Resource[F, A] + )(implicit F: Temporal[F]): F[(A, F[Unit])] = + Metrics.recordAcquireDuration(this, resource) + +} + +private[keypool] object Metrics { + + private val CauseKey: AttributeKey[String] = AttributeKey.string("cause") + + def fromMeterProvider[F[_]: Monad](meterProvider: MeterProvider[F]): F[Metrics[F]] = { + for { + meter <- meterProvider.get("org.typelevel.keypool") + + idleCounter <- meter + .upDownCounter("idle") + .withDescription("A current number of idle resources") + .create + + inUseCounter <- meter + .upDownCounter("in_use") + .withDescription("A current number of resources in use") + .create + + inUseDurationHistogram <- meter + .histogram("in_use_duration") + .withUnit("ms") + .withDescription("For how long a resource is in use") + .create + + acquiredCounter <- meter + .counter("acquired_total") + .withDescription("A total number of acquired resources") + .create + + acquireDurationHistogram <- meter + .histogram("acquire_duration") + .withUnit("ms") + .withDescription("How long does it take to acquire a resource") + .create + + } yield new Metrics[F] { + def idle: UpDownCounter[F, Long] = idleCounter + def inUse: UpDownCounter[F, Long] = inUseCounter + def inUseDuration: Histogram[F, Double] = inUseDurationHistogram + def acquiredTotal: Counter[F, Long] = acquiredCounter + def acquireDuration: Histogram[F, Double] = acquireDurationHistogram + } + } + + private def recordInUseDuration[F[_]]( + metrics: Metrics[F] + )(implicit F: Temporal[F]): Resource[F, Unit] = + Resource + .makeCase(Temporal[F].monotonic) { case (start, ec) => + for { + end <- Temporal[F].monotonic + _ <- metrics.inUseDuration.record((end - start).toMillis, causeAttributes(ec): _*) + } yield () + } + .void + + private def recordAcquireDuration[F[_], A]( + metrics: Metrics[F], + resource: Resource[F, A] + )(implicit F: Temporal[F]): F[(A, F[Unit])] = + Temporal[F] + .timed(resource.allocated) + .flatMap { case (acquireTime, r) => + metrics.acquireDuration.record(acquireTime.toMillis).as(r) + } + + private def causeAttributes(ec: Resource.ExitCase): List[Attribute[String]] = + ec match { + case Resource.ExitCase.Succeeded => Nil + case Resource.ExitCase.Errored(e) => List(Attribute(CauseKey, e.getClass.getName)) + case Resource.ExitCase.Canceled => List(Attribute(CauseKey, "canceled")) + } + +} From 0a99a7b114cf91e8fe578179769daa005465c05a Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Mon, 13 Jun 2022 10:32:03 +0300 Subject: [PATCH 02/19] Update otel4s --- build.sbt | 4 +- .../typelevel/keypool/PoolMetricsSpec.scala | 67 +++++++++++-------- .../scala/org/typelevel/keypool/KeyPool.scala | 26 ++++--- .../typelevel/keypool/internal/Metrics.scala | 45 ------------- 4 files changed, 56 insertions(+), 86 deletions(-) diff --git a/build.sbt b/build.sbt index 49e1a3fa..741c048c 100644 --- a/build.sbt +++ b/build.sbt @@ -13,7 +13,7 @@ ThisBuild / tlSiteApiUrl := Some(url("https://www.javadoc.io/doc/org.typelevel/k lazy val root = tlCrossRootProject.aggregate(core) lazy val core = crossProject(JVMPlatform, JSPlatform) - .crossType(CrossType.Full) + .crossType(CrossType.Pure) .in(file("core")) .settings(commonSettings) .settings( @@ -59,7 +59,7 @@ lazy val docs = project val catsV = "2.7.0" val catsEffectV = "3.3.12" -val otel4sV = "0.0-d3796fb-20220613T071000Z-SNAPSHOT" +val otel4sV = "0.0-7f89139-20220613T071957Z-SNAPSHOT" val munitV = "0.7.29" val munitCatsEffectV = "1.0.7" diff --git a/core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index ce09818b..1e785f4f 100644 --- a/core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -21,6 +21,7 @@ import scala.jdk.CollectionConverters._ import scala.util.control.NoStackTrace class PoolMetricsSpec extends CatsEffectSuite { + import PoolMetricsSpec._ test("Metrics should be empty for unused pool") { val expectedSnapshot = MetricsSnapshot(0, 0, Nil, 0, Nil) @@ -61,8 +62,7 @@ class PoolMetricsSpec extends CatsEffectSuite { } test("Idle: keep 0 when `maxIdle` is 0") { - // todo replace with `withMaxIdle` once https://github.com/typelevel/keypool/pull/394 gets merged - poolTest(_.withMaxTotal(-1)) { (sdk, pool) => + poolTest(_.withMaxIdle(0)) { (sdk, pool) => for { inUse <- pool.take.use(_ => sdk.snapshot) afterUse <- sdk.snapshot @@ -74,8 +74,7 @@ class PoolMetricsSpec extends CatsEffectSuite { } test("Idle: keep 1 when `maxIdle` is 1") { - // todo replace with `withMaxIdle` once https://github.com/typelevel/keypool/pull/394 gets merged - poolTest(_.withMaxTotal(1)) { (sdk, pool) => + poolTest(_.withMaxIdle(1)) { (sdk, pool) => for { inUse <- pool.take.use(_ => sdk.snapshot) afterUse <- sdk.snapshot @@ -87,8 +86,7 @@ class PoolMetricsSpec extends CatsEffectSuite { } test("Idle: decrement on reaper cleanup") { - // todo replace with `withMaxIdle` once https://github.com/typelevel/keypool/pull/394 gets merged - poolTest(_.withMaxTotal(1).withIdleTimeAllowedInPool(1.second)) { (sdk, pool) => + poolTest(_.withMaxIdle(1).withIdleTimeAllowedInPool(1.second)) { (sdk, pool) => for { inUse <- pool.take.use(_ => sdk.snapshot) afterUse <- sdk.snapshot @@ -107,7 +105,7 @@ class PoolMetricsSpec extends CatsEffectSuite { .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) .map { case (inUse, afterUse) => val acquireDuration = - List(HistogramSnapshot(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0))) + List(HistogramSnapshot(0, 1, HistogramBuckets, List(0, 1, 0, 0, 0))) val expectedInUse = MetricsSnapshot( idle = 0, @@ -125,11 +123,8 @@ class PoolMetricsSpec extends CatsEffectSuite { acquireDuration = acquireDuration ) - assertEquals(inUse, expectedInUse) - assertEquals( - afterUse.copy(inUseDuration = afterUse.inUseDuration.map(_.copy(sum = 0))), - expectedAfterUser - ) + assertEquals(inUse.zeroSumHistogram, expectedInUse) + assertEquals(afterUse.zeroSumHistogram, expectedAfterUser) assert(afterUse.inUseDuration.forall(r => r.sum >= 500 && r.sum <= 700)) } } @@ -181,7 +176,10 @@ class PoolMetricsSpec extends CatsEffectSuite { new OtelSdk[IO] { def metricReader: InMemoryMetricReader = mr def otel: Otel4s[IO] = OtelJava.forSync[IO](sdk) - def flush: IO[Unit] = IO.blocking(mp.forceFlush().join(5, TimeUnit.SECONDS)) + def flush: IO[Unit] = IO.blocking { + val _ = mp.forceFlush().join(5, TimeUnit.SECONDS) + () + } def snapshot: IO[MetricsSnapshot] = { IO.delay { @@ -227,32 +225,45 @@ class PoolMetricsSpec extends CatsEffectSuite { Resource.make(acquire)(release) } + private val HistogramBuckets: List[Double] = + List(0.01, 1, 100, 1000) + + private def nothing(ref: Ref[IO, Int]): IO[Unit] = + ref.get.void + +} + +object PoolMetricsSpec { + + trait OtelSdk[F[_]] { + def metricReader: InMemoryMetricReader + def otel: Otel4s[F] + def flush: F[Unit] + def snapshot: F[MetricsSnapshot] + } + final case class MetricsSnapshot( idle: Long, inUse: Long, inUseDuration: List[HistogramSnapshot], acquiredTotal: Long, acquireDuration: List[HistogramSnapshot] - ) + ) { + // use 0 for `histogram#sum` to simplify the comparison + def zeroSumHistogram: MetricsSnapshot = + copy( + inUseDuration = inUseDuration.map(_.zeroSum), + acquireDuration = acquireDuration.map(_.zeroSum) + ) + } final case class HistogramSnapshot( sum: Double, - count: Double, + count: Long, boundaries: List[Double], counts: List[Long] - ) - - private trait OtelSdk[F[_]] { - def metricReader: InMemoryMetricReader - def otel: Otel4s[F] - def flush: F[Unit] - def snapshot: F[MetricsSnapshot] + ) { + def zeroSum: HistogramSnapshot = copy(sum = 0) } - private val HistogramBuckets: List[Double] = - List(0.01, 1, 100, 1000) - - private def nothing(ref: Ref[IO, Int]): IO[Unit] = - ref.get.void - } diff --git a/core/src/main/scala/org/typelevel/keypool/KeyPool.scala b/core/src/main/scala/org/typelevel/keypool/KeyPool.scala index b4d011a6..e903e4e0 100644 --- a/core/src/main/scala/org/typelevel/keypool/KeyPool.scala +++ b/core/src/main/scala/org/typelevel/keypool/KeyPool.scala @@ -21,6 +21,7 @@ package org.typelevel.keypool +import java.util.concurrent.TimeUnit import cats._ import cats.effect.kernel._ import cats.effect.kernel.syntax.spawn._ @@ -202,7 +203,7 @@ object KeyPool { val (m_, toDestroy) = findStale(now, idleCount, m) ( m_, - toDestroy.traverse_(r => metrics.decIdle >> r._2._2).attempt.flatMap { + toDestroy.traverse_(r => metrics.idle.dec() >> r._2._2).attempt.flatMap { case Left(t) => onReaperException(t) // .handleErrorWith(t => F.delay(t.printStackTrace())) // CHEATING? case Right(()) => F.unit @@ -259,24 +260,24 @@ object KeyPool { } } - def decIdle = kp.kpMetrics.decIdle.whenA(isFromPool) + def decIdle = kp.kpMetrics.idle.dec().whenA(isFromPool) def go(now: FiniteDuration, pc: PoolMap[A, (B, F[Unit])]): (PoolMap[A, (B, F[Unit])], F[Unit]) = pc match { case p @ PoolClosed() => (p, decIdle >> destroy) case p @ PoolOpen(idleCount, m) => - if (idleCount > kp.kpMaxIdle) (p, decIdle >> destroy) + if (kp.kpMaxIdle == 0 || idleCount > kp.kpMaxIdle) (p, decIdle >> destroy) else m.get(k) match { case None => val cnt_ = idleCount + 1 val m_ = PoolMap.open(cnt_, m + (k -> One((r, destroy), now))) - (m_, kp.kpMetrics.incIdle) + (m_, kp.kpMetrics.idle.inc()) case Some(l) => val (l_, mx) = addToList(now, kp.kpMaxPerKey(k), (r, destroy), l) val cnt_ = idleCount + mx.fold(1)(_ => 0) val m_ = PoolMap.open(cnt_, m + (k -> l_)) - (m_, mx.fold(kp.kpMetrics.incIdle)(_ => decIdle >> destroy)) + (m_, mx.fold(kp.kpMetrics.idle.inc())(_ => decIdle >> destroy)) } } @@ -302,13 +303,16 @@ object KeyPool { } } + def allocateNew: F[(B, F[Unit])] = + kp.kpMetrics.acquireDuration + .recordDuration(TimeUnit.MILLISECONDS) + .use(_ => kp.kpRes(k).allocated) + for { _ <- kp.kpMaxTotalSem.permit optR <- Resource.eval(kp.kpVar.modify(go)) releasedState <- Resource.eval(Ref[F].of[Reusable](kp.kpDefaultReuseState)) - resource <- Resource.make( - optR.fold(kp.kpMetrics.recordAcquireDuration(kp.kpRes(k)))(r => Applicative[F].pure(r)) - ) { resource => + resource <- Resource.make(optR.fold(allocateNew)(r => Applicative[F].pure(r))) { resource => for { reusable <- releasedState.get out <- reusable match { @@ -317,9 +321,9 @@ object KeyPool { } } yield out } - _ <- Resource.eval(kp.kpMetrics.acquiredTotal.add(1).whenA(optR.isEmpty)) - _ <- Resource.make(kp.kpMetrics.incInUse)(_ => kp.kpMetrics.decInUse) - _ <- kp.kpMetrics.recordInUseDuration + _ <- Resource.eval(kp.kpMetrics.acquiredTotal.inc().whenA(optR.isEmpty)) + _ <- Resource.make(kp.kpMetrics.inUse.inc())(_ => kp.kpMetrics.inUse.dec()) + _ <- kp.kpMetrics.inUseDuration.recordDuration(TimeUnit.MILLISECONDS) } yield new Managed(resource._1, optR.isDefined, releasedState) } diff --git a/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala b/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala index 43ed53bd..b00a6d0f 100644 --- a/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala +++ b/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala @@ -1,7 +1,6 @@ package org.typelevel.keypool.internal import cats.Monad -import cats.effect.{Resource, Temporal} import cats.syntax.functor._ import cats.syntax.flatMap._ import org.typelevel.otel4s._ @@ -29,25 +28,10 @@ private[keypool] trait Metrics[F[_]] { def acquireDuration: Histogram[F, Double] - private[keypool] final def incIdle: F[Unit] = idle.add(1L) - private[keypool] final def decIdle: F[Unit] = idle.add(-1L) - private[keypool] final def incInUse: F[Unit] = inUse.add(1L) - private[keypool] final def decInUse: F[Unit] = inUse.add(-1L) - - private[keypool] final def recordInUseDuration(implicit F: Temporal[F]): Resource[F, Unit] = - Metrics.recordInUseDuration(this) - - private[keypool] final def recordAcquireDuration[A]( - resource: Resource[F, A] - )(implicit F: Temporal[F]): F[(A, F[Unit])] = - Metrics.recordAcquireDuration(this, resource) - } private[keypool] object Metrics { - private val CauseKey: AttributeKey[String] = AttributeKey.string("cause") - def fromMeterProvider[F[_]: Monad](meterProvider: MeterProvider[F]): F[Metrics[F]] = { for { meter <- meterProvider.get("org.typelevel.keypool") @@ -88,33 +72,4 @@ private[keypool] object Metrics { } } - private def recordInUseDuration[F[_]]( - metrics: Metrics[F] - )(implicit F: Temporal[F]): Resource[F, Unit] = - Resource - .makeCase(Temporal[F].monotonic) { case (start, ec) => - for { - end <- Temporal[F].monotonic - _ <- metrics.inUseDuration.record((end - start).toMillis, causeAttributes(ec): _*) - } yield () - } - .void - - private def recordAcquireDuration[F[_], A]( - metrics: Metrics[F], - resource: Resource[F, A] - )(implicit F: Temporal[F]): F[(A, F[Unit])] = - Temporal[F] - .timed(resource.allocated) - .flatMap { case (acquireTime, r) => - metrics.acquireDuration.record(acquireTime.toMillis).as(r) - } - - private def causeAttributes(ec: Resource.ExitCase): List[Attribute[String]] = - ec match { - case Resource.ExitCase.Succeeded => Nil - case Resource.ExitCase.Errored(e) => List(Attribute(CauseKey, e.getClass.getName)) - case Resource.ExitCase.Canceled => List(Attribute(CauseKey, "canceled")) - } - } From 734fedf1f7c717ac531aaf670cd58838e8baa310 Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Mon, 13 Jun 2022 10:53:16 +0300 Subject: [PATCH 03/19] Use `CrossType.Full` --- build.sbt | 2 +- .../src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala | 0 .../src/main/scala/org/typelevel/keypool/KeyPool.scala | 0 .../src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala | 0 .../src/main/scala/org/typelevel/keypool/Managed.scala | 0 .../src/main/scala/org/typelevel/keypool/Pool.scala | 0 .../src/main/scala/org/typelevel/keypool/PoolStats.scala | 0 .../src/main/scala/org/typelevel/keypool/Reusable.scala | 0 .../src/main/scala/org/typelevel/keypool/internal/Metrics.scala | 0 .../main/scala/org/typelevel/keypool/internal/PoolList.scala | 0 .../src/main/scala/org/typelevel/keypool/internal/PoolMap.scala | 0 .../src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala | 2 +- .../src/test/scala/org/typelevel/keypool/PoolSpec.scala | 0 13 files changed, 2 insertions(+), 2 deletions(-) rename core/{.jvm => jvm}/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala (100%) rename core/{ => shared}/src/main/scala/org/typelevel/keypool/KeyPool.scala (100%) rename core/{ => shared}/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala (100%) rename core/{ => shared}/src/main/scala/org/typelevel/keypool/Managed.scala (100%) rename core/{ => shared}/src/main/scala/org/typelevel/keypool/Pool.scala (100%) rename core/{ => shared}/src/main/scala/org/typelevel/keypool/PoolStats.scala (100%) rename core/{ => shared}/src/main/scala/org/typelevel/keypool/Reusable.scala (100%) rename core/{ => shared}/src/main/scala/org/typelevel/keypool/internal/Metrics.scala (100%) rename core/{ => shared}/src/main/scala/org/typelevel/keypool/internal/PoolList.scala (100%) rename core/{ => shared}/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala (100%) rename core/{ => shared}/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala (99%) rename core/{ => shared}/src/test/scala/org/typelevel/keypool/PoolSpec.scala (100%) diff --git a/build.sbt b/build.sbt index 741c048c..425bab03 100644 --- a/build.sbt +++ b/build.sbt @@ -13,7 +13,7 @@ ThisBuild / tlSiteApiUrl := Some(url("https://www.javadoc.io/doc/org.typelevel/k lazy val root = tlCrossRootProject.aggregate(core) lazy val core = crossProject(JVMPlatform, JSPlatform) - .crossType(CrossType.Pure) + .crossType(CrossType.Full) .in(file("core")) .settings(commonSettings) .settings( diff --git a/core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala similarity index 100% rename from core/.jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala rename to core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala diff --git a/core/src/main/scala/org/typelevel/keypool/KeyPool.scala b/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala similarity index 100% rename from core/src/main/scala/org/typelevel/keypool/KeyPool.scala rename to core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala diff --git a/core/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala b/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala similarity index 100% rename from core/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala rename to core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala diff --git a/core/src/main/scala/org/typelevel/keypool/Managed.scala b/core/shared/src/main/scala/org/typelevel/keypool/Managed.scala similarity index 100% rename from core/src/main/scala/org/typelevel/keypool/Managed.scala rename to core/shared/src/main/scala/org/typelevel/keypool/Managed.scala diff --git a/core/src/main/scala/org/typelevel/keypool/Pool.scala b/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala similarity index 100% rename from core/src/main/scala/org/typelevel/keypool/Pool.scala rename to core/shared/src/main/scala/org/typelevel/keypool/Pool.scala diff --git a/core/src/main/scala/org/typelevel/keypool/PoolStats.scala b/core/shared/src/main/scala/org/typelevel/keypool/PoolStats.scala similarity index 100% rename from core/src/main/scala/org/typelevel/keypool/PoolStats.scala rename to core/shared/src/main/scala/org/typelevel/keypool/PoolStats.scala diff --git a/core/src/main/scala/org/typelevel/keypool/Reusable.scala b/core/shared/src/main/scala/org/typelevel/keypool/Reusable.scala similarity index 100% rename from core/src/main/scala/org/typelevel/keypool/Reusable.scala rename to core/shared/src/main/scala/org/typelevel/keypool/Reusable.scala diff --git a/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala b/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala similarity index 100% rename from core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala rename to core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala diff --git a/core/src/main/scala/org/typelevel/keypool/internal/PoolList.scala b/core/shared/src/main/scala/org/typelevel/keypool/internal/PoolList.scala similarity index 100% rename from core/src/main/scala/org/typelevel/keypool/internal/PoolList.scala rename to core/shared/src/main/scala/org/typelevel/keypool/internal/PoolList.scala diff --git a/core/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala b/core/shared/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala similarity index 100% rename from core/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala rename to core/shared/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala diff --git a/core/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala b/core/shared/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala similarity index 99% rename from core/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala rename to core/shared/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala index 8eb60997..d5527133 100644 --- a/core/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala +++ b/core/shared/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala @@ -27,7 +27,7 @@ import cats.effect.std.CountDownLatch import scala.concurrent.duration._ import munit.CatsEffectSuite -class KeypoolSpec extends CatsEffectSuite { +class KeyPoolSpec extends CatsEffectSuite { test("Keep Resources marked to be kept") { KeyPool diff --git a/core/src/test/scala/org/typelevel/keypool/PoolSpec.scala b/core/shared/src/test/scala/org/typelevel/keypool/PoolSpec.scala similarity index 100% rename from core/src/test/scala/org/typelevel/keypool/PoolSpec.scala rename to core/shared/src/test/scala/org/typelevel/keypool/PoolSpec.scala From 4a178e8654937270ddcef813fc82d752f2c3b981 Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Sat, 18 Jun 2022 17:14:31 +0300 Subject: [PATCH 04/19] Use `otel4s-testkit` --- build.sbt | 7 +- .../typelevel/keypool/PoolMetricsSpec.scala | 224 ++++++++---------- 2 files changed, 97 insertions(+), 134 deletions(-) diff --git a/build.sbt b/build.sbt index 425bab03..dd0bd098 100644 --- a/build.sbt +++ b/build.sbt @@ -25,9 +25,8 @@ lazy val core = crossProject(JVMPlatform, JSPlatform) ) .jvmSettings( libraryDependencies ++= Seq( - "org.typelevel" %% "otel4s-java" % otel4sV % Test, - "io.opentelemetry" % "opentelemetry-sdk" % "1.15.0" % Test, - "io.opentelemetry" % "opentelemetry-sdk-testing" % "1.15.0" % Test + "org.typelevel" %% "otel4s-java" % otel4sV % Test, + "org.typelevel" %% "otel4s-testkit" % otel4sV % Test ) ) .settings( @@ -59,7 +58,7 @@ lazy val docs = project val catsV = "2.7.0" val catsEffectV = "3.3.12" -val otel4sV = "0.0-7f89139-20220613T071957Z-SNAPSHOT" +val otel4sV = "0.0-e63fe8a-20220618T140401Z-SNAPSHOT" val munitV = "0.7.29" val munitCatsEffectV = "1.0.7" diff --git a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index 1e785f4f..d0bab851 100644 --- a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -1,21 +1,17 @@ package org.typelevel.keypool -import java.util.concurrent.TimeUnit - -import cats.syntax.all._ import cats.effect._ -import io.opentelemetry.sdk.OpenTelemetrySdk import io.opentelemetry.sdk.metrics.{ Aggregation, InstrumentSelector, InstrumentType, - SdkMeterProvider, + SdkMeterProviderBuilder, View } -import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader import munit.CatsEffectSuite import org.typelevel.otel4s.{MeterProvider, Otel4s} import org.typelevel.otel4s.java.OtelJava +import org.typelevel.otel4s.testkit.{HistogramPointData, Metric, MetricData, Sdk} import scala.concurrent.duration._ import scala.jdk.CollectionConverters._ import scala.util.control.NoStackTrace @@ -24,10 +20,12 @@ class PoolMetricsSpec extends CatsEffectSuite { import PoolMetricsSpec._ test("Metrics should be empty for unused pool") { - val expectedSnapshot = MetricsSnapshot(0, 0, Nil, 0, Nil) + val sdk = createSdk + val expectedSnapshot = + MetricsSnapshot(Nil, Nil, Nil, Nil, Nil) for { - snapshot <- setupSdk.use(sdk => mkPool(sdk.otel.meterProvider).use(_ => sdk.snapshot)) + snapshot <- mkPool(sdk.otel.meterProvider).use(_ => sdk.snapshot) } yield assertEquals(snapshot, expectedSnapshot) } @@ -37,8 +35,8 @@ class PoolMetricsSpec extends CatsEffectSuite { inUse <- pool.take.use(_ => sdk.snapshot) afterUse <- sdk.snapshot } yield { - assertEquals(inUse.inUse, 1L) - assertEquals(afterUse.inUse, 0L) + assertEquals(inUse.inUse, List(1L)) + assertEquals(afterUse.inUse, List(0L)) } } } @@ -55,8 +53,8 @@ class PoolMetricsSpec extends CatsEffectSuite { inUse <- deferred.get afterUse <- sdk.snapshot } yield { - assertEquals(inUse.inUse, 1L) - assertEquals(afterUse.inUse, 0L) + assertEquals(inUse.inUse, List(1L)) + assertEquals(afterUse.inUse, List(0L)) } } } @@ -67,8 +65,8 @@ class PoolMetricsSpec extends CatsEffectSuite { inUse <- pool.take.use(_ => sdk.snapshot) afterUse <- sdk.snapshot } yield { - assertEquals(inUse.idle, 0L) - assertEquals(afterUse.idle, 0L) + assertEquals(inUse.idle, Nil) + assertEquals(afterUse.idle, Nil) } } } @@ -79,8 +77,8 @@ class PoolMetricsSpec extends CatsEffectSuite { inUse <- pool.take.use(_ => sdk.snapshot) afterUse <- sdk.snapshot } yield { - assertEquals(inUse.idle, 0L) - assertEquals(afterUse.idle, 1L) + assertEquals(inUse.idle, Nil) + assertEquals(afterUse.idle, List(1L)) } } } @@ -92,53 +90,54 @@ class PoolMetricsSpec extends CatsEffectSuite { afterUse <- sdk.snapshot afterSleep <- sdk.snapshot.delayBy(6.seconds) } yield { - assertEquals(inUse.idle, 0L) - assertEquals(afterUse.idle, 1L) - assertEquals(afterSleep.idle, 0L) + assertEquals(inUse.idle, Nil) + assertEquals(afterUse.idle, List(1L)) + assertEquals(afterSleep.idle, List(0L)) } } } test("Generate valid metric snapshots") { - setupSdk.use { sdk => - mkPool(sdk.otel.meterProvider) - .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) - .map { case (inUse, afterUse) => - val acquireDuration = - List(HistogramSnapshot(0, 1, HistogramBuckets, List(0, 1, 0, 0, 0))) - - val expectedInUse = MetricsSnapshot( - idle = 0, - inUse = 1, - inUseDuration = Nil, - acquiredTotal = 1, - acquireDuration = acquireDuration - ) + val sdk = createSdk + + mkPool(sdk.otel.meterProvider) + .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) + .map { case (inUse, afterUse) => + val acquireDuration = + List(HistogramPointData(0, 1, HistogramBuckets, List(0, 1, 0, 0, 0))) + + val expectedInUse = MetricsSnapshot( + idle = Nil, + inUse = List(1), + inUseDuration = Nil, + acquiredTotal = List(1), + acquireDuration = acquireDuration + ) - val expectedAfterUser = MetricsSnapshot( - idle = 1, - inUse = 0, - inUseDuration = List(HistogramSnapshot(0, 1, HistogramBuckets, List(0, 0, 0, 1, 0))), - acquiredTotal = 1, - acquireDuration = acquireDuration - ) + val expectedAfterUser = MetricsSnapshot( + idle = List(1), + inUse = List(0), + inUseDuration = List(HistogramPointData(0, 1, HistogramBuckets, List(0, 0, 0, 1, 0))), + acquiredTotal = List(1), + acquireDuration = acquireDuration + ) - assertEquals(inUse.zeroSumHistogram, expectedInUse) - assertEquals(afterUse.zeroSumHistogram, expectedAfterUser) - assert(afterUse.inUseDuration.forall(r => r.sum >= 500 && r.sum <= 700)) - } - } + assertEquals(inUse.zeroSumHistogram, expectedInUse) + assertEquals(afterUse.zeroSumHistogram, expectedAfterUser) + assert(afterUse.inUseDuration.forall(r => r.sum >= 500 && r.sum <= 700)) + } } private def poolTest( customize: Pool.Builder[IO, Ref[IO, Int]] => Pool.Builder[IO, Ref[IO, Int]] = identity - )(scenario: (OtelSdk[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = - setupSdk.use { sdk => - val builder = - Pool.Builder(Ref.of[IO, Int](1), nothing).withMeterProvider(sdk.otel.meterProvider) + )(scenario: (OtelSdk[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = { + val sdk = createSdk - customize(builder).build.use(pool => scenario(sdk, pool)) - } + val builder = + Pool.Builder(Ref.of[IO, Int](1), nothing).withMeterProvider(sdk.otel.meterProvider) + + customize(builder).build.use(pool => scenario(sdk, pool)) + } private def mkPool(meterProvider: MeterProvider[IO]) = Pool @@ -150,13 +149,9 @@ class PoolMetricsSpec extends CatsEffectSuite { .withMaxTotal(10) .build - private def setupSdk: Resource[IO, OtelSdk[IO]] = { - val acquire = IO.delay { - val mr = InMemoryMetricReader.create() - - val mp = SdkMeterProvider - .builder() - .registerMetricReader(mr) + private def createSdk: OtelSdk[IO] = { + def customize(builder: SdkMeterProviderBuilder) = + builder .registerView( InstrumentSelector.builder().setType(InstrumentType.HISTOGRAM).build(), View @@ -166,63 +161,43 @@ class PoolMetricsSpec extends CatsEffectSuite { ) .build() ) - .build() - - val sdk = OpenTelemetrySdk - .builder() - .setMeterProvider(mp) - .build() - - new OtelSdk[IO] { - def metricReader: InMemoryMetricReader = mr - def otel: Otel4s[IO] = OtelJava.forSync[IO](sdk) - def flush: IO[Unit] = IO.blocking { - val _ = mp.forceFlush().join(5, TimeUnit.SECONDS) - () - } - def snapshot: IO[MetricsSnapshot] = { - IO.delay { - val items = metricReader.collectAllMetrics().asScala.toList - - def counterValue(name: String): Long = - items - .find(_.getName === name) - .flatMap(_.getLongSumData.getPoints.asScala.headOption.map(_.getValue)) - .getOrElse(0L) - - def histogramSnapshot(name: String): List[HistogramSnapshot] = - items - .find(_.getName === name) - .map { metric => - val points = metric.getHistogramData.getPoints.asScala.toList - - points.map { hdp => - HistogramSnapshot( - hdp.getSum, - hdp.getCount, - hdp.getBoundaries.asScala.toList.map(Double.unbox), - hdp.getCounts.asScala.toList.map(Long.unbox) - ) - } - } - .getOrElse(Nil) - - MetricsSnapshot( - counterValue("idle"), - counterValue("in_use"), - histogramSnapshot("in_use_duration"), - counterValue("acquired_total"), - histogramSnapshot("acquire_duration") - ) - } + val sdk = Sdk.create[IO](customize) + + new OtelSdk[IO] { + val otel: Otel4s[IO] = OtelJava.forSync[IO](sdk.sdk) + + def snapshot: IO[MetricsSnapshot] = + for { + metrics <- sdk.metrics + } yield { + def counterValue(name: String): List[Long] = + metrics + .collectFirst { + case Metric(metricName, _, _, _, _, MetricData.LongSum(points)) + if metricName == name => + points.map(_.value) + } + .getOrElse(Nil) + + def histogramSnapshot(name: String): List[HistogramPointData] = + metrics + .collectFirst { + case Metric(metricName, _, _, _, _, h: MetricData.Histogram) + if metricName == name => + h.points.map(_.value) + } + .getOrElse(Nil) + + MetricsSnapshot( + counterValue("idle"), + counterValue("in_use"), + histogramSnapshot("in_use_duration"), + counterValue("acquired_total"), + histogramSnapshot("acquire_duration") + ) } - } } - - def release(sdk: OtelSdk[IO]) = sdk.flush - - Resource.make(acquire)(release) } private val HistogramBuckets: List[Double] = @@ -236,34 +211,23 @@ class PoolMetricsSpec extends CatsEffectSuite { object PoolMetricsSpec { trait OtelSdk[F[_]] { - def metricReader: InMemoryMetricReader def otel: Otel4s[F] - def flush: F[Unit] def snapshot: F[MetricsSnapshot] } final case class MetricsSnapshot( - idle: Long, - inUse: Long, - inUseDuration: List[HistogramSnapshot], - acquiredTotal: Long, - acquireDuration: List[HistogramSnapshot] + idle: List[Long], + inUse: List[Long], + inUseDuration: List[HistogramPointData], + acquiredTotal: List[Long], + acquireDuration: List[HistogramPointData] ) { // use 0 for `histogram#sum` to simplify the comparison def zeroSumHistogram: MetricsSnapshot = copy( - inUseDuration = inUseDuration.map(_.zeroSum), - acquireDuration = acquireDuration.map(_.zeroSum) + inUseDuration = inUseDuration.map(_.copy(sum = 0)), + acquireDuration = acquireDuration.map(_.copy(sum = 0)) ) } - final case class HistogramSnapshot( - sum: Double, - count: Long, - boundaries: List[Double], - counts: List[Long] - ) { - def zeroSum: HistogramSnapshot = copy(sum = 0) - } - } From a3b253d49fa46bca95a6a4227668dac8660015b5 Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Sun, 3 Jul 2022 10:38:57 +0300 Subject: [PATCH 05/19] Update `otel4s-testkit` --- build.sbt | 2 +- .../typelevel/keypool/PoolMetricsSpec.scala | 86 ++++++++++--------- .../scala/org/typelevel/keypool/KeyPool.scala | 2 +- .../typelevel/keypool/KeyPoolBuilder.scala | 2 +- .../scala/org/typelevel/keypool/Pool.scala | 2 +- .../typelevel/keypool/internal/Metrics.scala | 2 +- 6 files changed, 49 insertions(+), 47 deletions(-) diff --git a/build.sbt b/build.sbt index eadec3ed..4838427b 100644 --- a/build.sbt +++ b/build.sbt @@ -58,7 +58,7 @@ lazy val docs = project val catsV = "2.7.0" val catsEffectV = "3.3.13" -val otel4sV = "0.0-e63fe8a-20220618T140401Z-SNAPSHOT" +val otel4sV = "0.0-53d0a08-20220703T071423Z-SNAPSHOT" val munitV = "0.7.29" val munitCatsEffectV = "1.0.7" diff --git a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index d0bab851..46cf3593 100644 --- a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -1,6 +1,7 @@ package org.typelevel.keypool import cats.effect._ +import cats.effect.testkit._ import io.opentelemetry.sdk.metrics.{ Aggregation, InstrumentSelector, @@ -9,8 +10,9 @@ import io.opentelemetry.sdk.metrics.{ View } import munit.CatsEffectSuite -import org.typelevel.otel4s.{MeterProvider, Otel4s} +import org.typelevel.otel4s.Otel4s import org.typelevel.otel4s.java.OtelJava +import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.testkit.{HistogramPointData, Metric, MetricData, Sdk} import scala.concurrent.duration._ import scala.jdk.CollectionConverters._ @@ -84,15 +86,17 @@ class PoolMetricsSpec extends CatsEffectSuite { } test("Idle: decrement on reaper cleanup") { - poolTest(_.withMaxIdle(1).withIdleTimeAllowedInPool(1.second)) { (sdk, pool) => - for { - inUse <- pool.take.use(_ => sdk.snapshot) - afterUse <- sdk.snapshot - afterSleep <- sdk.snapshot.delayBy(6.seconds) - } yield { - assertEquals(inUse.idle, Nil) - assertEquals(afterUse.idle, List(1L)) - assertEquals(afterSleep.idle, List(0L)) + TestControl.executeEmbed { + poolTest(_.withMaxIdle(1).withIdleTimeAllowedInPool(1.second)) { (sdk, pool) => + for { + inUse <- pool.take.use(_ => sdk.snapshot) + afterUse <- sdk.snapshot + afterSleep <- sdk.snapshot.delayBy(6.seconds) + } yield { + assertEquals(inUse.idle, Nil) + assertEquals(afterUse.idle, List(1L)) + assertEquals(afterSleep.idle, List(0L)) + } } } } @@ -100,32 +104,37 @@ class PoolMetricsSpec extends CatsEffectSuite { test("Generate valid metric snapshots") { val sdk = createSdk - mkPool(sdk.otel.meterProvider) - .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) - .map { case (inUse, afterUse) => - val acquireDuration = - List(HistogramPointData(0, 1, HistogramBuckets, List(0, 1, 0, 0, 0))) - - val expectedInUse = MetricsSnapshot( - idle = Nil, - inUse = List(1), - inUseDuration = Nil, - acquiredTotal = List(1), - acquireDuration = acquireDuration - ) + TestControl.executeEmbed { + mkPool(sdk.otel.meterProvider) + .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) + .map { case (inUse, afterUse) => + val acquireDuration = + List( + HistogramPointData(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0)) + ) - val expectedAfterUser = MetricsSnapshot( - idle = List(1), - inUse = List(0), - inUseDuration = List(HistogramPointData(0, 1, HistogramBuckets, List(0, 0, 0, 1, 0))), - acquiredTotal = List(1), - acquireDuration = acquireDuration - ) + val expectedInUse = MetricsSnapshot( + idle = Nil, + inUse = List(1), + inUseDuration = Nil, + acquiredTotal = List(1), + acquireDuration = acquireDuration + ) - assertEquals(inUse.zeroSumHistogram, expectedInUse) - assertEquals(afterUse.zeroSumHistogram, expectedAfterUser) - assert(afterUse.inUseDuration.forall(r => r.sum >= 500 && r.sum <= 700)) - } + val expectedAfterUser = MetricsSnapshot( + idle = List(1), + inUse = List(0), + inUseDuration = List( + HistogramPointData(500.0, 1, HistogramBuckets, List(0, 0, 0, 1, 0)) + ), + acquiredTotal = List(1), + acquireDuration = acquireDuration + ) + + assertEquals(inUse, expectedInUse) + assertEquals(afterUse, expectedAfterUser) + } + } } private def poolTest( @@ -221,13 +230,6 @@ object PoolMetricsSpec { inUseDuration: List[HistogramPointData], acquiredTotal: List[Long], acquireDuration: List[HistogramPointData] - ) { - // use 0 for `histogram#sum` to simplify the comparison - def zeroSumHistogram: MetricsSnapshot = - copy( - inUseDuration = inUseDuration.map(_.copy(sum = 0)), - acquireDuration = acquireDuration.map(_.copy(sum = 0)) - ) - } + ) } diff --git a/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala b/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala index e903e4e0..9e220f5d 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala +++ b/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala @@ -29,7 +29,7 @@ import cats.effect.std.Semaphore import cats.syntax.all._ import scala.concurrent.duration._ import org.typelevel.keypool.internal._ -import org.typelevel.otel4s.MeterProvider +import org.typelevel.otel4s.metrics.MeterProvider /** * This pools internal guarantees are that the max number of values are in the pool at any time, not diff --git a/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala b/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala index ba4e8b80..ebb97c06 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala +++ b/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala @@ -27,7 +27,7 @@ import cats.syntax.all._ import cats.effect.kernel._ import cats.effect.kernel.syntax.spawn._ import cats.effect.std.Semaphore -import org.typelevel.otel4s.MeterProvider +import org.typelevel.otel4s.metrics.MeterProvider import scala.concurrent.duration._ @deprecated("use KeyPool.Builder", "0.4.7") diff --git a/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala b/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala index e2233f46..e33469af 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala +++ b/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala @@ -24,7 +24,7 @@ package org.typelevel.keypool import cats._ import cats.effect.kernel._ import cats.syntax.all._ -import org.typelevel.otel4s.MeterProvider +import org.typelevel.otel4s.metrics.MeterProvider import scala.concurrent.duration._ /** diff --git a/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala b/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala index b00a6d0f..cae523d6 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala +++ b/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala @@ -3,7 +3,7 @@ package org.typelevel.keypool.internal import cats.Monad import cats.syntax.functor._ import cats.syntax.flatMap._ -import org.typelevel.otel4s._ +import org.typelevel.otel4s.metrics._ private[keypool] trait Metrics[F[_]] { From 1e16c635a59302684b99a03a345349bde5f74ae1 Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Sun, 3 Jul 2022 10:41:04 +0300 Subject: [PATCH 06/19] Generate headers, update `ci.yml` --- .github/workflows/ci.yml | 4 ++-- .../typelevel/keypool/PoolMetricsSpec.scala | 21 +++++++++++++++++++ .../typelevel/keypool/internal/Metrics.scala | 21 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa7cfaaa..5923c37d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,11 +90,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p target .js/target site/target core/.js/target core/.jvm/target .jvm/target .native/target project/target + run: mkdir -p target .js/target site/target core/js/target core/jvm/target .jvm/target .native/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar target .js/target site/target core/.js/target core/.jvm/target .jvm/target .native/target project/target + run: tar cf targets.tar target .js/target site/target core/js/target core/jvm/target .jvm/target .native/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') diff --git a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index 46cf3593..3ce17de7 100644 --- a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2019 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + package org.typelevel.keypool import cats.effect._ diff --git a/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala b/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala index cae523d6..22b16141 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala +++ b/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2019 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + package org.typelevel.keypool.internal import cats.Monad From 7f172fbbd5971819b68717c9093a5079dcab59f7 Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Wed, 3 Aug 2022 12:53:37 +0300 Subject: [PATCH 07/19] Use snapshot version of `otel4s` --- build.sbt | 5 +++-- .../typelevel/keypool/PoolMetricsSpec.scala | 22 +++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/build.sbt b/build.sbt index 4838427b..38d3d9ff 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import com.typesafe.tools.mima.core._ val Scala213 = "2.13.8" ThisBuild / tlBaseVersion := "0.4" -ThisBuild / crossScalaVersions := Seq("2.12.16", Scala213, "3.0.2") +ThisBuild / crossScalaVersions := Seq("2.12.16", Scala213, "3.1.2") ThisBuild / tlVersionIntroduced := Map("3" -> "0.4.3") ThisBuild / developers += tlGitHubDev("ChristopherDavenport", "Christopher Davenport") ThisBuild / startYear := Some(2019) @@ -58,7 +58,7 @@ lazy val docs = project val catsV = "2.7.0" val catsEffectV = "3.3.13" -val otel4sV = "0.0-53d0a08-20220703T071423Z-SNAPSHOT" +val otel4sV = "0.0-2daac91-SNAPSHOT" val munitV = "0.7.29" val munitCatsEffectV = "1.0.7" @@ -69,6 +69,7 @@ val betterMonadicForV = "0.3.1" // General Settings lazy val commonSettings = Seq( Test / parallelExecution := false, + resolvers += "Sonatype OSS Snapshots" at "https://s01.oss.sonatype.org/content/repositories/snapshots", libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, "org.typelevel" %%% "cats-effect-std" % catsEffectV, diff --git a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index 3ce17de7..294a1cef 100644 --- a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -34,7 +34,7 @@ import munit.CatsEffectSuite import org.typelevel.otel4s.Otel4s import org.typelevel.otel4s.java.OtelJava import org.typelevel.otel4s.metrics.MeterProvider -import org.typelevel.otel4s.testkit.{HistogramPointData, Metric, MetricData, Sdk} +import org.typelevel.otel4s.testkit.{HistogramPointData, MetricData, Sdk} import scala.concurrent.duration._ import scala.jdk.CollectionConverters._ import scala.util.control.NoStackTrace @@ -131,7 +131,7 @@ class PoolMetricsSpec extends CatsEffectSuite { .map { case (inUse, afterUse) => val acquireDuration = List( - HistogramPointData(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0)) + new HistogramPointData(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0)) ) val expectedInUse = MetricsSnapshot( @@ -146,7 +146,7 @@ class PoolMetricsSpec extends CatsEffectSuite { idle = List(1), inUse = List(0), inUseDuration = List( - HistogramPointData(500.0, 1, HistogramBuckets, List(0, 0, 0, 1, 0)) + new HistogramPointData(500.0, 1, HistogramBuckets, List(0, 0, 0, 1, 0)) ), acquiredTotal = List(1), acquireDuration = acquireDuration @@ -203,19 +203,19 @@ class PoolMetricsSpec extends CatsEffectSuite { } yield { def counterValue(name: String): List[Long] = metrics - .collectFirst { - case Metric(metricName, _, _, _, _, MetricData.LongSum(points)) - if metricName == name => - points.map(_.value) + .find(_.name == name) + .map(_.data) + .collectFirst { case MetricData.LongSum(points) => + points.map(_.value) } .getOrElse(Nil) def histogramSnapshot(name: String): List[HistogramPointData] = metrics - .collectFirst { - case Metric(metricName, _, _, _, _, h: MetricData.Histogram) - if metricName == name => - h.points.map(_.value) + .find(_.name == name) + .map(_.data) + .collectFirst { case MetricData.Histogram(points) => + points.map(_.value) } .getOrElse(Nil) From 016178bd8fa35e9dcec83b0a8ce45df854944d03 Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Wed, 3 Aug 2022 13:02:18 +0300 Subject: [PATCH 08/19] Use `Resolver.sonatypeOssRepos` --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 74691012..689b51bb 100644 --- a/build.sbt +++ b/build.sbt @@ -69,7 +69,7 @@ val betterMonadicForV = "0.3.1" // General Settings lazy val commonSettings = Seq( Test / parallelExecution := false, - resolvers += "Sonatype OSS Snapshots" at "https://s01.oss.sonatype.org/content/repositories/snapshots", + resolvers ++= Resolver.sonatypeOssRepos("snapshots"), libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, "org.typelevel" %%% "cats-effect-std" % catsEffectV, From 831588c858fcf7c6d729390f4e44c201f10d631a Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Wed, 25 Jan 2023 09:37:25 +0200 Subject: [PATCH 09/19] Use `otel4s` snapshot --- .github/workflows/ci.yml | 4 +- .../typelevel/keypool/PoolMetricsSpec.scala | 144 +++++++++--------- 2 files changed, 75 insertions(+), 73 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31e2f4e8..0f269705 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,11 +94,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p target .js/target core/.native/target site/target core/.js/target core/.jvm/target .jvm/target .native/target project/target + run: mkdir -p target .js/target core/native/target site/target core/js/target core/jvm/target .jvm/target .native/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar target .js/target core/.native/target site/target core/.js/target core/.jvm/target .jvm/target .native/target project/target + run: tar cf targets.tar target .js/target core/native/target site/target core/js/target core/jvm/target .jvm/target .native/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') diff --git a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index 294a1cef..d1ec1452 100644 --- a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -34,7 +34,7 @@ import munit.CatsEffectSuite import org.typelevel.otel4s.Otel4s import org.typelevel.otel4s.java.OtelJava import org.typelevel.otel4s.metrics.MeterProvider -import org.typelevel.otel4s.testkit.{HistogramPointData, MetricData, Sdk} +import org.typelevel.otel4s.testkit.metrics.{HistogramPointData, MetricData, MetricsSdk} import scala.concurrent.duration._ import scala.jdk.CollectionConverters._ import scala.util.control.NoStackTrace @@ -43,11 +43,11 @@ class PoolMetricsSpec extends CatsEffectSuite { import PoolMetricsSpec._ test("Metrics should be empty for unused pool") { - val sdk = createSdk val expectedSnapshot = MetricsSnapshot(Nil, Nil, Nil, Nil, Nil) for { + sdk <- createSdk snapshot <- mkPool(sdk.otel.meterProvider).use(_ => sdk.snapshot) } yield assertEquals(snapshot, expectedSnapshot) } @@ -123,50 +123,50 @@ class PoolMetricsSpec extends CatsEffectSuite { } test("Generate valid metric snapshots") { - val sdk = createSdk - TestControl.executeEmbed { - mkPool(sdk.otel.meterProvider) - .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) - .map { case (inUse, afterUse) => - val acquireDuration = - List( - new HistogramPointData(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0)) + createSdk.flatMap { sdk => + mkPool(sdk.otel.meterProvider) + .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) + .map { case (inUse, afterUse) => + val acquireDuration = + List( + new HistogramPointData(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0)) + ) + + val expectedInUse = MetricsSnapshot( + idle = Nil, + inUse = List(1), + inUseDuration = Nil, + acquiredTotal = List(1), + acquireDuration = acquireDuration ) - val expectedInUse = MetricsSnapshot( - idle = Nil, - inUse = List(1), - inUseDuration = Nil, - acquiredTotal = List(1), - acquireDuration = acquireDuration - ) - - val expectedAfterUser = MetricsSnapshot( - idle = List(1), - inUse = List(0), - inUseDuration = List( - new HistogramPointData(500.0, 1, HistogramBuckets, List(0, 0, 0, 1, 0)) - ), - acquiredTotal = List(1), - acquireDuration = acquireDuration - ) + val expectedAfterUser = MetricsSnapshot( + idle = List(1), + inUse = List(0), + inUseDuration = List( + new HistogramPointData(500.0, 1, HistogramBuckets, List(0, 0, 0, 1, 0)) + ), + acquiredTotal = List(1), + acquireDuration = acquireDuration + ) - assertEquals(inUse, expectedInUse) - assertEquals(afterUse, expectedAfterUser) - } + assertEquals(inUse, expectedInUse) + assertEquals(afterUse, expectedAfterUser) + } + } } } private def poolTest( customize: Pool.Builder[IO, Ref[IO, Int]] => Pool.Builder[IO, Ref[IO, Int]] = identity )(scenario: (OtelSdk[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = { - val sdk = createSdk - - val builder = - Pool.Builder(Ref.of[IO, Int](1), nothing).withMeterProvider(sdk.otel.meterProvider) + createSdk.flatMap { sdk => + val builder = + Pool.Builder(Ref.of[IO, Int](1), nothing).withMeterProvider(sdk.otel.meterProvider) - customize(builder).build.use(pool => scenario(sdk, pool)) + customize(builder).build.use(pool => scenario(sdk, pool)) + } } private def mkPool(meterProvider: MeterProvider[IO]) = @@ -179,7 +179,7 @@ class PoolMetricsSpec extends CatsEffectSuite { .withMaxTotal(10) .build - private def createSdk: OtelSdk[IO] = { + private def createSdk: IO[OtelSdk[IO]] = { def customize(builder: SdkMeterProviderBuilder) = builder .registerView( @@ -192,41 +192,43 @@ class PoolMetricsSpec extends CatsEffectSuite { .build() ) - val sdk = Sdk.create[IO](customize) - - new OtelSdk[IO] { - val otel: Otel4s[IO] = OtelJava.forSync[IO](sdk.sdk) - - def snapshot: IO[MetricsSnapshot] = - for { - metrics <- sdk.metrics - } yield { - def counterValue(name: String): List[Long] = - metrics - .find(_.name == name) - .map(_.data) - .collectFirst { case MetricData.LongSum(points) => - points.map(_.value) - } - .getOrElse(Nil) - - def histogramSnapshot(name: String): List[HistogramPointData] = - metrics - .find(_.name == name) - .map(_.data) - .collectFirst { case MetricData.Histogram(points) => - points.map(_.value) - } - .getOrElse(Nil) - - MetricsSnapshot( - counterValue("idle"), - counterValue("in_use"), - histogramSnapshot("in_use_duration"), - counterValue("acquired_total"), - histogramSnapshot("acquire_duration") - ) - } + val sdk = MetricsSdk.create[IO](customize) + + OtelJava.forSync[IO](sdk.sdk).map { otel4s => + new OtelSdk[IO] { + val otel: Otel4s[IO] = otel4s + + def snapshot: IO[MetricsSnapshot] = + for { + metrics <- sdk.metrics + } yield { + def counterValue(name: String): List[Long] = + metrics + .find(_.name == name) + .map(_.data) + .collectFirst { case MetricData.LongSum(points) => + points.map(_.value) + } + .getOrElse(Nil) + + def histogramSnapshot(name: String): List[HistogramPointData] = + metrics + .find(_.name == name) + .map(_.data) + .collectFirst { case MetricData.Histogram(points) => + points.map(_.value) + } + .getOrElse(Nil) + + MetricsSnapshot( + counterValue("idle"), + counterValue("in_use"), + histogramSnapshot("in_use_duration"), + counterValue("acquired_total"), + histogramSnapshot("acquire_duration") + ) + } + } } } From 369482eb8b7078ce6b2af40aa9eac1de69854e2d Mon Sep 17 00:00:00 2001 From: Maksim Ochenashko Date: Wed, 8 Feb 2023 15:51:51 +0200 Subject: [PATCH 10/19] Use `0.1.0` of `otel4s` --- build.sbt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 73511815..195087c1 100644 --- a/build.sbt +++ b/build.sbt @@ -62,7 +62,7 @@ lazy val docs = project val catsV = "2.9.0" val catsEffectV = "3.4.6" -val otel4sV = "0.0-9193d5a-SNAPSHOT" +val otel4sV = "0.1.0" val munitV = "1.0.0-M7" val munitCatsEffectV = "2.0.0-M3" @@ -73,7 +73,6 @@ val betterMonadicForV = "0.3.1" // General Settings lazy val commonSettings = Seq( Test / parallelExecution := false, - resolvers ++= Resolver.sonatypeOssRepos("snapshots"), libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, "org.typelevel" %%% "cats-effect-std" % catsEffectV, From 8792c343c278b0cb653a9bc369875ea4354b41c4 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sat, 30 Mar 2024 19:09:38 +0200 Subject: [PATCH 11/19] Update otel4s to `0.5.0` --- build.sbt | 1 - .../typelevel/keypool/PoolMetricsSpec.scala | 63 +++++++------- .../scala/org/typelevel/keypool/KeyPool.scala | 31 ++++--- .../typelevel/keypool/KeyPoolBuilder.scala | 7 +- .../scala/org/typelevel/keypool/Pool.scala | 9 ++ .../typelevel/keypool/internal/Metrics.scala | 84 ++++++++++++------- 6 files changed, 117 insertions(+), 78 deletions(-) diff --git a/build.sbt b/build.sbt index 0ed5f4b6..210e5a91 100644 --- a/build.sbt +++ b/build.sbt @@ -26,7 +26,6 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) .jvmSettings( libraryDependencies ++= Seq( - "org.typelevel" %% "otel4s-oteljava" % otel4sV % Test, "org.typelevel" %% "otel4s-oteljava-testkit" % otel4sV % Test ) ) diff --git a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index d1ec1452..8cc3cafd 100644 --- a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -23,18 +23,12 @@ package org.typelevel.keypool import cats.effect._ import cats.effect.testkit._ -import io.opentelemetry.sdk.metrics.{ - Aggregation, - InstrumentSelector, - InstrumentType, - SdkMeterProviderBuilder, - View -} +import io.opentelemetry.sdk.metrics.{Aggregation, InstrumentSelector, InstrumentType, SdkMeterProviderBuilder, View} import munit.CatsEffectSuite -import org.typelevel.otel4s.Otel4s -import org.typelevel.otel4s.java.OtelJava import org.typelevel.otel4s.metrics.MeterProvider -import org.typelevel.otel4s.testkit.metrics.{HistogramPointData, MetricData, MetricsSdk} +import org.typelevel.otel4s.oteljava.testkit.metrics.MetricsTestkit +import org.typelevel.otel4s.oteljava.testkit.metrics.data.{HistogramPointData, Metric, MetricData} + import scala.concurrent.duration._ import scala.jdk.CollectionConverters._ import scala.util.control.NoStackTrace @@ -46,10 +40,11 @@ class PoolMetricsSpec extends CatsEffectSuite { val expectedSnapshot = MetricsSnapshot(Nil, Nil, Nil, Nil, Nil) - for { - sdk <- createSdk - snapshot <- mkPool(sdk.otel.meterProvider).use(_ => sdk.snapshot) - } yield assertEquals(snapshot, expectedSnapshot) + createTestkit.use { testkit => + for { + snapshot <- mkPool(testkit.metrics.meterProvider).use(_ => testkit.snapshot) + } yield assertEquals(snapshot, expectedSnapshot) + } } test("In use: increment on acquire and decrement on release") { @@ -124,13 +119,13 @@ class PoolMetricsSpec extends CatsEffectSuite { test("Generate valid metric snapshots") { TestControl.executeEmbed { - createSdk.flatMap { sdk => - mkPool(sdk.otel.meterProvider) + createTestkit.use { sdk => + mkPool(sdk.metrics.meterProvider) .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) .map { case (inUse, afterUse) => val acquireDuration = List( - new HistogramPointData(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0)) + HistogramPointData(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0)) ) val expectedInUse = MetricsSnapshot( @@ -145,7 +140,7 @@ class PoolMetricsSpec extends CatsEffectSuite { idle = List(1), inUse = List(0), inUseDuration = List( - new HistogramPointData(500.0, 1, HistogramBuckets, List(0, 0, 0, 1, 0)) + HistogramPointData(500.0, 1, HistogramBuckets, List(0, 0, 0, 1, 0)) ), acquiredTotal = List(1), acquireDuration = acquireDuration @@ -160,10 +155,10 @@ class PoolMetricsSpec extends CatsEffectSuite { private def poolTest( customize: Pool.Builder[IO, Ref[IO, Int]] => Pool.Builder[IO, Ref[IO, Int]] = identity - )(scenario: (OtelSdk[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = { - createSdk.flatMap { sdk => + )(scenario: (OtelTestkit[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = { + createTestkit.use { sdk => val builder = - Pool.Builder(Ref.of[IO, Int](1), nothing).withMeterProvider(sdk.otel.meterProvider) + Pool.Builder(Ref.of[IO, Int](1), nothing).withMeterProvider(sdk.metrics.meterProvider) customize(builder).build.use(pool => scenario(sdk, pool)) } @@ -179,7 +174,7 @@ class PoolMetricsSpec extends CatsEffectSuite { .withMaxTotal(10) .build - private def createSdk: IO[OtelSdk[IO]] = { + private def createTestkit: Resource[IO, OtelTestkit[IO]] = { def customize(builder: SdkMeterProviderBuilder) = builder .registerView( @@ -192,15 +187,13 @@ class PoolMetricsSpec extends CatsEffectSuite { .build() ) - val sdk = MetricsSdk.create[IO](customize) - - OtelJava.forSync[IO](sdk.sdk).map { otel4s => - new OtelSdk[IO] { - val otel: Otel4s[IO] = otel4s + MetricsTestkit.inMemory[IO](customize).map { testkit => + new OtelTestkit[IO] { + val metrics: MetricsTestkit[IO] = testkit def snapshot: IO[MetricsSnapshot] = for { - metrics <- sdk.metrics + metrics <- testkit.collectMetrics[Metric] } yield { def counterValue(name: String): List[Long] = metrics @@ -221,11 +214,11 @@ class PoolMetricsSpec extends CatsEffectSuite { .getOrElse(Nil) MetricsSnapshot( - counterValue("idle"), - counterValue("in_use"), - histogramSnapshot("in_use_duration"), - counterValue("acquired_total"), - histogramSnapshot("acquire_duration") + counterValue("idle.current"), + counterValue("in_use.current"), + histogramSnapshot("in_use.duration"), + counterValue("acquired.total"), + histogramSnapshot("acquire.duration") ) } } @@ -242,8 +235,8 @@ class PoolMetricsSpec extends CatsEffectSuite { object PoolMetricsSpec { - trait OtelSdk[F[_]] { - def otel: Otel4s[F] + trait OtelTestkit[F[_]] { + def metrics: MetricsTestkit[F] def snapshot: F[MetricsSnapshot] } diff --git a/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala b/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala index 13f93658..9e5906a0 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala +++ b/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala @@ -21,7 +21,6 @@ package org.typelevel.keypool -import java.util.concurrent.TimeUnit import cats._ import cats.effect.kernel._ import cats.effect.kernel.syntax.spawn._ @@ -203,7 +202,7 @@ object KeyPool { val (m_, toDestroy) = findStale(now, idleCount, m) ( m_, - toDestroy.traverse_(r => metrics.idle.dec() >> r._2._2).attempt.flatMap { + toDestroy.traverse_(r => metrics.idleDec >> r._2._2).attempt.flatMap { case Left(t) => onReaperException(t) // .handleErrorWith(t => F.delay(t.printStackTrace())) // CHEATING? case Right(()) => F.unit @@ -260,7 +259,7 @@ object KeyPool { } } - def decIdle = kp.kpMetrics.idle.dec().whenA(isFromPool) + def decIdle = kp.kpMetrics.idleDec.whenA(isFromPool) def go(now: FiniteDuration, pc: PoolMap[A, (B, F[Unit])]): (PoolMap[A, (B, F[Unit])], F[Unit]) = pc match { @@ -272,12 +271,12 @@ object KeyPool { case None => val cnt_ = idleCount + 1 val m_ = PoolMap.open(cnt_, m + (k -> One((r, destroy), now))) - (m_, kp.kpMetrics.idle.inc()) + (m_, kp.kpMetrics.idleInc) case Some(l) => val (l_, mx) = addToList(now, kp.kpMaxPerKey(k), (r, destroy), l) val cnt_ = idleCount + mx.fold(1)(_ => 0) val m_ = PoolMap.open(cnt_, m + (k -> l_)) - (m_, mx.fold(kp.kpMetrics.idle.inc())(_ => decIdle >> destroy)) + (m_, mx.fold(kp.kpMetrics.idleInc)(_ => decIdle >> destroy)) } } @@ -304,9 +303,7 @@ object KeyPool { } def allocateNew: F[(B, F[Unit])] = - kp.kpMetrics.acquireDuration - .recordDuration(TimeUnit.MILLISECONDS) - .use(_ => kp.kpRes(k).allocated) + kp.kpMetrics.acquireRecordDuration.use(_ => kp.kpRes(k).allocated) for { _ <- kp.kpMaxTotalSem.permit @@ -323,14 +320,15 @@ object KeyPool { } } yield out } - _ <- Resource.eval(kp.kpMetrics.acquiredTotal.inc().whenA(optR.isEmpty)) - _ <- Resource.make(kp.kpMetrics.inUse.inc())(_ => kp.kpMetrics.inUse.dec()) - _ <- kp.kpMetrics.inUseDuration.recordDuration(TimeUnit.MILLISECONDS) + _ <- Resource.eval(kp.kpMetrics.acquiredTotalInc.whenA(optR.isEmpty)) + _ <- kp.kpMetrics.inUseCount + _ <- kp.kpMetrics.inUseRecordDuration } yield new Managed(resource._1, optR.isDefined, releasedState) } final class Builder[F[_]: Temporal, A, B] private[keypool] ( val kpRes: A => Resource[F, B], + val name: String, val kpDefaultReuseState: Reusable, val idleTimeAllowedInPool: Duration, val kpMaxPerKey: A => Int, @@ -347,9 +345,11 @@ object KeyPool { kpMaxIdle: Int = this.kpMaxIdle, kpMaxTotal: Int = this.kpMaxTotal, onReaperException: Throwable => F[Unit] = this.onReaperException, - meterProvider: MeterProvider[F] = this.meterProvider + meterProvider: MeterProvider[F] = this.meterProvider, + name: String = this.name ): Builder[F, A, B] = new Builder[F, A, B]( kpRes, + name, kpDefaultReuseState, idleTimeAllowedInPool, kpMaxPerKey, @@ -388,6 +388,9 @@ object KeyPool { def withMeterProvider(meterProvider: MeterProvider[F]): Builder[F, A, B] = copy(meterProvider = meterProvider) + def withName(name: String): Builder[F, A, B] = + copy(name = name) + def build: Resource[F, KeyPool[F, A, B]] = { def keepRunning[Z](fa: F[Z]): F[Z] = fa.onError { case e => onReaperException(e) }.attempt >> keepRunning(fa) @@ -396,7 +399,7 @@ object KeyPool { Ref[F].of[PoolMap[A, (B, F[Unit])]](PoolMap.open(0, Map.empty[A, PoolList[(B, F[Unit])]])) )(kpVar => KeyPool.destroy(kpVar)) kpMaxTotalSem <- Resource.eval(Semaphore[F](kpMaxTotal.toLong)) - kpMetrics <- Resource.eval(Metrics.fromMeterProvider(meterProvider)) + kpMetrics <- Resource.eval(Metrics.fromMeterProvider(meterProvider, name)) _ <- idleTimeAllowedInPool match { case fd: FiniteDuration => val nanos = 0.seconds.max(fd) @@ -423,6 +426,7 @@ object KeyPool { res: A => Resource[F, B] ): Builder[F, A, B] = new Builder[F, A, B]( res, + Defaults.name, Defaults.defaultReuseState, Defaults.idleTimeAllowedInPool, Defaults.maxPerKey, @@ -444,6 +448,7 @@ object KeyPool { def maxPerKey[K](k: K): Int = Function.const(100)(k) val maxIdle = 100 val maxTotal = 100 + val name = "unknown" def onReaperException[F[_]: Applicative] = { (t: Throwable) => Function.const(Applicative[F].unit)(t) } diff --git a/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala b/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala index ebb97c06..7856c56e 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala +++ b/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala @@ -32,6 +32,7 @@ import scala.concurrent.duration._ @deprecated("use KeyPool.Builder", "0.4.7") final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( + val name: String, val kpCreate: A => F[B], val kpDestroy: B => F[Unit], val kpDefaultReuseState: Reusable, @@ -42,6 +43,7 @@ final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( val onReaperException: Throwable => F[Unit] ) { private def copy( + name: String = this.name, kpCreate: A => F[B] = this.kpCreate, kpDestroy: B => F[Unit] = this.kpDestroy, kpDefaultReuseState: Reusable = this.kpDefaultReuseState, @@ -51,6 +53,7 @@ final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( kpMaxTotal: Int = this.kpMaxTotal, onReaperException: Throwable => F[Unit] = this.onReaperException ): KeyPoolBuilder[F, A, B] = new KeyPoolBuilder[F, A, B]( + name, kpCreate, kpDestroy, kpDefaultReuseState, @@ -93,7 +96,7 @@ final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( Ref[F].of[PoolMap[A, (B, F[Unit])]](PoolMap.open(0, Map.empty[A, PoolList[(B, F[Unit])]])) )(kpVar => KeyPool.destroy(kpVar)) kpMaxTotalSem <- Resource.eval(Semaphore[F](kpMaxTotal.toLong)) - kpMetrics <- Resource.eval(Metrics.fromMeterProvider(MeterProvider.noop)) + kpMetrics <- Resource.eval(Metrics.fromMeterProvider(MeterProvider.noop, name)) _ <- idleTimeAllowedInPool match { case fd: FiniteDuration => val nanos = 0.seconds.max(fd) @@ -121,6 +124,7 @@ object KeyPoolBuilder { create: A => F[B], destroy: B => F[Unit] ): KeyPoolBuilder[F, A, B] = new KeyPoolBuilder[F, A, B]( + Defaults.name, create, destroy, Defaults.defaultReuseState, @@ -137,6 +141,7 @@ object KeyPoolBuilder { def maxPerKey[K](k: K): Int = Function.const(100)(k) val maxIdle = 100 val maxTotal = 100 + val name = "unknown" def onReaperException[F[_]: Applicative] = { (t: Throwable) => Function.const(Applicative[F].unit)(t) } diff --git a/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala b/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala index e33469af..b960e5d2 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala +++ b/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala @@ -73,6 +73,7 @@ object Pool { final class Builder[F[_]: Temporal, B] private ( val kpRes: Resource[F, B], + val name: String, val kpDefaultReuseState: Reusable, val idleTimeAllowedInPool: Duration, val kpMaxIdle: Int, @@ -82,6 +83,7 @@ object Pool { ) { private def copy( kpRes: Resource[F, B] = this.kpRes, + name: String = this.name, kpDefaultReuseState: Reusable = this.kpDefaultReuseState, idleTimeAllowedInPool: Duration = this.idleTimeAllowedInPool, kpMaxIdle: Int = this.kpMaxIdle, @@ -90,6 +92,7 @@ object Pool { meterProvider: MeterProvider[F] = this.meterProvider ): Builder[F, B] = new Builder[F, B]( kpRes, + name = name, kpDefaultReuseState, idleTimeAllowedInPool, kpMaxIdle, @@ -124,9 +127,13 @@ object Pool { def withMeterProvider(meterProvider: MeterProvider[F]): Builder[F, B] = copy(meterProvider = meterProvider) + def withName(name: String): Builder[F, B] = + copy(name = name) + private def toKeyPoolBuilder: KeyPool.Builder[F, Unit, B] = new KeyPool.Builder( kpRes = _ => kpRes, + name = name, kpDefaultReuseState = kpDefaultReuseState, idleTimeAllowedInPool = idleTimeAllowedInPool, kpMaxPerKey = _ => kpMaxTotal, @@ -151,6 +158,7 @@ object Pool { res: Resource[F, B] ): Builder[F, B] = new Builder[F, B]( res, + Defaults.name, Defaults.defaultReuseState, Defaults.idleTimeAllowedInPool, Defaults.maxIdle, @@ -170,6 +178,7 @@ object Pool { val idleTimeAllowedInPool = 30.seconds val maxIdle = 100 val maxTotal = 100 + val name = "unknown" def onReaperException[F[_]: Applicative] = { (t: Throwable) => Function.const(Applicative[F].unit)(t) } diff --git a/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala b/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala index 22b16141..346df04f 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala +++ b/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala @@ -21,76 +21,104 @@ package org.typelevel.keypool.internal +import java.util.concurrent.TimeUnit + import cats.Monad +import cats.effect.kernel.Resource import cats.syntax.functor._ import cats.syntax.flatMap._ +import org.typelevel.otel4s.Attribute import org.typelevel.otel4s.metrics._ private[keypool] trait Metrics[F[_]] { /** - * The '''current''' number of the '''idle''' resources + * Increments the number of idle resources. + */ + def idleInc: F[Unit] + + /** + * Decrements the number of idle resources. */ - def idle: UpDownCounter[F, Long] + def idleDec: F[Unit] /** - * The '''current''' number of the '''borrowed (in use)''' resources + * Records the number of in-use resources. */ - def inUse: UpDownCounter[F, Long] + def inUseCount: Resource[F, Unit] - def inUseDuration: Histogram[F, Double] + /** + * Records for how long the resource has been in use. + */ + def inUseRecordDuration: Resource[F, Unit] /** - * The '''total''' number of the '''acquired''' resources. - * - * The value can be only incremented. + * Increments the number of acquired resources. */ - def acquiredTotal: Counter[F, Long] + def acquiredTotalInc: F[Unit] - def acquireDuration: Histogram[F, Double] + /** + * Records how long does it take to acquire a resource. + */ + def acquireRecordDuration: Resource[F, Unit] } private[keypool] object Metrics { - def fromMeterProvider[F[_]: Monad](meterProvider: MeterProvider[F]): F[Metrics[F]] = { + def fromMeterProvider[F[_]: Monad](meterProvider: MeterProvider[F], name: String): F[Metrics[F]] = for { meter <- meterProvider.get("org.typelevel.keypool") - idleCounter <- meter - .upDownCounter("idle") + idle <- meter + .upDownCounter[Long]("idle.current") + .withUnit("{resource}") .withDescription("A current number of idle resources") .create - inUseCounter <- meter - .upDownCounter("in_use") + inUse <- meter + .upDownCounter[Long]("in_use.current") + .withUnit("{resource}") .withDescription("A current number of resources in use") .create - inUseDurationHistogram <- meter - .histogram("in_use_duration") + inUseDuration <- meter + .histogram[Long]("in_use.duration") .withUnit("ms") .withDescription("For how long a resource is in use") .create - acquiredCounter <- meter - .counter("acquired_total") + acquiredTotal <- meter + .counter[Long]("acquired.total") + .withUnit("{resource}") .withDescription("A total number of acquired resources") .create - acquireDurationHistogram <- meter - .histogram("acquire_duration") + acquireDuration <- meter + .histogram[Long]("acquire.duration") .withUnit("ms") .withDescription("How long does it take to acquire a resource") .create - } yield new Metrics[F] { - def idle: UpDownCounter[F, Long] = idleCounter - def inUse: UpDownCounter[F, Long] = inUseCounter - def inUseDuration: Histogram[F, Double] = inUseDurationHistogram - def acquiredTotal: Counter[F, Long] = acquiredCounter - def acquireDuration: Histogram[F, Double] = acquireDurationHistogram + private val nameAttribute = Attribute("pool.name", name) + + def idleInc: F[Unit] = + idle.inc(nameAttribute) + + def idleDec: F[Unit] = + idle.dec(nameAttribute) + + def inUseCount: Resource[F, Unit] = + Resource.make(inUse.inc(nameAttribute))(_ => inUse.dec(nameAttribute)) + + def inUseRecordDuration: Resource[F, Unit] = + inUseDuration.recordDuration(TimeUnit.MILLISECONDS, nameAttribute) + + def acquiredTotalInc: F[Unit] = + acquiredTotal.inc(nameAttribute) + + def acquireRecordDuration: Resource[F, Unit] = + acquireDuration.recordDuration(TimeUnit.MILLISECONDS, nameAttribute) } - } } From 8afd3222eca75da2c2c94d808602987fc35b4fe4 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 31 Mar 2024 13:45:41 +0300 Subject: [PATCH 12/19] Move otel4s integration to a separate module --- build.sbt | 27 ++-- .../typelevel/keypool/internal/Metrics.scala | 124 ------------------ .../scala/org/typelevel/keypool/KeyPool.scala | 25 ++-- .../typelevel/keypool/KeyPoolBuilder.scala | 8 +- .../scala/org/typelevel/keypool/Managed.scala | 0 .../scala/org/typelevel/keypool/Pool.scala | 27 ++-- .../org/typelevel/keypool/PoolStats.scala | 0 .../org/typelevel/keypool/Reusable.scala | 0 .../typelevel/keypool/internal/Metrics.scala | 84 ++++++++++++ .../typelevel/keypool/internal/PoolList.scala | 0 .../typelevel/keypool/internal/PoolMap.scala | 0 .../org/typelevel/keypool/KeyPoolSpec.scala | 0 .../org/typelevel/keypool/PoolSpec.scala | 0 .../typelevel/keypool/PoolMetricsSpec.scala | 12 +- .../org/typelevel/keypool/Otel4sMetrics.scala | 96 ++++++++++++++ 15 files changed, 224 insertions(+), 179 deletions(-) delete mode 100644 core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala rename core/{shared => }/src/main/scala/org/typelevel/keypool/KeyPool.scala (95%) rename core/{shared => }/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala (95%) rename core/{shared => }/src/main/scala/org/typelevel/keypool/Managed.scala (100%) rename core/{shared => }/src/main/scala/org/typelevel/keypool/Pool.scala (89%) rename core/{shared => }/src/main/scala/org/typelevel/keypool/PoolStats.scala (100%) rename core/{shared => }/src/main/scala/org/typelevel/keypool/Reusable.scala (100%) create mode 100644 core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala rename core/{shared => }/src/main/scala/org/typelevel/keypool/internal/PoolList.scala (100%) rename core/{shared => }/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala (100%) rename core/{shared => }/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala (100%) rename core/{shared => }/src/test/scala/org/typelevel/keypool/PoolSpec.scala (100%) rename {core => otel4s}/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala (96%) create mode 100644 otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala diff --git a/build.sbt b/build.sbt index 210e5a91..1e4806a8 100644 --- a/build.sbt +++ b/build.sbt @@ -11,10 +11,10 @@ ThisBuild / startYear := Some(2019) ThisBuild / licenses := Seq(License.MIT) ThisBuild / tlSiteApiUrl := Some(url("https://www.javadoc.io/doc/org.typelevel/keypool_2.12")) -lazy val root = tlCrossRootProject.aggregate(core) +lazy val root = tlCrossRootProject.aggregate(core, otel4s) lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) - .crossType(CrossType.Full) + .crossType(CrossType.Pure) .in(file("core")) .settings(commonSettings) .settings( @@ -24,11 +24,6 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) .jsSettings( tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "0.4.6").toMap ) - .jvmSettings( - libraryDependencies ++= Seq( - "org.typelevel" %% "otel4s-oteljava-testkit" % otel4sV % Test - ) - ) .nativeSettings( tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "0.4.8").toMap ) @@ -52,10 +47,25 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) ) +lazy val otel4s = crossProject(JVMPlatform, JSPlatform, NativePlatform) + .crossType(CrossType.Full) + .in(file("otel4s")) + .dependsOn(core) + .settings(commonSettings) + .settings( + name := "keypool-otel4s", + startYear := Some(2024), + crossScalaVersions := Seq(Scala213, Scala3), + libraryDependencies += "org.typelevel" %%% "otel4s-core" % otel4sV + ) + .jvmSettings( + libraryDependencies += "org.typelevel" %% "otel4s-oteljava-testkit" % otel4sV % Test + ) + lazy val docs = project .in(file("site")) .settings(commonSettings) - .dependsOn(core.jvm) + .dependsOn(core.jvm, otel4s.jvm) .enablePlugins(TypelevelSitePlugin) val catsV = "2.10.0" @@ -75,7 +85,6 @@ lazy val commonSettings = Seq( libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, "org.typelevel" %%% "cats-effect-std" % catsEffectV, - "org.typelevel" %%% "otel4s-core" % otel4sV, "org.typelevel" %%% "cats-effect-testkit" % catsEffectV % Test, "org.scalameta" %%% "munit" % munitV % Test, "org.typelevel" %%% "munit-cats-effect" % munitCatsEffectV % Test diff --git a/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala b/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala deleted file mode 100644 index 346df04f..00000000 --- a/core/shared/src/main/scala/org/typelevel/keypool/internal/Metrics.scala +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2019 Typelevel - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package org.typelevel.keypool.internal - -import java.util.concurrent.TimeUnit - -import cats.Monad -import cats.effect.kernel.Resource -import cats.syntax.functor._ -import cats.syntax.flatMap._ -import org.typelevel.otel4s.Attribute -import org.typelevel.otel4s.metrics._ - -private[keypool] trait Metrics[F[_]] { - - /** - * Increments the number of idle resources. - */ - def idleInc: F[Unit] - - /** - * Decrements the number of idle resources. - */ - def idleDec: F[Unit] - - /** - * Records the number of in-use resources. - */ - def inUseCount: Resource[F, Unit] - - /** - * Records for how long the resource has been in use. - */ - def inUseRecordDuration: Resource[F, Unit] - - /** - * Increments the number of acquired resources. - */ - def acquiredTotalInc: F[Unit] - - /** - * Records how long does it take to acquire a resource. - */ - def acquireRecordDuration: Resource[F, Unit] - -} - -private[keypool] object Metrics { - - def fromMeterProvider[F[_]: Monad](meterProvider: MeterProvider[F], name: String): F[Metrics[F]] = - for { - meter <- meterProvider.get("org.typelevel.keypool") - - idle <- meter - .upDownCounter[Long]("idle.current") - .withUnit("{resource}") - .withDescription("A current number of idle resources") - .create - - inUse <- meter - .upDownCounter[Long]("in_use.current") - .withUnit("{resource}") - .withDescription("A current number of resources in use") - .create - - inUseDuration <- meter - .histogram[Long]("in_use.duration") - .withUnit("ms") - .withDescription("For how long a resource is in use") - .create - - acquiredTotal <- meter - .counter[Long]("acquired.total") - .withUnit("{resource}") - .withDescription("A total number of acquired resources") - .create - - acquireDuration <- meter - .histogram[Long]("acquire.duration") - .withUnit("ms") - .withDescription("How long does it take to acquire a resource") - .create - } yield new Metrics[F] { - private val nameAttribute = Attribute("pool.name", name) - - def idleInc: F[Unit] = - idle.inc(nameAttribute) - - def idleDec: F[Unit] = - idle.dec(nameAttribute) - - def inUseCount: Resource[F, Unit] = - Resource.make(inUse.inc(nameAttribute))(_ => inUse.dec(nameAttribute)) - - def inUseRecordDuration: Resource[F, Unit] = - inUseDuration.recordDuration(TimeUnit.MILLISECONDS, nameAttribute) - - def acquiredTotalInc: F[Unit] = - acquiredTotal.inc(nameAttribute) - - def acquireRecordDuration: Resource[F, Unit] = - acquireDuration.recordDuration(TimeUnit.MILLISECONDS, nameAttribute) - } - -} diff --git a/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala b/core/src/main/scala/org/typelevel/keypool/KeyPool.scala similarity index 95% rename from core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala rename to core/src/main/scala/org/typelevel/keypool/KeyPool.scala index 9e5906a0..fe5a1019 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/KeyPool.scala +++ b/core/src/main/scala/org/typelevel/keypool/KeyPool.scala @@ -28,7 +28,6 @@ import cats.effect.std.Semaphore import cats.syntax.all._ import scala.concurrent.duration._ import org.typelevel.keypool.internal._ -import org.typelevel.otel4s.metrics.MeterProvider /** * This pools internal guarantees are that the max number of values are in the pool at any time, not @@ -328,14 +327,13 @@ object KeyPool { final class Builder[F[_]: Temporal, A, B] private[keypool] ( val kpRes: A => Resource[F, B], - val name: String, val kpDefaultReuseState: Reusable, val idleTimeAllowedInPool: Duration, val kpMaxPerKey: A => Int, val kpMaxIdle: Int, val kpMaxTotal: Int, val onReaperException: Throwable => F[Unit], - val meterProvider: MeterProvider[F] + val metricsProvider: Metrics.Provider[F] ) { private def copy( kpRes: A => Resource[F, B] = this.kpRes, @@ -345,18 +343,16 @@ object KeyPool { kpMaxIdle: Int = this.kpMaxIdle, kpMaxTotal: Int = this.kpMaxTotal, onReaperException: Throwable => F[Unit] = this.onReaperException, - meterProvider: MeterProvider[F] = this.meterProvider, - name: String = this.name + metricsProvider: Metrics.Provider[F] = this.metricsProvider ): Builder[F, A, B] = new Builder[F, A, B]( kpRes, - name, kpDefaultReuseState, idleTimeAllowedInPool, kpMaxPerKey, kpMaxIdle, kpMaxTotal, onReaperException, - meterProvider + metricsProvider ) def doOnCreate(f: B => F[Unit]): Builder[F, A, B] = @@ -385,11 +381,8 @@ object KeyPool { def withOnReaperException(f: Throwable => F[Unit]): Builder[F, A, B] = copy(onReaperException = f) - def withMeterProvider(meterProvider: MeterProvider[F]): Builder[F, A, B] = - copy(meterProvider = meterProvider) - - def withName(name: String): Builder[F, A, B] = - copy(name = name) + def withMetricsProvider(metricsProvider: Metrics.Provider[F]): Builder[F, A, B] = + copy(metricsProvider = metricsProvider) def build: Resource[F, KeyPool[F, A, B]] = { def keepRunning[Z](fa: F[Z]): F[Z] = @@ -399,7 +392,7 @@ object KeyPool { Ref[F].of[PoolMap[A, (B, F[Unit])]](PoolMap.open(0, Map.empty[A, PoolList[(B, F[Unit])]])) )(kpVar => KeyPool.destroy(kpVar)) kpMaxTotalSem <- Resource.eval(Semaphore[F](kpMaxTotal.toLong)) - kpMetrics <- Resource.eval(Metrics.fromMeterProvider(meterProvider, name)) + kpMetrics <- Resource.eval(metricsProvider.get) _ <- idleTimeAllowedInPool match { case fd: FiniteDuration => val nanos = 0.seconds.max(fd) @@ -426,14 +419,13 @@ object KeyPool { res: A => Resource[F, B] ): Builder[F, A, B] = new Builder[F, A, B]( res, - Defaults.name, Defaults.defaultReuseState, Defaults.idleTimeAllowedInPool, Defaults.maxPerKey, Defaults.maxIdle, Defaults.maxTotal, Defaults.onReaperException[F], - Defaults.meterProvider + Defaults.metricsProvider ) def apply[F[_]: Temporal, A, B]( @@ -448,11 +440,10 @@ object KeyPool { def maxPerKey[K](k: K): Int = Function.const(100)(k) val maxIdle = 100 val maxTotal = 100 - val name = "unknown" def onReaperException[F[_]: Applicative] = { (t: Throwable) => Function.const(Applicative[F].unit)(t) } - def meterProvider[F[_]: Applicative]: MeterProvider[F] = MeterProvider.noop + def metricsProvider[F[_]: Applicative]: Metrics.Provider[F] = Metrics.Provider.noop } } } diff --git a/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala b/core/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala similarity index 95% rename from core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala rename to core/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala index 7856c56e..681738fd 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala +++ b/core/src/main/scala/org/typelevel/keypool/KeyPoolBuilder.scala @@ -27,12 +27,10 @@ import cats.syntax.all._ import cats.effect.kernel._ import cats.effect.kernel.syntax.spawn._ import cats.effect.std.Semaphore -import org.typelevel.otel4s.metrics.MeterProvider import scala.concurrent.duration._ @deprecated("use KeyPool.Builder", "0.4.7") final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( - val name: String, val kpCreate: A => F[B], val kpDestroy: B => F[Unit], val kpDefaultReuseState: Reusable, @@ -43,7 +41,6 @@ final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( val onReaperException: Throwable => F[Unit] ) { private def copy( - name: String = this.name, kpCreate: A => F[B] = this.kpCreate, kpDestroy: B => F[Unit] = this.kpDestroy, kpDefaultReuseState: Reusable = this.kpDefaultReuseState, @@ -53,7 +50,6 @@ final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( kpMaxTotal: Int = this.kpMaxTotal, onReaperException: Throwable => F[Unit] = this.onReaperException ): KeyPoolBuilder[F, A, B] = new KeyPoolBuilder[F, A, B]( - name, kpCreate, kpDestroy, kpDefaultReuseState, @@ -96,7 +92,7 @@ final class KeyPoolBuilder[F[_]: Temporal, A, B] private ( Ref[F].of[PoolMap[A, (B, F[Unit])]](PoolMap.open(0, Map.empty[A, PoolList[(B, F[Unit])]])) )(kpVar => KeyPool.destroy(kpVar)) kpMaxTotalSem <- Resource.eval(Semaphore[F](kpMaxTotal.toLong)) - kpMetrics <- Resource.eval(Metrics.fromMeterProvider(MeterProvider.noop, name)) + kpMetrics <- Resource.pure(Metrics.noop) _ <- idleTimeAllowedInPool match { case fd: FiniteDuration => val nanos = 0.seconds.max(fd) @@ -124,7 +120,6 @@ object KeyPoolBuilder { create: A => F[B], destroy: B => F[Unit] ): KeyPoolBuilder[F, A, B] = new KeyPoolBuilder[F, A, B]( - Defaults.name, create, destroy, Defaults.defaultReuseState, @@ -141,7 +136,6 @@ object KeyPoolBuilder { def maxPerKey[K](k: K): Int = Function.const(100)(k) val maxIdle = 100 val maxTotal = 100 - val name = "unknown" def onReaperException[F[_]: Applicative] = { (t: Throwable) => Function.const(Applicative[F].unit)(t) } diff --git a/core/shared/src/main/scala/org/typelevel/keypool/Managed.scala b/core/src/main/scala/org/typelevel/keypool/Managed.scala similarity index 100% rename from core/shared/src/main/scala/org/typelevel/keypool/Managed.scala rename to core/src/main/scala/org/typelevel/keypool/Managed.scala diff --git a/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala b/core/src/main/scala/org/typelevel/keypool/Pool.scala similarity index 89% rename from core/shared/src/main/scala/org/typelevel/keypool/Pool.scala rename to core/src/main/scala/org/typelevel/keypool/Pool.scala index b960e5d2..635f76a3 100644 --- a/core/shared/src/main/scala/org/typelevel/keypool/Pool.scala +++ b/core/src/main/scala/org/typelevel/keypool/Pool.scala @@ -24,7 +24,7 @@ package org.typelevel.keypool import cats._ import cats.effect.kernel._ import cats.syntax.all._ -import org.typelevel.otel4s.metrics.MeterProvider +import org.typelevel.keypool.internal.Metrics import scala.concurrent.duration._ /** @@ -73,32 +73,29 @@ object Pool { final class Builder[F[_]: Temporal, B] private ( val kpRes: Resource[F, B], - val name: String, val kpDefaultReuseState: Reusable, val idleTimeAllowedInPool: Duration, val kpMaxIdle: Int, val kpMaxTotal: Int, val onReaperException: Throwable => F[Unit], - val meterProvider: MeterProvider[F] + val metricsProvider: Metrics.Provider[F] ) { private def copy( kpRes: Resource[F, B] = this.kpRes, - name: String = this.name, kpDefaultReuseState: Reusable = this.kpDefaultReuseState, idleTimeAllowedInPool: Duration = this.idleTimeAllowedInPool, kpMaxIdle: Int = this.kpMaxIdle, kpMaxTotal: Int = this.kpMaxTotal, onReaperException: Throwable => F[Unit] = this.onReaperException, - meterProvider: MeterProvider[F] = this.meterProvider + metricsProvider: Metrics.Provider[F] = this.metricsProvider ): Builder[F, B] = new Builder[F, B]( kpRes, - name = name, kpDefaultReuseState, idleTimeAllowedInPool, kpMaxIdle, kpMaxTotal, onReaperException, - meterProvider + metricsProvider ) def doOnCreate(f: B => F[Unit]): Builder[F, B] = @@ -124,23 +121,19 @@ object Pool { def withOnReaperException(f: Throwable => F[Unit]): Builder[F, B] = copy(onReaperException = f) - def withMeterProvider(meterProvider: MeterProvider[F]): Builder[F, B] = - copy(meterProvider = meterProvider) - - def withName(name: String): Builder[F, B] = - copy(name = name) + def withMetricsProvider(metricsProvider: Metrics.Provider[F]): Builder[F, B] = + copy(metricsProvider = metricsProvider) private def toKeyPoolBuilder: KeyPool.Builder[F, Unit, B] = new KeyPool.Builder( kpRes = _ => kpRes, - name = name, kpDefaultReuseState = kpDefaultReuseState, idleTimeAllowedInPool = idleTimeAllowedInPool, kpMaxPerKey = _ => kpMaxTotal, kpMaxIdle = kpMaxIdle, kpMaxTotal = kpMaxTotal, onReaperException = onReaperException, - meterProvider = meterProvider + metricsProvider = metricsProvider ) def build: Resource[F, Pool[F, B]] = { @@ -158,13 +151,12 @@ object Pool { res: Resource[F, B] ): Builder[F, B] = new Builder[F, B]( res, - Defaults.name, Defaults.defaultReuseState, Defaults.idleTimeAllowedInPool, Defaults.maxIdle, Defaults.maxTotal, Defaults.onReaperException[F], - Defaults.meterProvider + Defaults.metricsProvider ) def apply[F[_]: Temporal, B]( @@ -178,11 +170,10 @@ object Pool { val idleTimeAllowedInPool = 30.seconds val maxIdle = 100 val maxTotal = 100 - val name = "unknown" def onReaperException[F[_]: Applicative] = { (t: Throwable) => Function.const(Applicative[F].unit)(t) } - def meterProvider[F[_]: Applicative]: MeterProvider[F] = MeterProvider.noop + def metricsProvider[F[_]: Applicative]: Metrics.Provider[F] = Metrics.Provider.noop } } } diff --git a/core/shared/src/main/scala/org/typelevel/keypool/PoolStats.scala b/core/src/main/scala/org/typelevel/keypool/PoolStats.scala similarity index 100% rename from core/shared/src/main/scala/org/typelevel/keypool/PoolStats.scala rename to core/src/main/scala/org/typelevel/keypool/PoolStats.scala diff --git a/core/shared/src/main/scala/org/typelevel/keypool/Reusable.scala b/core/src/main/scala/org/typelevel/keypool/Reusable.scala similarity index 100% rename from core/shared/src/main/scala/org/typelevel/keypool/Reusable.scala rename to core/src/main/scala/org/typelevel/keypool/Reusable.scala diff --git a/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala b/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala new file mode 100644 index 00000000..a66df167 --- /dev/null +++ b/core/src/main/scala/org/typelevel/keypool/internal/Metrics.scala @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.typelevel.keypool.internal + +import cats.Applicative +import cats.effect.kernel.Resource + +private[keypool] trait Metrics[F[_]] { + + /** + * Increments the number of idle resources. + */ + def idleInc: F[Unit] + + /** + * Decrements the number of idle resources. + */ + def idleDec: F[Unit] + + /** + * Records the number of in-use resources. + */ + def inUseCount: Resource[F, Unit] + + /** + * Records for how long the resource has been in use. + */ + def inUseRecordDuration: Resource[F, Unit] + + /** + * Increments the number of acquired resources. + */ + def acquiredTotalInc: F[Unit] + + /** + * Records how long does it take to acquire a resource. + */ + def acquireRecordDuration: Resource[F, Unit] + +} + +private[keypool] object Metrics { + + trait Provider[F[_]] { + def get: F[Metrics[F]] + } + + object Provider { + def noop[F[_]: Applicative]: Provider[F] = + new Provider[F] { + def get: F[Metrics[F]] = Applicative[F].pure(Metrics.noop) + } + } + + def noop[F[_]: Applicative]: Metrics[F] = + new Metrics[F] { + def idleInc: F[Unit] = Applicative[F].unit + def idleDec: F[Unit] = Applicative[F].unit + def inUseCount: Resource[F, Unit] = Resource.unit + def inUseRecordDuration: Resource[F, Unit] = Resource.unit + def acquiredTotalInc: F[Unit] = Applicative[F].unit + def acquireRecordDuration: Resource[F, Unit] = Resource.unit + } + +} diff --git a/core/shared/src/main/scala/org/typelevel/keypool/internal/PoolList.scala b/core/src/main/scala/org/typelevel/keypool/internal/PoolList.scala similarity index 100% rename from core/shared/src/main/scala/org/typelevel/keypool/internal/PoolList.scala rename to core/src/main/scala/org/typelevel/keypool/internal/PoolList.scala diff --git a/core/shared/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala b/core/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala similarity index 100% rename from core/shared/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala rename to core/src/main/scala/org/typelevel/keypool/internal/PoolMap.scala diff --git a/core/shared/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala b/core/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala similarity index 100% rename from core/shared/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala rename to core/src/test/scala/org/typelevel/keypool/KeyPoolSpec.scala diff --git a/core/shared/src/test/scala/org/typelevel/keypool/PoolSpec.scala b/core/src/test/scala/org/typelevel/keypool/PoolSpec.scala similarity index 100% rename from core/shared/src/test/scala/org/typelevel/keypool/PoolSpec.scala rename to core/src/test/scala/org/typelevel/keypool/PoolSpec.scala diff --git a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala similarity index 96% rename from core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala rename to otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index 8cc3cafd..1931b686 100644 --- a/core/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Typelevel + * Copyright (c) 2024 Typelevel * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -23,7 +23,7 @@ package org.typelevel.keypool import cats.effect._ import cats.effect.testkit._ -import io.opentelemetry.sdk.metrics.{Aggregation, InstrumentSelector, InstrumentType, SdkMeterProviderBuilder, View} +import io.opentelemetry.sdk.metrics._ import munit.CatsEffectSuite import org.typelevel.otel4s.metrics.MeterProvider import org.typelevel.otel4s.oteljava.testkit.metrics.MetricsTestkit @@ -158,7 +158,11 @@ class PoolMetricsSpec extends CatsEffectSuite { )(scenario: (OtelTestkit[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = { createTestkit.use { sdk => val builder = - Pool.Builder(Ref.of[IO, Int](1), nothing).withMeterProvider(sdk.metrics.meterProvider) + Pool + .Builder(Ref.of[IO, Int](1), nothing) + .withMetricsProvider( + Otel4sMetrics.provider(sdk.metrics.meterProvider, "test") + ) customize(builder).build.use(pool => scenario(sdk, pool)) } @@ -170,7 +174,7 @@ class PoolMetricsSpec extends CatsEffectSuite { Ref.of[IO, Int](1), nothing ) - .withMeterProvider(meterProvider) + .withMetricsProvider(Otel4sMetrics.provider(meterProvider, "test")) .withMaxTotal(10) .build diff --git a/otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala b/otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala new file mode 100644 index 00000000..1e78a306 --- /dev/null +++ b/otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.typelevel.keypool + +import java.util.concurrent.TimeUnit + +import cats.Monad +import cats.effect.kernel.Resource +import cats.syntax.functor._ +import cats.syntax.flatMap._ +import org.typelevel.keypool.internal.Metrics +import org.typelevel.otel4s.Attribute +import org.typelevel.otel4s.metrics.MeterProvider + +object Otel4sMetrics { + + def provider[F[_]: Monad]( + meterProvider: MeterProvider[F], + poolName: String + ): Metrics.Provider[F] = + new Metrics.Provider[F] { + def get: F[Metrics[F]] = + for { + meter <- meterProvider.get("org.typelevel.keypool") + + idle <- meter + .upDownCounter[Long]("idle.current") + .withUnit("{resource}") + .withDescription("A current number of idle resources") + .create + + inUse <- meter + .upDownCounter[Long]("in_use.current") + .withUnit("{resource}") + .withDescription("A current number of resources in use") + .create + + inUseDuration <- meter + .histogram[Long]("in_use.duration") + .withUnit("ms") + .withDescription("For how long a resource is in use") + .create + + acquiredTotal <- meter + .counter[Long]("acquired.total") + .withUnit("{resource}") + .withDescription("A total number of acquired resources") + .create + + acquireDuration <- meter + .histogram[Long]("acquire.duration") + .withUnit("ms") + .withDescription("How long does it take to acquire a resource") + .create + } yield new Metrics[F] { + private val nameAttribute = Attribute("pool.name", poolName) + + def idleInc: F[Unit] = + idle.inc(nameAttribute) + + def idleDec: F[Unit] = + idle.dec(nameAttribute) + + def inUseCount: Resource[F, Unit] = + Resource.make(inUse.inc(nameAttribute))(_ => inUse.dec(nameAttribute)) + + def inUseRecordDuration: Resource[F, Unit] = + inUseDuration.recordDuration(TimeUnit.MILLISECONDS, nameAttribute) + + def acquiredTotalInc: F[Unit] = + acquiredTotal.inc(nameAttribute) + + def acquireRecordDuration: Resource[F, Unit] = + acquireDuration.recordDuration(TimeUnit.MILLISECONDS, nameAttribute) + } + } +} From 00e75eef109e6f7216bd50229fd559574d461871 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 31 Mar 2024 13:49:20 +0300 Subject: [PATCH 13/19] generate CI workflow --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7c4538a..f8cfe828 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,11 +80,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p core/.native/target core/.js/target core/.jvm/target project/target + run: mkdir -p otel4s/native/target core/.native/target otel4s/js/target otel4s/jvm/target core/.js/target core/.jvm/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar core/.native/target core/.js/target core/.jvm/target project/target + run: tar cf targets.tar otel4s/native/target core/.native/target otel4s/js/target otel4s/jvm/target core/.js/target core/.jvm/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') From 8212c5bed59a5f701f78e14eb0b21308f48dbfcb Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Tue, 16 Apr 2024 19:39:18 +0300 Subject: [PATCH 14/19] Update build.sbt --- .github/workflows/ci.yml | 123 ++++++++++++++++++++++++++++++--------- build.sbt | 11 ++++ 2 files changed, 106 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8cfe828..d7e995b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,14 @@ jobs: os: [ubuntu-latest] scala: [2.12, 2.13, 3] java: [temurin@8] - project: [rootJS, rootJVM, rootNative] + project: [coreJVM, coreJS, coreNative, otel4sJVM, otel4sJS, otel4sNative] + exclude: + - project: otel4sJVM + scala: 2.12 + - project: otel4sJS + scala: 2.12 + - project: otel4sNative + scala: 2.12 runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: @@ -121,92 +128,152 @@ jobs: if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' run: sbt +update - - name: Download target directories (2.12, rootJS) + - name: Download target directories (2.12, coreJVM) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-coreJVM - - name: Inflate target directories (2.12, rootJS) + - name: Inflate target directories (2.12, coreJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.12, rootJVM) + - name: Download target directories (2.12, coreJS) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-coreJS - - name: Inflate target directories (2.12, rootJVM) + - name: Inflate target directories (2.12, coreJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.12, rootNative) + - name: Download target directories (2.12, coreNative) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootNative + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-coreNative - - name: Inflate target directories (2.12, rootNative) + - name: Inflate target directories (2.12, coreNative) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13, rootJS) + - name: Download target directories (2.13, coreJVM) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-coreJVM - - name: Inflate target directories (2.13, rootJS) + - name: Inflate target directories (2.13, coreJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13, rootJVM) + - name: Download target directories (2.13, coreJS) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-coreJS - - name: Inflate target directories (2.13, rootJVM) + - name: Inflate target directories (2.13, coreJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13, rootNative) + - name: Download target directories (2.13, coreNative) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootNative + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-coreNative - - name: Inflate target directories (2.13, rootNative) + - name: Inflate target directories (2.13, coreNative) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3, rootJS) + - name: Download target directories (2.13, otel4sJVM) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-otel4sJVM - - name: Inflate target directories (3, rootJS) + - name: Inflate target directories (2.13, otel4sJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3, rootJVM) + - name: Download target directories (2.13, otel4sJS) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-otel4sJS - - name: Inflate target directories (3, rootJVM) + - name: Inflate target directories (2.13, otel4sJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3, rootNative) + - name: Download target directories (2.13, otel4sNative) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootNative + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-otel4sNative - - name: Inflate target directories (3, rootNative) + - name: Inflate target directories (2.13, otel4sNative) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3, coreJVM) + uses: actions/download-artifact@v4 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3-coreJVM + + - name: Inflate target directories (3, coreJVM) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3, coreJS) + uses: actions/download-artifact@v4 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3-coreJS + + - name: Inflate target directories (3, coreJS) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3, coreNative) + uses: actions/download-artifact@v4 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3-coreNative + + - name: Inflate target directories (3, coreNative) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3, otel4sJVM) + uses: actions/download-artifact@v4 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3-otel4sJVM + + - name: Inflate target directories (3, otel4sJVM) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3, otel4sJS) + uses: actions/download-artifact@v4 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3-otel4sJS + + - name: Inflate target directories (3, otel4sJS) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3, otel4sNative) + uses: actions/download-artifact@v4 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3-otel4sNative + + - name: Inflate target directories (3, otel4sNative) run: | tar xf targets.tar rm targets.tar diff --git a/build.sbt b/build.sbt index 1e4806a8..a39dec6b 100644 --- a/build.sbt +++ b/build.sbt @@ -13,6 +13,17 @@ ThisBuild / tlSiteApiUrl := Some(url("https://www.javadoc.io/doc/org.typelevel/k lazy val root = tlCrossRootProject.aggregate(core, otel4s) +ThisBuild / githubWorkflowBuildMatrixAdditions := { + val projects = core.componentProjects ++ otel4s.componentProjects + + Map("project" -> projects.map(_.id).toList) +} + +ThisBuild / githubWorkflowBuildMatrixExclusions ++= { + val projects = otel4s.componentProjects.map(_.id) + projects.map(project => MatrixExclude(Map("project" -> project, "scala" -> "2.12"))).toSeq +} + lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) .in(file("core")) From 8e1252692919464448d02ae6521e0e53af1f04ef Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Tue, 16 Apr 2024 20:47:56 +0300 Subject: [PATCH 15/19] build.sbt: update previous mima artifacts for otel4s --- build.sbt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index a39dec6b..56d5b0b8 100644 --- a/build.sbt +++ b/build.sbt @@ -21,7 +21,7 @@ ThisBuild / githubWorkflowBuildMatrixAdditions := { ThisBuild / githubWorkflowBuildMatrixExclusions ++= { val projects = otel4s.componentProjects.map(_.id) - projects.map(project => MatrixExclude(Map("project" -> project, "scala" -> "2.12"))).toSeq + projects.map(project => MatrixExclude(Map("project" -> project, "scala" -> "2.12"))) } lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) @@ -67,7 +67,8 @@ lazy val otel4s = crossProject(JVMPlatform, JSPlatform, NativePlatform) name := "keypool-otel4s", startYear := Some(2024), crossScalaVersions := Seq(Scala213, Scala3), - libraryDependencies += "org.typelevel" %%% "otel4s-core" % otel4sV + libraryDependencies += "org.typelevel" %%% "otel4s-core" % otel4sV, + mimaPreviousArtifacts ~= { _.filterNot(_.revision.startsWith("0.4")) } ) .jvmSettings( libraryDependencies += "org.typelevel" %% "otel4s-oteljava-testkit" % otel4sV % Test @@ -82,7 +83,7 @@ lazy val docs = project val catsV = "2.10.0" val catsEffectV = "3.5.4" -val otel4sV = "0.5.0" +val otel4sV = "0.6.0" val munitV = "1.0.0-M11" val munitCatsEffectV = "2.0.0-M4" From 98da94df2829ebc5e61d1d8c906c61a59f611ba8 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Tue, 16 Apr 2024 20:48:12 +0300 Subject: [PATCH 16/19] add scaladocs, update tests --- .../scala/org/typelevel/keypool/KeyPool.scala | 2 +- .../typelevel/keypool/PoolMetricsSpec.scala | 83 ++++++++--------- .../org/typelevel/keypool/Otel4sMetrics.scala | 91 ++++++++++++------- 3 files changed, 100 insertions(+), 76 deletions(-) diff --git a/core/src/main/scala/org/typelevel/keypool/KeyPool.scala b/core/src/main/scala/org/typelevel/keypool/KeyPool.scala index fe5a1019..ed26948b 100644 --- a/core/src/main/scala/org/typelevel/keypool/KeyPool.scala +++ b/core/src/main/scala/org/typelevel/keypool/KeyPool.scala @@ -302,7 +302,7 @@ object KeyPool { } def allocateNew: F[(B, F[Unit])] = - kp.kpMetrics.acquireRecordDuration.use(_ => kp.kpRes(k).allocated) + kp.kpMetrics.acquireRecordDuration.surround(kp.kpRes(k).allocated) for { _ <- kp.kpMaxTotalSem.permit diff --git a/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index 1931b686..ab120f23 100644 --- a/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -23,9 +23,10 @@ package org.typelevel.keypool import cats.effect._ import cats.effect.testkit._ -import io.opentelemetry.sdk.metrics._ import munit.CatsEffectSuite -import org.typelevel.otel4s.metrics.MeterProvider +import org.typelevel.keypool.internal.Metrics +import org.typelevel.otel4s.{Attribute, Attributes} +import org.typelevel.otel4s.metrics.{BucketBoundaries, Meter, MeterProvider} import org.typelevel.otel4s.oteljava.testkit.metrics.MetricsTestkit import org.typelevel.otel4s.oteljava.testkit.metrics.data.{HistogramPointData, Metric, MetricData} @@ -42,7 +43,7 @@ class PoolMetricsSpec extends CatsEffectSuite { createTestkit.use { testkit => for { - snapshot <- mkPool(testkit.metrics.meterProvider).use(_ => testkit.snapshot) + snapshot <- mkPool(testkit.metrics.meterProvider).surround(testkit.snapshot) } yield assertEquals(snapshot, expectedSnapshot) } } @@ -50,7 +51,7 @@ class PoolMetricsSpec extends CatsEffectSuite { test("In use: increment on acquire and decrement on release") { poolTest() { (sdk, pool) => for { - inUse <- pool.take.use(_ => sdk.snapshot) + inUse <- pool.take.surround(sdk.snapshot) afterUse <- sdk.snapshot } yield { assertEquals(inUse.inUse, List(1L)) @@ -66,7 +67,7 @@ class PoolMetricsSpec extends CatsEffectSuite { for { deferred <- IO.deferred[MetricsSnapshot] _ <- pool.take - .use(_ => sdk.snapshot.flatMap(deferred.complete) >> IO.raiseError(exception)) + .surround(sdk.snapshot.flatMap(deferred.complete) >> IO.raiseError(exception)) .attempt inUse <- deferred.get afterUse <- sdk.snapshot @@ -80,7 +81,7 @@ class PoolMetricsSpec extends CatsEffectSuite { test("Idle: keep 0 when `maxIdle` is 0") { poolTest(_.withMaxIdle(0)) { (sdk, pool) => for { - inUse <- pool.take.use(_ => sdk.snapshot) + inUse <- pool.take.surround(sdk.snapshot) afterUse <- sdk.snapshot } yield { assertEquals(inUse.idle, Nil) @@ -92,7 +93,7 @@ class PoolMetricsSpec extends CatsEffectSuite { test("Idle: keep 1 when `maxIdle` is 1") { poolTest(_.withMaxIdle(1)) { (sdk, pool) => for { - inUse <- pool.take.use(_ => sdk.snapshot) + inUse <- pool.take.surround(sdk.snapshot) afterUse <- sdk.snapshot } yield { assertEquals(inUse.idle, Nil) @@ -105,7 +106,7 @@ class PoolMetricsSpec extends CatsEffectSuite { TestControl.executeEmbed { poolTest(_.withMaxIdle(1).withIdleTimeAllowedInPool(1.second)) { (sdk, pool) => for { - inUse <- pool.take.use(_ => sdk.snapshot) + inUse <- pool.take.surround(sdk.snapshot) afterUse <- sdk.snapshot afterSleep <- sdk.snapshot.delayBy(6.seconds) } yield { @@ -121,7 +122,7 @@ class PoolMetricsSpec extends CatsEffectSuite { TestControl.executeEmbed { createTestkit.use { sdk => mkPool(sdk.metrics.meterProvider) - .use(pool => pool.take.use(_ => sdk.snapshot.delayBy(500.millis)).product(sdk.snapshot)) + .use(pool => pool.take.surround(sdk.snapshot.delayBy(1.second)).product(sdk.snapshot)) .map { case (inUse, afterUse) => val acquireDuration = List( @@ -140,7 +141,7 @@ class PoolMetricsSpec extends CatsEffectSuite { idle = List(1), inUse = List(0), inUseDuration = List( - HistogramPointData(500.0, 1, HistogramBuckets, List(0, 0, 0, 1, 0)) + HistogramPointData(1.0, 1, HistogramBuckets, List(0, 1, 0, 0, 0)) ), acquiredTotal = List(1), acquireDuration = acquireDuration @@ -157,41 +158,38 @@ class PoolMetricsSpec extends CatsEffectSuite { customize: Pool.Builder[IO, Ref[IO, Int]] => Pool.Builder[IO, Ref[IO, Int]] = identity )(scenario: (OtelTestkit[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = { createTestkit.use { sdk => - val builder = - Pool + sdk.metrics.meterProvider.get("org.typelevel.keypool").flatMap { implicit M: Meter[IO] => + val builder = Pool .Builder(Ref.of[IO, Int](1), nothing) - .withMetricsProvider( - Otel4sMetrics.provider(sdk.metrics.meterProvider, "test") - ) + .withMetricsProvider(metricsProvider) - customize(builder).build.use(pool => scenario(sdk, pool)) + customize(builder).build.use(pool => scenario(sdk, pool)) + } } } private def mkPool(meterProvider: MeterProvider[IO]) = - Pool - .Builder( - Ref.of[IO, Int](1), - nothing - ) - .withMetricsProvider(Otel4sMetrics.provider(meterProvider, "test")) - .withMaxTotal(10) - .build - - private def createTestkit: Resource[IO, OtelTestkit[IO]] = { - def customize(builder: SdkMeterProviderBuilder) = - builder - .registerView( - InstrumentSelector.builder().setType(InstrumentType.HISTOGRAM).build(), - View - .builder() - .setAggregation( - Aggregation.explicitBucketHistogram(HistogramBuckets.map(Double.box).asJava) - ) - .build() + Resource.eval(meterProvider.get("org.typelevel.keypool")).flatMap { implicit M: Meter[IO] => + Pool + .Builder( + Ref.of[IO, Int](1), + nothing ) + .withMetricsProvider(metricsProvider) + .withMaxTotal(10) + .build + } - MetricsTestkit.inMemory[IO](customize).map { testkit => + private def metricsProvider(implicit M: Meter[IO]): Metrics.Provider[IO] = + Otel4sMetrics.provider[IO]( + "keypool", + Attributes(Attribute("pool.name", "test")), + BucketBoundaries(HistogramBuckets.toVector), + BucketBoundaries(HistogramBuckets.toVector) + ) + + private def createTestkit: Resource[IO, OtelTestkit[IO]] = + MetricsTestkit.inMemory[IO]().map { testkit => new OtelTestkit[IO] { val metrics: MetricsTestkit[IO] = testkit @@ -218,16 +216,15 @@ class PoolMetricsSpec extends CatsEffectSuite { .getOrElse(Nil) MetricsSnapshot( - counterValue("idle.current"), - counterValue("in_use.current"), - histogramSnapshot("in_use.duration"), - counterValue("acquired.total"), - histogramSnapshot("acquire.duration") + counterValue("keypool.idle.current"), + counterValue("keypool.in_use.current"), + histogramSnapshot("keypool.in_use.duration"), + counterValue("keypool.acquired.total"), + histogramSnapshot("keypool.acquire.duration") ) } } } - } private val HistogramBuckets: List[Double] = List(0.01, 1, 100, 1000) diff --git a/otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala b/otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala index 1e78a306..61beb883 100644 --- a/otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala +++ b/otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala @@ -28,69 +28,96 @@ import cats.effect.kernel.Resource import cats.syntax.functor._ import cats.syntax.flatMap._ import org.typelevel.keypool.internal.Metrics -import org.typelevel.otel4s.Attribute -import org.typelevel.otel4s.metrics.MeterProvider +import org.typelevel.otel4s.Attributes +import org.typelevel.otel4s.metrics.{BucketBoundaries, Meter} object Otel4sMetrics { - def provider[F[_]: Monad]( - meterProvider: MeterProvider[F], - poolName: String + private val DefaultHistogramBuckets: BucketBoundaries = + BucketBoundaries(Vector(.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10)) + + /** + * Creates metrics provider using otel4s `Meter` under the hood. + * + * Use `attributes` to customize the metrics. + * + * @example + * {{{ + * val attributes = Attributes(Attribute("pool.name", "db-pool")) + * + * Otel4sMetrics.provider[IO](attributes = attributes) + * }}} + * + * @param prefix + * the prefix to prepend to the metrics + * + * @param attributes + * the attributes to attach to the measurements + * + * @param inUseDurationSecondsHistogramBuckets + * the histogram buckets for the 'in_use.duration' histogram + * + * @param acquireDurationSecondsHistogramBuckets + * the histogram buckets for 'acquire.duration' histogram + */ + def provider[F[_]: Monad: Meter]( + prefix: String = "keypool", + attributes: Attributes = Attributes.empty, + inUseDurationSecondsHistogramBuckets: BucketBoundaries = DefaultHistogramBuckets, + acquireDurationSecondsHistogramBuckets: BucketBoundaries = DefaultHistogramBuckets ): Metrics.Provider[F] = new Metrics.Provider[F] { def get: F[Metrics[F]] = for { - meter <- meterProvider.get("org.typelevel.keypool") - - idle <- meter - .upDownCounter[Long]("idle.current") + idle <- Meter[F] + .upDownCounter[Long](s"$prefix.idle.current") .withUnit("{resource}") - .withDescription("A current number of idle resources") + .withDescription("A current number of idle resources.") .create - inUse <- meter - .upDownCounter[Long]("in_use.current") + inUse <- Meter[F] + .upDownCounter[Long](s"$prefix.in_use.current") .withUnit("{resource}") - .withDescription("A current number of resources in use") + .withDescription("A current number of resources in use.") .create - inUseDuration <- meter - .histogram[Long]("in_use.duration") - .withUnit("ms") - .withDescription("For how long a resource is in use") + inUseDuration <- Meter[F] + .histogram[Long](s"$prefix.in_use.duration") + .withUnit("s") + .withDescription("For how long a resource is in use.") + .withExplicitBucketBoundaries(inUseDurationSecondsHistogramBuckets) .create - acquiredTotal <- meter - .counter[Long]("acquired.total") + acquiredTotal <- Meter[F] + .counter[Long](s"$prefix.acquired.total") .withUnit("{resource}") - .withDescription("A total number of acquired resources") + .withDescription("A total number of acquired resources.") .create - acquireDuration <- meter - .histogram[Long]("acquire.duration") - .withUnit("ms") - .withDescription("How long does it take to acquire a resource") + acquireDuration <- Meter[F] + .histogram[Long](s"$prefix.acquire.duration") + .withUnit("s") + .withDescription("How long does it take to acquire a resource.") + .withExplicitBucketBoundaries(acquireDurationSecondsHistogramBuckets) .create } yield new Metrics[F] { - private val nameAttribute = Attribute("pool.name", poolName) - def idleInc: F[Unit] = - idle.inc(nameAttribute) + idle.inc(attributes) def idleDec: F[Unit] = - idle.dec(nameAttribute) + idle.dec(attributes) def inUseCount: Resource[F, Unit] = - Resource.make(inUse.inc(nameAttribute))(_ => inUse.dec(nameAttribute)) + Resource.make(inUse.inc(attributes))(_ => inUse.dec(attributes)) def inUseRecordDuration: Resource[F, Unit] = - inUseDuration.recordDuration(TimeUnit.MILLISECONDS, nameAttribute) + inUseDuration.recordDuration(TimeUnit.SECONDS, attributes) def acquiredTotalInc: F[Unit] = - acquiredTotal.inc(nameAttribute) + acquiredTotal.inc(attributes) def acquireRecordDuration: Resource[F, Unit] = - acquireDuration.recordDuration(TimeUnit.MILLISECONDS, nameAttribute) + acquireDuration.recordDuration(TimeUnit.SECONDS, attributes) } } } From f7a16b7c5b5ab16269e668ac88fe3c0641ec57d9 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Tue, 16 Apr 2024 20:51:15 +0300 Subject: [PATCH 17/19] fix compilation issue --- .../src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index ab120f23..6fede521 100644 --- a/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -31,7 +31,6 @@ import org.typelevel.otel4s.oteljava.testkit.metrics.MetricsTestkit import org.typelevel.otel4s.oteljava.testkit.metrics.data.{HistogramPointData, Metric, MetricData} import scala.concurrent.duration._ -import scala.jdk.CollectionConverters._ import scala.util.control.NoStackTrace class PoolMetricsSpec extends CatsEffectSuite { From 041bd8112741486d3afe8e72e0ea4b691d5d0830 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Tue, 16 Apr 2024 20:56:55 +0300 Subject: [PATCH 18/19] make mima happy --- build.sbt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 56d5b0b8..e1708f3f 100644 --- a/build.sbt +++ b/build.sbt @@ -54,7 +54,8 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) ProblemFilters .exclude[DirectMissingMethodProblem]("org.typelevel.keypool.KeyPoolBuilder.this"), ProblemFilters - .exclude[DirectMissingMethodProblem]("org.typelevel.keypool.KeyPool#Builder.this") + .exclude[DirectMissingMethodProblem]("org.typelevel.keypool.KeyPool#Builder.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.typelevel.keypool.Pool#Builder.this") ) ) From 6bfb5572a21b9b0255a7b76ab7650d6d1d8b6d3c Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sun, 5 May 2024 11:59:59 +0300 Subject: [PATCH 19/19] Use otel4s `0.7.0` --- .github/workflows/ci.yml | 4 +- build.sbt | 16 +- .../org/typelevel/keypool/Otel4sMetrics.scala | 0 .../typelevel/keypool/PoolMetricsSpec.scala | 170 ++++++++++-------- 4 files changed, 102 insertions(+), 88 deletions(-) rename otel4s/{shared => }/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala (100%) rename otel4s/{jvm => }/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala (57%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7e995b4..b0a52b52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,11 +87,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p otel4s/native/target core/.native/target otel4s/js/target otel4s/jvm/target core/.js/target core/.jvm/target project/target + run: mkdir -p otel4s/.native/target core/.native/target otel4s/.js/target otel4s/.jvm/target core/.js/target core/.jvm/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar otel4s/native/target core/.native/target otel4s/js/target otel4s/jvm/target core/.js/target core/.jvm/target project/target + run: tar cf targets.tar otel4s/.native/target core/.native/target otel4s/.js/target otel4s/.jvm/target core/.js/target core/.jvm/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') diff --git a/build.sbt b/build.sbt index e1708f3f..b510774e 100644 --- a/build.sbt +++ b/build.sbt @@ -60,7 +60,7 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) lazy val otel4s = crossProject(JVMPlatform, JSPlatform, NativePlatform) - .crossType(CrossType.Full) + .crossType(CrossType.Pure) .in(file("otel4s")) .dependsOn(core) .settings(commonSettings) @@ -68,12 +68,12 @@ lazy val otel4s = crossProject(JVMPlatform, JSPlatform, NativePlatform) name := "keypool-otel4s", startYear := Some(2024), crossScalaVersions := Seq(Scala213, Scala3), - libraryDependencies += "org.typelevel" %%% "otel4s-core" % otel4sV, + libraryDependencies ++= Seq( + "org.typelevel" %%% "otel4s-core-metrics" % otel4sV, + "org.typelevel" %%% "otel4s-sdk-metrics-testkit" % otel4sV % Test + ), mimaPreviousArtifacts ~= { _.filterNot(_.revision.startsWith("0.4")) } ) - .jvmSettings( - libraryDependencies += "org.typelevel" %% "otel4s-oteljava-testkit" % otel4sV % Test - ) lazy val docs = project .in(file("site")) @@ -84,10 +84,10 @@ lazy val docs = project val catsV = "2.10.0" val catsEffectV = "3.5.4" -val otel4sV = "0.6.0" +val otel4sV = "0.7.0" -val munitV = "1.0.0-M11" -val munitCatsEffectV = "2.0.0-M4" +val munitV = "1.0.0-RC1" +val munitCatsEffectV = "2.0.0-M5" val kindProjectorV = "0.13.3" val betterMonadicForV = "0.3.1" diff --git a/otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala b/otel4s/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala similarity index 100% rename from otel4s/shared/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala rename to otel4s/src/main/scala/org/typelevel/keypool/Otel4sMetrics.scala diff --git a/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala b/otel4s/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala similarity index 57% rename from otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala rename to otel4s/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala index 6fede521..e03bee84 100644 --- a/otel4s/jvm/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala +++ b/otel4s/src/test/scala/org/typelevel/keypool/PoolMetricsSpec.scala @@ -27,8 +27,8 @@ import munit.CatsEffectSuite import org.typelevel.keypool.internal.Metrics import org.typelevel.otel4s.{Attribute, Attributes} import org.typelevel.otel4s.metrics.{BucketBoundaries, Meter, MeterProvider} -import org.typelevel.otel4s.oteljava.testkit.metrics.MetricsTestkit -import org.typelevel.otel4s.oteljava.testkit.metrics.data.{HistogramPointData, Metric, MetricData} +import org.typelevel.otel4s.sdk.metrics.data.{MetricPoints, PointData, TimeWindow} +import org.typelevel.otel4s.sdk.testkit.metrics.MetricsTestkit import scala.concurrent.duration._ import scala.util.control.NoStackTrace @@ -38,7 +38,7 @@ class PoolMetricsSpec extends CatsEffectSuite { test("Metrics should be empty for unused pool") { val expectedSnapshot = - MetricsSnapshot(Nil, Nil, Nil, Nil, Nil) + MetricsSnapshot(Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty) createTestkit.use { testkit => for { @@ -53,8 +53,8 @@ class PoolMetricsSpec extends CatsEffectSuite { inUse <- pool.take.surround(sdk.snapshot) afterUse <- sdk.snapshot } yield { - assertEquals(inUse.inUse, List(1L)) - assertEquals(afterUse.inUse, List(0L)) + assertEquals(inUse.inUse, Vector(1L)) + assertEquals(afterUse.inUse, Vector(0L)) } } } @@ -71,8 +71,8 @@ class PoolMetricsSpec extends CatsEffectSuite { inUse <- deferred.get afterUse <- sdk.snapshot } yield { - assertEquals(inUse.inUse, List(1L)) - assertEquals(afterUse.inUse, List(0L)) + assertEquals(inUse.inUse, Vector(1L)) + assertEquals(afterUse.inUse, Vector(0L)) } } } @@ -83,8 +83,8 @@ class PoolMetricsSpec extends CatsEffectSuite { inUse <- pool.take.surround(sdk.snapshot) afterUse <- sdk.snapshot } yield { - assertEquals(inUse.idle, Nil) - assertEquals(afterUse.idle, Nil) + assertEquals(inUse.idle, Vector.empty) + assertEquals(afterUse.idle, Vector.empty) } } } @@ -95,77 +95,89 @@ class PoolMetricsSpec extends CatsEffectSuite { inUse <- pool.take.surround(sdk.snapshot) afterUse <- sdk.snapshot } yield { - assertEquals(inUse.idle, Nil) - assertEquals(afterUse.idle, List(1L)) + assertEquals(inUse.idle, Vector.empty) + assertEquals(afterUse.idle, Vector(1L)) } } } test("Idle: decrement on reaper cleanup") { - TestControl.executeEmbed { - poolTest(_.withMaxIdle(1).withIdleTimeAllowedInPool(1.second)) { (sdk, pool) => - for { - inUse <- pool.take.surround(sdk.snapshot) - afterUse <- sdk.snapshot - afterSleep <- sdk.snapshot.delayBy(6.seconds) - } yield { - assertEquals(inUse.idle, Nil) - assertEquals(afterUse.idle, List(1L)) - assertEquals(afterSleep.idle, List(0L)) - } + poolTest(_.withMaxIdle(1).withIdleTimeAllowedInPool(1.second)) { (sdk, pool) => + for { + inUse <- pool.take.surround(sdk.snapshot) + afterUse <- sdk.snapshot + afterSleep <- sdk.snapshot.delayBy(6.seconds) + } yield { + assertEquals(inUse.idle, Vector.empty) + assertEquals(afterUse.idle, Vector(1L)) + assertEquals(afterSleep.idle, Vector(0L)) } } + } test("Generate valid metric snapshots") { - TestControl.executeEmbed { - createTestkit.use { sdk => - mkPool(sdk.metrics.meterProvider) - .use(pool => pool.take.surround(sdk.snapshot.delayBy(1.second)).product(sdk.snapshot)) - .map { case (inUse, afterUse) => - val acquireDuration = - List( - HistogramPointData(0, 1, HistogramBuckets, List(1, 0, 0, 0, 0)) - ) - - val expectedInUse = MetricsSnapshot( - idle = Nil, - inUse = List(1), - inUseDuration = Nil, - acquiredTotal = List(1), - acquireDuration = acquireDuration + poolTest() { (sdk, pool) => + pool.take + .surround(sdk.snapshot.delayBy(1.second)) + .product(sdk.snapshot) + .map { case (inUse, afterUse) => + val acquireDuration = Vector( + PointData.histogram( + TimeWindow(Duration.Zero, 1.second), + Attributes(Attribute("pool.name", "test")), + Vector.empty, + Some(PointData.Histogram.Stats(0.0, 0.0, 0.0, 1)), + HistogramBuckets, + Vector(1, 0, 0, 0, 0) ) + ) - val expectedAfterUser = MetricsSnapshot( - idle = List(1), - inUse = List(0), - inUseDuration = List( - HistogramPointData(1.0, 1, HistogramBuckets, List(0, 1, 0, 0, 0)) - ), - acquiredTotal = List(1), - acquireDuration = acquireDuration - ) + val expectedInUse = MetricsSnapshot( + idle = Vector.empty, + inUse = Vector(1L), + inUseDuration = Vector.empty, + acquiredTotal = Vector(1L), + acquireDuration = acquireDuration + ) - assertEquals(inUse, expectedInUse) - assertEquals(afterUse, expectedAfterUser) - } - } + val expectedAfterUser = MetricsSnapshot( + idle = Vector(1L), + inUse = Vector(0L), + inUseDuration = Vector( + PointData.histogram( + TimeWindow(Duration.Zero, 1.second), + Attributes(Attribute("pool.name", "test")), + Vector.empty, + Some(PointData.Histogram.Stats(1.0, 1.0, 1.0, 1)), + HistogramBuckets, + Vector(0, 1, 0, 0, 0) + ) + ), + acquiredTotal = Vector(1L), + acquireDuration = acquireDuration + ) + + assertEquals(inUse, expectedInUse) + assertEquals(afterUse, expectedAfterUser) + } } } private def poolTest( customize: Pool.Builder[IO, Ref[IO, Int]] => Pool.Builder[IO, Ref[IO, Int]] = identity - )(scenario: (OtelTestkit[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = { - createTestkit.use { sdk => - sdk.metrics.meterProvider.get("org.typelevel.keypool").flatMap { implicit M: Meter[IO] => - val builder = Pool - .Builder(Ref.of[IO, Int](1), nothing) - .withMetricsProvider(metricsProvider) - - customize(builder).build.use(pool => scenario(sdk, pool)) + )(scenario: (OtelTestkit[IO], Pool[IO, Ref[IO, Int]]) => IO[Unit]): IO[Unit] = + TestControl.executeEmbed { + createTestkit.use { sdk => + sdk.metrics.meterProvider.get("org.typelevel.keypool").flatMap { implicit M: Meter[IO] => + val builder = Pool + .Builder(Ref.of[IO, Int](1), nothing) + .withMetricsProvider(metricsProvider) + + customize(builder).build.use(pool => scenario(sdk, pool)) + } } } - } private def mkPool(meterProvider: MeterProvider[IO]) = Resource.eval(meterProvider.get("org.typelevel.keypool")).flatMap { implicit M: Meter[IO] => @@ -183,8 +195,8 @@ class PoolMetricsSpec extends CatsEffectSuite { Otel4sMetrics.provider[IO]( "keypool", Attributes(Attribute("pool.name", "test")), - BucketBoundaries(HistogramBuckets.toVector), - BucketBoundaries(HistogramBuckets.toVector) + HistogramBuckets, + HistogramBuckets ) private def createTestkit: Resource[IO, OtelTestkit[IO]] = @@ -194,25 +206,27 @@ class PoolMetricsSpec extends CatsEffectSuite { def snapshot: IO[MetricsSnapshot] = for { - metrics <- testkit.collectMetrics[Metric] + metrics <- testkit.collectMetrics } yield { - def counterValue(name: String): List[Long] = + def counterValue(name: String): Vector[Long] = metrics .find(_.name == name) .map(_.data) - .collectFirst { case MetricData.LongSum(points) => - points.map(_.value) + .collectFirst { case sum: MetricPoints.Sum => + sum.points.toVector.collect { case long: PointData.LongNumber => + long.value + } } - .getOrElse(Nil) + .getOrElse(Vector.empty) - def histogramSnapshot(name: String): List[HistogramPointData] = + def histogramSnapshot(name: String): Vector[PointData.Histogram] = metrics .find(_.name == name) .map(_.data) - .collectFirst { case MetricData.Histogram(points) => - points.map(_.value) + .collectFirst { case histogram: MetricPoints.Histogram => + histogram.points.toVector } - .getOrElse(Nil) + .getOrElse(Vector.empty) MetricsSnapshot( counterValue("keypool.idle.current"), @@ -225,8 +239,8 @@ class PoolMetricsSpec extends CatsEffectSuite { } } - private val HistogramBuckets: List[Double] = - List(0.01, 1, 100, 1000) + private val HistogramBuckets: BucketBoundaries = + BucketBoundaries(Vector(0.01, 1.0, 100.0, 1000.0)) private def nothing(ref: Ref[IO, Int]): IO[Unit] = ref.get.void @@ -241,11 +255,11 @@ object PoolMetricsSpec { } final case class MetricsSnapshot( - idle: List[Long], - inUse: List[Long], - inUseDuration: List[HistogramPointData], - acquiredTotal: List[Long], - acquireDuration: List[HistogramPointData] + idle: Vector[Long], + inUse: Vector[Long], + inUseDuration: Vector[PointData.Histogram], + acquiredTotal: Vector[Long], + acquireDuration: Vector[PointData.Histogram] ) }