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.
Some commands are specific to the promQL compatible VictoriaMetrics server and not supported in the original Prometheus server though.
It is in a draft state at the moment: we will avoid deep API changes if possible, but can't exclude them.
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.5.0"
For maven:
<dependency>
<groupId>io.sqooba.oss</groupId>
<artifactId>scala-promql-client_2.13</artifactId>
<version>0.5.0</version>
</dependency>
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
ssl = true
maxPointsPerTimeseries = 30000
retryNumber = 3
parallelRequests = 1
}
For authentication add a sub-configuration section such as :
promql-client {
...
//------------------
auth-basic-credentials {
username: "username"
password: "password"
password: ${?PROMQL_CLIENT_AUTH_BASIC_PASSWORD}
}
//------- OR -------
auth-basic-token {
token: "xxxx"
token: ${?PROMQL_CLIENT_AUTH_BASIC_TOKEN}
}
//------- OR -------
auth-bearer {
bearer: "xxxxx"
bearer: ${?PROMQL_CLIENT_AUTH_BASIC_BEARER}
}
Note : Prefer the use of environment variables to provide secrets to your application, such
as PROMQL_CLIENT_AUTH_BASIC_PASSWORD
in the previous example. Your can rename those example environment variables as you wish.
Both live
methods inside the PrometheusClient
object can be used to create a layer providing a PrometheusService
given a configuration.
PrometheusService
has a put
method that can be used to insert datapoints inside VictoriaMetrics, 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.
Prometheus does not support manually importing, other than backfilling.
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.
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
The following meta queries allow querying the set of available metrics for a specific time range:
SeriesQuery documentation
to find the metrics, identified by combinations of labels - returns MetricListReponseData
or EmptyResponseData
- LabelsQuery documentation to get a
list of actually used lables
- returns
StringListResponsedata
orEmptyResponseData
- returns
- LabelValuesQuery documentation to
get a list of actually used values for a given label
- returns
StringListResponsedata
orEmptyResponseData
- returns
The meta queries are not precisely specified, and the behaviour may differ sightly from one promQL compatible server implementation to another.
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,
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
}
}
Don't forget to pattern match and provide for a regular case _
. Meta queries have to deal with empty responses by the
means of the EmptyResponseData type.
Most unit tests are run against mocked data. Basing your "*Spec" suite object on
io.sqooba.oss.promql_container.PromClientRunnable
instead instanciates a fresh docker container for every test.
Note that the OSAG CI pipeline currently does not execute Docker-based unit tests!
To account for potentially different behaviour of various versions of VictoriaMetrics, use for a check against a single version or MultiVersionVictoriaClientRunnable to run the whole suite of tests against a list of versions at once.
The MultiVersionPromClientRunnable will instantiate a original Prometheus container. Obviously, the "put" command will fail on this one. As an alternative, the contents of the metrics resource file is pre-loaded.
Currently, in this library, the Victoriametetrics versions "v1.47.0", "v1.53.1", " v1.61.1" are checked by default.
The tests involving containers will be run by sbt test
.
However, the following JUnit tests have to be started manually (e.g "run" in your favourite IDE), because sbt test
will not pick them up:
- PrometheusClientSpec
- PrometheusInsertMetricSpec
- PrometheusQuerySpec
- PrometheusResponseSpec
- PrometheusScalarResponseSpec
The reason is the ZIO-Testrunner annotation. We haven't found a way to marry it with sbt and JUnit.
use the multi version facility by specifying your test suite as :
object PrometheusAppSpec extends MultiVersionPromClientRunnable {
optionally giving a custom list of Versions for your test suite :
override val versions: Seq[String] = Seq("v1.61.1")
When using higher level clients that use a promql-client layer, you can define a new *Runnable class extending the MultiVersionPromClient with your needed Environment Layers. (e.g. for Chronos):
abstract class ChronosRunnable extends MultiVersionPromClient[ChronosEnv] {
override val versions: Seq[String] = Seq("v1.42.0", "v1.47.0", "v1.53.1", "v1.61.1")
type ChronosRunnable = ZSpec[ChronosEnv, Any]
/**
* Create a test environment by spawning a VictoriaMetrics container, building a client configuration
* as well as a ChronosClient to be used by the tests
*/
override def buildLayer(v: String): ULayer[ChronosEnv] =
victoriaLayer(v) >+> ChronosClient.live
}
See the changelog for more details.