mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Add counter and gauge services based on in-memory buffers
This seems pretty efficient (approx 12M write/s as opposed to 2M with the DefaultCounterService). N.B. there is no need to change most of the rest of the metrics stuff because metrics are write-often, read- seldom, so we don't need high performance reads as much. The Spring Integration configuration and Dropwizard support has changed a bit. Functionally very similar and probably opaque to users, but now the messaging operates as an Exporter on a @Scheduled method, and Dropwizard is a replacement [Gauge,Counter]Service. Metrics are all collected live in-memory (and can be very fast with Java 8), buffered there and shipped out to a MessageChannel (if one exists with id "metricsChannel") in a background thread. We can still use Java 8 library APIs (like LongAdder) but to compile to java 7 compatible byte code we have to forgo the use of lambdas :-( and shorthand generics (<>). Fixes gh-2682, fixes gh-2513 (for Java 8 and Dropwizard users).
This commit is contained in:
parent
1b3efd41f5
commit
53a61474aa
@ -17,6 +17,7 @@
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/..</main.basedir>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Compile -->
|
||||
@ -214,6 +215,11 @@
|
||||
<artifactId>tomcat-embed-logging-juli</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.crashub</groupId>
|
||||
<artifactId>crash.connectors.telnet</artifactId>
|
||||
@ -234,5 +240,10 @@
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -17,36 +17,37 @@
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
|
||||
import org.springframework.boot.actuate.endpoint.PublicMetrics;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferCounterService;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferGaugeService;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferMetricReader;
|
||||
import org.springframework.boot.actuate.metrics.buffer.CounterBuffers;
|
||||
import org.springframework.boot.actuate.metrics.buffer.GaugeBuffers;
|
||||
import org.springframework.boot.actuate.metrics.export.Exporter;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader;
|
||||
import org.springframework.boot.actuate.metrics.export.MetricCopyExporter;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
|
||||
import org.springframework.boot.actuate.metrics.repository.MetricRepository;
|
||||
import org.springframework.boot.actuate.metrics.writer.CompositeMetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultCounterService;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService;
|
||||
import org.springframework.boot.actuate.metrics.writer.DropwizardMetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.writer.MessageChannelMetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriterMessageHandler;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.Range;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
@ -83,32 +84,77 @@ import com.codahale.metrics.MetricRegistry;
|
||||
* @see CounterService
|
||||
* @see MetricWriter
|
||||
* @see InMemoryMetricRepository
|
||||
* @see DropwizardMetricWriter
|
||||
* @see Exporter
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(MetricsProperties.class)
|
||||
public class MetricRepositoryAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private MetricWriter writer;
|
||||
@Configuration
|
||||
@ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN)
|
||||
@ConditionalOnMissingBean(MetricRepository.class)
|
||||
static class LegacyMetricServicesConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CounterService counterService() {
|
||||
return new DefaultCounterService(this.writer);
|
||||
}
|
||||
@Autowired
|
||||
private MetricWriter writer;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CounterService counterService() {
|
||||
return new DefaultCounterService(this.writer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GaugeService gaugeService() {
|
||||
return new DefaultGaugeService(this.writer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GaugeService gaugeService() {
|
||||
return new DefaultGaugeService(this.writer);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnJava(value = JavaVersion.EIGHT)
|
||||
@ConditionalOnMissingBean(MetricRepository.class)
|
||||
static class MetricRepositoryConfiguration {
|
||||
static class FastMetricServicesConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CounterBuffers counterBuffers() {
|
||||
return new CounterBuffers();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GaugeBuffers gaugeBuffers() {
|
||||
return new GaugeBuffers();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public BufferMetricReader metricReader(CounterBuffers counters,
|
||||
GaugeBuffers gauges) {
|
||||
return new BufferMetricReader(counters, gauges);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CounterService counterService(CounterBuffers writer) {
|
||||
return new BufferCounterService(writer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GaugeService gaugeService(GaugeBuffers writer) {
|
||||
return new BufferGaugeService(writer);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnJava(value = JavaVersion.EIGHT, range = Range.OLDER_THAN)
|
||||
@ConditionalOnMissingBean(MetricRepository.class)
|
||||
static class LegacyMetricRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
public InMemoryMetricRepository actuatorMetricRepository() {
|
||||
@ -118,69 +164,25 @@ public class MetricRepositoryAutoConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(MessageChannel.class)
|
||||
static class MetricsChannelConfiguration {
|
||||
@EnableScheduling
|
||||
@ConditionalOnProperty(value = "spring.metrics.export.enabled", matchIfMissing = true)
|
||||
static class DefaultMetricsExporterConfiguration {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("metricsExecutor")
|
||||
private Executor executor;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "metricsChannel")
|
||||
public SubscribableChannel metricsChannel() {
|
||||
return new ExecutorSubscribableChannel(this.executor);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "metricsExecutor")
|
||||
public Executor metricsExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnMissingBean(name = "primaryMetricWriter")
|
||||
public MetricWriter primaryMetricWriter(
|
||||
@Qualifier("metricsChannel") SubscribableChannel channel,
|
||||
List<MetricWriter> writers) {
|
||||
final MetricWriter observer = new CompositeMetricWriter(writers);
|
||||
channel.subscribe(new MetricWriterMessageHandler(observer));
|
||||
return new MessageChannelMetricWriter(channel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(MetricRegistry.class)
|
||||
static class DropwizardMetricRegistryConfiguration {
|
||||
@Autowired(required = false)
|
||||
private List<MetricWriter> writers;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MetricRegistry metricRegistry() {
|
||||
return new MetricRegistry();
|
||||
@ConditionalOnBean(MetricWriter.class)
|
||||
public MetricCopyExporter messageChannelMetricExporter(MetricReader reader) {
|
||||
return new MetricCopyExporter(reader, new CompositeMetricWriter(this.writers)) {
|
||||
@Scheduled(fixedDelayString = "${spring.metrics.export.delayMillis:5000}")
|
||||
@Override
|
||||
public void export() {
|
||||
super.export();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DropwizardMetricWriter dropwizardMetricWriter(MetricRegistry metricRegistry) {
|
||||
return new DropwizardMetricWriter(metricRegistry);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnMissingClass(name = "org.springframework.messaging.MessageChannel")
|
||||
@ConditionalOnMissingBean(name = "primaryMetricWriter")
|
||||
public MetricWriter primaryMetricWriter(List<MetricWriter> writers) {
|
||||
return new CompositeMetricWriter(writers);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PublicMetrics dropwizardPublicMetrics(MetricRegistry metricRegistry) {
|
||||
MetricRegistryMetricReader reader = new MetricRegistryMetricReader(
|
||||
metricRegistry);
|
||||
return new MetricReaderPublicMetrics(reader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.reader.MetricRegistryMetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.DropwizardMetricServices;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Dropwizard-based metrics.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(MetricRegistry.class)
|
||||
@AutoConfigureBefore(MetricRepositoryAutoConfiguration.class)
|
||||
public class MetricsDropwizardAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MetricRegistry metricRegistry() {
|
||||
return new MetricRegistry();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean({ DropwizardMetricServices.class, CounterService.class,
|
||||
GaugeService.class })
|
||||
public DropwizardMetricServices dropwizardMetricServices(MetricRegistry metricRegistry) {
|
||||
return new DropwizardMetricServices(metricRegistry);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PublicMetrics dropwizardPublicMetrics(MetricRegistry metricRegistry) {
|
||||
MetricRegistryMetricReader reader = new MetricRegistryMetricReader(metricRegistry);
|
||||
return new MetricReaderPublicMetrics(reader);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@ConfigurationProperties("spring.metrics")
|
||||
public class MetricsProperties {
|
||||
|
||||
private Export export = new Export();
|
||||
|
||||
public Export getExport() {
|
||||
return this.export;
|
||||
}
|
||||
|
||||
public static class Export {
|
||||
/**
|
||||
* Delay in milliseconds between export ticks. Metrics are exported to external
|
||||
* sources on a schedule with this delay.
|
||||
*/
|
||||
private long delayMillis;
|
||||
|
||||
/**
|
||||
* Flag to enable metric export (assuming a MetricWriter is available).
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Flag to switch off an optimization based on not exporting unchanged metric
|
||||
* values.
|
||||
*/
|
||||
private boolean ignoreTimestamps = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public long getDelayMillis() {
|
||||
return this.delayMillis;
|
||||
}
|
||||
|
||||
public void setDelayMillis(long delayMillis) {
|
||||
this.delayMillis = delayMillis;
|
||||
}
|
||||
|
||||
public boolean isIgnoreTimestamps() {
|
||||
return this.ignoreTimestamps;
|
||||
}
|
||||
|
||||
public void setIgnoreTimestamps(boolean ignoreTimestamps) {
|
||||
this.ignoreTimestamps = ignoreTimestamps;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -20,7 +20,7 @@ package org.springframework.boot.actuate.metrics.export;
|
||||
* Generic interface for metric exports. As you scale up metric collection you will often
|
||||
* need to buffer metric data locally and export it periodically (e.g. for aggregation
|
||||
* across a cluster), so this is the marker interface for those operations. The trigger of
|
||||
* an export operation might be periodic or even driven, but it remains outside the scope
|
||||
* an export operation might be periodic or event driven, but it remains outside the scope
|
||||
* of this interface. You might for instance create an instance of an Exporter and trigger
|
||||
* it using a {@code @Scheduled} annotation in a Spring ApplicationContext.
|
||||
*
|
||||
|
@ -48,13 +48,8 @@ public class SimpleInMemoryRepository<T> {
|
||||
synchronized (lock) {
|
||||
T current = this.values.get(name);
|
||||
T value = callback.modify(current);
|
||||
if (current != null) {
|
||||
this.values.replace(name, current, value);
|
||||
}
|
||||
else {
|
||||
this.values.putIfAbsent(name, value);
|
||||
}
|
||||
return this.values.get(name);
|
||||
this.values.put(name, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
|
||||
import com.codahale.metrics.Counter;
|
||||
import com.codahale.metrics.Gauge;
|
||||
@ -30,77 +31,107 @@ import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.Timer;
|
||||
|
||||
/**
|
||||
* A {@link MetricWriter} that send data to a Codahale {@link MetricRegistry} based on a
|
||||
* naming convention:
|
||||
* A {@link GaugeService} and {@link CounterService} that sends data to a Dropwizard
|
||||
* {@link MetricRegistry} based on a naming convention:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Updates to {@link #increment(Delta)} with names in "meter.*" are treated as
|
||||
* <li>Updates to {@link #increment(String)} with names in "meter.*" are treated as
|
||||
* {@link Meter} events</li>
|
||||
* <li>Other deltas are treated as simple {@link Counter} values</li>
|
||||
* <li>Inputs to {@link #set(Metric)} with names in "histogram.*" are treated as
|
||||
* {@link Histogram} updates</li>
|
||||
* <li>Inputs to {@link #set(Metric)} with names in "timer.*" are treated as {@link Timer}
|
||||
* updates</li>
|
||||
* <li>Inputs to {@link #submit(String, double)} with names in "histogram.*" are treated
|
||||
* as {@link Histogram} updates</li>
|
||||
* <li>Inputs to {@link #submit(String, double)} with names in "timer.*" are treated as
|
||||
* {@link Timer} updates</li>
|
||||
* <li>Other metrics are treated as simple {@link Gauge} values (single valued
|
||||
* measurements of type double)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DropwizardMetricWriter implements MetricWriter {
|
||||
public class DropwizardMetricServices implements CounterService, GaugeService {
|
||||
|
||||
private final MetricRegistry registry;
|
||||
|
||||
private final ConcurrentMap<String, Object> gaugeLocks = new ConcurrentHashMap<String, Object>();
|
||||
|
||||
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Create a new {@link DropwizardMetricWriter} instance.
|
||||
* Create a new {@link DropwizardMetricServices} instance.
|
||||
* @param registry the underlying metric registry
|
||||
*/
|
||||
public DropwizardMetricWriter(MetricRegistry registry) {
|
||||
public DropwizardMetricServices(MetricRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(Delta<?> delta) {
|
||||
String name = delta.getName();
|
||||
long value = delta.getValue().longValue();
|
||||
public void increment(String name) {
|
||||
incrementInternal(name, 1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrement(String name) {
|
||||
incrementInternal(name, -1L);
|
||||
}
|
||||
|
||||
private void incrementInternal(String name, long value) {
|
||||
if (name.startsWith("meter")) {
|
||||
Meter meter = this.registry.meter(name);
|
||||
meter.mark(value);
|
||||
}
|
||||
else {
|
||||
name = wrapCounterName(name);
|
||||
Counter counter = this.registry.counter(name);
|
||||
counter.inc(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Metric<?> value) {
|
||||
String name = value.getName();
|
||||
public void submit(String name, double value) {
|
||||
if (name.startsWith("histogram")) {
|
||||
long longValue = value.getValue().longValue();
|
||||
long longValue = (long) value;
|
||||
Histogram metric = this.registry.histogram(name);
|
||||
metric.update(longValue);
|
||||
}
|
||||
else if (name.startsWith("timer")) {
|
||||
long longValue = value.getValue().longValue();
|
||||
long longValue = (long) value;
|
||||
Timer metric = this.registry.timer(name);
|
||||
metric.update(longValue, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else {
|
||||
final double gauge = value.getValue().doubleValue();
|
||||
name = wrapGaugeName(name);
|
||||
final double gauge = value;
|
||||
// Ensure we synchronize to avoid another thread pre-empting this thread after
|
||||
// remove causing an error in CodaHale metrics
|
||||
// NOTE: CodaHale provides no way to do this atomically
|
||||
synchronized (getGuageLock(name)) {
|
||||
// remove causing an error in Dropwizard metrics
|
||||
// NOTE: Dropwizard provides no way to do this atomically
|
||||
synchronized (getGaugeLock(name)) {
|
||||
this.registry.remove(name);
|
||||
this.registry.register(name, new SimpleGauge(gauge));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object getGuageLock(String name) {
|
||||
private String wrapGaugeName(String metricName) {
|
||||
return wrapName(metricName, "gauge.");
|
||||
}
|
||||
|
||||
private String wrapCounterName(String metricName) {
|
||||
return wrapName(metricName, "counter.");
|
||||
}
|
||||
|
||||
private String wrapName(String metricName, String prefix) {
|
||||
if (this.names.containsKey(metricName)) {
|
||||
return this.names.get(metricName);
|
||||
}
|
||||
if (metricName.startsWith(prefix)) {
|
||||
return metricName;
|
||||
}
|
||||
String name = prefix + metricName;
|
||||
this.names.put(metricName, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
private Object getGaugeLock(String name) {
|
||||
Object lock = this.gaugeLocks.get(name);
|
||||
if (lock == null) {
|
||||
Object newLock = new Object();
|
||||
@ -111,8 +142,11 @@ public class DropwizardMetricWriter implements MetricWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(String metricName) {
|
||||
this.registry.remove(metricName);
|
||||
public void reset(String name) {
|
||||
if (!name.startsWith("meter")) {
|
||||
name = wrapCounterName(name);
|
||||
}
|
||||
this.registry.remove(name);
|
||||
}
|
||||
|
||||
/**
|
@ -11,6 +11,8 @@ org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfigurati
|
||||
org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.MetricsDropwizardAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.MetricsChannelAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration
|
||||
|
@ -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,30 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Matchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.boot.actuate.metrics.GaugeService;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferCounterService;
|
||||
import org.springframework.boot.actuate.metrics.buffer.BufferGaugeService;
|
||||
import org.springframework.boot.actuate.metrics.export.MetricCopyExporter;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultCounterService;
|
||||
import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService;
|
||||
import org.springframework.boot.actuate.metrics.reader.PrefixMetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.DropwizardMetricServices;
|
||||
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.integration.channel.FixedSubscriberChannel;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
@ -40,10 +48,7 @@ import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link MetricRepositoryAutoConfiguration}.
|
||||
@ -53,76 +58,90 @@ import static org.mockito.Mockito.verify;
|
||||
*/
|
||||
public class MetricRepositoryAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void defaultExecutor() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
ExecutorSubscribableChannel channel = context
|
||||
.getBean(ExecutorSubscribableChannel.class);
|
||||
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) channel.getExecutor();
|
||||
context.close();
|
||||
assertTrue(executor.getThreadPoolExecutor().isShutdown());
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createServices() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SyncTaskExecutorConfiguration.class,
|
||||
this.context = new AnnotationConfigApplicationContext(
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class);
|
||||
GaugeService gaugeService = this.context.getBean(BufferGaugeService.class);
|
||||
assertNotNull(gaugeService);
|
||||
assertNotNull(context.getBean(DefaultCounterService.class));
|
||||
assertNotNull(this.context.getBean(BufferCounterService.class));
|
||||
assertNotNull(this.context.getBean(PrefixMetricReader.class));
|
||||
gaugeService.submit("foo", 2.7);
|
||||
assertEquals(2.7, context.getBean(MetricReader.class).findOne("gauge.foo")
|
||||
assertEquals(2.7, this.context.getBean(MetricReader.class).findOne("gauge.foo")
|
||||
.getValue());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultExporterWhenMessageChannelAvailable() throws Exception {
|
||||
this.context = new AnnotationConfigApplicationContext(
|
||||
MessageChannelConfiguration.class, MetricsChannelAutoConfiguration.class,
|
||||
MetricRepositoryAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
MetricCopyExporter exporter = this.context.getBean(MetricCopyExporter.class);
|
||||
assertNotNull(exporter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void provideAdditionalWriter() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SyncTaskExecutorConfiguration.class, WriterConfig.class,
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class);
|
||||
this.context = new AnnotationConfigApplicationContext(WriterConfig.class,
|
||||
MetricRepositoryAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
GaugeService gaugeService = this.context.getBean(GaugeService.class);
|
||||
assertNotNull(gaugeService);
|
||||
gaugeService.submit("foo", 2.7);
|
||||
MetricWriter writer = context.getBean("writer", MetricWriter.class);
|
||||
verify(writer).set(any(Metric.class));
|
||||
context.close();
|
||||
MetricCopyExporter exporter = this.context.getBean(MetricCopyExporter.class);
|
||||
exporter.setIgnoreTimestamps(true);
|
||||
exporter.export();
|
||||
MetricWriter writer = this.context.getBean("writer", MetricWriter.class);
|
||||
Mockito.verify(writer, Mockito.atLeastOnce()).set(Matchers.any(Metric.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void codahaleInstalledIfPresent() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SyncTaskExecutorConfiguration.class, WriterConfig.class,
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class);
|
||||
public void dropwizardInstalledIfPresent() {
|
||||
this.context = new AnnotationConfigApplicationContext(
|
||||
MetricsDropwizardAutoConfiguration.class,
|
||||
MetricRepositoryAutoConfiguration.class, AopAutoConfiguration.class);
|
||||
GaugeService gaugeService = this.context.getBean(GaugeService.class);
|
||||
assertNotNull(gaugeService);
|
||||
gaugeService.submit("foo", 2.7);
|
||||
MetricRegistry registry = context.getBean(MetricRegistry.class);
|
||||
DropwizardMetricServices exporter = this.context
|
||||
.getBean(DropwizardMetricServices.class);
|
||||
assertEquals(gaugeService, exporter);
|
||||
MetricRegistry registry = this.context.getBean(MetricRegistry.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Gauge<Double> gauge = (Gauge<Double>) registry.getMetrics().get("gauge.foo");
|
||||
assertEquals(new Double(2.7), gauge.getValue());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipsIfBeansExist() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
Config.class, MetricRepositoryAutoConfiguration.class);
|
||||
assertThat(context.getBeansOfType(DefaultGaugeService.class).size(), equalTo(0));
|
||||
assertThat(context.getBeansOfType(DefaultCounterService.class).size(), equalTo(0));
|
||||
context.close();
|
||||
this.context = new AnnotationConfigApplicationContext(Config.class,
|
||||
MetricRepositoryAutoConfiguration.class);
|
||||
assertThat(this.context.getBeansOfType(BufferGaugeService.class).size(),
|
||||
equalTo(0));
|
||||
assertThat(this.context.getBeansOfType(BufferCounterService.class).size(),
|
||||
equalTo(0));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class SyncTaskExecutorConfiguration {
|
||||
|
||||
public static class MessageChannelConfiguration {
|
||||
@Bean
|
||||
public Executor metricsExecutor() {
|
||||
return new SyncTaskExecutor();
|
||||
public SubscribableChannel metricsChannel() {
|
||||
return new FixedSubscriberChannel(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ -130,7 +149,7 @@ public class MetricRepositoryAutoConfigurationTests {
|
||||
|
||||
@Bean
|
||||
public MetricWriter writer() {
|
||||
return mock(MetricWriter.class);
|
||||
return Mockito.mock(MetricWriter.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.reader.MetricReader;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader;
|
||||
import org.springframework.boot.actuate.metrics.writer.DropwizardMetricServices;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Speeds tests for {@link DropwizardMetricServices DropwizardMetricServices'}
|
||||
* {@link CounterService}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(Theories.class)
|
||||
@UsesJava8
|
||||
public class DropwizardCounterServiceSpeedTests {
|
||||
|
||||
@DataPoints
|
||||
public static String[] values = new String[10];
|
||||
|
||||
public static String[] names = new String[] { "foo", "bar", "spam", "bucket" };
|
||||
|
||||
public static String[] sample = new String[1000];
|
||||
|
||||
private MetricRegistry registry = new MetricRegistry();
|
||||
|
||||
private CounterService counterService = new DropwizardMetricServices(this.registry);
|
||||
|
||||
private MetricReader reader = new MetricRegistryMetricReader(this.registry);
|
||||
|
||||
private static int threadCount = 2;
|
||||
|
||||
private static final int number = Boolean.getBoolean("performance.test") ? 10000000
|
||||
: 100000;
|
||||
|
||||
private static int count;
|
||||
|
||||
private static StopWatch watch = new StopWatch("count");
|
||||
|
||||
private static PrintWriter err;
|
||||
|
||||
@BeforeClass
|
||||
public static void prime() throws FileNotFoundException {
|
||||
err = new PrintWriter("/dev/null");
|
||||
final Random random = new Random();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
sample[i] = names[random.nextInt(names.length)];
|
||||
}
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void counters(String input) throws Exception {
|
||||
watch.start("counters" + count++);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < number; i++) {
|
||||
String name = sample[i % sample.length];
|
||||
DropwizardCounterServiceSpeedTests.this.counterService
|
||||
.increment(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
Collection<Future<?>> futures = new HashSet<Future<?>>();
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
futures.add(pool.submit(task));
|
||||
}
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
watch.stop();
|
||||
double rate = number / watch.getLastTaskTimeMillis() * 1000;
|
||||
System.err.println("Counters rate(" + count + ")=" + rate + ", " + watch);
|
||||
watch.start("read" + count);
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> metric) {
|
||||
err.println(metric);
|
||||
}
|
||||
});
|
||||
final LongAdder total = new LongAdder();
|
||||
this.reader.findAll().forEach(new Consumer<Metric<?>>() {
|
||||
@Override
|
||||
public void accept(Metric<?> value) {
|
||||
total.add(value.getValue().intValue());
|
||||
}
|
||||
});
|
||||
watch.stop();
|
||||
System.err.println("Read(" + count + ")=" + watch.getLastTaskTimeMillis() + "ms");
|
||||
assertEquals(number * threadCount, total.longValue());
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -20,7 +20,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.actuate.metrics.Metric;
|
||||
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
@ -29,56 +28,60 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
/**
|
||||
* Tests for {@link DropwizardMetricWriter}.
|
||||
*
|
||||
* Tests for {@link DropwizardMetricServices}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class DropwizardMetricWriterTests {
|
||||
public class DropwizardMetricServicesTests {
|
||||
|
||||
private final MetricRegistry registry = new MetricRegistry();
|
||||
private final DropwizardMetricWriter writer = new DropwizardMetricWriter(this.registry);
|
||||
private final DropwizardMetricServices writer = new DropwizardMetricServices(
|
||||
this.registry);
|
||||
|
||||
@Test
|
||||
public void incrementCounter() {
|
||||
this.writer.increment(new Delta<Number>("foo", 2));
|
||||
this.writer.increment(new Delta<Number>("foo", 1));
|
||||
assertEquals(3, this.registry.counter("foo").getCount());
|
||||
this.writer.increment("foo");
|
||||
this.writer.increment("foo");
|
||||
this.writer.increment("foo");
|
||||
assertEquals(3, this.registry.counter("counter.foo").getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePredefinedMeter() {
|
||||
this.writer.increment(new Delta<Number>("meter.foo", 2));
|
||||
this.writer.increment(new Delta<Number>("meter.foo", 1));
|
||||
this.writer.increment("meter.foo");
|
||||
this.writer.increment("meter.foo");
|
||||
this.writer.increment("meter.foo");
|
||||
assertEquals(3, this.registry.meter("meter.foo").getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePredefinedCounter() {
|
||||
this.writer.increment(new Delta<Number>("counter.foo", 2));
|
||||
this.writer.increment(new Delta<Number>("counter.foo", 1));
|
||||
this.writer.increment("counter.foo");
|
||||
this.writer.increment("counter.foo");
|
||||
this.writer.increment("counter.foo");
|
||||
assertEquals(3, this.registry.counter("counter.foo").getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setGauge() {
|
||||
this.writer.set(new Metric<Number>("foo", 2.1));
|
||||
this.writer.set(new Metric<Number>("foo", 2.3));
|
||||
this.writer.submit("foo", 2.1);
|
||||
this.writer.submit("foo", 2.3);
|
||||
@SuppressWarnings("unchecked")
|
||||
Gauge<Double> gauge = (Gauge<Double>) this.registry.getMetrics().get("foo");
|
||||
Gauge<Double> gauge = (Gauge<Double>) this.registry.getMetrics().get("gauge.foo");
|
||||
assertEquals(new Double(2.3), gauge.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPredfinedTimer() {
|
||||
this.writer.set(new Metric<Number>("timer.foo", 200));
|
||||
this.writer.set(new Metric<Number>("timer.foo", 300));
|
||||
this.writer.submit("timer.foo", 200);
|
||||
this.writer.submit("timer.foo", 300);
|
||||
assertEquals(2, this.registry.timer("timer.foo").getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPredfinedHistogram() {
|
||||
this.writer.set(new Metric<Number>("histogram.foo", 2.1));
|
||||
this.writer.set(new Metric<Number>("histogram.foo", 2.3));
|
||||
this.writer.submit("histogram.foo", 2.1);
|
||||
this.writer.submit("histogram.foo", 2.3);
|
||||
assertEquals(2, this.registry.histogram("histogram.foo").getCount());
|
||||
}
|
||||
|
||||
@ -112,9 +115,9 @@ public class DropwizardMetricWriterTests {
|
||||
public static class WriterThread extends Thread {
|
||||
private int index;
|
||||
private boolean failed;
|
||||
private DropwizardMetricWriter writer;
|
||||
private DropwizardMetricServices writer;
|
||||
|
||||
public WriterThread(ThreadGroup group, int index, DropwizardMetricWriter writer) {
|
||||
public WriterThread(ThreadGroup group, int index, DropwizardMetricServices writer) {
|
||||
super(group, "Writer-" + index);
|
||||
|
||||
this.index = index;
|
||||
@ -129,17 +132,9 @@ public class DropwizardMetricWriterTests {
|
||||
public void run() {
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
try {
|
||||
Metric<Integer> metric1 = new Metric<Integer>("timer.test.service",
|
||||
this.index);
|
||||
this.writer.set(metric1);
|
||||
|
||||
Metric<Integer> metric2 = new Metric<Integer>(
|
||||
"histogram.test.service", this.index);
|
||||
this.writer.set(metric2);
|
||||
|
||||
Metric<Integer> metric3 = new Metric<Integer>("gauge.test.service",
|
||||
this.index);
|
||||
this.writer.set(metric3);
|
||||
this.writer.submit("timer.test.service", this.index);
|
||||
this.writer.submit("histogram.test.service", this.index);
|
||||
this.writer.submit("gauge.test.service", this.index);
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
this.failed = true;
|
@ -889,30 +889,77 @@ beans are gathered by the endpoint. You can easily change that by defining your
|
||||
`MetricsEndpoint`.
|
||||
|
||||
|
||||
|
||||
[[production-ready-metric-repositories]]
|
||||
=== Metric repositories
|
||||
Metric service implementations are usually bound to a
|
||||
{sc-spring-boot-actuator}/metrics/repository/MetricRepository.{sc-ext}[`MetricRepository`].
|
||||
A `MetricRepository` is responsible for storing and retrieving metric information. Spring
|
||||
Boot provides an `InMemoryMetricRepository` and a `RedisMetricRepository` out of the
|
||||
box (the in-memory repository is the default) but you can also write your own. The
|
||||
`MetricRepository` interface is actually composed of higher level `MetricReader` and
|
||||
`MetricWriter` interfaces. For full details refer to the
|
||||
{dc-spring-boot-actuator}/metrics/repository/MetricRepository.{dc-ext}[Javadoc].
|
||||
=== Performance
|
||||
|
||||
There's nothing to stop you hooking a `MetricRepository` with back-end storage directly
|
||||
into your app, but we recommend using the default `InMemoryMetricRepository`
|
||||
(possibly with a custom `Map` instance if you are worried about heap usage) and
|
||||
populating a back-end repository through a scheduled export job. In that way you get
|
||||
some buffering in memory of the metric values and you can reduce the network
|
||||
chatter by exporting less frequently or in batches. Spring Boot provides
|
||||
an `Exporter` interface and a few basic implementations for you to get started with that.
|
||||
The default implementation of `GaugeService` and `CounterService` provided by Spring Boot
|
||||
depends on the version of Java that you are using. With Java 8 (or better) the
|
||||
implementation switches to a high-performance version optimized for fast writes, backed by
|
||||
atomic in-memory buffers, rather than by the immutable but relatively expensive
|
||||
`Metric<?>` type (counters are approximately 5 times faster and gauges approximately twice
|
||||
as fast as the repository-based implementations). The Dropwizard metrics services (see
|
||||
below) are also very efficient even for Java 7 (they have backports of some of the Java 8
|
||||
concurrency libraries), but they do not record timestamps for metric values. If
|
||||
performance of metric gathering is a concern then it is always advisable to use one of the
|
||||
high-performance options, and also to only read metrics infrequently, so that the writes
|
||||
are buffered locally and only read when needed.
|
||||
|
||||
NOTE: The old `MetricRepository` and its `InMemoryMetricRepository` implementation are not
|
||||
used by default if you are on Java 8 or if you are using Dropwizard metrics.
|
||||
|
||||
|
||||
[[production-ready-code-hale-metrics]]
|
||||
|
||||
[[production-ready-metric-writers]]
|
||||
=== Metric writers and aggregation
|
||||
|
||||
Spring Boot provides a couple of implementations of a marker interface called `Exporter`
|
||||
which can be used to copy metric readings from the in-memory buffers to a place where they
|
||||
can be analysed and displayed. Indeed, if you provide a `@Bean` that implements the
|
||||
`MetricWriter` interface, then it will automatically be hooked up to an `Exporter` and fed
|
||||
metric updates every 5 seconds (configured via `spring.metrics.export.delayMillis`) via a
|
||||
`@Scheduled` annotation in `MetricRepositoryAutoConfiguration`.
|
||||
|
||||
The default exporter is a `MetricCopyExporter` which tries to optimize itself by not
|
||||
copying values that haven't changed since it was last called. The optimization can be
|
||||
switched off using a flag (`spring.metrics.export.ignoreTimestamps`). Note also that the
|
||||
`MetricRegistry` has no support for timestamps, so the optimization is not available if
|
||||
you are using Dropwizard metrics (all metrics will be copied on every tick).
|
||||
|
||||
|
||||
|
||||
[[production-ready-metric-writers-export-to-redis]]
|
||||
==== Example: Export to Redis
|
||||
|
||||
If you provide a `@Bean` of type `RedisMetricRepository` the metrics are exported to a
|
||||
Redis cache for aggregation. The `RedisMetricRepository` has 2 important parameters to
|
||||
configure it for this purpose: `prefix` and `key` (passed into its constructor). It is
|
||||
best to use a prefix that is unique to the application instance (e.g. using a random value
|
||||
and maybe the logical name of the application to make it possible to correlate with other
|
||||
instances of the same application). The "key" is used to keep a global index of all
|
||||
metric names, so it should be unique "globally", whatever that means for your system (e.g.
|
||||
2 instances of the same system could share a Redis cache if they have distinct keys).
|
||||
Example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
||||
@Value("${spring.application.name:application}.${random.value:0000}")
|
||||
private String prefix = "metrics;
|
||||
|
||||
@Value("${metrics.key:METRICSKEY}")
|
||||
private String key = "KEY;
|
||||
|
||||
@Bean
|
||||
MetricWriter metricWriter() {
|
||||
return new RedisMetricRepository(connectionFactory, prefix, key);
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[production-ready-dropwizard-metrics]]
|
||||
=== Dropwizard Metrics
|
||||
User of the https://dropwizard.github.io/metrics/[Dropwizard '`Metrics`' library] will
|
||||
Users of the https://dropwizard.github.io/metrics/[Dropwizard '`Metrics`' library] will
|
||||
automatically find that Spring Boot metrics are published to
|
||||
`com.codahale.metrics.MetricRegistry`. A default `com.codahale.metrics.MetricRegistry`
|
||||
Spring bean will be created when you declare a dependency to the
|
||||
@ -920,17 +967,21 @@ Spring bean will be created when you declare a dependency to the
|
||||
instance if you need customizations. Metrics from the `MetricRegistry` are also
|
||||
automatically exposed via the `/metrics` endpoint.
|
||||
|
||||
Users can create Dropwizard metrics by prefixing their metric names with the appropriate
|
||||
type (e.g. `+histogram.*+`, `+meter.*+`).
|
||||
When Dropwizard metrics are in use, the default `CounterService` and `GaugeService` are
|
||||
replaced with a `DropwizardMetricServices`, which is a wrapper around the `MetricRegistry`
|
||||
(so you can `@Autowired` one of those services and use it as normal). You can also create
|
||||
"special" Dropwizard metrics by prefixing your metric names with the appropriate type
|
||||
(i.e. `+timer.*+`, `+histogram.*+` for gauges, and `+meter.*+` for counters).
|
||||
|
||||
|
||||
|
||||
[[production-ready-metrics-message-channel-integration]]
|
||||
=== Message channel integration
|
||||
If the '`Spring Messaging`' jar is on your classpath a `MessageChannel` called
|
||||
`metricsChannel` is automatically created (unless one already exists). All metric update
|
||||
events are additionally published as '`messages`' on that channel. Additional analysis or
|
||||
actions can be taken by clients subscribing to that channel.
|
||||
If a `MessageChannel` bean called `metricsChannel` exists, then a `MetricWriter` will be
|
||||
created that writes metrics to that channel. The writer is automatically hooked up to an
|
||||
exporter (as for all writers), so all metric values will appear on the channel, and
|
||||
additional analysis or actions can be taken by subscribers (it's up to you to provide the
|
||||
channel and any subscribers you need).
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user