Add increment() to PrefixMetricWriter

In the redis repository we also switch to store the value in the
zset (so it can be atomically incremented) rather than in the
regular key-value.

Fixes gh-929
This commit is contained in:
Dave Syer 2014-05-22 12:02:51 +01:00
parent 14899ba3b2
commit 0cbd0b609a
8 changed files with 100 additions and 33 deletions

View File

@ -85,7 +85,7 @@ public class PrefixMetricGroupExporter extends AbstractMetricExporter {
@Override
protected void write(String group, Collection<Metric<?>> values) {
this.writer.save(group, values);
this.writer.set(group, values);
}
}

View File

@ -25,6 +25,7 @@ import org.springframework.boot.actuate.metrics.repository.MultiMetricRepository
import org.springframework.boot.actuate.metrics.rich.RichGauge;
import org.springframework.boot.actuate.metrics.rich.RichGaugeReader;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.boot.actuate.metrics.writer.PrefixMetricWriter;
/**
* Exporter or converter for {@link RichGauge} data to a metric-based back end. Each gauge
@ -53,13 +54,14 @@ public class RichGaugeExporter extends AbstractMetricExporter {
private static final String ALPHA = ".alpha";
private final RichGaugeReader reader;
private final MetricWriter writer;
private final PrefixMetricWriter writer;
public RichGaugeExporter(RichGaugeReader reader, MetricWriter writer) {
public RichGaugeExporter(RichGaugeReader reader, PrefixMetricWriter writer) {
this(reader, writer, "");
}
public RichGaugeExporter(RichGaugeReader reader, MetricWriter writer, String prefix) {
public RichGaugeExporter(RichGaugeReader reader, PrefixMetricWriter writer,
String prefix) {
super(prefix);
this.reader = reader;
this.writer = writer;
@ -89,14 +91,7 @@ public class RichGaugeExporter extends AbstractMetricExporter {
@Override
protected void write(String group, Collection<Metric<?>> values) {
if (this.writer instanceof MultiMetricRepository) {
((MultiMetricRepository) this.writer).save(group, values);
}
else {
for (Metric<?> value : values) {
this.writer.set(value);
}
}
this.writer.set(group, values);
}
}

View File

@ -71,7 +71,7 @@ public class InMemoryMetricRepository implements MetricRepository, MultiMetricRe
}
@Override
public void save(String group, Collection<Metric<?>> values) {
public void set(String group, Collection<Metric<?>> values) {
String prefix = group;
if (!prefix.endsWith(".")) {
prefix = prefix + ".";
@ -86,6 +86,20 @@ public class InMemoryMetricRepository implements MetricRepository, MultiMetricRe
this.groups.add(group);
}
@Override
public void increment(String group, Delta<?> delta) {
String prefix = group;
if (!prefix.endsWith(".")) {
prefix = prefix + ".";
}
if (!delta.getName().startsWith(prefix)) {
delta = new Delta<Number>(prefix + delta.getName(), delta.getValue(),
delta.getTimestamp());
}
increment(delta);
this.groups.add(group);
}
@Override
public Iterable<String> groups() {
return Collections.unmodifiableCollection(this.groups);

View File

@ -25,6 +25,7 @@ import java.util.Set;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.repository.MultiMetricRepository;
import org.springframework.boot.actuate.metrics.writer.Delta;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisOperations;
@ -67,10 +68,10 @@ public class RedisMultiMetricRepository implements MultiMetricRepository {
}
@Override
public Iterable<Metric<?>> findAll(String metricNamePrefix) {
public Iterable<Metric<?>> findAll(String group) {
BoundZSetOperations<String, String> zSetOperations = this.redisOperations
.boundZSetOps(keyFor(metricNamePrefix));
.boundZSetOps(keyFor(group));
Set<String> keys = zSetOperations.range(0, -1);
Iterator<String> keysIt = keys.iterator();
@ -78,14 +79,15 @@ public class RedisMultiMetricRepository implements MultiMetricRepository {
List<Metric<?>> result = new ArrayList<Metric<?>>(keys.size());
List<String> values = this.redisOperations.opsForValue().multiGet(keys);
for (String v : values) {
result.add(deserialize(keysIt.next(), v));
String key = keysIt.next();
result.add(deserialize(group, key, v, zSetOperations.score(key)));
}
return result;
}
@Override
public void save(String group, Collection<Metric<?>> values) {
public void set(String group, Collection<Metric<?>> values) {
String groupKey = keyFor(group);
trackMembership(groupKey);
BoundZSetOperations<String, String> zSetOperations = this.redisOperations
@ -93,11 +95,24 @@ public class RedisMultiMetricRepository implements MultiMetricRepository {
for (Metric<?> metric : values) {
String raw = serialize(metric);
String key = keyFor(metric.getName());
zSetOperations.add(key, 0.0D);
zSetOperations.add(key, metric.getValue().doubleValue());
this.redisOperations.opsForValue().set(key, raw);
}
}
@Override
public void increment(String group, Delta<?> delta) {
String groupKey = keyFor(group);
trackMembership(groupKey);
BoundZSetOperations<String, String> zSetOperations = this.redisOperations
.boundZSetOps(groupKey);
String key = keyFor(delta.getName());
double value = zSetOperations.incrementScore(key, delta.getValue().doubleValue());
String raw = serialize(new Metric<Double>(delta.getName(), value,
delta.getTimestamp()));
this.redisOperations.opsForValue().set(key, raw);
}
@Override
public Iterable<String> groups() {
Set<String> range = this.zSetOperations.range(0, -1);
@ -128,15 +143,17 @@ public class RedisMultiMetricRepository implements MultiMetricRepository {
this.zSetOperations.remove(groupKey);
}
private Metric<?> deserialize(String redisKey, String v) {
String[] vals = v.split("@");
Double value = Double.valueOf(vals[0]);
Date timestamp = vals.length > 1 ? new Date(Long.valueOf(vals[1])) : new Date();
return new Metric<Double>(nameFor(redisKey), value, timestamp);
private Metric<?> deserialize(String group, String redisKey, String v, Double value) {
String prefix = group;
if (!group.endsWith(".")) {
prefix = group + ".";
}
Date timestamp = new Date(Long.valueOf(v));
return new Metric<Double>(prefix + nameFor(redisKey), value, timestamp);
}
private String serialize(Metric<?> entity) {
return String.valueOf(entity.getValue() + "@" + entity.getTimestamp().getTime());
return String.valueOf(entity.getTimestamp().getTime());
}
private String keyFor(String name) {

View File

@ -33,7 +33,14 @@ public interface PrefixMetricWriter {
* @param group the name of the group
* @param values the metric values to save
*/
void save(String group, Collection<Metric<?>> values);
void set(String group, Collection<Metric<?>> values);
/**
* Increment the value of a metric (or decrement if the delta is negative). The name
* of the metric to increment is <code>group + "." + delta.name</code>.
* @param delta the amount to increment by
*/
void increment(String group, Delta<?> delta);
/**
* Rest the values of all metrics in the group. Implementations may choose to discard

View File

@ -60,7 +60,7 @@ public class PrefixMetricGroupExporterTests {
@Test
public void multiMetricGroupsCopiedAsDefault() {
this.reader.save("foo", Arrays.<Metric<?>> asList(new Metric<Number>("bar", 2.3),
this.reader.set("foo", Arrays.<Metric<?>> asList(new Metric<Number>("bar", 2.3),
new Metric<Number>("spam", 1.3)));
this.exporter.export();
assertEquals(1, this.writer.countGroups());

View File

@ -47,7 +47,7 @@ public class InMemoryPrefixMetricRepositoryTests {
}
@Test
public void perfixWithWildcard() {
public void prefixWithWildcard() {
this.repository.increment(new Delta<Number>("foo.bar", 1));
Set<String> names = new HashSet<String>();
for (Metric<?> metric : this.repository.findAll("foo.*")) {
@ -58,7 +58,7 @@ public class InMemoryPrefixMetricRepositoryTests {
}
@Test
public void perfixWithPeriod() {
public void prefixWithPeriod() {
this.repository.increment(new Delta<Number>("foo.bar", 1));
Set<String> names = new HashSet<String>();
for (Metric<?> metric : this.repository.findAll("foo.")) {
@ -80,4 +80,18 @@ public class InMemoryPrefixMetricRepositoryTests {
assertTrue(names.contains("foo.bar"));
}
@Test
public void incrementGroup() {
this.repository.increment("foo", new Delta<Number>("bar", 1));
this.repository.increment("foo", new Delta<Number>("bar", 2));
this.repository.increment("foo", new Delta<Number>("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"));
assertEquals(3L, this.repository.findOne("foo.bar").getValue());
}
}

View File

@ -17,7 +17,9 @@
package org.springframework.boot.actuate.metrics.repository.redis;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
@ -29,6 +31,7 @@ import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
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;
@ -78,27 +81,44 @@ public class RedisMultiMetricRepositoryTests {
@Test
public void setAndGet() {
this.repository.save("foo", Arrays.<Metric<?>> asList(new Metric<Number>(
this.repository.set("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>(
this.repository.set("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>(
this.repository.set("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>(
this.repository.set("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>(
this.repository.set("bar", Arrays.<Metric<?>> asList(new Metric<Number>(
"bar.val", 12.3), new Metric<Number>("bar.foo", 11.3)));
assertEquals(2, this.repository.countGroups());
}
@Test
public void increment() {
this.repository.increment("foo", new Delta<Number>("bar", 1));
this.repository.increment("foo", new Delta<Number>("bar", 2));
this.repository.increment("foo", new Delta<Number>("spam", 1));
Metric<?> bar = null;
Set<String> names = new HashSet<String>();
for (Metric<?> metric : this.repository.findAll("foo")) {
names.add(metric.getName());
if (metric.getName().equals("foo.bar")) {
bar = metric;
}
}
assertEquals(2, names.size());
assertTrue("Wrong names: " + names, names.contains("foo.bar"));
assertEquals(3d, bar.getValue());
}
}