Skip to content

Commit

Permalink
resolves #1653 export metrics using Prometheus format
Browse files Browse the repository at this point in the history
  • Loading branch information
ggrossetie committed Oct 12, 2023
1 parent 759ca1a commit 15c82fd
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 45 deletions.
3 changes: 2 additions & 1 deletion server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<structurizr-dsl.version>1.32.0</structurizr-dsl.version>
<structurizr-export.version>1.16.1</structurizr-export.version>
<structurizr-core.version>1.26.1</structurizr-core.version>
<caffeine.version>3.1.8</caffeine.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -59,7 +60,7 @@
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
Expand Down
4 changes: 3 additions & 1 deletion server/src/main/java/io/kroki/server/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.vertx.config.ConfigRetriever;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
Expand All @@ -27,12 +28,13 @@ public static String getApplicationProperty(String key, String defaultValue) {

public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
VertxOptions vertxOptions = new VertxOptions();
ConfigRetriever retriever = ConfigRetriever.create(vertx);
retriever.getConfig(configResult -> {
if (configResult.failed()) {
logger.error("Unable to start", configResult.cause());
} else {
Server.start(vertx, configResult.result(), startResult -> {
Server.start(vertx, vertxOptions, configResult.result(), startResult -> {
if (startResult.failed()) {
logger.error("Unable to start", startResult.cause());
}
Expand Down
41 changes: 10 additions & 31 deletions server/src/main/java/io/kroki/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,7 @@
import io.kroki.server.error.ErrorHandler;
import io.kroki.server.error.InvalidRequestHandler;
import io.kroki.server.log.Logging;
import io.kroki.server.service.Blockdiag;
import io.kroki.server.service.Bpmn;
import io.kroki.server.service.Bytefield;
import io.kroki.server.service.D2;
import io.kroki.server.service.KrokiBlockedThreadChecker;
import io.kroki.server.service.TikZ;
import io.kroki.server.service.Dbml;
import io.kroki.server.service.DiagramRegistry;
import io.kroki.server.service.DiagramRest;
import io.kroki.server.service.Diagramsnet;
import io.kroki.server.service.Ditaa;
import io.kroki.server.service.Erd;
import io.kroki.server.service.Excalidraw;
import io.kroki.server.service.Graphviz;
import io.kroki.server.service.HealthHandler;
import io.kroki.server.service.HelloHandler;
import io.kroki.server.service.Mermaid;
import io.kroki.server.service.Nomnoml;
import io.kroki.server.service.Pikchr;
import io.kroki.server.service.Plantuml;
import io.kroki.server.service.ServiceVersion;
import io.kroki.server.service.Structurizr;
import io.kroki.server.service.Svgbob;
import io.kroki.server.service.Symbolator;
import io.kroki.server.service.Umlet;
import io.kroki.server.service.Vega;
import io.kroki.server.service.Wavedrom;
import io.kroki.server.service.Wireviz;
import io.kroki.server.service.*;
import io.vertx.config.ConfigRetriever;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.AsyncResult;
Expand Down Expand Up @@ -67,11 +40,12 @@ public class Server extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) {
ConfigRetriever retriever = ConfigRetriever.create(vertx);
VertxOptions vertxOptions = new VertxOptions();
retriever.getConfig(configResult -> {
if (configResult.failed()) {
startPromise.fail(configResult.cause());
} else {
start(vertx, configResult.result(), startResult -> {
start(vertx, vertxOptions, configResult.result(), startResult -> {
if (startResult.succeeded()) {
startPromise.complete();
} else {
Expand All @@ -82,7 +56,7 @@ public void start(Promise<Void> startPromise) {
});
}

static void start(Vertx vertx, JsonObject config, Handler<AsyncResult<HttpServer>> listenHandler) {
static void start(Vertx vertx, VertxOptions vertxOptions, JsonObject config, Handler<AsyncResult<HttpServer>> listenHandler) {
HttpServerOptions serverOptions = new HttpServerOptions();
Optional<Integer> maxUriLength = Optional.ofNullable(config.getInteger("KROKI_MAX_URI_LENGTH"));
maxUriLength.ifPresent(serverOptions::setMaxInitialLineLength);
Expand Down Expand Up @@ -151,8 +125,13 @@ static void start(Vertx vertx, JsonObject config, Handler<AsyncResult<HttpServer
.handler(bodyHandler)
.handler(new DiagramRest(registry).create());

// metrics
final var blockedThreadChecker = new KrokiBlockedThreadChecker(vertx, vertxOptions);
MetricHandler metricHandler = new MetricHandler(blockedThreadChecker);
Handler<RoutingContext> metricHandlerService = metricHandler.create();
router.get("/metrics")
.handler(metricHandlerService);
// health
final var blockedThreadChecker = new KrokiBlockedThreadChecker(vertx, new VertxOptions());//TODO this should match the option used to init vertx
HealthHandler healthHandler = new HealthHandler(registry.getVersions(), blockedThreadChecker);
Handler<RoutingContext> healthHandlerService = healthHandler.create();
router.get("/health")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public Handler<RoutingContext> createPost(String serviceName) {
JsonObject jsonBody;
MIMEHeader contentType = routingContext.parsedHeaders().contentType();
if (contentType != null && contentType.value() != null && contentType.value().equals("application/json")) {
String bodyAsString = routingContext.getBodyAsString();
String bodyAsString = routingContext.body().asString();
if (bodyAsString == null || bodyAsString.trim().isEmpty()) {
routingContext.fail(new BadRequestException("Request body must not be empty."));
return;
Expand All @@ -98,7 +98,7 @@ public Handler<RoutingContext> createPost(String serviceName) {
} else {
// assumes that the Content-Type is "plain/text" (default)
jsonBody = new JsonObject();
diagramSource = routingContext.getBodyAsString();
diagramSource = routingContext.body().asString();
if (diagramSource == null || diagramSource.trim().isEmpty()) {
routingContext.fail(new BadRequestException("Request body must not be empty."));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public DiagramRest(DiagramRegistry registry) {

public Handler<RoutingContext> create() {
return routingContext -> {
String bodyAsString = routingContext.getBodyAsString();
String bodyAsString = routingContext.body().asString();
if (bodyAsString == null || bodyAsString.trim().isEmpty()) {
routingContext.fail(new BadRequestException("Request body must not be empty."));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ public class HealthHandler {
private final String krokiBuildHash;
private final List<ServiceVersion> serviceVersions;

private final KrokiBlockedThreadChecker blockedThreadChecker;

public HealthHandler(Map<String, String> versions) {
this(versions, null);
}
Expand All @@ -31,7 +29,6 @@ public HealthHandler(Map<String, String> versions, KrokiBlockedThreadChecker blo
for (Map.Entry<String, String> entry : versions.entrySet()) {
serviceVersions.add(new ServiceVersion(entry.getKey(), entry.getValue()));
}
this.blockedThreadChecker = blockedThreadChecker;
}

public Handler<RoutingContext> create() {
Expand All @@ -47,10 +44,6 @@ public Handler<RoutingContext> create() {
for (ServiceVersion serviceVersion : serviceVersions) {
versions.put(serviceVersion.getService(), serviceVersion.getVersion());
}
if (blockedThreadChecker != null) {
data.put("blockedWorkerPercentage", blockedThreadChecker.blockedWorkerThreadPercentage());
data.put("blockedEventLoopPercentage", blockedThreadChecker.blockedEventLoopThreadPercentage());
}
routingContext
.response()
.putHeader(HttpHeaders.CONTENT_TYPE, "application/health+json")
Expand Down
36 changes: 36 additions & 0 deletions server/src/main/java/io/kroki/server/service/MetricHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.kroki.server.service;

import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpHeaders;
import io.vertx.ext.web.RoutingContext;

public class MetricHandler {

private final KrokiBlockedThreadChecker blockedThreadChecker;
private final String namespace;

public MetricHandler(KrokiBlockedThreadChecker blockedThreadChecker) {
this.blockedThreadChecker = blockedThreadChecker;
this.namespace = "kroki";
}

public Handler<RoutingContext> create() {
String workerThreadBlockedMetricName = namespace + "_worker_thread_blocked_percentage";
String eventLoopThreadBlockedMetricName = namespace + "_event_loop_thread_blocked_percentage";
return routingContext -> {
long timestamp = System.currentTimeMillis();
Buffer buffer = Buffer.buffer();
buffer.appendString("# HELP " + workerThreadBlockedMetricName + " The percentage of worker thread blocked.\n");
buffer.appendString("# TYPE " + workerThreadBlockedMetricName + " gauge\n");
buffer.appendString(String.join(" ", workerThreadBlockedMetricName, Long.toString(blockedThreadChecker.blockedWorkerThreadPercentage()), Long.toString(timestamp)) + "\n\n");
buffer.appendString("# HELP " + eventLoopThreadBlockedMetricName + " The percentage of event loop thread blocked.\n");
buffer.appendString("# TYPE " + eventLoopThreadBlockedMetricName + " gauge\n");
buffer.appendString(String.join(" ", eventLoopThreadBlockedMetricName, Long.toString(blockedThreadChecker.blockedEventLoopThreadPercentage()), Long.toString(timestamp)) + "\n\n");
routingContext
.response()
.putHeader(HttpHeaders.CONTENT_TYPE, "text/plain; version=0.0.4")
.end(buffer);
};
}
}
4 changes: 2 additions & 2 deletions server/src/main/java/io/kroki/server/service/Structurizr.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ public StructurizrTheme(JsonObject object) {
if (elementsObject instanceof JsonArray) {
for (Object elementObject : ((JsonArray) elementsObject).getList()) {
if (elementObject instanceof Map) {
@SuppressWarnings("unchecked")
JsonObject element = new JsonObject((Map<String, Object>) elementObject);
ElementStyle elementStyle = new ElementStyle(
element.getString("tag"),
Expand Down Expand Up @@ -263,9 +264,8 @@ public List<ElementStyle> getElementStyles() {
}

public List<RelationshipStyle> getRelationshipStyle() {
List<RelationshipStyle> result = new ArrayList<>();
// remind: RelationshipStyle does not have a public constructor, as a result, we cannot instantiate it.
return result;
return new ArrayList<>();
}

private Shape getShape(JsonObject element) {
Expand Down
16 changes: 16 additions & 0 deletions server/src/test/java/io/kroki/server/ServerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ void http_server_check_response(Vertx vertx, VertxTestContext testContext) {
})));
}

@Test
void http_server_check_metrics(Vertx vertx, VertxTestContext testContext) {
WebClient client = WebClient.create(vertx);
client.get(port, "localhost", "/metrics")
.as(BodyCodec.string())
.send(testContext.succeeding(response -> testContext.verify(() -> {
assertThat(response.body()).contains("# HELP kroki_worker_thread_blocked_percentage");
assertThat(response.body()).contains("# TYPE kroki_worker_thread_blocked_percentage gauge");
assertThat(response.body()).contains("# HELP kroki_event_loop_thread_blocked_percentage The percentage of event loop thread blocked.");
assertThat(response.body()).contains("# TYPE kroki_event_loop_thread_blocked_percentage gauge");
assertThat(response.body()).contains("kroki_worker_thread_blocked_percentage 0 ");
assertThat(response.body()).contains("kroki_event_loop_thread_blocked_percentage 0 ");
testContext.completeNow();
})));
}

@Test
void http_server_check_cors_handling_regular_origin(Vertx vertx, VertxTestContext testContext) {
WebClient client = WebClient.create(vertx);
Expand Down

0 comments on commit 15c82fd

Please sign in to comment.