diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/InMemoryMetricRepository.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/InMemoryMetricRepository.java index 1adaf2a8c5..8a62abc539 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/InMemoryMetricRepository.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/InMemoryMetricRepository.java @@ -31,18 +31,26 @@ public class InMemoryMetricRepository implements MetricRepository { private ConcurrentMap metrics = new ConcurrentHashMap(); + private ConcurrentMap locks = new ConcurrentHashMap(); + @Override public void increment(String metricName, int amount, Date timestamp) { Measurement current = this.metrics.get(metricName); if (current != null) { - Metric metric = current.getMetric(); - this.metrics.replace(metricName, current, - new Measurement(timestamp, metric.increment(amount))); - } - else { - this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric( - metricName, amount))); + Object lock = this.locks.putIfAbsent(metricName, new Object()); + if (lock == null) { + lock = this.locks.get(metricName); + } + synchronized (lock) { + current = this.metrics.get(metricName); + Metric metric = current.getMetric(); + this.metrics.replace(metricName, current, new Measurement(timestamp, + metric.increment(amount))); + return; + } } + this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric( + metricName, amount))); } @Override 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 index ad48d2f607..12fa6cb476 100644 --- 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 @@ -16,24 +16,65 @@ package org.springframework.boot.actuate.metrics; -import org.junit.Ignore; -import org.junit.Test; -import org.springframework.boot.actuate.metrics.InMemoryMetricRepository; +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 static org.junit.Assert.fail; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Tests for {@link InMemoryMetricRepository}. */ -@Ignore public class InMemoryMetricRepositoryTests { - // FIXME write tests - // FIXME possibly also add Metric/Measurement tests + private InMemoryMetricRepository repository = new InMemoryMetricRepository(); @Test - public void test() { - fail("Not yet implemented"); + 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); } }