diff --git a/README.md b/README.md index 498118b..e7a87fe 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Main advantages of this library: 1) Universal abstraction with misc. implementations 2) Support of multiple exports at once (`MultiMonitor`) 3) Scala API -4) Scala Effect API (cats-effect 3) - If you need cats-effect 2 you can use version 2.9.x of this library. +4) Scala Effect API (cats-effect 2 and 3) The entry-point into the library is the interface `Monitor`. Your classes need to get an instance of a monitor which they can use to construct different metrics, e.g. meters, timers or histograms. Instances of the individuals metrics can be used to monitor your application. diff --git a/build.sbt b/build.sbt index f27d7d3..ed7178a 100644 --- a/build.sbt +++ b/build.sbt @@ -29,6 +29,7 @@ lazy val Versions = new { val grpc = "1.42.1" val slf4j = "1.7.30" val assertj = "3.12.2" + val catsEffect2 = "2.5.3" val catsEffect3 = "3.3.5" } @@ -59,7 +60,21 @@ lazy val root = (project in file(".")) publish / skip := true, crossScalaVersions := Nil ) - .aggregate(api, scalaApi, scalaCatsEffect3, core, dropwizardCommon, jmx, jmxAvast, graphite, filter, formatting, statsd, grpc) + .aggregate( + api, + scalaApi, + scalaCatsEffect2, + scalaCatsEffect3, + core, + dropwizardCommon, + jmx, + jmxAvast, + graphite, + filter, + formatting, + statsd, + grpc + ) lazy val api = (project in file("api")).settings( commonSettings, @@ -75,7 +90,19 @@ lazy val scalaApi = (project in file("scala-api")) ) .dependsOn(api, jmx % "test") -lazy val scalaCatsEffect3 = (project in file("scala-effect-api")) +lazy val scalaCatsEffect2 = (project in file("scala-effect-api-cats2")) + .settings( + commonSettings, + scalaSettings, + scalacOptions += "-language:higherKinds", + name := "metrics-cats-effect-2", + libraryDependencies ++= Seq( + "org.typelevel" %% "cats-effect" % Versions.catsEffect2 + ) + ) + .dependsOn(scalaApi, jmx % "test") + +lazy val scalaCatsEffect3 = (project in file("scala-effect-api-cats3")) .settings( commonSettings, scalaSettings, diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Counter.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Counter.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Counter.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Counter.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Counting.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Counting.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Counting.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Counting.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Gauge.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Gauge.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Gauge.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Gauge.scala diff --git a/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/GaugeFactory.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/GaugeFactory.scala new file mode 100644 index 0000000..28beb93 --- /dev/null +++ b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/GaugeFactory.scala @@ -0,0 +1,7 @@ +package com.avast.metrics.scalaeffectapi + +trait GaugeFactory[F[_]] { + def settableLong(name: String, replaceExisting: Boolean = false): SettableGauge[F, Long] + def settableDouble(name: String, replaceExisting: Boolean = false): SettableGauge[F, Double] + def generic[T](name: String, replaceExisting: Boolean = false)(gauge: () => T): Gauge[F, T] +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Histogram.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Histogram.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Histogram.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Histogram.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Meter.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Meter.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Meter.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Meter.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Monitor.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Monitor.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Monitor.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Monitor.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/SettableGauge.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/SettableGauge.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/SettableGauge.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/SettableGauge.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Timer.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Timer.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/Timer.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/Timer.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/TimerPair.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/TimerPair.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/TimerPair.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/TimerPair.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/CounterImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/CounterImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/CounterImpl.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/CounterImpl.scala diff --git a/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/GaugeFactoryImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/GaugeFactoryImpl.scala new file mode 100644 index 0000000..0494d10 --- /dev/null +++ b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/GaugeFactoryImpl.scala @@ -0,0 +1,53 @@ +package com.avast.metrics.scalaeffectapi.impl + +import cats.effect.Sync +import com.avast.metrics.scalaapi.{Monitor => SMonitor} +import com.avast.metrics.scalaeffectapi.{Gauge, GaugeFactory, SettableGauge} + +import java.util.concurrent.atomic.{AtomicLong, AtomicReference} + +private class GaugeFactoryImpl[F[_]: Sync](monitor: SMonitor) extends GaugeFactory[F] { + + override def settableLong(gaugeName: String, replaceExisting: Boolean = false): SettableGauge[F, Long] = new SettableGauge[F, Long] { + private[this] val valueRef = new AtomicLong(0L) + private[this] val gauge = monitor.gauge(gaugeName, replaceExisting)(valueRef.get) + + override def set(value: Long): F[Unit] = Sync[F].delay(valueRef.set(value)) + + override def name: String = gauge.name + + override def value: F[Long] = Sync[F].delay(gauge.value) + + override def update(f: Long => Long): F[Long] = Sync[F].delay(valueRef.updateAndGet(f(_))) + + override def inc: F[Long] = Sync[F].delay(valueRef.incrementAndGet()) + + override def dec: F[Long] = Sync[F].delay(valueRef.decrementAndGet()) + } + + override def settableDouble(gaugeName: String, replaceExisting: Boolean = false): SettableGauge[F, Double] = + new SettableGauge[F, Double] { + private[this] val valueRef = new AtomicReference(0.0) + private[this] val gauge = monitor.gauge(gaugeName, replaceExisting)(valueRef.get) + + override def set(value: Double): F[Unit] = Sync[F].delay(valueRef.set(value)) + + override def name: String = gauge.name + + override def value: F[Double] = Sync[F].delay(gauge.value) + + override def update(f: Double => Double): F[Double] = Sync[F].delay(valueRef.updateAndGet(f(_))) + + override def inc: F[Double] = Sync[F].delay(valueRef.accumulateAndGet(1, (a, b) => a + b)) + + override def dec: F[Double] = Sync[F].delay(valueRef.accumulateAndGet(1, (a, b) => a - b)) + } + + override def generic[T](gaugeName: String, replaceExisting: Boolean = false)(gauge: () => T): Gauge[F, T] = new Gauge[F, T] { + private[this] val sGauge = monitor.gauge(gaugeName, replaceExisting)(gauge) + + override def value: F[T] = Sync[F].delay(sGauge.value) + + override def name: String = sGauge.name + } +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/HistogramImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/HistogramImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/HistogramImpl.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/HistogramImpl.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MeterImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MeterImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MeterImpl.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MeterImpl.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MonitorImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MonitorImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MonitorImpl.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MonitorImpl.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerImpl.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerImpl.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerPairImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerPairImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerPairImpl.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerPairImpl.scala diff --git a/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyGaugeFactory.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyGaugeFactory.scala new file mode 100644 index 0000000..e78d1ed --- /dev/null +++ b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyGaugeFactory.scala @@ -0,0 +1,9 @@ +package com.avast.metrics.scalaeffectapi.perkey + +import com.avast.metrics.scalaeffectapi.{Gauge, SettableGauge} + +trait PerKeyGaugeFactory[F[_]] { + def settableLong(baseName: String, replaceExisting: Boolean = false): PerKeyMetric[SettableGauge[F, Long]] + def settableDouble(baseName: String, replaceExisting: Boolean = false): PerKeyMetric[SettableGauge[F, Double]] + def generic[T](baseName: String, replaceExisting: Boolean = false)(gauge: () => T): PerKeyMetric[Gauge[F, T]] +} diff --git a/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMetric.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMetric.scala new file mode 100644 index 0000000..43e8185 --- /dev/null +++ b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMetric.scala @@ -0,0 +1,5 @@ +package com.avast.metrics.scalaeffectapi.perkey + +trait PerKeyMetric[M] { + def forKey(str: String): M +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMonitor.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMonitor.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMonitor.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMonitor.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyOps.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyOps.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyOps.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyOps.scala diff --git a/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyGaugeFactoryImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyGaugeFactoryImpl.scala new file mode 100644 index 0000000..25aa909 --- /dev/null +++ b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyGaugeFactoryImpl.scala @@ -0,0 +1,28 @@ +package com.avast.metrics.scalaeffectapi.perkey.impl + +import com.avast.metrics.scalaeffectapi.{Gauge, Monitor, SettableGauge} +import com.avast.metrics.scalaeffectapi.perkey.{PerKeyGaugeFactory, PerKeyMetric} + +import scala.collection.concurrent.TrieMap + +class PerKeyGaugeFactoryImpl[F[_]](monitor: Monitor[F]) extends PerKeyGaugeFactory[F] { + private def emptyMap[M] = TrieMap.empty[String, M] + + override def settableLong(baseName: String, replaceExisting: Boolean = false): PerKeyMetric[SettableGauge[F, Long]] = { + val instanceBuilder = monitor.named(baseName) + new PerKeyMetricImpl[SettableGauge[F, Long]](emptyMap[SettableGauge[F, Long]], instanceBuilder.gauge.settableLong(_, replaceExisting)) + } + + override def settableDouble(baseName: String, replaceExisting: Boolean = false): PerKeyMetric[SettableGauge[F, Double]] = { + val instanceBuilder = monitor.named(baseName) + new PerKeyMetricImpl[SettableGauge[F, Double]]( + emptyMap[SettableGauge[F, Double]], + instanceBuilder.gauge.settableDouble(_, replaceExisting) + ) + } + + override def generic[T](baseName: String, replaceExisting: Boolean = false)(gauge: () => T): PerKeyMetric[Gauge[F, T]] = { + val instanceBuilder = monitor.named(baseName) + new PerKeyMetricImpl[Gauge[F, T]](emptyMap[Gauge[F, T]], instanceBuilder.gauge.generic(_, replaceExisting)(gauge)) + } +} diff --git a/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMetricImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMetricImpl.scala new file mode 100644 index 0000000..0e48b04 --- /dev/null +++ b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMetricImpl.scala @@ -0,0 +1,11 @@ +package com.avast.metrics.scalaeffectapi.perkey.impl + +import com.avast.metrics.scalaeffectapi.perkey.PerKeyMetric + +import scala.collection.concurrent.{Map => CMap} + +private[perkey] class PerKeyMetricImpl[A](map: CMap[String, A], metricBuilder: String => A) extends PerKeyMetric[A] { + override def forKey(str: String): A = { + map.getOrElseUpdate(str, metricBuilder(str)) + } +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMonitorImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMonitorImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMonitorImpl.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMonitorImpl.scala diff --git a/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyOpsImpl.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyOpsImpl.scala new file mode 100644 index 0000000..b6abc09 --- /dev/null +++ b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyOpsImpl.scala @@ -0,0 +1,40 @@ +package com.avast.metrics.scalaeffectapi.perkey.impl + +import com.avast.metrics.scalaeffectapi._ +import com.avast.metrics.scalaeffectapi.perkey.{PerKeyGaugeFactory, PerKeyMetric, PerKeyOps} + +import scala.collection.concurrent.TrieMap + +private[perkey] class PerKeyOpsImpl[F[_]](monitor: Monitor[F]) extends PerKeyOps[F] { + private def emptyMap[M] = TrieMap.empty[String, M] + + override def meter(baseName: String): PerKeyMetric[Meter[F]] = { + val instanceBuilder = monitor.named(baseName) + new PerKeyMetricImpl[Meter[F]](emptyMap[Meter[F]], instanceBuilder.meter) + } + + override def counter(baseName: String): PerKeyMetric[Counter[F]] = { + val instanceBuilder = monitor.named(baseName) + new PerKeyMetricImpl[Counter[F]](emptyMap, instanceBuilder.counter) + } + + override def timer(baseName: String): PerKeyMetric[Timer[F]] = { + val instanceBuilder = monitor.named(baseName) + new PerKeyMetricImpl[Timer[F]](emptyMap, instanceBuilder.timer) + } + + override def timerPair(baseName: String): PerKeyMetric[TimerPair[F]] = { + val instanceBuilder = monitor.named(baseName) + new PerKeyMetricImpl[TimerPair[F]](emptyMap, instanceBuilder.timerPair) + } + + override def histogram(baseName: String): PerKeyMetric[Histogram[F]] = { + val instanceBuilder = monitor.named(baseName) + new PerKeyMetricImpl[Histogram[F]](emptyMap, instanceBuilder.histogram) + } + + override def gauge: PerKeyGaugeFactory[F] = { + new PerKeyGaugeFactoryImpl[F](monitor) + } + +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/package.scala b/scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/package.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/package.scala rename to scala-effect-api-cats2/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/package.scala diff --git a/scala-effect-api-cats2/src/test/scala/com/avast/metrics/examples/EffectMonitor.scala b/scala-effect-api-cats2/src/test/scala/com/avast/metrics/examples/EffectMonitor.scala new file mode 100644 index 0000000..2f48d94 --- /dev/null +++ b/scala-effect-api-cats2/src/test/scala/com/avast/metrics/examples/EffectMonitor.scala @@ -0,0 +1,26 @@ +package com.avast.metrics.examples + +import cats.effect.{ExitCode, IO, IOApp, Timer} +import com.avast.metrics.dropwizard.JmxMetricsMonitor +import com.avast.metrics.scalaeffectapi.Monitor + +import java.util.concurrent.TimeUnit +import scala.concurrent.duration.FiniteDuration + +object EffectMonitor extends IOApp { + + val jmxMetricsMonitor = new JmxMetricsMonitor("com.avast.some.app") + + override def run(args: List[String]): IO[ExitCode] = { + val monitor = Monitor.wrapJava[IO](jmxMetricsMonitor) + val counter = monitor.counter("counter") + val timer = monitor.timer("timer") + + for { + _ <- counter.inc + _ <- timer.time { + Timer[IO](IO.timer(executionContext)).sleep(FiniteDuration(500, TimeUnit.MILLISECONDS)) + } + } yield ExitCode.Success + } +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Counter.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Counter.scala new file mode 100644 index 0000000..62a6cc0 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Counter.scala @@ -0,0 +1,10 @@ +package com.avast.metrics.scalaeffectapi + +import com.avast.metrics.scalaapi.Metric + +trait Counter[F[_]] extends Counting[F] with Metric { + def inc: F[Unit] + def inc(n: Long): F[Unit] + def dec: F[Unit] + def dec(n: Int): F[Unit] +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Counting.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Counting.scala new file mode 100644 index 0000000..4ee93c8 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Counting.scala @@ -0,0 +1,5 @@ +package com.avast.metrics.scalaeffectapi + +trait Counting[F[_]] { + def count: F[Long] +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Gauge.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Gauge.scala new file mode 100644 index 0000000..65fbbd3 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Gauge.scala @@ -0,0 +1,7 @@ +package com.avast.metrics.scalaeffectapi + +import com.avast.metrics.scalaapi.Metric + +trait Gauge[F[_], T] extends Metric { + def value: F[T] +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/GaugeFactory.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/GaugeFactory.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/GaugeFactory.scala rename to scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/GaugeFactory.scala diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Histogram.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Histogram.scala new file mode 100644 index 0000000..1b92e9d --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Histogram.scala @@ -0,0 +1,7 @@ +package com.avast.metrics.scalaeffectapi + +import com.avast.metrics.scalaapi.Metric + +trait Histogram[F[_]] extends Metric { + def update(value: Long): F[Unit] +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Meter.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Meter.scala new file mode 100644 index 0000000..8bfa51b --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Meter.scala @@ -0,0 +1,8 @@ +package com.avast.metrics.scalaeffectapi + +import com.avast.metrics.scalaapi.Metric + +trait Meter[F[_]] extends Counting[F] with Metric { + def mark: F[Unit] + def mark(n: Long): F[Unit] +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Monitor.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Monitor.scala new file mode 100644 index 0000000..9308f05 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Monitor.scala @@ -0,0 +1,36 @@ +package com.avast.metrics.scalaeffectapi + +import cats.effect.Sync +import com.avast.metrics.api.{Monitor => JMonitor, Naming} +import com.avast.metrics.scalaapi.{Monitor => SMonitor} +import com.avast.metrics.test.NoOpMonitor + +trait Monitor[F[_]] extends AutoCloseable { + def named(name: String): Monitor[F] + def named(name1: String, name2: String, restOfNames: String*): Monitor[F] + def getName: String + def meter(name: String): Meter[F] + def counter(name: String): Counter[F] + def timer(name: String): Timer[F] + def timerPair(name: String): TimerPair[F] + def histogram(name: String): Histogram[F] + + def gauge: GaugeFactory[F] + + def asJava: JMonitor + def asPlainScala: SMonitor +} + +object Monitor { + + def wrapJava[F[_]: Sync](monitor: JMonitor): Monitor[F] = wrapJava(monitor, Naming.defaultNaming()) + def wrapJava[F[_]: Sync](monitor: JMonitor, naming: Naming): Monitor[F] = wrap(SMonitor(monitor), naming) + + def wrap[F[_]: Sync](monitor: SMonitor): Monitor[F] = wrap(monitor, Naming.defaultNaming()) + def wrap[F[_]: Sync](monitor: SMonitor, naming: Naming): Monitor[F] = new impl.MonitorImpl(monitor, naming) + + def noOp[F[_]: Sync](): Monitor[F] = { + wrapJava(NoOpMonitor.INSTANCE) + } + +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/SettableGauge.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/SettableGauge.scala new file mode 100644 index 0000000..a7e1d6d --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/SettableGauge.scala @@ -0,0 +1,8 @@ +package com.avast.metrics.scalaeffectapi + +trait SettableGauge[F[_], T] extends Gauge[F, T] { + def set(value: T): F[Unit] + def update(f: T => T): F[T] + def inc: F[T] + def dec: F[T] +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Timer.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Timer.scala new file mode 100644 index 0000000..f905299 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/Timer.scala @@ -0,0 +1,15 @@ +package com.avast.metrics.scalaeffectapi + +import java.time.{Duration => JDuration} +import scala.concurrent.duration.Duration + +trait Timer[F[_]] { + trait TimeContext extends AutoCloseable { + def stop: F[Duration] + } + + def start: F[TimeContext] + def update(duration: JDuration): F[Unit] + def update(duration: Duration): F[Unit] + def time[A](block: F[A]): F[A] +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/TimerPair.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/TimerPair.scala new file mode 100644 index 0000000..5130fa8 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/TimerPair.scala @@ -0,0 +1,19 @@ +package com.avast.metrics.scalaeffectapi + +import java.time.{Duration => JDuration} +import scala.concurrent.duration.Duration + +trait TimerPair[F[_]] { + trait TimerPairContext extends AutoCloseable { + def stop: F[Duration] + def stopFailure: F[Duration] + } + + def start: F[TimerPairContext] + def update(duration: JDuration): F[Unit] + def updateFailure(duration: JDuration): F[Unit] + def update(duration: Duration): F[Unit] + def updateFailure(duration: Duration): F[Unit] + def time[T](action: F[T]): F[T] + def time[T](action: F[T])(successCheck: T => Boolean): F[T] +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/CounterImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/CounterImpl.scala new file mode 100644 index 0000000..fedc7be --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/CounterImpl.scala @@ -0,0 +1,19 @@ +package com.avast.metrics.scalaeffectapi.impl + +import cats.effect.Sync +import com.avast.metrics.scalaapi.{Counter => SCounter} +import com.avast.metrics.scalaeffectapi.Counter + +private class CounterImpl[F[_]: Sync](inner: SCounter) extends Counter[F] { + override def inc: F[Unit] = Sync[F].delay(inner.inc()) + + override def inc(n: Long): F[Unit] = Sync[F].delay(inner.inc(n)) + + override def dec: F[Unit] = Sync[F].delay(inner.dec()) + + override def dec(n: Int): F[Unit] = Sync[F].delay(inner.dec(n)) + + override def count: F[Long] = Sync[F].delay(inner.count) + + override def name: String = inner.name +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/GaugeFactoryImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/GaugeFactoryImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/impl/GaugeFactoryImpl.scala rename to scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/GaugeFactoryImpl.scala diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/HistogramImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/HistogramImpl.scala new file mode 100644 index 0000000..d481436 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/HistogramImpl.scala @@ -0,0 +1,11 @@ +package com.avast.metrics.scalaeffectapi.impl + +import cats.effect.Sync +import com.avast.metrics.scalaapi.{Histogram => SHistogram} +import com.avast.metrics.scalaeffectapi.Histogram + +private class HistogramImpl[F[_]: Sync](histogram: SHistogram) extends Histogram[F] { + override def update(value: Long): F[Unit] = Sync[F].delay(histogram.update(value)) + + override def name: String = histogram.name +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MeterImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MeterImpl.scala new file mode 100644 index 0000000..121c797 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MeterImpl.scala @@ -0,0 +1,15 @@ +package com.avast.metrics.scalaeffectapi.impl + +import cats.effect.Sync +import com.avast.metrics.scalaapi.{Meter => SMeter} +import com.avast.metrics.scalaeffectapi.Meter + +private class MeterImpl[F[_]: Sync](meter: SMeter) extends Meter[F] { + override def mark: F[Unit] = Sync[F].delay(meter.mark()) + + override def mark(n: Long): F[Unit] = Sync[F].delay(meter.mark(n)) + + override def count: F[Long] = Sync[F].delay(meter.count) + + override def name: String = meter.name +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MonitorImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MonitorImpl.scala new file mode 100644 index 0000000..780af02 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/MonitorImpl.scala @@ -0,0 +1,37 @@ +package com.avast.metrics.scalaeffectapi.impl + +import cats.effect.Sync +import com.avast.metrics.api.{Monitor => JMonitor, Naming} + +import com.avast.metrics.scalaapi.{Monitor => SMonitor} +import com.avast.metrics.scalaeffectapi._ + +private[scalaeffectapi] class MonitorImpl[F[_]: Sync](monitor: SMonitor, naming: Naming) extends Monitor[F] { + + override def named(name: String): Monitor[F] = new MonitorImpl(monitor.named(name), naming) + + override def named(name: String, name2: String, names: String*): Monitor[F] = + new MonitorImpl(monitor.named(name, name2, names: _*), naming) + + override def getName: String = monitor.getName + + override def meter(name: String): Meter[F] = new MeterImpl[F](monitor.meter(name)) + + override def counter(name: String): Counter[F] = new CounterImpl(monitor.counter(name)) + + override def timer(name: String): Timer[F] = new TimerImpl(monitor.timer(name)) + + override def timerPair(name: String): TimerPair[F] = + new TimerPairImpl(timer(naming.successTimerName(name)), timer(naming.failureTimerName(name))) + + override def histogram(name: String): Histogram[F] = new HistogramImpl(monitor.histogram(name)) + + override def gauge: GaugeFactory[F] = new GaugeFactoryImpl(monitor) + + override def close(): Unit = monitor.close() + + override def asJava: JMonitor = monitor.asJava + + override def asPlainScala: SMonitor = monitor + +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerImpl.scala new file mode 100644 index 0000000..e3afd4e --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerImpl.scala @@ -0,0 +1,34 @@ +package com.avast.metrics.scalaeffectapi.impl + +import cats.effect.Sync +import cats.syntax.flatMap._ +import cats.syntax.functor._ + +import java.time.{Duration => JDuration} +import com.avast.metrics.scalaapi.{Timer => STimer} +import com.avast.metrics.scalaeffectapi.Timer + +import scala.concurrent.duration.Duration + +private class TimerImpl[F[_]: Sync](inner: STimer) extends Timer[F] { + override def start: F[TimeContext] = Sync[F].delay { + val context = inner.start() + new TimeContext { + override def stop: F[Duration] = Sync[F].delay(Duration.fromNanos(context.stopAndGetTime())) + + override def close(): Unit = context.close() + } + } + + override def update(duration: JDuration): F[Unit] = Sync[F].delay(inner.update(duration)) + + override def update(duration: Duration): F[Unit] = update(JDuration.ofNanos(duration.toNanos)) + + override def time[A](block: F[A]): F[A] = { + for { + context <- start + result <- block + _ <- context.stop + } yield result + } +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerPairImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerPairImpl.scala new file mode 100644 index 0000000..bf56e7a --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/impl/TimerPairImpl.scala @@ -0,0 +1,52 @@ +package com.avast.metrics.scalaeffectapi.impl + +import cats.effect.Sync +import cats.syntax.applicativeError._ +import cats.syntax.flatMap._ +import cats.syntax.functor._ +import com.avast.metrics.scalaeffectapi.{Timer, TimerPair} + +import java.time.{Duration => JDuration} +import scala.concurrent.duration.Duration + +private class TimerPairImpl[F[_]: Sync](success: Timer[F], failure: Timer[F]) extends TimerPair[F] { + override def start: F[TimerPairContext] = { + for { + succCtx <- success.start + failCtx <- failure.start + } yield new TimerPairContext { + override def stop: F[Duration] = succCtx.stop + + override def stopFailure: F[Duration] = failCtx.stop + + override def close(): Unit = { + succCtx.close() + failCtx.close() + } + } + } + + override def update(duration: JDuration): F[Unit] = success.update(duration) + + override def updateFailure(duration: JDuration): F[Unit] = failure.update(duration) + + override def update(duration: Duration): F[Unit] = success.update(duration) + + override def updateFailure(duration: Duration): F[Unit] = failure.update(duration) + + override def time[T](action: F[T]): F[T] = { + for { + ctx <- start + result <- action.onError { case _ => ctx.stopFailure.void } + _ <- ctx.stop + } yield result + } + + override def time[T](action: F[T])(successCheck: T => Boolean): F[T] = { + for { + ctx <- start + result <- action.onError { case _ => ctx.stopFailure.void } + _ <- if (successCheck(result)) ctx.stop else ctx.stopFailure + } yield result + } +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyGaugeFactory.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyGaugeFactory.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyGaugeFactory.scala rename to scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyGaugeFactory.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyHelper.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyHelper.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyHelper.scala rename to scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyHelper.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMetric.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMetric.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMetric.scala rename to scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMetric.scala diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMonitor.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMonitor.scala new file mode 100644 index 0000000..8cace67 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyMonitor.scala @@ -0,0 +1,13 @@ +package com.avast.metrics.scalaeffectapi.perkey + +import com.avast.metrics.scalaeffectapi.Monitor +import com.avast.metrics.scalaeffectapi.perkey.impl.{PerKeyMonitorImpl, PerKeyOpsImpl} + +trait PerKeyMonitor[F[_]] extends Monitor[F] { + def perKey: PerKeyOps[F] +} + +object PerKeyMonitor { + def apply[F[_]](monitor: Monitor[F]): PerKeyMonitor[F] = + new PerKeyMonitorImpl(monitor, new PerKeyOpsImpl(monitor)) +} diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyOps.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyOps.scala new file mode 100644 index 0000000..7bd4db2 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/PerKeyOps.scala @@ -0,0 +1,12 @@ +package com.avast.metrics.scalaeffectapi.perkey + +import com.avast.metrics.scalaeffectapi._ + +trait PerKeyOps[F[_]] { + def meter(name: String): PerKeyMetric[Meter[F]] + def counter(name: String): PerKeyMetric[Counter[F]] + def timer(name: String): PerKeyMetric[Timer[F]] + def timerPair(name: String): PerKeyMetric[TimerPair[F]] + def histogram(name: String): PerKeyMetric[Histogram[F]] + def gauge: PerKeyGaugeFactory[F] +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyGaugeFactoryImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyGaugeFactoryImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyGaugeFactoryImpl.scala rename to scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyGaugeFactoryImpl.scala diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMetricImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMetricImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMetricImpl.scala rename to scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMetricImpl.scala diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMonitorImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMonitorImpl.scala new file mode 100644 index 0000000..c2fe724 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyMonitorImpl.scala @@ -0,0 +1,36 @@ +package com.avast.metrics.scalaeffectapi.perkey.impl + +import com.avast.metrics.{api, scalaapi} +import com.avast.metrics.scalaeffectapi._ +import com.avast.metrics.scalaeffectapi.perkey.{PerKeyMonitor, PerKeyOps} + +/** Adds `perKey` method, otherwise just forward method calls to [[com.avast.metrics.scalaeffectapi.Monitor]] + */ +private[perkey] class PerKeyMonitorImpl[F[_]](monitor: Monitor[F], perKeyOps: PerKeyOps[F]) extends PerKeyMonitor[F] { + override def perKey: PerKeyOps[F] = perKeyOps + + override def named(name: String): Monitor[F] = monitor.named(name) + + override def named(name: String, name2: String, names: String*): Monitor[F] = monitor.named(name, name2, names: _*) + + override def getName: String = monitor.getName + + override def meter(name: String): Meter[F] = monitor.meter(name) + + override def counter(name: String): Counter[F] = monitor.counter(name) + + override def timer(name: String): Timer[F] = monitor.timer(name) + + override def timerPair(name: String): TimerPair[F] = monitor.timerPair(name) + + override def histogram(name: String): Histogram[F] = monitor.histogram(name) + + override def gauge: GaugeFactory[F] = monitor.gauge + + override def close(): Unit = monitor.close() + + override def asJava: api.Monitor = monitor.asJava + + override def asPlainScala: scalaapi.Monitor = monitor.asPlainScala + +} diff --git a/scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyOpsImpl.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyOpsImpl.scala similarity index 100% rename from scala-effect-api/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyOpsImpl.scala rename to scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/impl/PerKeyOpsImpl.scala diff --git a/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/package.scala b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/package.scala new file mode 100644 index 0000000..6037e83 --- /dev/null +++ b/scala-effect-api-cats3/src/main/scala/com/avast/metrics/scalaeffectapi/perkey/package.scala @@ -0,0 +1,7 @@ +package com.avast.metrics.scalaeffectapi + +package object perkey { + implicit class MonitorToPerKeyOps[F[_]](monitor: Monitor[F]) { + def perKey: PerKeyOps[F] = PerKeyMonitor(monitor).perKey + } +} diff --git a/scala-effect-api/src/test/scala/com/avast/metrics/examples/EffectMonitor.scala b/scala-effect-api-cats3/src/test/scala/com/avast/metrics/examples/EffectMonitor.scala similarity index 100% rename from scala-effect-api/src/test/scala/com/avast/metrics/examples/EffectMonitor.scala rename to scala-effect-api-cats3/src/test/scala/com/avast/metrics/examples/EffectMonitor.scala