Metrics refactoring

- Change the metrics channel name to `applicationMetricsChannel`
- Update documentation
- Remove `instanceIndex` to reduce coupling to
  Spring Cloud Stream semantics and because it can be sent via
  the `properties` metadata set
This commit is contained in:
Marius Bogoevici
2017-03-31 17:31:24 -04:00
parent c5632cd5f3
commit d17e70ddaf
9 changed files with 66 additions and 100 deletions

View File

@@ -1840,11 +1840,13 @@ This module allow operators to collect metrics from stream applications without
HTTP polling can be challenging on cloud environments since applications could be on a private network or behind a Load balancer that prevents per instance access.
The module is activated when you set the destination name for its channel, `spring.cloud.stream.bindings.aggregateMetricsChannel.destination=<DESTINATION_NAME>`.
The module is activated when you set the destination name for its channel, `spring.cloud.stream.bindings.applicationMetricsChannel.destination=<DESTINATION_NAME>`.
`applicationMetricsChannel` can be configured in a similar fashion to any other bound output channel.
The default `contentType` setting of `applicationMetricsChannel` is `application/json`.
By default the module is configured to only send Spring Integration message channel metrics.
Available properties for customization using the prefix `spring.cloud.stream.metrics`.
Available properties for customization using the prefix `spring.cloud.stream.applicationMetricsChannel`.
key::
The name of the metric being emitted. Should be an unique value per application.
@@ -1875,11 +1877,6 @@ properties::
Just like the `includes` option, it allows white listing application properties that will be added to the metrics payload
+
Default: null.
+
spring.cloud.stream.bindings.aggregateMetricsChannel.contentType::
Content-Type of the message
+
Default: `application/json`
[NOTE]
====

View File

@@ -35,19 +35,15 @@ public class ApplicationMetrics {
private final Date createdTime;
private String name;
private int instanceIndex;
private Collection<Metric<?>> metrics;
private Map<String, Object> properties;
@JsonCreator
public ApplicationMetrics(@JsonProperty("name") String name,
@JsonProperty("instanceIndex") int instanceIndex,
@JsonProperty("metrics") Collection<Metric<?>> metrics) {
this.name = name;
this.instanceIndex = instanceIndex;
this.metrics = metrics;
this.createdTime = new Date();
}
@@ -60,14 +56,6 @@ public class ApplicationMetrics {
this.name = name;
}
public int getInstanceIndex() {
return instanceIndex;
}
public void setInstanceIndex(int instanceIndex) {
this.instanceIndex = instanceIndex;
}
public Collection<Metric<?>> getMetrics() {
return metrics;
}

View File

@@ -57,7 +57,7 @@ public class ApplicationMetricsExporter implements Exporter {
@Override
public void export() {
ApplicationMetrics appMetrics = new ApplicationMetrics(
this.properties.getMetricName(), this.properties.getInstanceIndex(),
this.properties.getMetricName(),
filter());
appMetrics.setProperties(this.properties.getExportProperties());
source.send(MessageBuilder.withPayload(appMetrics).build());

View File

@@ -44,9 +44,6 @@ public class ApplicationMetricsProperties
@Value("${spring.application.name:${vcap.application.name:${spring.config.name:application}}}")
private String key;
@Value("${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}")
private int instanceIndex;
private String metricName;
private String[] properties;
@@ -80,14 +77,6 @@ public class ApplicationMetricsProperties
this.key = key;
}
public int getInstanceIndex() {
return instanceIndex;
}
public void setInstanceIndex(int instanceIndex) {
this.instanceIndex = instanceIndex;
}
public String[] getProperties() {
return properties;
}

View File

@@ -34,14 +34,14 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnClass(Binder.class)
@EnableBinding(Emitter.class)
@EnableConfigurationProperties(ApplicationMetricsProperties.class)
@ConditionalOnProperty("spring.cloud.stream.bindings." + Emitter.METRICS_CHANNEL_NAME
@ConditionalOnProperty("spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS
+ ".destination")
public class BinderMetricsAutoConfiguration {
@Bean
public ApplicationMetricsExporter aggregateMetricsExporter(MetricsEndpoint endpoint,
Emitter emitter, ApplicationMetricsProperties properties) {
return new ApplicationMetricsExporter(endpoint, emitter.metrics(), properties);
return new ApplicationMetricsExporter(endpoint, emitter.applicationMetrics(), properties);
}
@Bean

View File

@@ -29,15 +29,12 @@ import org.springframework.core.env.MapPropertySource;
*/
public class BinderMetricsEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Map<String, Object> propertiesToAdd = new HashMap<>();
propertiesToAdd.put("spring.cloud.stream.bindings."+Emitter.METRICS_CHANNEL_NAME+".contentType",
propertiesToAdd.put("spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".contentType",
"application/json");
propertiesToAdd.put("spring.cloud.stream.metrics.instanceIndex",
"${spring.cloud.stream.instanceIndex:${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}}");
propertiesToAdd.put("spring.metrics.export.includes", "integration**");
environment.getPropertySources().addLast(
new MapPropertySource("binderMetricsDefaultProperties", propertiesToAdd));
environment.getPropertySources()
.addLast(new MapPropertySource("binderMetricsDefaultProperties", propertiesToAdd));
}
}

View File

@@ -24,9 +24,9 @@ import org.springframework.messaging.MessageChannel;
*/
public interface Emitter {
String METRICS_CHANNEL_NAME = "aggregateMetricsChannel";
String APPLICATION_METRICS = "applicationMetricsChannel";
@Output(METRICS_CHANNEL_NAME)
MessageChannel metrics();
@Output(APPLICATION_METRICS)
MessageChannel applicationMetrics();
}

View File

@@ -73,21 +73,20 @@ public class ApplicationMetricsExporterTests {
ConfigurableApplicationContext applicationContext = SpringApplication.run(
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.cloud.stream.bindings.aggregateMetricsChannel.destination=foo");
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.metrics()).poll(1000,
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000,
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetrics = mapper
ApplicationMetrics applicationMetricsChannel = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetrics.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetrics.getMetrics()));
Assert.assertEquals("application", applicationMetrics.getName());
Assert.assertEquals(0, applicationMetrics.getInstanceIndex());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertEquals("application", applicationMetricsChannel.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationContext.close();
}
@@ -96,22 +95,21 @@ public class ApplicationMetricsExporterTests {
ConfigurableApplicationContext applicationContext = SpringApplication.run(
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.application.name=foo", "--spring.cloud.stream.instanceIndex=1",
"--spring.cloud.stream.bindings.aggregateMetricsChannel.destination=foo");
"--spring.application.name=foo",
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.metrics()).poll(1000,
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000,
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetrics = mapper
ApplicationMetrics applicationMetricsChannel = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetrics.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetrics.getMetrics()));
Assert.assertEquals("foo", applicationMetrics.getName());
Assert.assertEquals(1, applicationMetrics.getInstanceIndex());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertEquals("foo", applicationMetricsChannel.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationContext.close();
}
@@ -121,22 +119,20 @@ public class ApplicationMetricsExporterTests {
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.cloud.stream.metrics.prefix=foo",
"--spring.cloud.stream.instanceIndex=1",
"--spring.cloud.stream.bindings.aggregateMetricsChannel.destination=foo");
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.metrics()).poll(1000,
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000,
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetrics = mapper
ApplicationMetrics applicationMetricsChannel = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetrics.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetrics.getMetrics()));
Assert.assertEquals("foo.application", applicationMetrics.getName());
Assert.assertEquals(1, applicationMetrics.getInstanceIndex());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertEquals("foo.application", applicationMetricsChannel.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationContext.close();
}
@@ -145,21 +141,21 @@ public class ApplicationMetricsExporterTests {
ConfigurableApplicationContext applicationContext = SpringApplication.run(
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.cloud.stream.bindings.aggregateMetricsChannel.destination=foo",
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo",
"--spring.metrics.export.includes=mem**",
"--spring.metrics.export.excludes=integration**");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.metrics()).poll(1000,
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000,
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetrics = mapper
ApplicationMetrics applicationMetricsChannel = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertFalse(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetrics.getMetrics()));
Assert.assertTrue(contains("mem", applicationMetrics.getMetrics()));
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationMetricsChannel.getMetrics()));
Assert.assertTrue(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationContext.close();
}
@@ -168,22 +164,22 @@ public class ApplicationMetricsExporterTests {
ConfigurableApplicationContext applicationContext = SpringApplication.run(
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.cloud.stream.bindings.aggregateMetricsChannel.destination=foo",
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo",
"--spring.metrics.export.includes=integration**",
"--spring.cloud.stream.metrics.properties=java**,spring.test.env**");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.metrics()).poll(1000,
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000,
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetrics = mapper
ApplicationMetrics applicationMetricsChannel = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertFalse(contains("mem", applicationMetrics.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetrics.getMetrics()));
Assert.assertFalse(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
Assert.assertTrue(applicationMetrics.getProperties().get("spring.test.env.syntax")
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
Assert.assertTrue(applicationMetricsChannel.getProperties().get("spring.test.env.syntax")
.equals("testing"));
applicationContext.close();
}
@@ -193,23 +189,22 @@ public class ApplicationMetricsExporterTests {
ConfigurableApplicationContext applicationContext = SpringApplication.run(
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.application.name=foo", "--spring.cloud.stream.instanceIndex=1",
"--spring.cloud.stream.bindings.aggregateMetricsChannel.destination=foo",
"--spring.application.name=foo",
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo",
"--spring.cloud.stream.metrics.key=foobarfoo");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.metrics()).poll(1000,
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000,
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetrics = mapper
ApplicationMetrics applicationMetricsChannel = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetrics.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetrics.getMetrics()));
Assert.assertEquals("foobarfoo", applicationMetrics.getName());
Assert.assertEquals(1, applicationMetrics.getInstanceIndex());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertEquals("foobarfoo", applicationMetricsChannel.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationContext.close();
}

View File

@@ -33,24 +33,24 @@ public class RelaxedPropertiesUtilsTests {
RelaxedNames springEnv = new RelaxedNames("SPRING_APPLICATION_NAME");
RelaxedNames springDot = new RelaxedNames("spring.application.name");
RelaxedNames springCamel = new RelaxedNames("springApplicationName");
RelaxedNames contentType = new RelaxedNames("spring.cloud.stream.bindings.aggregateMetricsChannel.contentType");
RelaxedNames contentTypeEnv = new RelaxedNames("SPRING_CLOUD_STREAM_BINDINGS_AGGREGATE-METRICS-CHANNEL_CONTENT-TYPE");
RelaxedNames contentType = new RelaxedNames("spring.cloud.stream.bindings.applicationMetricsChannel.contentType");
RelaxedNames contentTypeEnv = new RelaxedNames("SPRING_CLOUD_STREAM_BINDINGS_APPLICATION-METRICS-CHANNEL_CONTENT-TYPE");
RelaxedNames xyz = new RelaxedNames("My.X.Is");
RelaxedNames springMetrics = new RelaxedNames("spring.cloud.stream.aggregateMetricsChannel");
RelaxedNames springMetricsEnv = new RelaxedNames("spring.cloud.stream.aggregate-metrics-channel");
RelaxedNames springMetrics = new RelaxedNames("spring.cloud.stream.applicationMetricsChannel");
RelaxedNames springMetricsEnv = new RelaxedNames("spring.cloud.stream.application-metrics-channel");
Assert.assertEquals("java.home", RelaxedPropertiesUtils.findCanonicalFormat(javaHome));
Assert.assertEquals("os", RelaxedPropertiesUtils.findCanonicalFormat(os));
Assert.assertEquals("spring.application.name", RelaxedPropertiesUtils.findCanonicalFormat(springEnv));
Assert.assertEquals("spring.application.name", RelaxedPropertiesUtils.findCanonicalFormat(springDot));
Assert.assertEquals("spring.application.name", RelaxedPropertiesUtils.findCanonicalFormat(springCamel));
Assert.assertEquals("spring.cloud.stream.bindings.aggregateMetricsChannel.contentType",
Assert.assertEquals("spring.cloud.stream.bindings.applicationMetricsChannel.contentType",
RelaxedPropertiesUtils.findCanonicalFormat(contentType));
Assert.assertEquals("spring.cloud.stream.bindings.aggregateMetricsChannel.contentType",
Assert.assertEquals("spring.cloud.stream.bindings.applicationMetricsChannel.contentType",
RelaxedPropertiesUtils.findCanonicalFormat(contentTypeEnv));
Assert.assertEquals("My.X.Is", RelaxedPropertiesUtils.findCanonicalFormat(xyz));
Assert.assertEquals("spring.cloud.stream.aggregateMetricsChannel",
Assert.assertEquals("spring.cloud.stream.applicationMetricsChannel",
RelaxedPropertiesUtils.findCanonicalFormat(springMetrics));
Assert.assertEquals("spring.cloud.stream.aggregateMetricsChannel",
Assert.assertEquals("spring.cloud.stream.applicationMetricsChannel",
RelaxedPropertiesUtils.findCanonicalFormat(springMetricsEnv));
}