diff --git a/src/main/java/io/getunleash/impactmetrics/HistogramImpl.java b/src/main/java/io/getunleash/impactmetrics/HistogramImpl.java index 36812dd3d..539fd4862 100644 --- a/src/main/java/io/getunleash/impactmetrics/HistogramImpl.java +++ b/src/main/java/io/getunleash/impactmetrics/HistogramImpl.java @@ -84,8 +84,7 @@ public CollectedMetric collect() { countSnapshot = data.count; sumSnapshot = data.sum; for (Double le : buckets) { - bucketSamples.add( - new HistogramBucket(le, data.buckets.getOrDefault(le, 0L))); + bucketSamples.add(new HistogramBucket(le, data.buckets.getOrDefault(le, 0L))); } } diff --git a/src/main/java/io/getunleash/metric/ClientMetrics.java b/src/main/java/io/getunleash/metric/ClientMetrics.java index b027d344c..f8ff346dc 100644 --- a/src/main/java/io/getunleash/metric/ClientMetrics.java +++ b/src/main/java/io/getunleash/metric/ClientMetrics.java @@ -4,8 +4,10 @@ import io.getunleash.engine.UnleashEngine; import io.getunleash.event.UnleashEvent; import io.getunleash.event.UnleashSubscriber; +import io.getunleash.impactmetrics.CollectedMetric; import io.getunleash.lang.Nullable; import io.getunleash.util.UnleashConfig; +import java.util.List; public class ClientMetrics implements UnleashEvent { @@ -18,8 +20,16 @@ public class ClientMetrics implements UnleashEvent { @Nullable private final String platformName; @Nullable private final String platformVersion; @Nullable private final String yggdrasilVersion; + @Nullable private final List impactMetrics; ClientMetrics(UnleashConfig config, @Nullable MetricsBucket bucket) { + this(config, bucket, null); + } + + ClientMetrics( + UnleashConfig config, + @Nullable MetricsBucket bucket, + @Nullable List impactMetrics) { this.environment = config.getEnvironment(); this.appName = config.getAppName(); this.instanceId = config.getInstanceId(); @@ -29,6 +39,7 @@ public class ClientMetrics implements UnleashEvent { this.platformName = System.getProperty("java.vm.name"); this.platformVersion = System.getProperty("java.version"); this.yggdrasilVersion = UnleashEngine.getCoreVersion(); + this.impactMetrics = impactMetrics; } public String getAppName() { @@ -71,6 +82,11 @@ public String getYggdrasilVersion() { return yggdrasilVersion; } + @Nullable + public List getImpactMetrics() { + return impactMetrics; + } + @Override public void publishTo(UnleashSubscriber unleashSubscriber) { unleashSubscriber.clientMetrics(this); diff --git a/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java b/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java index 3a48caa99..7dfbded0d 100644 --- a/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java +++ b/src/main/java/io/getunleash/metric/DefaultHttpMetricsSender.java @@ -5,8 +5,10 @@ import com.google.gson.*; import io.getunleash.UnleashException; import io.getunleash.event.EventDispatcher; +import io.getunleash.impactmetrics.HistogramBucket; import io.getunleash.util.AtomicLongSerializer; import io.getunleash.util.DateTimeSerializer; +import io.getunleash.util.HistogramBucketSerializer; import io.getunleash.util.InstantSerializer; import io.getunleash.util.UnleashConfig; import io.getunleash.util.UnleashURLs; @@ -38,6 +40,7 @@ public DefaultHttpMetricsSender(UnleashConfig unleashConfig) { .registerTypeAdapter(LocalDateTime.class, new DateTimeSerializer()) .registerTypeAdapter(Instant.class, new InstantSerializer()) .registerTypeAdapter(AtomicLong.class, new AtomicLongSerializer()) + .registerTypeAdapter(HistogramBucket.class, new HistogramBucketSerializer()) .create(); } diff --git a/src/main/java/io/getunleash/metric/OkHttpMetricsSender.java b/src/main/java/io/getunleash/metric/OkHttpMetricsSender.java index 6d182a141..ff6804279 100644 --- a/src/main/java/io/getunleash/metric/OkHttpMetricsSender.java +++ b/src/main/java/io/getunleash/metric/OkHttpMetricsSender.java @@ -6,8 +6,10 @@ import com.google.gson.GsonBuilder; import io.getunleash.UnleashException; import io.getunleash.event.EventDispatcher; +import io.getunleash.impactmetrics.HistogramBucket; import io.getunleash.util.AtomicLongSerializer; import io.getunleash.util.DateTimeSerializer; +import io.getunleash.util.HistogramBucketSerializer; import io.getunleash.util.OkHttpClientConfigurer; import io.getunleash.util.UnleashConfig; import java.io.IOException; @@ -58,6 +60,7 @@ public OkHttpMetricsSender(UnleashConfig config) { new GsonBuilder() .registerTypeAdapter(LocalDateTime.class, new DateTimeSerializer()) .registerTypeAdapter(AtomicLong.class, new AtomicLongSerializer()) + .registerTypeAdapter(HistogramBucket.class, new HistogramBucketSerializer()) .create(); } diff --git a/src/main/java/io/getunleash/util/HistogramBucketSerializer.java b/src/main/java/io/getunleash/util/HistogramBucketSerializer.java new file mode 100644 index 000000000..04fda57b4 --- /dev/null +++ b/src/main/java/io/getunleash/util/HistogramBucketSerializer.java @@ -0,0 +1,24 @@ +package io.getunleash.util; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import io.getunleash.impactmetrics.HistogramBucket; +import java.lang.reflect.Type; + +public class HistogramBucketSerializer implements JsonSerializer { + + @Override + public JsonElement serialize( + HistogramBucket src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + if (Double.isInfinite(src.getLe()) && src.getLe() > 0) { + jsonObject.addProperty("le", "+Inf"); + } else { + jsonObject.addProperty("le", src.getLe()); + } + jsonObject.addProperty("count", src.getCount()); + return jsonObject; + } +} diff --git a/src/main/java/io/getunleash/util/UnleashConfig.java b/src/main/java/io/getunleash/util/UnleashConfig.java index 1314b845a..5e33eff35 100644 --- a/src/main/java/io/getunleash/util/UnleashConfig.java +++ b/src/main/java/io/getunleash/util/UnleashConfig.java @@ -6,6 +6,7 @@ import io.getunleash.UnleashException; import io.getunleash.event.NoOpSubscriber; import io.getunleash.event.UnleashSubscriber; +import io.getunleash.impactmetrics.ImpactMetricsDataSource; import io.getunleash.lang.Nullable; import io.getunleash.metric.DefaultHttpMetricsSender; import io.getunleash.repository.HttpFeatureFetcher; @@ -73,6 +74,7 @@ public class UnleashConfig { @Nullable private final ToggleBootstrapProvider toggleBootstrapProvider; @Nullable private final Proxy proxy; @Nullable private final Consumer startupExceptionHandler; + @Nullable private final ImpactMetricsDataSource impactMetricsRegistry; private UnleashConfig( @Nullable URI unleashAPI, @@ -106,7 +108,8 @@ private UnleashConfig( @Nullable ToggleBootstrapProvider unleashBootstrapProvider, @Nullable Proxy proxy, @Nullable Authenticator proxyAuthenticator, - @Nullable Consumer startupExceptionHandler) { + @Nullable Consumer startupExceptionHandler, + @Nullable ImpactMetricsDataSource impactMetricsRegistry) { if (appName == null) { throw new IllegalStateException("You are required to specify the unleash appName"); @@ -172,6 +175,7 @@ private UnleashConfig( this.clientSpecificationVersion = UnleashProperties.getProperty("client.specification.version"); this.startupExceptionHandler = startupExceptionHandler; + this.impactMetricsRegistry = impactMetricsRegistry; } public static Builder builder() { @@ -352,6 +356,11 @@ public Proxy getProxy() { return proxy; } + @Nullable + public ImpactMetricsDataSource getImpactMetricsRegistry() { + return impactMetricsRegistry; + } + public MetricSenderFactory getMetricSenderFactory() { return this.metricSenderFactory; } @@ -462,6 +471,7 @@ public static class Builder { private @Nullable Authenticator proxyAuthenticator; private @Nullable Consumer startupExceptionHandler; + private @Nullable ImpactMetricsDataSource impactMetricsRegistry; private static String getHostname() { String hostName = System.getProperty("hostname"); @@ -715,6 +725,12 @@ public Builder startupExceptionHandler( return this; } + public Builder impactMetricsRegistry( + @Nullable ImpactMetricsDataSource impactMetricsRegistry) { + this.impactMetricsRegistry = impactMetricsRegistry; + return this; + } + public UnleashConfig build() { return new UnleashConfig( unleashAPI, @@ -749,7 +765,8 @@ public UnleashConfig build() { toggleBootstrapProvider, proxy, proxyAuthenticator, - startupExceptionHandler); + startupExceptionHandler, + impactMetricsRegistry); } public String getDefaultSdkVersion() { diff --git a/src/test/java/io/getunleash/impactmetrics/MetricTypeTest.java b/src/test/java/io/getunleash/impactmetrics/MetricTypeTest.java index cb378a150..b3e37360d 100644 --- a/src/test/java/io/getunleash/impactmetrics/MetricTypeTest.java +++ b/src/test/java/io/getunleash/impactmetrics/MetricTypeTest.java @@ -2,6 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.getunleash.util.HistogramBucketSerializer; import java.util.Collections; import java.util.List; import java.util.Map; @@ -300,4 +303,60 @@ private NumericMetricSample sample(long value) { private NumericMetricSample sample(Map labels, long value) { return new NumericMetricSample(labels, value); } + + @Test + public void should_serialize_histogram_with_positive_infinity_as_plus_inf_string() { + InMemoryMetricRegistry registry = new InMemoryMetricRegistry(); + Histogram histogram = + registry.histogram( + new BucketMetricOptions( + "serialization_test", "test serialization", List.of(1.0, 5.0))); + + histogram.observe(10.0, Map.of("env", "prod")); + + List metrics = registry.collect(); + assertThat(metrics).hasSize(1); + + Gson gson = + new GsonBuilder() + .registerTypeAdapter(HistogramBucket.class, new HistogramBucketSerializer()) + .create(); + + String json = gson.toJson(metrics.get(0)); + + assertThat(json).contains("\"le\":\"+Inf\""); + assertThat(json).contains("\"le\":1.0"); + assertThat(json).contains("\"le\":5.0"); + } + + @Test + public void should_serialize_histogram_buckets_correctly() { + InMemoryMetricRegistry registry = new InMemoryMetricRegistry(); + Histogram histogram = + registry.histogram( + new BucketMetricOptions( + "bucket_serialization", "test buckets", List.of(0.5, 2.0))); + + histogram.observe(0.3); + histogram.observe(1.5); + histogram.observe(10.0); + + List metrics = registry.collect(); + assertThat(metrics).hasSize(1); + + BucketMetricSample sample = (BucketMetricSample) metrics.get(0).getSamples().get(0); + assertThat(sample.getBuckets()).hasSize(3); + + Gson gson = + new GsonBuilder() + .registerTypeAdapter(HistogramBucket.class, new HistogramBucketSerializer()) + .create(); + + String json = gson.toJson(sample); + + assertThat(json).contains("\"le\":0.5"); + assertThat(json).contains("\"le\":2.0"); + assertThat(json).contains("\"le\":\"+Inf\""); + assertThat(json).contains("\"count\":"); + } }