Merge branch 'feature/metrics'

This commit is contained in:
Andy Wilkinson 2015-05-13 13:56:10 +01:00
commit a012538955
88 changed files with 5068 additions and 337 deletions

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
@ -61,6 +62,11 @@
<artifactId>javax.mail</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.timgroup</groupId>
<artifactId>java-statsd-client</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
@ -214,6 +220,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 +245,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

@ -0,0 +1,42 @@
/*
* 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 java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Qualifier annotation for a metric repository that is used by the actuator (to
* distinguish it from others that might be installed by the user).
*
* @author Dave Syer
*/
@Qualifier
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ActuatorMetricRepository {
}

View File

@ -0,0 +1,84 @@
/*
* 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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.export.MetricExportProperties;
import org.springframework.boot.actuate.metrics.export.MetricExporters;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
/**
* @author Dave Syer
*/
@Configuration
@EnableScheduling
@ConditionalOnProperty(value = "spring.metrics.export.enabled", matchIfMissing = true)
public class MetricExportAutoConfiguration {
@Autowired(required = false)
private Map<String, MetricWriter> writers = Collections.emptyMap();
@Autowired
private MetricExportProperties metrics;
@Autowired(required = false)
@ActuatorMetricRepository
private MetricWriter actuatorMetricRepository;
@Autowired(required = false)
@ActuatorMetricRepository
private MetricReader reader;
@Bean
@ConditionalOnMissingBean
public SchedulingConfigurer metricWritersMetricExporter() {
Map<String, MetricWriter> writers = new HashMap<String, MetricWriter>();
if (this.reader != null) {
writers.putAll(this.writers);
if (this.actuatorMetricRepository != null
&& writers.containsValue(this.actuatorMetricRepository)) {
for (String name : this.writers.keySet()) {
if (writers.get(name).equals(this.actuatorMetricRepository)) {
writers.remove(name);
}
}
}
MetricExporters exporters = new MetricExporters(this.reader, writers,
this.metrics);
return exporters;
}
return new SchedulingConfigurer() {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
}
};
}
}

View File

@ -16,37 +16,31 @@
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.export.MetricExportProperties;
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.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.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 com.codahale.metrics.MetricRegistry;
@ -62,125 +56,106 @@ import com.codahale.metrics.MetricRegistry;
* periodic basis) using an {@link Exporter}, most implementations of which have
* optimizations for sending data to remote repositories.
* <p>
* If Spring Messaging is on the classpath a {@link MessageChannel} called
* "metricsChannel" is also created (unless one already exists) and all metric update
* events are published additionally as messages on that channel. Additional analysis or
* actions can be taken by clients subscribing to that channel.
* If Spring Messaging is on the classpath and a {@link MessageChannel} called
* "metricsChannel" is also available, all metric update events are published additionally
* as messages on that channel. Additional analysis or actions can be taken by clients
* subscribing to that channel.
* <p>
* In addition if Codahale's metrics library is on the classpath a {@link MetricRegistry}
* will be created and wired up to the counter and gauge services in addition to the basic
* repository. Users can create Codahale metrics by prefixing their metric names with the
* appropriate type (e.g. "histogram.*", "meter.*") and sending them to the standard
* <code>GaugeService</code> or <code>CounterService</code>.
* In addition if Dropwizard's metrics library is on the classpath a
* {@link MetricRegistry} will be created and the default counter and gauge services will
* switch to using it instead of the default repository. Users can create "special"
* Dropwizard metrics by prefixing their metric names with the appropriate type (e.g.
* "histogram.*", "meter.*". "timer.*") and sending them to the <code>GaugeService</code>
* or <code>CounterService</code>.
* <p>
* By default all metric updates go to all {@link MetricWriter} instances in the
* application context. To change this behaviour define your own metric writer bean called
* "primaryMetricWriter", mark it <code>@Primary</code>, and this one will receive all
* updates from the default counter and gauge services. Alternatively you can provide your
* own counter and gauge services and wire them to whichever writer you choose.
* application context via a {@link MetricCopyExporter} firing every 5 seconds (disable
* this by setting <code>spring.metrics.export.enabled=false</code>).
*
* @see GaugeService
* @see CounterService
* @see MetricWriter
* @see InMemoryMetricRepository
* @see DropwizardMetricWriter
* @see Exporter
*
* @author Dave Syer
*/
@Configuration
@EnableConfigurationProperties(MetricExportProperties.class)
public class MetricRepositoryAutoConfiguration {
@Autowired
private MetricWriter writer;
@Configuration
@ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN)
@ConditionalOnMissingBean(GaugeService.class)
static class LegacyMetricServicesConfiguration {
@Bean
@ConditionalOnMissingBean
public CounterService counterService() {
return new DefaultCounterService(this.writer);
}
@Autowired
@ActuatorMetricRepository
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
@ConditionalOnMissingBean(MetricRepository.class)
static class MetricRepositoryConfiguration {
@ConditionalOnJava(value = JavaVersion.EIGHT)
@ConditionalOnMissingBean(GaugeService.class)
static class FastMetricServicesConfiguration {
@Bean
@ConditionalOnMissingBean
public CounterBuffers counterBuffers() {
return new CounterBuffers();
}
@Bean
@ConditionalOnMissingBean
public GaugeBuffers gaugeBuffers() {
return new GaugeBuffers();
}
@Bean
@ActuatorMetricRepository
@ConditionalOnMissingBean
public BufferMetricReader actuatorMetricReader(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
@ActuatorMetricRepository
public InMemoryMetricRepository actuatorMetricRepository() {
return new InMemoryMetricRepository();
}
}
@Configuration
@ConditionalOnClass(MessageChannel.class)
static class MetricsChannelConfiguration {
@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 {
@Bean
@ConditionalOnMissingBean
public MetricRegistry metricRegistry() {
return new MetricRegistry();
}
@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.dropwizard.DropwizardMetricServices;
import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader;
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

@ -60,6 +60,7 @@ import org.springframework.context.annotation.Configuration;
public class PublicMetricsAutoConfiguration {
@Autowired(required = false)
@ActuatorMetricRepository
private MetricReader metricReader = new InMemoryMetricRepository();
@Bean

View File

@ -62,8 +62,13 @@ public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>();
for (PublicMetrics publicMetric : this.publicMetrics) {
for (Metric<?> metric : publicMetric.metrics()) {
result.put(metric.getName(), metric.getValue());
try {
for (Metric<?> metric : publicMetric.metrics()) {
result.put(metric.getName(), metric.getValue());
}
}
catch (Exception e) {
// Could not evaluate metrics
}
}
return result;

View File

@ -56,21 +56,21 @@ public class MetricsMvcEndpoint extends EndpointMvcAdapter {
/**
* {@link NamePatternFilter} for the Map source.
*/
private class NamePatternMapFilter extends NamePatternFilter<Map<String, Object>> {
private class NamePatternMapFilter extends NamePatternFilter<Map<String, ?>> {
public NamePatternMapFilter(Map<String, Object> source) {
public NamePatternMapFilter(Map<String, ?> source) {
super(source);
}
@Override
protected void getNames(Map<String, Object> source, NameCallback callback) {
protected void getNames(Map<String, ?> source, NameCallback callback) {
for (String name : source.keySet()) {
callback.addName(name);
}
}
@Override
protected Object getValue(Map<String, Object> source, String name) {
protected Object getValue(Map<String, ?> source, String name) {
Object value = source.get(name);
if (value == null) {
throw new NoSuchMetricException("No such metric: " + name);

View File

@ -0,0 +1,142 @@
/*
* Copyright 2014-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.aggregate;
import java.util.HashSet;
import java.util.Set;
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.util.StringUtils;
/**
* A metric reader that aggregates values from a source reader, normally one that has been
* collecting data from many sources in the same form (like a scaled-out application). The
* source has metrics with names in the form <code>*.*.counter.**</code> and
* <code>*.*.[anything].**</code> (the length of the prefix is controlled by the
* {@link #setTruncateKeyLength(int) truncateKeyLength} property, and defaults to 2,
* meaning 2 period separated fields), and the result has metric names in the form
* <code>aggregate.count.**</code> and <code>aggregate.[anything].**</code>. Counters are
* summed and anything else (i.e. gauges) are aggregated by choosing the most recent
* value.
*
* @author Dave Syer
*
*/
public class AggregateMetricReader implements MetricReader {
private MetricReader source;
private int truncate = 2;
private String prefix = "aggregate.";
public AggregateMetricReader(MetricReader source) {
this.source = source;
}
/**
* The number of period-separated keys to remove from the start of the input metric
* names before aggregating.
*
* @param truncate length of source metric prefixes
*/
public void setTruncateKeyLength(int truncate) {
this.truncate = truncate;
}
/**
* Prefix to apply to all output metrics. A period will be appended if no present in
* the provided value.
*
* @param prefix the prefix to use default "aggregator.")
*/
public void setPrefix(String prefix) {
this.prefix = prefix.endsWith(".") ? prefix : prefix + ".";
}
@Override
public Metric<?> findOne(String metricName) {
if (!metricName.startsWith(this.prefix)) {
return null;
}
InMemoryMetricRepository result = new InMemoryMetricRepository();
String baseName = metricName.substring(this.prefix.length());
for (Metric<?> metric : this.source.findAll()) {
String name = getSourceKey(metric.getName());
if (baseName.equals(name)) {
update(result, name, metric);
}
}
return result.findOne(metricName);
}
@Override
public Iterable<Metric<?>> findAll() {
InMemoryMetricRepository result = new InMemoryMetricRepository();
for (Metric<?> metric : this.source.findAll()) {
String key = getSourceKey(metric.getName());
if (key != null) {
update(result, key, metric);
}
}
return result.findAll();
}
@Override
public long count() {
Set<String> names = new HashSet<String>();
for (Metric<?> metric : this.source.findAll()) {
String name = getSourceKey(metric.getName());
if (name != null) {
names.add(name);
}
}
return names.size();
}
private void update(InMemoryMetricRepository result, String key, Metric<?> metric) {
String name = this.prefix + key;
Metric<?> aggregate = result.findOne(name);
if (aggregate == null) {
aggregate = new Metric<Number>(name, metric.getValue(), metric.getTimestamp());
}
else if (key.contains("counter.")) {
// accumulate all values
aggregate = new Metric<Number>(name, metric.increment(
aggregate.getValue().intValue()).getValue(), metric.getTimestamp());
}
else if (aggregate.getTimestamp().before(metric.getTimestamp())) {
// sort by timestamp and only take the latest
aggregate = new Metric<Number>(name, metric.getValue(), metric.getTimestamp());
}
result.set(aggregate);
}
private String getSourceKey(String name) {
String[] keys = StringUtils.delimitedListToStringArray(name, ".");
if (keys.length <= this.truncate) {
return null;
}
StringBuilder builder = new StringBuilder(keys[this.truncate]);
for (int i = this.truncate + 1; i < keys.length; i++) {
builder.append(".").append(keys[i]);
}
return builder.toString();
}
}

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

@ -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.
@ -14,13 +14,14 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.writer;
package org.springframework.boot.actuate.metrics.dropwizard;
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

@ -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 integration with Dropwizard Metrics.
*/
package org.springframework.boot.actuate.metrics.dropwizard;

View File

@ -22,6 +22,8 @@ import java.util.Collections;
import java.util.Date;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.util.StringUtils;
@ -34,6 +36,8 @@ import org.springframework.util.StringUtils;
*/
public abstract class AbstractMetricExporter implements Exporter {
private static final Log logger = LogFactory.getLog(AbstractMetricExporter.class);
private volatile AtomicBoolean processing = new AtomicBoolean(false);
private Date earliestTimestamp = new Date();
@ -42,6 +46,10 @@ public abstract class AbstractMetricExporter implements Exporter {
private final String prefix;
private Date latestTimestamp = new Date(0L);
private boolean sendLatest = true;
public AbstractMetricExporter(String prefix) {
this.prefix = !StringUtils.hasText(prefix) ? "" : (prefix.endsWith(".") ? prefix
: prefix + ".");
@ -63,13 +71,24 @@ public abstract class AbstractMetricExporter implements Exporter {
this.ignoreTimestamps = ignoreTimestamps;
}
/**
* Send only the data that changed since the last export.
*
* @param sendLatest the flag to set
*/
public void setSendLatest(boolean sendLatest) {
this.sendLatest = sendLatest;
}
@Override
public void export() {
if (!this.processing.compareAndSet(false, true)) {
// skip a tick
return;
}
long latestTimestamp = 0;
try {
latestTimestamp = System.currentTimeMillis();
for (String group : groups()) {
Collection<Metric<?>> values = new ArrayList<Metric<?>>();
for (Metric<?> metric : next(group)) {
@ -79,6 +98,10 @@ public abstract class AbstractMetricExporter implements Exporter {
if (!this.ignoreTimestamps && this.earliestTimestamp.after(timestamp)) {
continue;
}
if (!this.ignoreTimestamps && this.sendLatest
&& this.latestTimestamp.after(timestamp)) {
continue;
}
values.add(value);
}
if (!values.isEmpty()) {
@ -86,11 +109,26 @@ public abstract class AbstractMetricExporter implements Exporter {
}
}
}
catch (Exception e) {
logger.warn("Could not write to MetricWriter: " + e.getClass() + ": "
+ e.getMessage());
}
finally {
try {
this.latestTimestamp = new Date(latestTimestamp);
flush();
}
catch (Exception e) {
logger.warn("Could not flush MetricWriter: " + e.getClass() + ": "
+ e.getMessage());
}
this.processing.set(false);
}
}
public void flush() {
}
/**
* Generate a group of metrics to iterate over in the form of a set of Strings (e.g.
* prefixes). If the metrics to be exported partition into groups identified by a

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

@ -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.
@ -17,10 +17,13 @@
package org.springframework.boot.actuate.metrics.export;
import java.util.Collection;
import java.util.Iterator;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.boot.actuate.metrics.writer.WriterUtils;
import org.springframework.util.PatternMatchUtils;
/**
* {@link Exporter} that "exports" by copying metric data from a source
@ -34,10 +37,26 @@ public class MetricCopyExporter extends AbstractMetricExporter {
private final MetricWriter writer;
private String[] includes = new String[0];
private String[] excludes = new String[0];
public MetricCopyExporter(MetricReader reader, MetricWriter writer) {
this(reader, writer, "");
}
public void setIncludes(String... includes) {
if (includes != null) {
this.includes = includes;
}
}
public void setExcludes(String... excludes) {
if (excludes != null) {
this.excludes = excludes;
}
}
public MetricCopyExporter(MetricReader reader, MetricWriter writer, String prefix) {
super(prefix);
this.reader = reader;
@ -46,7 +65,17 @@ public class MetricCopyExporter extends AbstractMetricExporter {
@Override
protected Iterable<Metric<?>> next(String group) {
return this.reader.findAll();
if ((this.includes == null || this.includes.length == 0)
&& (this.excludes == null || this.excludes.length == 0)) {
return this.reader.findAll();
}
return new Iterable<Metric<?>>() {
@Override
public Iterator<Metric<?>> iterator() {
return new PatternMatchingIterator(MetricCopyExporter.this.reader
.findAll().iterator());
}
};
}
@Override
@ -56,4 +85,64 @@ public class MetricCopyExporter extends AbstractMetricExporter {
}
}
@Override
public void flush() {
WriterUtils.flush(this.writer);
}
private class PatternMatchingIterator implements Iterator<Metric<?>> {
private Metric<?> buffer = null;
private Iterator<Metric<?>> iterator;
public PatternMatchingIterator(Iterator<Metric<?>> iterator) {
this.iterator = iterator;
}
@Override
public boolean hasNext() {
if (this.buffer != null) {
return true;
}
this.buffer = findNext();
return this.buffer != null;
}
private Metric<?> findNext() {
Metric<?> metric = null;
boolean matched = false;
while (this.iterator.hasNext() && !matched) {
metric = this.iterator.next();
if (MetricCopyExporter.this.includes == null
|| MetricCopyExporter.this.includes.length == 0) {
matched = true;
}
else {
for (String pattern : MetricCopyExporter.this.includes) {
if (PatternMatchUtils.simpleMatch(pattern, metric.getName())) {
matched = true;
break;
}
}
}
if (MetricCopyExporter.this.excludes != null) {
for (String pattern : MetricCopyExporter.this.excludes) {
if (PatternMatchUtils.simpleMatch(pattern, metric.getName())) {
matched = false;
break;
}
}
}
}
return matched ? metric : null;
}
@Override
public Metric<?> next() {
Metric<?> metric = this.buffer;
this.buffer = null;
return metric;
}
};
}

View File

@ -0,0 +1,215 @@
/*
* 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.export;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.PostConstruct;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.PatternMatchUtils;
/**
* @author Dave Syer
*/
@ConfigurationProperties("spring.metrics.export")
public class MetricExportProperties {
/**
* Flag to disable all metric exports (assuming any MetricWriters are available).
*/
private boolean enabled = true;
private Export export = new Export();
private Map<String, Export> writers = new LinkedHashMap<String, Export>();
/**
* Default values for trigger configuration for all writers. Can also be set by
* including a writer with <code>name="*"</code>.
*
* @return the default trigger configuration
*/
public Export getDefault() {
return this.export;
}
/**
* Configuration for triggers on individual named writers. Each value can individually
* specify a name pattern explicitly, or else the map key will be used if the name is
* not set.
*
* @return the writers
*/
public Map<String, Export> getWriters() {
return this.writers;
}
@PostConstruct
public void setUpDefaults() {
Export defaults = null;
for (Entry<String, Export> entry : this.writers.entrySet()) {
String key = entry.getKey();
Export value = entry.getValue();
if (value.getNames() == null || value.getNames().length == 0) {
value.setNames(new String[] { key });
}
if (Arrays.asList(value.getNames()).contains("*")) {
defaults = value;
}
}
if (defaults == null) {
this.export.setNames(new String[] { "*" });
this.writers.put("*", this.export);
defaults = this.export;
}
if (defaults.isIgnoreTimestamps() == null) {
defaults.setIgnoreTimestamps(false);
}
if (defaults.isSendLatest() == null) {
defaults.setSendLatest(true);
}
if (defaults.getDelayMillis() == null) {
defaults.setDelayMillis(5000);
}
for (Export value : this.writers.values()) {
if (value.isIgnoreTimestamps() == null) {
value.setIgnoreTimestamps(defaults.isIgnoreTimestamps());
}
if (value.isSendLatest() == null) {
value.setSendLatest(defaults.isSendLatest());
}
if (value.getDelayMillis() == null) {
value.setDelayMillis(defaults.getDelayMillis());
}
}
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public static class Export {
/**
* Names (or patterns) for bean names that this configuration applies to.
*/
private String[] names;
/**
* 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 any available optimizations based on not exporting unchanged
* metric values.
*/
private Boolean sendLatest;
/**
* Flag to ignore timestamps completely. If true, send all metrics all the time,
* including ones that haven't changed since startup.
*/
private Boolean ignoreTimestamps;
private String[] includes;
private String[] excludes;
public String[] getNames() {
return this.names;
}
public void setNames(String[] names) {
this.names = names;
}
public String[] getIncludes() {
return this.includes;
}
public void setIncludes(String[] includes) {
this.includes = includes;
}
public void setExcludes(String[] excludes) {
this.excludes = excludes;
}
public String[] getExcludes() {
return this.excludes;
}
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;
}
public Boolean isSendLatest() {
return this.sendLatest;
}
public void setSendLatest(boolean sendLatest) {
this.sendLatest = sendLatest;
}
}
/**
* Find a matching trigger configuration.
* @param name the bean name to match
* @return a matching configuration if there is one
*/
public Export findExport(String name) {
for (Export value : this.writers.values()) {
if (PatternMatchUtils.simpleMatch(value.getNames(), name)) {
return value;
}
}
return this.export;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.export;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.boot.actuate.metrics.export.MetricExportProperties.Export;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.IntervalTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
/**
* @author Dave Syer
*/
public class MetricExporters implements SchedulingConfigurer {
private MetricExportProperties export;
private Map<String, MetricWriter> writers;
private Map<String, Exporter> exporters = new HashMap<String, Exporter>();
private MetricReader reader;
public MetricExporters(MetricReader reader, Map<String, MetricWriter> writers,
MetricExportProperties export) {
this.reader = reader;
this.export = export;
this.writers = writers;
}
public Map<String, Exporter> getExporters() {
return this.exporters;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
for (Entry<String, MetricWriter> entry : this.writers.entrySet()) {
String name = entry.getKey();
Export trigger = this.export.findExport(name);
if (trigger != null) {
MetricWriter writer = entry.getValue();
final MetricCopyExporter exporter = new MetricCopyExporter(this.reader,
writer);
if (trigger.getIncludes() != null || trigger.getExcludes() != null) {
exporter.setIncludes(trigger.getIncludes());
exporter.setExcludes(trigger.getExcludes());
}
exporter.setIgnoreTimestamps(trigger.isIgnoreTimestamps());
exporter.setSendLatest(trigger.isSendLatest());
this.exporters.put(name, exporter);
taskRegistrar.addFixedDelayTask(new IntervalTask(new Runnable() {
@Override
public void run() {
exporter.export();
}
}, trigger.getDelayMillis(), trigger.getDelayMillis()));
}
}
}
}

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.jmx;
import java.util.Hashtable;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.springframework.jmx.export.naming.KeyNamingStrategy;
import org.springframework.jmx.export.naming.ObjectNamingStrategy;
import org.springframework.util.StringUtils;
/**
* MBean naming strategy for metric keys. A metric name of
* <code>counter.foo.bar.spam</code> translates to an object name with
* <code>type=counter</code>, <code>name=foo</code> and <code>value=bar.spam</code>. This
* results in a more or less pleasing view with no tweaks in jconsole or jvisualvm. The
* domain is copied from the input key and the type in the input key is discarded.
*
* @author Dave Syer
*/
public class DefaultMetricNamingStrategy implements ObjectNamingStrategy {
private ObjectNamingStrategy namingStrategy = new KeyNamingStrategy();
@Override
public ObjectName getObjectName(Object managedBean, String beanKey)
throws MalformedObjectNameException {
ObjectName objectName = this.namingStrategy.getObjectName(managedBean, beanKey);
String domain = objectName.getDomain();
Hashtable<String, String> table = new Hashtable<String, String>(
objectName.getKeyPropertyList());
String name = objectName.getKeyProperty("name");
if (name != null) {
table.remove("name");
String[] parts = StringUtils.delimitedListToStringArray(name, ".");
table.put("type", parts[0]);
if (parts.length > 1) {
table.put(parts.length > 2 ? "name" : "value", parts[1]);
}
if (parts.length > 2) {
table.put("value",
name.substring(parts[0].length() + parts[1].length() + 2));
}
}
return new ObjectName(domain, table);
}
}

View File

@ -0,0 +1,159 @@
/*
* 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.jmx;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.management.MalformedObjectNameException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.writer.Delta;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.naming.ObjectNamingStrategy;
/**
* A {@link MetricWriter} for MBeans. Each metric is registered as an individual MBean, so
* (for instance) it can be graphed and monitored. The object names are provided by an
* {@link ObjectNamingStrategy}, where the default is a
* {@link DefaultMetricNamingStrategy} which provides <code>type</code>, <code>name</code>
* and <code>value</code> keys by splitting up the metric name on periods.
*
* @author Dave Syer
*/
@ManagedResource(description = "MetricWriter for pushing metrics to JMX MBeans.")
public class JmxMetricWriter implements MetricWriter {
private static Log logger = LogFactory.getLog(JmxMetricWriter.class);
private final ConcurrentMap<String, MetricValue> values = new ConcurrentHashMap<String, MetricValue>();
private final MBeanExporter exporter;
private ObjectNamingStrategy namingStrategy = new DefaultMetricNamingStrategy();
private String domain = "org.springframework.metrics";
public JmxMetricWriter(MBeanExporter exporter) {
this.exporter = exporter;
}
public void setNamingStrategy(ObjectNamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;
}
public void setDomain(String domain) {
this.domain = domain;
}
@ManagedOperation
public void increment(String name, long value) {
increment(new Delta<Long>(name, value));
}
@Override
public void increment(Delta<?> delta) {
MetricValue counter = getValue(delta.getName());
counter.increment(delta.getValue().longValue());
}
@ManagedOperation
public void set(String name, double value) {
set(new Metric<Double>(name, value));
}
@Override
public void set(Metric<?> value) {
MetricValue metric = getValue(value.getName());
metric.setValue(value.getValue().doubleValue());
}
@Override
@ManagedOperation
public void reset(String name) {
MetricValue value = this.values.remove(name);
if (value != null) {
try {
// We can unregister the MBean, but if this writer is on the end of an
// Exporter the chances are it will be re-registered almost immediately.
this.exporter.unregisterManagedResource(this.namingStrategy
.getObjectName(value, getKey(name)));
}
catch (MalformedObjectNameException e) {
logger.warn("Could not unregister MBean for " + name);
}
}
}
private MetricValue getValue(String name) {
if (!this.values.containsKey(name)) {
this.values.putIfAbsent(name, new MetricValue());
MetricValue value = this.values.get(name);
try {
this.exporter.registerManagedResource(value,
this.namingStrategy.getObjectName(value, getKey(name)));
}
catch (Exception e) {
// Could not register mbean, maybe just a race condition
}
}
return this.values.get(name);
}
private String getKey(String name) {
return String.format(this.domain + ":type=MetricValue,name=%s", name);
}
@ManagedResource
public static class MetricValue {
private double value;
private long lastUpdated = 0;
public void setValue(double value) {
if (this.value != value) {
this.lastUpdated = System.currentTimeMillis();
}
this.value = value;
}
public void increment(long value) {
this.lastUpdated = System.currentTimeMillis();
this.value += value;
}
@ManagedAttribute
public double getValue() {
return this.value;
}
@ManagedAttribute
public Date getLastUpdated() {
return new Date(this.lastUpdated);
}
}
}

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 integration with JMX.
*/
package org.springframework.boot.actuate.metrics.jmx;

View File

@ -0,0 +1,72 @@
/*
* 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.opentsdb;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.util.ObjectUtils;
/**
* A naming strategy that just passes through the metric name, together with tags from a
* set of static values. Open TSDB requires at least one tag, so tags are always added for
* you: the {@value #DOMAIN_KEY} key is added with a value "spring", and the
* {@value #PROCESS_KEY} key is added with a value equal to the object hash of "this" (the
* naming strategy). The "domain" value is a system identifier - it would be common to all
* processes in the same distributed system. In most cases this will be unique enough to
* allow aggregation of the underlying metrics in Open TSDB, but normally it is best to
* provide your own tags, including a prefix and process identifier if you know one
* (overwriting the default).
*
* @author Dave Syer
*/
public class DefaultOpenTsdbNamingStrategy implements OpenTsdbNamingStrategy {
public static final String DOMAIN_KEY = "domain";
public static final String PROCESS_KEY = "process";
/**
* Tags to apply to every metric. Open TSDB requires at least one tag, so a "prefix"
* tag is added for you by default.
*/
private Map<String, String> tags = new LinkedHashMap<String, String>();
private Map<String, OpenTsdbName> cache = new HashMap<String, OpenTsdbName>();
public DefaultOpenTsdbNamingStrategy() {
this.tags.put(DOMAIN_KEY, "org.springframework.metrics");
this.tags.put(PROCESS_KEY, ObjectUtils.getIdentityHexString(this));
}
public void setTags(Map<String, String> staticTags) {
this.tags.putAll(staticTags);
}
@Override
public OpenTsdbName getName(String name) {
if (this.cache.containsKey(name)) {
return this.cache.get(name);
}
OpenTsdbName value = new OpenTsdbName(name);
value.setTags(this.tags);
this.cache.put(name, value);
return value;
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.opentsdb;
import java.util.Map;
/**
* @author Dave Syer
*/
public class OpenTsdbData {
private OpenTsdbName name;
private Long timestamp;
private Number value;
protected OpenTsdbData() {
this.name = new OpenTsdbName();
}
public OpenTsdbData(String metric, Number value) {
this(metric, value, System.currentTimeMillis());
}
public OpenTsdbData(String metric, Number value, Long timestamp) {
this(new OpenTsdbName(metric), value, timestamp);
}
public OpenTsdbData(OpenTsdbName name, Number value, Long timestamp) {
this.name = name;
this.value = value;
this.timestamp = timestamp;
}
public String getMetric() {
return this.name.getMetric();
}
public void setMetric(String metric) {
this.name.setMetric(metric);
}
public Long getTimestamp() {
return this.timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public Number getValue() {
return this.value;
}
public void setValue(Number value) {
this.value = value;
}
public Map<String, String> getTags() {
return this.name.getTags();
}
public void setTags(Map<String, String> tags) {
this.name.setTags(tags);
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.opentsdb;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.writer.Delta;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
/**
* A {@link MetricWriter} for the Open TSDB database (version 2.0), writing metrics to the
* HTTP endpoint provided by the server. Data are buffered according to the
* {@link #setBufferSize(int) bufferSize} property, and only flushed automatically when
* the buffer size is reached. Users should either manually {@link #flush()} after writing
* a batch of data if that makes sense, or consider adding a {@link Scheduled
* <code>@Scheduled</code>} task to flush periodically.
*
* @author Dave Syer
*/
public class OpenTsdbMetricWriter implements MetricWriter {
private static final Log logger = LogFactory.getLog(OpenTsdbMetricWriter.class);
private RestOperations restTemplate = new RestTemplate();
/**
* URL for POSTing data. Defaults to http://localhost:4242/api/put.
*/
private String url = "http://localhost:4242/api/put";
/**
* Buffer size to fill before posting data to server.
*/
private int bufferSize = 64;
/**
* The media type to use to serialize and accept responses from the server. Defaults
* to "application/json".
*/
private MediaType mediaType = MediaType.APPLICATION_JSON;
private List<OpenTsdbData> buffer = new ArrayList<OpenTsdbData>(this.bufferSize);
private OpenTsdbNamingStrategy namingStrategy = new DefaultOpenTsdbNamingStrategy();
public RestOperations getRestTemplate() {
return this.restTemplate;
}
public void setRestTemplate(RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
public void setUrl(String url) {
this.url = url;
}
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
public void setMediaType(MediaType mediaType) {
this.mediaType = mediaType;
}
public void setNamingStrategy(OpenTsdbNamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;
}
@Override
public void increment(Delta<?> delta) {
throw new UnsupportedOperationException("Counters not supported via increment");
}
@Override
public void set(Metric<?> value) {
OpenTsdbData data = new OpenTsdbData(
this.namingStrategy.getName(value.getName()), value.getValue(), value
.getTimestamp().getTime());
this.buffer.add(data);
if (this.buffer.size() >= this.bufferSize) {
flush();
}
}
/**
* Flush the buffer without waiting for it to fill any further.
*/
public void flush() {
if (this.buffer.isEmpty()) {
return;
}
List<OpenTsdbData> temp = new ArrayList<OpenTsdbData>();
synchronized (this.buffer) {
temp.addAll(this.buffer);
this.buffer.clear();
}
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(this.mediaType));
headers.setContentType(this.mediaType);
HttpEntity<List<OpenTsdbData>> request = new HttpEntity<List<OpenTsdbData>>(temp,
headers);
@SuppressWarnings("rawtypes")
ResponseEntity<Map> response = this.restTemplate.postForEntity(this.url, request,
Map.class);
if (!response.getStatusCode().is2xxSuccessful()) {
logger.warn("Cannot write metrics (discarded " + temp.size() + " values): "
+ response.getBody());
}
}
@Override
public void reset(String metricName) {
set(new Metric<Long>(metricName, 0L));
}
}

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.opentsdb;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author Dave Syer
*/
public class OpenTsdbName {
private String metric;
private Map<String, String> tags = new LinkedHashMap<String, String>();
protected OpenTsdbName() {
}
public OpenTsdbName(String metric) {
this.metric = metric;
}
public String getMetric() {
return this.metric;
}
public void setMetric(String metric) {
this.metric = metric;
}
public Map<String, String> getTags() {
return this.tags;
}
public void setTags(Map<String, String> tags) {
this.tags.putAll(tags);
}
public void tag(String name, String value) {
this.tags.put(name, value);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.opentsdb;
/**
* @author Dave Syer
*/
public interface OpenTsdbNamingStrategy {
OpenTsdbName getName(String metricName);
}

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 integration with OpenTSDB.
*/
package org.springframework.boot.actuate.metrics.opentsdb;

View File

@ -29,6 +29,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link MetricRepository} implementation for a redis backend. Metric values are stored
@ -92,6 +93,8 @@ public class RedisMetricRepository implements MetricRepository {
public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,
String prefix, String key) {
Assert.notNull(redisConnectionFactory, "RedisConnectionFactory must not be null");
Assert.state(StringUtils.hasText(prefix), "Prefix must be non-empty");
Assert.state(StringUtils.hasText(key), "Key must be non-empty");
this.redisOperations = RedisUtils.stringTemplate(redisConnectionFactory);
if (!prefix.endsWith(".")) {
prefix = prefix + ".";

View File

@ -0,0 +1,111 @@
/*
* 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.statsd;
import java.io.Closeable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.writer.Delta;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.util.StringUtils;
import com.timgroup.statsd.NonBlockingStatsDClient;
import com.timgroup.statsd.StatsDClientErrorHandler;
/**
* A {@link MetricWriter} that pushes data to statsd. Statsd has the concept of counters
* and gauges, but only supports gauges with data type Long, so values will be truncated
* towards zero. Metrics whose name contains "timer." (but not "gauge." or "counter.")
* will be treated as execution times (in statsd terms). Anything incremented is treated
* as a counter, and anything with a snapshot value in {@link #set(Metric)} is treated as
* a gauge.
*
* @author Dave Syer
*/
public class StatsdMetricWriter implements MetricWriter, Closeable {
private static Log logger = LogFactory.getLog(StatsdMetricWriter.class);
private final NonBlockingStatsDClient client;
/**
* Create a new writer with the given parameters.
*
* @param host the hostname for the statsd server
* @param port the port for the statsd server
*/
public StatsdMetricWriter(String host, int port) {
this(null, host, port);
}
/**
* Create a new writer with the given parameters.
*
* @param prefix the prefix to apply to all metric names (can be null)
* @param host the hostname for the statsd server
* @param port the port for the statsd server
*/
public StatsdMetricWriter(String prefix, String host, int port) {
prefix = StringUtils.hasText(prefix) ? prefix : null;
while (prefix != null && prefix.endsWith(".")) {
prefix = prefix.substring(0, prefix.length() - 1);
}
this.client = new NonBlockingStatsDClient(prefix, host, port,
new LoggingStatsdErrorHandler());
}
@Override
public void increment(Delta<?> delta) {
this.client.count(delta.getName(), delta.getValue().longValue());
}
@Override
public void set(Metric<?> value) {
String name = value.getName();
if (name.contains("timer.") && !name.contains("gauge.")
&& !name.contains("counter.")) {
this.client.recordExecutionTime(name, value.getValue().longValue());
}
else {
this.client.gauge(name, value.getValue().longValue());
}
}
@Override
public void reset(String name) {
if (name.contains("counter.")) {
this.client.gauge(name, 0L);
}
}
@Override
public void close() {
this.client.stop();
}
private static final class LoggingStatsdErrorHandler implements
StatsDClientErrorHandler {
@Override
public void handle(Exception e) {
logger.debug("Failed to write metric. Exception: " + e.getClass()
+ ", message: " + e.getMessage());
}
}
}

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 integration with Statsd.
*/
package org.springframework.boot.actuate.metrics.statsd;

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

@ -18,6 +18,7 @@ package org.springframework.boot.actuate.metrics.writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.springframework.boot.actuate.metrics.Metric;
@ -28,7 +29,7 @@ import org.springframework.boot.actuate.metrics.Metric;
*
* @author Dave Syer
*/
public class CompositeMetricWriter implements MetricWriter {
public class CompositeMetricWriter implements MetricWriter, Iterable<MetricWriter> {
private final List<MetricWriter> writers = new ArrayList<MetricWriter>();
@ -40,6 +41,11 @@ public class CompositeMetricWriter implements MetricWriter {
this.writers.addAll(writers);
}
@Override
public Iterator<MetricWriter> iterator() {
return this.writers.iterator();
}
@Override
public void increment(Delta<?> delta) {
for (MetricWriter writer : this.writers) {

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

@ -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.writer;
import java.io.Flushable;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.metrics.export.MetricCopyExporter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author Dave Syer
*/
public class WriterUtils {
private static final Log logger = LogFactory.getLog(MetricCopyExporter.class);
public static void flush(MetricWriter writer) {
if (writer instanceof CompositeMetricWriter) {
for (MetricWriter element : (CompositeMetricWriter) writer) {
flush(element);
}
}
try {
if (ClassUtils.isPresent("java.io.Flushable", null)) {
if (writer instanceof Flushable) {
((Flushable) writer).flush();
return;
}
}
Method method = ReflectionUtils.findMethod(writer.getClass(), "flush");
if (method != null) {
ReflectionUtils.invokeMethod(method, writer);
}
}
catch (Exception e) {
logger.warn("Could not flush MetricWriter: " + e.getClass() + ": "
+ e.getMessage());
}
}
}

View File

@ -15,7 +15,7 @@
*/
/**
* Metrics integration with Dropwizard Metrics.
* Support for writing metrics
*/
package org.springframework.boot.actuate.metrics.writer;

View File

@ -11,6 +11,9 @@ 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.MetricExportAutoConfiguration,\
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,31 @@
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.dropwizard.DropwizardMetricServices;
import org.springframework.boot.actuate.metrics.export.MetricCopyExporter;
import org.springframework.boot.actuate.metrics.export.MetricExporters;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.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.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 +49,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 +59,95 @@ 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,
MetricRepositoryAutoConfiguration.class,
MetricsChannelAutoConfiguration.class,
MetricExportAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
MetricExporters exporter = this.context.getBean(MetricExporters.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,
MetricExportAutoConfiguration.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();
MetricExporters exporters = this.context.getBean(MetricExporters.class);
MetricCopyExporter exporter = (MetricCopyExporter) exporters.getExporters().get(
"writer");
exporter.setIgnoreTimestamps(true);
exporter.export();
MetricWriter writer = this.context.getBean("writer", MetricWriter.class);
Mockito.verify(writer, Mockito.atLeastOnce()).set(Matchers.any(Metric.class));
}
@Test
public void 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 +155,7 @@ public class MetricRepositoryAutoConfigurationTests {
@Bean
public MetricWriter writer() {
return mock(MetricWriter.class);
return Mockito.mock(MetricWriter.class);
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.aggregate;
import java.util.Date;
import org.junit.Test;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
import org.springframework.boot.actuate.metrics.writer.Delta;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* @author Dave Syer
*/
public class AggregateMetricReaderTests {
private InMemoryMetricRepository source = new InMemoryMetricRepository();
private AggregateMetricReader reader = new AggregateMetricReader(this.source);
@Test
public void writeAndReadDefaults() {
this.source.set(new Metric<Double>("foo.bar.spam", 2.3));
assertEquals(2.3, this.reader.findOne("aggregate.spam").getValue());
}
@Test
public void writeAndReadLatestValue() {
this.source.set(new Metric<Double>("foo.bar.spam", 2.3, new Date(100L)));
this.source.set(new Metric<Double>("oof.rab.spam", 2.4, new Date(0L)));
assertEquals(2.3, this.reader.findOne("aggregate.spam").getValue());
}
@Test
public void writeAndReadExtraLong() {
this.source.set(new Metric<Double>("blee.foo.bar.spam", 2.3));
this.reader.setTruncateKeyLength(3);
assertEquals(2.3, this.reader.findOne("aggregate.spam").getValue());
}
@Test
public void onlyPrefixed() {
this.source.set(new Metric<Double>("foo.bar.spam", 2.3));
assertNull(this.reader.findOne("spam"));
}
@Test
public void incrementCounter() {
this.source.increment(new Delta<Long>("foo.bar.counter.spam", 2L));
this.source.increment(new Delta<Long>("oof.rab.counter.spam", 3L));
assertEquals(5L, this.reader.findOne("aggregate.counter.spam").getValue());
}
@Test
public void countGauges() {
this.source.set(new Metric<Double>("foo.bar.spam", 2.3));
this.source.set(new Metric<Double>("oof.rab.spam", 2.4));
assertEquals(1, this.reader.count());
}
@Test
public void countGaugesAndCounters() {
this.source.set(new Metric<Double>("foo.bar.spam", 2.3));
this.source.set(new Metric<Double>("oof.rab.spam", 2.4));
this.source.increment(new Delta<Long>("foo.bar.counter.spam", 2L));
this.source.increment(new Delta<Long>("oof.rab.counter.spam", 3L));
assertEquals(2, this.reader.count());
}
}

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.dropwizard.DropwizardMetricServices;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader;
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.
@ -14,13 +14,13 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.writer;
package org.springframework.boot.actuate.metrics.dropwizard;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.dropwizard.DropwizardMetricServices;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
@ -29,56 +29,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 +116,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 +133,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

@ -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.
@ -41,6 +41,33 @@ public class MetricCopyExporterTests {
assertEquals(1, this.writer.count());
}
@Test
public void exportIncludes() {
this.exporter.setIncludes("*");
this.reader.set(new Metric<Number>("foo", 2.3));
this.exporter.export();
assertEquals(1, this.writer.count());
}
@Test
public void exportExcludesWithIncludes() {
this.exporter.setIncludes("*");
this.exporter.setExcludes("foo");
this.reader.set(new Metric<Number>("foo", 2.3));
this.reader.set(new Metric<Number>("bar", 2.4));
this.exporter.export();
assertEquals(1, this.writer.count());
}
@Test
public void exportExcludesDefaultIncludes() {
this.exporter.setExcludes("foo");
this.reader.set(new Metric<Number>("foo", 2.3));
this.reader.set(new Metric<Number>("bar", 2.4));
this.exporter.export();
assertEquals(1, this.writer.count());
}
@Test
public void timestamp() {
this.reader.set(new Metric<Number>("foo", 2.3));

View File

@ -0,0 +1,60 @@
/*
* 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.export;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class MetricExportersTests {
private MetricExporters exporters;
private MetricExportProperties export = new MetricExportProperties();
private Map<String, MetricWriter> writers = new LinkedHashMap<String, MetricWriter>();
private MetricReader reader = Mockito.mock(MetricReader.class);
private MetricWriter writer = Mockito.mock(MetricWriter.class);
@Test
public void emptyWriters() {
this.exporters = new MetricExporters(this.reader, this.writers, this.export);
this.exporters.configureTasks(new ScheduledTaskRegistrar());
assertNotNull(this.exporters.getExporters());
assertEquals(0, this.exporters.getExporters().size());
}
@Test
public void oneWriter() {
this.export.setUpDefaults();
this.writers.put("foo", this.writer);
this.exporters = new MetricExporters(this.reader, this.writers, this.export);
this.exporters.configureTasks(new ScheduledTaskRegistrar());
assertNotNull(this.exporters.getExporters());
assertEquals(1, this.exporters.getExporters().size());
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.jmx;
import javax.management.ObjectName;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* @author Dave Syer
*/
public class DefaultMetricNamingStrategyTests {
private DefaultMetricNamingStrategy strategy = new DefaultMetricNamingStrategy();
@Test
public void simpleName() throws Exception {
ObjectName name = this.strategy.getObjectName(null,
"domain:type=MetricValue,name=foo");
assertEquals("domain", name.getDomain());
assertEquals("foo", name.getKeyProperty("type"));
}
@Test
public void onePeriod() throws Exception {
ObjectName name = this.strategy.getObjectName(null,
"domain:type=MetricValue,name=foo.bar");
assertEquals("domain", name.getDomain());
assertEquals("foo", name.getKeyProperty("type"));
assertEquals("Wrong name: " + name, "bar", name.getKeyProperty("value"));
}
@Test
public void twoPeriods() throws Exception {
ObjectName name = this.strategy.getObjectName(null,
"domain:type=MetricValue,name=foo.bar.spam");
assertEquals("domain", name.getDomain());
assertEquals("foo", name.getKeyProperty("type"));
assertEquals("Wrong name: " + name, "bar", name.getKeyProperty("name"));
assertEquals("Wrong name: " + name, "spam", name.getKeyProperty("value"));
}
@Test
public void threePeriods() throws Exception {
ObjectName name = this.strategy.getObjectName(null,
"domain:type=MetricValue,name=foo.bar.spam.bucket");
assertEquals("domain", name.getDomain());
assertEquals("foo", name.getKeyProperty("type"));
assertEquals("Wrong name: " + name, "bar", name.getKeyProperty("name"));
assertEquals("Wrong name: " + name, "spam.bucket", name.getKeyProperty("value"));
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.opentsdb;
import java.util.Collections;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestOperations;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
/**
* @author Dave Syer
*/
public class OpenTsdbMetricWriterTests {
private OpenTsdbMetricWriter writer;
private RestOperations restTemplate = Mockito.mock(RestOperations.class);
@Before
public void init() {
this.writer = new OpenTsdbMetricWriter();
this.writer.setRestTemplate(this.restTemplate);
}
@Test
public void postSuccessfullyOnFlush() {
this.writer.set(new Metric<Double>("foo", 2.4));
given(
this.restTemplate.postForEntity(Matchers.anyString(),
Matchers.any(Object.class), anyMap()))
.willReturn(emptyResponse());
this.writer.flush();
verify(this.restTemplate).postForEntity(Matchers.anyString(),
Matchers.any(Object.class), anyMap());
}
@Test
public void flushAutomaticlly() {
given(
this.restTemplate.postForEntity(Matchers.anyString(),
Matchers.any(Object.class), anyMap()))
.willReturn(emptyResponse());
this.writer.setBufferSize(0);
this.writer.set(new Metric<Double>("foo", 2.4));
verify(this.restTemplate).postForEntity(Matchers.anyString(),
Matchers.any(Object.class), anyMap());
}
@SuppressWarnings("rawtypes")
private ResponseEntity<Map> emptyResponse() {
return new ResponseEntity<Map>(Collections.emptyMap(), HttpStatus.OK);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Class<Map> anyMap() {
return Matchers.any(Class.class);
}
}

View File

@ -0,0 +1,145 @@
/*
* 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.statsd;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.writer.Delta;
import org.springframework.util.SocketUtils;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link StatsdMetricWriter}.
*
* @author Dave Syer
*/
public class StatsdMetricWriterTests {
private int port = SocketUtils.findAvailableTcpPort();
private DummyStatsDServer server = new DummyStatsDServer(this.port);
private StatsdMetricWriter writer = new StatsdMetricWriter("me", "localhost",
this.port);
@After
public void close() {
this.server.stop();
this.writer.close();
}
@Test
public void increment() {
this.writer.increment(new Delta<Long>("counter.foo", 3L));
this.server.waitForMessage();
assertEquals("me.counter.foo:3|c", this.server.messagesReceived().get(0));
}
@Test
public void setLongMetric() throws Exception {
this.writer.set(new Metric<Long>("gauge.foo", 3L));
this.server.waitForMessage();
assertEquals("me.gauge.foo:3|g", this.server.messagesReceived().get(0));
}
@Test
public void setDoubleMetric() throws Exception {
this.writer.set(new Metric<Double>("gauge.foo", 3.7));
this.server.waitForMessage();
// Doubles are truncated
assertEquals("me.gauge.foo:3|g", this.server.messagesReceived().get(0));
}
@Test
public void setTimerMetric() throws Exception {
this.writer.set(new Metric<Long>("timer.foo", 37L));
this.server.waitForMessage();
assertEquals("me.timer.foo:37|ms", this.server.messagesReceived().get(0));
}
@Test
public void nullPrefix() throws Exception {
this.writer = new StatsdMetricWriter("localhost", this.port);
this.writer.set(new Metric<Long>("gauge.foo", 3L));
this.server.waitForMessage();
assertEquals("gauge.foo:3|g", this.server.messagesReceived().get(0));
}
@Test
public void perioPrefix() throws Exception {
this.writer = new StatsdMetricWriter("my.", "localhost", this.port);
this.writer.set(new Metric<Long>("gauge.foo", 3L));
this.server.waitForMessage();
assertEquals("my.gauge.foo:3|g", this.server.messagesReceived().get(0));
}
private static final class DummyStatsDServer {
private final List<String> messagesReceived = new ArrayList<String>();
private final DatagramSocket server;
public DummyStatsDServer(int port) {
try {
this.server = new DatagramSocket(port);
}
catch (SocketException e) {
throw new IllegalStateException(e);
}
new Thread(new Runnable() {
@Override
public void run() {
try {
final DatagramPacket packet = new DatagramPacket(new byte[256],
256);
DummyStatsDServer.this.server.receive(packet);
DummyStatsDServer.this.messagesReceived.add(new String(packet
.getData(), Charset.forName("UTF-8")).trim());
}
catch (Exception e) {
}
}
}).start();
}
public void stop() {
this.server.close();
}
public void waitForMessage() {
while (this.messagesReceived.isEmpty()) {
try {
Thread.sleep(50L);
}
catch (InterruptedException e) {
}
}
}
public List<String> messagesReceived() {
return new ArrayList<String>(this.messagesReceived);
}
}
}

View File

@ -136,6 +136,7 @@
<spring-social-linkedin.version>1.0.1.RELEASE</spring-social-linkedin.version>
<spring-social-twitter.version>1.1.0.RELEASE</spring-social-twitter.version>
<spring-ws.version>2.2.1.RELEASE</spring-ws.version>
<statsd-client.version>3.0.1</statsd-client.version>
<sun-mail.version>${javax-mail.version}</sun-mail.version>
<thymeleaf.version>2.1.4.RELEASE</thymeleaf.version>
<thymeleaf-extras-springsecurity4.version>2.1.2.RELEASE</thymeleaf-extras-springsecurity4.version>
@ -552,6 +553,11 @@
<artifactId>javax.mail</artifactId>
<version>${sun-mail.version}</version>
</dependency>
<dependency>
<groupId>com.timgroup</groupId>
<artifactId>java-statsd-client</artifactId>
<version>${statsd-client.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>

View File

@ -889,48 +889,227 @@ 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].
=== Special features with Java 8
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("metrics.mysystem.${random.value:0000}.${spring.application.name:application}")
private String prefix = "metrics.mysystem";
@Value("${metrics.key:keys.mysystem}")
private String key = "METRICSKEY";
@Bean
MetricWriter metricWriter() {
return new RedisMetricRepository(connectionFactory, prefix, key);
}
----
The prefix is constructed with the application name at the end, so it can easily be used
to identify a group of processes with the same logical name later.
NOTE: it's important to set both the key and the prefix. The key is used for all
repository operations, and can be shared by multiple repositories. If multiple
repositories share a key (like in the case where you need to aggregate across them), then
you normally have a read-only "master" repository that has a short, but identifiable,
prefix (like "metrics.mysystem"), and many write-only repositories with prefixes that
start with the master prefix (like `metrics.mysystem.*` in the example above). It is
efficient to read all the keys from a "master" repository like that, but inefficient to
read a subset with a longer prefix (e.g. using one of the writing repositories).
[[production-ready-metric-writers-export-to-open-tdsb]]
==== Example: Export to Open TSDB
If you provide a `@Bean` of type `OpenTsdbHttpMetricWriter` the metrics are exported to
http://opentsdb.net/[Open TSDB] for aggregation. The `OpenTsdbHttpMetricWriter` has a
`url` property that you need to set to the Open TSDB "/put" endpoint, e.g.
`http://localhost:4242/api/put`). It also has a `namingStrategy` that you can customize or
configure to make the metrics match the data structure you need on the server. By default
it just passes through the metric name as an Open TSDB metric name and adds a tag "domain"
with value "org.springframework.metrics" and another tag "process" with value equals to
the object hash of the naming strategy. Thus, after running the application and generating
some metrics (e.g. by pinging the home page) you can inspect the metrics in the TDB UI
(http://localhost:4242 by default). Example:
[source,indent=0]
----
curl localhost:4242/api/query?start=1h-ago&m=max:counter.status.200.root
[
{
"metric": "counter.status.200.root",
"tags": {
"domain": "org.springframework.metrics",
"process": "b968a76"
},
"aggregateTags": [],
"dps": {
"1430492872": 2,
"1430492875": 6
}
}
]
----
[[production-ready-metric-writers-export-to-statsd]]
==== Example: Export to Statsd
If you provide a `@Bean` of type `StatsdMetricWriter` the metrics are exported to a
statsd server:
[source,java,indent=0]
----
@Value("${spring.application.name:application}.${random.value:0000}")
private String prefix = "metrics";
@Value("${statsd.host:localhost}")
private String host = "localhost";
@Value("${statsd.port:8125}")
private int port;
@Bean
MetricWriter metricWriter() {
return new StatsdMetricWriter(prefix, host, port);
}
----
[[production-ready-metric-writers-export-to-jmx]]
==== Example: Export to JMX
If you provide a `@Bean` of type `JmxMetricWriter` the metrics are exported as MBeans to
the local server (the `MBeanExporter` is provided by Spring Boot JMX autoconfiguration as
long as it is switched on). Metrics can then be inspected, graphed, alerted etc. using any
tool that understands JMX (e.g. JConsole or JVisualVM). Example:
[source,java,indent=0]
----
@Bean
MetricWriter metricWriter(MBeanExporter exporter) {
return new JmxMetricWriter(exporter);
}
----
Each metric is exported as an individual MBean. The format for the `ObjectNames` is given
by an `ObjectNamingStrategy` which can be injected into the `JmxMetricWriter` (the default
breaks up the metric name and tags the first two period-separated sections in a way that
should make the metrics group nicely in JVisualVM or JConsole).
[[production-ready-metric-aggregation]]
=== Aggregating metrics from multiple sources
There is an `AggregateMetricReader` that you can use to consolidate metrics from different
physical sources. Sources for the same logical metric just need to publish them with a
period-separated prefix, and the reader will aggregate (by truncating the metric names,
and dropping the prefix). Counters are summed and everything else (i.e. gauges) take their
most recent value.
This is very useful (for instance) if multiple application instances are feeding to a
central (e.g. redis) repository and you want to display the results. Particularly
recommended in conjunction with a `MetricReaderPublicMetrics` for hooking up to the
results to the "/metrics" endpoint. Example:
[source,java,indent=0]
----
@Bean
public PublicMetrics metricsAggregate() {
return new MetricReaderPublicMetrics(aggregates());
}
@Bean
protected MetricReader repository(RedisConnectionFactory connectionFactory) {
RedisMetricRepository repository = new RedisMetricRepository(connectionFactory,
"mysystem", "myorg.keys");
return repository;
}
@Bean
protected MetricReader aggregates() {
AggregateMetricReader repository = new AggregateMetricReader(repository());
return repository;
}
----
[[production-ready-dropwizard-metrics]]
=== Dropwizard Metrics
User 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
`io.dropwizard.metrics:metrics-core` library; you can also register you own `@Bean`
instance if you need customizations. Metrics from the `MetricRegistry` are also
automatically exposed via the `/metrics` endpoint.
A default `MetricRegistry` Spring bean will be created when you declare a dependency to
the `io.dropwizard.metrics:metric-core` library; you can also register you own `@Bean`
instance if you need customizations. Users of the
https://dropwizard.github.io/metrics/[Dropwizard '`Metrics`' library] will find that
Spring Boot metrics are automatically published to `com.codahale.metrics.MetricRegistry`.
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).

View File

@ -52,6 +52,9 @@
<module>spring-boot-sample-jta-bitronix</module>
<module>spring-boot-sample-jta-jndi</module>
<module>spring-boot-sample-liquibase</module>
<module>spring-boot-sample-metrics-dropwizard</module>
<module>spring-boot-sample-metrics-opentsdb</module>
<module>spring-boot-sample-metrics-redis</module>
<module>spring-boot-sample-parent-context</module>
<module>spring-boot-sample-profile</module>
<module>spring-boot-sample-secure</module>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-metrics-dropwizard</artifactId>
<name>spring-boot-sample-metrics-dropwizard</name>
<description>Spring Boot Metrics Redis Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,40 @@
/*
* 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 sample.metrics.dropwizard;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "service", ignoreUnknownFields = false)
public class HelloWorldService {
private String name = "World";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getHelloMessage() {
return "Hello " + this.name;
}
}

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 sample.metrics.dropwizard;
import java.util.Collections;
import java.util.Map;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@Description("A controller for handling requests for hello messages")
public class SampleController {
@Autowired
private HelloWorldService helloWorldService;
@Autowired
private GaugeService gauges;
@RequestMapping(value = "/", method = RequestMethod.GET)
@ResponseBody
public Map<String, String> hello() {
this.gauges.submit("timer.test.value", Math.random() * 1000 + 1000);
return Collections.singletonMap("message",
this.helloWorldService.getHelloMessage());
}
protected static class Message {
@NotBlank(message = "Message value cannot be empty")
private String value;
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,29 @@
/*
* 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 sample.metrics.dropwizard;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleDropwizardMetricsApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleDropwizardMetricsApplication.class, args);
}
}

View File

@ -0,0 +1,57 @@
/*
* 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 sample.metrics.dropwizard;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.codahale.metrics.MetricRegistry;
import static org.junit.Assert.assertEquals;
/**
* Basic integration tests for {@link SampleDropwizardMetricsApplication}.
*
* @author Dave Syer
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleDropwizardMetricsApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
@DirtiesContext
public class SampleDropwizardMetricsApplicationTests {
@Autowired
private MetricRegistry registry;
@Autowired
private GaugeService gauges;
@Test
public void timerCreated() {
this.gauges.submit("timer.test", 1234);
assertEquals(1, this.registry.getTimers().get("timer.test").getCount());
}
}

View File

@ -0,0 +1,29 @@
Spring Boot sample with Open TSDB export for metrics.
Start opentsdb, e.g. with [Docker Compose]()
[source,indent=0]
----
$ docker-compose up
----
Run the app and ping the home page (http://localhost:8080) a few times. Go and look at
the result in the TDB UI, e.g.
[source,indent=0]
----
$ curl localhost:4242/api/query?start=1h-ago&m=max:counter.status.200.root
[
{
"metric": "counter.status.200.root",
"tags": {
"prefix": "spring.b968a76"
},
"aggregateTags": [],
"dps": {
"1430492872": 2,
"1430492875": 6
}
}
]
----

View File

@ -0,0 +1,4 @@
opentsdb:
image: lancope/opentsdb
ports:
- "4242:4242"

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-metrics-opentsdb</artifactId>
<name>spring-boot-sample-metrics-opentsdb</name>
<description>Spring Boot Actuator Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,40 @@
/*
* 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 sample.metrics.opentsdb;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "service", ignoreUnknownFields = false)
public class HelloWorldService {
private String name = "World";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getHelloMessage() {
return "Hello " + this.name;
}
}

View File

@ -0,0 +1,58 @@
/*
* 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 sample.metrics.opentsdb;
import java.util.Collections;
import java.util.Map;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@Description("A controller for handling requests for hello messages")
public class SampleController {
@Autowired
private HelloWorldService helloWorldService;
@RequestMapping(value = "/", method = RequestMethod.GET)
@ResponseBody
public Map<String, String> hello() {
return Collections.singletonMap("message",
this.helloWorldService.getHelloMessage());
}
protected static class Message {
@NotBlank(message = "Message value cannot be empty")
private String value;
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,49 @@
/*
* 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 sample.metrics.opentsdb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.metrics.opentsdb.DefaultOpenTsdbNamingStrategy;
import org.springframework.boot.actuate.metrics.opentsdb.OpenTsdbMetricWriter;
import org.springframework.boot.actuate.metrics.opentsdb.OpenTsdbNamingStrategy;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SampleOpenTsdbExportApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleOpenTsdbExportApplication.class, args);
}
@Bean
@ConfigurationProperties("metrics.export")
public MetricWriter openTsdbMetricWriter() {
OpenTsdbMetricWriter writer = new OpenTsdbMetricWriter();
writer.setNamingStrategy(namingStrategy());
return writer;
}
@Bean
@ConfigurationProperties("metrics.names")
public OpenTsdbNamingStrategy namingStrategy() {
return new DefaultOpenTsdbNamingStrategy();
}
}

View File

@ -0,0 +1,2 @@
service.name: Phil
metrics.names.tags.process: ${spring.application.name:application}:${random.value:0000}

View File

@ -0,0 +1,44 @@
/*
* 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 sample.metrics.opentsdb;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Basic integration tests for {@link SampleOpenTsdbExportApplication}.
*
* @author Dave Syer
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleOpenTsdbExportApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
@DirtiesContext
public class SampleOpenTsdbExportApplicationTests {
@Test
public void contextLoads() {
}
}

View File

@ -0,0 +1,46 @@
Spring Boot sample with Redis export for metrics.
Start redis, e.g. with [Docker Compose]()
[source,indent=0]
----
$ docker-compose up
----
Run the app and ping the home page (http://localhost:8080) a few times. Go and look at
the result in Redis, e.g.
[source,indent=0]
----
$ redis-cli
127.0.0.1:6379> keys *
1) "keys.spring.metrics"
2) "spring.metrics.counter.status.200.root"
3) "spring.metrics.gauge.response.root"
127.0.0.1:6379> zrange keys.spring.metrics 0 0 WITHSCORES
1) "spring.metrics.counter.status.200.root"
2) "4"
----
There is also an `AggregateMetricReader` with public metrics in the application context,
and you can see the result in the "/metrics" (metrics with names in "aggregate.*").
The way the Redis repository was set up (with a random key in the metric names) makes the
aggregates work across restarts of the same application, or across a scaled up application
running in multiple processes. E.g.
[source,indent=0]
----
$ curl localhost:8080/metrics
{
...
"aggregate.application.counter.status.200.metrics": 12,
"aggregate.application.counter.status.200.root": 29,
"aggregate.application.gauge.response.metrics": 43,
"aggregate.application.gauge.response.root": 5,
"counter.status.200.root": 2,
"counter.status.200.metrics": 1,
"gauge.response.metrics": 43,
"gauge.response.root": 5
}
----

View File

@ -0,0 +1,4 @@
redis:
image: redis
ports:
- "6379:6379"

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-metrics-redis</artifactId>
<name>spring-boot-sample-metrics-redis</name>
<description>Spring Boot Metrics Redis Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,56 @@
/*
* 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 sample.metrics.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
import org.springframework.boot.actuate.endpoint.PublicMetrics;
import org.springframework.boot.actuate.metrics.aggregate.AggregateMetricReader;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
/**
* @author Dave Syer
*/
@Configuration
public class AggregateMetricsConfiguration {
@Autowired
private ExportProperties export;
@Autowired
private RedisConnectionFactory connectionFactory;
@Bean
public PublicMetrics metricsAggregate() {
return new MetricReaderPublicMetrics(aggregatesMetricReader());
}
private MetricReader globalMetricsForAggregation() {
return new RedisMetricRepository(this.connectionFactory,
this.export.getAggregatePrefix(), this.export.getKey());
}
private MetricReader aggregatesMetricReader() {
AggregateMetricReader repository = new AggregateMetricReader(
globalMetricsForAggregation());
return repository;
}
}

View File

@ -0,0 +1,55 @@
/*
* 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 sample.metrics.redis;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
@ConfigurationProperties("redis.metrics.export")
class ExportProperties {
private String prefix = "spring.metrics";
private String key = "keys.spring.metrics";
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getKey() {
return this.key;
}
public void setKey(String key) {
this.key = key;
}
public String getAggregatePrefix() {
String[] tokens = StringUtils.delimitedListToStringArray(this.prefix, ".");
if (tokens.length > 1) {
if (StringUtils.hasText(tokens[1])) {
// If the prefix has 2 or more non-trivial parts, use the first 1
return tokens[0];
}
}
return this.prefix;
}
}

View File

@ -0,0 +1,40 @@
/*
* 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 sample.metrics.redis;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "service", ignoreUnknownFields = false)
public class HelloWorldService {
private String name = "World";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getHelloMessage() {
return "Hello " + this.name;
}
}

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 sample.metrics.redis;
import java.util.Collections;
import java.util.Map;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@Description("A controller for handling requests for hello messages")
public class SampleController {
@Autowired
private HelloWorldService helloWorldService;
@RequestMapping(value = "/", method = RequestMethod.GET)
@ResponseBody
public Map<String, String> hello() {
return Collections.singletonMap("message",
this.helloWorldService.getHelloMessage());
}
protected static class Message {
@NotBlank(message = "Message value cannot be empty")
private String value;
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,54 @@
/*
* 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 sample.metrics.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.metrics.jmx.JmxMetricWriter;
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.jmx.export.MBeanExporter;
@SpringBootApplication
@EnableConfigurationProperties(ExportProperties.class)
public class SampleRedisExportApplication {
@Autowired
private ExportProperties export;
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleRedisExportApplication.class, args);
}
@Bean
public RedisMetricRepository redisMetricWriter(
RedisConnectionFactory connectionFactory) {
return new RedisMetricRepository(connectionFactory, this.export.getPrefix(),
this.export.getKey());
}
@Bean
public JmxMetricWriter jmxMetricWriter(
@Qualifier("mbeanExporter") MBeanExporter exporter) {
return new JmxMetricWriter(exporter);
}
}

View File

@ -0,0 +1,4 @@
service.name: Phil
redis.metrics.export.prefix: metrics.sample.${random.value:0000}.${spring.application.name:application}
redis.metrics.export.key: keys.metrics.sample
spring.jmx.default-domain: org.springframework.boot

View File

@ -0,0 +1,43 @@
/*
* 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 sample.metrics.redis;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Basic integration tests for {@link SampleRedisExportApplication}.
*
* @author Dave Syer
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleRedisExportApplication.class)
@WebAppConfiguration
@IntegrationTest({ "server.port=0", "spring.jmx.enabled=true" })
@DirtiesContext
public class SampleRedisExportApplicationTests {
@Test
public void contextLoads() {
}
}