Merge branch 'feature/metrics'
This commit is contained in:
commit
a012538955
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
|
@ -61,6 +62,11 @@
|
|||
<artifactId>javax.mail</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.timgroup</groupId>
|
||||
<artifactId>java-statsd-client</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-core</artifactId>
|
||||
|
|
@ -214,6 +220,11 @@
|
|||
<artifactId>tomcat-embed-logging-juli</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.connectors.telnet</artifactId>
|
||||
|
|
@ -234,5 +245,10 @@
|
|||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/**
|
||||
* Qualifier annotation for a metric repository that is used by the actuator (to
|
||||
* distinguish it from others that might be installed by the user).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Qualifier
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
|
||||
ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface ActuatorMetricRepository {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.metrics.export.MetricExportProperties;
|
||||
import org.springframework.boot.actuate.metrics.export.MetricExporters;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@ConditionalOnProperty(value = "spring.metrics.export.enabled", matchIfMissing = true)
|
||||
public class MetricExportAutoConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Map<String, MetricWriter> writers = Collections.emptyMap();
|
||||
|
||||
@Autowired
|
||||
private MetricExportProperties metrics;
|
||||
|
||||
@Autowired(required = false)
|
||||
@ActuatorMetricRepository
|
||||
private MetricWriter actuatorMetricRepository;
|
||||
|
||||
@Autowired(required = false)
|
||||
@ActuatorMetricRepository
|
||||
private MetricReader reader;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SchedulingConfigurer metricWritersMetricExporter() {
|
||||
Map<String, MetricWriter> writers = new HashMap<String, MetricWriter>();
|
||||
if (this.reader != null) {
|
||||
writers.putAll(this.writers);
|
||||
if (this.actuatorMetricRepository != null
|
||||
&& writers.containsValue(this.actuatorMetricRepository)) {
|
||||
for (String name : this.writers.keySet()) {
|
||||
if (writers.get(name).equals(this.actuatorMetricRepository)) {
|
||||
writers.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
MetricExporters exporters = new MetricExporters(this.reader, writers,
|
||||
this.metrics);
|
||||
return exporters;
|
||||
}
|
||||
return new SchedulingConfigurer() {
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,37 +16,31 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
|
||||
import org.springframework.boot.actuate.endpoint.PublicMetrics;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferCounterService;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferGaugeService;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferMetricReader;
|
||||
import org.springframework.boot.actuate.metrics.buffer.CounterBuffers;
|
||||
import org.springframework.boot.actuate.metrics.buffer.GaugeBuffers;
|
||||
import org.springframework.boot.actuate.metrics.export.Exporter;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader;
|
||||
import org.springframework.boot.actuate.metrics.export.MetricCopyExporter;
|
||||
import org.springframework.boot.actuate.metrics.export.MetricExportProperties;
|
||||
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
|
||||
import org.springframework.boot.actuate.metrics.repository.MetricRepository;
|
||||
import org.springframework.boot.actuate.metrics.writer.CompositeMetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultCounterService;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService;
|
||||
import org.springframework.boot.actuate.metrics.writer.DropwizardMetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.writer.MessageChannelMetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriterMessageHandler;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.Range;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
|
|
@ -62,125 +56,106 @@ import com.codahale.metrics.MetricRegistry;
|
|||
* periodic basis) using an {@link Exporter}, most implementations of which have
|
||||
* optimizations for sending data to remote repositories.
|
||||
* <p>
|
||||
* If Spring Messaging is on the classpath a {@link MessageChannel} called
|
||||
* "metricsChannel" is also created (unless one already exists) and all metric update
|
||||
* events are published additionally as messages on that channel. Additional analysis or
|
||||
* actions can be taken by clients subscribing to that channel.
|
||||
* If Spring Messaging is on the classpath and a {@link MessageChannel} called
|
||||
* "metricsChannel" is also available, all metric update events are published additionally
|
||||
* as messages on that channel. Additional analysis or actions can be taken by clients
|
||||
* subscribing to that channel.
|
||||
* <p>
|
||||
* In addition if Codahale's metrics library is on the classpath a {@link MetricRegistry}
|
||||
* will be created and wired up to the counter and gauge services in addition to the basic
|
||||
* repository. Users can create Codahale metrics by prefixing their metric names with the
|
||||
* appropriate type (e.g. "histogram.*", "meter.*") and sending them to the standard
|
||||
* <code>GaugeService</code> or <code>CounterService</code>.
|
||||
* In addition if Dropwizard's metrics library is on the classpath a
|
||||
* {@link MetricRegistry} will be created and the default counter and gauge services will
|
||||
* switch to using it instead of the default repository. Users can create "special"
|
||||
* Dropwizard metrics by prefixing their metric names with the appropriate type (e.g.
|
||||
* "histogram.*", "meter.*". "timer.*") and sending them to the <code>GaugeService</code>
|
||||
* or <code>CounterService</code>.
|
||||
* <p>
|
||||
* By default all metric updates go to all {@link MetricWriter} instances in the
|
||||
* application context. To change this behaviour define your own metric writer bean called
|
||||
* "primaryMetricWriter", mark it <code>@Primary</code>, and this one will receive all
|
||||
* updates from the default counter and gauge services. Alternatively you can provide your
|
||||
* own counter and gauge services and wire them to whichever writer you choose.
|
||||
* application context via a {@link MetricCopyExporter} firing every 5 seconds (disable
|
||||
* this by setting <code>spring.metrics.export.enabled=false</code>).
|
||||
*
|
||||
* @see GaugeService
|
||||
* @see CounterService
|
||||
* @see MetricWriter
|
||||
* @see InMemoryMetricRepository
|
||||
* @see DropwizardMetricWriter
|
||||
* @see Exporter
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(MetricExportProperties.class)
|
||||
public class MetricRepositoryAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private MetricWriter writer;
|
||||
@Configuration
|
||||
@ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN)
|
||||
@ConditionalOnMissingBean(GaugeService.class)
|
||||
static class LegacyMetricServicesConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CounterService counterService() {
|
||||
return new DefaultCounterService(this.writer);
|
||||
}
|
||||
@Autowired
|
||||
@ActuatorMetricRepository
|
||||
private MetricWriter writer;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CounterService counterService() {
|
||||
return new DefaultCounterService(this.writer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GaugeService gaugeService() {
|
||||
return new DefaultGaugeService(this.writer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GaugeService gaugeService() {
|
||||
return new DefaultGaugeService(this.writer);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(MetricRepository.class)
|
||||
static class MetricRepositoryConfiguration {
|
||||
@ConditionalOnJava(value = JavaVersion.EIGHT)
|
||||
@ConditionalOnMissingBean(GaugeService.class)
|
||||
static class FastMetricServicesConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CounterBuffers counterBuffers() {
|
||||
return new CounterBuffers();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GaugeBuffers gaugeBuffers() {
|
||||
return new GaugeBuffers();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ActuatorMetricRepository
|
||||
@ConditionalOnMissingBean
|
||||
public BufferMetricReader actuatorMetricReader(CounterBuffers counters,
|
||||
GaugeBuffers gauges) {
|
||||
return new BufferMetricReader(counters, gauges);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CounterService counterService(CounterBuffers writer) {
|
||||
return new BufferCounterService(writer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GaugeService gaugeService(GaugeBuffers writer) {
|
||||
return new BufferGaugeService(writer);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN)
|
||||
@ConditionalOnMissingBean(MetricRepository.class)
|
||||
static class LegacyMetricRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
@ActuatorMetricRepository
|
||||
public InMemoryMetricRepository actuatorMetricRepository() {
|
||||
return new InMemoryMetricRepository();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(MessageChannel.class)
|
||||
static class MetricsChannelConfiguration {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("metricsExecutor")
|
||||
private Executor executor;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "metricsChannel")
|
||||
public SubscribableChannel metricsChannel() {
|
||||
return new ExecutorSubscribableChannel(this.executor);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "metricsExecutor")
|
||||
public Executor metricsExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnMissingBean(name = "primaryMetricWriter")
|
||||
public MetricWriter primaryMetricWriter(
|
||||
@Qualifier("metricsChannel") SubscribableChannel channel,
|
||||
List<MetricWriter> writers) {
|
||||
final MetricWriter observer = new CompositeMetricWriter(writers);
|
||||
channel.subscribe(new MetricWriterMessageHandler(observer));
|
||||
return new MessageChannelMetricWriter(channel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(MetricRegistry.class)
|
||||
static class DropwizardMetricRegistryConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MetricRegistry metricRegistry() {
|
||||
return new MetricRegistry();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DropwizardMetricWriter dropwizardMetricWriter(MetricRegistry metricRegistry) {
|
||||
return new DropwizardMetricWriter(metricRegistry);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnMissingClass(name = "org.springframework.messaging.MessageChannel")
|
||||
@ConditionalOnMissingBean(name = "primaryMetricWriter")
|
||||
public MetricWriter primaryMetricWriter(List<MetricWriter> writers) {
|
||||
return new CompositeMetricWriter(writers);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PublicMetrics dropwizardPublicMetrics(MetricRegistry metricRegistry) {
|
||||
MetricRegistryMetricReader reader = new MetricRegistryMetricReader(
|
||||
metricRegistry);
|
||||
return new MetricReaderPublicMetrics(reader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.actuate.metrics.writer.MessageChannelMetricWriter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for writing metrics to a
|
||||
* {@link MessageChannel}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(MessageChannel.class)
|
||||
@ConditionalOnBean(name = "metricsChannel")
|
||||
@AutoConfigureBefore(MetricRepositoryAutoConfiguration.class)
|
||||
public class MetricsChannelAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MessageChannelMetricWriter messageChannelMetricWriter(
|
||||
@Qualifier("metricsChannel") MessageChannel channel) {
|
||||
return new MessageChannelMetricWriter(channel);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
|
||||
import org.springframework.boot.actuate.endpoint.PublicMetrics;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.metrics.dropwizard.DropwizardMetricServices;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Dropwizard-based metrics.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(MetricRegistry.class)
|
||||
@AutoConfigureBefore(MetricRepositoryAutoConfiguration.class)
|
||||
public class MetricsDropwizardAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MetricRegistry metricRegistry() {
|
||||
return new MetricRegistry();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean({ DropwizardMetricServices.class, CounterService.class,
|
||||
GaugeService.class })
|
||||
public DropwizardMetricServices dropwizardMetricServices(MetricRegistry metricRegistry) {
|
||||
return new DropwizardMetricServices(metricRegistry);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PublicMetrics dropwizardPublicMetrics(MetricRegistry metricRegistry) {
|
||||
MetricRegistryMetricReader reader = new MetricRegistryMetricReader(metricRegistry);
|
||||
return new MetricReaderPublicMetrics(reader);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -60,6 +60,7 @@ import org.springframework.context.annotation.Configuration;
|
|||
public class PublicMetricsAutoConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
@ActuatorMetricRepository
|
||||
private MetricReader metricReader = new InMemoryMetricRepository();
|
||||
|
||||
@Bean
|
||||
|
|
|
|||
|
|
@ -62,8 +62,13 @@ public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
|
|||
public Map<String, Object> invoke() {
|
||||
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
||||
for (PublicMetrics publicMetric : this.publicMetrics) {
|
||||
for (Metric<?> metric : publicMetric.metrics()) {
|
||||
result.put(metric.getName(), metric.getValue());
|
||||
try {
|
||||
for (Metric<?> metric : publicMetric.metrics()) {
|
||||
result.put(metric.getName(), metric.getValue());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Could not evaluate metrics
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -56,21 +56,21 @@ public class MetricsMvcEndpoint extends EndpointMvcAdapter {
|
|||
/**
|
||||
* {@link NamePatternFilter} for the Map source.
|
||||
*/
|
||||
private class NamePatternMapFilter extends NamePatternFilter<Map<String, Object>> {
|
||||
private class NamePatternMapFilter extends NamePatternFilter<Map<String, ?>> {
|
||||
|
||||
public NamePatternMapFilter(Map<String, Object> source) {
|
||||
public NamePatternMapFilter(Map<String, ?> source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void getNames(Map<String, Object> source, NameCallback callback) {
|
||||
protected void getNames(Map<String, ?> source, NameCallback callback) {
|
||||
for (String name : source.keySet()) {
|
||||
callback.addName(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getValue(Map<String, Object> source, String name) {
|
||||
protected Object getValue(Map<String, ?> source, String name) {
|
||||
Object value = source.get(name);
|
||||
if (value == null) {
|
||||
throw new NoSuchMetricException("No such metric: " + name);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.actuate.metrics.aggregate;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A metric reader that aggregates values from a source reader, normally one that has been
|
||||
* collecting data from many sources in the same form (like a scaled-out application). The
|
||||
* source has metrics with names in the form <code>*.*.counter.**</code> and
|
||||
* <code>*.*.[anything].**</code> (the length of the prefix is controlled by the
|
||||
* {@link #setTruncateKeyLength(int) truncateKeyLength} property, and defaults to 2,
|
||||
* meaning 2 period separated fields), and the result has metric names in the form
|
||||
* <code>aggregate.count.**</code> and <code>aggregate.[anything].**</code>. Counters are
|
||||
* summed and anything else (i.e. gauges) are aggregated by choosing the most recent
|
||||
* value.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class AggregateMetricReader implements MetricReader {
|
||||
|
||||
private MetricReader source;
|
||||
|
||||
private int truncate = 2;
|
||||
|
||||
private String prefix = "aggregate.";
|
||||
|
||||
public AggregateMetricReader(MetricReader source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of period-separated keys to remove from the start of the input metric
|
||||
* names before aggregating.
|
||||
*
|
||||
* @param truncate length of source metric prefixes
|
||||
*/
|
||||
public void setTruncateKeyLength(int truncate) {
|
||||
this.truncate = truncate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix to apply to all output metrics. A period will be appended if no present in
|
||||
* the provided value.
|
||||
*
|
||||
* @param prefix the prefix to use default "aggregator.")
|
||||
*/
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix.endsWith(".") ? prefix : prefix + ".";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metric<?> findOne(String metricName) {
|
||||
if (!metricName.startsWith(this.prefix)) {
|
||||
return null;
|
||||
}
|
||||
InMemoryMetricRepository result = new InMemoryMetricRepository();
|
||||
String baseName = metricName.substring(this.prefix.length());
|
||||
for (Metric<?> metric : this.source.findAll()) {
|
||||
String name = getSourceKey(metric.getName());
|
||||
if (baseName.equals(name)) {
|
||||
update(result, name, metric);
|
||||
}
|
||||
}
|
||||
return result.findOne(metricName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Metric<?>> findAll() {
|
||||
InMemoryMetricRepository result = new InMemoryMetricRepository();
|
||||
for (Metric<?> metric : this.source.findAll()) {
|
||||
String key = getSourceKey(metric.getName());
|
||||
if (key != null) {
|
||||
update(result, key, metric);
|
||||
}
|
||||
}
|
||||
return result.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (Metric<?> metric : this.source.findAll()) {
|
||||
String name = getSourceKey(metric.getName());
|
||||
if (name != null) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
return names.size();
|
||||
}
|
||||
|
||||
private void update(InMemoryMetricRepository result, String key, Metric<?> metric) {
|
||||
String name = this.prefix + key;
|
||||
Metric<?> aggregate = result.findOne(name);
|
||||
if (aggregate == null) {
|
||||
aggregate = new Metric<Number>(name, metric.getValue(), metric.getTimestamp());
|
||||
}
|
||||
else if (key.contains("counter.")) {
|
||||
// accumulate all values
|
||||
aggregate = new Metric<Number>(name, metric.increment(
|
||||
aggregate.getValue().intValue()).getValue(), metric.getTimestamp());
|
||||
}
|
||||
else if (aggregate.getTimestamp().before(metric.getTimestamp())) {
|
||||
// sort by timestamp and only take the latest
|
||||
aggregate = new Metric<Number>(name, metric.getValue(), metric.getTimestamp());
|
||||
}
|
||||
result.set(aggregate);
|
||||
}
|
||||
|
||||
private String getSourceKey(String name) {
|
||||
String[] keys = StringUtils.delimitedListToStringArray(name, ".");
|
||||
if (keys.length <= this.truncate) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder(keys[this.truncate]);
|
||||
for (int i = this.truncate + 1; i < keys.length; i++) {
|
||||
builder.append(".").append(keys[i]);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
|
||||
/**
|
||||
* Fast implementation of {@link CounterService} using {@link CounterBuffers}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@UsesJava8
|
||||
public class BufferCounterService implements CounterService {
|
||||
|
||||
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
|
||||
|
||||
private final CounterBuffers writer;
|
||||
|
||||
/**
|
||||
* Create a {@link BufferCounterService} instance.
|
||||
* @param writer the underlying writer used to manage metrics
|
||||
*/
|
||||
public BufferCounterService(CounterBuffers writer) {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(String metricName) {
|
||||
this.writer.increment(wrap(metricName), 1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrement(String metricName) {
|
||||
this.writer.increment(wrap(metricName), -1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(String metricName) {
|
||||
this.writer.reset(wrap(metricName));
|
||||
}
|
||||
|
||||
private String wrap(String metricName) {
|
||||
if (this.names.containsKey(metricName)) {
|
||||
return this.names.get(metricName);
|
||||
}
|
||||
if (metricName.startsWith("counter") || metricName.startsWith("meter")) {
|
||||
return metricName;
|
||||
}
|
||||
String name = "counter." + metricName;
|
||||
this.names.put(metricName, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
|
||||
/**
|
||||
* Fast implementation of {@link GaugeService} using {@link GaugeBuffers}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@UsesJava8
|
||||
public class BufferGaugeService implements GaugeService {
|
||||
|
||||
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
|
||||
|
||||
private final GaugeBuffers writer;
|
||||
|
||||
/**
|
||||
* Create a {@link BufferGaugeService} instance.
|
||||
* @param writer the underlying writer used to manage metrics
|
||||
*/
|
||||
public BufferGaugeService(GaugeBuffers writer) {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(String metricName, double value) {
|
||||
this.writer.set(wrap(metricName), value);
|
||||
}
|
||||
|
||||
private String wrap(String metricName) {
|
||||
if (this.names.containsKey(metricName)) {
|
||||
return this.names.get(metricName);
|
||||
}
|
||||
if (metricName.startsWith("gauge") || metricName.startsWith("histogram")
|
||||
|| metricName.startsWith("timer")) {
|
||||
return metricName;
|
||||
}
|
||||
String name = "gauge." + metricName;
|
||||
this.names.put(metricName, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.reader.PrefixMetricReader;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
|
||||
/**
|
||||
* {@link MetricReader} implementation using {@link CounterBuffers} and
|
||||
* {@link GaugeBuffers}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@UsesJava8
|
||||
public class BufferMetricReader implements MetricReader, PrefixMetricReader {
|
||||
|
||||
private final CounterBuffers counters;
|
||||
|
||||
private final GaugeBuffers gauges;
|
||||
|
||||
private final Predicate<String> all = Pattern.compile(".*").asPredicate();
|
||||
|
||||
public BufferMetricReader(CounterBuffers counters, GaugeBuffers gauges) {
|
||||
this.counters = counters;
|
||||
this.gauges = gauges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Metric<?>> findAll(String prefix) {
|
||||
final List<Metric<?>> metrics = new ArrayList<Metric<?>>();
|
||||
this.counters.forEach(Pattern.compile(prefix + ".*").asPredicate(),
|
||||
new BiConsumer<String, LongBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, LongBuffer value) {
|
||||
metrics.add(new Metric<Long>(name, value.getValue(), new Date(
|
||||
value.getTimestamp())));
|
||||
}
|
||||
});
|
||||
this.gauges.forEach(Pattern.compile(prefix + ".*").asPredicate(),
|
||||
new BiConsumer<String, DoubleBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, DoubleBuffer value) {
|
||||
metrics.add(new Metric<Double>(name, value.getValue(), new Date(
|
||||
value.getTimestamp())));
|
||||
}
|
||||
});
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metric<?> findOne(final String name) {
|
||||
LongBuffer buffer = this.counters.find(name);
|
||||
if (buffer != null) {
|
||||
return new Metric<Long>(name, buffer.getValue(), new Date(
|
||||
buffer.getTimestamp()));
|
||||
}
|
||||
DoubleBuffer doubleValue = this.gauges.find(name);
|
||||
if (doubleValue != null) {
|
||||
return new Metric<Double>(name, doubleValue.getValue(), new Date(
|
||||
doubleValue.getTimestamp()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Metric<?>> findAll() {
|
||||
final List<Metric<?>> metrics = new ArrayList<Metric<?>>();
|
||||
this.counters.forEach(this.all, new BiConsumer<String, LongBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, LongBuffer value) {
|
||||
metrics.add(new Metric<Long>(name, value.getValue(), new Date(value
|
||||
.getTimestamp())));
|
||||
}
|
||||
});
|
||||
this.gauges.forEach(this.all, new BiConsumer<String, DoubleBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, DoubleBuffer value) {
|
||||
metrics.add(new Metric<Double>(name, value.getValue(), new Date(value
|
||||
.getTimestamp())));
|
||||
}
|
||||
});
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return this.counters.count() + this.gauges.count();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.lang.UsesJava8;
|
||||
|
||||
/**
|
||||
* Fast writes to in-memory metrics store using {@link LongBuffer}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@UsesJava8
|
||||
public class CounterBuffers {
|
||||
|
||||
private final ConcurrentHashMap<String, LongBuffer> metrics = new ConcurrentHashMap<String, LongBuffer>();
|
||||
|
||||
public void forEach(final Predicate<String> predicate,
|
||||
final BiConsumer<String, LongBuffer> consumer) {
|
||||
this.metrics.forEach(new BiConsumer<String, LongBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, LongBuffer value) {
|
||||
if (predicate.test(name)) {
|
||||
consumer.accept(name, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LongBuffer find(final String name) {
|
||||
return this.metrics.get(name);
|
||||
}
|
||||
|
||||
public void get(final String name, final Consumer<LongBuffer> consumer) {
|
||||
read(name, new Consumer<LongBuffer>() {
|
||||
@Override
|
||||
public void accept(LongBuffer adder) {
|
||||
consumer.accept(adder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void increment(final String name, final long delta) {
|
||||
write(name, new Consumer<LongBuffer>() {
|
||||
@Override
|
||||
public void accept(LongBuffer adder) {
|
||||
adder.add(delta);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void reset(final String name) {
|
||||
write(name, new Consumer<LongBuffer>() {
|
||||
@Override
|
||||
public void accept(LongBuffer adder) {
|
||||
adder.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int count() {
|
||||
return this.metrics.size();
|
||||
}
|
||||
|
||||
private void read(final String name, final Consumer<LongBuffer> consumer) {
|
||||
acceptInternal(name, new Consumer<LongBuffer>() {
|
||||
@Override
|
||||
public void accept(LongBuffer adder) {
|
||||
consumer.accept(adder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void write(final String name, final Consumer<LongBuffer> consumer) {
|
||||
acceptInternal(name, new Consumer<LongBuffer>() {
|
||||
@Override
|
||||
public void accept(LongBuffer buffer) {
|
||||
buffer.setTimestamp(System.currentTimeMillis());
|
||||
consumer.accept(buffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void acceptInternal(final String name, final Consumer<LongBuffer> consumer) {
|
||||
LongBuffer adder;
|
||||
if (null == (adder = this.metrics.get(name))) {
|
||||
adder = this.metrics.computeIfAbsent(name,
|
||||
new Function<String, LongBuffer>() {
|
||||
@Override
|
||||
public LongBuffer apply(String name) {
|
||||
return new LongBuffer(0L);
|
||||
}
|
||||
});
|
||||
}
|
||||
consumer.accept(adder);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
/**
|
||||
* Mutable buffer containing a double value and a timestamp.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DoubleBuffer {
|
||||
|
||||
private volatile double value;
|
||||
|
||||
private volatile long timestamp;
|
||||
|
||||
public DoubleBuffer(long timestamp) {
|
||||
this.value = 0;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.lang.UsesJava8;
|
||||
|
||||
/**
|
||||
* Fast writes to in-memory metrics store using {@link DoubleBuffer}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@UsesJava8
|
||||
public class GaugeBuffers {
|
||||
|
||||
private final ConcurrentHashMap<String, DoubleBuffer> metrics = new ConcurrentHashMap<String, DoubleBuffer>();
|
||||
|
||||
public void forEach(final Predicate<String> predicate,
|
||||
final BiConsumer<String, DoubleBuffer> consumer) {
|
||||
this.metrics.forEach(new BiConsumer<String, DoubleBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, DoubleBuffer value) {
|
||||
if (predicate.test(name)) {
|
||||
consumer.accept(name, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public DoubleBuffer find(final String name) {
|
||||
return this.metrics.get(name);
|
||||
}
|
||||
|
||||
public void get(final String name, final Consumer<DoubleBuffer> consumer) {
|
||||
acceptInternal(name, new Consumer<DoubleBuffer>() {
|
||||
@Override
|
||||
public void accept(DoubleBuffer value) {
|
||||
consumer.accept(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void set(final String name, final double value) {
|
||||
write(name, value);
|
||||
}
|
||||
|
||||
public int count() {
|
||||
return this.metrics.size();
|
||||
}
|
||||
|
||||
private void write(final String name, final double value) {
|
||||
acceptInternal(name, new Consumer<DoubleBuffer>() {
|
||||
@Override
|
||||
public void accept(DoubleBuffer buffer) {
|
||||
buffer.setTimestamp(System.currentTimeMillis());
|
||||
buffer.setValue(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void reset(String name) {
|
||||
this.metrics.remove(name, this.metrics.get(name));
|
||||
}
|
||||
|
||||
private void acceptInternal(final String name, final Consumer<DoubleBuffer> consumer) {
|
||||
DoubleBuffer value;
|
||||
if (null == (value = this.metrics.get(name))) {
|
||||
value = this.metrics.computeIfAbsent(name,
|
||||
new Function<String, DoubleBuffer>() {
|
||||
@Override
|
||||
public DoubleBuffer apply(String tag) {
|
||||
return new DoubleBuffer(0L);
|
||||
}
|
||||
});
|
||||
}
|
||||
consumer.accept(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
import org.springframework.lang.UsesJava8;
|
||||
|
||||
/**
|
||||
* Mutable buffer containing a long adder (Java 8) and a timestamp.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@UsesJava8
|
||||
public class LongBuffer {
|
||||
|
||||
private final LongAdder adder;
|
||||
|
||||
private volatile long timestamp;
|
||||
|
||||
public LongBuffer(long timestamp) {
|
||||
this.adder = new LongAdder();
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public long getValue() {
|
||||
return this.adder.sum();
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.adder.reset();
|
||||
}
|
||||
|
||||
public void add(long delta) {
|
||||
this.adder.add(delta);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Metrics buffering support.
|
||||
*/
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,13 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.writer;
|
||||
package org.springframework.boot.actuate.metrics.dropwizard;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
|
||||
import com.codahale.metrics.Counter;
|
||||
import com.codahale.metrics.Gauge;
|
||||
|
|
@ -30,77 +31,107 @@ import com.codahale.metrics.MetricRegistry;
|
|||
import com.codahale.metrics.Timer;
|
||||
|
||||
/**
|
||||
* A {@link MetricWriter} that send data to a Codahale {@link MetricRegistry} based on a
|
||||
* naming convention:
|
||||
* A {@link GaugeService} and {@link CounterService} that sends data to a Dropwizard
|
||||
* {@link MetricRegistry} based on a naming convention:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Updates to {@link #increment(Delta)} with names in "meter.*" are treated as
|
||||
* <li>Updates to {@link #increment(String)} with names in "meter.*" are treated as
|
||||
* {@link Meter} events</li>
|
||||
* <li>Other deltas are treated as simple {@link Counter} values</li>
|
||||
* <li>Inputs to {@link #set(Metric)} with names in "histogram.*" are treated as
|
||||
* {@link Histogram} updates</li>
|
||||
* <li>Inputs to {@link #set(Metric)} with names in "timer.*" are treated as {@link Timer}
|
||||
* updates</li>
|
||||
* <li>Inputs to {@link #submit(String, double)} with names in "histogram.*" are treated
|
||||
* as {@link Histogram} updates</li>
|
||||
* <li>Inputs to {@link #submit(String, double)} with names in "timer.*" are treated as
|
||||
* {@link Timer} updates</li>
|
||||
* <li>Other metrics are treated as simple {@link Gauge} values (single valued
|
||||
* measurements of type double)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DropwizardMetricWriter implements MetricWriter {
|
||||
public class DropwizardMetricServices implements CounterService, GaugeService {
|
||||
|
||||
private final MetricRegistry registry;
|
||||
|
||||
private final ConcurrentMap<String, Object> gaugeLocks = new ConcurrentHashMap<String, Object>();
|
||||
|
||||
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Create a new {@link DropwizardMetricWriter} instance.
|
||||
* Create a new {@link DropwizardMetricServices} instance.
|
||||
* @param registry the underlying metric registry
|
||||
*/
|
||||
public DropwizardMetricWriter(MetricRegistry registry) {
|
||||
public DropwizardMetricServices(MetricRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(Delta<?> delta) {
|
||||
String name = delta.getName();
|
||||
long value = delta.getValue().longValue();
|
||||
public void increment(String name) {
|
||||
incrementInternal(name, 1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrement(String name) {
|
||||
incrementInternal(name, -1L);
|
||||
}
|
||||
|
||||
private void incrementInternal(String name, long value) {
|
||||
if (name.startsWith("meter")) {
|
||||
Meter meter = this.registry.meter(name);
|
||||
meter.mark(value);
|
||||
}
|
||||
else {
|
||||
name = wrapCounterName(name);
|
||||
Counter counter = this.registry.counter(name);
|
||||
counter.inc(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Metric<?> value) {
|
||||
String name = value.getName();
|
||||
public void submit(String name, double value) {
|
||||
if (name.startsWith("histogram")) {
|
||||
long longValue = value.getValue().longValue();
|
||||
long longValue = (long) value;
|
||||
Histogram metric = this.registry.histogram(name);
|
||||
metric.update(longValue);
|
||||
}
|
||||
else if (name.startsWith("timer")) {
|
||||
long longValue = value.getValue().longValue();
|
||||
long longValue = (long) value;
|
||||
Timer metric = this.registry.timer(name);
|
||||
metric.update(longValue, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else {
|
||||
final double gauge = value.getValue().doubleValue();
|
||||
name = wrapGaugeName(name);
|
||||
final double gauge = value;
|
||||
// Ensure we synchronize to avoid another thread pre-empting this thread after
|
||||
// remove causing an error in CodaHale metrics
|
||||
// NOTE: CodaHale provides no way to do this atomically
|
||||
synchronized (getGuageLock(name)) {
|
||||
// remove causing an error in Dropwizard metrics
|
||||
// NOTE: Dropwizard provides no way to do this atomically
|
||||
synchronized (getGaugeLock(name)) {
|
||||
this.registry.remove(name);
|
||||
this.registry.register(name, new SimpleGauge(gauge));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object getGuageLock(String name) {
|
||||
private String wrapGaugeName(String metricName) {
|
||||
return wrapName(metricName, "gauge.");
|
||||
}
|
||||
|
||||
private String wrapCounterName(String metricName) {
|
||||
return wrapName(metricName, "counter.");
|
||||
}
|
||||
|
||||
private String wrapName(String metricName, String prefix) {
|
||||
if (this.names.containsKey(metricName)) {
|
||||
return this.names.get(metricName);
|
||||
}
|
||||
if (metricName.startsWith(prefix)) {
|
||||
return metricName;
|
||||
}
|
||||
String name = prefix + metricName;
|
||||
this.names.put(metricName, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
private Object getGaugeLock(String name) {
|
||||
Object lock = this.gaugeLocks.get(name);
|
||||
if (lock == null) {
|
||||
Object newLock = new Object();
|
||||
|
|
@ -111,8 +142,11 @@ public class DropwizardMetricWriter implements MetricWriter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reset(String metricName) {
|
||||
this.registry.remove(metricName);
|
||||
public void reset(String name) {
|
||||
if (!name.startsWith("meter")) {
|
||||
name = wrapCounterName(name);
|
||||
}
|
||||
this.registry.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Metrics integration with Dropwizard Metrics.
|
||||
*/
|
||||
package org.springframework.boot.actuate.metrics.dropwizard;
|
||||
|
||||
|
|
@ -22,6 +22,8 @@ import java.util.Collections;
|
|||
import java.util.Date;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
|
@ -34,6 +36,8 @@ import org.springframework.util.StringUtils;
|
|||
*/
|
||||
public abstract class AbstractMetricExporter implements Exporter {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AbstractMetricExporter.class);
|
||||
|
||||
private volatile AtomicBoolean processing = new AtomicBoolean(false);
|
||||
|
||||
private Date earliestTimestamp = new Date();
|
||||
|
|
@ -42,6 +46,10 @@ public abstract class AbstractMetricExporter implements Exporter {
|
|||
|
||||
private final String prefix;
|
||||
|
||||
private Date latestTimestamp = new Date(0L);
|
||||
|
||||
private boolean sendLatest = true;
|
||||
|
||||
public AbstractMetricExporter(String prefix) {
|
||||
this.prefix = !StringUtils.hasText(prefix) ? "" : (prefix.endsWith(".") ? prefix
|
||||
: prefix + ".");
|
||||
|
|
@ -63,13 +71,24 @@ public abstract class AbstractMetricExporter implements Exporter {
|
|||
this.ignoreTimestamps = ignoreTimestamps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send only the data that changed since the last export.
|
||||
*
|
||||
* @param sendLatest the flag to set
|
||||
*/
|
||||
public void setSendLatest(boolean sendLatest) {
|
||||
this.sendLatest = sendLatest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export() {
|
||||
if (!this.processing.compareAndSet(false, true)) {
|
||||
// skip a tick
|
||||
return;
|
||||
}
|
||||
long latestTimestamp = 0;
|
||||
try {
|
||||
latestTimestamp = System.currentTimeMillis();
|
||||
for (String group : groups()) {
|
||||
Collection<Metric<?>> values = new ArrayList<Metric<?>>();
|
||||
for (Metric<?> metric : next(group)) {
|
||||
|
|
@ -79,6 +98,10 @@ public abstract class AbstractMetricExporter implements Exporter {
|
|||
if (!this.ignoreTimestamps && this.earliestTimestamp.after(timestamp)) {
|
||||
continue;
|
||||
}
|
||||
if (!this.ignoreTimestamps && this.sendLatest
|
||||
&& this.latestTimestamp.after(timestamp)) {
|
||||
continue;
|
||||
}
|
||||
values.add(value);
|
||||
}
|
||||
if (!values.isEmpty()) {
|
||||
|
|
@ -86,11 +109,26 @@ public abstract class AbstractMetricExporter implements Exporter {
|
|||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.warn("Could not write to MetricWriter: " + e.getClass() + ": "
|
||||
+ e.getMessage());
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
this.latestTimestamp = new Date(latestTimestamp);
|
||||
flush();
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.warn("Could not flush MetricWriter: " + e.getClass() + ": "
|
||||
+ e.getMessage());
|
||||
}
|
||||
this.processing.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a group of metrics to iterate over in the form of a set of Strings (e.g.
|
||||
* prefixes). If the metrics to be exported partition into groups identified by a
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ package org.springframework.boot.actuate.metrics.export;
|
|||
* Generic interface for metric exports. As you scale up metric collection you will often
|
||||
* need to buffer metric data locally and export it periodically (e.g. for aggregation
|
||||
* across a cluster), so this is the marker interface for those operations. The trigger of
|
||||
* an export operation might be periodic or even driven, but it remains outside the scope
|
||||
* an export operation might be periodic or event driven, but it remains outside the scope
|
||||
* of this interface. You might for instance create an instance of an Exporter and trigger
|
||||
* it using a {@code @Scheduled} annotation in a Spring ApplicationContext.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -17,10 +17,13 @@
|
|||
package org.springframework.boot.actuate.metrics.export;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.writer.WriterUtils;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
|
||||
/**
|
||||
* {@link Exporter} that "exports" by copying metric data from a source
|
||||
|
|
@ -34,10 +37,26 @@ public class MetricCopyExporter extends AbstractMetricExporter {
|
|||
|
||||
private final MetricWriter writer;
|
||||
|
||||
private String[] includes = new String[0];
|
||||
|
||||
private String[] excludes = new String[0];
|
||||
|
||||
public MetricCopyExporter(MetricReader reader, MetricWriter writer) {
|
||||
this(reader, writer, "");
|
||||
}
|
||||
|
||||
public void setIncludes(String... includes) {
|
||||
if (includes != null) {
|
||||
this.includes = includes;
|
||||
}
|
||||
}
|
||||
|
||||
public void setExcludes(String... excludes) {
|
||||
if (excludes != null) {
|
||||
this.excludes = excludes;
|
||||
}
|
||||
}
|
||||
|
||||
public MetricCopyExporter(MetricReader reader, MetricWriter writer, String prefix) {
|
||||
super(prefix);
|
||||
this.reader = reader;
|
||||
|
|
@ -46,7 +65,17 @@ public class MetricCopyExporter extends AbstractMetricExporter {
|
|||
|
||||
@Override
|
||||
protected Iterable<Metric<?>> next(String group) {
|
||||
return this.reader.findAll();
|
||||
if ((this.includes == null || this.includes.length == 0)
|
||||
&& (this.excludes == null || this.excludes.length == 0)) {
|
||||
return this.reader.findAll();
|
||||
}
|
||||
return new Iterable<Metric<?>>() {
|
||||
@Override
|
||||
public Iterator<Metric<?>> iterator() {
|
||||
return new PatternMatchingIterator(MetricCopyExporter.this.reader
|
||||
.findAll().iterator());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -56,4 +85,64 @@ public class MetricCopyExporter extends AbstractMetricExporter {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
WriterUtils.flush(this.writer);
|
||||
}
|
||||
|
||||
private class PatternMatchingIterator implements Iterator<Metric<?>> {
|
||||
|
||||
private Metric<?> buffer = null;
|
||||
private Iterator<Metric<?>> iterator;
|
||||
|
||||
public PatternMatchingIterator(Iterator<Metric<?>> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (this.buffer != null) {
|
||||
return true;
|
||||
}
|
||||
this.buffer = findNext();
|
||||
return this.buffer != null;
|
||||
}
|
||||
|
||||
private Metric<?> findNext() {
|
||||
Metric<?> metric = null;
|
||||
boolean matched = false;
|
||||
while (this.iterator.hasNext() && !matched) {
|
||||
metric = this.iterator.next();
|
||||
if (MetricCopyExporter.this.includes == null
|
||||
|| MetricCopyExporter.this.includes.length == 0) {
|
||||
matched = true;
|
||||
}
|
||||
else {
|
||||
for (String pattern : MetricCopyExporter.this.includes) {
|
||||
if (PatternMatchUtils.simpleMatch(pattern, metric.getName())) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (MetricCopyExporter.this.excludes != null) {
|
||||
for (String pattern : MetricCopyExporter.this.excludes) {
|
||||
if (PatternMatchUtils.simpleMatch(pattern, metric.getName())) {
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return matched ? metric : null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metric<?> next() {
|
||||
Metric<?> metric = this.buffer;
|
||||
this.buffer = null;
|
||||
return metric;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.export;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@ConfigurationProperties("spring.metrics.export")
|
||||
public class MetricExportProperties {
|
||||
|
||||
/**
|
||||
* Flag to disable all metric exports (assuming any MetricWriters are available).
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
private Export export = new Export();
|
||||
|
||||
private Map<String, Export> writers = new LinkedHashMap<String, Export>();
|
||||
|
||||
/**
|
||||
* Default values for trigger configuration for all writers. Can also be set by
|
||||
* including a writer with <code>name="*"</code>.
|
||||
*
|
||||
* @return the default trigger configuration
|
||||
*/
|
||||
public Export getDefault() {
|
||||
return this.export;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for triggers on individual named writers. Each value can individually
|
||||
* specify a name pattern explicitly, or else the map key will be used if the name is
|
||||
* not set.
|
||||
*
|
||||
* @return the writers
|
||||
*/
|
||||
public Map<String, Export> getWriters() {
|
||||
return this.writers;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void setUpDefaults() {
|
||||
Export defaults = null;
|
||||
for (Entry<String, Export> entry : this.writers.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Export value = entry.getValue();
|
||||
if (value.getNames() == null || value.getNames().length == 0) {
|
||||
value.setNames(new String[] { key });
|
||||
}
|
||||
if (Arrays.asList(value.getNames()).contains("*")) {
|
||||
defaults = value;
|
||||
}
|
||||
}
|
||||
if (defaults == null) {
|
||||
this.export.setNames(new String[] { "*" });
|
||||
this.writers.put("*", this.export);
|
||||
defaults = this.export;
|
||||
}
|
||||
if (defaults.isIgnoreTimestamps() == null) {
|
||||
defaults.setIgnoreTimestamps(false);
|
||||
}
|
||||
if (defaults.isSendLatest() == null) {
|
||||
defaults.setSendLatest(true);
|
||||
}
|
||||
if (defaults.getDelayMillis() == null) {
|
||||
defaults.setDelayMillis(5000);
|
||||
}
|
||||
for (Export value : this.writers.values()) {
|
||||
if (value.isIgnoreTimestamps() == null) {
|
||||
value.setIgnoreTimestamps(defaults.isIgnoreTimestamps());
|
||||
}
|
||||
if (value.isSendLatest() == null) {
|
||||
value.setSendLatest(defaults.isSendLatest());
|
||||
}
|
||||
if (value.getDelayMillis() == null) {
|
||||
value.setDelayMillis(defaults.getDelayMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public static class Export {
|
||||
/**
|
||||
* Names (or patterns) for bean names that this configuration applies to.
|
||||
*/
|
||||
private String[] names;
|
||||
/**
|
||||
* Delay in milliseconds between export ticks. Metrics are exported to external
|
||||
* sources on a schedule with this delay.
|
||||
*/
|
||||
private Long delayMillis;
|
||||
|
||||
/**
|
||||
* Flag to enable metric export (assuming a MetricWriter is available).
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Flag to switch off any available optimizations based on not exporting unchanged
|
||||
* metric values.
|
||||
*/
|
||||
private Boolean sendLatest;
|
||||
|
||||
/**
|
||||
* Flag to ignore timestamps completely. If true, send all metrics all the time,
|
||||
* including ones that haven't changed since startup.
|
||||
*/
|
||||
private Boolean ignoreTimestamps;
|
||||
|
||||
private String[] includes;
|
||||
|
||||
private String[] excludes;
|
||||
|
||||
public String[] getNames() {
|
||||
return this.names;
|
||||
}
|
||||
|
||||
public void setNames(String[] names) {
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
public String[] getIncludes() {
|
||||
return this.includes;
|
||||
}
|
||||
|
||||
public void setIncludes(String[] includes) {
|
||||
this.includes = includes;
|
||||
}
|
||||
|
||||
public void setExcludes(String[] excludes) {
|
||||
this.excludes = excludes;
|
||||
}
|
||||
|
||||
public String[] getExcludes() {
|
||||
return this.excludes;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Long getDelayMillis() {
|
||||
return this.delayMillis;
|
||||
}
|
||||
|
||||
public void setDelayMillis(long delayMillis) {
|
||||
this.delayMillis = delayMillis;
|
||||
}
|
||||
|
||||
public Boolean isIgnoreTimestamps() {
|
||||
return this.ignoreTimestamps;
|
||||
}
|
||||
|
||||
public void setIgnoreTimestamps(boolean ignoreTimestamps) {
|
||||
this.ignoreTimestamps = ignoreTimestamps;
|
||||
}
|
||||
|
||||
public Boolean isSendLatest() {
|
||||
return this.sendLatest;
|
||||
}
|
||||
|
||||
public void setSendLatest(boolean sendLatest) {
|
||||
this.sendLatest = sendLatest;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a matching trigger configuration.
|
||||
* @param name the bean name to match
|
||||
* @return a matching configuration if there is one
|
||||
*/
|
||||
public Export findExport(String name) {
|
||||
for (Export value : this.writers.values()) {
|
||||
if (PatternMatchUtils.simpleMatch(value.getNames(), name)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return this.export;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.export;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.export.MetricExportProperties.Export;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.IntervalTask;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class MetricExporters implements SchedulingConfigurer {
|
||||
|
||||
private MetricExportProperties export;
|
||||
|
||||
private Map<String, MetricWriter> writers;
|
||||
|
||||
private Map<String, Exporter> exporters = new HashMap<String, Exporter>();
|
||||
|
||||
private MetricReader reader;
|
||||
|
||||
public MetricExporters(MetricReader reader, Map<String, MetricWriter> writers,
|
||||
MetricExportProperties export) {
|
||||
this.reader = reader;
|
||||
this.export = export;
|
||||
this.writers = writers;
|
||||
}
|
||||
|
||||
public Map<String, Exporter> getExporters() {
|
||||
return this.exporters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
|
||||
for (Entry<String, MetricWriter> entry : this.writers.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
Export trigger = this.export.findExport(name);
|
||||
|
||||
if (trigger != null) {
|
||||
|
||||
MetricWriter writer = entry.getValue();
|
||||
final MetricCopyExporter exporter = new MetricCopyExporter(this.reader,
|
||||
writer);
|
||||
if (trigger.getIncludes() != null || trigger.getExcludes() != null) {
|
||||
exporter.setIncludes(trigger.getIncludes());
|
||||
exporter.setExcludes(trigger.getExcludes());
|
||||
}
|
||||
exporter.setIgnoreTimestamps(trigger.isIgnoreTimestamps());
|
||||
exporter.setSendLatest(trigger.isSendLatest());
|
||||
|
||||
this.exporters.put(name, exporter);
|
||||
|
||||
taskRegistrar.addFixedDelayTask(new IntervalTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
exporter.export();
|
||||
}
|
||||
}, trigger.getDelayMillis(), trigger.getDelayMillis()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.jmx;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import javax.management.MalformedObjectNameException;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.springframework.jmx.export.naming.KeyNamingStrategy;
|
||||
import org.springframework.jmx.export.naming.ObjectNamingStrategy;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* MBean naming strategy for metric keys. A metric name of
|
||||
* <code>counter.foo.bar.spam</code> translates to an object name with
|
||||
* <code>type=counter</code>, <code>name=foo</code> and <code>value=bar.spam</code>. This
|
||||
* results in a more or less pleasing view with no tweaks in jconsole or jvisualvm. The
|
||||
* domain is copied from the input key and the type in the input key is discarded.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DefaultMetricNamingStrategy implements ObjectNamingStrategy {
|
||||
|
||||
private ObjectNamingStrategy namingStrategy = new KeyNamingStrategy();
|
||||
|
||||
@Override
|
||||
public ObjectName getObjectName(Object managedBean, String beanKey)
|
||||
throws MalformedObjectNameException {
|
||||
ObjectName objectName = this.namingStrategy.getObjectName(managedBean, beanKey);
|
||||
String domain = objectName.getDomain();
|
||||
Hashtable<String, String> table = new Hashtable<String, String>(
|
||||
objectName.getKeyPropertyList());
|
||||
String name = objectName.getKeyProperty("name");
|
||||
if (name != null) {
|
||||
table.remove("name");
|
||||
String[] parts = StringUtils.delimitedListToStringArray(name, ".");
|
||||
table.put("type", parts[0]);
|
||||
if (parts.length > 1) {
|
||||
table.put(parts.length > 2 ? "name" : "value", parts[1]);
|
||||
}
|
||||
if (parts.length > 2) {
|
||||
table.put("value",
|
||||
name.substring(parts[0].length() + parts[1].length() + 2));
|
||||
}
|
||||
}
|
||||
return new ObjectName(domain, table);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.jmx;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.management.MalformedObjectNameException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.writer.Delta;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.jmx.export.MBeanExporter;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.jmx.export.naming.ObjectNamingStrategy;
|
||||
|
||||
/**
|
||||
* A {@link MetricWriter} for MBeans. Each metric is registered as an individual MBean, so
|
||||
* (for instance) it can be graphed and monitored. The object names are provided by an
|
||||
* {@link ObjectNamingStrategy}, where the default is a
|
||||
* {@link DefaultMetricNamingStrategy} which provides <code>type</code>, <code>name</code>
|
||||
* and <code>value</code> keys by splitting up the metric name on periods.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@ManagedResource(description = "MetricWriter for pushing metrics to JMX MBeans.")
|
||||
public class JmxMetricWriter implements MetricWriter {
|
||||
|
||||
private static Log logger = LogFactory.getLog(JmxMetricWriter.class);
|
||||
|
||||
private final ConcurrentMap<String, MetricValue> values = new ConcurrentHashMap<String, MetricValue>();
|
||||
|
||||
private final MBeanExporter exporter;
|
||||
|
||||
private ObjectNamingStrategy namingStrategy = new DefaultMetricNamingStrategy();
|
||||
|
||||
private String domain = "org.springframework.metrics";
|
||||
|
||||
public JmxMetricWriter(MBeanExporter exporter) {
|
||||
this.exporter = exporter;
|
||||
}
|
||||
|
||||
public void setNamingStrategy(ObjectNamingStrategy namingStrategy) {
|
||||
this.namingStrategy = namingStrategy;
|
||||
}
|
||||
|
||||
public void setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
@ManagedOperation
|
||||
public void increment(String name, long value) {
|
||||
increment(new Delta<Long>(name, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(Delta<?> delta) {
|
||||
MetricValue counter = getValue(delta.getName());
|
||||
counter.increment(delta.getValue().longValue());
|
||||
}
|
||||
|
||||
@ManagedOperation
|
||||
public void set(String name, double value) {
|
||||
set(new Metric<Double>(name, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Metric<?> value) {
|
||||
MetricValue metric = getValue(value.getName());
|
||||
metric.setValue(value.getValue().doubleValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ManagedOperation
|
||||
public void reset(String name) {
|
||||
MetricValue value = this.values.remove(name);
|
||||
if (value != null) {
|
||||
try {
|
||||
// We can unregister the MBean, but if this writer is on the end of an
|
||||
// Exporter the chances are it will be re-registered almost immediately.
|
||||
this.exporter.unregisterManagedResource(this.namingStrategy
|
||||
.getObjectName(value, getKey(name)));
|
||||
}
|
||||
catch (MalformedObjectNameException e) {
|
||||
logger.warn("Could not unregister MBean for " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MetricValue getValue(String name) {
|
||||
if (!this.values.containsKey(name)) {
|
||||
this.values.putIfAbsent(name, new MetricValue());
|
||||
MetricValue value = this.values.get(name);
|
||||
try {
|
||||
this.exporter.registerManagedResource(value,
|
||||
this.namingStrategy.getObjectName(value, getKey(name)));
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Could not register mbean, maybe just a race condition
|
||||
}
|
||||
}
|
||||
return this.values.get(name);
|
||||
}
|
||||
|
||||
private String getKey(String name) {
|
||||
return String.format(this.domain + ":type=MetricValue,name=%s", name);
|
||||
}
|
||||
|
||||
@ManagedResource
|
||||
public static class MetricValue {
|
||||
|
||||
private double value;
|
||||
|
||||
private long lastUpdated = 0;
|
||||
|
||||
public void setValue(double value) {
|
||||
if (this.value != value) {
|
||||
this.lastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void increment(long value) {
|
||||
this.lastUpdated = System.currentTimeMillis();
|
||||
this.value += value;
|
||||
}
|
||||
|
||||
@ManagedAttribute
|
||||
public double getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@ManagedAttribute
|
||||
public Date getLastUpdated() {
|
||||
return new Date(this.lastUpdated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Metrics integration with JMX.
|
||||
*/
|
||||
package org.springframework.boot.actuate.metrics.jmx;
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.opentsdb;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A naming strategy that just passes through the metric name, together with tags from a
|
||||
* set of static values. Open TSDB requires at least one tag, so tags are always added for
|
||||
* you: the {@value #DOMAIN_KEY} key is added with a value "spring", and the
|
||||
* {@value #PROCESS_KEY} key is added with a value equal to the object hash of "this" (the
|
||||
* naming strategy). The "domain" value is a system identifier - it would be common to all
|
||||
* processes in the same distributed system. In most cases this will be unique enough to
|
||||
* allow aggregation of the underlying metrics in Open TSDB, but normally it is best to
|
||||
* provide your own tags, including a prefix and process identifier if you know one
|
||||
* (overwriting the default).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DefaultOpenTsdbNamingStrategy implements OpenTsdbNamingStrategy {
|
||||
|
||||
public static final String DOMAIN_KEY = "domain";
|
||||
|
||||
public static final String PROCESS_KEY = "process";
|
||||
|
||||
/**
|
||||
* Tags to apply to every metric. Open TSDB requires at least one tag, so a "prefix"
|
||||
* tag is added for you by default.
|
||||
*/
|
||||
private Map<String, String> tags = new LinkedHashMap<String, String>();
|
||||
|
||||
private Map<String, OpenTsdbName> cache = new HashMap<String, OpenTsdbName>();
|
||||
|
||||
public DefaultOpenTsdbNamingStrategy() {
|
||||
this.tags.put(DOMAIN_KEY, "org.springframework.metrics");
|
||||
this.tags.put(PROCESS_KEY, ObjectUtils.getIdentityHexString(this));
|
||||
}
|
||||
|
||||
public void setTags(Map<String, String> staticTags) {
|
||||
this.tags.putAll(staticTags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenTsdbName getName(String name) {
|
||||
if (this.cache.containsKey(name)) {
|
||||
return this.cache.get(name);
|
||||
}
|
||||
OpenTsdbName value = new OpenTsdbName(name);
|
||||
value.setTags(this.tags);
|
||||
this.cache.put(name, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.opentsdb;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class OpenTsdbData {
|
||||
|
||||
private OpenTsdbName name;
|
||||
|
||||
private Long timestamp;
|
||||
|
||||
private Number value;
|
||||
|
||||
protected OpenTsdbData() {
|
||||
this.name = new OpenTsdbName();
|
||||
}
|
||||
|
||||
public OpenTsdbData(String metric, Number value) {
|
||||
this(metric, value, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public OpenTsdbData(String metric, Number value, Long timestamp) {
|
||||
this(new OpenTsdbName(metric), value, timestamp);
|
||||
}
|
||||
|
||||
public OpenTsdbData(OpenTsdbName name, Number value, Long timestamp) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getMetric() {
|
||||
return this.name.getMetric();
|
||||
}
|
||||
|
||||
public void setMetric(String metric) {
|
||||
this.name.setMetric(metric);
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(Long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public Number getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(Number value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Map<String, String> getTags() {
|
||||
return this.name.getTags();
|
||||
}
|
||||
|
||||
public void setTags(Map<String, String> tags) {
|
||||
this.name.setTags(tags);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.opentsdb;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.writer.Delta;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* A {@link MetricWriter} for the Open TSDB database (version 2.0), writing metrics to the
|
||||
* HTTP endpoint provided by the server. Data are buffered according to the
|
||||
* {@link #setBufferSize(int) bufferSize} property, and only flushed automatically when
|
||||
* the buffer size is reached. Users should either manually {@link #flush()} after writing
|
||||
* a batch of data if that makes sense, or consider adding a {@link Scheduled
|
||||
* <code>@Scheduled</code>} task to flush periodically.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class OpenTsdbMetricWriter implements MetricWriter {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(OpenTsdbMetricWriter.class);
|
||||
|
||||
private RestOperations restTemplate = new RestTemplate();
|
||||
|
||||
/**
|
||||
* URL for POSTing data. Defaults to http://localhost:4242/api/put.
|
||||
*/
|
||||
private String url = "http://localhost:4242/api/put";
|
||||
|
||||
/**
|
||||
* Buffer size to fill before posting data to server.
|
||||
*/
|
||||
private int bufferSize = 64;
|
||||
|
||||
/**
|
||||
* The media type to use to serialize and accept responses from the server. Defaults
|
||||
* to "application/json".
|
||||
*/
|
||||
private MediaType mediaType = MediaType.APPLICATION_JSON;
|
||||
|
||||
private List<OpenTsdbData> buffer = new ArrayList<OpenTsdbData>(this.bufferSize);
|
||||
|
||||
private OpenTsdbNamingStrategy namingStrategy = new DefaultOpenTsdbNamingStrategy();
|
||||
|
||||
public RestOperations getRestTemplate() {
|
||||
return this.restTemplate;
|
||||
}
|
||||
|
||||
public void setRestTemplate(RestOperations restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public void setBufferSize(int bufferSize) {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public void setMediaType(MediaType mediaType) {
|
||||
this.mediaType = mediaType;
|
||||
}
|
||||
|
||||
public void setNamingStrategy(OpenTsdbNamingStrategy namingStrategy) {
|
||||
this.namingStrategy = namingStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(Delta<?> delta) {
|
||||
throw new UnsupportedOperationException("Counters not supported via increment");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Metric<?> value) {
|
||||
OpenTsdbData data = new OpenTsdbData(
|
||||
this.namingStrategy.getName(value.getName()), value.getValue(), value
|
||||
.getTimestamp().getTime());
|
||||
this.buffer.add(data);
|
||||
if (this.buffer.size() >= this.bufferSize) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the buffer without waiting for it to fill any further.
|
||||
*/
|
||||
public void flush() {
|
||||
if (this.buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<OpenTsdbData> temp = new ArrayList<OpenTsdbData>();
|
||||
synchronized (this.buffer) {
|
||||
temp.addAll(this.buffer);
|
||||
this.buffer.clear();
|
||||
}
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAccept(Arrays.asList(this.mediaType));
|
||||
headers.setContentType(this.mediaType);
|
||||
HttpEntity<List<OpenTsdbData>> request = new HttpEntity<List<OpenTsdbData>>(temp,
|
||||
headers);
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> response = this.restTemplate.postForEntity(this.url, request,
|
||||
Map.class);
|
||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
||||
logger.warn("Cannot write metrics (discarded " + temp.size() + " values): "
|
||||
+ response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(String metricName) {
|
||||
set(new Metric<Long>(metricName, 0L));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.opentsdb;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class OpenTsdbName {
|
||||
|
||||
private String metric;
|
||||
|
||||
private Map<String, String> tags = new LinkedHashMap<String, String>();
|
||||
|
||||
protected OpenTsdbName() {
|
||||
}
|
||||
|
||||
public OpenTsdbName(String metric) {
|
||||
this.metric = metric;
|
||||
}
|
||||
|
||||
public String getMetric() {
|
||||
return this.metric;
|
||||
}
|
||||
|
||||
public void setMetric(String metric) {
|
||||
this.metric = metric;
|
||||
}
|
||||
|
||||
public Map<String, String> getTags() {
|
||||
return this.tags;
|
||||
}
|
||||
|
||||
public void setTags(Map<String, String> tags) {
|
||||
this.tags.putAll(tags);
|
||||
}
|
||||
|
||||
public void tag(String name, String value) {
|
||||
this.tags.put(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.opentsdb;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public interface OpenTsdbNamingStrategy {
|
||||
|
||||
OpenTsdbName getName(String metricName);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Metrics integration with OpenTSDB.
|
||||
*/
|
||||
package org.springframework.boot.actuate.metrics.opentsdb;
|
||||
|
||||
|
|
@ -29,6 +29,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
|
|||
import org.springframework.data.redis.core.BoundZSetOperations;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link MetricRepository} implementation for a redis backend. Metric values are stored
|
||||
|
|
@ -92,6 +93,8 @@ public class RedisMetricRepository implements MetricRepository {
|
|||
public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,
|
||||
String prefix, String key) {
|
||||
Assert.notNull(redisConnectionFactory, "RedisConnectionFactory must not be null");
|
||||
Assert.state(StringUtils.hasText(prefix), "Prefix must be non-empty");
|
||||
Assert.state(StringUtils.hasText(key), "Key must be non-empty");
|
||||
this.redisOperations = RedisUtils.stringTemplate(redisConnectionFactory);
|
||||
if (!prefix.endsWith(".")) {
|
||||
prefix = prefix + ".";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.statsd;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.writer.Delta;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.timgroup.statsd.NonBlockingStatsDClient;
|
||||
import com.timgroup.statsd.StatsDClientErrorHandler;
|
||||
|
||||
/**
|
||||
* A {@link MetricWriter} that pushes data to statsd. Statsd has the concept of counters
|
||||
* and gauges, but only supports gauges with data type Long, so values will be truncated
|
||||
* towards zero. Metrics whose name contains "timer." (but not "gauge." or "counter.")
|
||||
* will be treated as execution times (in statsd terms). Anything incremented is treated
|
||||
* as a counter, and anything with a snapshot value in {@link #set(Metric)} is treated as
|
||||
* a gauge.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class StatsdMetricWriter implements MetricWriter, Closeable {
|
||||
|
||||
private static Log logger = LogFactory.getLog(StatsdMetricWriter.class);
|
||||
|
||||
private final NonBlockingStatsDClient client;
|
||||
|
||||
/**
|
||||
* Create a new writer with the given parameters.
|
||||
*
|
||||
* @param host the hostname for the statsd server
|
||||
* @param port the port for the statsd server
|
||||
*/
|
||||
public StatsdMetricWriter(String host, int port) {
|
||||
this(null, host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new writer with the given parameters.
|
||||
*
|
||||
* @param prefix the prefix to apply to all metric names (can be null)
|
||||
* @param host the hostname for the statsd server
|
||||
* @param port the port for the statsd server
|
||||
*/
|
||||
public StatsdMetricWriter(String prefix, String host, int port) {
|
||||
prefix = StringUtils.hasText(prefix) ? prefix : null;
|
||||
while (prefix != null && prefix.endsWith(".")) {
|
||||
prefix = prefix.substring(0, prefix.length() - 1);
|
||||
}
|
||||
this.client = new NonBlockingStatsDClient(prefix, host, port,
|
||||
new LoggingStatsdErrorHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(Delta<?> delta) {
|
||||
this.client.count(delta.getName(), delta.getValue().longValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Metric<?> value) {
|
||||
String name = value.getName();
|
||||
if (name.contains("timer.") && !name.contains("gauge.")
|
||||
&& !name.contains("counter.")) {
|
||||
this.client.recordExecutionTime(name, value.getValue().longValue());
|
||||
}
|
||||
else {
|
||||
this.client.gauge(name, value.getValue().longValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(String name) {
|
||||
if (name.contains("counter.")) {
|
||||
this.client.gauge(name, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.client.stop();
|
||||
}
|
||||
|
||||
private static final class LoggingStatsdErrorHandler implements
|
||||
StatsDClientErrorHandler {
|
||||
@Override
|
||||
public void handle(Exception e) {
|
||||
logger.debug("Failed to write metric. Exception: " + e.getClass()
|
||||
+ ", message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Metrics integration with Statsd.
|
||||
*/
|
||||
package org.springframework.boot.actuate.metrics.statsd;
|
||||
|
||||
|
|
@ -48,13 +48,8 @@ public class SimpleInMemoryRepository<T> {
|
|||
synchronized (lock) {
|
||||
T current = this.values.get(name);
|
||||
T value = callback.modify(current);
|
||||
if (current != null) {
|
||||
this.values.replace(name, current, value);
|
||||
}
|
||||
else {
|
||||
this.values.putIfAbsent(name, value);
|
||||
}
|
||||
return this.values.get(name);
|
||||
this.values.put(name, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.writer;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
|
||||
import com.codahale.metrics.Counter;
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.Histogram;
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.Timer;
|
||||
|
||||
/**
|
||||
* A {@link MetricWriter} that send data to a Codahale {@link MetricRegistry} based on a
|
||||
* naming convention:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Updates to {@link #increment(Delta)} with names in "meter.*" are treated as
|
||||
* {@link Meter} events</li>
|
||||
* <li>Other deltas are treated as simple {@link Counter} values</li>
|
||||
* <li>Inputs to {@link #set(Metric)} with names in "histogram.*" are treated as
|
||||
* {@link Histogram} updates</li>
|
||||
* <li>Inputs to {@link #set(Metric)} with names in "timer.*" are treated as {@link Timer}
|
||||
* updates</li>
|
||||
* <li>Other metrics are treated as simple {@link Gauge} values (single valued
|
||||
* measurements of type double)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @deprecated since 1.2.2 in favor of {@link DropwizardMetricWriter}
|
||||
*/
|
||||
@Deprecated
|
||||
public class CodahaleMetricWriter extends DropwizardMetricWriter {
|
||||
|
||||
/**
|
||||
* Create a new {@link DropwizardMetricWriter} instance.
|
||||
* @param registry the underlying metric registry
|
||||
*/
|
||||
public CodahaleMetricWriter(MetricRegistry registry) {
|
||||
super(registry);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.actuate.metrics.writer;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
|
|
@ -28,7 +29,7 @@ import org.springframework.boot.actuate.metrics.Metric;
|
|||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class CompositeMetricWriter implements MetricWriter {
|
||||
public class CompositeMetricWriter implements MetricWriter, Iterable<MetricWriter> {
|
||||
|
||||
private final List<MetricWriter> writers = new ArrayList<MetricWriter>();
|
||||
|
||||
|
|
@ -40,6 +41,11 @@ public class CompositeMetricWriter implements MetricWriter {
|
|||
this.writers.addAll(writers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<MetricWriter> iterator() {
|
||||
return this.writers.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(Delta<?> delta) {
|
||||
for (MetricWriter writer : this.writers) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.boot.actuate.metrics.writer;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
|
||||
/**
|
||||
|
|
@ -27,6 +29,8 @@ public class DefaultCounterService implements CounterService {
|
|||
|
||||
private final MetricWriter writer;
|
||||
|
||||
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Create a {@link DefaultCounterService} instance.
|
||||
* @param writer the underlying writer used to manage metrics
|
||||
|
|
@ -51,10 +55,14 @@ public class DefaultCounterService implements CounterService {
|
|||
}
|
||||
|
||||
private String wrap(String metricName) {
|
||||
if (this.names.containsKey(metricName)) {
|
||||
return this.names.get(metricName);
|
||||
}
|
||||
if (metricName.startsWith("counter") || metricName.startsWith("meter")) {
|
||||
return metricName;
|
||||
}
|
||||
return "counter." + metricName;
|
||||
String name = "counter." + metricName;
|
||||
this.names.put(metricName, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.boot.actuate.metrics.writer;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
|
||||
|
|
@ -28,6 +30,8 @@ public class DefaultGaugeService implements GaugeService {
|
|||
|
||||
private final MetricWriter writer;
|
||||
|
||||
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Create a {@link DefaultGaugeService} instance.
|
||||
* @param writer the underlying writer used to manage metrics
|
||||
|
|
@ -42,11 +46,15 @@ public class DefaultGaugeService implements GaugeService {
|
|||
}
|
||||
|
||||
private String wrap(String metricName) {
|
||||
if (this.names.containsKey(metricName)) {
|
||||
return this.names.get(metricName);
|
||||
}
|
||||
if (metricName.startsWith("gauge") || metricName.startsWith("histogram")
|
||||
|| metricName.startsWith("timer")) {
|
||||
return metricName;
|
||||
}
|
||||
return "gauge." + metricName;
|
||||
String name = "gauge." + metricName;
|
||||
this.names.put(metricName, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.writer;
|
||||
|
||||
import java.io.Flushable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.actuate.metrics.export.MetricCopyExporter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class WriterUtils {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(MetricCopyExporter.class);
|
||||
|
||||
public static void flush(MetricWriter writer) {
|
||||
if (writer instanceof CompositeMetricWriter) {
|
||||
for (MetricWriter element : (CompositeMetricWriter) writer) {
|
||||
flush(element);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (ClassUtils.isPresent("java.io.Flushable", null)) {
|
||||
if (writer instanceof Flushable) {
|
||||
((Flushable) writer).flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Method method = ReflectionUtils.findMethod(writer.getClass(), "flush");
|
||||
if (method != null) {
|
||||
ReflectionUtils.invokeMethod(method, writer);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.warn("Could not flush MetricWriter: " + e.getClass() + ": "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* Metrics integration with Dropwizard Metrics.
|
||||
* Support for writing metrics
|
||||
*/
|
||||
package org.springframework.boot.actuate.metrics.writer;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfigurati
|
|||
org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.MetricsDropwizardAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.MetricsChannelAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.MetricExportAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfigurationTests;
|
||||
import org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfigurationTests;
|
||||
|
||||
/**
|
||||
* A test suite for probing weird ordering problems in the tests.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses({ PublicMetricsAutoConfigurationTests.class,
|
||||
MetricRepositoryAutoConfigurationTests.class })
|
||||
@Ignore
|
||||
public class AdhocTestSuite {
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -16,22 +16,31 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Matchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferCounterService;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferGaugeService;
|
||||
import org.springframework.boot.actuate.metrics.dropwizard.DropwizardMetricServices;
|
||||
import org.springframework.boot.actuate.metrics.export.MetricCopyExporter;
|
||||
import org.springframework.boot.actuate.metrics.export.MetricExporters;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultCounterService;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService;
|
||||
import org.springframework.boot.actuate.metrics.reader.PrefixMetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.integration.channel.FixedSubscriberChannel;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
|
@ -40,10 +49,7 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link MetricRepositoryAutoConfiguration}.
|
||||
|
|
@ -53,76 +59,95 @@ import static org.mockito.Mockito.verify;
|
|||
*/
|
||||
public class MetricRepositoryAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void defaultExecutor() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
ExecutorSubscribableChannel channel = context
|
||||
.getBean(ExecutorSubscribableChannel.class);
|
||||
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) channel.getExecutor();
|
||||
context.close();
|
||||
assertTrue(executor.getThreadPoolExecutor().isShutdown());
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createServices() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SyncTaskExecutorConfiguration.class,
|
||||
this.context = new AnnotationConfigApplicationContext(
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class);
|
||||
GaugeService gaugeService = this.context.getBean(BufferGaugeService.class);
|
||||
assertNotNull(gaugeService);
|
||||
assertNotNull(context.getBean(DefaultCounterService.class));
|
||||
assertNotNull(this.context.getBean(BufferCounterService.class));
|
||||
assertNotNull(this.context.getBean(PrefixMetricReader.class));
|
||||
gaugeService.submit("foo", 2.7);
|
||||
assertEquals(2.7, context.getBean(MetricReader.class).findOne("gauge.foo")
|
||||
assertEquals(2.7, this.context.getBean(MetricReader.class).findOne("gauge.foo")
|
||||
.getValue());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultExporterWhenMessageChannelAvailable() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext(
|
||||
MessageChannelConfiguration.class,
|
||||
MetricRepositoryAutoConfiguration.class,
|
||||
MetricsChannelAutoConfiguration.class,
|
||||
MetricExportAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
MetricExporters exporter = this.context.getBean(MetricExporters.class);
|
||||
assertNotNull(exporter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void provideAdditionalWriter() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SyncTaskExecutorConfiguration.class, WriterConfig.class,
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class);
|
||||
this.context = new AnnotationConfigApplicationContext(WriterConfig.class,
|
||||
MetricRepositoryAutoConfiguration.class,
|
||||
MetricExportAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
GaugeService gaugeService = this.context.getBean(GaugeService.class);
|
||||
assertNotNull(gaugeService);
|
||||
gaugeService.submit("foo", 2.7);
|
||||
MetricWriter writer = context.getBean("writer", MetricWriter.class);
|
||||
verify(writer).set(any(Metric.class));
|
||||
context.close();
|
||||
MetricExporters exporters = this.context.getBean(MetricExporters.class);
|
||||
MetricCopyExporter exporter = (MetricCopyExporter) exporters.getExporters().get(
|
||||
"writer");
|
||||
exporter.setIgnoreTimestamps(true);
|
||||
exporter.export();
|
||||
MetricWriter writer = this.context.getBean("writer", MetricWriter.class);
|
||||
Mockito.verify(writer, Mockito.atLeastOnce()).set(Matchers.any(Metric.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void codahaleInstalledIfPresent() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SyncTaskExecutorConfiguration.class, WriterConfig.class,
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class);
|
||||
public void dropwizardInstalledIfPresent() {
|
||||
this.context = new AnnotationConfigApplicationContext(
|
||||
MetricsDropwizardAutoConfiguration.class,
|
||||
MetricRepositoryAutoConfiguration.class, AopAutoConfiguration.class);
|
||||
GaugeService gaugeService = this.context.getBean(GaugeService.class);
|
||||
assertNotNull(gaugeService);
|
||||
gaugeService.submit("foo", 2.7);
|
||||
MetricRegistry registry = context.getBean(MetricRegistry.class);
|
||||
DropwizardMetricServices exporter = this.context
|
||||
.getBean(DropwizardMetricServices.class);
|
||||
assertEquals(gaugeService, exporter);
|
||||
MetricRegistry registry = this.context.getBean(MetricRegistry.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Gauge<Double> gauge = (Gauge<Double>) registry.getMetrics().get("gauge.foo");
|
||||
assertEquals(new Double(2.7), gauge.getValue());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipsIfBeansExist() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
Config.class, MetricRepositoryAutoConfiguration.class);
|
||||
assertThat(context.getBeansOfType(DefaultGaugeService.class).size(), equalTo(0));
|
||||
assertThat(context.getBeansOfType(DefaultCounterService.class).size(), equalTo(0));
|
||||
context.close();
|
||||
this.context = new AnnotationConfigApplicationContext(Config.class,
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
assertThat(this.context.getBeansOfType(BufferGaugeService.class).size(),
|
||||
equalTo(0));
|
||||
assertThat(this.context.getBeansOfType(BufferCounterService.class).size(),
|
||||
equalTo(0));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class SyncTaskExecutorConfiguration {
|
||||
|
||||
public static class MessageChannelConfiguration {
|
||||
@Bean
|
||||
public Executor metricsExecutor() {
|
||||
return new SyncTaskExecutor();
|
||||
public SubscribableChannel metricsChannel() {
|
||||
return new FixedSubscriberChannel(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
|
@ -130,7 +155,7 @@ public class MetricRepositoryAutoConfigurationTests {
|
|||
|
||||
@Bean
|
||||
public MetricWriter writer() {
|
||||
return mock(MetricWriter.class);
|
||||
return Mockito.mock(MetricWriter.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.aggregate;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
|
||||
import org.springframework.boot.actuate.metrics.writer.Delta;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class AggregateMetricReaderTests {
|
||||
|
||||
private InMemoryMetricRepository source = new InMemoryMetricRepository();
|
||||
|
||||
private AggregateMetricReader reader = new AggregateMetricReader(this.source);
|
||||
|
||||
@Test
|
||||
public void writeAndReadDefaults() {
|
||||
this.source.set(new Metric<Double>("foo.bar.spam", 2.3));
|
||||
assertEquals(2.3, this.reader.findOne("aggregate.spam").getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeAndReadLatestValue() {
|
||||
this.source.set(new Metric<Double>("foo.bar.spam", 2.3, new Date(100L)));
|
||||
this.source.set(new Metric<Double>("oof.rab.spam", 2.4, new Date(0L)));
|
||||
assertEquals(2.3, this.reader.findOne("aggregate.spam").getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeAndReadExtraLong() {
|
||||
this.source.set(new Metric<Double>("blee.foo.bar.spam", 2.3));
|
||||
this.reader.setTruncateKeyLength(3);
|
||||
assertEquals(2.3, this.reader.findOne("aggregate.spam").getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlyPrefixed() {
|
||||
this.source.set(new Metric<Double>("foo.bar.spam", 2.3));
|
||||
assertNull(this.reader.findOne("spam"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incrementCounter() {
|
||||
this.source.increment(new Delta<Long>("foo.bar.counter.spam", 2L));
|
||||
this.source.increment(new Delta<Long>("oof.rab.counter.spam", 3L));
|
||||
assertEquals(5L, this.reader.findOne("aggregate.counter.spam").getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void countGauges() {
|
||||
this.source.set(new Metric<Double>("foo.bar.spam", 2.3));
|
||||
this.source.set(new Metric<Double>("oof.rab.spam", 2.4));
|
||||
assertEquals(1, this.reader.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void countGaugesAndCounters() {
|
||||
this.source.set(new Metric<Double>("foo.bar.spam", 2.3));
|
||||
this.source.set(new Metric<Double>("oof.rab.spam", 2.4));
|
||||
this.source.increment(new Delta<Long>("foo.bar.counter.spam", 2L));
|
||||
this.source.increment(new Delta<Long>("oof.rab.counter.spam", 3L));
|
||||
assertEquals(2, this.reader.count());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.DoubleAdder;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.experimental.theories.DataPoints;
|
||||
import org.junit.experimental.theories.Theories;
|
||||
import org.junit.experimental.theories.Theory;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Speed tests for {@link BufferGaugeService}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(Theories.class)
|
||||
@UsesJava8
|
||||
public class BufferGaugeServiceSpeedTests {
|
||||
|
||||
@DataPoints
|
||||
public static String[] values = new String[10];
|
||||
|
||||
public static String[] names = new String[] { "foo", "bar", "spam", "bucket" };
|
||||
|
||||
public static String[] sample = new String[1000];
|
||||
|
||||
private GaugeBuffers gauges = new GaugeBuffers();
|
||||
|
||||
private GaugeService service = new BufferGaugeService(this.gauges);
|
||||
|
||||
private BufferMetricReader reader = new BufferMetricReader(new CounterBuffers(),
|
||||
this.gauges);
|
||||
|
||||
private static int threadCount = 2;
|
||||
|
||||
private static final int number = Boolean.getBoolean("performance.test") ? 10000000
|
||||
: 100000;
|
||||
|
||||
private static StopWatch watch = new StopWatch("count");
|
||||
|
||||
private static int count;
|
||||
|
||||
private static PrintWriter err;
|
||||
|
||||
@BeforeClass
|
||||
public static void prime() throws FileNotFoundException {
|
||||
err = new PrintWriter("/dev/null");
|
||||
final Random random = new Random();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
sample[i] = names[random.nextInt(names.length)];
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void washup() {
|
||||
System.err.println(watch);
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void raw(String input) throws Exception {
|
||||
iterate("writeRaw");
|
||||
double rate = number / watch.getLastTaskTimeMillis() * 1000;
|
||||
System.err.println("Rate(" + count + ")=" + rate + ", " + watch);
|
||||
watch.start("readRaw" + count);
|
||||
for (String name : names) {
|
||||
this.gauges.forEach(Pattern.compile(name).asPredicate(),
|
||||
new BiConsumer<String, DoubleBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, DoubleBuffer value) {
|
||||
err.println(name + "=" + value);
|
||||
}
|
||||
});
|
||||
}
|
||||
final DoubleAdder total = new DoubleAdder();
|
||||
this.gauges.forEach(Pattern.compile(".*").asPredicate(),
|
||||
new BiConsumer<String, DoubleBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, DoubleBuffer value) {
|
||||
total.add(value.getValue());
|
||||
}
|
||||
});
|
||||
watch.stop();
|
||||
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
|
||||
assertTrue(number * threadCount < total.longValue());
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void reader(String input) throws Exception {
|
||||
iterate("writeReader");
|
||||
double rate = number / watch.getLastTaskTimeMillis() * 1000;
|
||||
System.err.println("Rate(" + count + ")=" + rate + ", " + watch);
|
||||
watch.start("readReader" + count);
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> metric) {
|
||||
err.println(metric);
|
||||
}
|
||||
});
|
||||
final LongAdder total = new LongAdder();
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> value) {
|
||||
total.add(value.getValue().intValue());
|
||||
}
|
||||
});
|
||||
watch.stop();
|
||||
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
|
||||
assertTrue(0 < total.longValue());
|
||||
}
|
||||
|
||||
private void iterate(String taskName) throws Exception {
|
||||
watch.start(taskName + count++);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < number; i++) {
|
||||
String name = sample[i % sample.length];
|
||||
BufferGaugeServiceSpeedTests.this.service.submit(name, count + i);
|
||||
}
|
||||
}
|
||||
};
|
||||
Collection<Future<?>> futures = new HashSet<Future<?>>();
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
futures.add(pool.submit(task));
|
||||
}
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
watch.stop();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* Tests for {@link BufferMetricReader}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class BufferMetricReaderTests {
|
||||
|
||||
private CounterBuffers counters = new CounterBuffers();
|
||||
|
||||
private GaugeBuffers gauges = new GaugeBuffers();
|
||||
|
||||
private BufferMetricReader reader = new BufferMetricReader(this.counters, this.gauges);
|
||||
|
||||
@Test
|
||||
public void countReflectsNumberOfMetrics() {
|
||||
this.gauges.set("foo", 1);
|
||||
this.counters.increment("bar", 2);
|
||||
assertEquals(2, this.reader.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findGauge() {
|
||||
this.gauges.set("foo", 1);
|
||||
assertNotNull(this.reader.findOne("foo"));
|
||||
assertEquals(1, this.reader.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findCounter() {
|
||||
this.counters.increment("foo", 1);
|
||||
assertNotNull(this.reader.findOne("foo"));
|
||||
assertEquals(1, this.reader.count());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Tests for {@link CounterBuffers}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class CounterBuffersTests {
|
||||
|
||||
private CounterBuffers buffers = new CounterBuffers();
|
||||
|
||||
private long value;
|
||||
|
||||
@Test
|
||||
public void inAndOut() {
|
||||
this.buffers.increment("foo", 2);
|
||||
this.buffers.get("foo", new Consumer<LongBuffer>() {
|
||||
@Override
|
||||
public void accept(LongBuffer buffer) {
|
||||
CounterBuffersTests.this.value = buffer.getValue();
|
||||
}
|
||||
});
|
||||
assertEquals(2, this.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNonExistent() {
|
||||
this.buffers.get("foo", new Consumer<LongBuffer>() {
|
||||
@Override
|
||||
public void accept(LongBuffer buffer) {
|
||||
CounterBuffersTests.this.value = buffer.getValue();
|
||||
}
|
||||
});
|
||||
assertEquals(0, this.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNonExistent() {
|
||||
assertNull(this.buffers.find("foo"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.experimental.theories.DataPoints;
|
||||
import org.junit.experimental.theories.Theories;
|
||||
import org.junit.experimental.theories.Theory;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Speed tests for {@link CounterService}.
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(Theories.class)
|
||||
@UsesJava8
|
||||
public class CounterServiceSpeedTests {
|
||||
|
||||
@DataPoints
|
||||
public static String[] values = new String[10];
|
||||
|
||||
public static String[] names = new String[] { "foo", "bar", "spam", "bucket" };
|
||||
|
||||
public static String[] sample = new String[1000];
|
||||
|
||||
private CounterBuffers counters = new CounterBuffers();
|
||||
|
||||
private CounterService service = new BufferCounterService(this.counters);
|
||||
|
||||
private BufferMetricReader reader = new BufferMetricReader(this.counters,
|
||||
new GaugeBuffers());
|
||||
|
||||
private static int threadCount = 2;
|
||||
|
||||
private static final int number = Boolean.getBoolean("performance.test") ? 10000000
|
||||
: 100000;
|
||||
|
||||
private static StopWatch watch = new StopWatch("count");
|
||||
|
||||
private static int count;
|
||||
|
||||
private static PrintWriter err;
|
||||
|
||||
@BeforeClass
|
||||
public static void prime() throws FileNotFoundException {
|
||||
err = new PrintWriter("/dev/null");
|
||||
final Random random = new Random();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
sample[i] = names[random.nextInt(names.length)];
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void washup() {
|
||||
System.err.println(watch);
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void raw(String input) throws Exception {
|
||||
iterate("writeRaw");
|
||||
double rate = number / watch.getLastTaskTimeMillis() * 1000;
|
||||
System.err.println("Rate(" + count + ")=" + rate + ", " + watch);
|
||||
watch.start("readRaw" + count);
|
||||
for (String name : names) {
|
||||
this.counters.forEach(Pattern.compile(name).asPredicate(),
|
||||
new BiConsumer<String, LongBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, LongBuffer value) {
|
||||
err.println(name + "=" + value);
|
||||
}
|
||||
});
|
||||
}
|
||||
final LongAdder total = new LongAdder();
|
||||
this.counters.forEach(Pattern.compile(".*").asPredicate(),
|
||||
new BiConsumer<String, LongBuffer>() {
|
||||
@Override
|
||||
public void accept(String name, LongBuffer value) {
|
||||
total.add(value.getValue());
|
||||
}
|
||||
});
|
||||
watch.stop();
|
||||
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
|
||||
assertEquals(number * threadCount, total.longValue());
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void reader(String input) throws Exception {
|
||||
iterate("writeReader");
|
||||
double rate = number / watch.getLastTaskTimeMillis() * 1000;
|
||||
System.err.println("Rate(" + count + ")=" + rate + ", " + watch);
|
||||
watch.start("readReader" + count);
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> metric) {
|
||||
err.println(metric);
|
||||
}
|
||||
});
|
||||
final LongAdder total = new LongAdder();
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> value) {
|
||||
total.add(value.getValue().intValue());
|
||||
}
|
||||
});
|
||||
watch.stop();
|
||||
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
|
||||
assertEquals(number * threadCount, total.longValue());
|
||||
}
|
||||
|
||||
private void iterate(String taskName) throws Exception {
|
||||
watch.start(taskName + count++);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < number; i++) {
|
||||
String name = sample[i % sample.length];
|
||||
CounterServiceSpeedTests.this.service.increment(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
Collection<Future<?>> futures = new HashSet<Future<?>>();
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
futures.add(pool.submit(task));
|
||||
}
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
watch.stop();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.experimental.theories.DataPoints;
|
||||
import org.junit.experimental.theories.Theories;
|
||||
import org.junit.experimental.theories.Theory;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultCounterService;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Speed tests for {@link DefaultCounterService}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(Theories.class)
|
||||
@UsesJava8
|
||||
public class DefaultCounterServiceSpeedTests {
|
||||
|
||||
@DataPoints
|
||||
public static String[] values = new String[10];
|
||||
|
||||
public static String[] names = new String[] { "foo", "bar", "spam", "bucket" };
|
||||
|
||||
public static String[] sample = new String[1000];
|
||||
|
||||
private InMemoryMetricRepository repository = new InMemoryMetricRepository();
|
||||
|
||||
private CounterService counterService = new DefaultCounterService(this.repository);
|
||||
|
||||
private MetricReader reader = this.repository;
|
||||
|
||||
private static int threadCount = 2;
|
||||
|
||||
private static final int number = Boolean.getBoolean("performance.test") ? 2000000
|
||||
: 100000;
|
||||
|
||||
private static int count;
|
||||
|
||||
private static StopWatch watch = new StopWatch("count");
|
||||
|
||||
private static PrintWriter err;
|
||||
|
||||
@BeforeClass
|
||||
public static void prime() throws FileNotFoundException {
|
||||
err = new PrintWriter("/dev/null");
|
||||
final Random random = new Random();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
sample[i] = names[random.nextInt(names.length)];
|
||||
}
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void counters(String input) throws Exception {
|
||||
watch.start("counters" + count++);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < number; i++) {
|
||||
String name = sample[i % sample.length];
|
||||
DefaultCounterServiceSpeedTests.this.counterService.increment(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
Collection<Future<?>> futures = new HashSet<Future<?>>();
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
futures.add(pool.submit(task));
|
||||
}
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
watch.stop();
|
||||
double rate = number / watch.getLastTaskTimeMillis() * 1000;
|
||||
System.err.println("Counters rate(" + count + ")=" + rate + ", " + watch);
|
||||
watch.start("read" + count);
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> metric) {
|
||||
err.println(metric);
|
||||
}
|
||||
});
|
||||
final LongAdder total = new LongAdder();
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> value) {
|
||||
total.add(value.getValue().intValue());
|
||||
}
|
||||
});
|
||||
watch.stop();
|
||||
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
|
||||
assertEquals(number * threadCount, total.longValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.experimental.theories.DataPoints;
|
||||
import org.junit.experimental.theories.Theories;
|
||||
import org.junit.experimental.theories.Theory;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Speed tests for {@link DefaultGaugeService}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(Theories.class)
|
||||
@UsesJava8
|
||||
public class DefaultGaugeServiceSpeedTests {
|
||||
|
||||
@DataPoints
|
||||
public static String[] values = new String[10];
|
||||
|
||||
public static String[] names = new String[] { "foo", "bar", "spam", "bucket" };
|
||||
|
||||
public static String[] sample = new String[1000];
|
||||
|
||||
private InMemoryMetricRepository repository = new InMemoryMetricRepository();
|
||||
|
||||
private GaugeService gaugeService = new DefaultGaugeService(this.repository);
|
||||
|
||||
private MetricReader reader = this.repository;
|
||||
|
||||
private static int threadCount = 2;
|
||||
|
||||
private static final int number = Boolean.getBoolean("performance.test") ? 5000000
|
||||
: 100000;
|
||||
|
||||
private static int count;
|
||||
|
||||
private static StopWatch watch = new StopWatch("count");
|
||||
|
||||
private static PrintWriter err;
|
||||
|
||||
@BeforeClass
|
||||
public static void prime() throws FileNotFoundException {
|
||||
err = new PrintWriter("/dev/null");
|
||||
final Random random = new Random();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
sample[i] = names[random.nextInt(names.length)];
|
||||
}
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void gauges(String input) throws Exception {
|
||||
watch.start("gauges" + count++);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < number; i++) {
|
||||
String name = sample[i % sample.length];
|
||||
DefaultGaugeServiceSpeedTests.this.gaugeService.submit(name, count
|
||||
+ i);
|
||||
}
|
||||
}
|
||||
};
|
||||
Collection<Future<?>> futures = new HashSet<Future<?>>();
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
futures.add(pool.submit(task));
|
||||
}
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
watch.stop();
|
||||
double rate = number / watch.getLastTaskTimeMillis() * 1000;
|
||||
System.err.println("Gauges rate(" + count + ")=" + rate + ", " + watch);
|
||||
watch.start("read" + count);
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> metric) {
|
||||
err.println(metric);
|
||||
}
|
||||
});
|
||||
final LongAdder total = new LongAdder();
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> value) {
|
||||
total.add(value.getValue().intValue());
|
||||
}
|
||||
});
|
||||
watch.stop();
|
||||
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
|
||||
assertTrue(0 < total.longValue());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.buffer;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.experimental.theories.DataPoints;
|
||||
import org.junit.experimental.theories.Theories;
|
||||
import org.junit.experimental.theories.Theory;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.dropwizard.DropwizardMetricServices;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Speeds tests for {@link DropwizardMetricServices DropwizardMetricServices'}
|
||||
* {@link CounterService}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(Theories.class)
|
||||
@UsesJava8
|
||||
public class DropwizardCounterServiceSpeedTests {
|
||||
|
||||
@DataPoints
|
||||
public static String[] values = new String[10];
|
||||
|
||||
public static String[] names = new String[] { "foo", "bar", "spam", "bucket" };
|
||||
|
||||
public static String[] sample = new String[1000];
|
||||
|
||||
private MetricRegistry registry = new MetricRegistry();
|
||||
|
||||
private CounterService counterService = new DropwizardMetricServices(this.registry);
|
||||
|
||||
private MetricReader reader = new MetricRegistryMetricReader(this.registry);
|
||||
|
||||
private static int threadCount = 2;
|
||||
|
||||
private static final int number = Boolean.getBoolean("performance.test") ? 10000000
|
||||
: 100000;
|
||||
|
||||
private static int count;
|
||||
|
||||
private static StopWatch watch = new StopWatch("count");
|
||||
|
||||
private static PrintWriter err;
|
||||
|
||||
@BeforeClass
|
||||
public static void prime() throws FileNotFoundException {
|
||||
err = new PrintWriter("/dev/null");
|
||||
final Random random = new Random();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
sample[i] = names[random.nextInt(names.length)];
|
||||
}
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void counters(String input) throws Exception {
|
||||
watch.start("counters" + count++);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < number; i++) {
|
||||
String name = sample[i % sample.length];
|
||||
DropwizardCounterServiceSpeedTests.this.counterService
|
||||
.increment(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
Collection<Future<?>> futures = new HashSet<Future<?>>();
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
futures.add(pool.submit(task));
|
||||
}
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
watch.stop();
|
||||
double rate = number / watch.getLastTaskTimeMillis() * 1000;
|
||||
System.err.println("Counters rate(" + count + ")=" + rate + ", " + watch);
|
||||
watch.start("read" + count);
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> metric) {
|
||||
err.println(metric);
|
||||
}
|
||||
});
|
||||
final LongAdder total = new LongAdder();
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> value) {
|
||||
total.add(value.getValue().intValue());
|
||||
}
|
||||
});
|
||||
watch.stop();
|
||||
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
|
||||
assertEquals(number * threadCount, total.longValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,13 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.writer;
|
||||
package org.springframework.boot.actuate.metrics.dropwizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.dropwizard.DropwizardMetricServices;
|
||||
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
|
@ -29,56 +29,60 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
/**
|
||||
* Tests for {@link DropwizardMetricWriter}.
|
||||
*
|
||||
* Tests for {@link DropwizardMetricServices}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DropwizardMetricWriterTests {
|
||||
public class DropwizardMetricServicesTests {
|
||||
|
||||
private final MetricRegistry registry = new MetricRegistry();
|
||||
private final DropwizardMetricWriter writer = new DropwizardMetricWriter(this.registry);
|
||||
private final DropwizardMetricServices writer = new DropwizardMetricServices(
|
||||
this.registry);
|
||||
|
||||
@Test
|
||||
public void incrementCounter() {
|
||||
this.writer.increment(new Delta<Number>("foo", 2));
|
||||
this.writer.increment(new Delta<Number>("foo", 1));
|
||||
assertEquals(3, this.registry.counter("foo").getCount());
|
||||
this.writer.increment("foo");
|
||||
this.writer.increment("foo");
|
||||
this.writer.increment("foo");
|
||||
assertEquals(3, this.registry.counter("counter.foo").getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePredefinedMeter() {
|
||||
this.writer.increment(new Delta<Number>("meter.foo", 2));
|
||||
this.writer.increment(new Delta<Number>("meter.foo", 1));
|
||||
this.writer.increment("meter.foo");
|
||||
this.writer.increment("meter.foo");
|
||||
this.writer.increment("meter.foo");
|
||||
assertEquals(3, this.registry.meter("meter.foo").getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePredefinedCounter() {
|
||||
this.writer.increment(new Delta<Number>("counter.foo", 2));
|
||||
this.writer.increment(new Delta<Number>("counter.foo", 1));
|
||||
this.writer.increment("counter.foo");
|
||||
this.writer.increment("counter.foo");
|
||||
this.writer.increment("counter.foo");
|
||||
assertEquals(3, this.registry.counter("counter.foo").getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setGauge() {
|
||||
this.writer.set(new Metric<Number>("foo", 2.1));
|
||||
this.writer.set(new Metric<Number>("foo", 2.3));
|
||||
this.writer.submit("foo", 2.1);
|
||||
this.writer.submit("foo", 2.3);
|
||||
@SuppressWarnings("unchecked")
|
||||
Gauge<Double> gauge = (Gauge<Double>) this.registry.getMetrics().get("foo");
|
||||
Gauge<Double> gauge = (Gauge<Double>) this.registry.getMetrics().get("gauge.foo");
|
||||
assertEquals(new Double(2.3), gauge.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPredfinedTimer() {
|
||||
this.writer.set(new Metric<Number>("timer.foo", 200));
|
||||
this.writer.set(new Metric<Number>("timer.foo", 300));
|
||||
this.writer.submit("timer.foo", 200);
|
||||
this.writer.submit("timer.foo", 300);
|
||||
assertEquals(2, this.registry.timer("timer.foo").getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPredfinedHistogram() {
|
||||
this.writer.set(new Metric<Number>("histogram.foo", 2.1));
|
||||
this.writer.set(new Metric<Number>("histogram.foo", 2.3));
|
||||
this.writer.submit("histogram.foo", 2.1);
|
||||
this.writer.submit("histogram.foo", 2.3);
|
||||
assertEquals(2, this.registry.histogram("histogram.foo").getCount());
|
||||
}
|
||||
|
||||
|
|
@ -112,9 +116,9 @@ public class DropwizardMetricWriterTests {
|
|||
public static class WriterThread extends Thread {
|
||||
private int index;
|
||||
private boolean failed;
|
||||
private DropwizardMetricWriter writer;
|
||||
private DropwizardMetricServices writer;
|
||||
|
||||
public WriterThread(ThreadGroup group, int index, DropwizardMetricWriter writer) {
|
||||
public WriterThread(ThreadGroup group, int index, DropwizardMetricServices writer) {
|
||||
super(group, "Writer-" + index);
|
||||
|
||||
this.index = index;
|
||||
|
|
@ -129,17 +133,9 @@ public class DropwizardMetricWriterTests {
|
|||
public void run() {
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
try {
|
||||
Metric<Integer> metric1 = new Metric<Integer>("timer.test.service",
|
||||
this.index);
|
||||
this.writer.set(metric1);
|
||||
|
||||
Metric<Integer> metric2 = new Metric<Integer>(
|
||||
"histogram.test.service", this.index);
|
||||
this.writer.set(metric2);
|
||||
|
||||
Metric<Integer> metric3 = new Metric<Integer>("gauge.test.service",
|
||||
this.index);
|
||||
this.writer.set(metric3);
|
||||
this.writer.submit("timer.test.service", this.index);
|
||||
this.writer.submit("histogram.test.service", this.index);
|
||||
this.writer.submit("gauge.test.service", this.index);
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
this.failed = true;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -41,6 +41,33 @@ public class MetricCopyExporterTests {
|
|||
assertEquals(1, this.writer.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportIncludes() {
|
||||
this.exporter.setIncludes("*");
|
||||
this.reader.set(new Metric<Number>("foo", 2.3));
|
||||
this.exporter.export();
|
||||
assertEquals(1, this.writer.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportExcludesWithIncludes() {
|
||||
this.exporter.setIncludes("*");
|
||||
this.exporter.setExcludes("foo");
|
||||
this.reader.set(new Metric<Number>("foo", 2.3));
|
||||
this.reader.set(new Metric<Number>("bar", 2.4));
|
||||
this.exporter.export();
|
||||
assertEquals(1, this.writer.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportExcludesDefaultIncludes() {
|
||||
this.exporter.setExcludes("foo");
|
||||
this.reader.set(new Metric<Number>("foo", 2.3));
|
||||
this.reader.set(new Metric<Number>("bar", 2.4));
|
||||
this.exporter.export();
|
||||
assertEquals(1, this.writer.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timestamp() {
|
||||
this.reader.set(new Metric<Number>("foo", 2.3));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.export;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class MetricExportersTests {
|
||||
|
||||
private MetricExporters exporters;
|
||||
private MetricExportProperties export = new MetricExportProperties();
|
||||
private Map<String, MetricWriter> writers = new LinkedHashMap<String, MetricWriter>();
|
||||
private MetricReader reader = Mockito.mock(MetricReader.class);
|
||||
private MetricWriter writer = Mockito.mock(MetricWriter.class);
|
||||
|
||||
@Test
|
||||
public void emptyWriters() {
|
||||
this.exporters = new MetricExporters(this.reader, this.writers, this.export);
|
||||
this.exporters.configureTasks(new ScheduledTaskRegistrar());
|
||||
assertNotNull(this.exporters.getExporters());
|
||||
assertEquals(0, this.exporters.getExporters().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oneWriter() {
|
||||
this.export.setUpDefaults();
|
||||
this.writers.put("foo", this.writer);
|
||||
this.exporters = new MetricExporters(this.reader, this.writers, this.export);
|
||||
this.exporters.configureTasks(new ScheduledTaskRegistrar());
|
||||
assertNotNull(this.exporters.getExporters());
|
||||
assertEquals(1, this.exporters.getExporters().size());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.jmx;
|
||||
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DefaultMetricNamingStrategyTests {
|
||||
|
||||
private DefaultMetricNamingStrategy strategy = new DefaultMetricNamingStrategy();
|
||||
|
||||
@Test
|
||||
public void simpleName() throws Exception {
|
||||
ObjectName name = this.strategy.getObjectName(null,
|
||||
"domain:type=MetricValue,name=foo");
|
||||
assertEquals("domain", name.getDomain());
|
||||
assertEquals("foo", name.getKeyProperty("type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onePeriod() throws Exception {
|
||||
ObjectName name = this.strategy.getObjectName(null,
|
||||
"domain:type=MetricValue,name=foo.bar");
|
||||
assertEquals("domain", name.getDomain());
|
||||
assertEquals("foo", name.getKeyProperty("type"));
|
||||
assertEquals("Wrong name: " + name, "bar", name.getKeyProperty("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void twoPeriods() throws Exception {
|
||||
ObjectName name = this.strategy.getObjectName(null,
|
||||
"domain:type=MetricValue,name=foo.bar.spam");
|
||||
assertEquals("domain", name.getDomain());
|
||||
assertEquals("foo", name.getKeyProperty("type"));
|
||||
assertEquals("Wrong name: " + name, "bar", name.getKeyProperty("name"));
|
||||
assertEquals("Wrong name: " + name, "spam", name.getKeyProperty("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void threePeriods() throws Exception {
|
||||
ObjectName name = this.strategy.getObjectName(null,
|
||||
"domain:type=MetricValue,name=foo.bar.spam.bucket");
|
||||
assertEquals("domain", name.getDomain());
|
||||
assertEquals("foo", name.getKeyProperty("type"));
|
||||
assertEquals("Wrong name: " + name, "bar", name.getKeyProperty("name"));
|
||||
assertEquals("Wrong name: " + name, "spam.bucket", name.getKeyProperty("value"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.opentsdb;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Matchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class OpenTsdbMetricWriterTests {
|
||||
|
||||
private OpenTsdbMetricWriter writer;
|
||||
private RestOperations restTemplate = Mockito.mock(RestOperations.class);
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
this.writer = new OpenTsdbMetricWriter();
|
||||
this.writer.setRestTemplate(this.restTemplate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postSuccessfullyOnFlush() {
|
||||
this.writer.set(new Metric<Double>("foo", 2.4));
|
||||
given(
|
||||
this.restTemplate.postForEntity(Matchers.anyString(),
|
||||
Matchers.any(Object.class), anyMap()))
|
||||
.willReturn(emptyResponse());
|
||||
this.writer.flush();
|
||||
verify(this.restTemplate).postForEntity(Matchers.anyString(),
|
||||
Matchers.any(Object.class), anyMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushAutomaticlly() {
|
||||
given(
|
||||
this.restTemplate.postForEntity(Matchers.anyString(),
|
||||
Matchers.any(Object.class), anyMap()))
|
||||
.willReturn(emptyResponse());
|
||||
this.writer.setBufferSize(0);
|
||||
this.writer.set(new Metric<Double>("foo", 2.4));
|
||||
verify(this.restTemplate).postForEntity(Matchers.anyString(),
|
||||
Matchers.any(Object.class), anyMap());
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private ResponseEntity<Map> emptyResponse() {
|
||||
return new ResponseEntity<Map>(Collections.emptyMap(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Class<Map> anyMap() {
|
||||
return Matchers.any(Class.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics.statsd;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.SocketException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.writer.Delta;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests for {@link StatsdMetricWriter}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class StatsdMetricWriterTests {
|
||||
|
||||
private int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private DummyStatsDServer server = new DummyStatsDServer(this.port);
|
||||
|
||||
private StatsdMetricWriter writer = new StatsdMetricWriter("me", "localhost",
|
||||
this.port);
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
this.server.stop();
|
||||
this.writer.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void increment() {
|
||||
this.writer.increment(new Delta<Long>("counter.foo", 3L));
|
||||
this.server.waitForMessage();
|
||||
assertEquals("me.counter.foo:3|c", this.server.messagesReceived().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setLongMetric() throws Exception {
|
||||
this.writer.set(new Metric<Long>("gauge.foo", 3L));
|
||||
this.server.waitForMessage();
|
||||
assertEquals("me.gauge.foo:3|g", this.server.messagesReceived().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDoubleMetric() throws Exception {
|
||||
this.writer.set(new Metric<Double>("gauge.foo", 3.7));
|
||||
this.server.waitForMessage();
|
||||
// Doubles are truncated
|
||||
assertEquals("me.gauge.foo:3|g", this.server.messagesReceived().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTimerMetric() throws Exception {
|
||||
this.writer.set(new Metric<Long>("timer.foo", 37L));
|
||||
this.server.waitForMessage();
|
||||
assertEquals("me.timer.foo:37|ms", this.server.messagesReceived().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullPrefix() throws Exception {
|
||||
this.writer = new StatsdMetricWriter("localhost", this.port);
|
||||
this.writer.set(new Metric<Long>("gauge.foo", 3L));
|
||||
this.server.waitForMessage();
|
||||
assertEquals("gauge.foo:3|g", this.server.messagesReceived().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void perioPrefix() throws Exception {
|
||||
this.writer = new StatsdMetricWriter("my.", "localhost", this.port);
|
||||
this.writer.set(new Metric<Long>("gauge.foo", 3L));
|
||||
this.server.waitForMessage();
|
||||
assertEquals("my.gauge.foo:3|g", this.server.messagesReceived().get(0));
|
||||
}
|
||||
|
||||
private static final class DummyStatsDServer {
|
||||
|
||||
private final List<String> messagesReceived = new ArrayList<String>();
|
||||
private final DatagramSocket server;
|
||||
|
||||
public DummyStatsDServer(int port) {
|
||||
try {
|
||||
this.server = new DatagramSocket(port);
|
||||
}
|
||||
catch (SocketException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final DatagramPacket packet = new DatagramPacket(new byte[256],
|
||||
256);
|
||||
DummyStatsDServer.this.server.receive(packet);
|
||||
DummyStatsDServer.this.messagesReceived.add(new String(packet
|
||||
.getData(), Charset.forName("UTF-8")).trim());
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.server.close();
|
||||
}
|
||||
|
||||
public void waitForMessage() {
|
||||
while (this.messagesReceived.isEmpty()) {
|
||||
try {
|
||||
Thread.sleep(50L);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> messagesReceived() {
|
||||
return new ArrayList<String>(this.messagesReceived);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,6 +136,7 @@
|
|||
<spring-social-linkedin.version>1.0.1.RELEASE</spring-social-linkedin.version>
|
||||
<spring-social-twitter.version>1.1.0.RELEASE</spring-social-twitter.version>
|
||||
<spring-ws.version>2.2.1.RELEASE</spring-ws.version>
|
||||
<statsd-client.version>3.0.1</statsd-client.version>
|
||||
<sun-mail.version>${javax-mail.version}</sun-mail.version>
|
||||
<thymeleaf.version>2.1.4.RELEASE</thymeleaf.version>
|
||||
<thymeleaf-extras-springsecurity4.version>2.1.2.RELEASE</thymeleaf-extras-springsecurity4.version>
|
||||
|
|
@ -552,6 +553,11 @@
|
|||
<artifactId>javax.mail</artifactId>
|
||||
<version>${sun-mail.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.timgroup</groupId>
|
||||
<artifactId>java-statsd-client</artifactId>
|
||||
<version>${statsd-client.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
|
|
|
|||
|
|
@ -889,48 +889,227 @@ beans are gathered by the endpoint. You can easily change that by defining your
|
|||
`MetricsEndpoint`.
|
||||
|
||||
|
||||
|
||||
[[production-ready-metric-repositories]]
|
||||
=== Metric repositories
|
||||
Metric service implementations are usually bound to a
|
||||
{sc-spring-boot-actuator}/metrics/repository/MetricRepository.{sc-ext}[`MetricRepository`].
|
||||
A `MetricRepository` is responsible for storing and retrieving metric information. Spring
|
||||
Boot provides an `InMemoryMetricRepository` and a `RedisMetricRepository` out of the
|
||||
box (the in-memory repository is the default) but you can also write your own. The
|
||||
`MetricRepository` interface is actually composed of higher level `MetricReader` and
|
||||
`MetricWriter` interfaces. For full details refer to the
|
||||
{dc-spring-boot-actuator}/metrics/repository/MetricRepository.{dc-ext}[Javadoc].
|
||||
=== Special features with Java 8
|
||||
|
||||
There's nothing to stop you hooking a `MetricRepository` with back-end storage directly
|
||||
into your app, but we recommend using the default `InMemoryMetricRepository`
|
||||
(possibly with a custom `Map` instance if you are worried about heap usage) and
|
||||
populating a back-end repository through a scheduled export job. In that way you get
|
||||
some buffering in memory of the metric values and you can reduce the network
|
||||
chatter by exporting less frequently or in batches. Spring Boot provides
|
||||
an `Exporter` interface and a few basic implementations for you to get started with that.
|
||||
The default implementation of `GaugeService` and `CounterService` provided by Spring Boot
|
||||
depends on the version of Java that you are using. With Java 8 (or better) the
|
||||
implementation switches to a high-performance version optimized for fast writes, backed by
|
||||
atomic in-memory buffers, rather than by the immutable but relatively expensive
|
||||
`Metric<?>` type (counters are approximately 5 times faster and gauges approximately twice
|
||||
as fast as the repository-based implementations). The Dropwizard metrics services (see
|
||||
below) are also very efficient even for Java 7 (they have backports of some of the Java 8
|
||||
concurrency libraries), but they do not record timestamps for metric values. If
|
||||
performance of metric gathering is a concern then it is always advisable to use one of the
|
||||
high-performance options, and also to only read metrics infrequently, so that the writes
|
||||
are buffered locally and only read when needed.
|
||||
|
||||
NOTE: The old `MetricRepository` and its `InMemoryMetricRepository` implementation are not
|
||||
used by default if you are on Java 8 or if you are using Dropwizard metrics.
|
||||
|
||||
|
||||
[[production-ready-code-hale-metrics]]
|
||||
|
||||
[[production-ready-metric-writers]]
|
||||
=== Metric writers and aggregation
|
||||
|
||||
Spring Boot provides a couple of implementations of a marker interface called `Exporter`
|
||||
which can be used to copy metric readings from the in-memory buffers to a place where they
|
||||
can be analysed and displayed. Indeed, if you provide a `@Bean` that implements the
|
||||
`MetricWriter` interface, then it will automatically be hooked up to an `Exporter` and fed
|
||||
metric updates every 5 seconds (configured via `spring.metrics.export.delayMillis`) via a
|
||||
`@Scheduled` annotation in `MetricRepositoryAutoConfiguration`.
|
||||
|
||||
The default exporter is a `MetricCopyExporter` which tries to optimize itself by not
|
||||
copying values that haven't changed since it was last called. The optimization can be
|
||||
switched off using a flag (`spring.metrics.export.ignoreTimestamps`). Note also that the
|
||||
`MetricRegistry` has no support for timestamps, so the optimization is not available if
|
||||
you are using Dropwizard metrics (all metrics will be copied on every tick).
|
||||
|
||||
|
||||
|
||||
[[production-ready-metric-writers-export-to-redis]]
|
||||
==== Example: Export to Redis
|
||||
|
||||
If you provide a `@Bean` of type `RedisMetricRepository` the metrics are exported to a
|
||||
Redis cache for aggregation. The `RedisMetricRepository` has 2 important parameters to
|
||||
configure it for this purpose: `prefix` and `key` (passed into its constructor). It is
|
||||
best to use a prefix that is unique to the application instance (e.g. using a random value
|
||||
and maybe the logical name of the application to make it possible to correlate with other
|
||||
instances of the same application). The "key" is used to keep a global index of all
|
||||
metric names, so it should be unique "globally", whatever that means for your system (e.g.
|
||||
2 instances of the same system could share a Redis cache if they have distinct keys).
|
||||
Example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
||||
@Value("metrics.mysystem.${random.value:0000}.${spring.application.name:application}")
|
||||
private String prefix = "metrics.mysystem";
|
||||
|
||||
@Value("${metrics.key:keys.mysystem}")
|
||||
private String key = "METRICSKEY";
|
||||
|
||||
@Bean
|
||||
MetricWriter metricWriter() {
|
||||
return new RedisMetricRepository(connectionFactory, prefix, key);
|
||||
}
|
||||
----
|
||||
|
||||
The prefix is constructed with the application name at the end, so it can easily be used
|
||||
to identify a group of processes with the same logical name later.
|
||||
|
||||
NOTE: it's important to set both the key and the prefix. The key is used for all
|
||||
repository operations, and can be shared by multiple repositories. If multiple
|
||||
repositories share a key (like in the case where you need to aggregate across them), then
|
||||
you normally have a read-only "master" repository that has a short, but identifiable,
|
||||
prefix (like "metrics.mysystem"), and many write-only repositories with prefixes that
|
||||
start with the master prefix (like `metrics.mysystem.*` in the example above). It is
|
||||
efficient to read all the keys from a "master" repository like that, but inefficient to
|
||||
read a subset with a longer prefix (e.g. using one of the writing repositories).
|
||||
|
||||
|
||||
|
||||
[[production-ready-metric-writers-export-to-open-tdsb]]
|
||||
==== Example: Export to Open TSDB
|
||||
|
||||
If you provide a `@Bean` of type `OpenTsdbHttpMetricWriter` the metrics are exported to
|
||||
http://opentsdb.net/[Open TSDB] for aggregation. The `OpenTsdbHttpMetricWriter` has a
|
||||
`url` property that you need to set to the Open TSDB "/put" endpoint, e.g.
|
||||
`http://localhost:4242/api/put`). It also has a `namingStrategy` that you can customize or
|
||||
configure to make the metrics match the data structure you need on the server. By default
|
||||
it just passes through the metric name as an Open TSDB metric name and adds a tag "domain"
|
||||
with value "org.springframework.metrics" and another tag "process" with value equals to
|
||||
the object hash of the naming strategy. Thus, after running the application and generating
|
||||
some metrics (e.g. by pinging the home page) you can inspect the metrics in the TDB UI
|
||||
(http://localhost:4242 by default). Example:
|
||||
|
||||
[source,indent=0]
|
||||
----
|
||||
curl localhost:4242/api/query?start=1h-ago&m=max:counter.status.200.root
|
||||
[
|
||||
{
|
||||
"metric": "counter.status.200.root",
|
||||
"tags": {
|
||||
"domain": "org.springframework.metrics",
|
||||
"process": "b968a76"
|
||||
},
|
||||
"aggregateTags": [],
|
||||
"dps": {
|
||||
"1430492872": 2,
|
||||
"1430492875": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[production-ready-metric-writers-export-to-statsd]]
|
||||
==== Example: Export to Statsd
|
||||
If you provide a `@Bean` of type `StatsdMetricWriter` the metrics are exported to a
|
||||
statsd server:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Value("${spring.application.name:application}.${random.value:0000}")
|
||||
private String prefix = "metrics";
|
||||
|
||||
@Value("${statsd.host:localhost}")
|
||||
private String host = "localhost";
|
||||
|
||||
@Value("${statsd.port:8125}")
|
||||
private int port;
|
||||
|
||||
@Bean
|
||||
MetricWriter metricWriter() {
|
||||
return new StatsdMetricWriter(prefix, host, port);
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[production-ready-metric-writers-export-to-jmx]]
|
||||
==== Example: Export to JMX
|
||||
If you provide a `@Bean` of type `JmxMetricWriter` the metrics are exported as MBeans to
|
||||
the local server (the `MBeanExporter` is provided by Spring Boot JMX autoconfiguration as
|
||||
long as it is switched on). Metrics can then be inspected, graphed, alerted etc. using any
|
||||
tool that understands JMX (e.g. JConsole or JVisualVM). Example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Bean
|
||||
MetricWriter metricWriter(MBeanExporter exporter) {
|
||||
return new JmxMetricWriter(exporter);
|
||||
}
|
||||
----
|
||||
|
||||
Each metric is exported as an individual MBean. The format for the `ObjectNames` is given
|
||||
by an `ObjectNamingStrategy` which can be injected into the `JmxMetricWriter` (the default
|
||||
breaks up the metric name and tags the first two period-separated sections in a way that
|
||||
should make the metrics group nicely in JVisualVM or JConsole).
|
||||
|
||||
|
||||
|
||||
[[production-ready-metric-aggregation]]
|
||||
=== Aggregating metrics from multiple sources
|
||||
There is an `AggregateMetricReader` that you can use to consolidate metrics from different
|
||||
physical sources. Sources for the same logical metric just need to publish them with a
|
||||
period-separated prefix, and the reader will aggregate (by truncating the metric names,
|
||||
and dropping the prefix). Counters are summed and everything else (i.e. gauges) take their
|
||||
most recent value.
|
||||
|
||||
This is very useful (for instance) if multiple application instances are feeding to a
|
||||
central (e.g. redis) repository and you want to display the results. Particularly
|
||||
recommended in conjunction with a `MetricReaderPublicMetrics` for hooking up to the
|
||||
results to the "/metrics" endpoint. Example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Bean
|
||||
public PublicMetrics metricsAggregate() {
|
||||
return new MetricReaderPublicMetrics(aggregates());
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected MetricReader repository(RedisConnectionFactory connectionFactory) {
|
||||
RedisMetricRepository repository = new RedisMetricRepository(connectionFactory,
|
||||
"mysystem", "myorg.keys");
|
||||
return repository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected MetricReader aggregates() {
|
||||
AggregateMetricReader repository = new AggregateMetricReader(repository());
|
||||
return repository;
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[production-ready-dropwizard-metrics]]
|
||||
=== Dropwizard Metrics
|
||||
User of the https://dropwizard.github.io/metrics/[Dropwizard '`Metrics`' library] will
|
||||
automatically find that Spring Boot metrics are published to
|
||||
`com.codahale.metrics.MetricRegistry`. A default `com.codahale.metrics.MetricRegistry`
|
||||
Spring bean will be created when you declare a dependency to the
|
||||
`io.dropwizard.metrics:metrics-core` library; you can also register you own `@Bean`
|
||||
instance if you need customizations. Metrics from the `MetricRegistry` are also
|
||||
automatically exposed via the `/metrics` endpoint.
|
||||
A default `MetricRegistry` Spring bean will be created when you declare a dependency to
|
||||
the `io.dropwizard.metrics:metric-core` library; you can also register you own `@Bean`
|
||||
instance if you need customizations. Users of the
|
||||
https://dropwizard.github.io/metrics/[Dropwizard '`Metrics`' library] will find that
|
||||
Spring Boot metrics are automatically published to `com.codahale.metrics.MetricRegistry`.
|
||||
Metrics from the `MetricRegistry` are also automatically exposed via the `/metrics`
|
||||
endpoint
|
||||
|
||||
Users can create Dropwizard metrics by prefixing their metric names with the appropriate
|
||||
type (e.g. `+histogram.*+`, `+meter.*+`).
|
||||
When Dropwizard metrics are in use, the default `CounterService` and `GaugeService` are
|
||||
replaced with a `DropwizardMetricServices`, which is a wrapper around the `MetricRegistry`
|
||||
(so you can `@Autowired` one of those services and use it as normal). You can also create
|
||||
"special" Dropwizard metrics by prefixing your metric names with the appropriate type
|
||||
(i.e. `+timer.*+`, `+histogram.*+` for gauges, and `+meter.*+` for counters).
|
||||
|
||||
|
||||
|
||||
[[production-ready-metrics-message-channel-integration]]
|
||||
=== Message channel integration
|
||||
If the '`Spring Messaging`' jar is on your classpath a `MessageChannel` called
|
||||
`metricsChannel` is automatically created (unless one already exists). All metric update
|
||||
events are additionally published as '`messages`' on that channel. Additional analysis or
|
||||
actions can be taken by clients subscribing to that channel.
|
||||
If a `MessageChannel` bean called `metricsChannel` exists, then a `MetricWriter` will be
|
||||
created that writes metrics to that channel. The writer is automatically hooked up to an
|
||||
exporter (as for all writers), so all metric values will appear on the channel, and
|
||||
additional analysis or actions can be taken by subscribers (it's up to you to provide the
|
||||
channel and any subscribers you need).
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@
|
|||
<module>spring-boot-sample-jta-bitronix</module>
|
||||
<module>spring-boot-sample-jta-jndi</module>
|
||||
<module>spring-boot-sample-liquibase</module>
|
||||
<module>spring-boot-sample-metrics-dropwizard</module>
|
||||
<module>spring-boot-sample-metrics-opentsdb</module>
|
||||
<module>spring-boot-sample-metrics-redis</module>
|
||||
<module>spring-boot-sample-parent-context</module>
|
||||
<module>spring-boot-sample-profile</module>
|
||||
<module>spring-boot-sample-secure</module>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-samples</artifactId>
|
||||
<version>1.3.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-sample-metrics-dropwizard</artifactId>
|
||||
<name>spring-boot-sample-metrics-dropwizard</name>
|
||||
<description>Spring Boot Metrics Redis Sample</description>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.dropwizard;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "service", ignoreUnknownFields = false)
|
||||
public class HelloWorldService {
|
||||
|
||||
private String name = "World";
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getHelloMessage() {
|
||||
return "Hello " + this.name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.dropwizard;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.validator.constraints.NotBlank;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.context.annotation.Description;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@Description("A controller for handling requests for hello messages")
|
||||
public class SampleController {
|
||||
|
||||
@Autowired
|
||||
private HelloWorldService helloWorldService;
|
||||
|
||||
@Autowired
|
||||
private GaugeService gauges;
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public Map<String, String> hello() {
|
||||
this.gauges.submit("timer.test.value", Math.random() * 1000 + 1000);
|
||||
return Collections.singletonMap("message",
|
||||
this.helloWorldService.getHelloMessage());
|
||||
}
|
||||
|
||||
protected static class Message {
|
||||
|
||||
@NotBlank(message = "Message value cannot be empty")
|
||||
private String value;
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.dropwizard;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SampleDropwizardMetricsApplication {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SpringApplication.run(SampleDropwizardMetricsApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
service.name: Phil
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.dropwizard;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Basic integration tests for {@link SampleDropwizardMetricsApplication}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = SampleDropwizardMetricsApplication.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest("server.port=0")
|
||||
@DirtiesContext
|
||||
public class SampleDropwizardMetricsApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private MetricRegistry registry;
|
||||
|
||||
@Autowired
|
||||
private GaugeService gauges;
|
||||
|
||||
@Test
|
||||
public void timerCreated() {
|
||||
this.gauges.submit("timer.test", 1234);
|
||||
assertEquals(1, this.registry.getTimers().get("timer.test").getCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
Spring Boot sample with Open TSDB export for metrics.
|
||||
|
||||
Start opentsdb, e.g. with [Docker Compose]()
|
||||
|
||||
[source,indent=0]
|
||||
----
|
||||
$ docker-compose up
|
||||
----
|
||||
|
||||
Run the app and ping the home page (http://localhost:8080) a few times. Go and look at
|
||||
the result in the TDB UI, e.g.
|
||||
|
||||
[source,indent=0]
|
||||
----
|
||||
$ curl localhost:4242/api/query?start=1h-ago&m=max:counter.status.200.root
|
||||
[
|
||||
{
|
||||
"metric": "counter.status.200.root",
|
||||
"tags": {
|
||||
"prefix": "spring.b968a76"
|
||||
},
|
||||
"aggregateTags": [],
|
||||
"dps": {
|
||||
"1430492872": 2,
|
||||
"1430492875": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
----
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
opentsdb:
|
||||
image: lancope/opentsdb
|
||||
ports:
|
||||
- "4242:4242"
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-samples</artifactId>
|
||||
<version>1.3.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-sample-metrics-opentsdb</artifactId>
|
||||
<name>spring-boot-sample-metrics-opentsdb</name>
|
||||
<description>Spring Boot Actuator Sample</description>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.opentsdb;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "service", ignoreUnknownFields = false)
|
||||
public class HelloWorldService {
|
||||
|
||||
private String name = "World";
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getHelloMessage() {
|
||||
return "Hello " + this.name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.opentsdb;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.validator.constraints.NotBlank;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Description;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@Description("A controller for handling requests for hello messages")
|
||||
public class SampleController {
|
||||
|
||||
@Autowired
|
||||
private HelloWorldService helloWorldService;
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public Map<String, String> hello() {
|
||||
return Collections.singletonMap("message",
|
||||
this.helloWorldService.getHelloMessage());
|
||||
}
|
||||
|
||||
protected static class Message {
|
||||
|
||||
@NotBlank(message = "Message value cannot be empty")
|
||||
private String value;
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.opentsdb;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.metrics.opentsdb.DefaultOpenTsdbNamingStrategy;
|
||||
import org.springframework.boot.actuate.metrics.opentsdb.OpenTsdbMetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.opentsdb.OpenTsdbNamingStrategy;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SampleOpenTsdbExportApplication {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SpringApplication.run(SampleOpenTsdbExportApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("metrics.export")
|
||||
public MetricWriter openTsdbMetricWriter() {
|
||||
OpenTsdbMetricWriter writer = new OpenTsdbMetricWriter();
|
||||
writer.setNamingStrategy(namingStrategy());
|
||||
return writer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("metrics.names")
|
||||
public OpenTsdbNamingStrategy namingStrategy() {
|
||||
return new DefaultOpenTsdbNamingStrategy();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
service.name: Phil
|
||||
metrics.names.tags.process: ${spring.application.name:application}:${random.value:0000}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.opentsdb;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
/**
|
||||
* Basic integration tests for {@link SampleOpenTsdbExportApplication}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = SampleOpenTsdbExportApplication.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest("server.port=0")
|
||||
@DirtiesContext
|
||||
public class SampleOpenTsdbExportApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
Spring Boot sample with Redis export for metrics.
|
||||
|
||||
Start redis, e.g. with [Docker Compose]()
|
||||
|
||||
[source,indent=0]
|
||||
----
|
||||
$ docker-compose up
|
||||
----
|
||||
|
||||
Run the app and ping the home page (http://localhost:8080) a few times. Go and look at
|
||||
the result in Redis, e.g.
|
||||
|
||||
[source,indent=0]
|
||||
----
|
||||
$ redis-cli
|
||||
127.0.0.1:6379> keys *
|
||||
1) "keys.spring.metrics"
|
||||
2) "spring.metrics.counter.status.200.root"
|
||||
3) "spring.metrics.gauge.response.root"
|
||||
127.0.0.1:6379> zrange keys.spring.metrics 0 0 WITHSCORES
|
||||
1) "spring.metrics.counter.status.200.root"
|
||||
2) "4"
|
||||
----
|
||||
|
||||
There is also an `AggregateMetricReader` with public metrics in the application context,
|
||||
and you can see the result in the "/metrics" (metrics with names in "aggregate.*").
|
||||
The way the Redis repository was set up (with a random key in the metric names) makes the
|
||||
aggregates work across restarts of the same application, or across a scaled up application
|
||||
running in multiple processes. E.g.
|
||||
|
||||
[source,indent=0]
|
||||
----
|
||||
$ curl localhost:8080/metrics
|
||||
{
|
||||
...
|
||||
"aggregate.application.counter.status.200.metrics": 12,
|
||||
"aggregate.application.counter.status.200.root": 29,
|
||||
"aggregate.application.gauge.response.metrics": 43,
|
||||
"aggregate.application.gauge.response.root": 5,
|
||||
"counter.status.200.root": 2,
|
||||
"counter.status.200.metrics": 1,
|
||||
"gauge.response.metrics": 43,
|
||||
"gauge.response.root": 5
|
||||
}
|
||||
----
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-samples</artifactId>
|
||||
<version>1.3.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-sample-metrics-redis</artifactId>
|
||||
<name>spring-boot-sample-metrics-redis</name>
|
||||
<description>Spring Boot Metrics Redis Sample</description>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.redis;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
|
||||
import org.springframework.boot.actuate.endpoint.PublicMetrics;
|
||||
import org.springframework.boot.actuate.metrics.aggregate.AggregateMetricReader;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
public class AggregateMetricsConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ExportProperties export;
|
||||
|
||||
@Autowired
|
||||
private RedisConnectionFactory connectionFactory;
|
||||
|
||||
@Bean
|
||||
public PublicMetrics metricsAggregate() {
|
||||
return new MetricReaderPublicMetrics(aggregatesMetricReader());
|
||||
}
|
||||
|
||||
private MetricReader globalMetricsForAggregation() {
|
||||
return new RedisMetricRepository(this.connectionFactory,
|
||||
this.export.getAggregatePrefix(), this.export.getKey());
|
||||
}
|
||||
|
||||
private MetricReader aggregatesMetricReader() {
|
||||
AggregateMetricReader repository = new AggregateMetricReader(
|
||||
globalMetricsForAggregation());
|
||||
return repository;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.redis;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@ConfigurationProperties("redis.metrics.export")
|
||||
class ExportProperties {
|
||||
|
||||
private String prefix = "spring.metrics";
|
||||
private String key = "keys.spring.metrics";
|
||||
|
||||
public String getPrefix() {
|
||||
return this.prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getAggregatePrefix() {
|
||||
String[] tokens = StringUtils.delimitedListToStringArray(this.prefix, ".");
|
||||
if (tokens.length > 1) {
|
||||
if (StringUtils.hasText(tokens[1])) {
|
||||
// If the prefix has 2 or more non-trivial parts, use the first 1
|
||||
return tokens[0];
|
||||
}
|
||||
}
|
||||
return this.prefix;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.redis;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "service", ignoreUnknownFields = false)
|
||||
public class HelloWorldService {
|
||||
|
||||
private String name = "World";
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getHelloMessage() {
|
||||
return "Hello " + this.name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.redis;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.validator.constraints.NotBlank;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Description;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@Description("A controller for handling requests for hello messages")
|
||||
public class SampleController {
|
||||
|
||||
@Autowired
|
||||
private HelloWorldService helloWorldService;
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public Map<String, String> hello() {
|
||||
return Collections.singletonMap("message",
|
||||
this.helloWorldService.getHelloMessage());
|
||||
}
|
||||
|
||||
protected static class Message {
|
||||
|
||||
@NotBlank(message = "Message value cannot be empty")
|
||||
private String value;
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.redis;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.metrics.jmx.JmxMetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.jmx.export.MBeanExporter;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties(ExportProperties.class)
|
||||
public class SampleRedisExportApplication {
|
||||
|
||||
@Autowired
|
||||
private ExportProperties export;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SpringApplication.run(SampleRedisExportApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisMetricRepository redisMetricWriter(
|
||||
RedisConnectionFactory connectionFactory) {
|
||||
return new RedisMetricRepository(connectionFactory, this.export.getPrefix(),
|
||||
this.export.getKey());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JmxMetricWriter jmxMetricWriter(
|
||||
@Qualifier("mbeanExporter") MBeanExporter exporter) {
|
||||
return new JmxMetricWriter(exporter);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
service.name: Phil
|
||||
redis.metrics.export.prefix: metrics.sample.${random.value:0000}.${spring.application.name:application}
|
||||
redis.metrics.export.key: keys.metrics.sample
|
||||
spring.jmx.default-domain: org.springframework.boot
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.metrics.redis;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
/**
|
||||
* Basic integration tests for {@link SampleRedisExportApplication}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = SampleRedisExportApplication.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest({ "server.port=0", "spring.jmx.enabled=true" })
|
||||
@DirtiesContext
|
||||
public class SampleRedisExportApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue