Avoiding duplicate entries on properties map
Fix #859 Instead of sending out all matching relaxed properties we now pick up one single canonical format and send the data to consumers using it. Formatting settings
This commit is contained in:
committed by
Marius Bogoevici
parent
69cbc7f05b
commit
d78adea895
@@ -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=<DESTINATION_NAME>`.
|
||||
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,7 +31,8 @@ public class BinderMetricsEnvironmentPostProcessor implements EnvironmentPostPro
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
Map<String, Object> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Metric> metrics;
|
||||
|
||||
private Map<String,Object> properties;
|
||||
|
||||
private Map<String, Object> 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<Metric> metrics) {
|
||||
this.name = name;
|
||||
this.instanceIndex = instanceIndex;
|
||||
|
||||
@@ -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<ContextRefreshedEvent>, ApplicationContextAware {
|
||||
|
||||
@@ -65,34 +68,33 @@ public class BinderMetricsEmitter implements ApplicationListener<ContextRefreshe
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* List of properties that are going to be appended to each message.
|
||||
* This gets populate by onApplicationEvent, once the context refreshes to avoid overhead of doing per message basis.
|
||||
* List of properties that are going to be appended to each message. This gets
|
||||
* populate by onApplicationEvent, once the context refreshes to avoid overhead of
|
||||
* doing per message basis.
|
||||
*/
|
||||
private Map<String,Object> whitelistedProperties;
|
||||
private Map<String, Object> 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<Metric> filter(){
|
||||
protected Collection<Metric> filter() {
|
||||
Collection<Metric> result = new ArrayList<>();
|
||||
Iterable<Metric<?>> 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<ContextRefreshe
|
||||
}
|
||||
|
||||
/**
|
||||
* Shameless copied from {@link MetricCopyExporter}
|
||||
* @return
|
||||
* Copy of similarly named method in {@link MetricCopyExporter}.
|
||||
*/
|
||||
private boolean isMatch(String name, String[] includes, String[] excludes) {
|
||||
if (ObjectUtils.isEmpty(includes)
|
||||
|| PatternMatchUtils.simpleMatch(includes, name)) {
|
||||
if (ObjectUtils.isEmpty(includes) || PatternMatchUtils.simpleMatch(includes, name)) {
|
||||
return !PatternMatchUtils.simpleMatch(excludes, name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Iterates over all property sources from this application context and copies the ones listed in {@link StreamMetricsProperties} includes
|
||||
* Iterates over all property sources from this application context and copies the
|
||||
* ones listed in {@link StreamMetricsProperties} includes.
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) event.getSource();
|
||||
if (!ObjectUtils.isEmpty(this.properties.getProperties())) {
|
||||
@@ -122,9 +123,12 @@ public class BinderMetricsEmitter implements ApplicationListener<ContextRefreshe
|
||||
if (source instanceof EnumerablePropertySource) {
|
||||
EnumerablePropertySource e = (EnumerablePropertySource) source;
|
||||
for (String propertyName : e.getPropertyNames()) {
|
||||
for (String relaxedPropertyName : new RelaxedNames(propertyName)) {
|
||||
RelaxedNames relaxedNames = new RelaxedNames(propertyName);
|
||||
relaxedLoop: for (String relaxedPropertyName : relaxedNames) {
|
||||
if (isMatch(relaxedPropertyName, this.properties.getProperties(), null)) {
|
||||
whitelistedProperties.put(relaxedPropertyName, source.getProperty(propertyName));
|
||||
whitelistedProperties.put(RelaxedPropertiesUtils.findCanonicalFormat(relaxedNames),
|
||||
source.getProperty(propertyName));
|
||||
break relaxedLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,20 +45,21 @@ public class BootMetricJsonSerializer {
|
||||
public static class Serializer extends JsonSerializer<Metric> {
|
||||
|
||||
@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<Metric> {
|
||||
|
||||
|
||||
@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<Number> metric = new Metric(name,value,timestamp);
|
||||
Metric<Number> metric = new Metric(name, value, timestamp);
|
||||
|
||||
return metric;
|
||||
}
|
||||
|
||||
@@ -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<String> names) {
|
||||
TreeSet<String> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Metric> metrics){
|
||||
private boolean contains(String metric, Collection<Metric> metrics) {
|
||||
boolean contains = false;
|
||||
for(Metric entry : metrics){
|
||||
for (Metric entry : metrics) {
|
||||
contains = entry.getName().equals(metric);
|
||||
if(contains){
|
||||
if (contains) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user