Skip to content

Commit

Permalink
Add Prometheus REST endpoint (moved from JVB). (#175)
Browse files Browse the repository at this point in the history
* Add Prometheus REST endpoint (moved from JVB).

* Add Prometheus REST endpoint.
* Add tests.
* Update pom.xml.

This commit relates to PR #1915 in jitsi-videobridge.

Signed-off-by: Alexandre Fonseca <4717855+alexcsf@users.noreply.github.com>

* fix: Add jvmTarget, jersey-hk2 and args to pom.

* fix: Add jvmTarget, jersey-hk2 and args to pom.

Signed-off-by: Alexandre Fonseca <4717855+alexcsf@users.noreply.github.com>
  • Loading branch information
alexcsf authored Jul 27, 2022
1 parent 4ddaa6f commit 332f4e7
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 2 deletions.
125 changes: 123 additions & 2 deletions jicoco-metrics/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
<name>jicoco-metrics</name>
<description>Jitsi Common Components (Metrics)</description>

<properties>
<jetty.version>11.0.11</jetty.version>
<jersey.version>3.0.5</jersey.version>
</properties>

<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
Expand All @@ -50,7 +55,56 @@
<artifactId>simpleclient_common</artifactId>
<version>${prometheus.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-jetty-http</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
</dependency>

<!-- test -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.kotest</groupId>
<artifactId>kotest-runner-junit5-jvm</artifactId>
Expand All @@ -63,11 +117,39 @@
<version>${kotest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-jetty</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
Expand All @@ -84,6 +166,10 @@
<args>
<arg>-opt-in=kotlin.ExperimentalStdlibApi</arg>
</args>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
Expand All @@ -96,13 +182,48 @@
<args>
<arg>-opt-in=kotlin.ExperimentalStdlibApi</arg>
</args>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
<configuration>
<jvmTarget>11</jvmTarget>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright @ 2022 - present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jitsi.rest.prometheus;

import io.prometheus.client.exporter.common.*;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.*;
import org.jetbrains.annotations.NotNull;
import org.jitsi.metrics.*;

/**
* A REST endpoint exposing JVB stats for Prometheus.
* Any scraper supporting Prometheus' text-based formats ({@code text/plain; version=0.0.4} or OpenMetrics)
* is compatible with this {@code /metrics} endpoint.<br>
* JSON is provided when the client performs a request with the 'Accept' header set to {@code application/json}.<br>
* The response defaults to {@code text/plain; version=0.0.4} formatted output.
*
* @see <a href="https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md">
* Prometheus' exposition formats</a>
*/
@Path("/metrics")
public class Prometheus
{
@NotNull
private final MetricsContainer metricsContainer;

public Prometheus(@NotNull MetricsContainer metricsContainer)
{
this.metricsContainer = metricsContainer;
}

@GET
@Produces(TextFormat.CONTENT_TYPE_004)
public String getPrometheusPlainText()
{
return metricsContainer.getPrometheusMetrics(TextFormat.CONTENT_TYPE_004);
}

@GET
@Produces(TextFormat.CONTENT_TYPE_OPENMETRICS_100)
public String getPrometheusOpenMetrics()
{
return metricsContainer.getPrometheusMetrics(TextFormat.CONTENT_TYPE_OPENMETRICS_100);
}

@GET
@Produces({MediaType.APPLICATION_JSON, "*/*;q=0"})
public String getJsonString()
{
return metricsContainer.getJsonString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.jitsi.rest.prometheus

import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeInstanceOf
import io.prometheus.client.CollectorRegistry
import io.prometheus.client.exporter.common.TextFormat
import jakarta.ws.rs.core.Application
import jakarta.ws.rs.core.MediaType
import jakarta.ws.rs.core.Response
import org.eclipse.jetty.http.HttpStatus
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.test.JerseyTest
import org.glassfish.jersey.test.TestProperties
import org.jitsi.metrics.MetricsContainer
import org.json.simple.JSONObject
import org.json.simple.parser.JSONParser
import org.junit.Test

class PrometheusTest : JerseyTest() {
private lateinit var metricsContainer: MetricsContainer
private val baseUrl = "/metrics"
private val mediaType004 = MediaType("text", "plain", mapOf("charset" to "utf-8", "version" to "0.0.4"))
private val mediaTypeOpenMetrics =
MediaType("application", "openmetrics-text", mapOf("charset" to "utf-8", "version" to "1.0.0"))

override fun configure(): Application {
metricsContainer = MetricsContainer(CollectorRegistry()).apply {
checkForNameConflicts = false
registerLongGauge("gauge", "A gauge", 50)
registerBooleanMetric("boolean", "A boolean", true)
}

enable(TestProperties.LOG_TRAFFIC)
enable(TestProperties.DUMP_ENTITY)
return object : ResourceConfig() {
init {
register(Prometheus(metricsContainer))
}
}
}

@Test
fun testGetJson() {
val resp = target(baseUrl).request(MediaType.APPLICATION_JSON_TYPE).get()
resp.status shouldBe HttpStatus.OK_200
resp.mediaType shouldBe MediaType.APPLICATION_JSON_TYPE
with(MetricsContainer(CollectorRegistry())) {
registerLongGauge("gauge", "A gauge", 50)
registerBooleanMetric("boolean", "A boolean", true)
resp.getResultAsJson() shouldBe mapOf("gauge" to 50, "boolean" to true)
}
}

@Test
fun testGetPrometheus004() {
val resp = target(baseUrl).request(TextFormat.CONTENT_TYPE_004).get()
resp.status shouldBe HttpStatus.OK_200
resp.mediaType shouldBe mediaType004
with(MetricsContainer(CollectorRegistry())) {
registerLongGauge("gauge", "A gauge", 50)
registerBooleanMetric("boolean", "A boolean", true)
val expected = getPrometheusMetrics(TextFormat.CONTENT_TYPE_004).split("\n").sorted()
resp.getSortedLines() shouldBe expected
}
}

@Test
fun testGetPrometheusOpenMetrics() {
val resp = target(baseUrl).request(TextFormat.CONTENT_TYPE_OPENMETRICS_100).get()
resp.status shouldBe HttpStatus.OK_200
resp.mediaType shouldBe mediaTypeOpenMetrics
with(MetricsContainer(CollectorRegistry())) {
registerLongGauge("gauge", "A gauge", 50)
registerBooleanMetric("boolean", "A boolean", true)
val expected = getPrometheusMetrics(TextFormat.CONTENT_TYPE_OPENMETRICS_100).split("\n").sorted()
resp.getSortedLines() shouldBe expected
}
}

private fun Response.getResultAsJson(): JSONObject {
val obj = JSONParser().parse(readEntity(String::class.java))
obj.shouldBeInstanceOf<JSONObject>()
return obj
}

private fun Response.getSortedLines(): List<String> {
return readEntity(String::class.java).split("\n").sorted()
}
}

0 comments on commit 332f4e7

Please sign in to comment.