Skip to main content
Version: 2.x

Introduction to ZIO Metrics

In highly concurrent applications, things are interconnected, so maintaining such a setup to run smoothly and without application downtimes is very challenging.

Imagine we have a complex infrastructure with lots of services. Services are replicated and distributed across our servers. So we have no insight into what happening across our services at the level of errors, response latency, service uptime, etc. With ZIO Metrics, we can capture these different metrics and provide them to a metric service for later investigation.

ZIO supports 5 types of Metrics:

  • Counter — The Counter is used for any value that increases over time like request counts.
  • Gauge — The gauge is a single numerical value that can go up or down over time like memory usage.
  • Histogram — The Histogram is used to track the distribution of a set of observed values across a set of buckets like request latencies.
  • Summary — The Summary represents a sliding window of a time series along with metrics for certain percentiles of the time series, referred to as quantiles like request latencies.
  • Frequency — The Frequency is a metric that counts the number of occurrences of distinct string values.

All ZIO Metrics are defined in the form of ZIO Aspects that can be applied to effects without changing the signature of the effect it is applied to:

import zio._
import zio.metrics._

def memoryUsage: ZIO[Any, Nothing, Double] = {
import java.lang.Runtime._
ZIO
.succeed(getRuntime.totalMemory() - getRuntime.freeMemory())
.map(_ / (1024.0 * 1024.0)) @@ Metric.gauge("memory_usage")
}

After adding metrics into our application, whenever we want we can capture snapshots of all metrics recorded by our application, by any of the metric backends supported by ZIO Metrics Connectors project.

Here is an example of adding a Prometheus connector to our application:

examples/jvm/src/main/scala/zio/examples/metrics/MetricAppExample.scala
package zio.examples.metrics

import zio._
import zio.http._
import zio.metrics.Metric
import zio.metrics.connectors.prometheus.PrometheusPublisher
import zio.metrics.connectors.{MetricsConfig, prometheus}

object MetricAppExample extends ZIOAppDefault {
def memoryUsage: ZIO[Any, Nothing, Double] = {
import java.lang.Runtime._
ZIO
.succeed(getRuntime.totalMemory() - getRuntime.freeMemory())
.map(_ / (1024.0 * 1024.0)) @@ Metric.gauge("memory_usage")
}

private val httpApp =
Routes(
Method.GET / "metrics" ->
handler(ZIO.serviceWithZIO[PrometheusPublisher](_.get.map(Response.text))),
Method.GET / "foo" -> handler {
for {
_ <- memoryUsage
time <- Clock.currentDateTime
} yield Response.text(s"$time\t/foo API called")
}
)

override def run = Server
.serve(httpApp)
.provide(
// ZIO Http default server layer, default port: 8080
Server.default,
// The prometheus reporting layer
prometheus.prometheusLayer,
prometheus.publisherLayer,
// Interval for polling metrics
ZLayer.succeed(MetricsConfig(5.seconds))
)
}

To run this example we need to add following lines to our build.sbt file:

ZIO Metrics Connectors currently supports the following backends: