diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5caea72f..606204cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.12.15, 3.2.2, 2.13.8] + scala: [2.12.18, 3.3.1, 2.13.12] java: [temurin@17] project: [rootJVM] runs-on: ${{ matrix.os }} @@ -102,7 +102,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.8] + scala: [2.13.12] java: [temurin@17] runs-on: ${{ matrix.os }} steps: @@ -139,32 +139,32 @@ jobs: ~/Library/Caches/Coursier/v1 key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} - - name: Download target directories (2.12.15, rootJVM) + - name: Download target directories (2.12.18, rootJVM) uses: actions/download-artifact@v3 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.15-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.18-rootJVM - - name: Inflate target directories (2.12.15, rootJVM) + - name: Inflate target directories (2.12.18, rootJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3.2.2, rootJVM) + - name: Download target directories (3.3.1, rootJVM) uses: actions/download-artifact@v3 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.2-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-3.3.1-rootJVM - - name: Inflate target directories (3.2.2, rootJVM) + - name: Inflate target directories (3.3.1, rootJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.8, rootJVM) + - name: Download target directories (2.13.12, rootJVM) uses: actions/download-artifact@v3 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.12-rootJVM - - name: Inflate target directories (2.13.8, rootJVM) + - name: Inflate target directories (2.13.12, rootJVM) run: | tar xf targets.tar rm targets.tar @@ -188,7 +188,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.8] + scala: [2.13.12] java: [temurin@17] runs-on: ${{ matrix.os }} steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d3947bf..f0e1eff9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This file summarizes **notable** changes for each release, but does not describe Initial Stable Release of Epimetheus. This exposes a set of tools for safely abstacting over Prometheus metrics. We start with the epic of epimetheus to walk through all the tools you need to know, where we expose the core mechanics. -A central collection of the shared mutable state of the metrics being aggregated called a `CollectorRegistry`. +A central collection of the shared mutable state of the metrics being aggregated called a `PrometheusRegistry`. There are 4 metric types Counter, Gauge, Histogram and Summary. diff --git a/build.sbt b/build.sbt index df6821c9..5005088d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,3 +1,5 @@ +import com.typesafe.tools.mima.core._ + ThisBuild / tlBaseVersion := "0.5" // your current series x.y ThisBuild / organization := "io.chrisdavenport" @@ -12,9 +14,9 @@ ThisBuild / developers := List( ThisBuild / tlSonatypeUseLegacyHost := true ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17")) -val Scala213 = "2.13.8" +val Scala213 = "2.13.12" -ThisBuild / crossScalaVersions := Seq("2.12.15", "3.2.2", Scala213) +ThisBuild / crossScalaVersions := Seq("2.12.18", "3.3.1", Scala213) ThisBuild / scalaVersion := crossScalaVersions.value.last lazy val `epimetheus` = tlCrossRootProject @@ -32,7 +34,7 @@ lazy val site = project.in(file("site")) .dependsOn(core) -val prometheusV = "0.16.0" +val prometheusV = "1.1.0" val catsV = "2.9.0" val catsEffectV = "3.4.8" val shapelessV = "2.3.9" @@ -43,6 +45,8 @@ val munitCatsEffectV = "1.0.7" // General Settings lazy val commonSettings = Seq( + mimaBinaryIssueFilters := List({(_: Problem) => false}), // TODO: remove this once switched to next major version + javacOptions ++= Seq("--release", "8"), scalacOptions --= List("-source", "future", "-Xfatal-warnings"), @@ -51,14 +55,15 @@ lazy val commonSettings = Seq( Compile / doc / scalacOptions ++= Opts.doc.title("epimetheus"), libraryDependencies ++= Seq( - "io.prometheus" % "simpleclient" % prometheusV, - "io.prometheus" % "simpleclient_common" % prometheusV, - "io.prometheus" % "simpleclient_hotspot" % prometheusV, + "io.prometheus" % "prometheus-metrics-core" % prometheusV, + "io.prometheus" % "prometheus-metrics-instrumentation-jvm" % prometheusV, + "io.prometheus" % "prometheus-metrics-exposition-formats" % prometheusV, - "org.typelevel" %% "cats-core" % catsV, - "org.typelevel" %% "cats-effect" % catsEffectV, + "org.typelevel" %% "cats-core" % catsV, + "org.typelevel" %% "cats-effect" % catsEffectV, - "org.typelevel" %%% "munit-cats-effect-3" % munitCatsEffectV % Test + "org.typelevel" %%% "munit-cats-effect-3" % munitCatsEffectV % Test, + "org.scala-lang.modules" %%% "scala-collection-compat" % "2.11.0" % Test ), libraryDependencies ++= PartialFunction.condOpt(CrossVersion.partialVersion(scalaVersion.value)) { case Some((2, _)) => Seq( diff --git a/core/src/main/scala-2/io/chrisdavenport/epimetheus/Summary.scala b/core/src/main/scala-2/io/chrisdavenport/epimetheus/Summary.scala index c8207be0..9f510a0d 100644 --- a/core/src/main/scala-2/io/chrisdavenport/epimetheus/Summary.scala +++ b/core/src/main/scala-2/io/chrisdavenport/epimetheus/Summary.scala @@ -1,7 +1,7 @@ package io.chrisdavenport.epimetheus import cats._ -import io.prometheus.client.{Summary => JSummary} +import io.prometheus.metrics.core.metrics.{Summary => JSummary} import scala.language.experimental.macros import scala.reflect.macros.whitebox diff --git a/core/src/main/scala-3/io/chrisdavenport/epimetheus/Summary.scala b/core/src/main/scala-3/io/chrisdavenport/epimetheus/Summary.scala index 6b6f3f56..9896c364 100644 --- a/core/src/main/scala-3/io/chrisdavenport/epimetheus/Summary.scala +++ b/core/src/main/scala-3/io/chrisdavenport/epimetheus/Summary.scala @@ -1,7 +1,7 @@ package io.chrisdavenport.epimetheus import cats.* -import io.prometheus.client.{Summary => JSummary} +import io.prometheus.metrics.core.metrics.{Summary => JSummary} import scala.quoted.* diff --git a/core/src/main/scala/io/chrisdavenport/epimetheus/Collector.scala b/core/src/main/scala/io/chrisdavenport/epimetheus/Collector.scala index 4047f88e..1afc5e7e 100644 --- a/core/src/main/scala/io/chrisdavenport/epimetheus/Collector.scala +++ b/core/src/main/scala/io/chrisdavenport/epimetheus/Collector.scala @@ -1,17 +1,16 @@ package io.chrisdavenport.epimetheus -import cats.implicits._ import cats.effect._ -import io.prometheus.client.{Collector => JCollector} -import io.prometheus.client.hotspot._ +import io.prometheus.metrics.model.registry.{Collector => JCollector} +import io.prometheus.metrics.instrumentation.jvm._ /** * A [[Collector]] Represents a Metric or Group of Metrics that - * can be registered with a [[CollectorRegistry]]. - * + * can be registered with a [[PrometheusRegistry]]. + * * This is generally used for wrapping and bringing in Collectors * as defined for Java Components - * + * */ final class Collector private (private val underlying: JCollector) object Collector { @@ -24,54 +23,31 @@ object Collector { /** * Register all defaults with the supplied registry. */ - def registerDefaults[F[_]: Sync](cr: CollectorRegistry[F]): F[Unit] = - for { - bpe <- BufferPoolsExports[F] - _ <- cr.register(bpe) - cle <- ClassLoadingExports - _ <- cr.register(cle) - gce <- GarbageCollectorExports - _ <- cr.register(gce) - mae <- MemoryAllocationExports - _ <- cr.register(mae) - mpe <- MemoryPoolsExports - _ <- cr.register(mpe) - se <- StandardExports - _ <- cr.register(se) - te <- ThreadExports - _ <- cr.register(te) - vie <- VersionInfoExports - _ <- cr.register(vie) - } yield () + def registerDefaults[F[_]: Sync](pr: PrometheusRegistry[F]): F[Unit] = + registerJvmMetrics[F](pr) - def defaultCollectorRegisterDefaults[F[_]: Sync]: F[Unit] = Sync[F].delay{ - DefaultExports.initialize - } + // registers all jvm metrics + def registerJvmMetrics[F[_] : Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(JvmMetrics.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) - def BufferPoolsExports[F[_]: Sync]: F[Collector] = - Sync[F].delay(new BufferPoolsExports()) - .map(Unsafe.fromJava(_)) - def ClassLoadingExports[F[_]: Sync]: F[Collector] = - Sync[F].delay(new ClassLoadingExports()) - .map(Unsafe.fromJava(_)) - def GarbageCollectorExports[F[_]: Sync]: F[Collector] = - Sync[F].delay(new GarbageCollectorExports()) - .map(Unsafe.fromJava(_)) - def MemoryAllocationExports[F[_]: Sync]: F[Collector] = - Sync[F].delay(new MemoryAllocationExports()) - .map(Unsafe.fromJava(_)) - def MemoryPoolsExports[F[_]: Sync]: F[Collector] = - Sync[F].delay(new MemoryPoolsExports()) - .map(Unsafe.fromJava(_)) - def StandardExports[F[_]: Sync]: F[Collector] = - Sync[F].delay(new StandardExports()) - .map(Unsafe.fromJava(_)) - def ThreadExports[F[_]: Sync]: F[Collector] = - Sync[F].delay(new ThreadExports()) - .map(Unsafe.fromJava(_)) - def VersionInfoExports[F[_]: Sync]: F[Collector] = - Sync[F].delay(new VersionInfoExports()) - .map(Unsafe.fromJava(_)) + def registerJvmBufferPoolMetrics[F[_]: Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(JvmBufferPoolMetrics.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) + def registerJvmClassLoadingMetrics[F[_]: Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(JvmClassLoadingMetrics.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) + def registerJvmCompilationMetrics[F[_]: Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(JvmCompilationMetrics.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) + def registerJvmGarbageCollectorMetrics[F[_]: Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(JvmGarbageCollectorMetrics.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) + def registerJvmMemoryMetrics[F[_] : Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(JvmMemoryMetrics.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) + def registerJvmMemoryPoolAllocationMetrics[F[_]: Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(JvmMemoryPoolAllocationMetrics.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) + def registerProcessMetrics[F[_] : Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(ProcessMetrics.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) + def registerJvmRuntimeInfoMetric[F[_] : Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(JvmRuntimeInfoMetric.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) + def registerJvmThreadsMetrics[F[_]: Sync](pr: PrometheusRegistry[F]): F[Unit] = + Sync[F].delay(JvmThreadsMetrics.builder().register(PrometheusRegistry.Unsafe.asJava(pr))) } object Unsafe { diff --git a/core/src/main/scala/io/chrisdavenport/epimetheus/CollectorRegistry.scala b/core/src/main/scala/io/chrisdavenport/epimetheus/CollectorRegistry.scala deleted file mode 100644 index e00ae3cf..00000000 --- a/core/src/main/scala/io/chrisdavenport/epimetheus/CollectorRegistry.scala +++ /dev/null @@ -1,97 +0,0 @@ -package io.chrisdavenport.epimetheus - -import cats.effect._ -import cats.implicits._ -import io.prometheus.client.{CollectorRegistry => JCollectorRegistry} - -import java.io.StringWriter -import io.prometheus.client.exporter.common.TextFormat - -/** - * A [[CollectorRegistry]] is a registry of Collectors. - * - * It represents the concurrently shared state which holds the information - * of the metrics in question. - * - * ==On Creation== - * Due to how prometheus scraping occurs, only one CollectorRegistry is generally useful per - * application. There are generally 2 approaches. - * - * 1. Create your own registry. Register Metrics with it. Expose that. - * Advantages: Full Control Of the Code - * 2. Use the global [[CollectorRegistry.defaultRegistry defaultRegistry]] - * Advantages: Easier Interop with Java libraries that may not give - * an option for interaction with arbitrary CollectorRegistries. - */ -final class CollectorRegistry[F[_]: Sync] private(private val cr: JCollectorRegistry){ - /** - * Register A [[Collector]] with this Collector Registory - */ - def register(c: Collector): F[Unit] = - Sync[F].delay(cr.register(Collector.Unsafe.asJava(c))) - - /** - * Unregister A [[Collector]] with this CollectorRegistry - */ - def unregister(c: Collector): F[Unit] = - Sync[F].delay(cr.unregister(Collector.Unsafe.asJava(c))) - - /** - * Write out the text version Prometheus 0.0.4 of the given MetricFamilySamples - * contained in the CollectorRegistry. - * - * See https://prometheus.io/docs/instrumenting/exposition_formats/ - * for the output format specification - */ - def write004: F[String] = Sync[F].delay { - val writer = new StringWriter - TextFormat.write004(writer, cr.metricFamilySamples) - writer.toString - } - - /** - * Write out the text version OpenMetrics 1.0.0 of the given MetricFamilySamples - * contained in the CollectorRegistry. - * - * See https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#overall-structure - * for the output format specification - */ - def writeOpenMetrics100: F[String] = Sync[F].delay { - val writer = new StringWriter - TextFormat.writeOpenMetrics100(writer, cr.metricFamilySamples) - writer.toString - } - -} -object CollectorRegistry { - - /** - * Build an Empty CollectorRegistry - */ - def build[F[_]: Sync]: F[CollectorRegistry[F]] = - Sync[F].delay(new CollectorRegistry(new JCollectorRegistry)) - - /** - * Build a CollectorRegistry which has all of the [[Collector Collectors]] in - * [[Collector.Defaults]] registered. - * - * This is simply a convenience function. - */ - def buildWithDefaults[F[_]: Sync]: F[CollectorRegistry[F]] = - for { - cr <- build[F] - _ <- Collector.Defaults.registerDefaults(cr) - } yield cr - - /** - * Default Global Registry, what many Java interactions may - * automatically register with, so may be necessary for those tools. - */ - def defaultRegistry[F[_]: Sync]: CollectorRegistry[F] = - Unsafe.fromJava(JCollectorRegistry.defaultRegistry) - - object Unsafe { - def fromJava[F[_]: Sync](j: JCollectorRegistry): CollectorRegistry[F] = new CollectorRegistry[F](j) - def asJava[F[_]](c: CollectorRegistry[F]): JCollectorRegistry = c.cr - } -} \ No newline at end of file diff --git a/core/src/main/scala/io/chrisdavenport/epimetheus/Counter.scala b/core/src/main/scala/io/chrisdavenport/epimetheus/Counter.scala index 3a51774f..3e98f471 100644 --- a/core/src/main/scala/io/chrisdavenport/epimetheus/Counter.scala +++ b/core/src/main/scala/io/chrisdavenport/epimetheus/Counter.scala @@ -3,12 +3,12 @@ package io.chrisdavenport.epimetheus import cats._ import cats.implicits._ import cats.effect._ -import io.prometheus.client.{Counter => JCounter} +import io.prometheus.metrics.core.datapoints.CounterDataPoint +import io.prometheus.metrics.core.metrics.{Counter => JCounter} import scala.annotation.tailrec -/** - * Counter metric, to track counts, running totals, or events. +/** Counter metric, to track counts, running totals, or events. * * If your use case can go up or down consider using a [[Gauge]] instead. * Use the `rate()` function in Prometheus to calculate the rate of increase of a Counter. @@ -17,8 +17,8 @@ import scala.annotation.tailrec * An Example Counter without Labels: * {{{ * for { - * cr <- CollectorRegistry.build[IO] - * successCounter <- Counter.noLabels(cr, "example_success_total", "Example Counter of Success") + * pr <- PrometheusRegistry.build[IO] + * successCounter <- Counter.noLabels(pr, "example_success_total", "Example Counter of Success") * failureCounter <- Counter.noLabels(Cr, "example_failure_total", "Example Counter of Failure") * _ <- IO(println("Action Here")).guaranteeCase{ * case ExitCase.Completed => successCounter.inc @@ -30,140 +30,148 @@ import scala.annotation.tailrec * An Example of a Counter with Labels: * {{{ * for { - * cr <- CollectorRegistry.build[IO] - * counter <- Counter.labelled(cr, "example_total", "Example Counter", Sized("foo"), {s: String => Sized(s)}) + * pr <- PrometheusRegistry.build[IO] + * counter <- Counter.labelled(pr, "example_total", "Example Counter", Sized("foo"), {s: String => Sized(s)}) * _ <- counter.label("bar").inc * _ <- counter.label("baz").inc * } yield () * }}} */ -sealed abstract class Counter[F[_]]{ +sealed abstract class Counter[F[_]] { - /** - * Access to the current value of this [[Counter]]. - */ - def get: F[Double] - - /** - * Increment the value of this [[Counter]] by 1. - */ + /** Increment the value of this [[Counter]] by 1. + */ def inc: F[Unit] - /** - * Increment the value of this counter by the provided value. - * - * @param d The value to increase the [[Counter]] by. - * - */ + /** Increment the value of this counter by the provided value. + * + * @param d The value to increase the [[Counter]] by. + */ def incBy(d: Double): F[Unit] - def mapK[G[_]](fk: F ~> G): Counter[G] = new Counter.MapKCounter[F, G](this, fk) + def mapK[G[_]](fk: F ~> G): Counter[G] = + new Counter.MapKCounter[F, G](this, fk) private[epimetheus] def asJava: F[JCounter] } -/** - * Counter Constructors, and Unsafe Counter Access - */ +/** Counter Constructors, and Unsafe Counter Access + */ object Counter { - /** - * Constructor for a Counter with no labels. - * - * @param cr CollectorRegistry this [[Counter]] will be registered with - * @param name The name of the Counter - By convention, the names of Counters are suffixed by `_total`. - * @param help The help string of the metric - */ - def noLabels[F[_]: Sync](cr: CollectorRegistry[F], name: Name, help: String): F[Counter[F]] = for { - c <- Sync[F].delay(JCounter.build().name(name.getName).help(help)) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + /** Constructor for a Counter with no labels. + * + * @param pr PrometheusRegistry this [[Counter]] will be registered with + * @param name The name of the Counter - By convention, the names of Counters are suffixed by `_total`. + * @param help The help string of the metric + */ + def noLabels[F[_]: Sync]( + pr: PrometheusRegistry[F], + name: Name, + help: String + ): F[Counter[F]] = for { + c <- Sync[F].delay(JCounter.builder().name(name.getName).help(help)) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new NoLabelsCounter[F](out) - /** - * Constructor for a labelled [[Counter]]. - * - * This generates a specific number of labels via `Sized`, in combination with a function - * to generate an equally `Sized` set of labels from some type. Values are applied by position. - * - * This counter needs to have a label applied to the [[UnlabelledCounter]] in order to - * be measureable or recorded. - * - * @param cr CollectorRegistry this [[Counter]] will be registred with - * @param name The name of the Counter - By convention, the names of Counters are suffixed by `_total`. - * @param help The help string of the metric - * @param labels The name of the labels to be applied to this metric - * @param f Function to take some value provided in the future to generate an equally sized list - * of strings as the list of labels. These are assigned to labels by position. - */ + /** Constructor for a labelled [[Counter]]. + * + * This generates a specific number of labels via `Sized`, in combination with a function + * to generate an equally `Sized` set of labels from some type. Values are applied by position. + * + * This counter needs to have a label applied to the [[UnlabelledCounter]] in order to + * be measureable or recorded. + * + * @param pr PrometheusRegistry this [[Counter]] will be registred with + * @param name The name of the Counter - By convention, the names of Counters are suffixed by `_total`. + * @param help The help string of the metric + * @param labels The name of the labels to be applied to this metric + * @param f Function to take some value provided in the future to generate an equally sized list + * of strings as the list of labels. These are assigned to labels by position. + */ def labelled[F[_]: Sync, A, N <: Nat]( - cr: CollectorRegistry[F], - name: Name, - help: String, - labels: Sized[IndexedSeq[Label], N], - f: A => Sized[IndexedSeq[String], N] + pr: PrometheusRegistry[F], + name: Name, + help: String, + labels: Sized[IndexedSeq[Label], N], + f: A => Sized[IndexedSeq[String], N] ): F[UnlabelledCounter[F, A]] = for { - c <- Sync[F].delay(JCounter.build().name(name.getName).help(help).labelNames(labels.unsized.map(_.getLabel):_*)) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + c <- Sync[F].delay( + JCounter + .builder() + .name(name.getName) + .help(help) + .labelNames(labels.unsized.map(_.getLabel): _*) + ) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new UnlabelledCounterImpl[F, A](out, f.andThen(_.unsized)) - private final class NoLabelsCounter[F[_]: Sync] private[Counter] (private[Counter] val underlying: JCounter) extends Counter[F] { - override def get: F[Double] = Sync[F].delay(underlying.get) - - override def inc: F[Unit] = Sync[F].delay(underlying.inc) + private final class NoLabelsCounter[F[_]: Sync] private[Counter] ( + private[Counter] val underlying: JCounter + ) extends Counter[F] { + override def inc: F[Unit] = Sync[F].delay(underlying.inc()) override def incBy(d: Double): F[Unit] = Sync[F].delay(underlying.inc(d)) override private[epimetheus] def asJava: F[JCounter] = underlying.pure[F] } - private final class LabelledCounter[F[_]: Sync] private[Counter] (private[Counter] val underlying: JCounter.Child) extends Counter[F] { - override def get: F[Double] = Sync[F].delay(underlying.get) + private final class LabelledCounter[F[_]: Sync] private[Counter] ( + private[Counter] val underlying: JCounter, + private[Counter] val underlyingDataPoint: CounterDataPoint + ) extends Counter[F] { + def inc: F[Unit] = Sync[F].delay(underlyingDataPoint.inc) + def incBy(d: Double): F[Unit] = Sync[F].delay(underlyingDataPoint.inc(d)) - def inc: F[Unit] = Sync[F].delay(underlying.inc) - def incBy(d: Double): F[Unit] = Sync[F].delay(underlying.inc(d)) - - override private[epimetheus] def asJava: F[JCounter] = - ApplicativeThrow[F].raiseError(new IllegalArgumentException("Cannot Get Underlying Parent with Labels Applied")) + override private[epimetheus] def asJava: F[JCounter] = underlying.pure[F] } - private final class MapKCounter[F[_], G[_]](private[Counter] val base: Counter[F], fk: F ~> G) extends Counter[G]{ - def get: G[Double] = fk(base.get) + private final class MapKCounter[F[_], G[_]]( + private[Counter] val base: Counter[F], + fk: F ~> G + ) extends Counter[G] { def inc: G[Unit] = fk(base.inc) def incBy(d: Double): G[Unit] = fk(base.incBy(d)) override private[epimetheus] def asJava: G[JCounter] = fk(base.asJava) } - /** - * Generic Unlabeled Counter - * - * It is necessary to apply a value of type `A` to this - * counter to be able to take any measurements. - */ - sealed trait UnlabelledCounter[F[_], A]{ + /** Generic Unlabeled Counter + * + * It is necessary to apply a value of type `A` to this + * counter to be able to take any measurements. + */ + sealed trait UnlabelledCounter[F[_], A] { def label(a: A): Counter[F] - def mapK[G[_]](fk: F ~> G): UnlabelledCounter[G, A] = new MapKUnlabelledCounter[F, G, A](this, fk) + def mapK[G[_]](fk: F ~> G): UnlabelledCounter[G, A] = + new MapKUnlabelledCounter[F, G, A](this, fk) } - private final class UnlabelledCounterImpl[F[_]: Sync, A] private[Counter]( - private[Counter] val underlying: JCounter, - private val f: A => IndexedSeq[String] - ) extends UnlabelledCounter[F, A]{ + private final class UnlabelledCounterImpl[F[_]: Sync, A] private[Counter] ( + private[Counter] val underlying: JCounter, + private val f: A => IndexedSeq[String] + ) extends UnlabelledCounter[F, A] { def label(a: A): Counter[F] = - new LabelledCounter(underlying.labels(f(a):_*)) + new LabelledCounter(underlying, underlying.labelValues(f(a): _*)) } - private final class MapKUnlabelledCounter[F[_], G[_], A](private[Counter] val base: UnlabelledCounter[F, A], fk: F ~> G) extends UnlabelledCounter[G, A]{ + private final class MapKUnlabelledCounter[F[_], G[_], A]( + private[Counter] val base: UnlabelledCounter[F, A], + fk: F ~> G + ) extends UnlabelledCounter[G, A] { def label(a: A): Counter[G] = base.label(a).mapK(fk) } object Unsafe { @tailrec - def asJavaUnlabelled[F[_], A](c: UnlabelledCounter[F, A]): JCounter = c match { - case m: MapKUnlabelledCounter[f, _, a] => asJavaUnlabelled(m.base) - case m: UnlabelledCounterImpl[_, _] => m.underlying - } + def asJavaUnlabelled[F[_], A](c: UnlabelledCounter[F, A]): JCounter = + c match { + case m: MapKUnlabelledCounter[f, _, a] => asJavaUnlabelled(m.base) + case m: UnlabelledCounterImpl[_, _] => m.underlying + } def asJava[F[_]](c: Counter[F]): F[JCounter] = c.asJava - def fromJava[F[_]: Sync](c: JCounter.Child): Counter[F] = new LabelledCounter(c) - def fromJavaUnlabelled[F[_]: Sync](c: JCounter): Counter[F] = new NoLabelsCounter(c) + def fromJava[F[_]: Sync](c: JCounter): Counter[F] = + new LabelledCounter(c, c) + def fromJavaUnlabelled[F[_]: Sync](c: JCounter): Counter[F] = + new NoLabelsCounter(c) } } diff --git a/core/src/main/scala/io/chrisdavenport/epimetheus/Gauge.scala b/core/src/main/scala/io/chrisdavenport/epimetheus/Gauge.scala index c98c4ecc..f3d42962 100644 --- a/core/src/main/scala/io/chrisdavenport/epimetheus/Gauge.scala +++ b/core/src/main/scala/io/chrisdavenport/epimetheus/Gauge.scala @@ -3,70 +3,59 @@ package io.chrisdavenport.epimetheus import cats._ import cats.implicits._ import cats.effect._ -import io.prometheus.client.{Gauge => JGauge} +import io.prometheus.metrics.core.datapoints.GaugeDataPoint +import io.prometheus.metrics.core.metrics.{Gauge => JGauge} import scala.annotation.tailrec -/** - * Gauge metric, to report instantaneous values. - * - * Gauges can go both up and down. - * - * An Example With No Labels: - * {{{ - * for { - * cr <- CollectorRegistry.build - * gauge <- Gauge.noLabels(cr, "gauge_value", "Gauge Help") - * _ <- gauge.inc - * _ <- gauge.dec - * } yield () - * }}} - * - * An Example With Labels: - * {{{ - * for { - * cr <- CollectorRegistry.build - * gauge <- Gauge.labelled(cr, "gauge_value", "Gauge Help", Sized("foo"), {s: String => Sized(s)}) - * _ <- gauge.label("bar").inc - * _ <- gauge.label("bar").dec - * _ <- gauge.label("baz").inc - * _ <- gauge.label("baz").dec - * }}} - * - * These can be aggregated and processed together much more easily in the Prometheus - * server than individual metrics for each labelset. - */ -sealed abstract class Gauge[F[_]]{ - - /** - * Access to the current value of this [[Gauge]]. - */ - def get: F[Double] - - /** - * Decrement the value of this [[Gauge]] by 1. - */ +/** Gauge metric, to report instantaneous values. + * + * Gauges can go both up and down. + * + * An Example With No Labels: + * {{{ + * for { + * pr <- PrometheusRegistry.build + * gauge <- Gauge.noLabels(pr, "gauge_value", "Gauge Help") + * _ <- gauge.inc + * _ <- gauge.dec + * } yield () + * }}} + * + * An Example With Labels: + * {{{ + * for { + * pr <- PrometheusRegistry.build + * gauge <- Gauge.labelled(pr, "gauge_value", "Gauge Help", Sized("foo"), {s: String => Sized(s)}) + * _ <- gauge.label("bar").inc + * _ <- gauge.label("bar").dec + * _ <- gauge.label("baz").inc + * _ <- gauge.label("baz").dec + * }}} + * + * These can be aggregated and processed together much more easily in the Prometheus + * server than individual metrics for each labelset. + */ +sealed abstract class Gauge[F[_]] { + + /** Decrement the value of this [[Gauge]] by 1. + */ def dec: F[Unit] - /** - * Decrement the value of this gauge by the provided value. - * - * @param d The value to decrease the [[Gauge]] by. - * - */ + /** Decrement the value of this gauge by the provided value. + * + * @param d The value to decrease the [[Gauge]] by. + */ def decBy(d: Double): F[Unit] - /** - * Increment the value of this [[Gauge]] by 1. - */ + /** Increment the value of this [[Gauge]] by 1. + */ def inc: F[Unit] - /** - * Increment the value of this gauge by the provided value. - * - * @param d The value to increase the [[Gauge]] by. - * - */ + /** Increment the value of this gauge by the provided value. + * + * @param d The value to increase the [[Gauge]] by. + */ def incBy(d: Double): F[Unit] def set(d: Double): F[Unit] @@ -76,71 +65,83 @@ sealed abstract class Gauge[F[_]]{ private[epimetheus] def asJava: F[JGauge] } -/** - * Gauge Constructors, and Unsafe Gauge Access - */ +/** Gauge Constructors, and Unsafe Gauge Access + */ object Gauge { // Convenience - def incIn[F[_], A](g: Gauge[F], fa: F[A])(implicit C: MonadCancel[F, _]): F[A] = + def incIn[F[_], A](g: Gauge[F], fa: F[A])(implicit + C: MonadCancel[F, _] + ): F[A] = C.bracket(g.inc)(_ => fa)(_ => g.dec) - def incByIn[F[_], A](g: Gauge[F], fa: F[A], i: Double)(implicit C: MonadCancel[F, _]): F[A] = + def incByIn[F[_], A](g: Gauge[F], fa: F[A], i: Double)(implicit + C: MonadCancel[F, _] + ): F[A] = C.bracket(g.incBy(i))(_ => fa)(_ => g.decBy(i)) - def decIn[F[_], A](g: Gauge[F], fa: F[A])(implicit C: MonadCancel[F, _]): F[A] = + def decIn[F[_], A](g: Gauge[F], fa: F[A])(implicit + C: MonadCancel[F, _] + ): F[A] = C.bracket(g.dec)(_ => fa)(_ => g.inc) - def decByIn[F[_], A](g: Gauge[F], fa: F[A], i: Double)(implicit C: MonadCancel[F, _]): F[A] = + def decByIn[F[_], A](g: Gauge[F], fa: F[A], i: Double)(implicit + C: MonadCancel[F, _] + ): F[A] = C.bracket(g.decBy(i))(_ => fa)(_ => g.incBy(i)) // Constructors - /** - * Constructor for a [[Gauge]] with no labels. - * - * @param cr CollectorRegistry this [[Gauge]] will be registered with - * @param name The name of the Gauge - * @param help The help string of the metric - */ - def noLabels[F[_]: Sync](cr: CollectorRegistry[F], name: Name, help: String): F[Gauge[F]] = for { - c <- Sync[F].delay(JGauge.build().name(name.getName).help(help)) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + /** Constructor for a [[Gauge]] with no labels. + * + * @param pr PrometheusRegistry this [[Gauge]] will be registered with + * @param name The name of the Gauge + * @param help The help string of the metric + */ + def noLabels[F[_]: Sync]( + pr: PrometheusRegistry[F], + name: Name, + help: String + ): F[Gauge[F]] = for { + c <- Sync[F].delay(JGauge.builder().name(name.getName).help(help)) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new NoLabelsGauge[F](out) - /** - * Constructor for a labelled [[Gauge]]. - * - * This generates a specific number of labels via `Sized`, in combination with a function - * to generate an equally `Sized` set of labels from some type. Values are applied by position. - * - * This counter needs to have a label applied to the [[UnlabelledGauge]] in order to - * be measureable or recorded. - * - * @param cr CollectorRegistry this [[Gauge]] will be registred with - * @param name The name of the [[Gauge]]. - * @param help The help string of the metric - * @param labels The name of the labels to be applied to this metric - * @param f Function to take some value provided in the future to generate an equally sized list - * of strings as the list of labels. These are assigned to labels by position. - */ + /** Constructor for a labelled [[Gauge]]. + * + * This generates a specific number of labels via `Sized`, in combination with a function + * to generate an equally `Sized` set of labels from some type. Values are applied by position. + * + * This counter needs to have a label applied to the [[UnlabelledGauge]] in order to + * be measureable or recorded. + * + * @param pr PrometheusRegistry this [[Gauge]] will be registred with + * @param name The name of the [[Gauge]]. + * @param help The help string of the metric + * @param labels The name of the labels to be applied to this metric + * @param f Function to take some value provided in the future to generate an equally sized list + * of strings as the list of labels. These are assigned to labels by position. + */ def labelled[F[_]: Sync, A, N <: Nat]( - cr: CollectorRegistry[F], - name: Name, - help: String, - labels: Sized[IndexedSeq[Label], N], - f: A => Sized[IndexedSeq[String], N] + pr: PrometheusRegistry[F], + name: Name, + help: String, + labels: Sized[IndexedSeq[Label], N], + f: A => Sized[IndexedSeq[String], N] ): F[UnlabelledGauge[F, A]] = for { - c <- Sync[F].delay(JGauge.build().name(name.getName).help(help).labelNames(labels.unsized.map(_.getLabel):_*)) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + c <- Sync[F].delay( + JGauge + .builder() + .name(name.getName) + .help(help) + .labelNames(labels.unsized.map(_.getLabel): _*) + ) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new UnlabelledGaugeImpl[F, A](out, f.andThen(_.unsized)) - private final class NoLabelsGauge[F[_]: Sync] private[Gauge] ( - private[Gauge] val underlying: JGauge + private[Gauge] val underlying: JGauge ) extends Gauge[F] { - def get: F[Double] = Sync[F].delay(underlying.get()) - def dec: F[Unit] = Sync[F].delay(underlying.dec()) def decBy(d: Double): F[Unit] = Sync[F].delay(underlying.dec(d)) @@ -153,25 +154,24 @@ object Gauge { } private final class LabelledGauge[F[_]: Sync] private[Gauge] ( - private val underlying: JGauge.Child + private val underlying: JGauge, + private val underlyingDataPoint: GaugeDataPoint ) extends Gauge[F] { - def get: F[Double] = Sync[F].delay(underlying.get()) - - def dec: F[Unit] = Sync[F].delay(underlying.dec()) - def decBy(d: Double): F[Unit] = Sync[F].delay(underlying.dec(d)) + def dec: F[Unit] = Sync[F].delay(underlyingDataPoint.dec()) + def decBy(d: Double): F[Unit] = Sync[F].delay(underlyingDataPoint.dec(d)) - def inc: F[Unit] = Sync[F].delay(underlying.inc()) - def incBy(d: Double): F[Unit] = Sync[F].delay(underlying.inc(d)) + def inc: F[Unit] = Sync[F].delay(underlyingDataPoint.inc()) + def incBy(d: Double): F[Unit] = Sync[F].delay(underlyingDataPoint.inc(d)) - def set(d: Double): F[Unit] = Sync[F].delay(underlying.set(d)) + def set(d: Double): F[Unit] = Sync[F].delay(underlyingDataPoint.set(d)) - override private[epimetheus] def asJava: F[JGauge] = - ApplicativeThrow[F].raiseError(new IllegalArgumentException("Cannot Get Underlying Parent with Labels Applied")) + override private[epimetheus] def asJava: F[JGauge] = underlying.pure[F] } - private final class MapKGauge[F[_], G[_]](private[Gauge] val base: Gauge[F], fk: F ~> G) extends Gauge[G]{ - def get: G[Double] = fk(base.get) - + private final class MapKGauge[F[_], G[_]]( + private[Gauge] val base: Gauge[F], + fk: F ~> G + ) extends Gauge[G] { def dec: G[Unit] = fk(base.dec) def decBy(d: Double): G[Unit] = fk(base.decBy(d)) @@ -183,39 +183,45 @@ object Gauge { override private[epimetheus] def asJava: G[JGauge] = fk(base.asJava) } - - sealed trait UnlabelledGauge[F[_], A]{ + sealed trait UnlabelledGauge[F[_], A] { def label(a: A): Gauge[F] - def mapK[G[_]](fk: F ~> G): UnlabelledGauge[G, A] = new MapKUnlabelledGauge[F, G, A](this, fk) + def mapK[G[_]](fk: F ~> G): UnlabelledGauge[G, A] = + new MapKUnlabelledGauge[F, G, A](this, fk) } - /** - * Generic Unlabeled Gauge - * - * It is necessary to apply a value of type `A` to this - * gauge to be able to take any measurements. - */ - final private[epimetheus] class UnlabelledGaugeImpl[F[_]: Sync, A] private[epimetheus]( - private[Gauge] val underlying: JGauge, - private val f: A => IndexedSeq[String] + /** Generic Unlabeled Gauge + * + * It is necessary to apply a value of type `A` to this + * gauge to be able to take any measurements. + */ + final private[epimetheus] class UnlabelledGaugeImpl[ + F[_]: Sync, + A + ] private[epimetheus] ( + private[Gauge] val underlying: JGauge, + private val f: A => IndexedSeq[String] ) extends UnlabelledGauge[F, A] { def label(a: A): Gauge[F] = - new LabelledGauge[F](underlying.labels(f(a):_*)) + new LabelledGauge[F](underlying, underlying.labelValues(f(a): _*)) } - final private class MapKUnlabelledGauge[F[_], G[_], A](private[Gauge] val base: UnlabelledGauge[F, A], fk: F ~> G) extends UnlabelledGauge[G, A]{ + final private class MapKUnlabelledGauge[F[_], G[_], A]( + private[Gauge] val base: UnlabelledGauge[F, A], + fk: F ~> G + ) extends UnlabelledGauge[G, A] { def label(a: A): Gauge[G] = base.label(a).mapK(fk) } - object Unsafe { @tailrec def asJavaUnlabelled[F[_], A](g: UnlabelledGauge[F, A]): JGauge = g match { - case x: UnlabelledGaugeImpl[_, _] => x.underlying + case x: UnlabelledGaugeImpl[_, _] => x.underlying case x: MapKUnlabelledGauge[f, _, a] => asJavaUnlabelled(x.base) } def asJava[F[_]](c: Gauge[F]): F[JGauge] = c.asJava - def fromJava[F[_]: Sync](g: JGauge.Child): Gauge[F] = new LabelledGauge(g) - def fromJavaUnlabelled[F[_]: Sync](g: JGauge): Gauge[F] = new NoLabelsGauge(g) + def fromJava[F[_]: Sync](g: JGauge): Gauge[F] = new LabelledGauge(g, g) + def fromJavaUnlabelled[F[_]: Sync](g: JGauge): Gauge[F] = new NoLabelsGauge( + g + ) } } diff --git a/core/src/main/scala/io/chrisdavenport/epimetheus/Histogram.scala b/core/src/main/scala/io/chrisdavenport/epimetheus/Histogram.scala index 30f85e7a..60193d49 100644 --- a/core/src/main/scala/io/chrisdavenport/epimetheus/Histogram.scala +++ b/core/src/main/scala/io/chrisdavenport/epimetheus/Histogram.scala @@ -3,253 +3,257 @@ package io.chrisdavenport.epimetheus import cats._ import cats.effect._ import cats.implicits._ -import io.prometheus.client.{Histogram => JHistogram} +import io.prometheus.metrics.core.datapoints.DistributionDataPoint +import io.prometheus.metrics.core.metrics.{Histogram => JHistogram} import scala.annotation.tailrec import scala.concurrent.duration._ -/** - * Histogram metric, to track distributions of events. - * - * Note: Each bucket is one timeseries. Many buckets and/or many dimensions with labels - * can produce large amount of time series, that may cause performance problems. - * - * The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds. - * - */ -sealed abstract class Histogram[F[_]]{ +/** Histogram metric, to track distributions of events. + * + * Note: Each bucket is one timeseries. Many buckets and/or many dimensions with labels + * can produce large amount of time series, that may cause performance problems. + * + * The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds. + */ +sealed abstract class Histogram[F[_]] { - /** - * Persist an observation into this [[Histogram]] - * - * @param d The observation to persist - */ + /** Persist an observation into this [[Histogram]] + * + * @param d The observation to persist + */ def observe(d: Double): F[Unit] - def mapK[G[_]](fk: F ~> G): Histogram[G] = new Histogram.MapKHistogram[F, G](this, fk) + def mapK[G[_]](fk: F ~> G): Histogram[G] = + new Histogram.MapKHistogram[F, G](this, fk) private[epimetheus] def asJava: F[JHistogram] } -/** - * Histogram Constructors, Convenience Methods and Unsafe Histogram Access - * - * Convenience function exposed here will also be exposed as implicit syntax - * enhancements on the Histogram - */ +/** Histogram Constructors, Convenience Methods and Unsafe Histogram Access + * + * Convenience function exposed here will also be exposed as implicit syntax + * enhancements on the Histogram + */ object Histogram { // Convenience ---------------------------------------------------- // Since these methods are not ex - /** - * Persist a timed value into this [[Histogram]] - * - * @param h Histogram - * @param fa The action to time - * @param unit The unit of time to observe the timing in. Default Histogram buckets - * are optimized for `SECONDS`. - */ - def timed[F[_] : Clock, A](h: Histogram[F], fa: F[A], unit: TimeUnit)(implicit C: MonadCancel[F, _]): F[A] = - C.bracket(Clock[F].monotonic)((_: FiniteDuration) => fa) { (start: FiniteDuration) => - Clock[F].monotonic.flatMap(now => h.observe((now - start).toUnit(unit))) + /** Persist a timed value into this [[Histogram]] + * + * @param h Histogram + * @param fa The action to time + * @param unit The unit of time to observe the timing in. Default Histogram buckets + * are optimized for `SECONDS`. + */ + def timed[F[_]: Clock, A](h: Histogram[F], fa: F[A], unit: TimeUnit)(implicit + C: MonadCancel[F, _] + ): F[A] = + C.bracket(Clock[F].monotonic)((_: FiniteDuration) => fa) { + (start: FiniteDuration) => + Clock[F].monotonic.flatMap(now => h.observe((now - start).toUnit(unit))) } - /** - * Persist a timed value into this [[Histogram]] in unit Seconds. This is exposed. - * since default buckets are in seconds it makes sense the general case will be to - * match the default buckets. - * - * @param h Histogram - * @param fa The action to time - */ - def timedSeconds[F[_] : Clock, A](h: Histogram[F], fa: F[A])(implicit C: MonadCancel[F, _]): F[A] = + /** Persist a timed value into this [[Histogram]] in unit Seconds. This is exposed. + * since default buckets are in seconds it makes sense the general case will be to + * match the default buckets. + * + * @param h Histogram + * @param fa The action to time + */ + def timedSeconds[F[_]: Clock, A](h: Histogram[F], fa: F[A])(implicit + C: MonadCancel[F, _] + ): F[A] = timed(h, fa, SECONDS) // Constructors --------------------------------------------------- - /** - * Default Buckets - * - * Intended to cover a typical web/rpc request from milliseconds to seconds. - */ - val defaults = List(.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10) + /** Default Buckets + * + * Intended to cover a typical web/rpc request from milliseconds to seconds. + */ + val defaults = + List(.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10) - /** - * Constructor for a [[Histogram]] with no labels. Default buckets are [[defaults]] - * and are intended to cover a typical web/rpc request from milliseconds to seconds. - * - * @param cr CollectorRegistry this [[Histogram]] will be registered with - * @param name The name of the Histogram - * @param help The help string of the metric - */ + /** Constructor for a [[Histogram]] with no labels. Default buckets are [[defaults]] + * and are intended to cover a typical web/rpc request from milliseconds to seconds. + * + * @param pr PrometheusRegistry this [[Histogram]] will be registered with + * @param name The name of the Histogram + * @param help The help string of the metric + */ def noLabels[F[_]: Sync]( - cr: CollectorRegistry[F], - name: Name, - help: String + pr: PrometheusRegistry[F], + name: Name, + help: String ): F[Histogram[F]] = - noLabelsBuckets(cr, name, help, defaults:_*) + noLabelsBuckets(pr, name, help, defaults: _*) - /** - * Constructor for a [[Histogram]] with no labels. Default buckets are [[defaults]] - * and are intended to cover a typical web/rpc request from milliseconds to seconds. - * - * @param cr CollectorRegistry this [[Histogram]] will be registered with - * @param name The name of the Gauge - * @param help The help string of the metric - * @param buckets The buckets to measure observations by. - */ + /** Constructor for a [[Histogram]] with no labels. Default buckets are [[defaults]] + * and are intended to cover a typical web/rpc request from milliseconds to seconds. + * + * @param pr PrometheusRegistry this [[Histogram]] will be registered with + * @param name The name of the Gauge + * @param help The help string of the metric + * @param upperBounds The buckets to measure observations by. + */ def noLabelsBuckets[F[_]: Sync]( - cr: CollectorRegistry[F], - name: Name, - help: String, - buckets: Double* + pr: PrometheusRegistry[F], + name: Name, + help: String, + upperBounds: Double* ): F[Histogram[F]] = for { c <- Sync[F].delay( - JHistogram.build() - .name(name.getName) - .help(help) - .buckets(buckets:_*) + JHistogram + .builder() + .name(name.getName) + .help(help) + .classicUpperBounds(upperBounds: _*) ) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new NoLabelsHistogram[F](out) def noLabelsLinearBuckets[F[_]: Sync]( - cr: CollectorRegistry[F], - name: Name, - help: String, - start: Double, - factor: Double, - count: Int + pr: PrometheusRegistry[F], + name: Name, + help: String, + start: Double, + width: Double, + count: Int ): F[Histogram[F]] = for { c <- Sync[F].delay( - JHistogram.build() - .name(name.getName) - .help(help) - .linearBuckets(start, factor, count) + JHistogram + .builder() + .name(name.getName) + .help(help) + .classicLinearUpperBounds(start, width, count) ) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new NoLabelsHistogram[F](out) def noLabelsExponentialBuckets[F[_]: Sync]( - cr: CollectorRegistry[F], - name: Name, help: String, - start: Double, - factor: Double, - count: Int + pr: PrometheusRegistry[F], + name: Name, + help: String, + start: Double, + factor: Double, + count: Int ): F[Histogram[F]] = for { c <- Sync[F].delay( - JHistogram.build() - .name(name.getName) - .help(help) - .exponentialBuckets(start, factor, count) + JHistogram + .builder() + .name(name.getName) + .help(help) + .classicExponentialUpperBounds(start, factor, count) ) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new NoLabelsHistogram[F](out) - /** - * Constructor for a labelled [[Histogram]]. Default buckets are [[defaults]] - * and are intended to cover a typical web/rpc request from milliseconds to seconds. - * - * This generates a specific number of labels via `Sized`, in combination with a function - * to generate an equally `Sized` set of labels from some type. Values are applied by position. - * - * This counter needs to have a label applied to the [[UnlabelledHistogram]] in order to - * be measureable or recorded. - * - * @param cr CollectorRegistry this [[Histogram]] will be registred with - * @param name The name of the [[Histogram]]. - * @param help The help string of the metric - * @param labels The name of the labels to be applied to this metric - * @param f Function to take some value provided in the future to generate an equally sized list - * of strings as the list of labels. These are assigned to labels by position. - */ + /** Constructor for a labelled [[Histogram]]. Default buckets are [[defaults]] + * and are intended to cover a typical web/rpc request from milliseconds to seconds. + * + * This generates a specific number of labels via `Sized`, in combination with a function + * to generate an equally `Sized` set of labels from some type. Values are applied by position. + * + * This counter needs to have a label applied to the [[UnlabelledHistogram]] in order to + * be measureable or recorded. + * + * @param pr PrometheusRegistry this [[Histogram]] will be registred with + * @param name The name of the [[Histogram]]. + * @param help The help string of the metric + * @param labels The name of the labels to be applied to this metric + * @param f Function to take some value provided in the future to generate an equally sized list + * of strings as the list of labels. These are assigned to labels by position. + */ def labelled[F[_]: Sync, A, N <: Nat]( - cr: CollectorRegistry[F], - name: Name, - help: String, - labels: Sized[IndexedSeq[Label], N], - f: A => Sized[IndexedSeq[String], N] + pr: PrometheusRegistry[F], + name: Name, + help: String, + labels: Sized[IndexedSeq[Label], N], + f: A => Sized[IndexedSeq[String], N] ): F[UnlabelledHistogram[F, A]] = - labelledBuckets(cr, name, help, labels, f, defaults:_*) + labelledBuckets(pr, name, help, labels, f, defaults: _*) - /** - * Constructor for a labelled [[Histogram]]. Default buckets are [[defaults]] - * and are intended to cover a typical web/rpc request from milliseconds to seconds. - * - * This generates a specific number of labels via `Sized`, in combination with a function - * to generate an equally `Sized` set of labels from some type. Values are applied by position. - * - * This counter needs to have a label applied to the [[UnlabelledHistogram]] in order to - * be measureable or recorded. - * - * @param cr CollectorRegistry this [[Histogram]] will be registred with - * @param name The name of the [[Histogram]]. - * @param help The help string of the metric - * @param labels The name of the labels to be applied to this metric - * @param f Function to take some value provided in the future to generate an equally sized list - * of strings as the list of labels. These are assigned to labels by position. - * @param buckets The buckets to measure observations by. - */ + /** Constructor for a labelled [[Histogram]]. Default buckets are [[defaults]] + * and are intended to cover a typical web/rpc request from milliseconds to seconds. + * + * This generates a specific number of labels via `Sized`, in combination with a function + * to generate an equally `Sized` set of labels from some type. Values are applied by position. + * + * This counter needs to have a label applied to the [[UnlabelledHistogram]] in order to + * be measureable or recorded. + * + * @param pr PrometheusRegistry this [[Histogram]] will be registred with + * @param name The name of the [[Histogram]]. + * @param help The help string of the metric + * @param labels The name of the labels to be applied to this metric + * @param f Function to take some value provided in the future to generate an equally sized list + * of strings as the list of labels. These are assigned to labels by position. + * @param upperBounds The buckets to measure observations by. + */ def labelledBuckets[F[_]: Sync, A, N <: Nat]( - cr: CollectorRegistry[F], - name: Name, - help: String, - labels: Sized[IndexedSeq[Label], N], - f: A => Sized[IndexedSeq[String], N], - buckets: Double* + pr: PrometheusRegistry[F], + name: Name, + help: String, + labels: Sized[IndexedSeq[Label], N], + f: A => Sized[IndexedSeq[String], N], + upperBounds: Double* ): F[UnlabelledHistogram[F, A]] = for { c <- Sync[F].delay( - JHistogram.build() - .name(name.getName) - .help(help) - .labelNames(labels.unsized.map(_.getLabel):_*) - .buckets(buckets:_*) + JHistogram + .builder() + .name(name.getName) + .help(help) + .labelNames(labels.unsized.map(_.getLabel): _*) + .classicUpperBounds(upperBounds: _*) ) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new UnlabelledHistogramImpl[F, A](out, f.andThen(_.unsized)) def labelledLinearBuckets[F[_]: Sync, A, N <: Nat]( - cr: CollectorRegistry[F], - name: Name, - help: String, - labels: Sized[IndexedSeq[Label], N], - f: A => Sized[IndexedSeq[String], N], - start: Double, - factor: Double, - count: Int + pr: PrometheusRegistry[F], + name: Name, + help: String, + labels: Sized[IndexedSeq[Label], N], + f: A => Sized[IndexedSeq[String], N], + start: Double, + width: Double, + count: Int ): F[UnlabelledHistogram[F, A]] = for { c <- Sync[F].delay( - JHistogram.build() - .name(name.getName) - .help(help) - .labelNames(labels.unsized.map(_.getLabel):_*) - .linearBuckets(start, factor, count) + JHistogram + .builder() + .name(name.getName) + .help(help) + .labelNames(labels.unsized.map(_.getLabel): _*) + .classicLinearUpperBounds(start, width, count) ) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new UnlabelledHistogramImpl[F, A](out, f.andThen(_.unsized)) def labelledExponentialBuckets[F[_]: Sync, A, N <: Nat]( - cr: CollectorRegistry[F], - name: Name, - help: String, - labels: Sized[IndexedSeq[Label], N], - f: A => Sized[IndexedSeq[String], N], - start: Double, - factor: Double, - count: Int + pr: PrometheusRegistry[F], + name: Name, + help: String, + labels: Sized[IndexedSeq[Label], N], + f: A => Sized[IndexedSeq[String], N], + start: Double, + factor: Double, + count: Int ): F[UnlabelledHistogram[F, A]] = for { c <- Sync[F].delay( - JHistogram.build() - .name(name.getName) - .help(help) - .labelNames(labels.unsized.map(_.getLabel):_*) - .exponentialBuckets(start, factor, count) + JHistogram + .builder() + .name(name.getName) + .help(help) + .labelNames(labels.unsized.map(_.getLabel): _*) + .classicExponentialUpperBounds(start, factor, count) ) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new UnlabelledHistogramImpl[F, A](out, f.andThen(_.unsized)) private final class NoLabelsHistogram[F[_]: Sync] private[Histogram] ( - private[Histogram] val underlying: JHistogram + private[Histogram] val underlying: JHistogram ) extends Histogram[F] { def observe(d: Double): F[Unit] = Sync[F].delay(underlying.observe(d)) @@ -257,52 +261,68 @@ object Histogram { } private final class LabelledHistogram[F[_]: Sync] private[Histogram] ( - private val underlying: JHistogram.Child + private val underlying: DistributionDataPoint ) extends Histogram[F] { def observe(d: Double): F[Unit] = Sync[F].delay(underlying.observe(d)) override private[epimetheus] def asJava: F[JHistogram] = - ApplicativeThrow[F].raiseError(new IllegalArgumentException("Cannot Get Underlying Parent with Labels Applied")) + ApplicativeThrow[F].raiseError( + new IllegalArgumentException( + "Cannot Get Underlying Parent with Labels Applied" + ) + ) } - private final class MapKHistogram[F[_], G[_]](private[Histogram] val base: Histogram[F], fk: F ~> G) extends Histogram[G]{ + private final class MapKHistogram[F[_], G[_]]( + private[Histogram] val base: Histogram[F], + fk: F ~> G + ) extends Histogram[G] { def observe(d: Double): G[Unit] = fk(base.observe(d)) override private[epimetheus] def asJava: G[JHistogram] = fk(base.asJava) } - /** - * Generic UnlabelledHistorgram - * - * It is necessary to apply a value of type `A` to this - * histogram to be able to take any measurements. - */ - sealed trait UnlabelledHistogram[F[_], A]{ + /** Generic UnlabelledHistorgram + * + * It is necessary to apply a value of type `A` to this + * histogram to be able to take any measurements. + */ + sealed trait UnlabelledHistogram[F[_], A] { def label(a: A): Histogram[F] - def mapK[G[_]](fk: F ~> G): UnlabelledHistogram[G, A] = new MapKUnlabelledHistogram[F, G, A](this, fk) + def mapK[G[_]](fk: F ~> G): UnlabelledHistogram[G, A] = + new MapKUnlabelledHistogram[F, G, A](this, fk) } - final private[epimetheus] class UnlabelledHistogramImpl[F[_]: Sync, A] private[epimetheus] ( - private[Histogram] val underlying: JHistogram, - private val f: A => IndexedSeq[String] - ) extends UnlabelledHistogram[F, A]{ + final private[epimetheus] class UnlabelledHistogramImpl[ + F[_]: Sync, + A + ] private[epimetheus] ( + private[Histogram] val underlying: JHistogram, + private val f: A => IndexedSeq[String] + ) extends UnlabelledHistogram[F, A] { def label(a: A): Histogram[F] = - new LabelledHistogram[F](underlying.labels(f(a):_*)) + new LabelledHistogram[F](underlying.labelValues(f(a): _*)) } - final private class MapKUnlabelledHistogram[F[_], G[_], A](private[Histogram] val base: UnlabelledHistogram[F, A], fk: F ~> G) extends UnlabelledHistogram[G, A]{ + final private class MapKUnlabelledHistogram[F[_], G[_], A]( + private[Histogram] val base: UnlabelledHistogram[F, A], + fk: F ~> G + ) extends UnlabelledHistogram[G, A] { def label(a: A): Histogram[G] = base.label(a).mapK(fk) } object Unsafe { @tailrec - def asJavaUnlabelled[F[_], A](h: UnlabelledHistogram[F, A]): JHistogram = h match { - case h: UnlabelledHistogramImpl[F, A] => h.underlying - case h: MapKUnlabelledHistogram[f, _, a] => asJavaUnlabelled(h.base) - } + def asJavaUnlabelled[F[_], A](h: UnlabelledHistogram[F, A]): JHistogram = + h match { + case h: UnlabelledHistogramImpl[F, A] => h.underlying + case h: MapKUnlabelledHistogram[f, _, a] => asJavaUnlabelled(h.base) + } def asJava[F[_]](c: Histogram[F]): F[JHistogram] = c.asJava - def fromJava[F[_]: Sync](h: JHistogram.Child): Histogram[F] = new LabelledHistogram(h) - def fromJavaUnlabelled[F[_]: Sync](h: JHistogram): Histogram[F] = new NoLabelsHistogram(h) + def fromJava[F[_]: Sync](h: DistributionDataPoint): Histogram[F] = + new LabelledHistogram(h) + def fromJavaUnlabelled[F[_]: Sync](h: JHistogram): Histogram[F] = + new NoLabelsHistogram(h) } } diff --git a/core/src/main/scala/io/chrisdavenport/epimetheus/PrometheusRegistry.scala b/core/src/main/scala/io/chrisdavenport/epimetheus/PrometheusRegistry.scala new file mode 100644 index 00000000..95c324b7 --- /dev/null +++ b/core/src/main/scala/io/chrisdavenport/epimetheus/PrometheusRegistry.scala @@ -0,0 +1,100 @@ +package io.chrisdavenport.epimetheus + +import cats.effect._ +import cats.implicits._ +import io.prometheus.metrics.expositionformats.ExpositionFormats +import io.prometheus.metrics.model.registry.{PrometheusRegistry => JPrometheusRegistry} + +import java.io.ByteArrayOutputStream +import java.nio.charset.StandardCharsets + +/** + * A [[PrometheusRegistry]] is a registry of Collectors. + * + * It represents the concurrently shared state which holds the information + * of the metrics in question. + * + * ==On Creation== + * Due to how prometheus scraping occurs, only one PrometheusRegistry is generally useful per + * application. There are generally 2 approaches. + * + * 1. Create your own registry. Register Metrics with it. Expose that. + * Advantages: Full Control Of the Code + * 2. Use the global [[PrometheusRegistry.defaultRegistry defaultRegistry]] + * Advantages: Easier Interop with Java libraries that may not give + * an option for interaction with arbitrary PrometheusRegistries. + */ +final class PrometheusRegistry[F[_]: Sync] private(private val pr: JPrometheusRegistry){ + /** + * Register A [[Collector]] with this Collector Registry + */ + def register(c: Collector): F[Unit] = + Sync[F].delay(pr.register(Collector.Unsafe.asJava(c))) + + /** + * Unregister A [[Collector]] with this PrometheusRegistry + */ + def unregister(c: Collector): F[Unit] = + Sync[F].delay(pr.unregister(Collector.Unsafe.asJava(c))) + + private lazy val expositionFormats = ExpositionFormats.init() + + /** + * Write out the text version Prometheus 0.0.4 of the given MetricFamilySamples + * contained in the CollectorRegistry. + * + * See https://prometheus.io/docs/instrumenting/exposition_formats/ + * for the output format specification + */ + def write004: F[String] = Sync[F].delay { + val output = new ByteArrayOutputStream() + expositionFormats.getPrometheusTextFormatWriter.write(output, pr.scrape()) + output.toString(StandardCharsets.UTF_8) + } + + /** + * Write out the text version OpenMetrics 1.0.0 of the given MetricFamilySamples + * contained in the CollectorRegistry. + * + * See https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#overall-structure + * for the output format specification + */ + def writeOpenMetrics100: F[String] = Sync[F].delay { + val output = new ByteArrayOutputStream() + expositionFormats.getOpenMetricsTextFormatWriter.write(output, pr.scrape()) + output.toString(StandardCharsets.UTF_8) + } +} + +object PrometheusRegistry { + + /** + * Build an Empty PrometheusRegistry + */ + def build[F[_]: Sync]: F[PrometheusRegistry[F]] = + Sync[F].delay(new PrometheusRegistry(new JPrometheusRegistry)) + + /** + * Build a PrometheusRegistry which has all of the [[Collector Collectors]] in + * [[Collector.Defaults]] registered. + * + * This is simply a convenience function. + */ + def buildWithDefaults[F[_]: Sync]: F[PrometheusRegistry[F]] = + for { + pr <- build[F] + _ <- Collector.Defaults.registerDefaults(pr) + } yield pr + + /** + * Default Global Registry, what many Java interactions may + * automatically register with, so may be necessary for those tools. + */ + def defaultRegistry[F[_]: Sync]: PrometheusRegistry[F] = + Unsafe.fromJava(JPrometheusRegistry.defaultRegistry) + + object Unsafe { + def fromJava[F[_]: Sync](j: JPrometheusRegistry): PrometheusRegistry[F] = new PrometheusRegistry[F](j) + def asJava[F[_]](c: PrometheusRegistry[F]): JPrometheusRegistry = c.pr + } +} \ No newline at end of file diff --git a/core/src/main/scala/io/chrisdavenport/epimetheus/SummaryCommons.scala b/core/src/main/scala/io/chrisdavenport/epimetheus/SummaryCommons.scala index aa0df987..2e9795ff 100644 --- a/core/src/main/scala/io/chrisdavenport/epimetheus/SummaryCommons.scala +++ b/core/src/main/scala/io/chrisdavenport/epimetheus/SummaryCommons.scala @@ -3,252 +3,309 @@ package io.chrisdavenport.epimetheus import cats._ import cats.implicits._ import cats.effect._ -import io.prometheus.client.{Summary => JSummary} +import io.prometheus.metrics.core.datapoints.DistributionDataPoint +import io.prometheus.metrics.core.metrics.{Summary => JSummary} import scala.annotation.tailrec import scala.concurrent.duration._ -/** - * Summary Constructors, and Unsafe Summary Access - */ +/** Summary Constructors, and Unsafe Summary Access + */ trait SummaryCommons { // Convenience ---------------------------------------------------- - /** - * Persist a timed value into this [[Summary]] - * - * @param s The summary to persist into. - * @param fa The action to time - * @param unit The unit of time to observe the timing in. - */ - def timed[F[_] : Clock, A](s: Summary[F], fa: F[A], unit: TimeUnit)(implicit C: MonadCancel[F, _]): F[A] = - C.bracket(Clock[F].monotonic)((_: FiniteDuration) => fa) { (start: FiniteDuration) => - Clock[F].monotonic.flatMap(now => s.observe((now - start).toUnit(unit))) + /** Persist a timed value into this [[Summary]] + * + * @param s The summary to persist into. + * @param fa The action to time + * @param unit The unit of time to observe the timing in. + */ + def timed[F[_]: Clock, A](s: Summary[F], fa: F[A], unit: TimeUnit)(implicit + C: MonadCancel[F, _] + ): F[A] = + C.bracket(Clock[F].monotonic)((_: FiniteDuration) => fa) { + (start: FiniteDuration) => + Clock[F].monotonic.flatMap(now => s.observe((now - start).toUnit(unit))) } - /** - * Persist a timed value into this [[Summary]] in unit Seconds. Since the default - * buckets for histogram are in seconds and Summary are in some ways counterparts - * to histograms, this exposes convenience function. - * - * @param s The summary to persist to - * @param fa The action to time - */ - def timedSeconds[F[_] : Clock, A](s: Summary[F], fa: F[A])(implicit C: MonadCancel[F, _]): F[A] = + /** Persist a timed value into this [[Summary]] in unit Seconds. Since the default + * buckets for histogram are in seconds and Summary are in some ways counterparts + * to histograms, this exposes convenience function. + * + * @param s The summary to persist to + * @param fa The action to time + */ + def timedSeconds[F[_]: Clock, A](s: Summary[F], fa: F[A])(implicit + C: MonadCancel[F, _] + ): F[A] = timed(s, fa, SECONDS) // Constructors --------------------------------------------------- val defaultMaxAgeSeconds = 600L val defaultAgeBuckets = 5 - /** - * Default Constructor for a [[Summary]] with no labels. - * - * maxAgeSeconds is set to [[defaultMaxAgeSeconds]] which is 10 minutes. - * - * ageBuckets is the number of buckets for the sliding time window, set to [[defaultAgeBuckets]] which is 5. - * - * If you want to exert control, use the full constructor [[Summary.noLabelsQuantiles noLabelsQuantiles]] - * - * @param cr CollectorRegistry this [[Summary]] will be registered with - * @param name The name of the Summary - * @param help The help string of the metric - * @param quantiles The measurements to track for specifically over the sliding time window. - */ + /** Default Constructor for a [[Summary]] with no labels. + * + * maxAgeSeconds is set to [[defaultMaxAgeSeconds]] which is 10 minutes. + * + * ageBuckets is the number of buckets for the sliding time window, set to [[defaultAgeBuckets]] which is 5. + * + * If you want to exert control, use the full constructor [[Summary.noLabelsQuantiles noLabelsQuantiles]] + * + * @param pr PrometheusRegistry this [[Summary]] will be registered with + * @param name The name of the Summary + * @param help The help string of the metric + * @param quantiles The measurements to track for specifically over the sliding time window. + */ def noLabels[F[_]: Sync]( - cr: CollectorRegistry[F], - name: Name, - help: String, - quantiles: Summary.Quantile* + pr: PrometheusRegistry[F], + name: Name, + help: String, + quantiles: Summary.Quantile* ): F[Summary[F]] = - noLabelsQuantiles(cr, name, help, defaultMaxAgeSeconds, defaultAgeBuckets, quantiles:_*) - - /** - * Constructor for a [[Summary]] with no labels. - * - * maxAgeSeconds is set to [[defaultMaxAgeSeconds]] which is 10 minutes. - * - * ageBuckets is the number of buckets for the sliding time window, set to [[defaultAgeBuckets]] which is 5. - * - * If you want to exert control, use the full constructor [[Summary.noLabelsQuantiles noLabelsQuantiles]] - * - * @param cr CollectorRegistry this [[Summary]] will be registered with - * @param name The name of the Summary - * @param help The help string of the metric - * @param maxAgeSeconds Set the duration of the time window is, - * i.e. how long observations are kept before they are discarded. - * @param ageBuckets Set the number of buckets used to implement the sliding time window. If your time window is 10 minutes, and you have ageBuckets=5, - * buckets will be switched every 2 minutes. The value is a trade-off between resources (memory and cpu for maintaining the bucket) - * and how smooth the time window is moved. - * @param quantiles The measurements to track for specifically over the sliding time window. - */ + noLabelsQuantiles( + pr, + name, + help, + defaultMaxAgeSeconds, + defaultAgeBuckets, + quantiles: _* + ) + + /** Constructor for a [[Summary]] with no labels. + * + * maxAgeSeconds is set to [[defaultMaxAgeSeconds]] which is 10 minutes. + * + * ageBuckets is the number of buckets for the sliding time window, set to [[defaultAgeBuckets]] which is 5. + * + * If you want to exert control, use the full constructor [[Summary.noLabelsQuantiles noLabelsQuantiles]] + * + * @param pr PrometheusRegistry this [[Summary]] will be registered with + * @param name The name of the Summary + * @param help The help string of the metric + * @param maxAgeSeconds Set the duration of the time window is, + * i.e. how long observations are kept before they are discarded. + * @param ageBuckets Set the number of buckets used to implement the sliding time window. If your time window is 10 minutes, and you have ageBuckets=5, + * buckets will be switched every 2 minutes. The value is a trade-off between resources (memory and cpu for maintaining the bucket) + * and how smooth the time window is moved. + * @param quantiles The measurements to track for specifically over the sliding time window. + */ def noLabelsQuantiles[F[_]: Sync]( - cr: CollectorRegistry[F], - name: Name, - help: String, - maxAgeSeconds: Long, - ageBuckets: Int, - quantiles: Summary.Quantile* + pr: PrometheusRegistry[F], + name: Name, + help: String, + maxAgeSeconds: Long, + ageBuckets: Int, + quantiles: Summary.Quantile* ): F[Summary[F]] = for { c1 <- Sync[F].delay( - JSummary.build() - .name(name.getName) - .help(help) - .maxAgeSeconds(maxAgeSeconds) - .ageBuckets(ageBuckets) + JSummary + .builder() + .name(name.getName) + .help(help) + .maxAgeSeconds(maxAgeSeconds) + .numberOfAgeBuckets(ageBuckets) ) - c <- Sync[F].delay(quantiles.foldLeft(c1){ case (c, q) => c.quantile(q.quantile, q.error)}) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + c <- Sync[F].delay(quantiles.foldLeft(c1) { case (c, q) => + c.quantile(q.quantile, q.error) + }) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new NoLabelsSummary[F](out) - /** - * Default Constructor for a labelled [[Summary]]. - * - * maxAgeSeconds is set to [[defaultMaxAgeSeconds]] which is 10 minutes. - * - * ageBuckets is the number of buckets for the sliding time window, set to [[defaultAgeBuckets]] which is 5. - * - * This generates a specific number of labels via `Sized`, in combination with a function - * to generate an equally `Sized` set of labels from some type. Values are applied by position. - * - * This counter needs to have a label applied to the [[UnlabelledSummary]] in order to - * be measureable or recorded. - * - * @param cr CollectorRegistry this [[Summary]] will be registred with - * @param name The name of the [[Summary]]. - * @param help The help string of the metric - * @param labels The name of the labels to be applied to this metric - * @param f Function to take some value provided in the future to generate an equally sized list - * of strings as the list of labels. These are assigned to labels by position. - * @param quantiles The measurements to track for specifically over the sliding time window. - */ + /** Default Constructor for a labelled [[Summary]]. + * + * maxAgeSeconds is set to [[defaultMaxAgeSeconds]] which is 10 minutes. + * + * ageBuckets is the number of buckets for the sliding time window, set to [[defaultAgeBuckets]] which is 5. + * + * This generates a specific number of labels via `Sized`, in combination with a function + * to generate an equally `Sized` set of labels from some type. Values are applied by position. + * + * This counter needs to have a label applied to the [[UnlabelledSummary]] in order to + * be measureable or recorded. + * + * @param pr PrometheusRegistry this [[Summary]] will be registred with + * @param name The name of the [[Summary]]. + * @param help The help string of the metric + * @param labels The name of the labels to be applied to this metric + * @param f Function to take some value provided in the future to generate an equally sized list + * of strings as the list of labels. These are assigned to labels by position. + * @param quantiles The measurements to track for specifically over the sliding time window. + */ def labelled[F[_]: Sync, A, N <: Nat]( - cr: CollectorRegistry[F], - name: Name, - help: String, - labels: Sized[IndexedSeq[Label], N], - f: A => Sized[IndexedSeq[String], N], - quantiles: Summary.Quantile* + pr: PrometheusRegistry[F], + name: Name, + help: String, + labels: Sized[IndexedSeq[Label], N], + f: A => Sized[IndexedSeq[String], N], + quantiles: Summary.Quantile* ): F[UnlabelledSummary[F, A]] = - labelledQuantiles(cr, name, help, defaultMaxAgeSeconds, defaultAgeBuckets, labels, f, quantiles:_*) - - /** - * Constructor for a labelled [[Summary]]. - * - * maxAgeSeconds is set to [[defaultMaxAgeSeconds]] which is 10 minutes. - * - * ageBuckets is the number of buckets for the sliding time window, set to [[defaultAgeBuckets]] which is 5. - * - * This generates a specific number of labels via `Sized`, in combination with a function - * to generate an equally `Sized` set of labels from some type. Values are applied by position. - * - * This counter needs to have a label applied to the [[UnlabelledSummary]] in order to - * be measureable or recorded. - * - * @param cr CollectorRegistry this [[Summary]] will be registred with - * @param name The name of the [[Summary]]. - * @param help The help string of the metric - * @param maxAgeSeconds Set the duration of the time window is, - * i.e. how long observations are kept before they are discarded. - * @param ageBuckets Set the number of buckets used to implement the sliding time window. - * If your time window is 10 minutes, and you have ageBuckets=5, - * buckets will be switched every 2 minutes. - * The value is a trade-off between resources (memory and cpu for maintaining the bucket) - * and how smooth the time window is moved. - * @param labels The name of the labels to be applied to this metric - * @param f Function to take some value provided in the future to generate an equally sized list - * of strings as the list of labels. These are assigned to labels by position. - * @param quantiles The measurements to track for specifically over the sliding time window. - */ + labelledQuantiles( + pr, + name, + help, + defaultMaxAgeSeconds, + defaultAgeBuckets, + labels, + f, + quantiles: _* + ) + + /** Constructor for a labelled [[Summary]]. + * + * maxAgeSeconds is set to [[defaultMaxAgeSeconds]] which is 10 minutes. + * + * ageBuckets is the number of buckets for the sliding time window, set to [[defaultAgeBuckets]] which is 5. + * + * This generates a specific number of labels via `Sized`, in combination with a function + * to generate an equally `Sized` set of labels from some type. Values are applied by position. + * + * This counter needs to have a label applied to the [[UnlabelledSummary]] in order to + * be measureable or recorded. + * + * @param pr PrometheusRegistry this [[Summary]] will be registred with + * @param name The name of the [[Summary]]. + * @param help The help string of the metric + * @param maxAgeSeconds Set the duration of the time window is, + * i.e. how long observations are kept before they are discarded. + * @param ageBuckets Set the number of buckets used to implement the sliding time window. + * If your time window is 10 minutes, and you have ageBuckets=5, + * buckets will be switched every 2 minutes. + * The value is a trade-off between resources (memory and cpu for maintaining the bucket) + * and how smooth the time window is moved. + * @param labels The name of the labels to be applied to this metric + * @param f Function to take some value provided in the future to generate an equally sized list + * of strings as the list of labels. These are assigned to labels by position. + * @param quantiles The measurements to track for specifically over the sliding time window. + */ def labelledQuantiles[F[_]: Sync, A, N <: Nat]( - cr: CollectorRegistry[F], - name: Name, - help: String, - maxAgeSeconds: Long, - ageBuckets: Int, - labels: Sized[IndexedSeq[Label], N], - f: A => Sized[IndexedSeq[String], N], - quantiles: Summary.Quantile* + pr: PrometheusRegistry[F], + name: Name, + help: String, + maxAgeSeconds: Long, + numberOfAgeBuckets: Int, + labels: Sized[IndexedSeq[Label], N], + f: A => Sized[IndexedSeq[String], N], + quantiles: Summary.Quantile* ): F[UnlabelledSummary[F, A]] = for { c1 <- Sync[F].delay( - JSummary.build() - .name(name.getName) - .help(help) - .maxAgeSeconds(maxAgeSeconds) - .ageBuckets(ageBuckets) - .labelNames(labels.unsized.map(_.getLabel):_*) + JSummary + .builder() + .name(name.getName) + .help(help) + .maxAgeSeconds(maxAgeSeconds) + .numberOfAgeBuckets(numberOfAgeBuckets) + .labelNames(labels.unsized.map(_.getLabel): _*) ) - c <- Sync[F].delay(quantiles.foldLeft(c1){ case (c, q) => c.quantile(q.quantile, q.error)}) - out <- Sync[F].delay(c.register(CollectorRegistry.Unsafe.asJava(cr))) + c <- Sync[F].delay(quantiles.foldLeft(c1) { case (c, q) => + c.quantile(q.quantile, q.error) + }) + out <- Sync[F].delay(c.register(PrometheusRegistry.Unsafe.asJava(pr))) } yield new UnlabelledSummaryImpl[F, A](out, f.andThen(_.unsized)) - final private[epimetheus] class NoLabelsSummary[F[_]: Sync] private[epimetheus] ( - private[epimetheus] val underlying: JSummary + final private[epimetheus] class NoLabelsSummary[ + F[_]: Sync + ] private[epimetheus] ( + private[epimetheus] val underlying: JSummary ) extends Summary[F] { def observe(d: Double): F[Unit] = Sync[F].delay(underlying.observe(d)) override private[epimetheus] def asJava: F[JSummary] = underlying.pure[F] } - final private[epimetheus] class LabelledSummary[F[_]: Sync] private[epimetheus] ( - private[epimetheus] val underlying: JSummary.Child + final private[epimetheus] class LabelledSummary[ + F[_]: Sync + ] private[epimetheus] ( + private[epimetheus] val underlying: DistributionDataPoint ) extends Summary[F] { def observe(d: Double): F[Unit] = Sync[F].delay(underlying.observe(d)) override private[epimetheus] def asJava: F[JSummary] = - ApplicativeThrow[F].raiseError(new IllegalArgumentException("Cannot Get Underlying Parent with Labels Applied")) + ApplicativeThrow[F].raiseError( + new IllegalArgumentException( + "Cannot Get Underlying Parent with Labels Applied" + ) + ) } - final private[epimetheus] class MapKSummary[F[_], G[_]](private[epimetheus] val base: Summary[F], fk: F ~> G) extends Summary[G]{ + final private[epimetheus] class MapKSummary[F[_], G[_]]( + private[epimetheus] val base: Summary[F], + fk: F ~> G + ) extends Summary[G] { def observe(d: Double): G[Unit] = fk(base.observe(d)) override private[epimetheus] def asJava = fk(base.asJava) } - /** - * Generic Unlabeled Summary - * - * Apply a label to be able to measure events. - */ - sealed trait UnlabelledSummary[F[_], A]{ + /** Generic Unlabeled Summary + * + * Apply a label to be able to measure events. + */ + sealed trait UnlabelledSummary[F[_], A] { def label(a: A): Summary[F] - def mapK[G[_]](fk: F ~> G): UnlabelledSummary[G, A] = new MapKUnlabelledSummary[F,G, A](this, fk) + def mapK[G[_]](fk: F ~> G): UnlabelledSummary[G, A] = + new MapKUnlabelledSummary[F, G, A](this, fk) } - final private[epimetheus] class UnlabelledSummaryImpl[F[_]: Sync, A] private[epimetheus]( - private[epimetheus] val underlying: JSummary, - private val f: A => IndexedSeq[String] - ) extends UnlabelledSummary[F,A]{ + final private[epimetheus] class UnlabelledSummaryImpl[ + F[_]: Sync, + A + ] private[epimetheus] ( + private[epimetheus] val underlying: JSummary, + private val f: A => IndexedSeq[String] + ) extends UnlabelledSummary[F, A] { def label(a: A): Summary[F] = - new LabelledSummary[F](underlying.labels(f(a):_*)) + new LabelledSummary[F](underlying.labelValues(f(a): _*)) } - final private[epimetheus] class MapKUnlabelledSummary[F[_], G[_], A](private[epimetheus] val base: UnlabelledSummary[F,A], fk: F ~> G) extends UnlabelledSummary[G, A]{ + final private[epimetheus] class MapKUnlabelledSummary[F[_], G[_], A]( + private[epimetheus] val base: UnlabelledSummary[F, A], + fk: F ~> G + ) extends UnlabelledSummary[G, A] { def label(a: A): Summary[G] = base.label(a).mapK(fk) } trait QuantileCommons { - /** - * Safe Constructor of a Quantile valid values for both values are greater than 0 - * but less than 1. - */ - def impl(quantile: Double, error: Double): Either[IllegalArgumentException, Summary.Quantile] = { - if (quantile < 0.0 || quantile > 1.0) Either.left(new IllegalArgumentException("Quantile " + quantile + " invalid: Expected number between 0.0 and 1.0.")) - else if (error < 0.0 || error > 1.0) Either.left(new IllegalArgumentException("Error " + error + " invalid: Expected number between 0.0 and 1.0.")) + + /** Safe Constructor of a Quantile valid values for both values are greater than 0 + * but less than 1. + */ + def impl( + quantile: Double, + error: Double + ): Either[IllegalArgumentException, Summary.Quantile] = { + if (quantile < 0.0 || quantile > 1.0) + Either.left( + new IllegalArgumentException( + "Quantile " + quantile + " invalid: Expected number between 0.0 and 1.0." + ) + ) + else if (error < 0.0 || error > 1.0) + Either.left( + new IllegalArgumentException( + "Error " + error + " invalid: Expected number between 0.0 and 1.0." + ) + ) else Either.right(new Summary.Quantile(quantile, error)) } - def implF[F[_]: ApplicativeThrow](quantile: Double, error: Double): F[Summary.Quantile] = + def implF[F[_]: ApplicativeThrow]( + quantile: Double, + error: Double + ): F[Summary.Quantile] = impl(quantile, error).liftTo[F] } object Unsafe { @tailrec - def asJavaUnlabelled[F[_], A](g: UnlabelledSummary[F, A]): JSummary = g match { - case a: UnlabelledSummaryImpl[F, A] => a.underlying - case a: MapKUnlabelledSummary[f, _, a] => asJavaUnlabelled(a.base) - } + def asJavaUnlabelled[F[_], A](g: UnlabelledSummary[F, A]): JSummary = + g match { + case a: UnlabelledSummaryImpl[F, A] => a.underlying + case a: MapKUnlabelledSummary[f, _, a] => asJavaUnlabelled(a.base) + } def asJava[F[_]](c: Summary[F]): F[JSummary] = c.asJava - def fromJava[F[_]: Sync](s: JSummary.Child): Summary[F] = new LabelledSummary(s) - def fromJavaUnlabelled[F[_]: Sync](s: JSummary): Summary[F] = new NoLabelsSummary(s) + def fromJava[F[_]: Sync](s: DistributionDataPoint): Summary[F] = + new LabelledSummary(s) + def fromJavaUnlabelled[F[_]: Sync](s: JSummary): Summary[F] = + new NoLabelsSummary(s) } } diff --git a/core/src/test/scala/io/chrisdavenport/epimetheus/CounterSpec.scala b/core/src/test/scala/io/chrisdavenport/epimetheus/CounterSpec.scala index 89fd9f3b..1036a729 100644 --- a/core/src/test/scala/io/chrisdavenport/epimetheus/CounterSpec.scala +++ b/core/src/test/scala/io/chrisdavenport/epimetheus/CounterSpec.scala @@ -2,12 +2,14 @@ package io.chrisdavenport.epimetheus import cats.effect._ +import scala.jdk.CollectionConverters._ + class CounterSpec extends munit.CatsEffectSuite { test("Counter No Labels: Register cleanly in the collector") { val test = for { - cr <- CollectorRegistry.build[IO] - counter <- Counter.noLabels[IO](cr, Name("boo"), "Boo Counter") + pr <- PrometheusRegistry.build[IO] + counter <- Counter.noLabels[IO](pr, Name("boo"), "Boo Counter") } yield counter test.attempt.map(_.isRight).assert @@ -15,10 +17,10 @@ class CounterSpec extends munit.CatsEffectSuite { test("Counter No Labels: Increase correctly") { val test = for { - cr <- CollectorRegistry.build[IO] - counter <- Counter.noLabels[IO](cr, Name("boo"), "Boo Counter") + pr <- PrometheusRegistry.build[IO] + counter <- Counter.noLabels[IO](pr, Name("boo"), "Boo Counter") _ <- counter.inc - out <- counter.get + out <- counter.asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield out test.assertEquals(1D) @@ -26,8 +28,8 @@ class CounterSpec extends munit.CatsEffectSuite { test("Counter Labelled: Register cleanly in the collector") { val test = for { - cr <- CollectorRegistry.build[IO] - counter <- Counter.labelled(cr, Name("boo"), "Boo Counter", Sized(Label("foo")), {(s: String) => Sized(s)}) + pr <- PrometheusRegistry.build[IO] + counter <- Counter.labelled(pr, Name("boo"), "Boo Counter", Sized(Label("foo")), {(s: String) => Sized(s)}) } yield counter test.attempt.map(_.isRight).assert @@ -35,10 +37,10 @@ class CounterSpec extends munit.CatsEffectSuite { test("Counter Labelled: Increase correctly") { val test = for { - cr <- CollectorRegistry.build[IO] - counter <- Counter.labelled(cr, Name("boo"), "Boo Counter", Sized(Label("foo")), {(s: String) => Sized(s)}) + pr <- PrometheusRegistry.build[IO] + counter <- Counter.labelled(pr, Name("boo"), "Boo Counter", Sized(Label("foo")), {(s: String) => Sized(s)}) _ <- counter.label("foo").inc - out <- counter.label("foo").get + out <- counter.label("foo").asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield out test.assertEquals(1D) diff --git a/core/src/test/scala/io/chrisdavenport/epimetheus/GaugeSpec.scala b/core/src/test/scala/io/chrisdavenport/epimetheus/GaugeSpec.scala index f2c3c25a..acebc98f 100644 --- a/core/src/test/scala/io/chrisdavenport/epimetheus/GaugeSpec.scala +++ b/core/src/test/scala/io/chrisdavenport/epimetheus/GaugeSpec.scala @@ -2,14 +2,16 @@ package io.chrisdavenport.epimetheus import cats.effect._ import io.chrisdavenport.epimetheus.implicits._ + import scala.concurrent.duration._ +import scala.jdk.CollectionConverters._ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge No Labels: Register cleanly in the collector") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels[IO](cr, Name("boo"), "Boo Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels[IO](pr, Name("boo"), "Boo Gauge") } yield gauge test.attempt.map(_.isRight).assert @@ -17,10 +19,10 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge No Labels: Increase correctly") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels[IO](cr, Name("boo"), "Boo Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels[IO](pr, Name("boo"), "Boo Gauge") _ <- gauge.inc - out <- gauge.get + out <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield out test.assertEquals(1D) @@ -28,11 +30,11 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge No Labels: Decrease correctly") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels[IO](cr, Name("boo"), "Boo Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels[IO](pr, Name("boo"), "Boo Gauge") _ <- gauge.inc _ <- gauge.dec - out <- gauge.get + out <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield out test.assertEquals(0D) @@ -41,10 +43,10 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge No Labels: Set correctly") { val set = 52D val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels[IO](cr, Name("boo"), "Boo Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels[IO](pr, Name("boo"), "Boo Gauge") _ <- gauge.set(set) - out <- gauge.get + out <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield out test.assertEquals(set) @@ -52,8 +54,8 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge Labelled: Register cleanly in the collector") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.labelled(cr, Name("boo"), "Boo Gauge", Sized(Label("boo")), { (s: String) => Sized(s) }) + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.labelled(pr, Name("boo"), "Boo Gauge", Sized(Label("boo")), { (s: String) => Sized(s) }) } yield gauge test.attempt.map(_.isRight).assert @@ -61,10 +63,10 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge Labelled: Increase correctly") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.labelled(cr, Name("boo"), "Boo Gauge", Sized(Label("boo")), { (s: String) => Sized(s) }) + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.labelled(pr, Name("boo"), "Boo Gauge", Sized(Label("boo")), { (s: String) => Sized(s) }) _ <- gauge.label("boo").inc - out <- gauge.label("boo").get + out <- gauge.label("boo").asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield out test.assertEquals(1D) @@ -72,11 +74,11 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge Labelled: Decrease correctly") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.labelled(cr, Name("boo"), "Boo Gauge", Sized(Label("boo")), { (s: String) => Sized(s) }) + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.labelled(pr, Name("boo"), "Boo Gauge", Sized(Label("boo")), { (s: String) => Sized(s) }) _ <- gauge.label("boo").inc _ <- gauge.label("boo").dec - out <- gauge.label("boo").get + out <- gauge.label("boo").asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield out test.assertEquals(0D) @@ -85,10 +87,10 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge Labelled: Set correctly") { val set = 52D val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.labelled(cr, Name("boo"), "Boo Gauge", Sized(Label("boo")), { (s: String) => Sized(s) }) + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.labelled(pr, Name("boo"), "Boo Gauge", Sized(Label("boo")), { (s: String) => Sized(s) }) _ <- gauge.label("boo").set(set) - out <- gauge.label("boo").get + out <- gauge.label("boo").asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield out test.assertEquals(set) @@ -96,15 +98,15 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge Convenience: incIn an operation succesfully") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels[IO](cr, Name("boo"), "Boo Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels[IO](pr, Name("boo"), "Boo Gauge") defer <- Deferred[IO, Unit] fib <- gauge.incIn(defer.get).start _ <- IO.sleep(1.second) - current <- gauge.get + current <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) _ <- defer.complete(()) _ <- fib.join - after <- gauge.get + after <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield (current, after) test.assertEquals((1D, 0D)) @@ -112,15 +114,15 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge Convenience: incByIn an operation succesfully") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels[IO](cr, Name("boo"), "Boo Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels[IO](pr, Name("boo"), "Boo Gauge") defer <- Deferred[IO, Unit] fib <- gauge.incByIn(defer.get, 10).start _ <- IO.sleep(1.second) - current <- gauge.get + current <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) _ <- defer.complete(()) _ <- fib.join - after <- gauge.get + after <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield (current, after) test.assertEquals((10D, 0D)) @@ -128,16 +130,16 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge Convenience: decIn an operation succesfully") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels[IO](cr, Name("boo"), "Boo Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels[IO](pr, Name("boo"), "Boo Gauge") _ <- gauge.inc defer <- Deferred[IO, Unit] fib <- gauge.decIn(defer.get).start _ <- IO.sleep(1.second) - current <- gauge.get + current <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) _ <- defer.complete(()) _ <- fib.join - after <- gauge.get + after <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield (current, after) test.assertEquals((0D, 1D)) @@ -145,16 +147,16 @@ class GaugeSpec extends munit.CatsEffectSuite { test("Gauge Convenience: decByIn an operation succesfully") { val test = for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels[IO](cr, Name("boo"), "Boo Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels[IO](pr, Name("boo"), "Boo Gauge") _ <- gauge.incBy(10) defer <- Deferred[IO, Unit] fib <- gauge.decByIn(defer.get, 10).start _ <- IO.sleep(1.second) - current <- gauge.get + current <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) _ <- defer.complete(()) _ <- fib.join - after <- gauge.get + after <- gauge.asJava.map(_.collect().getDataPoints.asScala.last.getValue) } yield (current, after) test.assertEquals((0D, 10D)) diff --git a/core/src/test/scala/io/chrisdavenport/epimetheus/HistogramSpec.scala b/core/src/test/scala/io/chrisdavenport/epimetheus/HistogramSpec.scala index 9a4405a9..f6b07ff7 100644 --- a/core/src/test/scala/io/chrisdavenport/epimetheus/HistogramSpec.scala +++ b/core/src/test/scala/io/chrisdavenport/epimetheus/HistogramSpec.scala @@ -5,8 +5,8 @@ import cats.effect._ class HistogramSpec extends munit.CatsEffectSuite { test("Histogram No Labels: Register cleanly in the collector") { val test = for { - cr <- CollectorRegistry.build[IO] - h <- Histogram.noLabelsBuckets[IO](cr, Name("boo"), "Boo ", 0.1, 0.2, 0.3, 0.4) + pr <- PrometheusRegistry.build[IO] + h <- Histogram.noLabelsBuckets[IO](pr, Name("boo"), "Boo ", 0.1, 0.2, 0.3, 0.4) } yield h test.attempt.map(_.isRight).assert @@ -14,8 +14,8 @@ class HistogramSpec extends munit.CatsEffectSuite { test("Histogram Labelled: Register cleanly in the collector") { val test = for { - cr <- CollectorRegistry.build[IO] - h <- Histogram.labelledBuckets(cr, Name("boo"), "Boo ", Sized(Label("boo")), { (s: String) => Sized(s) }, 0.1, 0.2, 0.3, 0.4) + pr <- PrometheusRegistry.build[IO] + h <- Histogram.labelledBuckets(pr, Name("boo"), "Boo ", Sized(Label("boo")), { (s: String) => Sized(s) }, 0.1, 0.2, 0.3, 0.4) } yield h test.attempt.map(_.isRight).assert diff --git a/core/src/test/scala/io/chrisdavenport/epimetheus/SummarySpec.scala b/core/src/test/scala/io/chrisdavenport/epimetheus/SummarySpec.scala index b6d6bd8c..658e4aef 100644 --- a/core/src/test/scala/io/chrisdavenport/epimetheus/SummarySpec.scala +++ b/core/src/test/scala/io/chrisdavenport/epimetheus/SummarySpec.scala @@ -5,8 +5,8 @@ import cats.effect._ class SummarySpec extends munit.CatsEffectSuite { test("Summary No Labels: Register cleanly in the collector") { val test = for { - cr <- CollectorRegistry.build[IO] - s <- Summary.noLabels[IO](cr, Name("boo"), "Boo ", Summary.quantile(0.5, 0.05)) + pr <- PrometheusRegistry.build[IO] + s <- Summary.noLabels[IO](pr, Name("boo"), "Boo ", Summary.quantile(0.5, 0.05)) } yield s test.attempt.map(_.isRight).assert @@ -14,8 +14,8 @@ class SummarySpec extends munit.CatsEffectSuite { test("Summary Labelled: Register cleanly in the collector") { val test = for { - cr <- CollectorRegistry.build[IO] - s <- Summary.labelled(cr, Name("boo"), "Boo ", Sized(Label("boo")), { (s: String) => Sized(s) }, Summary.quantile(0.5, 0.05)) + pr <- PrometheusRegistry.build[IO] + s <- Summary.labelled(pr, Name("boo"), "Boo ", Sized(Label("boo")), { (s: String) => Sized(s) }, Summary.quantile(0.5, 0.05)) } yield s test.attempt.map(_.isRight).assert diff --git a/docs/docs/04-CollectorRegistry.md b/docs/docs/04-CollectorRegistry.md index 04832fa9..6f22cf20 100644 --- a/docs/docs/04-CollectorRegistry.md +++ b/docs/docs/04-CollectorRegistry.md @@ -1,22 +1,22 @@ -# Collector Registry +# Prometheus Registry -A `CollectorRegistry` represents the concurrently shared state which holds the information +A `PrometheusRegistry` represents the concurrently shared state which holds the information of the metrics in question. You can view this as the entry-point to any metrics. It collects and keeps the relevant data for all of the metrics. -**Caution:** Only a single metric with a given name can be registered with a `CollectorRegistry`, +**Caution:** Only a single metric with a given name can be registered with a `PrometheusRegistry`, attempting to register a metric with the same name twice, will yield an error. ## On Creation -Due to how prometheus scraping occurs, only one `CollectorRegistry` is generally useful per application. There are generally 2 approaches. +Due to how prometheus scraping occurs, only one `PrometheusRegistry` is generally useful per application. There are generally 2 approaches. 1. Create your own registry. Register Metrics with it. Expose that. - Advantages: Full Control Of the Code -2. Use the global `CollectorRegistry.defaultRegistry` +2. Use the global `PrometheusRegistry.defaultRegistry` - Advantages: Easier Interop with Java libraries that may not give an option for interaction with arbitrary CollectorRegistries. Imports @@ -40,8 +40,8 @@ shared space. ```scala mdoc { for { - cr <- CollectorRegistry.build[IO] - } yield cr + pr <- PrometheusRegistry.build[IO] + } yield pr } ``` @@ -50,8 +50,8 @@ You can build your own registry with the default Hotspot Metrics registered with ```scala mdoc { for { - cr <- CollectorRegistry.buildWithDefaults[IO] - } yield cr + pr <- PrometheusRegistry.buildWithDefaults[IO] + } yield pr } ``` @@ -60,9 +60,9 @@ or you can do it yourself. ```scala mdoc { for { - cr <- CollectorRegistry.build[IO] - _ <- Collector.Defaults.registerDefaults(cr) - } yield cr + pr <- PrometheusRegistry.build[IO] + _ <- Collector.Defaults.registerDefaults(pr) + } yield pr } ``` @@ -74,32 +74,9 @@ as well as a method which initializes the default metrics into the default. This You have access to the global pool automatically. ```scala mdoc -CollectorRegistry.defaultRegistry[IO] -``` - -You can also ensure the baseline has been initiated. - -```scala mdoc -Collector.Defaults.defaultCollectorRegisterDefaults[IO] -``` - -## Exporting Your Metrics - -Each `CollectorRegistry` exposes a `write004` function which will write out the current value of the metrics with the appropriate encoding for the 004 encoding that Prometheus expects. - -```scala mdoc -val exportExample = { - for { - cr <- CollectorRegistry.build[IO] - _ <- Collector.Defaults.registerDefaults(cr) - currentMetrics <- cr.write004 - _ <- IO(println(currentMetrics)) - } yield () -} - -exportExample.unsafeRunSync() +PrometheusRegistry.defaultRegistry[IO] ``` ## Conclusion -Regardless which strategy you choose to use to get access to your `CollectorRegistry`, you will need to have one to work and build your metrics moving forward. It is the fundamental shared space on which metrics, and groups of metrics are based upon. +Regardless which strategy you choose to use to get access to your `PrometheusRegistry`, you will need to have one to work and build your metrics moving forward. It is the fundamental shared space on which metrics, and groups of metrics are based upon. diff --git a/docs/docs/05-Counter.md b/docs/docs/05-Counter.md index efaf9a34..43de52c8 100644 --- a/docs/docs/05-Counter.md +++ b/docs/docs/05-Counter.md @@ -24,14 +24,14 @@ An Example Counter without Labels: ```scala mdoc val noLabelsExample = { for { - cr <- CollectorRegistry.build[IO] + pr <- PrometheusRegistry.build[IO] successCounter <- Counter.noLabels( - cr, + pr, Name("example_success_total"), "Example Counter of Success" ) failureCounter <- Counter.noLabels( - cr, + pr, Name("example_failure_total"), "Example Counter of Failure" ) @@ -39,7 +39,7 @@ val noLabelsExample = { case Outcome.Succeeded(_) => successCounter.inc case _ => failureCounter.inc } - out <- cr.write004 + out <- pr.write004 } yield out } @@ -51,9 +51,9 @@ An Example of a Counter with Labels: ```scala mdoc val labelledExample = { for { - cr <- CollectorRegistry.build[IO] + pr <- PrometheusRegistry.build[IO] counter <- Counter.labelled( - cr, + pr, Name("example_total"), "Example Counter", Sized(Label("foo")), @@ -61,7 +61,7 @@ val labelledExample = { ) _ <- counter.label("bar").inc _ <- counter.label("baz").inc - out <- cr.write004 + out <- pr.write004 } yield out } @@ -92,9 +92,9 @@ trait FooAlg[F[_]]{ val fooAgebraExample = { for { - cr <- CollectorRegistry.build[IO] + pr <- PrometheusRegistry.build[IO] counter <- Counter.labelled( - cr, + pr, Name("example_total"), "Example Counter", Sized(Label("foo")), @@ -104,7 +104,7 @@ val fooAgebraExample = { _ <- foo.bar _ <- foo.bar _ <- foo.baz - out <- cr.write004 + out <- pr.write004 } yield out } @@ -114,7 +114,7 @@ fooAgebraExample.unsafeRunSync() We force labels to always match the same size. This will fail to compile. ```scala -def incorrectlySized[F[_]: Sync](cr: CollectorRegistry[F]) = { - Counter.labelled(cr, Name("fail"), "Example Failure", Sized(Label("color"), Name("method")), {s: String => Sized(s)}) +def incorrectlySized[F[_]: Sync](pr: PrometheusRegistry[F]) = { + Counter.labelled(pr, Name("fail"), "Example Failure", Sized(Label("color"), Name("method")), {s: String => Sized(s)}) } ``` diff --git a/docs/docs/06-Gauge.md b/docs/docs/06-Gauge.md index 6b4b0739..863837c9 100644 --- a/docs/docs/06-Gauge.md +++ b/docs/docs/06-Gauge.md @@ -23,12 +23,12 @@ An Example of a Gauge with no labels: ```scala mdoc val noLabelsGaugeExample = { for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels(cr, Name("gauge_total"), "Example Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels(pr, Name("gaugetotal"), "Example Gauge") _ <- gauge.inc _ <- gauge.inc _ <- gauge.dec - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 } yield currentMetrics } @@ -40,10 +40,10 @@ An Example of a Gauge with labels: ```scala mdoc val labelledGaugeExample = { for { - cr <- CollectorRegistry.build[IO] + pr <- PrometheusRegistry.build[IO] gauge <- Gauge.labelled( - cr, - Name("gauge_total"), + pr, + Name("gaugetotal"), "Example Gauge", Sized(Label("foo")), {s: String => Sized(s)} @@ -53,7 +53,7 @@ val labelledGaugeExample = { _ <- gauge.label("bar").inc _ <- gauge.label("baz").inc _ <- gauge.label("bar").dec - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 } yield currentMetrics } diff --git a/docs/docs/07-Histogram.md b/docs/docs/07-Histogram.md index 4d815cc1..10a48027 100644 --- a/docs/docs/07-Histogram.md +++ b/docs/docs/07-Histogram.md @@ -36,11 +36,11 @@ And Example of a Histogram with no labels: ```scala mdoc val noLabelsHistogramExample = { for { - cr <- CollectorRegistry.build[IO] - h <- Histogram.noLabels(cr, Name("example_histogram"), "Example Histogram") + pr <- PrometheusRegistry.build[IO] + h <- Histogram.noLabels(pr, Name("example_histogram"), "Example Histogram") _ <- h.observe(0.2) _ <- h.timed(Temporal[IO].sleep(1.second), SECONDS) - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 _ <- IO(println(currentMetrics)) } yield () } @@ -53,9 +53,9 @@ An Example of a Histogram with labels: ```scala mdoc val labelledHistogramExample = { for { - cr <- CollectorRegistry.build[IO] + pr <- PrometheusRegistry.build[IO] h <- Histogram.labelled( - cr, + pr, Name("example_histogram"), "Example Histogram", Sized(Label("foo")), @@ -63,7 +63,7 @@ val labelledHistogramExample = { ) _ <- h.label("bar").observe(0.2) _ <- h.label("baz").timed(Temporal[IO].sleep(1.second), SECONDS) - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 _ <- IO(println(currentMetrics)) } yield () } diff --git a/docs/docs/08-Summary.md b/docs/docs/08-Summary.md index 535fb001..f18e2623 100644 --- a/docs/docs/08-Summary.md +++ b/docs/docs/08-Summary.md @@ -32,9 +32,9 @@ And Example of a Summary with no labels: ```scala mdoc val noLabelsSummaryExample = { for { - cr <- CollectorRegistry.build[IO] + pr <- PrometheusRegistry.build[IO] s <- Summary.noLabels( - cr, + pr, Name("example_summary"), "Example Summary", Summary.quantile(0.5,0.05) @@ -42,7 +42,7 @@ val noLabelsSummaryExample = { _ <- s.observe(0.1) _ <- s.observe(0.2) _ <- s.observe(1.0) - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 _ <- IO(println(currentMetrics)) } yield () } @@ -55,9 +55,9 @@ An Example of a Summary with labels: ```scala mdoc val withLabelsSummaryExample = { for { - cr <- CollectorRegistry.build[IO] + pr <- PrometheusRegistry.build[IO] s <- Summary.labelled( - cr, + pr, Name("example_summary"), "Example Summary", Sized(Label("foo")), @@ -67,7 +67,7 @@ val withLabelsSummaryExample = { _ <- s.label("bar").observe(0.1) _ <- s.label("baz").observe(0.2) _ <- s.label("baz").observe(1.0) - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 _ <- IO(println(currentMetrics)) } yield () } diff --git a/docs/index.md b/docs/index.md index 81f8d4ab..eb09cd6a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,10 +35,10 @@ import cats.effect.unsafe.implicits.global ```scala mdoc val noLabelsCounterExample = { for { - cr <- CollectorRegistry.build[IO] - counter <- Counter.noLabels(cr, Name("counter_total"), "Example Counter") + pr <- PrometheusRegistry.build[IO] + counter <- Counter.noLabels(pr, Name("counter_total"), "Example Counter") _ <- counter.inc - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 } yield currentMetrics } @@ -50,12 +50,12 @@ noLabelsCounterExample.unsafeRunSync() ```scala mdoc val noLabelsGaugeExample = { for { - cr <- CollectorRegistry.build[IO] - gauge <- Gauge.noLabels(cr, Name("gauge_total"), "Example Gauge") + pr <- PrometheusRegistry.build[IO] + gauge <- Gauge.noLabels(pr, Name("gaugetotal"), "Example Gauge") _ <- gauge.inc _ <- gauge.inc _ <- gauge.dec - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 } yield currentMetrics } @@ -67,11 +67,11 @@ noLabelsGaugeExample.unsafeRunSync() ```scala mdoc val noLabelsHistogramExample = { for { - cr <- CollectorRegistry.build[IO] - h <- Histogram.noLabels(cr, Name("example_histogram"), "Example Histogram") + pr <- PrometheusRegistry.build[IO] + h <- Histogram.noLabels(pr, Name("example_histogram"), "Example Histogram") _ <- h.observe(0.2) _ <- h.timed(Temporal[IO].sleep(1.second), SECONDS) - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 } yield currentMetrics } @@ -83,12 +83,12 @@ noLabelsHistogramExample.unsafeRunSync() ```scala mdoc val noLabelsSummaryExample = { for { - cr <- CollectorRegistry.build[IO] - s <- Summary.noLabels(cr, Name("example_summary"), "Example Summary", Summary.quantile(0.5,0.05)) + pr <- PrometheusRegistry.build[IO] + s <- Summary.noLabels(pr, Name("example_summary"), "Example Summary", Summary.quantile(0.5,0.05)) _ <- s.observe(0.1) _ <- s.observe(0.2) _ <- s.observe(1.0) - currentMetrics <- cr.write004 + currentMetrics <- pr.write004 } yield currentMetrics }