Add counter and gauge services based on in-memory buffers

This seems pretty efficient (approx 12M write/s as opposed to 2M with
the DefaultCounterService). N.B. there is no need to change most of
the rest of the metrics stuff because metrics are write-often, read-
seldom, so we don't need high performance reads as much.

The Spring Integration configuration and Dropwizard support has changed
a bit. Functionally very similar and probably opaque to users, but now
the messaging operates as an Exporter on a @Scheduled method, and
Dropwizard is a replacement [Gauge,Counter]Service.

Metrics are all
collected live in-memory (and can be very fast with Java 8), buffered
there and shipped out to a MessageChannel (if one exists with id
"metricsChannel") in a background thread.

We can still use Java 8 library APIs (like LongAdder) but to compile
to java 7 compatible byte code we have to forgo the use of lambdas :-(
and shorthand generics (<>).

Fixes gh-2682, fixes gh-2513 (for Java 8 and Dropwizard users).
This commit is contained in:
Dave Syer 2015-03-30 10:39:41 +01:00 committed by Andy Wilkinson
parent 1b3efd41f5
commit 53a61474aa
31 changed files with 2016 additions and 291 deletions

View File

@ -17,6 +17,7 @@
</organization>
<properties>
<main.basedir>${basedir}/..</main.basedir>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Compile -->
@ -214,6 +215,11 @@
<artifactId>tomcat-embed-logging-juli</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.crashub</groupId>
<artifactId>crash.connectors.telnet</artifactId>
@ -234,5 +240,10 @@
<artifactId>spring-data-elasticsearch</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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<MetricWriter> 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<MetricWriter> 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<MetricWriter> writers) {
return new CompositeMetricWriter(writers);
}
@Bean
public PublicMetrics dropwizardPublicMetrics(MetricRegistry metricRegistry) {
MetricRegistryMetricReader reader = new MetricRegistryMetricReader(
metricRegistry);
return new MetricReaderPublicMetrics(reader);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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<String, String> names = new ConcurrentHashMap<String, String>();
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;
}
}

View File

@ -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<String, String> names = new ConcurrentHashMap<String, String>();
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;
}
}

View File

@ -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<String> all = Pattern.compile(".*").asPredicate();
public BufferMetricReader(CounterBuffers counters, GaugeBuffers gauges) {
this.counters = counters;
this.gauges = gauges;
}
@Override
public Iterable<Metric<?>> findAll(String prefix) {
final List<Metric<?>> metrics = new ArrayList<Metric<?>>();
this.counters.forEach(Pattern.compile(prefix + ".*").asPredicate(),
new BiConsumer<String, LongBuffer>() {
@Override
public void accept(String name, LongBuffer value) {
metrics.add(new Metric<Long>(name, value.getValue(), new Date(
value.getTimestamp())));
}
});
this.gauges.forEach(Pattern.compile(prefix + ".*").asPredicate(),
new BiConsumer<String, DoubleBuffer>() {
@Override
public void accept(String name, DoubleBuffer value) {
metrics.add(new Metric<Double>(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<Long>(name, buffer.getValue(), new Date(
buffer.getTimestamp()));
}
DoubleBuffer doubleValue = this.gauges.find(name);
if (doubleValue != null) {
return new Metric<Double>(name, doubleValue.getValue(), new Date(
doubleValue.getTimestamp()));
}
return null;
}
@Override
public Iterable<Metric<?>> findAll() {
final List<Metric<?>> metrics = new ArrayList<Metric<?>>();
this.counters.forEach(this.all, new BiConsumer<String, LongBuffer>() {
@Override
public void accept(String name, LongBuffer value) {
metrics.add(new Metric<Long>(name, value.getValue(), new Date(value
.getTimestamp())));
}
});
this.gauges.forEach(this.all, new BiConsumer<String, DoubleBuffer>() {
@Override
public void accept(String name, DoubleBuffer value) {
metrics.add(new Metric<Double>(name, value.getValue(), new Date(value
.getTimestamp())));
}
});
return metrics;
}
@Override
public long count() {
return this.counters.count() + this.gauges.count();
}
}

View File

@ -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<String, LongBuffer> metrics = new ConcurrentHashMap<String, LongBuffer>();
public void forEach(final Predicate<String> predicate,
final BiConsumer<String, LongBuffer> consumer) {
this.metrics.forEach(new BiConsumer<String, LongBuffer>() {
@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<LongBuffer> consumer) {
read(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer adder) {
consumer.accept(adder);
}
});
}
public void increment(final String name, final long delta) {
write(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer adder) {
adder.add(delta);
}
});
}
public void reset(final String name) {
write(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer adder) {
adder.reset();
}
});
}
public int count() {
return this.metrics.size();
}
private void read(final String name, final Consumer<LongBuffer> consumer) {
acceptInternal(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer adder) {
consumer.accept(adder);
}
});
}
private void write(final String name, final Consumer<LongBuffer> consumer) {
acceptInternal(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer buffer) {
buffer.setTimestamp(System.currentTimeMillis());
consumer.accept(buffer);
}
});
}
private void acceptInternal(final String name, final Consumer<LongBuffer> consumer) {
LongBuffer adder;
if (null == (adder = this.metrics.get(name))) {
adder = this.metrics.computeIfAbsent(name,
new Function<String, LongBuffer>() {
@Override
public LongBuffer apply(String name) {
return new LongBuffer(0L);
}
});
}
consumer.accept(adder);
}
}

View File

@ -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;
}
}

View File

@ -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<String, DoubleBuffer> metrics = new ConcurrentHashMap<String, DoubleBuffer>();
public void forEach(final Predicate<String> predicate,
final BiConsumer<String, DoubleBuffer> consumer) {
this.metrics.forEach(new BiConsumer<String, DoubleBuffer>() {
@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<DoubleBuffer> consumer) {
acceptInternal(name, new Consumer<DoubleBuffer>() {
@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<DoubleBuffer>() {
@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<DoubleBuffer> consumer) {
DoubleBuffer value;
if (null == (value = this.metrics.get(name))) {
value = this.metrics.computeIfAbsent(name,
new Function<String, DoubleBuffer>() {
@Override
public DoubleBuffer apply(String tag) {
return new DoubleBuffer(0L);
}
});
}
consumer.accept(value);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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.
*

View File

@ -48,13 +48,8 @@ public class SimpleInMemoryRepository<T> {
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;
}
}

View File

@ -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:
*
* <ul>
* <li>Updates to {@link #increment(Delta)} with names in "meter.*" are treated as
* {@link Meter} events</li>
* <li>Other deltas are treated as simple {@link Counter} values</li>
* <li>Inputs to {@link #set(Metric)} with names in "histogram.*" are treated as
* {@link Histogram} updates</li>
* <li>Inputs to {@link #set(Metric)} with names in "timer.*" are treated as {@link Timer}
* updates</li>
* <li>Other metrics are treated as simple {@link Gauge} values (single valued
* measurements of type double)</li>
* </ul>
*
* @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);
}
}

View File

@ -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<String, String> names = new ConcurrentHashMap<String, String>();
/**
* 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;
}
}

View File

@ -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<String, String> names = new ConcurrentHashMap<String, String>();
/**
* 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;
}
}

View File

@ -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:
*
* <ul>
* <li>Updates to {@link #increment(Delta)} with names in "meter.*" are treated as
* <li>Updates to {@link #increment(String)} with names in "meter.*" are treated as
* {@link Meter} events</li>
* <li>Other deltas are treated as simple {@link Counter} values</li>
* <li>Inputs to {@link #set(Metric)} with names in "histogram.*" are treated as
* {@link Histogram} updates</li>
* <li>Inputs to {@link #set(Metric)} with names in "timer.*" are treated as {@link Timer}
* updates</li>
* <li>Inputs to {@link #submit(String, double)} with names in "histogram.*" are treated
* as {@link Histogram} updates</li>
* <li>Inputs to {@link #submit(String, double)} with names in "timer.*" are treated as
* {@link Timer} updates</li>
* <li>Other metrics are treated as simple {@link Gauge} values (single valued
* measurements of type double)</li>
* </ul>
*
* @author Dave Syer
*/
public class DropwizardMetricWriter implements MetricWriter {
public class DropwizardMetricServices implements CounterService, GaugeService {
private final MetricRegistry registry;
private final ConcurrentMap<String, Object> gaugeLocks = new ConcurrentHashMap<String, Object>();
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
/**
* 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);
}
/**

View File

@ -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

View File

@ -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 {
}

View File

@ -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<Double> gauge = (Gauge<Double>) 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);
}
}

View File

@ -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<String, DoubleBuffer>() {
@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<String, DoubleBuffer>() {
@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<Metric<?>>() {
@Override
public void accept(Metric<?> metric) {
err.println(metric);
}
});
final LongAdder total = new LongAdder();
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@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<Future<?>> futures = new HashSet<Future<?>>();
for (int i = 0; i < threadCount; i++) {
futures.add(pool.submit(task));
}
for (Future<?> future : futures) {
future.get();
}
watch.stop();
}
}

View File

@ -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());
}
}

View File

@ -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<LongBuffer>() {
@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<LongBuffer>() {
@Override
public void accept(LongBuffer buffer) {
CounterBuffersTests.this.value = buffer.getValue();
}
});
assertEquals(0, this.value);
}
@Test
public void findNonExistent() {
assertNull(this.buffers.find("foo"));
}
}

View File

@ -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<String, LongBuffer>() {
@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<String, LongBuffer>() {
@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<Metric<?>>() {
@Override
public void accept(Metric<?> metric) {
err.println(metric);
}
});
final LongAdder total = new LongAdder();
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@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<Future<?>> futures = new HashSet<Future<?>>();
for (int i = 0; i < threadCount; i++) {
futures.add(pool.submit(task));
}
for (Future<?> future : futures) {
future.get();
}
watch.stop();
}
}

View File

@ -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<Future<?>> futures = new HashSet<Future<?>>();
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<Metric<?>>() {
@Override
public void accept(Metric<?> metric) {
err.println(metric);
}
});
final LongAdder total = new LongAdder();
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@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());
}
}

View File

@ -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<Future<?>> futures = new HashSet<Future<?>>();
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<Metric<?>>() {
@Override
public void accept(Metric<?> metric) {
err.println(metric);
}
});
final LongAdder total = new LongAdder();
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@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());
}
}

View File

@ -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<Future<?>> futures = new HashSet<Future<?>>();
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<Metric<?>>() {
@Override
public void accept(Metric<?> metric) {
err.println(metric);
}
});
final LongAdder total = new LongAdder();
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@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());
}
}

View File

@ -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<Number>("foo", 2));
this.writer.increment(new Delta<Number>("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<Number>("meter.foo", 2));
this.writer.increment(new Delta<Number>("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<Number>("counter.foo", 2));
this.writer.increment(new Delta<Number>("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<Number>("foo", 2.1));
this.writer.set(new Metric<Number>("foo", 2.3));
this.writer.submit("foo", 2.1);
this.writer.submit("foo", 2.3);
@SuppressWarnings("unchecked")
Gauge<Double> gauge = (Gauge<Double>) this.registry.getMetrics().get("foo");
Gauge<Double> gauge = (Gauge<Double>) this.registry.getMetrics().get("gauge.foo");
assertEquals(new Double(2.3), gauge.getValue());
}
@Test
public void setPredfinedTimer() {
this.writer.set(new Metric<Number>("timer.foo", 200));
this.writer.set(new Metric<Number>("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<Number>("histogram.foo", 2.1));
this.writer.set(new Metric<Number>("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<Integer> metric1 = new Metric<Integer>("timer.test.service",
this.index);
this.writer.set(metric1);
Metric<Integer> metric2 = new Metric<Integer>(
"histogram.test.service", this.index);
this.writer.set(metric2);
Metric<Integer> metric3 = new Metric<Integer>("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;

View File

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