Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON exporter for Micrometer #11939

Merged
merged 1 commit into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkus.micrometer.deployment.export;

import java.util.function.BooleanSupplier;

import org.jboss.logging.Logger;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.micrometer.deployment.MicrometerRegistryProviderBuildItem;
import io.quarkus.micrometer.runtime.config.MicrometerConfig;
import io.quarkus.micrometer.runtime.export.JsonMeterRegistryProvider;
import io.quarkus.micrometer.runtime.export.JsonRecorder;
import io.quarkus.micrometer.runtime.registry.json.JsonMeterRegistry;
import io.quarkus.vertx.http.deployment.RouteBuildItem;

public class JsonRegistryProcessor {

private static final Logger log = Logger.getLogger(JsonRegistryProcessor.class);

public static class JsonRegistryEnabled implements BooleanSupplier {
MicrometerConfig mConfig;

public boolean getAsBoolean() {
return mConfig.checkRegistryEnabledWithDefault(mConfig.export.json);
}
}

@BuildStep(onlyIf = JsonRegistryEnabled.class)
@Record(ExecutionTime.STATIC_INIT)
public void initializeJsonRegistry(MicrometerConfig config,
BuildProducer<MicrometerRegistryProviderBuildItem> registryProviders,
BuildProducer<RouteBuildItem> routes,
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
JsonRecorder recorder,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses) {
additionalBeans.produce(AdditionalBeanBuildItem.builder()
.addBeanClass(JsonMeterRegistryProvider.class)
.setUnremovable().build());
registryProviders.produce(new MicrometerRegistryProviderBuildItem(JsonMeterRegistry.class));
routes.produce(new RouteBuildItem(recorder.route(config.export.json.path), recorder.getHandler()));
reflectiveClasses.produce(ReflectiveClassBuildItem
.builder("org.HdrHistogram.Histogram",
"org.HdrHistogram.DoubleHistogram",
"org.HdrHistogram.ConcurrentHistogram")
.constructors(true).build());
log.debug("Initialized a JSON meter registry on path=" + config.export.json.path);
}

}
7 changes: 7 additions & 0 deletions extensions/micrometer/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
<artifactId>micrometer-core</artifactId>
</dependency>

<!-- Needed for the JSON meter registry -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
</dependency>


<!-- Registry providers (optional) -->

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public GaugeAdapterImpl(String name, String description, String targetName, Stri
public GaugeAdapterImpl register(MetricDescriptor id, MeterRegistry registry) {
this.id = id;
if (gauge == null || metadata.cleanDirtyMetadata()) {
gauge = io.micrometer.core.instrument.Gauge.builder(metadata.name, this::getValue)
gauge = io.micrometer.core.instrument.Gauge.builder(metadata.name, this, g -> g.getValue().doubleValue())
.description(metadata.description())
.tags(id.tags())
.baseUnit(metadata.unit())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ConcurrentGaugeImpl implements ConcurrentGauge, MeterHolder {
Gauge gauge;

ConcurrentGaugeImpl register(MpMetadata metadata, MetricDescriptor metricInfo, MeterRegistry registry) {
gauge = io.micrometer.core.instrument.Gauge.builder(metricInfo.name(), longAdder::longValue)
gauge = io.micrometer.core.instrument.Gauge.builder(metricInfo.name(), longAdder, LongAdder::doubleValue)
.description(metadata.description())
.baseUnit(metadata.unit())
.tags(metricInfo.tags())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.quarkus.micrometer.runtime.config;

import java.time.Duration;
import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class JsonConfig implements MicrometerConfig.CapabilityEnabled {
/**
* Support for export to JSON format. Off by default.
*/
@ConfigItem(defaultValue = "false")
public Optional<Boolean> enabled;

/**
* The path for the JSON metrics endpoint.
* The default value is {@code /metrics}.
*/
@ConfigItem(defaultValue = "/metrics")
public String path;

/**
* Statistics like max, percentiles, and histogram counts decay over time to give greater weight to recent
* samples. Samples are accumulated to such statistics in ring buffers which rotate after
* the expiry, with this buffer length.
*/
@ConfigItem(defaultValue = "1024")
public Integer bufferLength;

/**
* Statistics like max, percentiles, and histogram counts decay over time to give greater weight to recent
* samples. Samples are accumulated to such statistics in ring buffers which rotate after
* this expiry, with a particular buffer length.
*/
@ConfigItem(defaultValue = "P3D")
public Duration expiry;

@Override
public Optional<Boolean> getEnabled() {
return enabled;
}

@Override
public String toString() {
return this.getClass().getSimpleName()
+ "{path='" + path
+ ",enabled=" + enabled
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public static class ExportConfig {
public JmxConfig jmx;
public PrometheusConfig prometheus;
public StackdriverConfig stackdriver;
public JsonConfig json;
}

public static interface CapabilityEnabled {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.micrometer.runtime.export;

import javax.enterprise.inject.Produces;
import javax.inject.Singleton;

import io.micrometer.core.instrument.Clock;
import io.quarkus.micrometer.runtime.registry.json.JsonMeterRegistry;

@Singleton
public class JsonMeterRegistryProvider {

@Produces
@Singleton
public JsonMeterRegistry registry(Clock clock, io.quarkus.micrometer.runtime.config.MicrometerConfig config) {
return new JsonMeterRegistry(clock, config.export.json.bufferLength, config.export.json.expiry);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.micrometer.runtime.export;

import java.util.function.Function;

import io.quarkus.micrometer.runtime.export.handlers.JsonHandler;
import io.quarkus.runtime.annotations.Recorder;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;

@Recorder
public class JsonRecorder {
JsonHandler handler;

public JsonHandler getHandler() {
if (handler == null) {
handler = new JsonHandler();
}

return handler;
}

public Function<Router, Route> route(String path) {
return new Function<Router, Route>() {
@Override
public Route apply(Router router) {
return router.route(path).order(2).produces("application/json");
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.util.function.Function;

import io.prometheus.client.exporter.common.TextFormat;
import io.quarkus.micrometer.runtime.export.handlers.PrometheusHandler;
import io.quarkus.runtime.annotations.Recorder;
import io.vertx.ext.web.Route;
Expand All @@ -24,7 +23,7 @@ public Function<Router, Route> route(String path) {
return new Function<Router, Route>() {
@Override
public Route apply(Router router) {
return router.route(path).produces(TextFormat.CONTENT_TYPE_004);
return router.route(path).order(1).produces("text/plain");
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.quarkus.micrometer.runtime.export.handlers;

import javax.enterprise.inject.Default;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.CDI;

import org.jboss.logging.Logger;

import io.quarkus.micrometer.runtime.registry.json.JsonMeterRegistry;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;

public class JsonHandler implements Handler<RoutingContext> {
private static final Logger log = Logger.getLogger(JsonHandler.class);

private JsonMeterRegistry registry;

private boolean setup = false;

@Override
public void handle(RoutingContext routingContext) {
if (!setup) {
setup();
}

HttpServerResponse response = routingContext.response();
if (registry == null) {
response.setStatusCode(500)
.setStatusMessage("Unable to resolve JSON registry instance");
} else {
response.putHeader("Content-Type", "application/json")
.end(Buffer.buffer(registry.scrape()));
}
}

private void setup() {
Instance<JsonMeterRegistry> registries = CDI.current().select(JsonMeterRegistry.class,
Default.Literal.INSTANCE);

if (registries.isUnsatisfied()) {
registry = null;
} else if (registries.isAmbiguous()) {
registry = registries.iterator().next();
log.warnf("Multiple JSON registries present: %s. Using %s with the built in scrape endpoint", registries,
registry);
} else {
registry = registries.get();
}

setup = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.quarkus.micrometer.runtime.registry.json;

import java.util.Arrays;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;

import io.micrometer.core.instrument.AbstractDistributionSummary;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.core.instrument.distribution.TimeWindowMax;

public class JsonDistributionSummary extends AbstractDistributionSummary {

private final LongAdder count;
private final DoubleAdder total;
private final TimeWindowMax max;
private final TimeWindowMin min;

public JsonDistributionSummary(Id id, Clock clock, DistributionStatisticConfig distributionStatisticConfig,
double scale, boolean supportsAggregablePercentiles) {
super(id, clock, distributionStatisticConfig, scale, supportsAggregablePercentiles);
this.count = new LongAdder();
this.total = new DoubleAdder();
this.max = new TimeWindowMax(clock, distributionStatisticConfig);
this.min = new TimeWindowMin(clock, distributionStatisticConfig);
}

@Override
protected void recordNonNegative(double amount) {
count.increment();
total.add(amount);
max.record(amount);
min.record(amount);
}

@Override
public long count() {
return count.longValue();
}

@Override
public double totalAmount() {
return total.sum();
}

@Override
public double max() {
return max.poll();
}

public double min() {
return min.poll();
}

@Override
public Iterable<Measurement> measure() {
return Arrays.asList(
new Measurement(() -> (double) count(), Statistic.COUNT),
new Measurement(this::totalAmount, Statistic.TOTAL),
new Measurement(this::max, Statistic.MAX));
}
}
Loading