diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfiguration.java index 20b6db5425..4c4f0ef9f4 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfiguration.java @@ -22,12 +22,15 @@ import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.MetricsEndpointMetricReader; import org.springframework.boot.actuate.metrics.export.MetricExportProperties; import org.springframework.boot.actuate.metrics.export.MetricExporters; +import org.springframework.boot.actuate.metrics.reader.CompositeMetricReader; import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; @@ -40,6 +43,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; @Configuration @EnableScheduling @ConditionalOnProperty(value = "spring.metrics.export.enabled", matchIfMissing = true) +@EnableConfigurationProperties(MetricExportProperties.class) public class MetricExportAutoConfiguration { @Autowired(required = false) @@ -54,28 +58,40 @@ public class MetricExportAutoConfiguration { @Autowired(required = false) @ActuatorMetricReader - private MetricReader reader; + private List readers; + + @Autowired(required = false) + private MetricsEndpointMetricReader endpointReader; @Bean @ConditionalOnMissingBean public SchedulingConfigurer metricWritersMetricExporter() { + Map writers = new HashMap(); - if (this.reader != null) { + + MetricReader reader = endpointReader; + if (reader == null && !this.readers.isEmpty()) { + reader = new CompositeMetricReader(this.readers.toArray(new MetricReader[0])); + } + + if (reader != null) { writers.putAll(this.writers); for (String name : this.writers.keySet()) { if (this.actuatorMetrics.contains(writers.get(name))) { writers.remove(name); } } - MetricExporters exporters = new MetricExporters(this.reader, writers, - this.metrics); + MetricExporters exporters = new MetricExporters(reader, writers, this.metrics); return exporters; } + return new SchedulingConfigurer() { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { } }; + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java index 2c11bc056c..673c3a7be6 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpoint.java @@ -58,10 +58,20 @@ public class MetricsEndpoint extends AbstractEndpoint> { AnnotationAwareOrderComparator.sort(this.publicMetrics); } + public void registerPublicMetrics(PublicMetrics metrics) { + this.publicMetrics.add(metrics); + AnnotationAwareOrderComparator.sort(this.publicMetrics); + } + + public void unregisterPublicMetrics(PublicMetrics metrics) { + this.publicMetrics.remove(metrics); + } + @Override public Map invoke() { Map result = new LinkedHashMap(); - for (PublicMetrics publicMetric : this.publicMetrics) { + List metrics = new ArrayList(this.publicMetrics); + for (PublicMetrics publicMetric : metrics) { try { for (Metric metric : publicMetric.metrics()) { result.put(metric.getName(), metric.getValue()); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java new file mode 100644 index 0000000000..be756a7ee9 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/MetricsEndpointMetricReader.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-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.boot.actuate.endpoint; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.reader.MetricReader; + +/** + * A {@link MetricReader} that pulls all current values out of the {@link MetricsEndpoint} + * . No timestamp information is available, so there is no way to check if the values are + * recent, and they all come out with the default (current time). + * + * @author Dave Syer + * + */ +public class MetricsEndpointMetricReader implements MetricReader { + + private final MetricsEndpoint endpoint; + + public MetricsEndpointMetricReader(MetricsEndpoint endpoint) { + this.endpoint = endpoint; + } + + @Override + public Metric findOne(String metricName) { + Metric metric = null; + Object value = endpoint.invoke().get(metricName); + if (value != null) { + metric = new Metric(metricName, (Number) value); + } + return metric; + } + + @Override + public Iterable> findAll() { + List> metrics = new ArrayList>(); + Map values = endpoint.invoke(); + Date timestamp = new Date(); + for (Entry entry : values.entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + metrics.add(new Metric(name, (Number) value, timestamp)); + } + return metrics; + } + + @Override + public long count() { + return endpoint.invoke().size(); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java new file mode 100644 index 0000000000..6402ceb575 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-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.boot.actuate.autoconfigure; + +import static org.junit.Assert.assertNotNull; + +import org.junit.After; +import org.junit.Test; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.springframework.boot.actuate.endpoint.MetricsEndpointMetricReader; +import org.springframework.boot.actuate.metrics.GaugeService; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.export.MetricCopyExporter; +import org.springframework.boot.actuate.metrics.export.MetricExporters; +import org.springframework.boot.actuate.metrics.writer.MetricWriter; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.channel.FixedSubscriberChannel; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.SubscribableChannel; + +/** + * Tests for {@link MetricRepositoryAutoConfiguration}. + * + * @author Phillip Webb + * @author Dave Syer + */ +public class MetricExportAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void after() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void defaultExporterWhenMessageChannelAvailable() throws Exception { + this.context = new AnnotationConfigApplicationContext( + MessageChannelConfiguration.class, + MetricRepositoryAutoConfiguration.class, + MetricsChannelAutoConfiguration.class, + MetricExportAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + MetricExporters exporter = this.context.getBean(MetricExporters.class); + assertNotNull(exporter); + } + + @Test + public void provideAdditionalWriter() { + this.context = new AnnotationConfigApplicationContext(WriterConfig.class, + MetricRepositoryAutoConfiguration.class, + MetricExportAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + GaugeService gaugeService = this.context.getBean(GaugeService.class); + assertNotNull(gaugeService); + gaugeService.submit("foo", 2.7); + MetricExporters exporters = this.context.getBean(MetricExporters.class); + MetricCopyExporter exporter = (MetricCopyExporter) exporters.getExporters().get( + "writer"); + exporter.setIgnoreTimestamps(true); + exporter.export(); + MetricWriter writer = this.context.getBean("writer", MetricWriter.class); + Mockito.verify(writer, Mockito.atLeastOnce()).set(Matchers.any(Metric.class)); + } + + @Test + public void exportMetricsEndpoint() { + this.context = new AnnotationConfigApplicationContext(WriterConfig.class, + MetricEndpointConfiguration.class, + MetricExportAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + MetricExporters exporters = this.context.getBean(MetricExporters.class); + MetricCopyExporter exporter = (MetricCopyExporter) exporters.getExporters().get( + "writer"); + exporter.setIgnoreTimestamps(true); + exporter.export(); + MetricsEndpointMetricReader reader = this.context.getBean("endpointReader", MetricsEndpointMetricReader.class); + Mockito.verify(reader, Mockito.atLeastOnce()).findAll(); + } + + @Configuration + public static class MessageChannelConfiguration { + @Bean + public SubscribableChannel metricsChannel() { + return new FixedSubscriberChannel(new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + } + }); + } + } + + @Configuration + public static class WriterConfig { + + @Bean + public MetricWriter writer() { + return Mockito.mock(MetricWriter.class); + } + + } + + @Configuration + public static class MetricEndpointConfiguration { + + @Bean + public MetricsEndpointMetricReader endpointReader() { + return Mockito.mock(MetricsEndpointMetricReader.class); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java index 12a1d47aff..102a0d5943 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java @@ -16,41 +16,29 @@ package org.springframework.boot.actuate.autoconfigure; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + import org.junit.After; import org.junit.Test; -import org.mockito.Matchers; -import org.mockito.Mockito; import org.springframework.boot.actuate.metrics.CounterService; import org.springframework.boot.actuate.metrics.GaugeService; -import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.buffer.BufferCounterService; import org.springframework.boot.actuate.metrics.buffer.BufferGaugeService; import org.springframework.boot.actuate.metrics.dropwizard.DropwizardMetricServices; -import org.springframework.boot.actuate.metrics.export.MetricCopyExporter; -import org.springframework.boot.actuate.metrics.export.MetricExporters; import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.reader.PrefixMetricReader; -import org.springframework.boot.actuate.metrics.writer.MetricWriter; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.integration.channel.FixedSubscriberChannel; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.SubscribableChannel; import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; - /** * Tests for {@link MetricRepositoryAutoConfiguration}. * @@ -81,36 +69,6 @@ public class MetricRepositoryAutoConfigurationTests { .getValue()); } - @Test - public void defaultExporterWhenMessageChannelAvailable() throws Exception { - this.context = new AnnotationConfigApplicationContext( - MessageChannelConfiguration.class, - MetricRepositoryAutoConfiguration.class, - MetricsChannelAutoConfiguration.class, - MetricExportAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - MetricExporters exporter = this.context.getBean(MetricExporters.class); - assertNotNull(exporter); - } - - @Test - public void provideAdditionalWriter() { - this.context = new AnnotationConfigApplicationContext(WriterConfig.class, - MetricRepositoryAutoConfiguration.class, - MetricExportAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - GaugeService gaugeService = this.context.getBean(GaugeService.class); - assertNotNull(gaugeService); - gaugeService.submit("foo", 2.7); - MetricExporters exporters = this.context.getBean(MetricExporters.class); - MetricCopyExporter exporter = (MetricCopyExporter) exporters.getExporters().get( - "writer"); - exporter.setIgnoreTimestamps(true); - exporter.export(); - MetricWriter writer = this.context.getBean("writer", MetricWriter.class); - Mockito.verify(writer, Mockito.atLeastOnce()).set(Matchers.any(Metric.class)); - } - @Test public void dropwizardInstalledIfPresent() { this.context = new AnnotationConfigApplicationContext( @@ -138,28 +96,6 @@ public class MetricRepositoryAutoConfigurationTests { equalTo(0)); } - @Configuration - public static class MessageChannelConfiguration { - @Bean - public SubscribableChannel metricsChannel() { - return new FixedSubscriberChannel(new MessageHandler() { - @Override - public void handleMessage(Message message) throws MessagingException { - } - }); - } - } - - @Configuration - public static class WriterConfig { - - @Bean - public MetricWriter writer() { - return Mockito.mock(MetricWriter.class); - } - - } - @Configuration public static class Config {