Refactor metrics to expose richer feature set

Main user-facing interface is still Counter/GaugeService but the
back end behind that has more options. The Default*Services write
metrics to a MetricWriter and there are some variants of that, and
also variants of MetricReader (basic read-only actions).

MetricRepository is now a combination of MetricReader, MetricWriter
and some more methods that make it a bit more repository like.

There is also a MultiMetricReader and a MultiMetricRepository for
the common case where metrics are stored in related (often open
ended) groups. Examples would be complex metrics like histograms
and "rich" metrics with averages and statistics attached (which
are both closed) and "field counters" which count the occurrences
of values of a particular named field or slot in an incoming message
(e.g. counting Twitter hastags, open ended).

In memory and redis implementations are provided for the repositories.
Generally speaking the in memory repository should be used as a
local buffer and then scheduled "exports" can be executed to copy
metric values accross to a remote repository for aggregation.
There is an Exporter interface to support this and a few implementations
dealing with different strategies for storing the results (singly or
grouped).

Codahale metrics are also supported through the MetricWriter interface.
Currently implemented through a naming convention (since Codahale has
a fixed object model this makes sense): metrics beginning with "histogram"
are Histograms, "timer" for Timers, "meter" for Meters etc.

Support for message driven metric consumption and production are provided
through a MetricWriterMessageHandler and a MessageChannelMetricWriter.

No support yet for pagination in the repositories, or for HATEOAS style
HTTP endpoints.
This commit is contained in:
Dave Syer
2013-12-06 16:10:50 +00:00
parent 5d8e58d12c
commit aa2b020660
61 changed files with 3095 additions and 395 deletions

View File

@@ -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();
}

View File

@@ -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<Double> gauge = (Gauge<Double>) 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 {

View File

@@ -41,7 +41,7 @@ public class MetricsEndpointTests extends AbstractEndpointTests<MetricsEndpoint>
@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<MetricsEndpoint>
@Bean
public MetricsEndpoint endpoint() {
final Metric metric = new Metric("a", 0.5f);
final Metric<Float> metric = new Metric<Float>("a", 0.5f);
PublicMetrics metrics = new PublicMetrics() {
@Override
public Collection<Metric> metrics() {
return Collections.singleton(metric);
public Collection<Metric<?>> metrics() {
return Collections.<Metric<?>> singleton(metric);
}
};
return new MetricsEndpoint(metrics);

View File

@@ -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<Double>("a", 0.5, new Date()));
VanillaPublicMetrics publicMetrics = new VanillaPublicMetrics(repository);
Map<String, Metric> results = new HashMap<String, Metric>();
for (Metric metric : publicMetrics.metrics()) {
Map<String, Metric<?>> results = new HashMap<String, Metric<?>>();
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));
}
}

View File

@@ -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<Callable<Boolean>> tasks = new ArrayList<Callable<Boolean>>();
for (int i = 0; i < 100; i++) {
tasks.add(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
InMemoryMetricRepositoryTests.this.repository.increment("foo", 1,
new Date());
return true;
}
});
tasks.add(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
InMemoryMetricRepositoryTests.this.repository.increment("foo", -1,
new Date());
return true;
}
});
}
List<Future<Boolean>> all = Executors.newFixedThreadPool(10).invokeAll(tasks);
for (Future<Boolean> 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);
}
}

View File

@@ -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;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author Dave Syer
*/
public abstract class Iterables {
public static <T> Collection<T> collection(Iterable<T> iterable) {
if (iterable instanceof Collection) {
return (Collection<T>) iterable;
}
ArrayList<T> list = new ArrayList<T>();
for (T t : iterable) {
list.add(t);
}
return list;
}
}

View File

@@ -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<Number>("foo", 2.3));
this.exporter.export();
assertEquals(1, this.writer.count());
}
@Test
public void timestamp() {
this.reader.set(new Metric<Number>("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<Number>("foo", 2.3));
this.exporter.setIgnoreTimestamps(true);
this.exporter.setEarliestTimestamp(new Date(System.currentTimeMillis() + 10000));
this.exporter.export();
assertEquals(1, this.writer.count());
}
}

View File

@@ -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<Number>("foo.bar", 2.3));
this.reader.set(new Metric<Number>("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<Number>("foo.bar", 2.3));
this.reader.set(new Metric<Number>("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<Number>("foo.bar", 2.3));
this.reader.set(new Metric<Number>("foo.spam", 1.3));
this.reader.set(new Metric<Number>("foobar.spam", 1.3));
this.exporter.setGroups(Collections.singleton("foo"));
this.exporter.export();
assertEquals(1, Iterables.collection(this.writer.groups()).size());
}
}

View File

@@ -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<Number>("foo", 2.3));
this.exporter.export();
assertEquals(1, Iterables.collection(this.writer.groups()).size());
}
}

View File

@@ -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<Integer>("foo", 1, new Date()));
assertEquals(1.0, this.repository.findOne("foo").getValue().doubleValue(), 0.01);
}
@Test
public void set() {
this.repository.set(new Metric<Double>("foo", 2.5, new Date()));
assertEquals(2.5, this.repository.findOne("foo").getValue().doubleValue(), 0.01);
}
}

View File

@@ -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<Number>("foo.bar", 1));
this.repository.increment(new Delta<Number>("foo.bar", 1));
this.repository.increment(new Delta<Number>("foo.spam", 1));
Set<String> names = new HashSet<String>();
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<Number>("foo.bar", 1));
Set<String> names = new HashSet<String>();
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<Number>("foo.bar", 1));
Set<String> names = new HashSet<String>();
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<Number>("foo.bar", 1));
this.repository.increment(new Delta<Number>("foobar.spam", 1));
Set<String> names = new HashSet<String>();
for (Metric<?> metric : this.repository.findAll("foo")) {
names.add(metric.getName());
}
assertEquals(1, names.size());
assertTrue(names.contains("foo.bar"));
}
}

View File

@@ -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<Number>("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<Long>("foo", 3L));
assertEquals(3, this.repository.findOne("foo").getValue().longValue());
}
@Test
public void findAll() {
this.repository.increment(new Delta<Long>("foo", 3L));
this.repository.set(new Metric<Number>("bar", 12.3));
assertEquals(2, Iterables.collection(this.repository.findAll()).size());
}
@Test
public void findOneWithAll() {
this.repository.increment(new Delta<Long>("foo", 3L));
Metric<?> metric = this.repository.findAll().iterator().next();
assertEquals("foo", metric.getName());
}
@Test
public void count() {
this.repository.increment(new Delta<Long>("foo", 3L));
this.repository.set(new Metric<Number>("bar", 12.3));
assertEquals(2, this.repository.count());
}
}

View File

@@ -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.<Metric<?>> asList(new Metric<Number>(
"foo.val", 12.3), new Metric<Number>("foo.bar", 11.3)));
assertEquals(2, Iterables.collection(this.repository.findAll("foo")).size());
}
@Test
public void groups() {
this.repository.save("foo", Arrays.<Metric<?>> asList(new Metric<Number>(
"foo.val", 12.3), new Metric<Number>("foo.bar", 11.3)));
this.repository.save("bar", Arrays.<Metric<?>> asList(new Metric<Number>(
"bar.val", 12.3), new Metric<Number>("bar.foo", 11.3)));
assertEquals(2, Iterables.collection(this.repository.groups()).size());
}
@Test
public void count() {
this.repository.save("foo", Arrays.<Metric<?>> asList(new Metric<Number>(
"foo.val", 12.3), new Metric<Number>("foo.bar", 11.3)));
this.repository.save("bar", Arrays.<Metric<?>> asList(new Metric<Number>(
"bar.val", 12.3), new Metric<Number>("bar.foo", 11.3)));
assertEquals(2, this.repository.count());
}
}

View File

@@ -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;
}
}

View File

@@ -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<Double>("foo", 1d));
this.repository.set(new Metric<Double>("foo", 2d));
assertEquals(2L, this.repository.findOne("foo").getCount());
assertEquals(2d, this.repository.findOne("foo").getValue(), 0.01);
}
}

View File

@@ -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<String> repository = new SimpleInMemoryRepository<String>();
@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<String>() {
@Override
public String modify(String current) {
return "bar";
}
});
assertEquals("bar", this.repository.findOne("foo"));
}
@Test
public void updateNonexistent() {
this.repository.update("foo", new Callback<String>() {
@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<String> iterator = this.repository.findAllWithPrefix("foo.*").iterator();
assertEquals("spam", iterator.next());
assertFalse(iterator.hasNext());
}
@Test
public void updateConcurrent() throws Exception {
final SimpleInMemoryRepository<Integer> repository = new SimpleInMemoryRepository<Integer>();
Collection<Callable<Boolean>> tasks = new ArrayList<Callable<Boolean>>();
for (int i = 0; i < 1000; i++) {
tasks.add(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
repository.update("foo", new Callback<Integer>() {
@Override
public Integer modify(Integer current) {
if (current == null) {
return 1;
}
return current + 1;
}
});
return true;
}
});
tasks.add(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
repository.update("foo", new Callback<Integer>() {
@Override
public Integer modify(Integer current) {
if (current == null) {
return -1;
}
return current - 1;
}
});
return true;
}
});
}
List<Future<Boolean>> all = Executors.newFixedThreadPool(10).invokeAll(tasks);
for (Future<Boolean> future : all) {
assertTrue(future.get(1, TimeUnit.SECONDS));
}
assertEquals(new Integer(0), repository.findOne("foo"));
}
}

View File

@@ -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<Number>("foo", 2));
this.writer.increment(new Delta<Number>("foo", 1));
assertEquals(3, this.registry.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));
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));
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));
@SuppressWarnings("unchecked")
Gauge<Double> gauge = (Gauge<Double>) this.registry.getMetrics().get("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));
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));
assertEquals(2, this.registry.histogram("histogram.foo").getCount());
}
}

View File

@@ -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<Delta> 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<Delta> captor = ArgumentCaptor.forClass(Delta.class);
verify(this.repository).increment(captor.capture());
assertEquals("counter.foo", captor.getValue().getName());
assertEquals(-1L, captor.getValue().getValue());
}
}

View File

@@ -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<Metric> captor = ArgumentCaptor.forClass(Metric.class);
verify(this.repository).set(captor.capture());
assertEquals("gauge.foo", captor.getValue().getName());
assertEquals(2.3, captor.getValue().getValue());
}
}

View File

@@ -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<Integer>("foo", 1));
verify(this.channel).send(any(Message.class));
}
@Test
public void messageSentOnSet() {
this.observer.set(new Metric<Double>("foo", 1d));
verify(this.channel).send(any(Message.class));
}
}