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).