Commit a0125389 authored by Andy Wilkinson's avatar Andy Wilkinson

Merge branch 'feature/metrics'

parents 1b3efd41 64af3272
<?xml version="1.0" encoding="UTF-8"?> <?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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
...@@ -61,6 +62,11 @@ ...@@ -61,6 +62,11 @@
<artifactId>javax.mail</artifactId> <artifactId>javax.mail</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.timgroup</groupId>
<artifactId>java-statsd-client</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>io.dropwizard.metrics</groupId> <groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId> <artifactId>metrics-core</artifactId>
...@@ -214,6 +220,11 @@ ...@@ -214,6 +220,11 @@
<artifactId>tomcat-embed-logging-juli</artifactId> <artifactId>tomcat-embed-logging-juli</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.crashub</groupId> <groupId>org.crashub</groupId>
<artifactId>crash.connectors.telnet</artifactId> <artifactId>crash.connectors.telnet</artifactId>
...@@ -234,5 +245,10 @@ ...@@ -234,5 +245,10 @@
<artifactId>spring-data-elasticsearch</artifactId> <artifactId>spring-data-elasticsearch</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>
/*
* 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 {
}
/*
* 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) {
}
};
}
}
...@@ -16,37 +16,31 @@ ...@@ -16,37 +16,31 @@
package org.springframework.boot.actuate.autoconfigure; 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.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.CounterService;
import org.springframework.boot.actuate.metrics.GaugeService; 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.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.InMemoryMetricRepository;
import org.springframework.boot.actuate.metrics.repository.MetricRepository; 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.DefaultCounterService;
import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService; 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.MetricWriter;
import org.springframework.boot.actuate.metrics.writer.MetricWriterMessageHandler;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.messaging.MessageChannel; 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; import com.codahale.metrics.MetricRegistry;
...@@ -62,123 +56,104 @@ import com.codahale.metrics.MetricRegistry; ...@@ -62,123 +56,104 @@ import com.codahale.metrics.MetricRegistry;
* periodic basis) using an {@link Exporter}, most implementations of which have * periodic basis) using an {@link Exporter}, most implementations of which have
* optimizations for sending data to remote repositories. * optimizations for sending data to remote repositories.
* <p> * <p>
* If Spring Messaging is on the classpath a {@link MessageChannel} called * If Spring Messaging is on the classpath and a {@link MessageChannel} called
* "metricsChannel" is also created (unless one already exists) and all metric update * "metricsChannel" is also available, all metric update events are published additionally
* events are published additionally as messages on that channel. Additional analysis or * as messages on that channel. Additional analysis or actions can be taken by clients
* actions can be taken by clients subscribing to that channel. * subscribing to that channel.
* <p> * <p>
* In addition if Codahale's metrics library is on the classpath a {@link MetricRegistry} * In addition if Dropwizard's metrics library is on the classpath a
* will be created and wired up to the counter and gauge services in addition to the basic * {@link MetricRegistry} will be created and the default counter and gauge services will
* repository. Users can create Codahale metrics by prefixing their metric names with the * switch to using it instead of the default repository. Users can create "special"
* appropriate type (e.g. "histogram.*", "meter.*") and sending them to the standard * Dropwizard metrics by prefixing their metric names with the appropriate type (e.g.
* <code>GaugeService</code> or <code>CounterService</code>. * "histogram.*", "meter.*". "timer.*") and sending them to the <code>GaugeService</code>
* or <code>CounterService</code>.
* <p> * <p>
* By default all metric updates go to all {@link MetricWriter} instances in the * 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 * application context via a {@link MetricCopyExporter} firing every 5 seconds (disable
* "primaryMetricWriter", mark it <code>@Primary</code>, and this one will receive all * this by setting <code>spring.metrics.export.enabled=false</code>).
* 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.
* *
* @see GaugeService * @see GaugeService
* @see CounterService * @see CounterService
* @see MetricWriter * @see MetricWriter
* @see InMemoryMetricRepository * @see InMemoryMetricRepository
* @see DropwizardMetricWriter
* @see Exporter * @see Exporter
* *
* @author Dave Syer * @author Dave Syer
*/ */
@Configuration @Configuration
@EnableConfigurationProperties(MetricExportProperties.class)
public class MetricRepositoryAutoConfiguration { public class MetricRepositoryAutoConfiguration {
@Autowired @Configuration
private MetricWriter writer; @ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN)
@ConditionalOnMissingBean(GaugeService.class)
@Bean static class LegacyMetricServicesConfiguration {
@ConditionalOnMissingBean
public CounterService counterService() {
return new DefaultCounterService(this.writer);
}
@Bean @Autowired
@ConditionalOnMissingBean @ActuatorMetricRepository
public GaugeService gaugeService() { private MetricWriter writer;
return new DefaultGaugeService(this.writer);
}
@Configuration @Bean
@ConditionalOnMissingBean(MetricRepository.class) @ConditionalOnMissingBean
static class MetricRepositoryConfiguration { public CounterService counterService() {
return new DefaultCounterService(this.writer);
}
@Bean @Bean
public InMemoryMetricRepository actuatorMetricRepository() { @ConditionalOnMissingBean
return new InMemoryMetricRepository(); public GaugeService gaugeService() {
return new DefaultGaugeService(this.writer);
} }
} }
@Configuration @Configuration
@ConditionalOnClass(MessageChannel.class) @ConditionalOnJava(value = JavaVersion.EIGHT)
static class MetricsChannelConfiguration { @ConditionalOnMissingBean(GaugeService.class)
static class FastMetricServicesConfiguration {
@Autowired
@Qualifier("metricsExecutor")
private Executor executor;
@Bean @Bean
@ConditionalOnMissingBean(name = "metricsChannel") @ConditionalOnMissingBean
public SubscribableChannel metricsChannel() { public CounterBuffers counterBuffers() {
return new ExecutorSubscribableChannel(this.executor); return new CounterBuffers();
} }
@Bean @Bean
@ConditionalOnMissingBean(name = "metricsExecutor") @ConditionalOnMissingBean
public Executor metricsExecutor() { public GaugeBuffers gaugeBuffers() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); return new GaugeBuffers();
return executor;
} }
@Bean @Bean
@Primary @ActuatorMetricRepository
@ConditionalOnMissingBean(name = "primaryMetricWriter") @ConditionalOnMissingBean
public MetricWriter primaryMetricWriter( public BufferMetricReader actuatorMetricReader(CounterBuffers counters,
@Qualifier("metricsChannel") SubscribableChannel channel, GaugeBuffers gauges) {
List<MetricWriter> writers) { return new BufferMetricReader(counters, gauges);
final MetricWriter observer = new CompositeMetricWriter(writers);
channel.subscribe(new MetricWriterMessageHandler(observer));
return new MessageChannelMetricWriter(channel);
} }
}
@Configuration
@ConditionalOnClass(MetricRegistry.class)
static class DropwizardMetricRegistryConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public MetricRegistry metricRegistry() { public CounterService counterService(CounterBuffers writer) {
return new MetricRegistry(); return new BufferCounterService(writer);
} }
@Bean @Bean
public DropwizardMetricWriter dropwizardMetricWriter(MetricRegistry metricRegistry) { @ConditionalOnMissingBean
return new DropwizardMetricWriter(metricRegistry); public GaugeService gaugeService(GaugeBuffers writer) {
return new BufferGaugeService(writer);
} }
}
@Bean @Configuration
@Primary @ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN)
@ConditionalOnMissingClass(name = "org.springframework.messaging.MessageChannel") @ConditionalOnMissingBean(MetricRepository.class)
@ConditionalOnMissingBean(name = "primaryMetricWriter") static class LegacyMetricRepositoryConfiguration {
public MetricWriter primaryMetricWriter(List<MetricWriter> writers) {
return new CompositeMetricWriter(writers);
}
@Bean @Bean
public PublicMetrics dropwizardPublicMetrics(MetricRegistry metricRegistry) { @ActuatorMetricRepository
MetricRegistryMetricReader reader = new MetricRegistryMetricReader( public InMemoryMetricRepository actuatorMetricRepository() {
metricRegistry); return new InMemoryMetricRepository();
return new MetricReaderPublicMetrics(reader);
} }
} }
......
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.metrics.writer.MessageChannelMetricWriter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.MessageChannel;
/**
* {@link EnableAutoConfiguration Auto-configuration} for writing metrics to a
* {@link MessageChannel}.
*
* @author Dave Syer
*/
@Configuration
@ConditionalOnClass(MessageChannel.class)
@ConditionalOnBean(name = "metricsChannel")
@AutoConfigureBefore(MetricRepositoryAutoConfiguration.class)
public class MetricsChannelAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MessageChannelMetricWriter messageChannelMetricWriter(
@Qualifier("metricsChannel") MessageChannel channel) {
return new MessageChannelMetricWriter(channel);
}
}
\ No newline at end of file
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure;
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
import org.springframework.boot.actuate.endpoint.PublicMetrics;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.actuate.metrics.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);
}
}
\ No newline at end of file
...@@ -60,6 +60,7 @@ import org.springframework.context.annotation.Configuration; ...@@ -60,6 +60,7 @@ import org.springframework.context.annotation.Configuration;
public class PublicMetricsAutoConfiguration { public class PublicMetricsAutoConfiguration {
@Autowired(required = false) @Autowired(required = false)
@ActuatorMetricRepository
private MetricReader metricReader = new InMemoryMetricRepository(); private MetricReader metricReader = new InMemoryMetricRepository();
@Bean @Bean
......
...@@ -62,8 +62,13 @@ public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> { ...@@ -62,8 +62,13 @@ public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
public Map<String, Object> invoke() { public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>(); Map<String, Object> result = new LinkedHashMap<String, Object>();
for (PublicMetrics publicMetric : this.publicMetrics) { for (PublicMetrics publicMetric : this.publicMetrics) {
for (Metric<?> metric : publicMetric.metrics()) { try {
result.put(metric.getName(), metric.getValue()); for (Metric<?> metric : publicMetric.metrics()) {
result.put(metric.getName(), metric.getValue());
}
}
catch (Exception e) {
// Could not evaluate metrics
} }
} }
return result; return result;
......
...@@ -56,21 +56,21 @@ public class MetricsMvcEndpoint extends EndpointMvcAdapter { ...@@ -56,21 +56,21 @@ public class MetricsMvcEndpoint extends EndpointMvcAdapter {
/** /**
* {@link NamePatternFilter} for the Map source. * {@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); super(source);
} }
@Override @Override
protected void getNames(Map<String, Object> source, NameCallback callback) { protected void getNames(Map<String, ?> source, NameCallback callback) {
for (String name : source.keySet()) { for (String name : source.keySet()) {
callback.addName(name); callback.addName(name);
} }
} }
@Override @Override
protected Object getValue(Map<String, Object> source, String name) { protected Object getValue(Map<String, ?> source, String name) {
Object value = source.get(name); Object value = source.get(name);
if (value == null) { if (value == null) {
throw new NoSuchMetricException("No such metric: " + name); throw new NoSuchMetricException("No such metric: " + name);
......
/*
* 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();
}
}
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.lang.UsesJava8;
/**
* Fast implementation of {@link CounterService} using {@link CounterBuffers}.
*
* @author Dave Syer
*/
@UsesJava8
public class BufferCounterService implements CounterService {
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
private final CounterBuffers writer;
/**
* Create a {@link BufferCounterService} instance.
* @param writer the underlying writer used to manage metrics
*/
public BufferCounterService(CounterBuffers writer) {
this.writer = writer;
}
@Override
public void increment(String metricName) {
this.writer.increment(wrap(metricName), 1L);
}
@Override
public void decrement(String metricName) {
this.writer.increment(wrap(metricName), -1L);
}
@Override
public void reset(String metricName) {
this.writer.reset(wrap(metricName));
}
private String wrap(String metricName) {
if (this.names.containsKey(metricName)) {
return this.names.get(metricName);
}
if (metricName.startsWith("counter") || metricName.startsWith("meter")) {
return metricName;
}
String name = "counter." + metricName;
this.names.put(metricName, name);
return name;
}
}
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.lang.UsesJava8;
/**
* Fast implementation of {@link GaugeService} using {@link GaugeBuffers}.
*
* @author Dave Syer
*/
@UsesJava8
public class BufferGaugeService implements GaugeService {
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
private final GaugeBuffers writer;
/**
* Create a {@link BufferGaugeService} instance.
* @param writer the underlying writer used to manage metrics
*/
public BufferGaugeService(GaugeBuffers writer) {
this.writer = writer;
}
@Override
public void submit(String metricName, double value) {
this.writer.set(wrap(metricName), value);
}
private String wrap(String metricName) {
if (this.names.containsKey(metricName)) {
return this.names.get(metricName);
}
if (metricName.startsWith("gauge") || metricName.startsWith("histogram")
|| metricName.startsWith("timer")) {
return metricName;
}
String name = "gauge." + metricName;
this.names.put(metricName, name);
return name;
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.reader.PrefixMetricReader;
import org.springframework.lang.UsesJava8;
/**
* {@link MetricReader} implementation using {@link CounterBuffers} and
* {@link GaugeBuffers}.
*
* @author Dave Syer
*/
@UsesJava8
public class BufferMetricReader implements MetricReader, PrefixMetricReader {
private final CounterBuffers counters;
private final GaugeBuffers gauges;
private final Predicate<String> all = Pattern.compile(".*").asPredicate();
public BufferMetricReader(CounterBuffers counters, GaugeBuffers gauges) {
this.counters = counters;
this.gauges = gauges;
}
@Override
public Iterable<Metric<?>> findAll(String prefix) {
final List<Metric<?>> metrics = new ArrayList<Metric<?>>();
this.counters.forEach(Pattern.compile(prefix + ".*").asPredicate(),
new BiConsumer<String, LongBuffer>() {
@Override
public void accept(String name, LongBuffer value) {
metrics.add(new Metric<Long>(name, value.getValue(), new Date(
value.getTimestamp())));
}
});
this.gauges.forEach(Pattern.compile(prefix + ".*").asPredicate(),
new BiConsumer<String, DoubleBuffer>() {
@Override
public void accept(String name, DoubleBuffer value) {
metrics.add(new Metric<Double>(name, value.getValue(), new Date(
value.getTimestamp())));
}
});
return metrics;
}
@Override
public Metric<?> findOne(final String name) {
LongBuffer buffer = this.counters.find(name);
if (buffer != null) {
return new Metric<Long>(name, buffer.getValue(), new Date(
buffer.getTimestamp()));
}
DoubleBuffer doubleValue = this.gauges.find(name);
if (doubleValue != null) {
return new Metric<Double>(name, doubleValue.getValue(), new Date(
doubleValue.getTimestamp()));
}
return null;
}
@Override
public Iterable<Metric<?>> findAll() {
final List<Metric<?>> metrics = new ArrayList<Metric<?>>();
this.counters.forEach(this.all, new BiConsumer<String, LongBuffer>() {
@Override
public void accept(String name, LongBuffer value) {
metrics.add(new Metric<Long>(name, value.getValue(), new Date(value
.getTimestamp())));
}
});
this.gauges.forEach(this.all, new BiConsumer<String, DoubleBuffer>() {
@Override
public void accept(String name, DoubleBuffer value) {
metrics.add(new Metric<Double>(name, value.getValue(), new Date(value
.getTimestamp())));
}
});
return metrics;
}
@Override
public long count() {
return this.counters.count() + this.gauges.count();
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import org.springframework.lang.UsesJava8;
/**
* Fast writes to in-memory metrics store using {@link LongBuffer}.
*
* @author Dave Syer
*/
@UsesJava8
public class CounterBuffers {
private final ConcurrentHashMap<String, LongBuffer> metrics = new ConcurrentHashMap<String, LongBuffer>();
public void forEach(final Predicate<String> predicate,
final BiConsumer<String, LongBuffer> consumer) {
this.metrics.forEach(new BiConsumer<String, LongBuffer>() {
@Override
public void accept(String name, LongBuffer value) {
if (predicate.test(name)) {
consumer.accept(name, value);
}
}
});
}
public LongBuffer find(final String name) {
return this.metrics.get(name);
}
public void get(final String name, final Consumer<LongBuffer> consumer) {
read(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer adder) {
consumer.accept(adder);
}
});
}
public void increment(final String name, final long delta) {
write(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer adder) {
adder.add(delta);
}
});
}
public void reset(final String name) {
write(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer adder) {
adder.reset();
}
});
}
public int count() {
return this.metrics.size();
}
private void read(final String name, final Consumer<LongBuffer> consumer) {
acceptInternal(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer adder) {
consumer.accept(adder);
}
});
}
private void write(final String name, final Consumer<LongBuffer> consumer) {
acceptInternal(name, new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer buffer) {
buffer.setTimestamp(System.currentTimeMillis());
consumer.accept(buffer);
}
});
}
private void acceptInternal(final String name, final Consumer<LongBuffer> consumer) {
LongBuffer adder;
if (null == (adder = this.metrics.get(name))) {
adder = this.metrics.computeIfAbsent(name,
new Function<String, LongBuffer>() {
@Override
public LongBuffer apply(String name) {
return new LongBuffer(0L);
}
});
}
consumer.accept(adder);
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
/**
* Mutable buffer containing a double value and a timestamp.
*
* @author Dave Syer
*/
public class DoubleBuffer {
private volatile double value;
private volatile long timestamp;
public DoubleBuffer(long timestamp) {
this.value = 0;
this.timestamp = timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public double getValue() {
return this.value;
}
public void setValue(double value) {
this.value = value;
}
public long getTimestamp() {
return this.timestamp;
}
}
\ No newline at end of file
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import org.springframework.lang.UsesJava8;
/**
* Fast writes to in-memory metrics store using {@link DoubleBuffer}.
*
* @author Dave Syer
*/
@UsesJava8
public class GaugeBuffers {
private final ConcurrentHashMap<String, DoubleBuffer> metrics = new ConcurrentHashMap<String, DoubleBuffer>();
public void forEach(final Predicate<String> predicate,
final BiConsumer<String, DoubleBuffer> consumer) {
this.metrics.forEach(new BiConsumer<String, DoubleBuffer>() {
@Override
public void accept(String name, DoubleBuffer value) {
if (predicate.test(name)) {
consumer.accept(name, value);
}
}
});
}
public DoubleBuffer find(final String name) {
return this.metrics.get(name);
}
public void get(final String name, final Consumer<DoubleBuffer> consumer) {
acceptInternal(name, new Consumer<DoubleBuffer>() {
@Override
public void accept(DoubleBuffer value) {
consumer.accept(value);
}
});
}
public void set(final String name, final double value) {
write(name, value);
}
public int count() {
return this.metrics.size();
}
private void write(final String name, final double value) {
acceptInternal(name, new Consumer<DoubleBuffer>() {
@Override
public void accept(DoubleBuffer buffer) {
buffer.setTimestamp(System.currentTimeMillis());
buffer.setValue(value);
}
});
}
public void reset(String name) {
this.metrics.remove(name, this.metrics.get(name));
}
private void acceptInternal(final String name, final Consumer<DoubleBuffer> consumer) {
DoubleBuffer value;
if (null == (value = this.metrics.get(name))) {
value = this.metrics.computeIfAbsent(name,
new Function<String, DoubleBuffer>() {
@Override
public DoubleBuffer apply(String tag) {
return new DoubleBuffer(0L);
}
});
}
consumer.accept(value);
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.util.concurrent.atomic.LongAdder;
import org.springframework.lang.UsesJava8;
/**
* Mutable buffer containing a long adder (Java 8) and a timestamp.
*
* @author Dave Syer
*/
@UsesJava8
public class LongBuffer {
private final LongAdder adder;
private volatile long timestamp;
public LongBuffer(long timestamp) {
this.adder = new LongAdder();
this.timestamp = timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public long getValue() {
return this.adder.sum();
}
public long getTimestamp() {
return this.timestamp;
}
public void reset() {
this.adder.reset();
}
public void add(long delta) {
this.adder.add(delta);
}
}
\ No newline at end of file
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Metrics buffering support.
*/
package org.springframework.boot.actuate.metrics.buffer;
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -14,13 +14,14 @@ ...@@ -14,13 +14,14 @@
* limitations under the License. * 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.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; 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.Counter;
import com.codahale.metrics.Gauge; import com.codahale.metrics.Gauge;
...@@ -30,77 +31,107 @@ import com.codahale.metrics.MetricRegistry; ...@@ -30,77 +31,107 @@ import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer; import com.codahale.metrics.Timer;
/** /**
* A {@link MetricWriter} that send data to a Codahale {@link MetricRegistry} based on a * A {@link GaugeService} and {@link CounterService} that sends data to a Dropwizard
* naming convention: * {@link MetricRegistry} based on a naming convention:
* *
* <ul> * <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> * {@link Meter} events</li>
* <li>Other deltas are treated as simple {@link Counter} values</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 * <li>Inputs to {@link #submit(String, double)} with names in "histogram.*" are treated
* {@link Histogram} updates</li> * as {@link Histogram} updates</li>
* <li>Inputs to {@link #set(Metric)} with names in "timer.*" are treated as {@link Timer} * <li>Inputs to {@link #submit(String, double)} with names in "timer.*" are treated as
* updates</li> * {@link Timer} updates</li>
* <li>Other metrics are treated as simple {@link Gauge} values (single valued * <li>Other metrics are treated as simple {@link Gauge} values (single valued
* measurements of type double)</li> * measurements of type double)</li>
* </ul> * </ul>
* *
* @author Dave Syer * @author Dave Syer
*/ */
public class DropwizardMetricWriter implements MetricWriter { public class DropwizardMetricServices implements CounterService, GaugeService {
private final MetricRegistry registry; private final MetricRegistry registry;
private final ConcurrentMap<String, Object> gaugeLocks = new ConcurrentHashMap<String, Object>(); 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 * @param registry the underlying metric registry
*/ */
public DropwizardMetricWriter(MetricRegistry registry) { public DropwizardMetricServices(MetricRegistry registry) {
this.registry = registry; this.registry = registry;
} }
@Override @Override
public void increment(Delta<?> delta) { public void increment(String name) {
String name = delta.getName(); incrementInternal(name, 1L);
long value = delta.getValue().longValue(); }
@Override
public void decrement(String name) {
incrementInternal(name, -1L);
}
private void incrementInternal(String name, long value) {
if (name.startsWith("meter")) { if (name.startsWith("meter")) {
Meter meter = this.registry.meter(name); Meter meter = this.registry.meter(name);
meter.mark(value); meter.mark(value);
} }
else { else {
name = wrapCounterName(name);
Counter counter = this.registry.counter(name); Counter counter = this.registry.counter(name);
counter.inc(value); counter.inc(value);
} }
} }
@Override @Override
public void set(Metric<?> value) { public void submit(String name, double value) {
String name = value.getName();
if (name.startsWith("histogram")) { if (name.startsWith("histogram")) {
long longValue = value.getValue().longValue(); long longValue = (long) value;
Histogram metric = this.registry.histogram(name); Histogram metric = this.registry.histogram(name);
metric.update(longValue); metric.update(longValue);
} }
else if (name.startsWith("timer")) { else if (name.startsWith("timer")) {
long longValue = value.getValue().longValue(); long longValue = (long) value;
Timer metric = this.registry.timer(name); Timer metric = this.registry.timer(name);
metric.update(longValue, TimeUnit.MILLISECONDS); metric.update(longValue, TimeUnit.MILLISECONDS);
} }
else { 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 // Ensure we synchronize to avoid another thread pre-empting this thread after
// remove causing an error in CodaHale metrics // remove causing an error in Dropwizard metrics
// NOTE: CodaHale provides no way to do this atomically // NOTE: Dropwizard provides no way to do this atomically
synchronized (getGuageLock(name)) { synchronized (getGaugeLock(name)) {
this.registry.remove(name); this.registry.remove(name);
this.registry.register(name, new SimpleGauge(gauge)); 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); Object lock = this.gaugeLocks.get(name);
if (lock == null) { if (lock == null) {
Object newLock = new Object(); Object newLock = new Object();
...@@ -111,8 +142,11 @@ public class DropwizardMetricWriter implements MetricWriter { ...@@ -111,8 +142,11 @@ public class DropwizardMetricWriter implements MetricWriter {
} }
@Override @Override
public void reset(String metricName) { public void reset(String name) {
this.registry.remove(metricName); if (!name.startsWith("meter")) {
name = wrapCounterName(name);
}
this.registry.remove(name);
} }
/** /**
......
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Metrics integration with Dropwizard Metrics.
*/
package org.springframework.boot.actuate.metrics.dropwizard;
...@@ -22,6 +22,8 @@ import java.util.Collections; ...@@ -22,6 +22,8 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.concurrent.atomic.AtomicBoolean; 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.boot.actuate.metrics.Metric;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -34,6 +36,8 @@ import org.springframework.util.StringUtils; ...@@ -34,6 +36,8 @@ import org.springframework.util.StringUtils;
*/ */
public abstract class AbstractMetricExporter implements Exporter { public abstract class AbstractMetricExporter implements Exporter {
private static final Log logger = LogFactory.getLog(AbstractMetricExporter.class);
private volatile AtomicBoolean processing = new AtomicBoolean(false); private volatile AtomicBoolean processing = new AtomicBoolean(false);
private Date earliestTimestamp = new Date(); private Date earliestTimestamp = new Date();
...@@ -42,6 +46,10 @@ public abstract class AbstractMetricExporter implements Exporter { ...@@ -42,6 +46,10 @@ public abstract class AbstractMetricExporter implements Exporter {
private final String prefix; private final String prefix;
private Date latestTimestamp = new Date(0L);
private boolean sendLatest = true;
public AbstractMetricExporter(String prefix) { public AbstractMetricExporter(String prefix) {
this.prefix = !StringUtils.hasText(prefix) ? "" : (prefix.endsWith(".") ? prefix this.prefix = !StringUtils.hasText(prefix) ? "" : (prefix.endsWith(".") ? prefix
: prefix + "."); : prefix + ".");
...@@ -63,13 +71,24 @@ public abstract class AbstractMetricExporter implements Exporter { ...@@ -63,13 +71,24 @@ public abstract class AbstractMetricExporter implements Exporter {
this.ignoreTimestamps = ignoreTimestamps; 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 @Override
public void export() { public void export() {
if (!this.processing.compareAndSet(false, true)) { if (!this.processing.compareAndSet(false, true)) {
// skip a tick // skip a tick
return; return;
} }
long latestTimestamp = 0;
try { try {
latestTimestamp = System.currentTimeMillis();
for (String group : groups()) { for (String group : groups()) {
Collection<Metric<?>> values = new ArrayList<Metric<?>>(); Collection<Metric<?>> values = new ArrayList<Metric<?>>();
for (Metric<?> metric : next(group)) { for (Metric<?> metric : next(group)) {
...@@ -79,6 +98,10 @@ public abstract class AbstractMetricExporter implements Exporter { ...@@ -79,6 +98,10 @@ public abstract class AbstractMetricExporter implements Exporter {
if (!this.ignoreTimestamps && this.earliestTimestamp.after(timestamp)) { if (!this.ignoreTimestamps && this.earliestTimestamp.after(timestamp)) {
continue; continue;
} }
if (!this.ignoreTimestamps && this.sendLatest
&& this.latestTimestamp.after(timestamp)) {
continue;
}
values.add(value); values.add(value);
} }
if (!values.isEmpty()) { if (!values.isEmpty()) {
...@@ -86,11 +109,26 @@ public abstract class AbstractMetricExporter implements Exporter { ...@@ -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 { 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); 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. * 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 * prefixes). If the metrics to be exported partition into groups identified by a
......
...@@ -20,7 +20,7 @@ package org.springframework.boot.actuate.metrics.export; ...@@ -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 * 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 * 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 * 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 * 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. * it using a {@code @Scheduled} annotation in a Spring ApplicationContext.
* *
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -17,10 +17,13 @@ ...@@ -17,10 +17,13 @@
package org.springframework.boot.actuate.metrics.export; package org.springframework.boot.actuate.metrics.export;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.writer.MetricWriter; 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 * {@link Exporter} that "exports" by copying metric data from a source
...@@ -34,10 +37,26 @@ public class MetricCopyExporter extends AbstractMetricExporter { ...@@ -34,10 +37,26 @@ public class MetricCopyExporter extends AbstractMetricExporter {
private final MetricWriter writer; private final MetricWriter writer;
private String[] includes = new String[0];
private String[] excludes = new String[0];
public MetricCopyExporter(MetricReader reader, MetricWriter writer) { public MetricCopyExporter(MetricReader reader, MetricWriter writer) {
this(reader, 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) { public MetricCopyExporter(MetricReader reader, MetricWriter writer, String prefix) {
super(prefix); super(prefix);
this.reader = reader; this.reader = reader;
...@@ -46,7 +65,17 @@ public class MetricCopyExporter extends AbstractMetricExporter { ...@@ -46,7 +65,17 @@ public class MetricCopyExporter extends AbstractMetricExporter {
@Override @Override
protected Iterable<Metric<?>> next(String group) { 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 @Override
...@@ -56,4 +85,64 @@ public class MetricCopyExporter extends AbstractMetricExporter { ...@@ -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;
}
};
} }
/*
* 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;
}
}
/*
* 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()));
}
}
}
}
/*
* 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);
}
}
/*
* 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);
}
}
}
/*
* 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;
/*
* 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;
}
}
/*
* 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);
}
}
/*
* 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));
}
}
\ No newline at end of file
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.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);
}
}
/*
* 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);
}
/*
* 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;
...@@ -29,6 +29,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; ...@@ -29,6 +29,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.BoundZSetOperations; import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* A {@link MetricRepository} implementation for a redis backend. Metric values are stored * A {@link MetricRepository} implementation for a redis backend. Metric values are stored
...@@ -92,6 +93,8 @@ public class RedisMetricRepository implements MetricRepository { ...@@ -92,6 +93,8 @@ public class RedisMetricRepository implements MetricRepository {
public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory, public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,
String prefix, String key) { String prefix, String key) {
Assert.notNull(redisConnectionFactory, "RedisConnectionFactory must not be null"); 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); this.redisOperations = RedisUtils.stringTemplate(redisConnectionFactory);
if (!prefix.endsWith(".")) { if (!prefix.endsWith(".")) {
prefix = prefix + "."; prefix = prefix + ".";
......
/*
* 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());
}
}
}
/*
* 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;
...@@ -48,13 +48,8 @@ public class SimpleInMemoryRepository<T> { ...@@ -48,13 +48,8 @@ public class SimpleInMemoryRepository<T> {
synchronized (lock) { synchronized (lock) {
T current = this.values.get(name); T current = this.values.get(name);
T value = callback.modify(current); T value = callback.modify(current);
if (current != null) { this.values.put(name, value);
this.values.replace(name, current, value); return value;
}
else {
this.values.putIfAbsent(name, value);
}
return this.values.get(name);
} }
} }
......
...@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.metrics.writer; ...@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.metrics.writer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.Metric;
...@@ -28,7 +29,7 @@ import org.springframework.boot.actuate.metrics.Metric; ...@@ -28,7 +29,7 @@ import org.springframework.boot.actuate.metrics.Metric;
* *
* @author Dave Syer * @author Dave Syer
*/ */
public class CompositeMetricWriter implements MetricWriter { public class CompositeMetricWriter implements MetricWriter, Iterable<MetricWriter> {
private final List<MetricWriter> writers = new ArrayList<MetricWriter>(); private final List<MetricWriter> writers = new ArrayList<MetricWriter>();
...@@ -40,6 +41,11 @@ public class CompositeMetricWriter implements MetricWriter { ...@@ -40,6 +41,11 @@ public class CompositeMetricWriter implements MetricWriter {
this.writers.addAll(writers); this.writers.addAll(writers);
} }
@Override
public Iterator<MetricWriter> iterator() {
return this.writers.iterator();
}
@Override @Override
public void increment(Delta<?> delta) { public void increment(Delta<?> delta) {
for (MetricWriter writer : this.writers) { for (MetricWriter writer : this.writers) {
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.actuate.metrics.writer; package org.springframework.boot.actuate.metrics.writer;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.boot.actuate.metrics.CounterService; import org.springframework.boot.actuate.metrics.CounterService;
/** /**
...@@ -27,6 +29,8 @@ public class DefaultCounterService implements CounterService { ...@@ -27,6 +29,8 @@ public class DefaultCounterService implements CounterService {
private final MetricWriter writer; private final MetricWriter writer;
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
/** /**
* Create a {@link DefaultCounterService} instance. * Create a {@link DefaultCounterService} instance.
* @param writer the underlying writer used to manage metrics * @param writer the underlying writer used to manage metrics
...@@ -51,10 +55,14 @@ public class DefaultCounterService implements CounterService { ...@@ -51,10 +55,14 @@ public class DefaultCounterService implements CounterService {
} }
private String wrap(String metricName) { private String wrap(String metricName) {
if (this.names.containsKey(metricName)) {
return this.names.get(metricName);
}
if (metricName.startsWith("counter") || metricName.startsWith("meter")) { if (metricName.startsWith("counter") || metricName.startsWith("meter")) {
return metricName; return metricName;
} }
return "counter." + metricName; String name = "counter." + metricName;
this.names.put(metricName, name);
return name;
} }
} }
/* /*
* Copyright 2012-2014 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.actuate.metrics.writer; 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.GaugeService;
import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.Metric;
...@@ -28,6 +30,8 @@ public class DefaultGaugeService implements GaugeService { ...@@ -28,6 +30,8 @@ public class DefaultGaugeService implements GaugeService {
private final MetricWriter writer; private final MetricWriter writer;
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
/** /**
* Create a {@link DefaultGaugeService} instance. * Create a {@link DefaultGaugeService} instance.
* @param writer the underlying writer used to manage metrics * @param writer the underlying writer used to manage metrics
...@@ -42,11 +46,15 @@ public class DefaultGaugeService implements GaugeService { ...@@ -42,11 +46,15 @@ public class DefaultGaugeService implements GaugeService {
} }
private String wrap(String metricName) { private String wrap(String metricName) {
if (this.names.containsKey(metricName)) {
return this.names.get(metricName);
}
if (metricName.startsWith("gauge") || metricName.startsWith("histogram") if (metricName.startsWith("gauge") || metricName.startsWith("histogram")
|| metricName.startsWith("timer")) { || metricName.startsWith("timer")) {
return metricName; return metricName;
} }
return "gauge." + metricName; String name = "gauge." + metricName;
this.names.put(metricName, name);
return name;
} }
} }
...@@ -16,43 +16,44 @@ ...@@ -16,43 +16,44 @@
package org.springframework.boot.actuate.metrics.writer; package org.springframework.boot.actuate.metrics.writer;
import org.springframework.boot.actuate.metrics.Metric; import java.io.Flushable;
import java.lang.reflect.Method;
import com.codahale.metrics.Counter; import org.apache.commons.logging.Log;
import com.codahale.metrics.Gauge; import org.apache.commons.logging.LogFactory;
import com.codahale.metrics.Histogram; import org.springframework.boot.actuate.metrics.export.MetricCopyExporter;
import com.codahale.metrics.Meter; import org.springframework.util.ClassUtils;
import com.codahale.metrics.MetricRegistry; import org.springframework.util.ReflectionUtils;
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 * @author Dave Syer
* @deprecated since 1.2.2 in favor of {@link DropwizardMetricWriter}
*/ */
@Deprecated public class WriterUtils {
public class CodahaleMetricWriter extends DropwizardMetricWriter {
private static final Log logger = LogFactory.getLog(MetricCopyExporter.class);
/** public static void flush(MetricWriter writer) {
* Create a new {@link DropwizardMetricWriter} instance. if (writer instanceof CompositeMetricWriter) {
* @param registry the underlying metric registry for (MetricWriter element : (CompositeMetricWriter) writer) {
*/ flush(element);
public CodahaleMetricWriter(MetricRegistry registry) { }
super(registry); }
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());
}
} }
} }
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
/** /**
* Metrics integration with Dropwizard Metrics. * Support for writing metrics
*/ */
package org.springframework.boot.actuate.metrics.writer; package org.springframework.boot.actuate.metrics.writer;
...@@ -11,6 +11,9 @@ org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfigurati ...@@ -11,6 +11,9 @@ org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfigurati
org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration,\ 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.PublicMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfigurationTests;
import org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfigurationTests;
/**
* A test suite for probing weird ordering problems in the tests.
*
* @author Dave Syer
*/
@RunWith(Suite.class)
@SuiteClasses({ PublicMetricsAutoConfigurationTests.class,
MetricRepositoryAutoConfigurationTests.class })
@Ignore
public class AdhocTestSuite {
}
/* /*
* Copyright 2012-2013 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,22 +16,31 @@ ...@@ -16,22 +16,31 @@
package org.springframework.boot.actuate.autoconfigure; package org.springframework.boot.actuate.autoconfigure;
import java.util.concurrent.Executor; import org.junit.After;
import org.junit.Test; 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.CounterService;
import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.actuate.metrics.Metric; 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.reader.MetricReader;
import org.springframework.boot.actuate.metrics.writer.DefaultCounterService; import org.springframework.boot.actuate.metrics.reader.PrefixMetricReader;
import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService;
import org.springframework.boot.actuate.metrics.writer.MetricWriter; 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.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor; import org.springframework.integration.channel.FixedSubscriberChannel;
import org.springframework.messaging.support.ExecutorSubscribableChannel; import org.springframework.messaging.Message;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import com.codahale.metrics.Gauge; import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
...@@ -40,10 +49,7 @@ import static org.hamcrest.Matchers.equalTo; ...@@ -40,10 +49,7 @@ import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat; 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.mock;
import static org.mockito.Mockito.verify;
/** /**
* Tests for {@link MetricRepositoryAutoConfiguration}. * Tests for {@link MetricRepositoryAutoConfiguration}.
...@@ -53,76 +59,95 @@ import static org.mockito.Mockito.verify; ...@@ -53,76 +59,95 @@ import static org.mockito.Mockito.verify;
*/ */
public class MetricRepositoryAutoConfigurationTests { public class MetricRepositoryAutoConfigurationTests {
@Test private AnnotationConfigApplicationContext context;
public void defaultExecutor() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( @After
MetricRepositoryAutoConfiguration.class); public void after() {
ExecutorSubscribableChannel channel = context if (this.context != null) {
.getBean(ExecutorSubscribableChannel.class); this.context.close();
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) channel.getExecutor(); }
context.close();
assertTrue(executor.getThreadPoolExecutor().isShutdown());
} }
@Test @Test
public void createServices() throws Exception { public void createServices() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( this.context = new AnnotationConfigApplicationContext(
SyncTaskExecutorConfiguration.class,
MetricRepositoryAutoConfiguration.class); MetricRepositoryAutoConfiguration.class);
DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class); GaugeService gaugeService = this.context.getBean(BufferGaugeService.class);
assertNotNull(gaugeService); assertNotNull(gaugeService);
assertNotNull(context.getBean(DefaultCounterService.class)); assertNotNull(this.context.getBean(BufferCounterService.class));
assertNotNull(this.context.getBean(PrefixMetricReader.class));
gaugeService.submit("foo", 2.7); 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()); .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 @Test
public void provideAdditionalWriter() { public void provideAdditionalWriter() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( this.context = new AnnotationConfigApplicationContext(WriterConfig.class,
SyncTaskExecutorConfiguration.class, WriterConfig.class, MetricRepositoryAutoConfiguration.class,
MetricRepositoryAutoConfiguration.class); MetricExportAutoConfiguration.class,
DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class); PropertyPlaceholderAutoConfiguration.class);
GaugeService gaugeService = this.context.getBean(GaugeService.class);
assertNotNull(gaugeService); assertNotNull(gaugeService);
gaugeService.submit("foo", 2.7); gaugeService.submit("foo", 2.7);
MetricWriter writer = context.getBean("writer", MetricWriter.class); MetricExporters exporters = this.context.getBean(MetricExporters.class);
verify(writer).set(any(Metric.class)); MetricCopyExporter exporter = (MetricCopyExporter) exporters.getExporters().get(
context.close(); "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 @Test
public void codahaleInstalledIfPresent() { public void dropwizardInstalledIfPresent() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( this.context = new AnnotationConfigApplicationContext(
SyncTaskExecutorConfiguration.class, WriterConfig.class, MetricsDropwizardAutoConfiguration.class,
MetricRepositoryAutoConfiguration.class); MetricRepositoryAutoConfiguration.class, AopAutoConfiguration.class);
DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class); GaugeService gaugeService = this.context.getBean(GaugeService.class);
assertNotNull(gaugeService); assertNotNull(gaugeService);
gaugeService.submit("foo", 2.7); 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") @SuppressWarnings("unchecked")
Gauge<Double> gauge = (Gauge<Double>) registry.getMetrics().get("gauge.foo"); Gauge<Double> gauge = (Gauge<Double>) registry.getMetrics().get("gauge.foo");
assertEquals(new Double(2.7), gauge.getValue()); assertEquals(new Double(2.7), gauge.getValue());
context.close();
} }
@Test @Test
public void skipsIfBeansExist() throws Exception { public void skipsIfBeansExist() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( this.context = new AnnotationConfigApplicationContext(Config.class,
Config.class, MetricRepositoryAutoConfiguration.class); MetricRepositoryAutoConfiguration.class);
assertThat(context.getBeansOfType(DefaultGaugeService.class).size(), equalTo(0)); assertThat(this.context.getBeansOfType(BufferGaugeService.class).size(),
assertThat(context.getBeansOfType(DefaultCounterService.class).size(), equalTo(0)); equalTo(0));
context.close(); assertThat(this.context.getBeansOfType(BufferCounterService.class).size(),
equalTo(0));
} }
@Configuration @Configuration
public static class SyncTaskExecutorConfiguration { public static class MessageChannelConfiguration {
@Bean @Bean
public Executor metricsExecutor() { public SubscribableChannel metricsChannel() {
return new SyncTaskExecutor(); return new FixedSubscriberChannel(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
}
});
} }
} }
@Configuration @Configuration
...@@ -130,7 +155,7 @@ public class MetricRepositoryAutoConfigurationTests { ...@@ -130,7 +155,7 @@ public class MetricRepositoryAutoConfigurationTests {
@Bean @Bean
public MetricWriter writer() { public MetricWriter writer() {
return mock(MetricWriter.class); return Mockito.mock(MetricWriter.class);
} }
} }
......
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.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());
}
}
/*
* 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();
}
}
/*
* 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());
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.util.function.Consumer;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Tests for {@link CounterBuffers}.
*
* @author Dave Syer
*/
public class CounterBuffersTests {
private CounterBuffers buffers = new CounterBuffers();
private long value;
@Test
public void inAndOut() {
this.buffers.increment("foo", 2);
this.buffers.get("foo", new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer buffer) {
CounterBuffersTests.this.value = buffer.getValue();
}
});
assertEquals(2, this.value);
}
@Test
public void getNonExistent() {
this.buffers.get("foo", new Consumer<LongBuffer>() {
@Override
public void accept(LongBuffer buffer) {
CounterBuffersTests.this.value = buffer.getValue();
}
});
assertEquals(0, this.value);
}
@Test
public void findNonExistent() {
assertNull(this.buffers.find("foo"));
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.lang.UsesJava8;
import org.springframework.util.StopWatch;
import static org.junit.Assert.assertEquals;
/**
* Speed tests for {@link CounterService}.
* @author Dave Syer
*/
@RunWith(Theories.class)
@UsesJava8
public class CounterServiceSpeedTests {
@DataPoints
public static String[] values = new String[10];
public static String[] names = new String[] { "foo", "bar", "spam", "bucket" };
public static String[] sample = new String[1000];
private CounterBuffers counters = new CounterBuffers();
private CounterService service = new BufferCounterService(this.counters);
private BufferMetricReader reader = new BufferMetricReader(this.counters,
new GaugeBuffers());
private static int threadCount = 2;
private static final int number = Boolean.getBoolean("performance.test") ? 10000000
: 100000;
private static StopWatch watch = new StopWatch("count");
private static int count;
private static PrintWriter err;
@BeforeClass
public static void prime() throws FileNotFoundException {
err = new PrintWriter("/dev/null");
final Random random = new Random();
for (int i = 0; i < 1000; i++) {
sample[i] = names[random.nextInt(names.length)];
}
}
@AfterClass
public static void washup() {
System.err.println(watch);
}
@Theory
public void raw(String input) throws Exception {
iterate("writeRaw");
double rate = number / watch.getLastTaskTimeMillis() * 1000;
System.err.println("Rate(" + count + ")=" + rate + ", " + watch);
watch.start("readRaw" + count);
for (String name : names) {
this.counters.forEach(Pattern.compile(name).asPredicate(),
new BiConsumer<String, LongBuffer>() {
@Override
public void accept(String name, LongBuffer value) {
err.println(name + "=" + value);
}
});
}
final LongAdder total = new LongAdder();
this.counters.forEach(Pattern.compile(".*").asPredicate(),
new BiConsumer<String, LongBuffer>() {
@Override
public void accept(String name, LongBuffer value) {
total.add(value.getValue());
}
});
watch.stop();
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
assertEquals(number * threadCount, total.longValue());
}
@Theory
public void reader(String input) throws Exception {
iterate("writeReader");
double rate = number / watch.getLastTaskTimeMillis() * 1000;
System.err.println("Rate(" + count + ")=" + rate + ", " + watch);
watch.start("readReader" + count);
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@Override
public void accept(Metric<?> metric) {
err.println(metric);
}
});
final LongAdder total = new LongAdder();
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@Override
public void accept(Metric<?> value) {
total.add(value.getValue().intValue());
}
});
watch.stop();
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
assertEquals(number * threadCount, total.longValue());
}
private void iterate(String taskName) throws Exception {
watch.start(taskName + count++);
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
Runnable task = new Runnable() {
@Override
public void run() {
for (int i = 0; i < number; i++) {
String name = sample[i % sample.length];
CounterServiceSpeedTests.this.service.increment(name);
}
}
};
Collection<Future<?>> futures = new HashSet<Future<?>>();
for (int i = 0; i < threadCount; i++) {
futures.add(pool.submit(task));
}
for (Future<?> future : futures) {
future.get();
}
watch.stop();
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import org.junit.BeforeClass;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
import org.springframework.boot.actuate.metrics.writer.DefaultCounterService;
import org.springframework.lang.UsesJava8;
import org.springframework.util.StopWatch;
import static org.junit.Assert.assertEquals;
/**
* Speed tests for {@link DefaultCounterService}.
*
* @author Dave Syer
*/
@RunWith(Theories.class)
@UsesJava8
public class DefaultCounterServiceSpeedTests {
@DataPoints
public static String[] values = new String[10];
public static String[] names = new String[] { "foo", "bar", "spam", "bucket" };
public static String[] sample = new String[1000];
private InMemoryMetricRepository repository = new InMemoryMetricRepository();
private CounterService counterService = new DefaultCounterService(this.repository);
private MetricReader reader = this.repository;
private static int threadCount = 2;
private static final int number = Boolean.getBoolean("performance.test") ? 2000000
: 100000;
private static int count;
private static StopWatch watch = new StopWatch("count");
private static PrintWriter err;
@BeforeClass
public static void prime() throws FileNotFoundException {
err = new PrintWriter("/dev/null");
final Random random = new Random();
for (int i = 0; i < 1000; i++) {
sample[i] = names[random.nextInt(names.length)];
}
}
@Theory
public void counters(String input) throws Exception {
watch.start("counters" + count++);
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
Runnable task = new Runnable() {
@Override
public void run() {
for (int i = 0; i < number; i++) {
String name = sample[i % sample.length];
DefaultCounterServiceSpeedTests.this.counterService.increment(name);
}
}
};
Collection<Future<?>> futures = new HashSet<Future<?>>();
for (int i = 0; i < threadCount; i++) {
futures.add(pool.submit(task));
}
for (Future<?> future : futures) {
future.get();
}
watch.stop();
double rate = number / watch.getLastTaskTimeMillis() * 1000;
System.err.println("Counters rate(" + count + ")=" + rate + ", " + watch);
watch.start("read" + count);
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@Override
public void accept(Metric<?> metric) {
err.println(metric);
}
});
final LongAdder total = new LongAdder();
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@Override
public void accept(Metric<?> value) {
total.add(value.getValue().intValue());
}
});
watch.stop();
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
assertEquals(number * threadCount, total.longValue());
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import org.junit.BeforeClass;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService;
import org.springframework.lang.UsesJava8;
import org.springframework.util.StopWatch;
import static org.junit.Assert.assertTrue;
/**
* Speed tests for {@link DefaultGaugeService}.
*
* @author Dave Syer
*/
@RunWith(Theories.class)
@UsesJava8
public class DefaultGaugeServiceSpeedTests {
@DataPoints
public static String[] values = new String[10];
public static String[] names = new String[] { "foo", "bar", "spam", "bucket" };
public static String[] sample = new String[1000];
private InMemoryMetricRepository repository = new InMemoryMetricRepository();
private GaugeService gaugeService = new DefaultGaugeService(this.repository);
private MetricReader reader = this.repository;
private static int threadCount = 2;
private static final int number = Boolean.getBoolean("performance.test") ? 5000000
: 100000;
private static int count;
private static StopWatch watch = new StopWatch("count");
private static PrintWriter err;
@BeforeClass
public static void prime() throws FileNotFoundException {
err = new PrintWriter("/dev/null");
final Random random = new Random();
for (int i = 0; i < 1000; i++) {
sample[i] = names[random.nextInt(names.length)];
}
}
@Theory
public void gauges(String input) throws Exception {
watch.start("gauges" + count++);
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
Runnable task = new Runnable() {
@Override
public void run() {
for (int i = 0; i < number; i++) {
String name = sample[i % sample.length];
DefaultGaugeServiceSpeedTests.this.gaugeService.submit(name, count
+ i);
}
}
};
Collection<Future<?>> futures = new HashSet<Future<?>>();
for (int i = 0; i < threadCount; i++) {
futures.add(pool.submit(task));
}
for (Future<?> future : futures) {
future.get();
}
watch.stop();
double rate = number / watch.getLastTaskTimeMillis() * 1000;
System.err.println("Gauges rate(" + count + ")=" + rate + ", " + watch);
watch.start("read" + count);
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@Override
public void accept(Metric<?> metric) {
err.println(metric);
}
});
final LongAdder total = new LongAdder();
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
@Override
public void accept(Metric<?> value) {
total.add(value.getValue().intValue());
}
});
watch.stop();
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
assertTrue(0 < total.longValue());
}
}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.metrics.buffer;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import org.junit.BeforeClass;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.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());
}
}
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -14,13 +14,13 @@ ...@@ -14,13 +14,13 @@
* limitations under the License. * 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.ArrayList;
import java.util.List; import java.util.List;
import org.junit.Test; 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.Gauge;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
...@@ -29,56 +29,60 @@ import static org.junit.Assert.assertEquals; ...@@ -29,56 +29,60 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
/** /**
* Tests for {@link DropwizardMetricWriter}. * Tests for {@link DropwizardMetricServices}.
* *
* @author Dave Syer * @author Dave Syer
*/ */
public class DropwizardMetricWriterTests { public class DropwizardMetricServicesTests {
private final MetricRegistry registry = new MetricRegistry(); private final MetricRegistry registry = new MetricRegistry();
private final DropwizardMetricWriter writer = new DropwizardMetricWriter(this.registry); private final DropwizardMetricServices writer = new DropwizardMetricServices(
this.registry);
@Test @Test
public void incrementCounter() { public void incrementCounter() {
this.writer.increment(new Delta<Number>("foo", 2)); this.writer.increment("foo");
this.writer.increment(new Delta<Number>("foo", 1)); this.writer.increment("foo");
assertEquals(3, this.registry.counter("foo").getCount()); this.writer.increment("foo");
assertEquals(3, this.registry.counter("counter.foo").getCount());
} }
@Test @Test
public void updatePredefinedMeter() { public void updatePredefinedMeter() {
this.writer.increment(new Delta<Number>("meter.foo", 2)); this.writer.increment("meter.foo");
this.writer.increment(new Delta<Number>("meter.foo", 1)); this.writer.increment("meter.foo");
this.writer.increment("meter.foo");
assertEquals(3, this.registry.meter("meter.foo").getCount()); assertEquals(3, this.registry.meter("meter.foo").getCount());
} }
@Test @Test
public void updatePredefinedCounter() { public void updatePredefinedCounter() {
this.writer.increment(new Delta<Number>("counter.foo", 2)); this.writer.increment("counter.foo");
this.writer.increment(new Delta<Number>("counter.foo", 1)); this.writer.increment("counter.foo");
this.writer.increment("counter.foo");
assertEquals(3, this.registry.counter("counter.foo").getCount()); assertEquals(3, this.registry.counter("counter.foo").getCount());
} }
@Test @Test
public void setGauge() { public void setGauge() {
this.writer.set(new Metric<Number>("foo", 2.1)); this.writer.submit("foo", 2.1);
this.writer.set(new Metric<Number>("foo", 2.3)); this.writer.submit("foo", 2.3);
@SuppressWarnings("unchecked") @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()); assertEquals(new Double(2.3), gauge.getValue());
} }
@Test @Test
public void setPredfinedTimer() { public void setPredfinedTimer() {
this.writer.set(new Metric<Number>("timer.foo", 200)); this.writer.submit("timer.foo", 200);
this.writer.set(new Metric<Number>("timer.foo", 300)); this.writer.submit("timer.foo", 300);
assertEquals(2, this.registry.timer("timer.foo").getCount()); assertEquals(2, this.registry.timer("timer.foo").getCount());
} }
@Test @Test
public void setPredfinedHistogram() { public void setPredfinedHistogram() {
this.writer.set(new Metric<Number>("histogram.foo", 2.1)); this.writer.submit("histogram.foo", 2.1);
this.writer.set(new Metric<Number>("histogram.foo", 2.3)); this.writer.submit("histogram.foo", 2.3);
assertEquals(2, this.registry.histogram("histogram.foo").getCount()); assertEquals(2, this.registry.histogram("histogram.foo").getCount());
} }
...@@ -112,9 +116,9 @@ public class DropwizardMetricWriterTests { ...@@ -112,9 +116,9 @@ public class DropwizardMetricWriterTests {
public static class WriterThread extends Thread { public static class WriterThread extends Thread {
private int index; private int index;
private boolean failed; 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); super(group, "Writer-" + index);
this.index = index; this.index = index;
...@@ -129,17 +133,9 @@ public class DropwizardMetricWriterTests { ...@@ -129,17 +133,9 @@ public class DropwizardMetricWriterTests {
public void run() { public void run() {
for (int i = 0; i < 10000; i++) { for (int i = 0; i < 10000; i++) {
try { try {
Metric<Integer> metric1 = new Metric<Integer>("timer.test.service", this.writer.submit("timer.test.service", this.index);
this.index); this.writer.submit("histogram.test.service", this.index);
this.writer.set(metric1); this.writer.submit("gauge.test.service", this.index);
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);
} }
catch (IllegalArgumentException iae) { catch (IllegalArgumentException iae) {
this.failed = true; this.failed = true;
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -41,6 +41,33 @@ public class MetricCopyExporterTests { ...@@ -41,6 +41,33 @@ public class MetricCopyExporterTests {
assertEquals(1, this.writer.count()); 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 @Test
public void timestamp() { public void timestamp() {
this.reader.set(new Metric<Number>("foo", 2.3)); this.reader.set(new Metric<Number>("foo", 2.3));
......
/*
* 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());
}
}
/*
* 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"));
}
}
/*
* 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);
}
}
/*
* 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);
}
}
}
...@@ -136,6 +136,7 @@ ...@@ -136,6 +136,7 @@
<spring-social-linkedin.version>1.0.1.RELEASE</spring-social-linkedin.version> <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-social-twitter.version>1.1.0.RELEASE</spring-social-twitter.version>
<spring-ws.version>2.2.1.RELEASE</spring-ws.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> <sun-mail.version>${javax-mail.version}</sun-mail.version>
<thymeleaf.version>2.1.4.RELEASE</thymeleaf.version> <thymeleaf.version>2.1.4.RELEASE</thymeleaf.version>
<thymeleaf-extras-springsecurity4.version>2.1.2.RELEASE</thymeleaf-extras-springsecurity4.version> <thymeleaf-extras-springsecurity4.version>2.1.2.RELEASE</thymeleaf-extras-springsecurity4.version>
...@@ -552,6 +553,11 @@ ...@@ -552,6 +553,11 @@
<artifactId>javax.mail</artifactId> <artifactId>javax.mail</artifactId>
<version>${sun-mail.version}</version> <version>${sun-mail.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.timgroup</groupId>
<artifactId>java-statsd-client</artifactId>
<version>${statsd-client.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.zaxxer</groupId> <groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId> <artifactId>HikariCP</artifactId>
......
...@@ -52,6 +52,9 @@ ...@@ -52,6 +52,9 @@
<module>spring-boot-sample-jta-bitronix</module> <module>spring-boot-sample-jta-bitronix</module>
<module>spring-boot-sample-jta-jndi</module> <module>spring-boot-sample-jta-jndi</module>
<module>spring-boot-sample-liquibase</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-parent-context</module>
<module>spring-boot-sample-profile</module> <module>spring-boot-sample-profile</module>
<module>spring-boot-sample-secure</module> <module>spring-boot-sample-secure</module>
......
opentsdb:
image: lancope/opentsdb
ports:
- "4242:4242"
service.name: Phil
metrics.names.tags.process: ${spring.application.name:application}:${random.value:0000}
\ No newline at end of file
This diff is collapsed.
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment