diff --git a/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc b/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc index 8b3ead78e..8dc2dcae9 100644 --- a/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc +++ b/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc @@ -1828,8 +1828,6 @@ 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. -This module is included on the each of the out of box https://github.com/spring-cloud-stream-app-starters[application starters], if you want to emit metrics from your application, just import it on your pom.xml and follow the activation instructions. - The module is activated when you set the destination name for its channel, `spring.cloud.stream.bindings.streamMetrics.destination=`. By default the module is configured to only send Spring Integration message channel metrics. @@ -1866,6 +1864,35 @@ spring.cloud.stream.bindings.streamMetrics.contentType:: + Default: `application/json` +[NOTE] +==== +Due to Spring Boot's https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-relaxed-binding[relaxed binding] the value of a property being included can be slightly different than the original value. + +As a rule of thumb, the metric exporter will attempt to normalize all the properties in a consistent format using the dot notation (e.g. `JAVA_HOME` becomes `java.home`). + +The goal of normalization is to make downstream consumers of those metrics capable of receiving property names consistently, regardless of how they are set on the monitored application (`--spring.application.name` or `SPRING_APPLICATION_NAME` would always yield `spring.application.name`). + +==== + +Below is a sample of data that is pushed to the channel in `application/json` format. + +[source,javascript] +---- +{ + "name" : "application", + "instanceIndex" : 0, + "createdTime" : "2017-03-14T13:55:38.547Z", + "properties" : { + "java.specification.version" : "1.8" + } + "metrics" : [ { + "name" : "mem", + "value" : 170757.0, + "timestamp" : "2017-03-14T09:55:38.547Z" + } ] +} +---- + == Samples For Spring Cloud Stream samples, please refer to the https://github.com/spring-cloud/spring-cloud-stream-samples[spring-cloud-stream-samples] repository on GitHub. diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsAutoConfiguration.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsAutoConfiguration.java index e8dcea219..498f8a8ae 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsAutoConfiguration.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsAutoConfiguration.java @@ -37,7 +37,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling @EnableBinding(Emitter.class) @EnableConfigurationProperties(StreamMetricsProperties.class) -@ConditionalOnProperty("spring.cloud.stream.bindings."+ Emitter.METRICS_CHANNEL_NAME + ".destination") +@ConditionalOnProperty("spring.cloud.stream.bindings." + Emitter.METRICS_CHANNEL_NAME + ".destination") public class BinderMetricsAutoConfiguration { @Bean diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsEnvironmentPostProcessor.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsEnvironmentPostProcessor.java index 4424f0a2d..cb2d32da5 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsEnvironmentPostProcessor.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsEnvironmentPostProcessor.java @@ -31,7 +31,8 @@ public class BinderMetricsEnvironmentPostProcessor implements EnvironmentPostPro @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { Map propertiesToAdd = new HashMap<>(); - propertiesToAdd.put("spring.cloud.stream.bindings.streamMetrics.contentType","application/json"); - environment.getPropertySources().addLast(new MapPropertySource("binderMetricsDefaultProperties",propertiesToAdd)); + propertiesToAdd.put("spring.cloud.stream.bindings.streamMetrics.contentType", "application/json"); + environment.getPropertySources() + .addLast(new MapPropertySource("binderMetricsDefaultProperties", propertiesToAdd)); } } diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/StreamMetricsProperties.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/StreamMetricsProperties.java index a320707b8..f92bf0351 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/StreamMetricsProperties.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/StreamMetricsProperties.java @@ -26,7 +26,6 @@ import org.springframework.util.StringUtils; @ConfigurationProperties(prefix = "spring.cloud.stream.metrics") public class StreamMetricsProperties { - private String prefix; @Value("${spring.application.name:${vcap.application.name:${spring.config.name:application}}}") @@ -34,7 +33,7 @@ public class StreamMetricsProperties { private String metricName; - private String[] includes = new String[]{"integration**"}; + private String[] includes = new String[] { "integration**" }; private String[] excludes; @@ -78,6 +77,10 @@ public class StreamMetricsProperties { return applicationName; } + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + public String[] getProperties() { return properties; } @@ -87,25 +90,21 @@ public class StreamMetricsProperties { } public String getMetricName() { - if(this.metricName == null){ + if (this.metricName == null) { this.metricName = resolveMetricName(); } return metricName; } - private String resolveMetricName(){ + private String resolveMetricName() { StringBuffer name = new StringBuffer(this.applicationName); - if(!StringUtils.isEmpty(this.prefix)){ + if (!StringUtils.isEmpty(this.prefix)) { String prefix = this.prefix; - if(prefix.lastIndexOf(".") == -1){ + if (prefix.lastIndexOf(".") == -1) { prefix += "."; } - name.insert(0,prefix); + name.insert(0, prefix); } return name.toString(); } - - public void setApplicationName(String applicationName) { - this.applicationName = applicationName; - } } diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetrics.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetrics.java index 38782e4f2..646364d40 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetrics.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetrics.java @@ -31,21 +31,19 @@ import org.springframework.boot.actuate.metrics.Metric; */ public class ApplicationMetrics { + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private final Date createdTime; + private String name; private int instanceIndex; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") - private final Date createdTime; - private Collection metrics; - private Map properties; - + private Map properties; @JsonCreator - public ApplicationMetrics(@JsonProperty("name") String name, - @JsonProperty("instanceIndex") int instanceIndex, + public ApplicationMetrics(@JsonProperty("name") String name, @JsonProperty("instanceIndex") int instanceIndex, @JsonProperty("metrics") Collection metrics) { this.name = name; this.instanceIndex = instanceIndex; diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitter.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitter.java index 942866eeb..516302002 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitter.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitter.java @@ -43,11 +43,14 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; /** + * + * Component that sends metrics from {@link MetricsEndpointMetricReader} downstream via + * the configured metrics channel. + * + * It uses {@link Scheduled} support to periodially emit messages polled from the + * endpoint. + * * @author Vinicius Carvalho - * - * Component that sends metrics from {@link MetricsEndpointMetricReader} downstream via the configured metrics channel. - * - * It uses {@link Scheduled} support to periodially emit messages polled from the endpoint. */ public class BinderMetricsEmitter implements ApplicationListener, ApplicationContextAware { @@ -65,34 +68,33 @@ public class BinderMetricsEmitter implements ApplicationListener whitelistedProperties; + private Map whitelistedProperties; - public BinderMetricsEmitter(MetricsEndpoint endpoint){ + public BinderMetricsEmitter(MetricsEndpoint endpoint) { this.metricsReader = new MetricsEndpointMetricReader(endpoint); this.whitelistedProperties = new HashMap<>(); } @Scheduled(fixedRateString = "${spring.cloud.stream.metrics.delay-millis:5000}") - public void sendMetrics(){ + public void sendMetrics() { ApplicationMetrics appMetrics = new ApplicationMetrics(this.properties.getMetricName(), - this.bindingServiceProperties.getInstanceIndex(), - filter()); + this.bindingServiceProperties.getInstanceIndex(), filter()); appMetrics.setProperties(whitelistedProperties); source.metrics().send(MessageBuilder.withPayload(appMetrics).build()); } /** - * Shameless copied from {@link MetricCopyExporter} - * @return + * Copy of similarly named method in {@link MetricCopyExporter}. */ - protected Collection filter(){ + protected Collection filter() { Collection result = new ArrayList<>(); Iterable> metrics = metricsReader.findAll(); - for(Metric metric : metrics){ - if(isMatch(metric.getName(),this.properties.getIncludes(),this.properties.getExcludes())){ + for (Metric metric : metrics) { + if (isMatch(metric.getName(), this.properties.getIncludes(), this.properties.getExcludes())) { result.add(metric); } } @@ -100,21 +102,20 @@ public class BinderMetricsEmitter implements ApplicationListener { @Override - public void serialize(Metric metric, JsonGenerator json, SerializerProvider serializerProvider) throws IOException { + public void serialize(Metric metric, JsonGenerator json, SerializerProvider serializerProvider) + throws IOException { json.writeStartObject(); - json.writeStringField("name",metric.getName()); - json.writeNumberField("value",metric.getValue().doubleValue()); - json.writeStringField("timestamp",df.format(metric.getTimestamp())); + json.writeStringField("name", metric.getName()); + json.writeNumberField("value", metric.getValue().doubleValue()); + json.writeStringField("timestamp", df.format(metric.getTimestamp())); json.writeEndObject(); } } public static class Deserializer extends JsonDeserializer { - @Override - public Metric deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + public Metric deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { JsonNode node = p.getCodec().readTree(p); String name = node.get("name").asText(); Number value = node.get("value").asDouble(); @@ -69,7 +70,7 @@ public class BootMetricJsonSerializer { } catch (ParseException e) { } - Metric metric = new Metric(name,value,timestamp); + Metric metric = new Metric(name, value, timestamp); return metric; } diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtils.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtils.java new file mode 100644 index 000000000..91f7d8d1f --- /dev/null +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtils.java @@ -0,0 +1,145 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.metrics; + +import java.util.TreeSet; +import java.util.regex.Pattern; + +import org.springframework.boot.bind.RelaxedNames; +import org.springframework.util.StringUtils; + +/** + * Utility class to deal with {@link RelaxedNames}. + * + * @author Vinicius Carvalho + */ +public class RelaxedPropertiesUtils { + + private static final Pattern HYPHEN_LOWER = Pattern.compile("-_|_-|__|\\.-|\\._"); + + private static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern.compile("[_\\-.]"); + + private static final char[] SUFFIXES = new char[] { '_', '-', '.' }; + + /** + * Searches relaxed names and tries to find a best match for a canonical form using + * dot notation. + * For example, if a new list was built with JAVA_HOME as property, the return of this + * method would be {@code java.home}. + * + * Relaxed names generate a long list of variations of a property, it can be tricky + * trying to infer the correct format, which sometimes may not even exist in dot + * notation. + * + * This method attempts to find a best match, or if none is found it converts + * underscore format to dot notation. + * + * @param names list of possible permutations of a property + * @return the canonical form (dot notation) of this property + */ + public static String findCanonicalFormat(Iterable names) { + TreeSet sorted = new TreeSet<>(); + String environmentFormat = null; + String simpleFormat = null; + for (String name : names) { + sorted.add(name); + } + String canonicalForm = null; + for (String name : sorted) { + if (HYPHEN_LOWER.matcher(name).find()) { + continue; + } + if (upperCaseRatio(name) == 1.0) { + if (name.contains("_")) { + environmentFormat = name; + } + if (!name.matches("^.*?(_|-).*$")) { + simpleFormat = name; + } + continue; + } + if (name.contains(".")) { + String[] keys = name.split("\\."); + for (int i = 0; i < keys.length; i++) { + keys[i] = separatedToCamelCase(keys[i], false); + } + canonicalForm = StringUtils.arrayToDelimitedString(keys, "."); + break; + } + } + // If we can't find any variation it could mean we have a camelCase only value + // such as springApplicationName. + // In this case RelaxedNames only generate _ values, so we should get one and + // transform into dot notation. + // Another possibility is a top level property such as MEM or OS, in this case + // there's no separator and we only set to lowercase + if (canonicalForm == null) { + if (environmentFormat != null) { + canonicalForm = environmentFormat.toLowerCase().replace("_", "."); + } + if (canonicalForm == null) { + if (simpleFormat != null) { + canonicalForm = simpleFormat.toLowerCase(); + } + } + } + return canonicalForm; + } + + /** + * Returns the ratio of uppercase chars on a string + * @param input + * @return + */ + private static double upperCaseRatio(String input) { + int upperCaseCount = 0; + String compare = input.replaceAll("[._-]", ""); + for (int i = 0; i < compare.length(); i++) { + if (Character.isUpperCase(compare.charAt(i))) { + upperCaseCount++; + } + } + return (float) upperCaseCount / compare.length(); + } + + /** + * Taken from {@link RelaxedNames}, convert an input of type string-string into + * stringString + * @param value + * @param caseInsensitive + * @return + */ + private static String separatedToCamelCase(String value, boolean caseInsensitive) { + if (value.isEmpty()) { + return value; + } + StringBuilder builder = new StringBuilder(); + for (String field : SEPARATED_TO_CAMEL_CASE_PATTERN.split(value)) { + field = (caseInsensitive ? field.toLowerCase() : field); + builder.append(builder.length() == 0 ? field : StringUtils.capitalize(field)); + } + char lastChar = value.charAt(value.length() - 1); + for (char suffix : SUFFIXES) { + if (lastChar == suffix) { + builder.append(suffix); + break; + } + } + return builder.toString(); + } + +} diff --git a/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitterTests.java b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitterTests.java index 25bf92177..141154a71 100644 --- a/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitterTests.java +++ b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitterTests.java @@ -39,11 +39,9 @@ import org.springframework.util.CollectionUtils; */ public class BinderMetricsEmitterTests { - - @BeforeClass public static void setSystemProps() { - System.setProperty("SPRING_TEST_ENV_SYNTAX","testing"); + System.setProperty("SPRING_TEST_ENV_SYNTAX", "testing"); } @AfterClass @@ -51,17 +49,14 @@ public class BinderMetricsEmitterTests { System.clearProperty("SPRING_TEST_ENV_SYNTAX"); } - @Test(expected = NoSuchBeanDefinitionException.class) public void checkDisabledConfiguration() throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", - "--spring.jmx.enabled=false", - "--spring.cloud.stream.metrics.delay-millis=500" - ); + "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500"); try { applicationContext.getBean(Emitter.class); - } catch (Exception e){ + } + catch (Exception e) { throw e; } finally { @@ -73,43 +68,40 @@ public class BinderMetricsEmitterTests { @Test public void defaultIncludes() throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", - "--spring.jmx.enabled=false", - "--spring.cloud.stream.metrics.delay-millis=500", + "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", "--spring.cloud.stream.bindings.streamMetrics.destination=foo"); Emitter emitterSource = applicationContext.getBean(Emitter.class); MessageCollector collector = applicationContext.getBean(MessageCollector.class); Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String)message.getPayload(),ApplicationMetrics.class); + ApplicationMetrics applicationMetrics = 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.assertFalse(contains("mem", applicationMetrics.getMetrics())); + Assert.assertEquals("application", applicationMetrics.getName()); + Assert.assertEquals(0, applicationMetrics.getInstanceIndex()); Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties())); applicationContext.close(); } @Test - public void customAppNameAndIndex() throws Exception{ + public void customAppNameAndIndex() throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", - "--spring.jmx.enabled=false", - "--spring.cloud.stream.metrics.delay-millis=500", - "--spring.application.name=foo", - "--spring.cloud.stream.instanceIndex=1", + "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", + "--spring.application.name=foo", "--spring.cloud.stream.instanceIndex=1", "--spring.cloud.stream.bindings.streamMetrics.destination=foo"); Emitter emitterSource = applicationContext.getBean(Emitter.class); MessageCollector collector = applicationContext.getBean(MessageCollector.class); Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String)message.getPayload(),ApplicationMetrics.class); + ApplicationMetrics applicationMetrics = 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.assertFalse(contains("mem", applicationMetrics.getMetrics())); + Assert.assertEquals("foo", applicationMetrics.getName()); + Assert.assertEquals(1, applicationMetrics.getInstanceIndex()); Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties())); applicationContext.close(); } @@ -117,22 +109,20 @@ public class BinderMetricsEmitterTests { @Test public void usingPrefix() throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", - "--spring.jmx.enabled=false", - "--spring.cloud.stream.metrics.delay-millis=500", - "--spring.cloud.stream.metrics.prefix=foo", - "--spring.cloud.stream.instanceIndex=1", + "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", + "--spring.cloud.stream.metrics.prefix=foo", "--spring.cloud.stream.instanceIndex=1", "--spring.cloud.stream.bindings.streamMetrics.destination=foo"); Emitter emitterSource = applicationContext.getBean(Emitter.class); MessageCollector collector = applicationContext.getBean(MessageCollector.class); Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String)message.getPayload(),ApplicationMetrics.class); + ApplicationMetrics applicationMetrics = 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.assertFalse(contains("mem", applicationMetrics.getMetrics())); + Assert.assertEquals("foo.application", applicationMetrics.getName()); + Assert.assertEquals(1, applicationMetrics.getInstanceIndex()); Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties())); applicationContext.close(); } @@ -140,20 +130,19 @@ public class BinderMetricsEmitterTests { @Test public void includesExcludes() throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", - "--spring.jmx.enabled=false", - "--spring.cloud.stream.metrics.delay-millis=500", + "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", "--spring.cloud.stream.bindings.streamMetrics.destination=foo", - "--spring.cloud.stream.metrics.includes=mem**", - "--spring.cloud.stream.metrics.excludes=integration**"); + "--spring.cloud.stream.metrics.includes=mem**", "--spring.cloud.stream.metrics.excludes=integration**"); Emitter emitterSource = applicationContext.getBean(Emitter.class); MessageCollector collector = applicationContext.getBean(MessageCollector.class); Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String)message.getPayload(),ApplicationMetrics.class); - Assert.assertFalse(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); - Assert.assertTrue(contains("mem",applicationMetrics.getMetrics())); + ApplicationMetrics applicationMetrics = 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())); applicationContext.close(); } @@ -161,9 +150,7 @@ public class BinderMetricsEmitterTests { @Test public void includesExcludesWithProperties() throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", - "--spring.jmx.enabled=false", - "--spring.cloud.stream.metrics.delay-millis=500", + "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", "--spring.cloud.stream.bindings.streamMetrics.destination=foo", "--spring.cloud.stream.metrics.includes=integration**", "--spring.cloud.stream.metrics.properties=java**,spring.test.env**"); @@ -172,19 +159,20 @@ public class BinderMetricsEmitterTests { Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String)message.getPayload(),ApplicationMetrics.class); + ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), + ApplicationMetrics.class); Assert.assertFalse(contains("mem", applicationMetrics.getMetrics())); - Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",applicationMetrics.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").equals("testing")); applicationContext.close(); } - private boolean contains(String metric, Collection metrics){ + private boolean contains(String metric, Collection metrics) { boolean contains = false; - for(Metric entry : metrics){ + for (Metric entry : metrics) { contains = entry.getName().equals(metric); - if(contains){ + if (contains) { break; } } diff --git a/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtilsTests.java b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtilsTests.java new file mode 100644 index 000000000..81a01d568 --- /dev/null +++ b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtilsTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.metrics; + +import org.junit.Assert; +import org.junit.Test; + +import org.springframework.boot.bind.RelaxedNames; + +/** + * @author Vinicius Carvalho + */ +public class RelaxedPropertiesUtilsTests { + + @Test + public void testVariations() throws Exception { + RelaxedNames javaHome = new RelaxedNames("JAVA_HOME"); + RelaxedNames os = new RelaxedNames("OS"); + 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.streamMetrics.contentType"); + RelaxedNames contentTypeEnv = new RelaxedNames("SPRING_CLOUD_STREAM_BINDINGS_STREAM-METRICS_CONTENT-TYPE"); + RelaxedNames xyz = new RelaxedNames("My.X.Is"); + RelaxedNames springMetrics = new RelaxedNames("spring.cloud.stream.streamMetrics"); + RelaxedNames springMetricsEnv = new RelaxedNames("spring.cloud.stream.stream-metrics"); + 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.streamMetrics.contentType", + RelaxedPropertiesUtils.findCanonicalFormat(contentType)); + Assert.assertEquals("spring.cloud.stream.bindings.streamMetrics.contentType", + RelaxedPropertiesUtils.findCanonicalFormat(contentTypeEnv)); + Assert.assertEquals("My.X.Is", RelaxedPropertiesUtils.findCanonicalFormat(xyz)); + Assert.assertEquals("spring.cloud.stream.streamMetrics", + RelaxedPropertiesUtils.findCanonicalFormat(springMetrics)); + Assert.assertEquals("spring.cloud.stream.streamMetrics", + RelaxedPropertiesUtils.findCanonicalFormat(springMetricsEnv)); + } + +}