Commit 6764e5e2 authored by Stephane Nicoll's avatar Stephane Nicoll

Export metrics to Wavefront using WavefrontSender

This commit upgrades the Wavefront metrics export auto-configuration to
provide a `WavefrontSender` if necessary and use that to export metrics
rather than the http client Micrometer used previously.

As a result, the "read-timeout" and "connect-timeout" properties are no
longer honoured.

Closes gh-20810
parent 9543ab1c
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,14 +16,19 @@ ...@@ -16,14 +16,19 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront;
import java.time.Duration;
import com.wavefront.sdk.common.WavefrontSender;
import com.wavefront.sdk.direct.ingestion.WavefrontDirectIngestionClient.Builder;
import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Clock;
import io.micrometer.core.ipc.http.HttpUrlConnectionSender; import io.micrometer.core.instrument.config.MissingRequiredConfigurationException;
import io.micrometer.wavefront.WavefrontConfig; import io.micrometer.wavefront.WavefrontConfig;
import io.micrometer.wavefront.WavefrontMeterRegistry; import io.micrometer.wavefront.WavefrontMeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontProperties.Sender;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
...@@ -32,21 +37,25 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; ...@@ -32,21 +37,25 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.springframework.util.unit.DataSize;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Wavefront. * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Wavefront.
* *
* @author Jon Schneider * @author Jon Schneider
* @author Artsiom Yudovin * @author Artsiom Yudovin
* @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@AutoConfigureBefore({ CompositeMeterRegistryAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class }) @AutoConfigureBefore({ CompositeMeterRegistryAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class })
@AutoConfigureAfter(MetricsAutoConfiguration.class) @AutoConfigureAfter(MetricsAutoConfiguration.class)
@ConditionalOnBean(Clock.class) @ConditionalOnBean(Clock.class)
@ConditionalOnClass(WavefrontMeterRegistry.class) @ConditionalOnClass({ WavefrontMeterRegistry.class, WavefrontSender.class })
@ConditionalOnProperty(prefix = "management.metrics.export.wavefront", name = "enabled", havingValue = "true", @ConditionalOnProperty(prefix = "management.metrics.export.wavefront", name = "enabled", havingValue = "true",
matchIfMissing = true) matchIfMissing = true)
@EnableConfigurationProperties(WavefrontProperties.class) @EnableConfigurationProperties(WavefrontProperties.class)
...@@ -66,10 +75,38 @@ public class WavefrontMetricsExportAutoConfiguration { ...@@ -66,10 +75,38 @@ public class WavefrontMetricsExportAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public WavefrontMeterRegistry wavefrontMeterRegistry(WavefrontConfig wavefrontConfig, Clock clock) { public WavefrontSender wavefrontSender(WavefrontConfig wavefrontConfig) {
return WavefrontMeterRegistry.builder(wavefrontConfig).clock(clock).httpClient( if (!StringUtils.hasText(wavefrontConfig.apiToken())) {
new HttpUrlConnectionSender(this.properties.getConnectTimeout(), this.properties.getReadTimeout())) throw new MissingRequiredConfigurationException(
.build(); "apiToken must be set whenever publishing directly to the Wavefront API");
}
return createWavefrontSender(wavefrontConfig);
}
@Bean
@ConditionalOnMissingBean
public WavefrontMeterRegistry wavefrontMeterRegistry(WavefrontConfig wavefrontConfig, Clock clock,
WavefrontSender wavefrontSender) {
return WavefrontMeterRegistry.builder(wavefrontConfig).clock(clock).wavefrontSender(wavefrontSender).build();
}
private WavefrontSender createWavefrontSender(WavefrontConfig wavefrontConfig) {
Builder builder = new Builder(getWavefrontReportingUri(wavefrontConfig.uri()), wavefrontConfig.apiToken());
PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
Sender sender = this.properties.getSender();
mapper.from(sender.getMaxQueueSize()).to(builder::maxQueueSize);
mapper.from(sender.getBatchSize()).to(builder::batchSize);
mapper.from(sender.getFlushInterval()).asInt(Duration::getSeconds).to(builder::flushIntervalSeconds);
mapper.from(sender.getMessageSize()).asInt(DataSize::toBytes).to(builder::messageSizeBytes);
return builder.build();
}
private String getWavefrontReportingUri(String uri) {
// proxy reporting is now http reporting on newer wavefront proxies.
if (uri.startsWith("proxy")) {
return "http" + uri.substring(5);
}
return uri;
} }
} }
...@@ -17,15 +17,18 @@ ...@@ -17,15 +17,18 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront;
import java.net.URI; import java.net.URI;
import java.time.Duration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryProperties; import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryProperties;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.unit.DataSize;
/** /**
* {@link ConfigurationProperties @ConfigurationProperties} for configuring Wavefront * {@link ConfigurationProperties @ConfigurationProperties} for configuring Wavefront
* metrics export. * metrics export.
* *
* @author Jon Schneider * @author Jon Schneider
* @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
@ConfigurationProperties("management.metrics.export.wavefront") @ConfigurationProperties("management.metrics.export.wavefront")
...@@ -54,6 +57,8 @@ public class WavefrontProperties extends PushRegistryProperties { ...@@ -54,6 +57,8 @@ public class WavefrontProperties extends PushRegistryProperties {
*/ */
private String globalPrefix; private String globalPrefix;
private final Sender sender = new Sender();
public URI getUri() { public URI getUri() {
return this.uri; return this.uri;
} }
...@@ -86,4 +91,52 @@ public class WavefrontProperties extends PushRegistryProperties { ...@@ -86,4 +91,52 @@ public class WavefrontProperties extends PushRegistryProperties {
this.globalPrefix = globalPrefix; this.globalPrefix = globalPrefix;
} }
public Sender getSender() {
return this.sender;
}
public static class Sender {
private int maxQueueSize = 50000;
private int batchSize = 10000;
private Duration flushInterval = Duration.ofSeconds(1);
private DataSize messageSize = DataSize.ofBytes(Integer.MAX_VALUE);
public int getMaxQueueSize() {
return this.maxQueueSize;
}
public void setMaxQueueSize(int maxQueueSize) {
this.maxQueueSize = maxQueueSize;
}
public int getBatchSize() {
return this.batchSize;
}
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
public Duration getFlushInterval() {
return this.flushInterval;
}
public void setFlushInterval(Duration flushInterval) {
this.flushInterval = flushInterval;
}
public DataSize getMessageSize() {
return this.messageSize;
}
public void setMessageSize(DataSize messageSize) {
this.messageSize = messageSize;
}
}
} }
...@@ -380,6 +380,18 @@ ...@@ -380,6 +380,18 @@
"level": "error" "level": "error"
} }
}, },
{
"name": "management.metrics.export.wavefront.connect-timeout",
"deprecation": {
"level": "error"
}
},
{
"name": "management.metrics.export.wavefront.read-timeout",
"deprecation": {
"level": "error"
}
},
{ {
"name": "management.metrics.web.client.request.autotime.enabled", "name": "management.metrics.web.client.request.autotime.enabled",
"description": "Whether to automatically time web client requests.", "description": "Whether to automatically time web client requests.",
......
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront;
import com.wavefront.sdk.common.WavefrontSender;
import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Clock;
import io.micrometer.wavefront.WavefrontConfig; import io.micrometer.wavefront.WavefrontConfig;
import io.micrometer.wavefront.WavefrontMeterRegistry; import io.micrometer.wavefront.WavefrontMeterRegistry;
...@@ -28,6 +29,7 @@ import org.springframework.context.annotation.Configuration; ...@@ -28,6 +29,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link WavefrontMetricsExportAutoConfiguration}. * Tests for {@link WavefrontMetricsExportAutoConfiguration}.
...@@ -53,9 +55,10 @@ class WavefrontMetricsExportAutoConfigurationTests { ...@@ -53,9 +55,10 @@ class WavefrontMetricsExportAutoConfigurationTests {
@Test @Test
void autoConfigurationCanBeDisabled() { void autoConfigurationCanBeDisabled() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class) this.contextRunner.withUserConfiguration(BaseConfiguration.class)
.withPropertyValues("management.metrics.export.wavefront.enabled=false") .withPropertyValues("management.metrics.export.wavefront.api-token=abcde",
"management.metrics.export.wavefront.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean(WavefrontMeterRegistry.class) .run((context) -> assertThat(context).doesNotHaveBean(WavefrontMeterRegistry.class)
.doesNotHaveBean(WavefrontConfig.class)); .doesNotHaveBean(WavefrontConfig.class).doesNotHaveBean(WavefrontSender.class));
} }
@Test @Test
...@@ -63,7 +66,30 @@ class WavefrontMetricsExportAutoConfigurationTests { ...@@ -63,7 +66,30 @@ class WavefrontMetricsExportAutoConfigurationTests {
this.contextRunner.withUserConfiguration(CustomConfigConfiguration.class) this.contextRunner.withUserConfiguration(CustomConfigConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(Clock.class) .run((context) -> assertThat(context).hasSingleBean(Clock.class)
.hasSingleBean(WavefrontMeterRegistry.class).hasSingleBean(WavefrontConfig.class) .hasSingleBean(WavefrontMeterRegistry.class).hasSingleBean(WavefrontConfig.class)
.hasBean("customConfig")); .hasSingleBean(WavefrontSender.class).hasBean("customConfig"));
}
@Test
void configureWavefrontSender() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class)
.withPropertyValues("management.metrics.export.wavefront.api-token=abcde",
"management.metrics.export.wavefront.sender.max-queue-size=100",
"management.metrics.export.wavefront.sender.batch-size=200",
"management.metrics.export.wavefront.sender.message-size=1KB")
.run((context) -> {
WavefrontSender sender = context.getBean(WavefrontSender.class);
assertThat(sender).extracting("metricsBuffer").hasFieldOrPropertyWithValue("capacity", 100);
assertThat(sender).hasFieldOrPropertyWithValue("batchSize", 200);
assertThat(sender).hasFieldOrPropertyWithValue("messageSizeBytes", 1024);
});
}
@Test
void allowsWavefrontSenderToBeCustomized() {
this.contextRunner.withUserConfiguration(CustomSenderConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(Clock.class)
.hasSingleBean(WavefrontMeterRegistry.class).hasSingleBean(WavefrontConfig.class)
.hasSingleBean(WavefrontSender.class).hasBean("customSender"));
} }
@Test @Test
...@@ -109,13 +135,29 @@ class WavefrontMetricsExportAutoConfigurationTests { ...@@ -109,13 +135,29 @@ class WavefrontMetricsExportAutoConfigurationTests {
@Override @Override
public String uri() { public String uri() {
return WavefrontConfig.DEFAULT_PROXY.uri(); return WavefrontConfig.DEFAULT_DIRECT.uri();
}
@Override
public String apiToken() {
return "abc-def";
} }
}; };
} }
} }
@Configuration(proxyBeanMethods = false)
@Import(BaseConfiguration.class)
static class CustomSenderConfiguration {
@Bean
WavefrontSender customSender() {
return mock(WavefrontSender.class);
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@Import(BaseConfiguration.class) @Import(BaseConfiguration.class)
static class CustomRegistryConfiguration { static class CustomRegistryConfiguration {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment