Metrics: Fix RubyTimestamp serialization

This change requires setting the and retrieving the value with different types. The Gauge interface has been update to allow for setting / getting using different types.

Fixes #7719

Fixes #7720
This commit is contained in:
Jake Landis 2017-07-17 17:52:50 -05:00
parent c4983e509f
commit eaaec59380
11 changed files with 130 additions and 12 deletions

View file

@ -24,14 +24,18 @@ public enum MetricType {
* A gauge backed by a {@link Number} type * A gauge backed by a {@link Number} type
*/ */
GAUGE_NUMERIC("gauge/numeric"), GAUGE_NUMERIC("gauge/numeric"),
/** /**
* A gauge backed by a {@link Object} type. * A gauge backed by a {@link Object} type.
*/ */
GAUGE_UNKNOWN("gauge/unknown"), GAUGE_UNKNOWN("gauge/unknown"),
/** /**
* A gauge backed by a {@link org.jruby.RubyHash} type. Note - Java consumers should not use this, exist for legacy Ruby code. * A gauge backed by a {@link org.jruby.RubyHash} type. Note - Java consumers should not use this, exist for legacy Ruby code.
*/ */
GAUGE_RUBYHASH("gauge/rubyhash"); GAUGE_RUBYHASH("gauge/rubyhash"),
/**
* A gauge backed by a {@link org.logstash.ext.JrubyTimestampExtLibrary.RubyTimestamp} type. Note - Java consumers should not use this, exist for legacy Ruby code.
*/
GAUGE_RUBYTIMESTAMP("gauge/rubytimestamp");
private final String type; private final String type;

View file

@ -9,7 +9,7 @@ import java.util.List;
/** /**
* A {@link GaugeMetric} that is backed by a {@link Boolean} * A {@link GaugeMetric} that is backed by a {@link Boolean}
*/ */
public class BooleanGauge extends AbstractMetric<Boolean> implements GaugeMetric<Boolean> { public class BooleanGauge extends AbstractMetric<Boolean> implements GaugeMetric<Boolean,Boolean> {
private volatile Boolean value; private volatile Boolean value;

View file

@ -6,13 +6,14 @@ import org.logstash.instrument.metrics.Metric;
/** /**
* A {@link Metric} to set/get a value. A Gauge is useful for measuring a single value that may change over time, but does not carry any additional semantics beyond simply setting * A {@link Metric} to set/get a value. A Gauge is useful for measuring a single value that may change over time, but does not carry any additional semantics beyond simply setting
* and getting the value. * and getting the value.
* @param <T> The backing Java type for the gauge. For example, a text gauge is backed by a {@link String} * @param <G> The backing Java type for getting the gauge. For example, a text gauge should return a {@link String}
* @param <S> The backing Java type for setting the gauge. For example, a text gauge is set with a {@link String}
*/ */
public interface GaugeMetric<T> extends Metric<T> { public interface GaugeMetric<G,S> extends Metric<G> {
/** /**
* Sets the value * Sets the value
* @param value The value to set * @param value The value to set
*/ */
void set(T value); void set(S value);
} }

View file

@ -3,9 +3,10 @@ package org.logstash.instrument.metrics.gauge;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jruby.RubyHash; import org.jruby.RubyHash;
import org.logstash.Timestamp;
import org.logstash.ext.JrubyTimestampExtLibrary.RubyTimestamp;
import org.logstash.instrument.metrics.AbstractMetric; import org.logstash.instrument.metrics.AbstractMetric;
import org.logstash.instrument.metrics.MetricType; import org.logstash.instrument.metrics.MetricType;
import org.logstash.instrument.metrics.counter.CounterMetric;
import java.util.List; import java.util.List;
@ -13,7 +14,7 @@ import java.util.List;
* A lazy proxy to a more specific typed {@link GaugeMetric}. The metric will only be initialized if the initial value is set, or once the {@code set} operation is called. * A lazy proxy to a more specific typed {@link GaugeMetric}. The metric will only be initialized if the initial value is set, or once the {@code set} operation is called.
* <p><strong>Intended only for use with Ruby's duck typing, Java consumers should use the specific typed {@link GaugeMetric}</strong></p> * <p><strong>Intended only for use with Ruby's duck typing, Java consumers should use the specific typed {@link GaugeMetric}</strong></p>
*/ */
public class LazyDelegatingGauge extends AbstractMetric<Object> implements GaugeMetric<Object> { public class LazyDelegatingGauge extends AbstractMetric<Object> implements GaugeMetric<Object,Object> {
private final static Logger LOGGER = LogManager.getLogger(LazyDelegatingGauge.class); private final static Logger LOGGER = LogManager.getLogger(LazyDelegatingGauge.class);
@ -89,6 +90,8 @@ public class LazyDelegatingGauge extends AbstractMetric<Object> implements Gauge
lazyMetric = new BooleanGauge(nameSpaces, key, (Boolean) value); lazyMetric = new BooleanGauge(nameSpaces, key, (Boolean) value);
} else if (value instanceof RubyHash) { } else if (value instanceof RubyHash) {
lazyMetric = new RubyHashGauge(nameSpaces, key, (RubyHash) value); lazyMetric = new RubyHashGauge(nameSpaces, key, (RubyHash) value);
} else if (value instanceof RubyTimestamp) {
lazyMetric = new RubyTimeStampGauge(nameSpaces, key, ((RubyTimestamp) value));
} else { } else {
LOGGER.warn("A gauge metric of an unknown type ({}) has been create for key: {}, namespace:{}. This may result in invalid serialization. It is recommended to " + LOGGER.warn("A gauge metric of an unknown type ({}) has been create for key: {}, namespace:{}. This may result in invalid serialization. It is recommended to " +
"log an issue to the responsible developer/development team.", value.getClass().getCanonicalName(), key, nameSpaces); "log an issue to the responsible developer/development team.", value.getClass().getCanonicalName(), key, nameSpaces);

View file

@ -8,7 +8,7 @@ import java.util.List;
/** /**
* A {@link GaugeMetric} that is backed by a {@link Number} * A {@link GaugeMetric} that is backed by a {@link Number}
*/ */
public class NumericGauge extends AbstractMetric<Number> implements GaugeMetric<Number> { public class NumericGauge extends AbstractMetric<Number> implements GaugeMetric<Number,Number> {
private volatile Number value; private volatile Number value;

View file

@ -4,13 +4,14 @@ import org.jruby.RubyHash;
import org.logstash.instrument.metrics.AbstractMetric; import org.logstash.instrument.metrics.AbstractMetric;
import org.logstash.instrument.metrics.MetricType; import org.logstash.instrument.metrics.MetricType;
import java.util.List; import java.util.List;
/** /**
* A {@link GaugeMetric} that is backed by a {@link RubyHash}. Note - This should not be used directly from Java code and exists for passivity with legacy Ruby code. Depending * A {@link GaugeMetric} that is backed by a {@link RubyHash}. Note - This should not be used directly from Java code and exists for passivity with legacy Ruby code. Depending
* on the types in in the {@link RubyHash} there are no guarantees serializing properly. * on the types in in the {@link RubyHash} there are no guarantees serializing properly.
*/ */
public class RubyHashGauge extends AbstractMetric<RubyHash> implements GaugeMetric<RubyHash> { public class RubyHashGauge extends AbstractMetric<RubyHash> implements GaugeMetric<RubyHash,RubyHash> {
private volatile RubyHash value; private volatile RubyHash value;

View file

@ -0,0 +1,55 @@
package org.logstash.instrument.metrics.gauge;
import org.logstash.Timestamp;
import org.logstash.bivalues.BiValues;
import org.logstash.ext.JrubyTimestampExtLibrary.RubyTimestamp;
import org.logstash.instrument.metrics.AbstractMetric;
import org.logstash.instrument.metrics.MetricType;
import java.util.List;
/**
* A {@link GaugeMetric} that is set by a {@link RubyTimestamp}, and retrieved/serialized as a {@link Timestamp}. Note - This should not be used directly from Java code and
* exists for passivity with legacy Ruby code.
*/
public class RubyTimeStampGauge extends AbstractMetric<Timestamp> implements GaugeMetric<Timestamp, RubyTimestamp> {
private volatile Timestamp value;
/**
* Constructor - protected so that Ruby may sub class proxy and discourage usage from Java, null initial value
*
* @param nameSpace The namespace for this metric
* @param key The key <i>(with in the namespace)</i> for this metric
*/
protected RubyTimeStampGauge(List<String> nameSpace, String key) {
this(nameSpace, key, null);
}
/**
* Constructor - protected so that Ruby may sub class proxy and discourage usage from Java
*
* @param nameSpace The namespace for this metric
* @param key The key <i>(with in the namespace)</i> for this metric
* @param initialValue The initial value for this {@link GaugeMetric}, may be null
*/
protected RubyTimeStampGauge(List<String> nameSpace, String key, RubyTimestamp initialValue) {
super(nameSpace, key);
this.value = initialValue == null ? null : initialValue.getTimestamp();
}
@Override
public MetricType getType() {
return MetricType.GAUGE_RUBYTIMESTAMP;
}
@Override
public Timestamp getValue() {
return value;
}
@Override
public void set(RubyTimestamp value) {
this.value = value == null ? null : value.getTimestamp();
}
}

View file

@ -9,7 +9,7 @@ import java.util.List;
/** /**
* A {@link GaugeMetric} that is backed by a {@link String} * A {@link GaugeMetric} that is backed by a {@link String}
*/ */
public class TextGauge extends AbstractMetric<String> implements GaugeMetric<String> { public class TextGauge extends AbstractMetric<String> implements GaugeMetric<String,String> {
private volatile String value; private volatile String value;

View file

@ -8,7 +8,7 @@ import java.util.List;
/** /**
* A {@link GaugeMetric} that is backed by a {@link Object}. Note - A stronger typed {@link GaugeMetric} should be used since this makes no guarantees of serializing properly. * A {@link GaugeMetric} that is backed by a {@link Object}. Note - A stronger typed {@link GaugeMetric} should be used since this makes no guarantees of serializing properly.
*/ */
public class UnknownGauge extends AbstractMetric<Object> implements GaugeMetric<Object> { public class UnknownGauge extends AbstractMetric<Object> implements GaugeMetric<Object,Object> {
private volatile Object value; private volatile Object value;

View file

@ -27,6 +27,7 @@ public class MetricTypeTest {
nameMap.put(MetricType.GAUGE_NUMERIC, "gauge/numeric"); nameMap.put(MetricType.GAUGE_NUMERIC, "gauge/numeric");
nameMap.put(MetricType.GAUGE_UNKNOWN, "gauge/unknown"); nameMap.put(MetricType.GAUGE_UNKNOWN, "gauge/unknown");
nameMap.put(MetricType.GAUGE_RUBYHASH, "gauge/rubyhash"); nameMap.put(MetricType.GAUGE_RUBYHASH, "gauge/rubyhash");
nameMap.put(MetricType.GAUGE_RUBYTIMESTAMP, "gauge/rubytimestamp");
//ensure we are testing all of the enumerations //ensure we are testing all of the enumerations
assertThat(EnumSet.allOf(MetricType.class).size()).isEqualTo(nameMap.size()); assertThat(EnumSet.allOf(MetricType.class).size()).isEqualTo(nameMap.size());

View file

@ -0,0 +1,53 @@
package org.logstash.instrument.metrics.gauge;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.logstash.Timestamp;
import org.logstash.ext.JrubyTimestampExtLibrary.RubyTimestamp;
import org.logstash.instrument.metrics.MetricType;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link RubyTimeStampGauge}
*/
@RunWith(MockitoJUnitRunner.class)
public class RubyTimeStampGaugeTest {
@Mock
private RubyTimestamp rubyTimestamp;
private final Timestamp timestamp = new Timestamp();
@Before
public void _setup() {
when(rubyTimestamp.getTimestamp()).thenReturn(timestamp);
}
@Test
public void getValue() {
RubyTimeStampGauge gauge = new RubyTimeStampGauge(Collections.singletonList("foo"), "bar", rubyTimestamp);
assertThat(gauge.getValue()).isEqualTo(rubyTimestamp.getTimestamp());
assertThat(gauge.getType()).isEqualTo(MetricType.GAUGE_RUBYTIMESTAMP);
//Null initialize
gauge = new RubyTimeStampGauge(Collections.singletonList("foo"), "bar");
assertThat(gauge.getValue()).isNull();
assertThat(gauge.getType()).isEqualTo(MetricType.GAUGE_RUBYTIMESTAMP);
}
@Test
public void set() {
RubyTimeStampGauge gauge = new RubyTimeStampGauge(Collections.singletonList("foo"), "bar", Mockito.mock(RubyTimestamp.class));
gauge.set(rubyTimestamp);
assertThat(gauge.getValue()).isEqualTo(rubyTimestamp.getTimestamp());
assertThat(gauge.getType()).isEqualTo(MetricType.GAUGE_RUBYTIMESTAMP);
}
}