diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index caa62a59fe..67849c35fa 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -17,6 +17,7 @@ ${basedir}/.. + 1.8 @@ -214,6 +215,11 @@ tomcat-embed-logging-juli test + + org.aspectj + aspectjrt + test + org.crashub crash.connectors.telnet @@ -234,5 +240,10 @@ spring-data-elasticsearch test + + org.springframework.integration + spring-integration-core + test + diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfiguration.java index b3e8a4ab81..f9674a61e3 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfiguration.java @@ -17,36 +17,37 @@ package org.springframework.boot.actuate.autoconfigure; import java.util.List; -import java.util.concurrent.Executor; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics; -import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.metrics.CounterService; import org.springframework.boot.actuate.metrics.GaugeService; +import org.springframework.boot.actuate.metrics.buffer.BufferCounterService; +import org.springframework.boot.actuate.metrics.buffer.BufferGaugeService; +import org.springframework.boot.actuate.metrics.buffer.BufferMetricReader; +import org.springframework.boot.actuate.metrics.buffer.CounterBuffers; +import org.springframework.boot.actuate.metrics.buffer.GaugeBuffers; import org.springframework.boot.actuate.metrics.export.Exporter; -import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader; +import org.springframework.boot.actuate.metrics.export.MetricCopyExporter; +import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository; import org.springframework.boot.actuate.metrics.repository.MetricRepository; import org.springframework.boot.actuate.metrics.writer.CompositeMetricWriter; import org.springframework.boot.actuate.metrics.writer.DefaultCounterService; import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService; -import org.springframework.boot.actuate.metrics.writer.DropwizardMetricWriter; -import org.springframework.boot.actuate.metrics.writer.MessageChannelMetricWriter; import org.springframework.boot.actuate.metrics.writer.MetricWriter; -import org.springframework.boot.actuate.metrics.writer.MetricWriterMessageHandler; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnJava; +import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion; +import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.Range; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +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.context.annotation.Primary; import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.support.ExecutorSubscribableChannel; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; import com.codahale.metrics.MetricRegistry; @@ -83,32 +84,77 @@ import com.codahale.metrics.MetricRegistry; * @see CounterService * @see MetricWriter * @see InMemoryMetricRepository - * @see DropwizardMetricWriter * @see Exporter * * @author Dave Syer */ @Configuration +@EnableConfigurationProperties(MetricsProperties.class) public class MetricRepositoryAutoConfiguration { - @Autowired - private MetricWriter writer; + @Configuration + @ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN) + @ConditionalOnMissingBean(MetricRepository.class) + static class LegacyMetricServicesConfiguration { - @Bean - @ConditionalOnMissingBean - public CounterService counterService() { - return new DefaultCounterService(this.writer); - } + @Autowired + private MetricWriter writer; + + @Bean + @ConditionalOnMissingBean + public CounterService counterService() { + return new DefaultCounterService(this.writer); + } + + @Bean + @ConditionalOnMissingBean + public GaugeService gaugeService() { + return new DefaultGaugeService(this.writer); + } - @Bean - @ConditionalOnMissingBean - public GaugeService gaugeService() { - return new DefaultGaugeService(this.writer); } @Configuration + @ConditionalOnJava(value = JavaVersion.EIGHT) @ConditionalOnMissingBean(MetricRepository.class) - static class MetricRepositoryConfiguration { + static class FastMetricServicesConfiguration { + + @Bean + @ConditionalOnMissingBean + public CounterBuffers counterBuffers() { + return new CounterBuffers(); + } + + @Bean + @ConditionalOnMissingBean + public GaugeBuffers gaugeBuffers() { + return new GaugeBuffers(); + } + + @Bean + @ConditionalOnMissingBean + public BufferMetricReader metricReader(CounterBuffers counters, + GaugeBuffers gauges) { + return new BufferMetricReader(counters, gauges); + } + + @Bean + @ConditionalOnMissingBean + public CounterService counterService(CounterBuffers writer) { + return new BufferCounterService(writer); + } + + @Bean + @ConditionalOnMissingBean + public GaugeService gaugeService(GaugeBuffers writer) { + return new BufferGaugeService(writer); + } + } + + @Configuration + @ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN) + @ConditionalOnMissingBean(MetricRepository.class) + static class LegacyMetricRepositoryConfiguration { @Bean public InMemoryMetricRepository actuatorMetricRepository() { @@ -118,69 +164,25 @@ public class MetricRepositoryAutoConfiguration { } @Configuration - @ConditionalOnClass(MessageChannel.class) - static class MetricsChannelConfiguration { + @EnableScheduling + @ConditionalOnProperty(value = "spring.metrics.export.enabled", matchIfMissing = true) + static class DefaultMetricsExporterConfiguration { - @Autowired - @Qualifier("metricsExecutor") - private Executor executor; - - @Bean - @ConditionalOnMissingBean(name = "metricsChannel") - public SubscribableChannel metricsChannel() { - return new ExecutorSubscribableChannel(this.executor); - } - - @Bean - @ConditionalOnMissingBean(name = "metricsExecutor") - public Executor metricsExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - return executor; - } - - @Bean - @Primary - @ConditionalOnMissingBean(name = "primaryMetricWriter") - public MetricWriter primaryMetricWriter( - @Qualifier("metricsChannel") SubscribableChannel channel, - List writers) { - final MetricWriter observer = new CompositeMetricWriter(writers); - channel.subscribe(new MetricWriterMessageHandler(observer)); - return new MessageChannelMetricWriter(channel); - } - - } - - @Configuration - @ConditionalOnClass(MetricRegistry.class) - static class DropwizardMetricRegistryConfiguration { + @Autowired(required = false) + private List writers; @Bean @ConditionalOnMissingBean - public MetricRegistry metricRegistry() { - return new MetricRegistry(); + @ConditionalOnBean(MetricWriter.class) + public MetricCopyExporter messageChannelMetricExporter(MetricReader reader) { + return new MetricCopyExporter(reader, new CompositeMetricWriter(this.writers)) { + @Scheduled(fixedDelayString = "${spring.metrics.export.delayMillis:5000}") + @Override + public void export() { + super.export(); + } + }; } - - @Bean - public DropwizardMetricWriter dropwizardMetricWriter(MetricRegistry metricRegistry) { - return new DropwizardMetricWriter(metricRegistry); - } - - @Bean - @Primary - @ConditionalOnMissingClass(name = "org.springframework.messaging.MessageChannel") - @ConditionalOnMissingBean(name = "primaryMetricWriter") - public MetricWriter primaryMetricWriter(List writers) { - return new CompositeMetricWriter(writers); - } - - @Bean - public PublicMetrics dropwizardPublicMetrics(MetricRegistry metricRegistry) { - MetricRegistryMetricReader reader = new MetricRegistryMetricReader( - metricRegistry); - return new MetricReaderPublicMetrics(reader); - } - } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsChannelAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsChannelAutoConfiguration.java new file mode 100644 index 0000000000..154451a4e7 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsChannelAutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * 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 org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.actuate.metrics.writer.MessageChannelMetricWriter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.MessageChannel; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for writing metrics to a + * {@link MessageChannel}. + * + * @author Dave Syer + */ +@Configuration +@ConditionalOnClass(MessageChannel.class) +@ConditionalOnBean(name = "metricsChannel") +@AutoConfigureBefore(MetricRepositoryAutoConfiguration.class) +public class MetricsChannelAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public MessageChannelMetricWriter messageChannelMetricWriter( + @Qualifier("metricsChannel") MessageChannel channel) { + return new MessageChannelMetricWriter(channel); + } + +} \ No newline at end of file diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfiguration.java new file mode 100644 index 0000000000..7ba198ce4d --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfiguration.java @@ -0,0 +1,63 @@ +/* + * 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 org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics; +import org.springframework.boot.actuate.endpoint.PublicMetrics; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.GaugeService; +import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader; +import org.springframework.boot.actuate.metrics.writer.DropwizardMetricServices; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.codahale.metrics.MetricRegistry; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Dropwizard-based metrics. + * + * @author Dave Syer + */ +@Configuration +@ConditionalOnClass(MetricRegistry.class) +@AutoConfigureBefore(MetricRepositoryAutoConfiguration.class) +public class MetricsDropwizardAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public MetricRegistry metricRegistry() { + return new MetricRegistry(); + } + + @Bean + @ConditionalOnMissingBean({ DropwizardMetricServices.class, CounterService.class, + GaugeService.class }) + public DropwizardMetricServices dropwizardMetricServices(MetricRegistry metricRegistry) { + return new DropwizardMetricServices(metricRegistry); + } + + @Bean + public PublicMetrics dropwizardPublicMetrics(MetricRegistry metricRegistry) { + MetricRegistryMetricReader reader = new MetricRegistryMetricReader(metricRegistry); + return new MetricReaderPublicMetrics(reader); + } + +} \ No newline at end of file diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsProperties.java new file mode 100644 index 0000000000..2070fc18ae --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsProperties.java @@ -0,0 +1,75 @@ +/* + * 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 org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Dave Syer + */ +@ConfigurationProperties("spring.metrics") +public class MetricsProperties { + + private Export export = new Export(); + + public Export getExport() { + return this.export; + } + + public static class Export { + /** + * Delay in milliseconds between export ticks. Metrics are exported to external + * sources on a schedule with this delay. + */ + private long delayMillis; + + /** + * Flag to enable metric export (assuming a MetricWriter is available). + */ + private boolean enabled = true; + + /** + * Flag to switch off an optimization based on not exporting unchanged metric + * values. + */ + private boolean ignoreTimestamps = false; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public long getDelayMillis() { + return this.delayMillis; + } + + public void setDelayMillis(long delayMillis) { + this.delayMillis = delayMillis; + } + + public boolean isIgnoreTimestamps() { + return this.ignoreTimestamps; + } + + public void setIgnoreTimestamps(boolean ignoreTimestamps) { + this.ignoreTimestamps = ignoreTimestamps; + } + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/BufferCounterService.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/BufferCounterService.java new file mode 100644 index 0000000000..34b4862b32 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/BufferCounterService.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2014 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.metrics.buffer; + +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.lang.UsesJava8; + +/** + * Fast implementation of {@link CounterService} using {@link CounterBuffers}. + * + * @author Dave Syer + */ +@UsesJava8 +public class BufferCounterService implements CounterService { + + private final ConcurrentHashMap names = new ConcurrentHashMap(); + + private final CounterBuffers writer; + + /** + * Create a {@link BufferCounterService} instance. + * @param writer the underlying writer used to manage metrics + */ + public BufferCounterService(CounterBuffers writer) { + this.writer = writer; + } + + @Override + public void increment(String metricName) { + this.writer.increment(wrap(metricName), 1L); + } + + @Override + public void decrement(String metricName) { + this.writer.increment(wrap(metricName), -1L); + } + + @Override + public void reset(String metricName) { + this.writer.reset(wrap(metricName)); + } + + private String wrap(String metricName) { + if (this.names.containsKey(metricName)) { + return this.names.get(metricName); + } + if (metricName.startsWith("counter") || metricName.startsWith("meter")) { + return metricName; + } + String name = "counter." + metricName; + this.names.put(metricName, name); + return name; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/BufferGaugeService.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/BufferGaugeService.java new file mode 100644 index 0000000000..3ef6d697ac --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/BufferGaugeService.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2014 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.metrics.buffer; + +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.boot.actuate.metrics.GaugeService; +import org.springframework.lang.UsesJava8; + +/** + * Fast implementation of {@link GaugeService} using {@link GaugeBuffers}. + * + * @author Dave Syer + */ +@UsesJava8 +public class BufferGaugeService implements GaugeService { + + private final ConcurrentHashMap names = new ConcurrentHashMap(); + + private final GaugeBuffers writer; + + /** + * Create a {@link BufferGaugeService} instance. + * @param writer the underlying writer used to manage metrics + */ + public BufferGaugeService(GaugeBuffers writer) { + this.writer = writer; + } + + @Override + public void submit(String metricName, double value) { + this.writer.set(wrap(metricName), value); + } + + private String wrap(String metricName) { + if (this.names.containsKey(metricName)) { + return this.names.get(metricName); + } + if (metricName.startsWith("gauge") || metricName.startsWith("histogram") + || metricName.startsWith("timer")) { + return metricName; + } + String name = "gauge." + metricName; + this.names.put(metricName, name); + return name; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/BufferMetricReader.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/BufferMetricReader.java new file mode 100644 index 0000000000..b3856bd15e --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/BufferMetricReader.java @@ -0,0 +1,113 @@ +/* + * 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.metrics.buffer; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.reader.MetricReader; +import org.springframework.boot.actuate.metrics.reader.PrefixMetricReader; +import org.springframework.lang.UsesJava8; + +/** + * {@link MetricReader} implementation using {@link CounterBuffers} and + * {@link GaugeBuffers}. + * + * @author Dave Syer + */ +@UsesJava8 +public class BufferMetricReader implements MetricReader, PrefixMetricReader { + + private final CounterBuffers counters; + + private final GaugeBuffers gauges; + + private final Predicate all = Pattern.compile(".*").asPredicate(); + + public BufferMetricReader(CounterBuffers counters, GaugeBuffers gauges) { + this.counters = counters; + this.gauges = gauges; + } + + @Override + public Iterable> findAll(String prefix) { + final List> metrics = new ArrayList>(); + this.counters.forEach(Pattern.compile(prefix + ".*").asPredicate(), + new BiConsumer() { + @Override + public void accept(String name, LongBuffer value) { + metrics.add(new Metric(name, value.getValue(), new Date( + value.getTimestamp()))); + } + }); + this.gauges.forEach(Pattern.compile(prefix + ".*").asPredicate(), + new BiConsumer() { + @Override + public void accept(String name, DoubleBuffer value) { + metrics.add(new Metric(name, value.getValue(), new Date( + value.getTimestamp()))); + } + }); + return metrics; + } + + @Override + public Metric findOne(final String name) { + LongBuffer buffer = this.counters.find(name); + if (buffer != null) { + return new Metric(name, buffer.getValue(), new Date( + buffer.getTimestamp())); + } + DoubleBuffer doubleValue = this.gauges.find(name); + if (doubleValue != null) { + return new Metric(name, doubleValue.getValue(), new Date( + doubleValue.getTimestamp())); + } + return null; + } + + @Override + public Iterable> findAll() { + final List> metrics = new ArrayList>(); + this.counters.forEach(this.all, new BiConsumer() { + @Override + public void accept(String name, LongBuffer value) { + metrics.add(new Metric(name, value.getValue(), new Date(value + .getTimestamp()))); + } + }); + this.gauges.forEach(this.all, new BiConsumer() { + @Override + public void accept(String name, DoubleBuffer value) { + metrics.add(new Metric(name, value.getValue(), new Date(value + .getTimestamp()))); + } + }); + return metrics; + } + + @Override + public long count() { + return this.counters.count() + this.gauges.count(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/CounterBuffers.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/CounterBuffers.java new file mode 100644 index 0000000000..96878be4c3 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/CounterBuffers.java @@ -0,0 +1,116 @@ +/* + * 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.metrics.buffer; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.springframework.lang.UsesJava8; + +/** + * Fast writes to in-memory metrics store using {@link LongBuffer}. + * + * @author Dave Syer + */ +@UsesJava8 +public class CounterBuffers { + + private final ConcurrentHashMap metrics = new ConcurrentHashMap(); + + public void forEach(final Predicate predicate, + final BiConsumer consumer) { + this.metrics.forEach(new BiConsumer() { + @Override + public void accept(String name, LongBuffer value) { + if (predicate.test(name)) { + consumer.accept(name, value); + } + } + }); + } + + public LongBuffer find(final String name) { + return this.metrics.get(name); + } + + public void get(final String name, final Consumer consumer) { + read(name, new Consumer() { + @Override + public void accept(LongBuffer adder) { + consumer.accept(adder); + } + }); + } + + public void increment(final String name, final long delta) { + write(name, new Consumer() { + @Override + public void accept(LongBuffer adder) { + adder.add(delta); + } + }); + } + + public void reset(final String name) { + write(name, new Consumer() { + @Override + public void accept(LongBuffer adder) { + adder.reset(); + } + }); + } + + public int count() { + return this.metrics.size(); + } + + private void read(final String name, final Consumer consumer) { + acceptInternal(name, new Consumer() { + @Override + public void accept(LongBuffer adder) { + consumer.accept(adder); + } + }); + } + + private void write(final String name, final Consumer consumer) { + acceptInternal(name, new Consumer() { + @Override + public void accept(LongBuffer buffer) { + buffer.setTimestamp(System.currentTimeMillis()); + consumer.accept(buffer); + } + }); + } + + private void acceptInternal(final String name, final Consumer consumer) { + LongBuffer adder; + if (null == (adder = this.metrics.get(name))) { + adder = this.metrics.computeIfAbsent(name, + new Function() { + @Override + public LongBuffer apply(String name) { + return new LongBuffer(0L); + } + }); + } + consumer.accept(adder); + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/DoubleBuffer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/DoubleBuffer.java new file mode 100644 index 0000000000..ec2d139abf --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/DoubleBuffer.java @@ -0,0 +1,50 @@ +/* + * 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.metrics.buffer; + +/** + * Mutable buffer containing a double value and a timestamp. + * + * @author Dave Syer + */ +public class DoubleBuffer { + + private volatile double value; + + private volatile long timestamp; + + public DoubleBuffer(long timestamp) { + this.value = 0; + this.timestamp = timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public double getValue() { + return this.value; + } + + public void setValue(double value) { + this.value = value; + } + + public long getTimestamp() { + return this.timestamp; + } +} \ No newline at end of file diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/GaugeBuffers.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/GaugeBuffers.java new file mode 100644 index 0000000000..80cb065d91 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/GaugeBuffers.java @@ -0,0 +1,97 @@ +/* + * 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.metrics.buffer; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.springframework.lang.UsesJava8; + +/** + * Fast writes to in-memory metrics store using {@link DoubleBuffer}. + * + * @author Dave Syer + */ +@UsesJava8 +public class GaugeBuffers { + + private final ConcurrentHashMap metrics = new ConcurrentHashMap(); + + public void forEach(final Predicate predicate, + final BiConsumer consumer) { + this.metrics.forEach(new BiConsumer() { + @Override + public void accept(String name, DoubleBuffer value) { + if (predicate.test(name)) { + consumer.accept(name, value); + } + } + }); + } + + public DoubleBuffer find(final String name) { + return this.metrics.get(name); + } + + public void get(final String name, final Consumer consumer) { + acceptInternal(name, new Consumer() { + @Override + public void accept(DoubleBuffer value) { + consumer.accept(value); + } + }); + } + + public void set(final String name, final double value) { + write(name, value); + } + + public int count() { + return this.metrics.size(); + } + + private void write(final String name, final double value) { + acceptInternal(name, new Consumer() { + @Override + public void accept(DoubleBuffer buffer) { + buffer.setTimestamp(System.currentTimeMillis()); + buffer.setValue(value); + } + }); + } + + public void reset(String name) { + this.metrics.remove(name, this.metrics.get(name)); + } + + private void acceptInternal(final String name, final Consumer consumer) { + DoubleBuffer value; + if (null == (value = this.metrics.get(name))) { + value = this.metrics.computeIfAbsent(name, + new Function() { + @Override + public DoubleBuffer apply(String tag) { + return new DoubleBuffer(0L); + } + }); + } + consumer.accept(value); + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/LongBuffer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/LongBuffer.java new file mode 100644 index 0000000000..08c2e97ef0 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/LongBuffer.java @@ -0,0 +1,59 @@ +/* + * 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.metrics.buffer; + +import java.util.concurrent.atomic.LongAdder; + +import org.springframework.lang.UsesJava8; + +/** + * Mutable buffer containing a long adder (Java 8) and a timestamp. + * + * @author Dave Syer + */ +@UsesJava8 +public class LongBuffer { + + private final LongAdder adder; + + private volatile long timestamp; + + public LongBuffer(long timestamp) { + this.adder = new LongAdder(); + this.timestamp = timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public long getValue() { + return this.adder.sum(); + } + + public long getTimestamp() { + return this.timestamp; + } + + public void reset() { + this.adder.reset(); + } + + public void add(long delta) { + this.adder.add(delta); + } +} \ No newline at end of file diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/package-info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/package-info.java new file mode 100644 index 0000000000..4f57bc24d0 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/buffer/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Metrics buffering support. + */ +package org.springframework.boot.actuate.metrics.buffer; + diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/Exporter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/Exporter.java index cd4f7b9fa2..d0c92a8b49 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/Exporter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/Exporter.java @@ -20,7 +20,7 @@ package org.springframework.boot.actuate.metrics.export; * Generic interface for metric exports. As you scale up metric collection you will often * need to buffer metric data locally and export it periodically (e.g. for aggregation * across a cluster), so this is the marker interface for those operations. The trigger of - * an export operation might be periodic or even driven, but it remains outside the scope + * an export operation might be periodic or event driven, but it remains outside the scope * of this interface. You might for instance create an instance of an Exporter and trigger * it using a {@code @Scheduled} annotation in a Spring ApplicationContext. * diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/util/SimpleInMemoryRepository.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/util/SimpleInMemoryRepository.java index e256884f4e..117bbf0120 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/util/SimpleInMemoryRepository.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/util/SimpleInMemoryRepository.java @@ -48,13 +48,8 @@ public class SimpleInMemoryRepository { synchronized (lock) { T current = this.values.get(name); T value = callback.modify(current); - if (current != null) { - this.values.replace(name, current, value); - } - else { - this.values.putIfAbsent(name, value); - } - return this.values.get(name); + this.values.put(name, value); + return value; } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/CodahaleMetricWriter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/CodahaleMetricWriter.java deleted file mode 100644 index f93c29d4f3..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/CodahaleMetricWriter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.metrics.writer; - -import org.springframework.boot.actuate.metrics.Metric; - -import com.codahale.metrics.Counter; -import com.codahale.metrics.Gauge; -import com.codahale.metrics.Histogram; -import com.codahale.metrics.Meter; -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.Timer; - -/** - * A {@link MetricWriter} that send data to a Codahale {@link MetricRegistry} based on a - * naming convention: - * - *
    - *
  • Updates to {@link #increment(Delta)} with names in "meter.*" are treated as - * {@link Meter} events
  • - *
  • Other deltas are treated as simple {@link Counter} values
  • - *
  • Inputs to {@link #set(Metric)} with names in "histogram.*" are treated as - * {@link Histogram} updates
  • - *
  • Inputs to {@link #set(Metric)} with names in "timer.*" are treated as {@link Timer} - * updates
  • - *
  • Other metrics are treated as simple {@link Gauge} values (single valued - * measurements of type double)
  • - *
- * - * @author Dave Syer - * @deprecated since 1.2.2 in favor of {@link DropwizardMetricWriter} - */ -@Deprecated -public class CodahaleMetricWriter extends DropwizardMetricWriter { - - /** - * Create a new {@link DropwizardMetricWriter} instance. - * @param registry the underlying metric registry - */ - public CodahaleMetricWriter(MetricRegistry registry) { - super(registry); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterService.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterService.java index f0ad86fd4b..28fe6ac479 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterService.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * 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. @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.metrics.writer; +import java.util.concurrent.ConcurrentHashMap; + import org.springframework.boot.actuate.metrics.CounterService; /** @@ -27,6 +29,8 @@ public class DefaultCounterService implements CounterService { private final MetricWriter writer; + private final ConcurrentHashMap names = new ConcurrentHashMap(); + /** * Create a {@link DefaultCounterService} instance. * @param writer the underlying writer used to manage metrics @@ -51,10 +55,14 @@ public class DefaultCounterService implements CounterService { } private String wrap(String metricName) { + if (this.names.containsKey(metricName)) { + return this.names.get(metricName); + } if (metricName.startsWith("counter") || metricName.startsWith("meter")) { return metricName; } - return "counter." + metricName; + String name = "counter." + metricName; + this.names.put(metricName, name); + return name; } - } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeService.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeService.java index 8ab448dc91..f11b6d0f23 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeService.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * 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. @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.metrics.writer; +import java.util.concurrent.ConcurrentHashMap; + import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.boot.actuate.metrics.Metric; @@ -28,6 +30,8 @@ public class DefaultGaugeService implements GaugeService { private final MetricWriter writer; + private final ConcurrentHashMap names = new ConcurrentHashMap(); + /** * Create a {@link DefaultGaugeService} instance. * @param writer the underlying writer used to manage metrics @@ -42,11 +46,15 @@ public class DefaultGaugeService implements GaugeService { } private String wrap(String metricName) { + if (this.names.containsKey(metricName)) { + return this.names.get(metricName); + } if (metricName.startsWith("gauge") || metricName.startsWith("histogram") || metricName.startsWith("timer")) { return metricName; } - return "gauge." + metricName; + String name = "gauge." + metricName; + this.names.put(metricName, name); + return name; } - } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricWriter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricServices.java similarity index 56% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricWriter.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricServices.java index e3045976ac..0116268c4e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricWriter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricServices.java @@ -20,7 +20,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; -import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.GaugeService; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; @@ -30,77 +31,107 @@ import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; /** - * A {@link MetricWriter} that send data to a Codahale {@link MetricRegistry} based on a - * naming convention: + * A {@link GaugeService} and {@link CounterService} that sends data to a Dropwizard + * {@link MetricRegistry} based on a naming convention: * *
    - *
  • Updates to {@link #increment(Delta)} with names in "meter.*" are treated as + *
  • Updates to {@link #increment(String)} with names in "meter.*" are treated as * {@link Meter} events
  • *
  • Other deltas are treated as simple {@link Counter} values
  • - *
  • Inputs to {@link #set(Metric)} with names in "histogram.*" are treated as - * {@link Histogram} updates
  • - *
  • Inputs to {@link #set(Metric)} with names in "timer.*" are treated as {@link Timer} - * updates
  • + *
  • Inputs to {@link #submit(String, double)} with names in "histogram.*" are treated + * as {@link Histogram} updates
  • + *
  • Inputs to {@link #submit(String, double)} with names in "timer.*" are treated as + * {@link Timer} updates
  • *
  • Other metrics are treated as simple {@link Gauge} values (single valued * measurements of type double)
  • *
* * @author Dave Syer */ -public class DropwizardMetricWriter implements MetricWriter { +public class DropwizardMetricServices implements CounterService, GaugeService { private final MetricRegistry registry; private final ConcurrentMap gaugeLocks = new ConcurrentHashMap(); + private final ConcurrentHashMap names = new ConcurrentHashMap(); + /** - * Create a new {@link DropwizardMetricWriter} instance. + * Create a new {@link DropwizardMetricServices} instance. * @param registry the underlying metric registry */ - public DropwizardMetricWriter(MetricRegistry registry) { + public DropwizardMetricServices(MetricRegistry registry) { this.registry = registry; } @Override - public void increment(Delta delta) { - String name = delta.getName(); - long value = delta.getValue().longValue(); + public void increment(String name) { + incrementInternal(name, 1L); + } + + @Override + public void decrement(String name) { + incrementInternal(name, -1L); + } + + private void incrementInternal(String name, long value) { if (name.startsWith("meter")) { Meter meter = this.registry.meter(name); meter.mark(value); } else { + name = wrapCounterName(name); Counter counter = this.registry.counter(name); counter.inc(value); } } @Override - public void set(Metric value) { - String name = value.getName(); + public void submit(String name, double value) { if (name.startsWith("histogram")) { - long longValue = value.getValue().longValue(); + long longValue = (long) value; Histogram metric = this.registry.histogram(name); metric.update(longValue); } else if (name.startsWith("timer")) { - long longValue = value.getValue().longValue(); + long longValue = (long) value; Timer metric = this.registry.timer(name); metric.update(longValue, TimeUnit.MILLISECONDS); } else { - final double gauge = value.getValue().doubleValue(); + name = wrapGaugeName(name); + final double gauge = value; // Ensure we synchronize to avoid another thread pre-empting this thread after - // remove causing an error in CodaHale metrics - // NOTE: CodaHale provides no way to do this atomically - synchronized (getGuageLock(name)) { + // remove causing an error in Dropwizard metrics + // NOTE: Dropwizard provides no way to do this atomically + synchronized (getGaugeLock(name)) { this.registry.remove(name); this.registry.register(name, new SimpleGauge(gauge)); } } } - private Object getGuageLock(String name) { + private String wrapGaugeName(String metricName) { + return wrapName(metricName, "gauge."); + } + + private String wrapCounterName(String metricName) { + return wrapName(metricName, "counter."); + } + + private String wrapName(String metricName, String prefix) { + if (this.names.containsKey(metricName)) { + return this.names.get(metricName); + } + if (metricName.startsWith(prefix)) { + return metricName; + } + String name = prefix + metricName; + this.names.put(metricName, name); + return name; + } + + private Object getGaugeLock(String name) { Object lock = this.gaugeLocks.get(name); if (lock == null) { Object newLock = new Object(); @@ -111,8 +142,11 @@ public class DropwizardMetricWriter implements MetricWriter { } @Override - public void reset(String metricName) { - this.registry.remove(metricName); + public void reset(String name) { + if (!name.startsWith("meter")) { + name = wrapCounterName(name); + } + this.registry.remove(name); } /** diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories index 01f62773c1..98b52fd295 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -11,6 +11,8 @@ org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfigurati org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.MetricsDropwizardAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.MetricsChannelAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/AdhocTestSuite.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/AdhocTestSuite.java new file mode 100644 index 0000000000..ce93990c99 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/AdhocTestSuite.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2013 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; + +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; +import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfigurationTests; +import org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfigurationTests; + +/** + * A test suite for probing weird ordering problems in the tests. + * + * @author Dave Syer + */ +@RunWith(Suite.class) +@SuiteClasses({ PublicMetricsAutoConfigurationTests.class, + MetricRepositoryAutoConfigurationTests.class }) +@Ignore +public class AdhocTestSuite { + +} 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 78fbf1f4b8..29959948cf 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * 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. @@ -16,22 +16,30 @@ package org.springframework.boot.actuate.autoconfigure; -import java.util.concurrent.Executor; - +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.export.MetricCopyExporter; import org.springframework.boot.actuate.metrics.reader.MetricReader; -import org.springframework.boot.actuate.metrics.writer.DefaultCounterService; -import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService; +import org.springframework.boot.actuate.metrics.reader.PrefixMetricReader; +import org.springframework.boot.actuate.metrics.writer.DropwizardMetricServices; 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.core.task.SyncTaskExecutor; -import org.springframework.messaging.support.ExecutorSubscribableChannel; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +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; @@ -40,10 +48,7 @@ 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.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; /** * Tests for {@link MetricRepositoryAutoConfiguration}. @@ -53,76 +58,90 @@ import static org.mockito.Mockito.verify; */ public class MetricRepositoryAutoConfigurationTests { - @Test - public void defaultExecutor() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - MetricRepositoryAutoConfiguration.class); - ExecutorSubscribableChannel channel = context - .getBean(ExecutorSubscribableChannel.class); - ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) channel.getExecutor(); - context.close(); - assertTrue(executor.getThreadPoolExecutor().isShutdown()); + private AnnotationConfigApplicationContext context; + + @After + public void after() { + if (this.context != null) { + this.context.close(); + } } @Test public void createServices() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - SyncTaskExecutorConfiguration.class, + this.context = new AnnotationConfigApplicationContext( MetricRepositoryAutoConfiguration.class); - DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class); + GaugeService gaugeService = this.context.getBean(BufferGaugeService.class); assertNotNull(gaugeService); - assertNotNull(context.getBean(DefaultCounterService.class)); + assertNotNull(this.context.getBean(BufferCounterService.class)); + assertNotNull(this.context.getBean(PrefixMetricReader.class)); gaugeService.submit("foo", 2.7); - assertEquals(2.7, context.getBean(MetricReader.class).findOne("gauge.foo") + assertEquals(2.7, this.context.getBean(MetricReader.class).findOne("gauge.foo") .getValue()); - context.close(); + } + + @Test + public void defaultExporterWhenMessageChannelAvailable() throws Exception { + this.context = new AnnotationConfigApplicationContext( + MessageChannelConfiguration.class, MetricsChannelAutoConfiguration.class, + MetricRepositoryAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + MetricCopyExporter exporter = this.context.getBean(MetricCopyExporter.class); + assertNotNull(exporter); } @Test public void provideAdditionalWriter() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - SyncTaskExecutorConfiguration.class, WriterConfig.class, - MetricRepositoryAutoConfiguration.class); - DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class); + this.context = new AnnotationConfigApplicationContext(WriterConfig.class, + MetricRepositoryAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + GaugeService gaugeService = this.context.getBean(GaugeService.class); assertNotNull(gaugeService); gaugeService.submit("foo", 2.7); - MetricWriter writer = context.getBean("writer", MetricWriter.class); - verify(writer).set(any(Metric.class)); - context.close(); + MetricCopyExporter exporter = this.context.getBean(MetricCopyExporter.class); + 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 codahaleInstalledIfPresent() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - SyncTaskExecutorConfiguration.class, WriterConfig.class, - MetricRepositoryAutoConfiguration.class); - DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class); + public void dropwizardInstalledIfPresent() { + this.context = new AnnotationConfigApplicationContext( + MetricsDropwizardAutoConfiguration.class, + MetricRepositoryAutoConfiguration.class, AopAutoConfiguration.class); + GaugeService gaugeService = this.context.getBean(GaugeService.class); assertNotNull(gaugeService); gaugeService.submit("foo", 2.7); - MetricRegistry registry = context.getBean(MetricRegistry.class); + DropwizardMetricServices exporter = this.context + .getBean(DropwizardMetricServices.class); + assertEquals(gaugeService, exporter); + MetricRegistry registry = this.context.getBean(MetricRegistry.class); @SuppressWarnings("unchecked") Gauge gauge = (Gauge) registry.getMetrics().get("gauge.foo"); assertEquals(new Double(2.7), gauge.getValue()); - context.close(); } @Test public void skipsIfBeansExist() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - Config.class, MetricRepositoryAutoConfiguration.class); - assertThat(context.getBeansOfType(DefaultGaugeService.class).size(), equalTo(0)); - assertThat(context.getBeansOfType(DefaultCounterService.class).size(), equalTo(0)); - context.close(); + this.context = new AnnotationConfigApplicationContext(Config.class, + MetricRepositoryAutoConfiguration.class); + assertThat(this.context.getBeansOfType(BufferGaugeService.class).size(), + equalTo(0)); + assertThat(this.context.getBeansOfType(BufferCounterService.class).size(), + equalTo(0)); } @Configuration - public static class SyncTaskExecutorConfiguration { - + public static class MessageChannelConfiguration { @Bean - public Executor metricsExecutor() { - return new SyncTaskExecutor(); + public SubscribableChannel metricsChannel() { + return new FixedSubscriberChannel(new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + } + }); } - } @Configuration @@ -130,7 +149,7 @@ public class MetricRepositoryAutoConfigurationTests { @Bean public MetricWriter writer() { - return mock(MetricWriter.class); + return Mockito.mock(MetricWriter.class); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/BufferGaugeServiceSpeedTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/BufferGaugeServiceSpeedTests.java new file mode 100644 index 0000000000..3af6ef06b5 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/BufferGaugeServiceSpeedTests.java @@ -0,0 +1,167 @@ +/* + * 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.metrics.buffer; + +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashSet; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.DoubleAdder; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; +import org.springframework.boot.actuate.metrics.GaugeService; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.lang.UsesJava8; +import org.springframework.util.StopWatch; + +import static org.junit.Assert.assertTrue; + +/** + * Speed tests for {@link BufferGaugeService}. + * + * @author Dave Syer + */ +@RunWith(Theories.class) +@UsesJava8 +public class BufferGaugeServiceSpeedTests { + + @DataPoints + public static String[] values = new String[10]; + + public static String[] names = new String[] { "foo", "bar", "spam", "bucket" }; + + public static String[] sample = new String[1000]; + + private GaugeBuffers gauges = new GaugeBuffers(); + + private GaugeService service = new BufferGaugeService(this.gauges); + + private BufferMetricReader reader = new BufferMetricReader(new CounterBuffers(), + this.gauges); + + private static int threadCount = 2; + + private static final int number = Boolean.getBoolean("performance.test") ? 10000000 + : 100000; + + private static StopWatch watch = new StopWatch("count"); + + private static int count; + + private static PrintWriter err; + + @BeforeClass + public static void prime() throws FileNotFoundException { + err = new PrintWriter("/dev/null"); + final Random random = new Random(); + for (int i = 0; i < 1000; i++) { + sample[i] = names[random.nextInt(names.length)]; + } + } + + @AfterClass + public static void washup() { + System.err.println(watch); + } + + @Theory + public void raw(String input) throws Exception { + iterate("writeRaw"); + double rate = number / watch.getLastTaskTimeMillis() * 1000; + System.err.println("Rate(" + count + ")=" + rate + ", " + watch); + watch.start("readRaw" + count); + for (String name : names) { + this.gauges.forEach(Pattern.compile(name).asPredicate(), + new BiConsumer() { + @Override + public void accept(String name, DoubleBuffer value) { + err.println(name + "=" + value); + } + }); + } + final DoubleAdder total = new DoubleAdder(); + this.gauges.forEach(Pattern.compile(".*").asPredicate(), + new BiConsumer() { + @Override + public void accept(String name, DoubleBuffer value) { + total.add(value.getValue()); + } + }); + watch.stop(); + System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms"); + assertTrue(number * threadCount < total.longValue()); + } + + @Theory + public void reader(String input) throws Exception { + iterate("writeReader"); + double rate = number / watch.getLastTaskTimeMillis() * 1000; + System.err.println("Rate(" + count + ")=" + rate + ", " + watch); + watch.start("readReader" + count); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric metric) { + err.println(metric); + } + }); + final LongAdder total = new LongAdder(); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric value) { + total.add(value.getValue().intValue()); + } + }); + watch.stop(); + System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms"); + assertTrue(0 < total.longValue()); + } + + private void iterate(String taskName) throws Exception { + watch.start(taskName + count++); + ExecutorService pool = Executors.newFixedThreadPool(threadCount); + Runnable task = new Runnable() { + @Override + public void run() { + for (int i = 0; i < number; i++) { + String name = sample[i % sample.length]; + BufferGaugeServiceSpeedTests.this.service.submit(name, count + i); + } + } + }; + Collection> futures = new HashSet>(); + for (int i = 0; i < threadCount; i++) { + futures.add(pool.submit(task)); + } + for (Future future : futures) { + future.get(); + } + watch.stop(); + } +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/BufferMetricReaderTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/BufferMetricReaderTests.java new file mode 100644 index 0000000000..be9be3cb5e --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/BufferMetricReaderTests.java @@ -0,0 +1,58 @@ +/* + * 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.metrics.buffer; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests for {@link BufferMetricReader}. + * + * @author Dave Syer + */ +public class BufferMetricReaderTests { + + private CounterBuffers counters = new CounterBuffers(); + + private GaugeBuffers gauges = new GaugeBuffers(); + + private BufferMetricReader reader = new BufferMetricReader(this.counters, this.gauges); + + @Test + public void countReflectsNumberOfMetrics() { + this.gauges.set("foo", 1); + this.counters.increment("bar", 2); + assertEquals(2, this.reader.count()); + } + + @Test + public void findGauge() { + this.gauges.set("foo", 1); + assertNotNull(this.reader.findOne("foo")); + assertEquals(1, this.reader.count()); + } + + @Test + public void findCounter() { + this.counters.increment("foo", 1); + assertNotNull(this.reader.findOne("foo")); + assertEquals(1, this.reader.count()); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/CounterBuffersTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/CounterBuffersTests.java new file mode 100644 index 0000000000..4a781ecde3 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/CounterBuffersTests.java @@ -0,0 +1,64 @@ +/* + * 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.metrics.buffer; + +import java.util.function.Consumer; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Tests for {@link CounterBuffers}. + * + * @author Dave Syer + */ +public class CounterBuffersTests { + + private CounterBuffers buffers = new CounterBuffers(); + + private long value; + + @Test + public void inAndOut() { + this.buffers.increment("foo", 2); + this.buffers.get("foo", new Consumer() { + @Override + public void accept(LongBuffer buffer) { + CounterBuffersTests.this.value = buffer.getValue(); + } + }); + assertEquals(2, this.value); + } + + @Test + public void getNonExistent() { + this.buffers.get("foo", new Consumer() { + @Override + public void accept(LongBuffer buffer) { + CounterBuffersTests.this.value = buffer.getValue(); + } + }); + assertEquals(0, this.value); + } + + @Test + public void findNonExistent() { + assertNull(this.buffers.find("foo")); + } +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/CounterServiceSpeedTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/CounterServiceSpeedTests.java new file mode 100644 index 0000000000..ecaae90f98 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/CounterServiceSpeedTests.java @@ -0,0 +1,165 @@ +/* + * 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.metrics.buffer; + +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashSet; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.lang.UsesJava8; +import org.springframework.util.StopWatch; + +import static org.junit.Assert.assertEquals; + +/** + * Speed tests for {@link CounterService}. + * @author Dave Syer + */ +@RunWith(Theories.class) +@UsesJava8 +public class CounterServiceSpeedTests { + + @DataPoints + public static String[] values = new String[10]; + + public static String[] names = new String[] { "foo", "bar", "spam", "bucket" }; + + public static String[] sample = new String[1000]; + + private CounterBuffers counters = new CounterBuffers(); + + private CounterService service = new BufferCounterService(this.counters); + + private BufferMetricReader reader = new BufferMetricReader(this.counters, + new GaugeBuffers()); + + private static int threadCount = 2; + + private static final int number = Boolean.getBoolean("performance.test") ? 10000000 + : 100000; + + private static StopWatch watch = new StopWatch("count"); + + private static int count; + + private static PrintWriter err; + + @BeforeClass + public static void prime() throws FileNotFoundException { + err = new PrintWriter("/dev/null"); + final Random random = new Random(); + for (int i = 0; i < 1000; i++) { + sample[i] = names[random.nextInt(names.length)]; + } + } + + @AfterClass + public static void washup() { + System.err.println(watch); + } + + @Theory + public void raw(String input) throws Exception { + iterate("writeRaw"); + double rate = number / watch.getLastTaskTimeMillis() * 1000; + System.err.println("Rate(" + count + ")=" + rate + ", " + watch); + watch.start("readRaw" + count); + for (String name : names) { + this.counters.forEach(Pattern.compile(name).asPredicate(), + new BiConsumer() { + @Override + public void accept(String name, LongBuffer value) { + err.println(name + "=" + value); + } + }); + } + final LongAdder total = new LongAdder(); + this.counters.forEach(Pattern.compile(".*").asPredicate(), + new BiConsumer() { + @Override + public void accept(String name, LongBuffer value) { + total.add(value.getValue()); + } + }); + watch.stop(); + System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms"); + assertEquals(number * threadCount, total.longValue()); + } + + @Theory + public void reader(String input) throws Exception { + iterate("writeReader"); + double rate = number / watch.getLastTaskTimeMillis() * 1000; + System.err.println("Rate(" + count + ")=" + rate + ", " + watch); + watch.start("readReader" + count); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric metric) { + err.println(metric); + } + }); + final LongAdder total = new LongAdder(); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric value) { + total.add(value.getValue().intValue()); + } + }); + watch.stop(); + System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms"); + assertEquals(number * threadCount, total.longValue()); + } + + private void iterate(String taskName) throws Exception { + watch.start(taskName + count++); + ExecutorService pool = Executors.newFixedThreadPool(threadCount); + Runnable task = new Runnable() { + @Override + public void run() { + for (int i = 0; i < number; i++) { + String name = sample[i % sample.length]; + CounterServiceSpeedTests.this.service.increment(name); + } + } + }; + Collection> futures = new HashSet>(); + for (int i = 0; i < threadCount; i++) { + futures.add(pool.submit(task)); + } + for (Future future : futures) { + future.get(); + } + watch.stop(); + } +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/DefaultCounterServiceSpeedTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/DefaultCounterServiceSpeedTests.java new file mode 100644 index 0000000000..7bb1485f65 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/DefaultCounterServiceSpeedTests.java @@ -0,0 +1,129 @@ +/* + * 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.metrics.buffer; + +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashSet; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Consumer; + +import org.junit.BeforeClass; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.reader.MetricReader; +import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository; +import org.springframework.boot.actuate.metrics.writer.DefaultCounterService; +import org.springframework.lang.UsesJava8; +import org.springframework.util.StopWatch; + +import static org.junit.Assert.assertEquals; + +/** + * Speed tests for {@link DefaultCounterService}. + * + * @author Dave Syer + */ +@RunWith(Theories.class) +@UsesJava8 +public class DefaultCounterServiceSpeedTests { + + @DataPoints + public static String[] values = new String[10]; + + public static String[] names = new String[] { "foo", "bar", "spam", "bucket" }; + + public static String[] sample = new String[1000]; + + private InMemoryMetricRepository repository = new InMemoryMetricRepository(); + + private CounterService counterService = new DefaultCounterService(this.repository); + + private MetricReader reader = this.repository; + + private static int threadCount = 2; + + private static final int number = Boolean.getBoolean("performance.test") ? 2000000 + : 100000; + + private static int count; + + private static StopWatch watch = new StopWatch("count"); + + private static PrintWriter err; + + @BeforeClass + public static void prime() throws FileNotFoundException { + err = new PrintWriter("/dev/null"); + final Random random = new Random(); + for (int i = 0; i < 1000; i++) { + sample[i] = names[random.nextInt(names.length)]; + } + } + + @Theory + public void counters(String input) throws Exception { + watch.start("counters" + count++); + ExecutorService pool = Executors.newFixedThreadPool(threadCount); + Runnable task = new Runnable() { + @Override + public void run() { + for (int i = 0; i < number; i++) { + String name = sample[i % sample.length]; + DefaultCounterServiceSpeedTests.this.counterService.increment(name); + } + } + }; + Collection> futures = new HashSet>(); + for (int i = 0; i < threadCount; i++) { + futures.add(pool.submit(task)); + } + for (Future future : futures) { + future.get(); + } + watch.stop(); + double rate = number / watch.getLastTaskTimeMillis() * 1000; + System.err.println("Counters rate(" + count + ")=" + rate + ", " + watch); + watch.start("read" + count); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric metric) { + err.println(metric); + } + }); + final LongAdder total = new LongAdder(); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric value) { + total.add(value.getValue().intValue()); + } + }); + watch.stop(); + System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms"); + assertEquals(number * threadCount, total.longValue()); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/DefaultGaugeServiceSpeedTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/DefaultGaugeServiceSpeedTests.java new file mode 100644 index 0000000000..595e2832d1 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/DefaultGaugeServiceSpeedTests.java @@ -0,0 +1,129 @@ +/* + * 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.metrics.buffer; + +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashSet; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Consumer; + +import org.junit.BeforeClass; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; +import org.springframework.boot.actuate.metrics.GaugeService; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.reader.MetricReader; +import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository; +import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService; +import org.springframework.lang.UsesJava8; +import org.springframework.util.StopWatch; + +import static org.junit.Assert.assertTrue; + +/** + * Speed tests for {@link DefaultGaugeService}. + * + * @author Dave Syer + */ +@RunWith(Theories.class) +@UsesJava8 +public class DefaultGaugeServiceSpeedTests { + + @DataPoints + public static String[] values = new String[10]; + + public static String[] names = new String[] { "foo", "bar", "spam", "bucket" }; + + public static String[] sample = new String[1000]; + + private InMemoryMetricRepository repository = new InMemoryMetricRepository(); + + private GaugeService gaugeService = new DefaultGaugeService(this.repository); + + private MetricReader reader = this.repository; + + private static int threadCount = 2; + + private static final int number = Boolean.getBoolean("performance.test") ? 5000000 + : 100000; + + private static int count; + + private static StopWatch watch = new StopWatch("count"); + + private static PrintWriter err; + + @BeforeClass + public static void prime() throws FileNotFoundException { + err = new PrintWriter("/dev/null"); + final Random random = new Random(); + for (int i = 0; i < 1000; i++) { + sample[i] = names[random.nextInt(names.length)]; + } + } + + @Theory + public void gauges(String input) throws Exception { + watch.start("gauges" + count++); + ExecutorService pool = Executors.newFixedThreadPool(threadCount); + Runnable task = new Runnable() { + @Override + public void run() { + for (int i = 0; i < number; i++) { + String name = sample[i % sample.length]; + DefaultGaugeServiceSpeedTests.this.gaugeService.submit(name, count + + i); + } + } + }; + Collection> futures = new HashSet>(); + for (int i = 0; i < threadCount; i++) { + futures.add(pool.submit(task)); + } + for (Future future : futures) { + future.get(); + } + watch.stop(); + double rate = number / watch.getLastTaskTimeMillis() * 1000; + System.err.println("Gauges rate(" + count + ")=" + rate + ", " + watch); + watch.start("read" + count); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric metric) { + err.println(metric); + } + }); + final LongAdder total = new LongAdder(); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric value) { + total.add(value.getValue().intValue()); + } + }); + watch.stop(); + System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms"); + assertTrue(0 < total.longValue()); + } +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/DropwizardCounterServiceSpeedTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/DropwizardCounterServiceSpeedTests.java new file mode 100644 index 0000000000..9f2b697f43 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/buffer/DropwizardCounterServiceSpeedTests.java @@ -0,0 +1,133 @@ +/* + * 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.metrics.buffer; + +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashSet; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Consumer; + +import org.junit.BeforeClass; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.reader.MetricReader; +import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader; +import org.springframework.boot.actuate.metrics.writer.DropwizardMetricServices; +import org.springframework.lang.UsesJava8; +import org.springframework.util.StopWatch; + +import com.codahale.metrics.MetricRegistry; + +import static org.junit.Assert.assertEquals; + +/** + * Speeds tests for {@link DropwizardMetricServices DropwizardMetricServices'} + * {@link CounterService}. + * + * @author Dave Syer + */ +@RunWith(Theories.class) +@UsesJava8 +public class DropwizardCounterServiceSpeedTests { + + @DataPoints + public static String[] values = new String[10]; + + public static String[] names = new String[] { "foo", "bar", "spam", "bucket" }; + + public static String[] sample = new String[1000]; + + private MetricRegistry registry = new MetricRegistry(); + + private CounterService counterService = new DropwizardMetricServices(this.registry); + + private MetricReader reader = new MetricRegistryMetricReader(this.registry); + + private static int threadCount = 2; + + private static final int number = Boolean.getBoolean("performance.test") ? 10000000 + : 100000; + + private static int count; + + private static StopWatch watch = new StopWatch("count"); + + private static PrintWriter err; + + @BeforeClass + public static void prime() throws FileNotFoundException { + err = new PrintWriter("/dev/null"); + final Random random = new Random(); + for (int i = 0; i < 1000; i++) { + sample[i] = names[random.nextInt(names.length)]; + } + } + + @Theory + public void counters(String input) throws Exception { + watch.start("counters" + count++); + ExecutorService pool = Executors.newFixedThreadPool(threadCount); + Runnable task = new Runnable() { + @Override + public void run() { + for (int i = 0; i < number; i++) { + String name = sample[i % sample.length]; + DropwizardCounterServiceSpeedTests.this.counterService + .increment(name); + } + } + }; + Collection> futures = new HashSet>(); + for (int i = 0; i < threadCount; i++) { + futures.add(pool.submit(task)); + } + for (Future future : futures) { + future.get(); + } + watch.stop(); + double rate = number / watch.getLastTaskTimeMillis() * 1000; + System.err.println("Counters rate(" + count + ")=" + rate + ", " + watch); + watch.start("read" + count); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric metric) { + err.println(metric); + } + }); + final LongAdder total = new LongAdder(); + this.reader.findAll().forEach(new Consumer>() { + @Override + public void accept(Metric value) { + total.add(value.getValue().intValue()); + } + }); + watch.stop(); + System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms"); + assertEquals(number * threadCount, total.longValue()); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricWriterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricServicesTests.java similarity index 66% rename from spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricWriterTests.java rename to spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricServicesTests.java index 896fee979a..068ebffc85 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricWriterTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DropwizardMetricServicesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * 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. @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.List; import org.junit.Test; -import org.springframework.boot.actuate.metrics.Metric; import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; @@ -29,56 +28,60 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; /** - * Tests for {@link DropwizardMetricWriter}. - * + * Tests for {@link DropwizardMetricServices}. + * * @author Dave Syer */ -public class DropwizardMetricWriterTests { +public class DropwizardMetricServicesTests { private final MetricRegistry registry = new MetricRegistry(); - private final DropwizardMetricWriter writer = new DropwizardMetricWriter(this.registry); + private final DropwizardMetricServices writer = new DropwizardMetricServices( + this.registry); @Test public void incrementCounter() { - this.writer.increment(new Delta("foo", 2)); - this.writer.increment(new Delta("foo", 1)); - assertEquals(3, this.registry.counter("foo").getCount()); + this.writer.increment("foo"); + this.writer.increment("foo"); + this.writer.increment("foo"); + assertEquals(3, this.registry.counter("counter.foo").getCount()); } @Test public void updatePredefinedMeter() { - this.writer.increment(new Delta("meter.foo", 2)); - this.writer.increment(new Delta("meter.foo", 1)); + this.writer.increment("meter.foo"); + this.writer.increment("meter.foo"); + this.writer.increment("meter.foo"); assertEquals(3, this.registry.meter("meter.foo").getCount()); } @Test public void updatePredefinedCounter() { - this.writer.increment(new Delta("counter.foo", 2)); - this.writer.increment(new Delta("counter.foo", 1)); + this.writer.increment("counter.foo"); + this.writer.increment("counter.foo"); + this.writer.increment("counter.foo"); assertEquals(3, this.registry.counter("counter.foo").getCount()); } @Test public void setGauge() { - this.writer.set(new Metric("foo", 2.1)); - this.writer.set(new Metric("foo", 2.3)); + this.writer.submit("foo", 2.1); + this.writer.submit("foo", 2.3); @SuppressWarnings("unchecked") - Gauge gauge = (Gauge) this.registry.getMetrics().get("foo"); + Gauge gauge = (Gauge) this.registry.getMetrics().get("gauge.foo"); assertEquals(new Double(2.3), gauge.getValue()); } @Test public void setPredfinedTimer() { - this.writer.set(new Metric("timer.foo", 200)); - this.writer.set(new Metric("timer.foo", 300)); + this.writer.submit("timer.foo", 200); + this.writer.submit("timer.foo", 300); assertEquals(2, this.registry.timer("timer.foo").getCount()); } @Test public void setPredfinedHistogram() { - this.writer.set(new Metric("histogram.foo", 2.1)); - this.writer.set(new Metric("histogram.foo", 2.3)); + this.writer.submit("histogram.foo", 2.1); + this.writer.submit("histogram.foo", 2.3); assertEquals(2, this.registry.histogram("histogram.foo").getCount()); } @@ -112,9 +115,9 @@ public class DropwizardMetricWriterTests { public static class WriterThread extends Thread { private int index; private boolean failed; - private DropwizardMetricWriter writer; + private DropwizardMetricServices writer; - public WriterThread(ThreadGroup group, int index, DropwizardMetricWriter writer) { + public WriterThread(ThreadGroup group, int index, DropwizardMetricServices writer) { super(group, "Writer-" + index); this.index = index; @@ -129,17 +132,9 @@ public class DropwizardMetricWriterTests { public void run() { for (int i = 0; i < 10000; i++) { try { - Metric metric1 = new Metric("timer.test.service", - this.index); - this.writer.set(metric1); - - Metric metric2 = new Metric( - "histogram.test.service", this.index); - this.writer.set(metric2); - - Metric metric3 = new Metric("gauge.test.service", - this.index); - this.writer.set(metric3); + this.writer.submit("timer.test.service", this.index); + this.writer.submit("histogram.test.service", this.index); + this.writer.submit("gauge.test.service", this.index); } catch (IllegalArgumentException iae) { this.failed = true; diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index f655a5fb5e..6761384b0f 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -889,30 +889,77 @@ beans are gathered by the endpoint. You can easily change that by defining your `MetricsEndpoint`. - [[production-ready-metric-repositories]] -=== Metric repositories -Metric service implementations are usually bound to a -{sc-spring-boot-actuator}/metrics/repository/MetricRepository.{sc-ext}[`MetricRepository`]. -A `MetricRepository` is responsible for storing and retrieving metric information. Spring -Boot provides an `InMemoryMetricRepository` and a `RedisMetricRepository` out of the -box (the in-memory repository is the default) but you can also write your own. The -`MetricRepository` interface is actually composed of higher level `MetricReader` and -`MetricWriter` interfaces. For full details refer to the -{dc-spring-boot-actuator}/metrics/repository/MetricRepository.{dc-ext}[Javadoc]. +=== Performance -There's nothing to stop you hooking a `MetricRepository` with back-end storage directly -into your app, but we recommend using the default `InMemoryMetricRepository` -(possibly with a custom `Map` instance if you are worried about heap usage) and -populating a back-end repository through a scheduled export job. In that way you get -some buffering in memory of the metric values and you can reduce the network -chatter by exporting less frequently or in batches. Spring Boot provides -an `Exporter` interface and a few basic implementations for you to get started with that. +The default implementation of `GaugeService` and `CounterService` provided by Spring Boot +depends on the version of Java that you are using. With Java 8 (or better) the +implementation switches to a high-performance version optimized for fast writes, backed by +atomic in-memory buffers, rather than by the immutable but relatively expensive +`Metric` type (counters are approximately 5 times faster and gauges approximately twice +as fast as the repository-based implementations). The Dropwizard metrics services (see +below) are also very efficient even for Java 7 (they have backports of some of the Java 8 +concurrency libraries), but they do not record timestamps for metric values. If +performance of metric gathering is a concern then it is always advisable to use one of the +high-performance options, and also to only read metrics infrequently, so that the writes +are buffered locally and only read when needed. + +NOTE: The old `MetricRepository` and its `InMemoryMetricRepository` implementation are not +used by default if you are on Java 8 or if you are using Dropwizard metrics. -[[production-ready-code-hale-metrics]] + +[[production-ready-metric-writers]] +=== Metric writers and aggregation + +Spring Boot provides a couple of implementations of a marker interface called `Exporter` +which can be used to copy metric readings from the in-memory buffers to a place where they +can be analysed and displayed. Indeed, if you provide a `@Bean` that implements the +`MetricWriter` interface, then it will automatically be hooked up to an `Exporter` and fed +metric updates every 5 seconds (configured via `spring.metrics.export.delayMillis`) via a +`@Scheduled` annotation in `MetricRepositoryAutoConfiguration`. + +The default exporter is a `MetricCopyExporter` which tries to optimize itself by not +copying values that haven't changed since it was last called. The optimization can be +switched off using a flag (`spring.metrics.export.ignoreTimestamps`). Note also that the +`MetricRegistry` has no support for timestamps, so the optimization is not available if +you are using Dropwizard metrics (all metrics will be copied on every tick). + + + +[[production-ready-metric-writers-export-to-redis]] +==== Example: Export to Redis + +If you provide a `@Bean` of type `RedisMetricRepository` the metrics are exported to a +Redis cache for aggregation. The `RedisMetricRepository` has 2 important parameters to +configure it for this purpose: `prefix` and `key` (passed into its constructor). It is +best to use a prefix that is unique to the application instance (e.g. using a random value +and maybe the logical name of the application to make it possible to correlate with other +instances of the same application). The "key" is used to keep a global index of all +metric names, so it should be unique "globally", whatever that means for your system (e.g. +2 instances of the same system could share a Redis cache if they have distinct keys). +Example: + +[source,java,indent=0] +---- + +@Value("${spring.application.name:application}.${random.value:0000}") +private String prefix = "metrics; + +@Value("${metrics.key:METRICSKEY}") +private String key = "KEY; + +@Bean +MetricWriter metricWriter() { + return new RedisMetricRepository(connectionFactory, prefix, key); +} +---- + + + +[[production-ready-dropwizard-metrics]] === Dropwizard Metrics -User of the https://dropwizard.github.io/metrics/[Dropwizard '`Metrics`' library] will +Users of the https://dropwizard.github.io/metrics/[Dropwizard '`Metrics`' library] will automatically find that Spring Boot metrics are published to `com.codahale.metrics.MetricRegistry`. A default `com.codahale.metrics.MetricRegistry` Spring bean will be created when you declare a dependency to the @@ -920,17 +967,21 @@ Spring bean will be created when you declare a dependency to the instance if you need customizations. Metrics from the `MetricRegistry` are also automatically exposed via the `/metrics` endpoint. -Users can create Dropwizard metrics by prefixing their metric names with the appropriate -type (e.g. `+histogram.*+`, `+meter.*+`). +When Dropwizard metrics are in use, the default `CounterService` and `GaugeService` are +replaced with a `DropwizardMetricServices`, which is a wrapper around the `MetricRegistry` +(so you can `@Autowired` one of those services and use it as normal). You can also create +"special" Dropwizard metrics by prefixing your metric names with the appropriate type +(i.e. `+timer.*+`, `+histogram.*+` for gauges, and `+meter.*+` for counters). [[production-ready-metrics-message-channel-integration]] === Message channel integration -If the '`Spring Messaging`' jar is on your classpath a `MessageChannel` called -`metricsChannel` is automatically created (unless one already exists). All metric update -events are additionally published as '`messages`' on that channel. Additional analysis or -actions can be taken by clients subscribing to that channel. +If a `MessageChannel` bean called `metricsChannel` exists, then a `MetricWriter` will be +created that writes metrics to that channel. The writer is automatically hooked up to an +exporter (as for all writers), so all metric values will appear on the channel, and +additional analysis or actions can be taken by subscribers (it's up to you to provide the +channel and any subscribers you need).