Metrics DSL
The metrics DSL provides a fluent API for constructing primitive and derived metrics from a MetricFactory. It
is designed to provide some compile time safety when recording metrics in terms of matching label values to label
names. It does not check that a metric is unique - this is only checked at runtime, the uniqueness of a
metric (name & labels combination) depends on the MetricFactory implementation.
The examples in this section assume you have imported the following and have created a MetricFactory:
import cats.effect._
import prometheus4cats._
val factory: MetricFactory[IO] = MetricFactory.builder.noop[IO]
Expected Behaviour
Every metric created using this DSL returns a Cats-Effect Resource which indicates the lifecycle of that metric. When
the Resource is allocated the metric is registered and when it is finalized it is de-registered.
It should be possible to request/register the same metric multiple times without error, where you will be returned
the currently registered instance rather than a new instance. This does depend on the implementation of
MetricRegistry however, the provided Java wrapper implementation
implements this via reference counting and implementers of MetricRegistry are advised to do the same in order to
preserve this expected behaviour at runtime.
Refined Types
Value classes exist for metric and label names that are refined at compile time from string literals. It is also
possible to refine at runtime, where the result is returned in an Either.
The value classes used by the DSL are as follows:
Counter.NameGauge.NameHistogram.NameSummary.NameInfo.NameLabel.NameMetric.Help
When used in the DSL with string literals the value classes are implicitly resolved, so there is no need to wrap every value.
Choosing a Primitive Metric
factory.counter("counter_total")
factory.gauge("gauge")
factory.histogram("histogram")
factory.nativeHistogram("native_histogram")
factory.summary("summary")
factory.info("info_info")
All metric values are Double by default — the underlying Prometheus wire format stores everything as double, so a
separate Long path was duplication. Long-valued sources should .contramap[Long](_.toDouble) on the resulting DSL.
Defining the Help String
factory.counter("counter_total").help("Describe what this metric does")
Building a Simple Metric
Once you have specified all the parameters with which you want to create your metric you can call the build method.
This will return a cats.effect.Resource of your desired metric, which will de-register the metric from the underlying
MetricRegistry upon finalization.
val simpleCounter = factory
.counter("counter_total")
.help("Describe what this metric does")
simpleCounter.build
While not recommended, it is possible to build the metric without a cats.effect.Resource, which will not de-register
from the underlying MetricRegistry:
simpleCounter.unsafeBuild
Adding Labels
Adding Individual Labels
case class MyClass(value: String)
val tupleLabelledCounter = factory
.counter("counter_total")
.help("Describe what this metric does")
.label[String]("this_uses_show")
.label[MyClass]("this_doesnt_use_show", _.value)
tupleLabelledCounter.build.evalMap(_.inc(2.0, ("label_value", MyClass("label_value"))))
Compile-Time Checked Sequence of Labels
case class MyMultiClass(value1: String, value2: Int)
val classLabelledCounter = factory
.counter("counter_total")
.help("Describe what this metric does")
.labels[MyMultiClass](
Label.Name("label1") -> (_.value1),
Label.Name("label2") -> (_.value2.toString)
)
classLabelledCounter.build.evalMap(_.inc(2.0, MyMultiClass("label_value", 42)))
Unchecked Sequence of Labels
val unsafeLabelledCounter = factory
.counter("counter_total")
.help("Describe what this metric does")
.unsafeLabels(Label.Name("label1"), Label.Name("label2"))
val labels = Map(
Label.Name("label1") -> "label1_value",
Label.Name("label2") -> "label1_value"
)
unsafeLabelledCounter.build.evalMap(_.inc(3.0, labels))
Contramapping a Metric Type
Simple Metric
val intCounter: Resource[IO, Counter[IO, Int, Unit]] = factory
.counter("counter_total")
.help("Describe what this metric does")
.contramap[Int](_.toDouble)
.build
val shortCounter: Resource[IO, Counter[IO, Short, Unit]] =
intCounter.map(_.contramap[Short](_.toInt))
Labelled Metric
val intLabelledCounter: Resource[IO, Counter[IO, Int, (String, Int)]] = factory
.counter("counter_total")
.help("Describe what this metric does")
.label[String]("string_label")
.label[Int]("int_label")
.contramap[Int](_.toDouble)
.build
val shortLabelledCounter: Resource[IO, Counter[IO, Short, (String, Int)]] =
intLabelledCounter.map(_.contramap[Short](_.toInt))
Contramapping Metric Labels
This can work as a nice alternative to providing a compile-time checked sequence of labels
case class LabelsClass(string: String, int: Int)
val updatedLabelsCounter: Resource[IO, Counter[IO, Double, LabelsClass]] = factory
.counter("counter_total")
.help("Describe what this metric does")
.label[String]("string_label")
.label[Int]("int_label")
.contramapLabels[LabelsClass](c => (c.string, c.int))
.build