PERORMANCE: Remove all BiValue usage by removing NullBiValue

Fixes #8371
This commit is contained in:
Armin 2017-09-22 08:52:50 +02:00 committed by Armin Braun
parent 129519df67
commit be573eb878
14 changed files with 81 additions and 289 deletions

View file

@ -12,10 +12,10 @@ import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joda.time.DateTime;
import org.jruby.RubyNil;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.logstash.ackedqueue.Queueable;
import org.logstash.bivalues.NullBiValue;
import org.logstash.ext.JrubyTimestampExtLibrary;
import static org.logstash.ObjectMappers.CBOR_MAPPER;
@ -283,7 +283,7 @@ public final class Event implements Cloneable, Queueable {
}
private static Timestamp initTimestamp(Object o) {
if (o == null || o instanceof NullBiValue) {
if (o == null || o instanceof RubyNil) {
// most frequent
return new Timestamp();
} else {

View file

@ -8,20 +8,16 @@ import org.jruby.RubyBignum;
import org.jruby.RubyBoolean;
import org.jruby.RubyFixnum;
import org.jruby.RubyFloat;
import org.jruby.RubyNil;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.ext.bigdecimal.RubyBigDecimal;
import org.logstash.bivalues.BiValue;
import org.logstash.bivalues.BiValues;
import org.logstash.ext.JrubyTimestampExtLibrary;
public final class Javafier {
private static final Map<Class<?>, Valuefier.Converter> CONVERTER_MAP = initConverters();
private static final Valuefier.Converter BIVALUES_CONVERTER =
value -> BiValues.newBiValue(value).javaValue();
/**
* Javafier.deep() is called by getField.
* When any value is added to the Event it should pass through Valuefier.convert.
@ -32,6 +28,9 @@ public final class Javafier {
}
public static Object deep(Object o) {
if (o == null) {
return null;
}
final Class<?> cls = o.getClass();
final Valuefier.Converter converter = CONVERTER_MAP.get(cls);
if (converter != null) {
@ -48,8 +47,7 @@ public final class Javafier {
return found.convert(o);
}
}
CONVERTER_MAP.put(cls, BIVALUES_CONVERTER);
return BIVALUES_CONVERTER.convert(o);
throw new MissingConverterException(cls);
}
private static Map<Class<?>, Valuefier.Converter> initConverters() {
@ -57,6 +55,7 @@ public final class Javafier {
new ConcurrentHashMap<>(50, 0.2F, 1);
converters.put(String.class, Valuefier.IDENTITY);
converters.put(Float.class, Valuefier.IDENTITY);
converters.put(RubyNil.class, value -> null);
converters.put(Double.class, Valuefier.IDENTITY);
converters.put(Long.class, Valuefier.IDENTITY);
converters.put(Integer.class, Valuefier.IDENTITY);
@ -73,7 +72,6 @@ public final class Javafier {
RubyBigDecimal.class, value -> ((RubyBigDecimal) value).getBigDecimalValue()
);
converters.put(RubyBoolean.class, value -> ((RubyBoolean) value).isTrue());
converters.put(BiValue.class, value -> ((BiValue<?, ?>) value).javaValue());
converters.put(RubyFixnum.class, value -> ((RubyFixnum) value).getLongValue());
converters.put(RubyFloat.class, value -> ((RubyFloat) value).getDoubleValue());
converters.put(ConvertedMap.class, value -> ((ConvertedMap) value).unconvert());

View file

@ -1,7 +1,6 @@
package org.logstash;
import java.util.List;
import org.logstash.bivalues.BiValue;
/**
* Created by ph on 15-05-22.
@ -31,9 +30,6 @@ public class KeyNode {
private static String toString(Object value, String delim) {
if (value == null) return "";
if (value instanceof List) return join((List)value, delim);
if (value instanceof BiValue) {
return value.toString();
}
return value.toString();
}
}

View file

@ -0,0 +1,19 @@
package org.logstash;
/**
* Exception thrown by {@link Javafier}, {@link Rubyfier} and {@link Valuefier} if trying to convert
* an illegal argument type.
*/
final class MissingConverterException extends RuntimeException {
private static final long serialVersionUID = 1L;
MissingConverterException(final Class<?> cls) {
super(
String.format(
"Missing Converter handling for full class name=%s, simple name=%s",
cls.getName(), cls.getSimpleName()
)
);
}
}

View file

@ -20,6 +20,7 @@ import org.jruby.RubyBignum;
import org.jruby.RubyBoolean;
import org.jruby.RubyFixnum;
import org.jruby.RubyFloat;
import org.jruby.RubyNil;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.ext.bigdecimal.RubyBigDecimal;
@ -35,12 +36,14 @@ public final class ObjectMappers {
.addSerializer(RubyBoolean.class, new RubyBooleanSerializer())
.addSerializer(RubyFixnum.class, new RubyFixnumSerializer())
.addSerializer(RubyBigDecimal.class, new RubyBigDecimalSerializer())
.addSerializer(RubyBignum.class, new RubyBignumSerializer());
.addSerializer(RubyBignum.class, new RubyBignumSerializer())
.addSerializer(RubyNil.class, new RubyNilSerializer());
private static final SimpleModule CBOR_DESERIALIZERS =
new SimpleModule("CborRubyDeserializers")
.addDeserializer(RubyBigDecimal.class, new RubyBigDecimalDeserializer())
.addDeserializer(RubyBignum.class, new RubyBignumDeserializer());
.addDeserializer(RubyBignum.class, new RubyBignumDeserializer())
.addDeserializer(RubyNil.class, new RubyNilDeserializer());
public static final ObjectMapper JSON_MAPPER =
new ObjectMapper().registerModule(RUBY_SERIALIZERS);
@ -297,4 +300,41 @@ public final class ObjectMappers {
typeSer.writeTypeSuffixForScalar(value, jgen);
}
}
/**
* Serializer for {@link RubyNil} that serializes it to as an empty {@link String} for JSON
* serialization and as a typed {@link RubyNil} for CBOR.
*/
private static final class RubyNilSerializer extends StdSerializer<RubyNil> {
RubyNilSerializer() {
super(RubyNil.class);
}
@Override
public void serialize(final RubyNil value, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeString("");
}
@Override
public void serializeWithType(final RubyNil value, final JsonGenerator jgen,
final SerializerProvider serializers, final TypeSerializer typeSer) throws IOException {
typeSer.writeTypePrefixForScalar(value, jgen, RubyNil.class);
jgen.writeNull();
typeSer.writeTypeSuffixForScalar(value, jgen);
}
}
private static final class RubyNilDeserializer extends StdDeserializer<RubyNil> {
RubyNilDeserializer() {
super(RubyNil.class);
}
@Override
public RubyNil deserialize(final JsonParser p, final DeserializationContext ctxt) {
return (RubyNil) RubyUtil.RUBY.getNil();
}
}
}

View file

@ -12,19 +12,15 @@ import org.jruby.RubyBoolean;
import org.jruby.RubyFixnum;
import org.jruby.RubyFloat;
import org.jruby.RubyHash;
import org.jruby.RubyNil;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.ext.bigdecimal.RubyBigDecimal;
import org.jruby.runtime.builtin.IRubyObject;
import org.logstash.bivalues.BiValue;
import org.logstash.bivalues.BiValues;
import org.logstash.ext.JrubyTimestampExtLibrary;
public final class Rubyfier {
private static final Rubyfier.Converter BIVALUES_CONVERTER =
(ruby, val) -> BiValues.newBiValue(val).rubyValue(ruby);
private static final Rubyfier.Converter IDENTITY = (runtime, input) -> (IRubyObject) input;
private static final Rubyfier.Converter FLOAT_CONVERTER =
@ -79,6 +75,7 @@ public final class Rubyfier {
final Map<Class<?>, Rubyfier.Converter> converters =
new ConcurrentHashMap<>(50, 0.2F, 1);
converters.put(RubyString.class, IDENTITY);
converters.put(RubyNil.class, IDENTITY);
converters.put(RubySymbol.class, IDENTITY);
converters.put(RubyBignum.class, IDENTITY);
converters.put(RubyBigDecimal.class, IDENTITY);
@ -98,9 +95,6 @@ public final class Rubyfier {
);
converters.put(Long.class, LONG_CONVERTER);
converters.put(Boolean.class, (runtime, input) -> runtime.newBoolean((Boolean) input));
converters.put(
BiValue.class, (runtime, input) -> ((BiValue<?, ?>) input).rubyValue(runtime)
);
converters.put(Map.class, (runtime, input) -> deepMap(runtime, (Map<?, ?>) input));
converters.put(
Collection.class, (runtime, input) -> deepList(runtime, (Collection<?>) input)
@ -126,8 +120,7 @@ public final class Rubyfier {
return found.convert(runtime, o);
}
}
CONVERTER_MAP.put(cls, BIVALUES_CONVERTER);
return BIVALUES_CONVERTER.convert(runtime, o);
throw new MissingConverterException(cls);
}
private interface Converter {

View file

@ -12,6 +12,7 @@ import org.jruby.RubyBoolean;
import org.jruby.RubyFixnum;
import org.jruby.RubyFloat;
import org.jruby.RubyHash;
import org.jruby.RubyNil;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.RubyTime;
@ -22,8 +23,6 @@ import org.jruby.java.proxies.JavaProxy;
import org.jruby.java.proxies.MapJavaProxy;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.builtin.IRubyObject;
import org.logstash.bivalues.BiValue;
import org.logstash.bivalues.BiValues;
import org.logstash.ext.JrubyTimestampExtLibrary;
public final class Valuefier {
@ -59,8 +58,6 @@ public final class Valuefier {
}
};
private static final Valuefier.Converter BIVALUES_CONVERTER = BiValues::newBiValue;
private static final Map<Class<?>, Valuefier.Converter> CONVERTER_MAP = initConverters();
private Valuefier() {
@ -68,7 +65,7 @@ public final class Valuefier {
public static Object convert(final Object o) {
if (o == null) {
return BiValues.NULL_BI_VALUE;
return RubyUtil.RUBY.getNil();
}
final Class<?> cls = o.getClass();
final Valuefier.Converter converter = CONVERTER_MAP.get(cls);
@ -84,8 +81,7 @@ public final class Valuefier {
* the only subclasses of the keys in {@link Valuefier#CONVERTER_MAP} as set up by
* {@link Valuefier#initConverters()} can be converted here and hence find the appropriate
* super class for unknown types by checking each entry in {@link Valuefier#CONVERTER_MAP} for
* being a supertype of the given class. If this fails {@link Valuefier#BIVALUES_CONVERTER}
* will be cached and used.
* being a supertype of the given class.
* @param o Object to convert
* @param cls Class of given object {@code o}
* @return Conversion result equivalent to what {@link Valuefier#convert(Object)} would return
@ -98,14 +94,14 @@ public final class Valuefier {
return found.convert(o);
}
}
CONVERTER_MAP.put(cls, BIVALUES_CONVERTER);
return BIVALUES_CONVERTER.convert(o);
throw new MissingConverterException(cls);
}
private static Map<Class<?>, Valuefier.Converter> initConverters() {
final Map<Class<?>, Valuefier.Converter> converters =
new ConcurrentHashMap<>(50, 0.2F, 1);
converters.put(RubyString.class, IDENTITY);
converters.put(RubyNil.class, IDENTITY);
converters.put(RubySymbol.class, IDENTITY);
converters.put(RubyFixnum.class, IDENTITY);
converters.put(JrubyTimestampExtLibrary.RubyTimestamp.class, IDENTITY);
@ -115,7 +111,6 @@ public final class Valuefier {
converters.put(RubyBoolean.class, IDENTITY);
converters.put(RubyBignum.class, IDENTITY);
converters.put(RubyBigDecimal.class, IDENTITY);
converters.put(BiValue.class, IDENTITY);
converters.put(String.class, input -> RubyUtil.RUBY.newString((String) input));
converters.put(Float.class, FLOAT_CONVERTER);
converters.put(Double.class, FLOAT_CONVERTER);

View file

@ -1,112 +0,0 @@
package org.logstash.bivalues;
import com.fasterxml.jackson.annotation.JsonValue;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import org.jruby.Ruby;
import org.jruby.runtime.builtin.IRubyObject;
public abstract class BiValue<R extends IRubyObject, J> implements Serializable {
private static final long serialVersionUID = -8602478677605589528L;
protected transient R rubyValue;
protected J javaValue;
public final R rubyValue(Ruby runtime) {
if (hasRubyValue()) {
return rubyValue;
}
addRuby(runtime);
return rubyValue;
}
@JsonValue
public J javaValue() {
if (javaValue == null) {
addJava();
}
return javaValue;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (hasJavaValue() && javaValue.getClass().isAssignableFrom(o.getClass())){
return javaValue.equals(o);
}
if(!(o instanceof BiValue)) {
return false;
}
BiValue<?, ?> other = (BiValue<?, ?>) o;
return (other.hasJavaValue() && other.javaValue().equals(javaValue)) ||
(other.hasRubyValue() && other.rubyValueUnconverted().equals(rubyValue));
}
@Override
public final int hashCode() {
if (hasRubyValue()) {
return rubyValue.hashCode();
}
if (hasJavaValue()) {
return javaValue.hashCode();
}
return 0;
}
public final R rubyValueUnconverted() {
return rubyValue;
}
public boolean hasRubyValue() {
return null != rubyValue;
}
public boolean hasJavaValue() {
return null != javaValue;
}
protected abstract void addRuby(Ruby runtime);
protected abstract void addJava();
@Override
public String toString() {
if (hasRubyValue()) {
javaValue();
}
if (javaValue == null) {
return "";
}
return String.valueOf(javaValue);
}
protected static Object newProxy(BiValue<?, ?> instance) {
return new SerializationProxy(instance);
}
private static final class SerializationProxy implements Serializable {
private static final long serialVersionUID = -1749700725129586973L;
private final Object javaValue;
public SerializationProxy(BiValue o) {
javaValue = o.javaValue(); // ensure the javaValue is converted from a ruby one if it exists
}
private Object readResolve() throws ObjectStreamException {
return BiValues.newBiValue(javaValue);
}
}
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
}

View file

@ -1,41 +0,0 @@
package org.logstash.bivalues;
import java.util.HashMap;
import java.util.Map;
import org.jruby.RubyNil;
public final class BiValues {
private BiValues() {
}
public static final NullBiValue NULL_BI_VALUE = NullBiValue.newNullBiValue();
private static final Map<Class<?>, BiValues.BiValueType> CONVERTER_CACHE = initCache();
public static BiValue newBiValue(Object o) {
if (o == null) {
return NULL_BI_VALUE;
}
final Class<?> cls = o.getClass();
final BiValues.BiValueType type = CONVERTER_CACHE.get(cls);
if (type == null) {
throw new IllegalArgumentException(
String.format(
"Missing Converter handling for full class name=%s, simple name=%s",
cls.getName(), cls.getSimpleName()
)
);
}
return type.build(o);
}
private interface BiValueType {
BiValue build(Object value);
}
private static Map<Class<?>, BiValues.BiValueType> initCache() {
final Map<Class<?>, BiValues.BiValueType> hm = new HashMap<>(50, 0.2F);
hm.put(RubyNil.class, value -> NULL_BI_VALUE);
return hm;
}
}

View file

@ -1,50 +0,0 @@
package org.logstash.bivalues;
import com.fasterxml.jackson.annotation.JsonValue;
import java.io.ObjectStreamException;
import org.jruby.Ruby;
import org.jruby.RubyNil;
public final class NullBiValue extends BiValue<RubyNil, Object> {
private static final NullBiValue INSTANCE =
new NullBiValue((RubyNil) Ruby.getGlobalRuntime().getNil());
private static final Object WRITE_PROXY = newProxy(INSTANCE);
public static NullBiValue newNullBiValue() {
return INSTANCE;
}
private NullBiValue(final RubyNil rubyValue) {
this.rubyValue = rubyValue;
javaValue = null;
}
@JsonValue
@Override
public Object javaValue() {
return null;
}
@Override
public boolean hasJavaValue() {
return true;
}
@Override
public boolean hasRubyValue() {
return true;
}
@Override
protected void addRuby(Ruby runtime) {}
@Override
protected void addJava() {}
// Called when object is to be serialized on a stream to allow the object to substitute a proxy for itself.
private Object writeReplace() throws ObjectStreamException {
return WRITE_PROXY;
}
}

View file

@ -22,6 +22,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
public final class EventTest {
@ -34,6 +35,7 @@ public final class EventTest {
inner.put("innerFoo", 42L);
final RubySymbol symbol = RubyUtil.RUBY.newSymbol("val");
e.setField("symbol", symbol);
e.setField("null", null);
inner.put("innerQuux", 42.42);
e.setField("baz", inner);
final BigInteger bigint = BigInteger.valueOf(Long.MAX_VALUE).multiply(BigInteger.TEN);
@ -51,6 +53,7 @@ public final class EventTest {
assertEquals(42L, er.getField("[baz][innerFoo]"));
assertEquals(42.42, er.getField("[baz][innerQuux]"));
assertEquals(42L, er.getField("[@metadata][foo]"));
assertNull(er.getField("null"));
assertEquals(e.getTimestamp().toString(), er.getTimestamp().toString());
}

View file

@ -78,7 +78,7 @@ public class ValuefierTest extends TestBase {
@Test
public void testUnhandledObject() {
RubyMatchData md = new RubyMatchData(ruby);
exception.expect(IllegalArgumentException.class);
exception.expect(MissingConverterException.class);
exception.expectMessage("Missing Converter handling for full class name=org.jruby.RubyMatchData, simple name=RubyMatchData");
Valuefier.convert(md);
}

View file

@ -1,18 +0,0 @@
package org.logstash.bivalues;
import org.junit.Test;
import org.logstash.TestBase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class BiValueTest extends TestBase {
@Test
public void testNullBiValueFromJava() {
NullBiValue subject = NullBiValue.newNullBiValue();
assertTrue(subject.hasRubyValue());
assertTrue(subject.hasJavaValue());
assertEquals(ruby.getNil(), subject.rubyValue(ruby));
}
}

View file

@ -1,31 +0,0 @@
package org.logstash.bivalues;
import org.jruby.RubyNil;
import org.junit.Test;
import org.logstash.TestBase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class BiValuesTest extends TestBase {
@Test
public void testBiValuesNilRuby() {
RubyNil ro = (RubyNil) ruby.getNil();
BiValue subject = BiValues.newBiValue(ro);
assertEquals(ro, subject.rubyValueUnconverted());
assertEquals(ro.getClass(), subject.rubyValue(ruby).getClass());
assertNull(subject.javaValue());
}
@Test
public void testBiValuesNullJava() {
RubyNil ro = (RubyNil) ruby.getNil();
BiValue subject = BiValues.newBiValue(null);
assertNull(subject.javaValue());
assertEquals(ro, subject.rubyValue(ruby));
assertEquals(ro.getClass(), subject.rubyValue(ruby).getClass());
}
}