diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/DefaultServoMetricNaming.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/DefaultServoMetricNaming.java new file mode 100644 index 000000000..ed53b8450 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/DefaultServoMetricNaming.java @@ -0,0 +1,114 @@ +/* + * Copyright 2013-2015 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.netflix.servo; + +import com.netflix.servo.Metric; +import com.netflix.servo.annotations.DataSourceType; +import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.servo.tag.Tag; +import com.netflix.servo.tag.TagList; + +/** + * @author Spencer Gibb + */ +public class DefaultServoMetricNaming implements ServoMetricNaming { + private static final String JMX_DOMAIN_KEY = "JmxDomain"; + public static final String SERVO = "servo."; + + @Override + public String getName(Metric metric) { + MonitorConfig config = metric.getConfig(); + TagList tags = config.getTags(); + + Tag domainTag = tags.getTag(JMX_DOMAIN_KEY); + String name; + if (domainTag != null) { // jmx metric + name = handleJmxMetric(config, tags); + } + else { + name = handleMetric(config, tags); + } + return name.toLowerCase(); + } + + private String handleMetric(MonitorConfig config, TagList tags) { + String type = cleanValue(tags.getTag(DataSourceType.KEY), false); + String instanceName = cleanValue(tags.getTag("instance"), false); + String name = cleanupIllegalCharacters(config.getName(), false); + String statistic = cleanValue(tags.getTag("statistic"), false); + + StringBuilder nameBuilder = new StringBuilder(); + if (type != null) { + nameBuilder.append(type).append("."); + } + nameBuilder.append(SERVO); + if (instanceName != null) { + nameBuilder.append(instanceName).append("."); + } + if (name != null) { + nameBuilder.append(name).append("."); + } + if (statistic != null) { + nameBuilder.append(statistic).append("."); + } + // remove trailing "." + nameBuilder.deleteCharAt(nameBuilder.lastIndexOf(".")); + return nameBuilder.toString(); + } + + private String handleJmxMetric(MonitorConfig config, TagList tags) { + String domain = cleanValue(tags.getTag(JMX_DOMAIN_KEY), true); + String type = cleanValue(tags.getTag("Jmx.type"), false); + String instanceName = cleanValue(tags.getTag("Jmx.instance"), false); + String name = cleanValue(tags.getTag("Jmx.name"), false); + String fieldName = cleanupIllegalCharacters(config.getName(), false); + + StringBuilder nameBuilder = new StringBuilder(); + nameBuilder.append(domain).append("."); + if (type != null) { + nameBuilder.append(type).append("."); + } + nameBuilder.append(SERVO); + if (instanceName != null) { + nameBuilder.append(instanceName).append("."); + } + if (name != null) { + nameBuilder.append(name).append("."); + } + if (fieldName != null) { + nameBuilder.append(fieldName).append("."); + } + // remove trailing "." + nameBuilder.deleteCharAt(nameBuilder.lastIndexOf(".")); + return nameBuilder.toString(); + } + + private String cleanValue(Tag tag, boolean allowPeriodsInName) { + if (tag == null) { + return null; + } + + return cleanupIllegalCharacters(tag.getValue(), allowPeriodsInName); + } + + private String cleanupIllegalCharacters(String s, boolean allowPeriodsInName) { + if (!allowPeriodsInName) { + s = s.replace(".", "_"); + } + return s.replace(" ", "_"); + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricCollector.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricCollector.java index e4f267b49..9234edd85 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricCollector.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricCollector.java @@ -17,19 +17,15 @@ package org.springframework.cloud.netflix.servo; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import lombok.extern.apachecommons.CommonsLog; import org.springframework.beans.factory.DisposableBean; -import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.writer.MetricWriter; -import com.netflix.servo.monitor.MonitorConfig; -import com.netflix.servo.publish.BaseMetricObserver; import com.netflix.servo.publish.BasicMetricFilter; import com.netflix.servo.publish.MetricObserver; import com.netflix.servo.publish.MonitorRegistryMetricPoller; @@ -46,9 +42,9 @@ import com.netflix.servo.publish.PollScheduler; @CommonsLog public class ServoMetricCollector implements DisposableBean { - public ServoMetricCollector(MetricWriter metrics) { - List observers = new ArrayList(); - observers.add(new ServoMetricObserver(metrics)); + public ServoMetricCollector(MetricWriter metrics, ServoMetricNaming naming) { + List observers = new ArrayList<>(); + observers.add(new ServoMetricObserver(metrics, naming)); PollRunnable task = new PollRunnable(new MonitorRegistryMetricPoller(), BasicMetricFilter.MATCH_ALL, true, observers); @@ -80,35 +76,4 @@ public class ServoMetricCollector implements DisposableBean { } } - /** - * {@link MetricObserver} to convert Servo metrics into Spring Boot {@link Metric} - * instances. - */ - private static final class ServoMetricObserver extends BaseMetricObserver { - - private final MetricWriter metrics; - - public ServoMetricObserver(MetricWriter metrics) { - super("spring-boot"); - this.metrics = metrics; - } - - @Override - public void updateImpl(List servoMetrics) { - for (com.netflix.servo.Metric servoMetric : servoMetrics) { - MonitorConfig config = servoMetric.getConfig(); - String type = config.getTags().getValue("type"); - String key = new StringBuilder(type).append(".servo.") - .append(config.getName()).toString().toLowerCase(); - - if (servoMetric.hasNumberValue()) { - this.metrics.set(new Metric(key, - servoMetric.getNumberValue(), new Date(servoMetric - .getTimestamp()))); - } - } - } - - } - } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricNaming.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricNaming.java new file mode 100644 index 000000000..99c7d0e35 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricNaming.java @@ -0,0 +1,26 @@ +/* + * Copyright 2013-2015 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.netflix.servo; + +import com.netflix.servo.Metric; + +/** + * @author Spencer Gibb + */ +public interface ServoMetricNaming { + String getName(Metric metric); +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricObserver.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricObserver.java new file mode 100644 index 000000000..edb5dad11 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricObserver.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2015 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.netflix.servo; + +import com.netflix.servo.Metric; +import com.netflix.servo.publish.BaseMetricObserver; +import org.springframework.boot.actuate.metrics.writer.MetricWriter; + +import java.util.Date; +import java.util.List; + +/** + * {@link com.netflix.servo.publish.MetricObserver} to convert Servo metrics into Spring Boot {@link org.springframework.boot.actuate.metrics.Metric} + * instances. + */ +final class ServoMetricObserver extends BaseMetricObserver { + + private final MetricWriter metrics; + private final ServoMetricNaming naming; + + public ServoMetricObserver(MetricWriter metrics, ServoMetricNaming naming) { + super("spring-boot"); + this.metrics = metrics; + this.naming = naming; + } + + @Override + public void updateImpl(List servoMetrics) { + for (Metric servoMetric : servoMetrics) { + String key = naming.getName(servoMetric); + + if (servoMetric.hasNumberValue()) { + this.metrics.set(new org.springframework.boot.actuate.metrics.Metric<>(key, + servoMetric.getNumberValue(), new Date(servoMetric + .getTimestamp()))); + } + } + } + +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java index 5af9bd5a1..52eac58d7 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java @@ -45,8 +45,15 @@ public class ServoMetricsAutoConfiguration { @Bean @ConditionalOnMissingBean - public ServoMetricCollector servoMetricCollector(MetricWriter metrics) { - return new ServoMetricCollector(metrics); + public ServoMetricNaming servoMetricNaming() { + return new DefaultServoMetricNaming(); + } + + @Bean + @ConditionalOnMissingBean + public ServoMetricCollector servoMetricCollector(MetricWriter metrics, + ServoMetricNaming naming) { + return new ServoMetricCollector(metrics, naming); } } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/servo/DefaultServerMetricNamingTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/servo/DefaultServerMetricNamingTests.java new file mode 100644 index 000000000..16e89387a --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/servo/DefaultServerMetricNamingTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-2015 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.netflix.servo; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + +import com.netflix.servo.annotations.DataSourceType; +import com.netflix.servo.monitor.MonitorConfig; +import org.junit.Test; + +import com.netflix.servo.Metric; + +/** + * @author Spencer Gibb + */ +public class DefaultServerMetricNamingTests { + + private DefaultServoMetricNaming naming = new DefaultServoMetricNaming(); + + @Test + public void vanillaServoMetricWorks() { + MonitorConfig config = MonitorConfig.builder("testMetric") .build(); + String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0)); + assertThat(name, is(equalTo("servo.testmetric"))); + } + + @Test + public void typeTagWorks() { + MonitorConfig config = MonitorConfig.builder("testMetric") + .withTag(DataSourceType.KEY, DataSourceType.COUNTER.getValue()) + .build(); + String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0)); + assertThat(name, is(equalTo("counter.servo.testmetric"))); + } + + @Test + public void instanceTagWorks() { + MonitorConfig config = MonitorConfig.builder("testMetric") + .withTag("instance", "instance0") + .build(); + String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0)); + assertThat(name, is(equalTo("servo.instance0.testmetric"))); + } + + @Test + public void statisticTagWorks() { + MonitorConfig config = MonitorConfig.builder("testMetric") + .withTag("statistic", "min") + .build(); + String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0)); + assertThat(name, is(equalTo("servo.testmetric.min"))); + } + + @Test + public void allTagsWork() { + MonitorConfig config = MonitorConfig.builder("testMetric") + .withTag(DataSourceType.KEY, DataSourceType.COUNTER.getValue()) + .withTag("instance", "instance0") + .withTag("statistic", "min") + .build(); + String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0)); + assertThat(name, is(equalTo("counter.servo.instance0.testmetric.min"))); + } +}