findAll() {
+ return this.repository.findAll();
+ }
+
+ @Override
+ public long count() {
+ return this.repository.count();
+ }
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/rich/RichGauge.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/rich/RichGauge.java
new file mode 100644
index 00000000000..211fbc4f6f6
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/rich/RichGauge.java
@@ -0,0 +1,190 @@
+package org.springframework.boot.actuate.metrics.rich;
+
+import org.springframework.util.Assert;
+
+/**
+ * A gauge which stores the maximum, minimum and average in addition to the current value.
+ *
+ * The value of the average will depend on whether a weight ('alpha') is set for the
+ * gauge. If it is unset, the average will contain a simple arithmetic mean. If a weight
+ * is set, an exponential moving average will be calculated as defined in this NIST
+ * document.
+ *
+ * @author Luke Taylor
+ */
+public final class RichGauge {
+
+ private final String name;
+
+ private double value;
+
+ private double average;
+
+ private double max;
+
+ private double min;
+
+ private long count;
+
+ private double alpha;
+
+ /**
+ * Creates an "empty" gauge.
+ *
+ * The average, max and min will be zero, but this initial value will not be included
+ * after the first value has been set on the gauge.
+ *
+ * @param name the name under which the gauge will be stored.
+ */
+ public RichGauge(String name) {
+ this(name, 0.0);
+ this.count = 0;
+ }
+
+ public RichGauge(String name, double value) {
+ Assert.notNull(name, "The gauge name cannot be null or empty");
+ this.name = name;
+ this.value = value;
+ this.average = this.value;
+ this.min = this.value;
+ this.max = this.value;
+ this.alpha = -1.0;
+ this.count = 1;
+ }
+
+ public RichGauge(String name, double value, double alpha, double mean, double max,
+ double min, long count) {
+ this.name = name;
+ this.value = value;
+ this.alpha = alpha;
+ this.average = mean;
+ this.max = max;
+ this.min = min;
+ this.count = count;
+ }
+
+ /**
+ * @return the name of the gauge
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * @return the current value
+ */
+ public double getValue() {
+ return this.value;
+ }
+
+ /**
+ * Either an exponential weighted moving average or a simple mean, respectively,
+ * depending on whether the weight 'alpha' has been set for this gauge.
+ *
+ * @return The average over all the accumulated values
+ */
+ public double getAverage() {
+ return this.average;
+ }
+
+ /**
+ * @return the maximum value
+ */
+ public double getMax() {
+ return this.max;
+ }
+
+ /**
+ * @return the minimum value
+ */
+ public double getMin() {
+ return this.min;
+ }
+
+ /**
+ * @return Number of times the value has been set.
+ */
+ public long getCount() {
+ return this.count;
+ }
+
+ /**
+ * @return the smoothing constant value.
+ */
+ public double getAlpha() {
+ return this.alpha;
+ }
+
+ public RichGauge setAlpha(double alpha) {
+ Assert.isTrue(alpha == -1 || (alpha > 0.0 && alpha < 1.0),
+ "Smoothing constant must be between 0 and 1, or -1 to use arithmetic mean");
+ this.alpha = alpha;
+ return this;
+ }
+
+ RichGauge set(double value) {
+ if (this.count == 0) {
+ this.max = value;
+ this.min = value;
+ }
+ else if (value > this.max) {
+ this.max = value;
+ }
+ else if (value < this.min) {
+ this.min = value;
+ }
+
+ if (this.alpha > 0.0 && this.count > 0) {
+ this.average = this.alpha * this.value + (1 - this.alpha) * this.average;
+ }
+ else {
+ double sum = this.average * this.count;
+ sum += value;
+ this.average = sum / (this.count + 1);
+ }
+ this.count++;
+ this.value = value;
+ return this;
+ }
+
+ RichGauge reset() {
+ this.value = 0.0;
+ this.max = 0.0;
+ this.min = 0.0;
+ this.average = 0.0;
+ this.count = 0;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ RichGauge richGauge = (RichGauge) o;
+
+ if (!this.name.equals(richGauge.name)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.name.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Gauge [name = " + this.name + ", value = " + this.value + ", alpha = "
+ + this.alpha + ", average = " + this.average + ", max = " + this.max
+ + ", min = " + this.min + ", count = " + this.count + "]";
+ }
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/rich/RichGaugeReader.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/rich/RichGaugeReader.java
new file mode 100644
index 00000000000..c1a5d80b8d9
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/rich/RichGaugeReader.java
@@ -0,0 +1,32 @@
+/*
+ * 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.metrics.rich;
+
+/**
+ * A basic set of read operations for {@link RichGauge} instances.
+ *
+ * @author Dave Syer
+ */
+public interface RichGaugeReader {
+
+ RichGauge findOne(String name);
+
+ Iterable findAll();
+
+ long count();
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/rich/RichGaugeRepository.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/rich/RichGaugeRepository.java
new file mode 100644
index 00000000000..7af85e9b199
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/rich/RichGaugeRepository.java
@@ -0,0 +1,28 @@
+/*
+ * 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.metrics.rich;
+
+import org.springframework.boot.actuate.metrics.writer.MetricWriter;
+
+/**
+ * Convenient combination of reader and writer concerns for {@link RichGauge} instances.
+ *
+ * @author Dave Syer
+ */
+public interface RichGaugeRepository extends RichGaugeReader, MetricWriter {
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/util/SimpleInMemoryRepository.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/util/SimpleInMemoryRepository.java
new file mode 100644
index 00000000000..a434a0b2e84
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/util/SimpleInMemoryRepository.java
@@ -0,0 +1,103 @@
+/*
+ * 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.metrics.util;
+
+import java.util.ArrayList;
+import java.util.NavigableMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+
+/**
+ * Repository utility that stores stuff in memory with period-separated String keys.
+ *
+ * @author Dave Syer
+ */
+public class SimpleInMemoryRepository {
+
+ private ConcurrentNavigableMap values = new ConcurrentSkipListMap();
+
+ private ConcurrentMap locks = new ConcurrentHashMap();
+
+ public static interface Callback {
+ T modify(T current);
+ }
+
+ public T update(String name, Callback callback) {
+ Object lock = this.locks.putIfAbsent(name, new Object());
+ if (lock == null) {
+ lock = this.locks.get(name);
+ }
+ 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);
+ }
+ }
+
+ public void set(String name, T value) {
+ T current = this.values.get(name);
+ if (current != null) {
+ this.values.replace(name, current, value);
+ }
+ else {
+ this.values.putIfAbsent(name, value);
+ }
+ }
+
+ public long count() {
+ return this.values.size();
+ }
+
+ public void remove(String name) {
+ this.values.remove(name);
+ }
+
+ public T findOne(String name) {
+ if (this.values.containsKey(name)) {
+ return this.values.get(name);
+ }
+ return null;
+ }
+
+ public Iterable findAll() {
+ return new ArrayList(this.values.values());
+ }
+
+ public Iterable findAllWithPrefix(String prefix) {
+ if (prefix.endsWith(".*")) {
+ prefix = prefix.substring(0, prefix.length() - 1);
+ }
+ if (!prefix.endsWith(".")) {
+ prefix = prefix + ".";
+ }
+ return new ArrayList(this.values.subMap(prefix, false, prefix + "~", true)
+ .values());
+ }
+
+ protected NavigableMap getValues() {
+ return this.values;
+ }
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/CodahaleMetricWriter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/CodahaleMetricWriter.java
new file mode 100644
index 00000000000..47c5f9d4e25
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/CodahaleMetricWriter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.metrics.writer;
+
+import java.util.concurrent.TimeUnit;
+
+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:
+ *
+ * - Updates to {@link #increment(Delta)} with names in "meter.*" are treated as
+ * {@link Meter} events
+ * - Other deltas are treated as simple {@link Counter} values
+ * - Inputs to {@link #set(Metric)} with names in "histogram.*" are treated as
+ * {@link Histogram} updates
+ * - Inputs to {@link #set(Metric)} with names in "timer.*" are treated as {@link Timer}
+ * updates
+ * - Other metrics are treated as simple {@link Gauge} values (single valued
+ * measurements of type double)
+ *
+ *
+ * @author Dave Syer
+ */
+public class CodahaleMetricWriter implements MetricWriter {
+
+ private final MetricRegistry registry;
+
+ /**
+ * @param registry
+ */
+ public CodahaleMetricWriter(MetricRegistry registry) {
+ this.registry = registry;
+ }
+
+ @Override
+ public void increment(Delta> delta) {
+ String name = delta.getName();
+ long value = delta.getValue().longValue();
+ if (name.startsWith("meter")) {
+ Meter meter = this.registry.meter(name);
+ meter.mark(value);
+ }
+ else {
+ Counter counter = this.registry.counter(name);
+ counter.inc(value);
+ }
+ }
+
+ @Override
+ public void set(Metric> value) {
+ String name = value.getName();
+ if (name.startsWith("histogram")) {
+ long longValue = value.getValue().longValue();
+ Histogram metric = this.registry.histogram(name);
+ metric.update(longValue);
+ }
+ else if (name.startsWith("timer")) {
+ long longValue = value.getValue().longValue();
+ Timer metric = this.registry.timer(name);
+ metric.update(longValue, TimeUnit.MILLISECONDS);
+ }
+ else {
+ final double gauge = value.getValue().doubleValue();
+ this.registry.remove(name);
+ this.registry.register(name, new SimpleGauge(gauge));
+ }
+ }
+
+ @Override
+ public void reset(String metricName) {
+ this.registry.remove(metricName);
+ }
+
+ private static class SimpleGauge implements Gauge {
+
+ private final double gauge;
+
+ private SimpleGauge(double gauge) {
+ this.gauge = gauge;
+ }
+
+ @Override
+ public Double getValue() {
+ return this.gauge;
+ }
+ }
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/CompositeMetricWriter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/CompositeMetricWriter.java
new file mode 100644
index 00000000000..881695a8fa2
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/CompositeMetricWriter.java
@@ -0,0 +1,65 @@
+/*
+ * 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.metrics.writer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.boot.actuate.metrics.Metric;
+
+/**
+ * Composite implementation of {@link MetricWriter} that just sends its input to all of
+ * the delegates that have been registered.
+ *
+ * @author Dave Syer
+ */
+public class CompositeMetricWriter implements MetricWriter {
+
+ private List writers = new ArrayList();
+
+ public CompositeMetricWriter(MetricWriter... writers) {
+ for (MetricWriter writer : writers) {
+ this.writers.add(writer);
+ }
+ }
+
+ public CompositeMetricWriter(List writers) {
+ this.writers.addAll(writers);
+ }
+
+ @Override
+ public void increment(Delta> delta) {
+ for (MetricWriter writer : this.writers) {
+ writer.increment(delta);
+ }
+ }
+
+ @Override
+ public void set(Metric> value) {
+ for (MetricWriter writer : this.writers) {
+ writer.set(value);
+ }
+ }
+
+ @Override
+ public void reset(String metricName) {
+ for (MetricWriter writer : this.writers) {
+ writer.reset(metricName);
+ }
+ }
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/DefaultCounterService.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterService.java
similarity index 66%
rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/DefaultCounterService.java
rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterService.java
index 31e6e1438a6..d9433a43fbd 100644
--- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/DefaultCounterService.java
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterService.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package org.springframework.boot.actuate.metrics;
+package org.springframework.boot.actuate.metrics.writer;
-import java.util.Date;
+import org.springframework.boot.actuate.metrics.CounterService;
/**
* Default implementation of {@link CounterService}.
@@ -25,34 +25,33 @@ import java.util.Date;
*/
public class DefaultCounterService implements CounterService {
- private MetricRepository repository;
+ private final MetricWriter writer;
/**
* Create a {@link DefaultCounterService} instance.
- * @param repository the underlying repository used to manage metrics
+ * @param writer the underlying writer used to manage metrics
*/
- public DefaultCounterService(MetricRepository repository) {
- super();
- this.repository = repository;
+ public DefaultCounterService(MetricWriter writer) {
+ this.writer = writer;
}
@Override
public void increment(String metricName) {
- this.repository.increment(wrap(metricName), 1, new Date());
+ this.writer.increment(new Delta(wrap(metricName), 1L));
}
@Override
public void decrement(String metricName) {
- this.repository.increment(wrap(metricName), -1, new Date());
+ this.writer.increment(new Delta(wrap(metricName), -1L));
}
@Override
public void reset(String metricName) {
- this.repository.set(wrap(metricName), 0, new Date());
+ this.writer.increment(new Delta(wrap(metricName), 0L));
}
private String wrap(String metricName) {
- if (metricName.startsWith("counter")) {
+ if (metricName.startsWith("counter") || metricName.startsWith("meter")) {
return metricName;
}
else {
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/DefaultGaugeService.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeService.java
similarity index 59%
rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/DefaultGaugeService.java
rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeService.java
index 9e94ca1ff79..e25a9538b10 100644
--- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/DefaultGaugeService.java
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeService.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package org.springframework.boot.actuate.metrics;
+package org.springframework.boot.actuate.metrics.writer;
-import java.util.Date;
+import org.springframework.boot.actuate.metrics.GaugeService;
+import org.springframework.boot.actuate.metrics.Metric;
/**
* Default implementation of {@link GaugeService}.
@@ -25,24 +26,24 @@ import java.util.Date;
*/
public class DefaultGaugeService implements GaugeService {
- private MetricRepository metricRepository;
+ private final MetricWriter writer;
/**
- * Create a new {@link DefaultGaugeService} instance.
- * @param counterRepository
+ * Create a {@link DefaultCounterService} instance.
+ * @param writer the underlying writer used to manage metrics
*/
- public DefaultGaugeService(MetricRepository counterRepository) {
- super();
- this.metricRepository = counterRepository;
+ public DefaultGaugeService(MetricWriter writer) {
+ this.writer = writer;
}
@Override
- public void set(String metricName, double value) {
- this.metricRepository.set(wrap(metricName), value, new Date());
+ public void submit(String metricName, double value) {
+ this.writer.set(new Metric(wrap(metricName), value));
}
private String wrap(String metricName) {
- if (metricName.startsWith("gauge")) {
+ if (metricName.startsWith("gauge") || metricName.startsWith("histogram")
+ || metricName.startsWith("timer")) {
return metricName;
}
else {
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/Delta.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/Delta.java
new file mode 100644
index 00000000000..df59a5312e1
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/Delta.java
@@ -0,0 +1,38 @@
+/*
+ * 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.metrics.writer;
+
+import java.util.Date;
+
+import org.springframework.boot.actuate.metrics.Metric;
+
+/**
+ * A value object representing an increment in a metric value (usually a counter).
+ *
+ * @author Dave Syer
+ */
+public class Delta extends Metric {
+
+ public Delta(String name, T value, Date timestamp) {
+ super(name, value, timestamp);
+ }
+
+ public Delta(String name, T value) {
+ super(name, value);
+ }
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/MessageChannelMetricWriter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/MessageChannelMetricWriter.java
new file mode 100644
index 00000000000..2beb8c585e8
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/MessageChannelMetricWriter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.metrics.writer;
+
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.support.MessageBuilder;
+
+/**
+ * A {@link MetricWriter} that publishes the metric updates on a {@link MessageChannel}.
+ * The messages have the writer input ({@link Delta} or {@link Metric}) as payload, and
+ * carry an additional header "metricName" with the name of the metric in it.
+ *
+ * @author Dave Syer
+ */
+public class MessageChannelMetricWriter implements MetricWriter {
+
+ private static final String METRIC_NAME = "metricName";
+
+ private String DELETE = "delete";
+
+ private final MessageChannel channel;
+
+ public MessageChannelMetricWriter(MessageChannel channel) {
+ this.channel = channel;
+ }
+
+ @Override
+ public void increment(Delta> delta) {
+ this.channel.send(MessageBuilder.withPayload(delta)
+ .setHeader(METRIC_NAME, delta.getName()).build());
+ }
+
+ @Override
+ public void set(Metric> value) {
+ this.channel.send(MessageBuilder.withPayload(value)
+ .setHeader(METRIC_NAME, value.getName()).build());
+ }
+
+ @Override
+ public void reset(String metricName) {
+ this.channel.send(MessageBuilder.withPayload(this.DELETE)
+ .setHeader(METRIC_NAME, metricName).build());
+ }
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/MetricWriter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/MetricWriter.java
new file mode 100644
index 00000000000..1a6e7868a79
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/MetricWriter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.metrics.writer;
+
+import org.springframework.boot.actuate.metrics.Metric;
+
+/**
+ * Basic strategy for write operations on {@link Metric} data.
+ *
+ * @author Dave Syer
+ */
+public interface MetricWriter {
+
+ void increment(Delta> delta);
+
+ void set(Metric> value);
+
+ void reset(String metricName);
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/MetricWriterMessageHandler.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/MetricWriterMessageHandler.java
new file mode 100644
index 00000000000..bba4baa7c45
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/writer/MetricWriterMessageHandler.java
@@ -0,0 +1,50 @@
+/*
+ * 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.metrics.writer;
+
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.MessagingException;
+
+/**
+ * A {@link MessageHandler} that updates {@link Metric} values through a
+ * {@link MetricWriter}.
+ *
+ * @author Dave Syer
+ */
+public final class MetricWriterMessageHandler implements MessageHandler {
+
+ private final MetricWriter observer;
+
+ public MetricWriterMessageHandler(MetricWriter observer) {
+ this.observer = observer;
+ }
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ Object payload = message.getPayload();
+ if (payload instanceof Delta) {
+ Delta> value = (Delta>) payload;
+ this.observer.increment(value);
+ }
+ else {
+ Metric> value = (Metric>) payload;
+ this.observer.set(value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java
index 3de0240fb6d..3d4dc1e6d93 100644
--- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricFilterAutoConfigurationTests.java
@@ -64,7 +64,7 @@ public class MetricFilterAutoConfigurationTests {
}).given(chain).doFilter(request, response);
filter.doFilter(request, response, chain);
verify(context.getBean(CounterService.class)).increment("status.200.test.path");
- verify(context.getBean(GaugeService.class)).set(eq("response.test.path"),
+ verify(context.getBean(GaugeService.class)).submit(eq("response.test.path"),
anyDouble());
context.close();
}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java
index e490e395e79..8af794e0110 100644
--- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java
@@ -16,34 +16,79 @@
package org.springframework.boot.actuate.autoconfigure;
+import java.util.concurrent.Executor;
+
import org.junit.Test;
-import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration;
import org.springframework.boot.actuate.metrics.CounterService;
-import org.springframework.boot.actuate.metrics.DefaultCounterService;
-import org.springframework.boot.actuate.metrics.DefaultGaugeService;
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.writer.DefaultCounterService;
+import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService;
+import org.springframework.boot.actuate.metrics.writer.MetricWriter;
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 com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
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.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
/**
* Tests for {@link MetricRepositoryAutoConfiguration}.
*
* @author Phillip Webb
+ * @author Dave Syer
*/
public class MetricRepositoryAutoConfigurationTests {
@Test
- public void createServices() {
+ public void createServices() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ SyncTaskExecutorConfiguration.class,
MetricRepositoryAutoConfiguration.class);
- assertNotNull(context.getBean(DefaultGaugeService.class));
+ DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class);
+ assertNotNull(gaugeService);
assertNotNull(context.getBean(DefaultCounterService.class));
+ gaugeService.submit("foo", 2.7);
+ assertEquals(2.7, context.getBean(MetricReader.class).findOne("gauge.foo")
+ .getValue());
+ context.close();
+ }
+
+ @Test
+ public void provideAdditionalWriter() {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ SyncTaskExecutorConfiguration.class, WriterConfig.class,
+ MetricRepositoryAutoConfiguration.class);
+ DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class);
+ assertNotNull(gaugeService);
+ gaugeService.submit("foo", 2.7);
+ MetricWriter writer = context.getBean("writer", MetricWriter.class);
+ verify(writer).set(any(Metric.class));
+ context.close();
+ }
+
+ @Test
+ public void codahaleInstalledIfPresent() {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ SyncTaskExecutorConfiguration.class, WriterConfig.class,
+ MetricRepositoryAutoConfiguration.class);
+ DefaultGaugeService gaugeService = context.getBean(DefaultGaugeService.class);
+ assertNotNull(gaugeService);
+ gaugeService.submit("foo", 2.7);
+ MetricRegistry registry = context.getBean(MetricRegistry.class);
+ @SuppressWarnings("unchecked")
+ Gauge gauge = (Gauge) registry.getMetrics().get("gauge.foo");
+ assertEquals(new Double(2.7), gauge.getValue());
context.close();
}
@@ -56,6 +101,26 @@ public class MetricRepositoryAutoConfigurationTests {
context.close();
}
+ @Configuration
+ public static class SyncTaskExecutorConfiguration {
+
+ @Bean
+ public Executor metricsExecutor() {
+ return new SyncTaskExecutor();
+ }
+
+ }
+
+ @Configuration
+ public static class WriterConfig {
+
+ @Bean
+ public MetricWriter writer() {
+ return mock(MetricWriter.class);
+ }
+
+ }
+
@Configuration
public static class Config {
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java
index a05efee4319..e24febd7add 100644
--- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/MetricsEndpointTests.java
@@ -41,7 +41,7 @@ public class MetricsEndpointTests extends AbstractEndpointTests
@Test
public void invoke() throws Exception {
- assertThat(getEndpointBean().invoke().get("a"), equalTo((Object) 0.5));
+ assertThat(getEndpointBean().invoke().get("a"), equalTo((Object) 0.5f));
}
@Configuration
@@ -50,11 +50,11 @@ public class MetricsEndpointTests extends AbstractEndpointTests
@Bean
public MetricsEndpoint endpoint() {
- final Metric metric = new Metric("a", 0.5f);
+ final Metric metric = new Metric("a", 0.5f);
PublicMetrics metrics = new PublicMetrics() {
@Override
- public Collection metrics() {
- return Collections.singleton(metric);
+ public Collection> metrics() {
+ return Collections.> singleton(metric);
}
};
return new MetricsEndpoint(metrics);
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetricsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetricsTests.java
index 58f10884458..d573fa31af2 100644
--- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetricsTests.java
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetricsTests.java
@@ -21,9 +21,8 @@ import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
-import org.springframework.boot.actuate.endpoint.VanillaPublicMetrics;
-import org.springframework.boot.actuate.metrics.InMemoryMetricRepository;
import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@@ -39,14 +38,14 @@ public class VanillaPublicMetricsTests {
@Test
public void testMetrics() throws Exception {
InMemoryMetricRepository repository = new InMemoryMetricRepository();
- repository.set("a", 0.5, new Date());
+ repository.set(new Metric("a", 0.5, new Date()));
VanillaPublicMetrics publicMetrics = new VanillaPublicMetrics(repository);
- Map results = new HashMap();
- for (Metric metric : publicMetrics.metrics()) {
+ Map> results = new HashMap>();
+ for (Metric> metric : publicMetrics.metrics()) {
results.put(metric.getName(), metric);
}
assertTrue(results.containsKey("mem"));
assertTrue(results.containsKey("mem.free"));
- assertThat(results.get("a").getValue(), equalTo(0.5));
+ assertThat(results.get("a").getValue().doubleValue(), equalTo(0.5));
}
}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/InMemoryMetricRepositoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/InMemoryMetricRepositoryTests.java
deleted file mode 100644
index 12fa6cb4769..00000000000
--- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/InMemoryMetricRepositoryTests.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.metrics;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Tests for {@link InMemoryMetricRepository}.
- */
-public class InMemoryMetricRepositoryTests {
-
- private InMemoryMetricRepository repository = new InMemoryMetricRepository();
-
- @Test
- public void increment() {
- this.repository.increment("foo", 1, new Date());
- assertEquals(1.0, this.repository.findOne("foo").getValue(), 0.01);
- }
-
- @Test
- public void incrementConcurrent() throws Exception {
- Collection> tasks = new ArrayList>();
- for (int i = 0; i < 100; i++) {
- tasks.add(new Callable() {
- @Override
- public Boolean call() throws Exception {
- InMemoryMetricRepositoryTests.this.repository.increment("foo", 1,
- new Date());
- return true;
- }
- });
- tasks.add(new Callable() {
- @Override
- public Boolean call() throws Exception {
- InMemoryMetricRepositoryTests.this.repository.increment("foo", -1,
- new Date());
- return true;
- }
- });
- }
- List> all = Executors.newFixedThreadPool(10).invokeAll(tasks);
- for (Future future : all) {
- assertTrue(future.get(1, TimeUnit.SECONDS));
- }
- assertEquals(0, this.repository.findOne("foo").getValue(), 0.01);
- }
-
- @Test
- public void set() {
- this.repository.set("foo", 1, new Date());
- assertEquals(1.0, this.repository.findOne("foo").getValue(), 0.01);
- }
-
-}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricRepository.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/Iterables.java
similarity index 68%
rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricRepository.java
rename to spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/Iterables.java
index b03e01af110..33cc8754bb6 100644
--- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricRepository.java
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/Iterables.java
@@ -16,24 +16,23 @@
package org.springframework.boot.actuate.metrics;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.Date;
/**
- * A Repository used to manage {@link Metric}s.
- *
* @author Dave Syer
*/
-public interface MetricRepository {
+public abstract class Iterables {
- void increment(String metricName, int amount, Date timestamp);
-
- void set(String metricName, double value, Date timestamp);
-
- void delete(String metricName);
-
- Metric findOne(String metricName);
-
- Collection findAll();
+ public static Collection collection(Iterable iterable) {
+ if (iterable instanceof Collection) {
+ return (Collection) iterable;
+ }
+ ArrayList list = new ArrayList();
+ for (T t : iterable) {
+ list.add(t);
+ }
+ return list;
+ }
}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/MetricCopyExporterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/MetricCopyExporterTests.java
new file mode 100644
index 00000000000..67a29e84b04
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/MetricCopyExporterTests.java
@@ -0,0 +1,60 @@
+/*
+ * 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.metrics.export;
+
+import java.util.Date;
+
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Dave Syer
+ */
+public class MetricCopyExporterTests {
+
+ private InMemoryMetricRepository writer = new InMemoryMetricRepository();
+ private InMemoryMetricRepository reader = new InMemoryMetricRepository();
+ private MetricCopyExporter exporter = new MetricCopyExporter(this.reader, this.writer);
+
+ @Test
+ public void export() {
+ this.reader.set(new Metric("foo", 2.3));
+ this.exporter.export();
+ assertEquals(1, this.writer.count());
+ }
+
+ @Test
+ public void timestamp() {
+ this.reader.set(new Metric("foo", 2.3));
+ this.exporter.setEarliestTimestamp(new Date(System.currentTimeMillis() + 10000));
+ this.exporter.export();
+ assertEquals(0, this.writer.count());
+ }
+
+ @Test
+ public void ignoreTimestamp() {
+ this.reader.set(new Metric("foo", 2.3));
+ this.exporter.setIgnoreTimestamps(true);
+ this.exporter.setEarliestTimestamp(new Date(System.currentTimeMillis() + 10000));
+ this.exporter.export();
+ assertEquals(1, this.writer.count());
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/PrefixMetricGroupExporterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/PrefixMetricGroupExporterTests.java
new file mode 100644
index 00000000000..4e8ac03ba8f
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/PrefixMetricGroupExporterTests.java
@@ -0,0 +1,66 @@
+/*
+ * 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.metrics.export;
+
+import java.util.Collections;
+
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Iterables;
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Dave Syer
+ */
+public class PrefixMetricGroupExporterTests {
+
+ private InMemoryMetricRepository writer = new InMemoryMetricRepository();
+ private InMemoryMetricRepository reader = new InMemoryMetricRepository();
+ private PrefixMetricGroupExporter exporter = new PrefixMetricGroupExporter(
+ this.reader, this.writer);
+
+ @Test
+ public void prefixedMetricsCopied() {
+ this.reader.set(new Metric("foo.bar", 2.3));
+ this.reader.set(new Metric("foo.spam", 1.3));
+ this.exporter.setGroups(Collections.singleton("foo"));
+ this.exporter.export();
+ assertEquals(1, Iterables.collection(this.writer.groups()).size());
+ }
+
+ @Test
+ public void unprefixedMetricsNotCopied() {
+ this.reader.set(new Metric("foo.bar", 2.3));
+ this.reader.set(new Metric("foo.spam", 1.3));
+ this.exporter.setGroups(Collections.singleton("bar"));
+ this.exporter.export();
+ assertEquals(0, Iterables.collection(this.writer.groups()).size());
+ }
+
+ @Test
+ public void onlyPrefixedMetricsCopied() {
+ this.reader.set(new Metric("foo.bar", 2.3));
+ this.reader.set(new Metric("foo.spam", 1.3));
+ this.reader.set(new Metric("foobar.spam", 1.3));
+ this.exporter.setGroups(Collections.singleton("foo"));
+ this.exporter.export();
+ assertEquals(1, Iterables.collection(this.writer.groups()).size());
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/RichGaugeExporterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/RichGaugeExporterTests.java
new file mode 100644
index 00000000000..ae30c5275b9
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/RichGaugeExporterTests.java
@@ -0,0 +1,43 @@
+/*
+ * 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.metrics.export;
+
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Iterables;
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
+import org.springframework.boot.actuate.metrics.rich.InMemoryRichGaugeRepository;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Dave Syer
+ */
+public class RichGaugeExporterTests {
+
+ private InMemoryRichGaugeRepository reader = new InMemoryRichGaugeRepository();
+ private InMemoryMetricRepository writer = new InMemoryMetricRepository();
+ private RichGaugeExporter exporter = new RichGaugeExporter(this.reader, this.writer);
+
+ @Test
+ public void prefixedMetricsCopied() {
+ this.reader.set(new Metric("foo", 2.3));
+ this.exporter.export();
+ assertEquals(1, Iterables.collection(this.writer.groups()).size());
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/InMemoryMetricRepositoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/InMemoryMetricRepositoryTests.java
new file mode 100644
index 00000000000..aa264d11bbc
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/InMemoryMetricRepositoryTests.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics.repository;
+
+import java.util.Date;
+
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository;
+import org.springframework.boot.actuate.metrics.writer.Delta;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for {@link InMemoryMetricRepository}.
+ */
+public class InMemoryMetricRepositoryTests {
+
+ private InMemoryMetricRepository repository = new InMemoryMetricRepository();
+
+ @Test
+ public void increment() {
+ this.repository.increment(new Delta("foo", 1, new Date()));
+ assertEquals(1.0, this.repository.findOne("foo").getValue().doubleValue(), 0.01);
+ }
+
+ @Test
+ public void set() {
+ this.repository.set(new Metric("foo", 2.5, new Date()));
+ assertEquals(2.5, this.repository.findOne("foo").getValue().doubleValue(), 0.01);
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/InMemoryPrefixMetricRepositoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/InMemoryPrefixMetricRepositoryTests.java
new file mode 100644
index 00000000000..58f0dcc3e90
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/InMemoryPrefixMetricRepositoryTests.java
@@ -0,0 +1,83 @@
+/*
+ * 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.metrics.repository;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.boot.actuate.metrics.writer.Delta;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Dave Syer
+ */
+public class InMemoryPrefixMetricRepositoryTests {
+
+ private InMemoryMetricRepository repository = new InMemoryMetricRepository();
+
+ @Test
+ public void registeredPrefixCounted() {
+ this.repository.increment(new Delta("foo.bar", 1));
+ this.repository.increment(new Delta("foo.bar", 1));
+ this.repository.increment(new Delta("foo.spam", 1));
+ Set names = new HashSet();
+ for (Metric> metric : this.repository.findAll("foo")) {
+ names.add(metric.getName());
+ }
+ assertEquals(2, names.size());
+ assertTrue(names.contains("foo.bar"));
+ }
+
+ @Test
+ public void perfixWithWildcard() {
+ this.repository.increment(new Delta("foo.bar", 1));
+ Set names = new HashSet();
+ for (Metric> metric : this.repository.findAll("foo.*")) {
+ names.add(metric.getName());
+ }
+ assertEquals(1, names.size());
+ assertTrue(names.contains("foo.bar"));
+ }
+
+ @Test
+ public void perfixWithPeriod() {
+ this.repository.increment(new Delta("foo.bar", 1));
+ Set names = new HashSet();
+ for (Metric> metric : this.repository.findAll("foo.")) {
+ names.add(metric.getName());
+ }
+ assertEquals(1, names.size());
+ assertTrue(names.contains("foo.bar"));
+ }
+
+ @Test
+ public void onlyRegisteredPrefixCounted() {
+ this.repository.increment(new Delta("foo.bar", 1));
+ this.repository.increment(new Delta("foobar.spam", 1));
+ Set names = new HashSet();
+ for (Metric> metric : this.repository.findAll("foo")) {
+ names.add(metric.getName());
+ }
+ assertEquals(1, names.size());
+ assertTrue(names.contains("foo.bar"));
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/redis/RedisMetricRepositoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/redis/RedisMetricRepositoryTests.java
new file mode 100644
index 00000000000..9f05be460ca
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/redis/RedisMetricRepositoryTests.java
@@ -0,0 +1,91 @@
+/*
+ * 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.metrics.repository.redis;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Iterables;
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.boot.actuate.metrics.writer.Delta;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author Dave Syer
+ */
+public class RedisMetricRepositoryTests {
+
+ @Rule
+ public RedisServer redis = RedisServer.running();
+ private RedisMetricRepository repository;
+
+ @Before
+ public void init() {
+ this.repository = new RedisMetricRepository(this.redis.getResource());
+ }
+
+ @After
+ public void clear() {
+ assertNotNull(new StringRedisTemplate(this.redis.getResource()).opsForValue()
+ .get("spring.metrics.foo"));
+ this.repository.reset("foo");
+ this.repository.reset("bar");
+ assertNull(new StringRedisTemplate(this.redis.getResource()).opsForValue().get(
+ "spring.metrics.foo"));
+ }
+
+ @Test
+ public void setAndGet() {
+ this.repository.set(new Metric("foo", 12.3));
+ Metric> metric = this.repository.findOne("foo");
+ assertEquals("foo", metric.getName());
+ assertEquals(12.3, metric.getValue().doubleValue(), 0.01);
+ }
+
+ @Test
+ public void incrementAndGet() {
+ this.repository.increment(new Delta("foo", 3L));
+ assertEquals(3, this.repository.findOne("foo").getValue().longValue());
+ }
+
+ @Test
+ public void findAll() {
+ this.repository.increment(new Delta("foo", 3L));
+ this.repository.set(new Metric("bar", 12.3));
+ assertEquals(2, Iterables.collection(this.repository.findAll()).size());
+ }
+
+ @Test
+ public void findOneWithAll() {
+ this.repository.increment(new Delta("foo", 3L));
+ Metric> metric = this.repository.findAll().iterator().next();
+ assertEquals("foo", metric.getName());
+ }
+
+ @Test
+ public void count() {
+ this.repository.increment(new Delta("foo", 3L));
+ this.repository.set(new Metric("bar", 12.3));
+ assertEquals(2, this.repository.count());
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/redis/RedisMultiMetricRepositoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/redis/RedisMultiMetricRepositoryTests.java
new file mode 100644
index 00000000000..0af5c7a8911
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/redis/RedisMultiMetricRepositoryTests.java
@@ -0,0 +1,84 @@
+/*
+ * 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.metrics.repository.redis;
+
+import java.util.Arrays;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Iterables;
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Dave Syer
+ */
+public class RedisMultiMetricRepositoryTests {
+
+ @Rule
+ public RedisServer redis = RedisServer.running();
+ private RedisMultiMetricRepository repository;
+
+ @Before
+ public void init() {
+ this.repository = new RedisMultiMetricRepository(this.redis.getResource());
+ }
+
+ @After
+ public void clear() {
+ assertTrue(new StringRedisTemplate(this.redis.getResource()).opsForZSet().size(
+ "spring.groups.foo") > 0);
+ this.repository.reset("foo");
+ this.repository.reset("bar");
+ assertNull(new StringRedisTemplate(this.redis.getResource()).opsForValue().get(
+ "spring.groups.foo"));
+ assertNull(new StringRedisTemplate(this.redis.getResource()).opsForValue().get(
+ "spring.groups.bar"));
+ }
+
+ @Test
+ public void setAndGet() {
+ this.repository.save("foo", Arrays.> asList(new Metric(
+ "foo.val", 12.3), new Metric("foo.bar", 11.3)));
+ assertEquals(2, Iterables.collection(this.repository.findAll("foo")).size());
+ }
+
+ @Test
+ public void groups() {
+ this.repository.save("foo", Arrays.> asList(new Metric(
+ "foo.val", 12.3), new Metric("foo.bar", 11.3)));
+ this.repository.save("bar", Arrays.> asList(new Metric(
+ "bar.val", 12.3), new Metric("bar.foo", 11.3)));
+ assertEquals(2, Iterables.collection(this.repository.groups()).size());
+ }
+
+ @Test
+ public void count() {
+ this.repository.save("foo", Arrays.> asList(new Metric(
+ "foo.val", 12.3), new Metric("foo.bar", 11.3)));
+ this.repository.save("bar", Arrays.> asList(new Metric(
+ "bar.val", 12.3), new Metric("bar.foo", 11.3)));
+ assertEquals(2, this.repository.count());
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/redis/RedisServer.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/redis/RedisServer.java
new file mode 100644
index 00000000000..1929a273419
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/repository/redis/RedisServer.java
@@ -0,0 +1,144 @@
+/*
+ * 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.metrics.repository.redis;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+
+import static org.junit.Assert.fail;
+
+/**
+ * @author Eric Bottard
+ * @author Gary Russell
+ * @author Dave Syer
+ */
+public class RedisServer implements TestRule {
+
+ private static final String EXTERNAL_SERVERS_REQUIRED = "EXTERNAL_SERVERS_REQUIRED";
+
+ protected LettuceConnectionFactory resource;
+
+ private String resourceDescription = "Redis ConnectionFactory";
+
+ private static final Log logger = LogFactory.getLog(RedisServer.class);
+
+ public static RedisServer running() {
+ return new RedisServer();
+ }
+
+ private RedisServer() {
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ try {
+ this.resource = obtainResource();
+ }
+ catch (Exception e) {
+ maybeCleanup();
+
+ return failOrSkip(e);
+ }
+
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ base.evaluate();
+ }
+ finally {
+ try {
+ cleanupResource();
+ }
+ catch (Exception ignored) {
+ RedisServer.logger.warn(
+ "Exception while trying to cleanup proper resource",
+ ignored);
+ }
+ }
+ }
+
+ };
+ }
+
+ private Statement failOrSkip(Exception e) {
+ String serversRequired = System.getenv(EXTERNAL_SERVERS_REQUIRED);
+ if ("true".equalsIgnoreCase(serversRequired)) {
+ logger.error(this.resourceDescription + " IS REQUIRED BUT NOT AVAILABLE", e);
+ fail(this.resourceDescription + " IS NOT AVAILABLE");
+ // Never reached, here to satisfy method signature
+ return null;
+ }
+ else {
+ logger.error(this.resourceDescription + " IS NOT AVAILABLE, SKIPPING TESTS",
+ e);
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ Assume.assumeTrue("Skipping test due to "
+ + RedisServer.this.resourceDescription
+ + " not being available", false);
+ }
+ };
+ }
+ }
+
+ private void maybeCleanup() {
+ if (this.resource != null) {
+ try {
+ cleanupResource();
+ }
+ catch (Exception ignored) {
+ logger.warn("Exception while trying to cleanup failed resource", ignored);
+ }
+ }
+ }
+
+ public RedisConnectionFactory getResource() {
+ return this.resource;
+ }
+
+ /**
+ * Perform cleanup of the {@link #resource} field, which is guaranteed to be non null.
+ *
+ * @throws Exception any exception thrown by this method will be logged and swallowed
+ */
+ protected void cleanupResource() throws Exception {
+ this.resource.destroy();
+ }
+
+ /**
+ * Try to obtain and validate a resource. Implementors should either set the
+ * {@link #resource} field with a valid resource and return normally, or throw an
+ * exception.
+ */
+ protected LettuceConnectionFactory obtainResource() throws Exception {
+ LettuceConnectionFactory resource = new LettuceConnectionFactory();
+ resource.afterPropertiesSet();
+ resource.getConnection().close();
+ return resource;
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/rich/InMemoryRichGaugeRepositoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/rich/InMemoryRichGaugeRepositoryTests.java
new file mode 100644
index 00000000000..ac8383977d1
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/rich/InMemoryRichGaugeRepositoryTests.java
@@ -0,0 +1,39 @@
+/*
+ * 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.metrics.rich;
+
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Metric;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Dave Syer
+ */
+public class InMemoryRichGaugeRepositoryTests {
+
+ private InMemoryRichGaugeRepository repository = new InMemoryRichGaugeRepository();
+
+ @Test
+ public void writeAndRead() {
+ this.repository.set(new Metric("foo", 1d));
+ this.repository.set(new Metric("foo", 2d));
+ assertEquals(2L, this.repository.findOne("foo").getCount());
+ assertEquals(2d, this.repository.findOne("foo").getValue(), 0.01);
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/util/InMemoryRepositoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/util/InMemoryRepositoryTests.java
new file mode 100644
index 00000000000..b9e825a184a
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/util/InMemoryRepositoryTests.java
@@ -0,0 +1,131 @@
+/*
+ * 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.metrics.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.util.SimpleInMemoryRepository.Callback;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Dave Syer
+ */
+public class InMemoryRepositoryTests {
+
+ private SimpleInMemoryRepository repository = new SimpleInMemoryRepository();
+
+ @Test
+ public void setAndGet() {
+ this.repository.set("foo", "bar");
+ assertEquals("bar", this.repository.findOne("foo"));
+ }
+
+ @Test
+ public void updateExisting() {
+ this.repository.set("foo", "spam");
+ this.repository.update("foo", new Callback() {
+ @Override
+ public String modify(String current) {
+ return "bar";
+ }
+ });
+ assertEquals("bar", this.repository.findOne("foo"));
+ }
+
+ @Test
+ public void updateNonexistent() {
+ this.repository.update("foo", new Callback() {
+ @Override
+ public String modify(String current) {
+ return "bar";
+ }
+ });
+ assertEquals("bar", this.repository.findOne("foo"));
+ }
+
+ @Test
+ public void findWithPrefix() {
+ this.repository.set("foo", "bar");
+ this.repository.set("foo.bar", "one");
+ this.repository.set("foo.min", "two");
+ this.repository.set("foo.max", "three");
+ assertEquals(3, ((Collection>) this.repository.findAllWithPrefix("foo")).size());
+ }
+
+ @Test
+ public void patternsAcceptedForRegisteredPrefix() {
+ this.repository.set("foo.bar", "spam");
+ Iterator iterator = this.repository.findAllWithPrefix("foo.*").iterator();
+ assertEquals("spam", iterator.next());
+ assertFalse(iterator.hasNext());
+ }
+
+ @Test
+ public void updateConcurrent() throws Exception {
+ final SimpleInMemoryRepository repository = new SimpleInMemoryRepository();
+ Collection> tasks = new ArrayList>();
+ for (int i = 0; i < 1000; i++) {
+ tasks.add(new Callable() {
+ @Override
+ public Boolean call() throws Exception {
+ repository.update("foo", new Callback() {
+ @Override
+ public Integer modify(Integer current) {
+ if (current == null) {
+ return 1;
+ }
+ return current + 1;
+ }
+ });
+ return true;
+ }
+ });
+ tasks.add(new Callable() {
+ @Override
+ public Boolean call() throws Exception {
+ repository.update("foo", new Callback() {
+ @Override
+ public Integer modify(Integer current) {
+ if (current == null) {
+ return -1;
+ }
+ return current - 1;
+ }
+ });
+ return true;
+ }
+ });
+ }
+ List> all = Executors.newFixedThreadPool(10).invokeAll(tasks);
+ for (Future future : all) {
+ assertTrue(future.get(1, TimeUnit.SECONDS));
+ }
+ assertEquals(new Integer(0), repository.findOne("foo"));
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/CodahaleMetricWriterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/CodahaleMetricWriterTests.java
new file mode 100644
index 00000000000..150bed13ef4
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/CodahaleMetricWriterTests.java
@@ -0,0 +1,79 @@
+/*
+ * 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.metrics.writer;
+
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Metric;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Dave Syer
+ */
+public class CodahaleMetricWriterTests {
+
+ private MetricRegistry registry = new MetricRegistry();
+ private CodahaleMetricWriter writer = new CodahaleMetricWriter(this.registry);
+
+ @Test
+ public void incrementCounter() {
+ this.writer.increment(new Delta("foo", 2));
+ this.writer.increment(new Delta("foo", 1));
+ assertEquals(3, this.registry.counter("foo").getCount());
+ }
+
+ @Test
+ public void updatePredefinedMeter() {
+ this.writer.increment(new Delta("meter.foo", 2));
+ this.writer.increment(new Delta("meter.foo", 1));
+ assertEquals(3, this.registry.meter("meter.foo").getCount());
+ }
+
+ @Test
+ public void updatePredefinedCounter() {
+ this.writer.increment(new Delta("counter.foo", 2));
+ this.writer.increment(new Delta("counter.foo", 1));
+ assertEquals(3, this.registry.counter("counter.foo").getCount());
+ }
+
+ @Test
+ public void setGauge() {
+ this.writer.set(new Metric("foo", 2.1));
+ this.writer.set(new Metric("foo", 2.3));
+ @SuppressWarnings("unchecked")
+ Gauge gauge = (Gauge) this.registry.getMetrics().get("foo");
+ assertEquals(new Double(2.3), gauge.getValue());
+ }
+
+ @Test
+ public void setPredfinedTimer() {
+ this.writer.set(new Metric("timer.foo", 200));
+ this.writer.set(new Metric("timer.foo", 300));
+ assertEquals(2, this.registry.timer("timer.foo").getCount());
+ }
+
+ @Test
+ public void setPredfinedHistogram() {
+ this.writer.set(new Metric("histogram.foo", 2.1));
+ this.writer.set(new Metric("histogram.foo", 2.3));
+ assertEquals(2, this.registry.histogram("histogram.foo").getCount());
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/DefaultCounterServiceTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterServiceTests.java
similarity index 54%
rename from spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/DefaultCounterServiceTests.java
rename to spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterServiceTests.java
index 693d79c252c..a66ab5b4d8f 100644
--- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/DefaultCounterServiceTests.java
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DefaultCounterServiceTests.java
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-package org.springframework.boot.actuate.metrics;
-
-import java.util.Date;
+package org.springframework.boot.actuate.metrics.writer;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.springframework.boot.actuate.metrics.writer.DefaultCounterService;
+import org.springframework.boot.actuate.metrics.writer.Delta;
+import org.springframework.boot.actuate.metrics.writer.MetricWriter;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -30,19 +31,26 @@ import static org.mockito.Mockito.verify;
*/
public class DefaultCounterServiceTests {
- private MetricRepository repository = mock(MetricRepository.class);
+ private MetricWriter repository = mock(MetricWriter.class);
private DefaultCounterService service = new DefaultCounterService(this.repository);
@Test
public void incrementPrependsCounter() {
this.service.increment("foo");
- verify(this.repository).increment(eq("counter.foo"), eq(1), any(Date.class));
+ @SuppressWarnings("rawtypes")
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Delta.class);
+ verify(this.repository).increment(captor.capture());
+ assertEquals("counter.foo", captor.getValue().getName());
}
@Test
public void decrementPrependsCounter() {
this.service.decrement("foo");
- verify(this.repository).increment(eq("counter.foo"), eq(-1), any(Date.class));
+ @SuppressWarnings("rawtypes")
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Delta.class);
+ verify(this.repository).increment(captor.capture());
+ assertEquals("counter.foo", captor.getValue().getName());
+ assertEquals(-1L, captor.getValue().getValue());
}
}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/DefaultGaugeServiceTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeServiceTests.java
similarity index 61%
rename from spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/DefaultGaugeServiceTests.java
rename to spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeServiceTests.java
index e926ca1261d..61dd5e99a99 100644
--- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/DefaultGaugeServiceTests.java
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/DefaultGaugeServiceTests.java
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package org.springframework.boot.actuate.metrics;
-
-import java.util.Date;
+package org.springframework.boot.actuate.metrics.writer;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.springframework.boot.actuate.metrics.Metric;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -30,14 +29,18 @@ import static org.mockito.Mockito.verify;
*/
public class DefaultGaugeServiceTests {
- private MetricRepository repository = mock(MetricRepository.class);
+ private MetricWriter repository = mock(MetricWriter.class);
private DefaultGaugeService service = new DefaultGaugeService(this.repository);
@Test
- public void setPrependsGuager() {
- this.service.set("foo", 2.3);
- verify(this.repository).set(eq("gauge.foo"), eq(2.3), any(Date.class));
+ public void setPrependsGauge() {
+ this.service.submit("foo", 2.3);
+ @SuppressWarnings("rawtypes")
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Metric.class);
+ verify(this.repository).set(captor.capture());
+ assertEquals("gauge.foo", captor.getValue().getName());
+ assertEquals(2.3, captor.getValue().getValue());
}
}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/MessageChannelMetricWriterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/MessageChannelMetricWriterTests.java
new file mode 100644
index 00000000000..97038789175
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/writer/MessageChannelMetricWriterTests.java
@@ -0,0 +1,50 @@
+/*
+ * 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.metrics.writer;
+
+import org.junit.Test;
+import org.springframework.boot.actuate.metrics.Metric;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * @author Dave Syer
+ */
+public class MessageChannelMetricWriterTests {
+
+ private MessageChannel channel = mock(MessageChannel.class);
+
+ private MessageChannelMetricWriter observer = new MessageChannelMetricWriter(
+ this.channel);
+
+ @Test
+ public void messageSentOnAdd() {
+ this.observer.increment(new Delta("foo", 1));
+ verify(this.channel).send(any(Message.class));
+ }
+
+ @Test
+ public void messageSentOnSet() {
+ this.observer.set(new Metric("foo", 1d));
+ verify(this.channel).send(any(Message.class));
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/JreProxySelector.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/JreProxySelector.java
index 3fc039c8d38..1738971383e 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/JreProxySelector.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/JreProxySelector.java
@@ -35,6 +35,8 @@ import org.eclipse.aether.repository.RemoteRepository;
/**
* (Copied from aether source code - not available yet in Maven repo.)
+ *
+ * @author Dave Syer
*/
public final class JreProxySelector implements ProxySelector {
diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml
index b2c1926ceef..06374a8c888 100644
--- a/spring-boot-dependencies/pom.xml
+++ b/spring-boot-dependencies/pom.xml
@@ -9,6 +9,7 @@
5.7.0
1.7.4
+ 3.0.1
1.4
1.6
1.6
@@ -60,6 +61,26 @@
logback-classic
${logback.version}