sqooba / scala-promql-client

A Scala client for running queries against a Prometheus backend.

Version Matrix

Scala Prometheus Query Client

Welcome to scala-promql-client.

This is an sttp based scala client for issuing queries against a Prometheus server.

This is not a library to instrument your scala application.

This library is relied on in contexts where we use a Prometheus instance as a time-series store and wish to run queries for analytical purposes from a Scala application.

It is in a draft state at the moment: we will avoid deep API changes if possible, but can't exclude them.

Installation

The library is available on sonatype, to use it in an SBT project add the following line:

libraryDependencies += "io.sqooba.oss" %% "scala-promql-client" % "0.3.1"

For maven:

<dependency>
    <groupId>io.sqooba.oss</groupId>
    <artifactId>scala-promql-client_2.13</artifactId>
    <version>0.3.1</version>
</dependency>

Usage

Configuration

In order to work properly, the client needs a configuration. This configuration is available and can be constructed using a PrometheusClientConfig case class. The following parameters are required:

  • Server's hostname or ip
  • Server's port
  • The maximum number of points a prometheus endpoint can return before we have to split queries (defined by the server, VictoriaMetrics has this value set to 30 000 by default)
  • Number of retries to perform in case of a failed query
  • Number of requests that can be made in parallel when splitting queries, be careful when changing this value

It is also possible to load this configuration from a file. The PrometheusClient.liveDefault method will look inside application.conf for a block named promql-client and that contains the following values:

promql-client {
    host = localhost
    port = 8428
    maxPointsPerTimeseries = 30000
    retryNumber = 3
    parallelRequests = 1
}

Both live methods inside the PrometheusClient object can be used to create a layer providing a PrometheusService given a configuration.

Importing data

PrometheusService has a put method that can be used to insert datapoints inside Prometheus, it can be used that way:

import java.time.Instant
import io.sqooba.oss.promql.metrics.PrometheusInsertMetric
import io.sqooba.oss.promql.{PrometheusClient, PrometheusService}

object Main extends zio.App {

  def run(args: List[String]) = {
    val now = Instant.now
    val layer = PrometheusClient.liveDefault // Load configuration from file

    PrometheusService
      .put(
        Seq(
          PrometheusInsertMetric(
            Map(
                "__name__" -> "timeseries_label"
            ),
            Seq(1, 2, 3),
            Seq(now.toEpochMilli, now.minusSeconds(60).toEpochMilli, now.minusSeconds(120).toEpochMilli)
          )
        )
      )
      .provideLayer(layer)
      .exitCode
  }

}

This will insert three points with value 1, 2 and 3 for the last three minutes.

Addings tags

It is possible to add tags to a timeseries by using the Map given as first argument to PrometheusInsertMetric. The tag called __name__ is a special tag that contains the name of the timeseries in prometheus.

Running queries

Using the query method available on a PrometheusService it is possible to run arbitrary Prometheus queries. As described on Prometheus' documentation, there are a few different queries that can be run, this client currently supports:

  • InstantQuery documentation to run a query at a single point in time
  • RangeQuery documentation to run a query over a range of time

Prometheus also has the following query types which are not supported yet by this client:

The query can be used in the following way:

import java.time.Instant
import scala.concurrent.duration._
import io.sqooba.oss.promql.metrics.PrometheusInsertMetric
import io.sqooba.oss.promql.{RangeQuery, PrometheusClient, PrometheusService}

object Main extends zio.App {

  def run(args: List[String]) = {
    val start = Instant.now.minusSeconds(24 * 60 * 60)
    val layer = PrometheusClient.liveDefault

    val query = RangeQuery(
      "timeseries_label",
      start,
      Instant.now,
      1.hour.toSeconds.toInt,
      None
    )

    PrometheusService
      .query(query)
      .provideLayer(layer)
      .exitCode
  }

}

A way to run multiple queries is by using a for-comprehension:

for {
    firstData <- PrometheusService.query(query)
    secondData <- PrometheusService.query(secondQuery)
} yield {
    (firstData, secondData) match {
        case (x @ MatrixResponseData(_), y @ MatrixResponseData(_)) => Some(x.merge(y))
        case _ => None
    }
}

Versions and releases

See the changelog for more details.