Commit 53a61474 authored by Dave Syer's avatar Dave Syer Committed by Andy Wilkinson

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).
parent 1b3efd41
......@@ -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>
......@@ -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,104 +84,105 @@ 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;
@Bean
@ConditionalOnMissingBean
public CounterService counterService() {
return new DefaultCounterService(this.writer);
}
@Bean
@ConditionalOnMissingBean
public GaugeService gaugeService() {
return new DefaultGaugeService(this.writer);
}
@Configuration
@ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN)
@ConditionalOnMissingBean(MetricRepository.class)
static class MetricRepositoryConfiguration {
static class LegacyMetricServicesConfiguration {
@Autowired
private MetricWriter writer;
@Bean
public InMemoryMetricRepository actuatorMetricRepository() {
return new InMemoryMetricRepository();
@ConditionalOnMissingBean
public CounterService counterService() {
return new DefaultCounterService(this.writer);
}
@Bean
@ConditionalOnMissingBean
public GaugeService gaugeService() {
return new DefaultGaugeService(this.writer);
}
}
@Configuration
@ConditionalOnClass(MessageChannel.class)
static class MetricsChannelConfiguration {
@Autowired
@Qualifier("metricsExecutor")
private Executor executor;
@ConditionalOnJava(value = JavaVersion.EIGHT)
@ConditionalOnMissingBean(MetricRepository.class)
static class FastMetricServicesConfiguration {
@Bean
@ConditionalOnMissingBean(name = "metricsChannel")
public SubscribableChannel metricsChannel() {
return new ExecutorSubscribableChannel(this.executor);
@ConditionalOnMissingBean
public CounterBuffers counterBuffers() {
return new CounterBuffers();
}
@Bean
@ConditionalOnMissingBean(name = "metricsExecutor")
public Executor metricsExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
return executor;
@ConditionalOnMissingBean
public GaugeBuffers gaugeBuffers() {
return new GaugeBuffers();
}
@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);
@ConditionalOnMissingBean
public BufferMetricReader metricReader(CounterBuffers counters,
GaugeBuffers gauges) {
return new BufferMetricReader(counters, gauges);
}
}
@Configuration
@ConditionalOnClass(MetricRegistry.class)
static class DropwizardMetricRegistryConfiguration {
@Bean
@ConditionalOnMissingBean
public MetricRegistry metricRegistry() {
return new MetricRegistry();
public CounterService counterService(CounterBuffers writer) {
return new BufferCounterService(writer);
}
@Bean
public DropwizardMetricWriter dropwizardMetricWriter(MetricRegistry metricRegistry) {
return new DropwizardMetricWriter(metricRegistry);
@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
@Primary
@ConditionalOnMissingClass(name = "org.springframework.messaging.MessageChannel")
@ConditionalOnMissingBean(name = "primaryMetricWriter")
public MetricWriter primaryMetricWriter(List<MetricWriter> writers) {
return new CompositeMetricWriter(writers);
public InMemoryMetricRepository actuatorMetricRepository() {
return new InMemoryMetricRepository();
}
}
@Configuration
@EnableScheduling
@ConditionalOnProperty(value = "spring.metrics.export.enabled", matchIfMissing = true)
static class DefaultMetricsExporterConfiguration {
@Autowired(required = false)
private List<MetricWriter> writers;
@Bean
public PublicMetrics dropwizardPublicMetrics(MetricRegistry metricRegistry) {
MetricRegistryMetricReader reader = new MetricRegistryMetricReader(
metricRegistry);
return new MetricReaderPublicMetrics(reader);
@ConditionalOnMissingBean
@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();
}
};
}
}
}
/*
* 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
/*
* 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
/*
* 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;
}
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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();
}
}
/*
* 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);
}
}
/*
* 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
/*
* 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);
}
}
/*
* 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
/*
* 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;
......@@ -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.
*
......
......@@ -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;
}
}
......
/*
* 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;
}
}
/*
* 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;
}
}
......@@ -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);
}
/**
......
......@@ -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
/*
* 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 {
}
/*
* 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);
}
}
......
/*
* 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();
}
}
......@@ -14,45 +14,45 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.writer;
package org.springframework.boot.actuate.metrics.buffer;
import org.springframework.boot.actuate.metrics.Metric;
import org.junit.Test;
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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* 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>
* Tests for {@link BufferMetricReader}.
*
* @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);
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());
}
}
/*
* 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"));
}
}
/*
* 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();
}
}
/*
* 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());
}
}
/*
* 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());
}
}
/*
* 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());
}
}
/*
* 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;
......
......@@ -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].
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.
[[production-ready-code-hale-metrics]]
=== Performance
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-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).
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment