Primitive Metric Types

For more detailed information on building metrics see the Metrics DSL section.

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.noop[IO]
// factory: MetricFactory[IO] = prometheus4cats.MetricFactory$$anon$36@2faddb07

Counter

This implements an OpenMetrics counter, allowing a number to be incremented by 1 or some positive value.

See the example below on how to obtain a Counter from a MetricFactory:

factory.counter("my_counter_total").ofLong.help("Metric description")

Gauge

This implements an OpenMetrics gauge, allowing a number to be incremented and decremented by 1 or some positive value, as well as reset to 0.

See the example below on how to obtain a Gauge from a MetricFactory:

factory.gauge("my_summary").ofLong.help("Metric description")

Histogram

This implements an OpenMetrics histogram, allowing a number to be recorded on a distribution of pre-defined buckets.

See the example below on how to obtain a Histogram from a MetricFactory:

val histogram = factory
  .histogram("my_histogram")
  .ofDouble
  .help("Metric description")

User Defined Buckets

You can set buckets statically with your own values, the type of these values must match that of the Histogram (either Double or Long).

histogram.buckets(0.1, 0.5, 1.0, 1.5, 2.0)

You can also use pre-defined values for default HTTP timing buckets.

Note that this syntax is only available on Histograms that record Double values.

histogram.defaultHttpBuckets

Generated Buckets

It is possible to generate buckets of a certain size, which is specified at compile time with a natural number.

In Scala 2, you must import Shapeless' Nat, whereas in Scala 3 this will work with integers.

import shapeless.Nat

Linear buckets may be generated using the method below:

histogram.linearBuckets[Nat._10](start = 1, width = 3)

Exponential buckets may be generated using the method below:

Note that this syntax is only available on Histograms that record Double values.

histogram.exponentialBuckets[Nat._5](start = 1.0, factor = 1.5)

Summary

This implements an OpenMetrics summary, allowing a number to be recorded against a calculated.

See the example below on how to obtain a Summary from a MetricFactory:

val summary = factory
  .summary("my_summary")
  .ofDouble
  .help("Metric description")

By default, Summary metrics provide the count and the sum. For example, if you measure latencies of a REST service, the count will tell you how often the REST service was called, and the sum will tell you the total aggregated response time. You can calculate the average response time using a Prometheus query dividing sum / count.

Quantiles

In addition to count and sum, you can configure a Summary to provide quantiles:

val quantileSummary = factory
  .summary("my_summary")
  .ofDouble
  .help("Metric description")
  .quantile(0.5, 0.01)    // 0.5 quantile (median) with 0.01 allowed error
  .quantile(0.95, 0.005)  // 0.95 quantile with 0.005 allowed error

As an example, a 0.95 quantile of 120ms tells you that 95% of the calls were faster than 120ms, and 5% of the calls were slower than 120ms.

Tracking exact quantiles require a large amount of memory, because all observations need to be stored in a sorted list. Therefore, we allow an error to significantly reduce memory usage.

In the example, the allowed error of 0.005 means that you will not get the exact 0.95 quantile, but anything between the 0.945 quantile and the 0.955 quantile.

Experiments show that the Summary typically needs to keep less than 100 samples to provide that precision, even if you have hundreds of millions of observations.

There are a few special cases:

Maximum Age and Age Buckets

Typically, you don't want to have a Summary representing the entire runtime of the application, but you want to look at a reasonable time interval. Summary metrics implement a configurable sliding time window:

import scala.concurrent.duration._

val ageSummary = factory
  .summary("my_summary")
  .ofDouble
  .help("Metric description")
  .maxAge(10.seconds)
  .ageBuckets(5)

The default is a time window of 10 minutes and 5 age buckets, i.e. the time window is 10 minutes wide, and we slide it forward every 2 minutes.

Info

This implements an OpenMetrics info type, allowing labels to be registered as a sort of gauge where the value is always 1.

val info = factory
  .info("my_info")
  .help("Info description")