diff --git a/data-model/src/main/java/org/hypertrace/core/datamodel/shared/AvroBuilderCache.java b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/AvroBuilderCache.java new file mode 100644 index 0000000..cbf8299 --- /dev/null +++ b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/AvroBuilderCache.java @@ -0,0 +1,70 @@ +package org.hypertrace.core.datamodel.shared; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import org.apache.avro.specific.SpecificRecord; +import org.apache.avro.specific.SpecificRecordBuilderBase; +import org.apache.commons.lang3.tuple.Pair; + +/** + * Utility class to instantiate a new avro builder in a fast and efficient way. Default Avro + * implementation has a perf issue due to uncached reflection based implementation. For more + * details, https://issues.apache.org/jira/browse/AVRO-3048 + * + *

This avro perf bug is fixed in unreleased avro version (0.11.x). We can get rid of this + * utility class once we migrate to Avro 0.11.x or above + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class AvroBuilderCache { + private static final Map< + Class, Pair> + AVRO_BUILDER_CACHE = new ConcurrentHashMap<>(); + + public static T fastNewBuilder( + Class specificAvroBuilderClass) { + try { + Pair pair = + AVRO_BUILDER_CACHE.computeIfAbsent( + (Class) specificAvroBuilderClass, + AvroBuilderCache::newBuilderFor); + return (T) pair.getLeft().invoke(null, pair.getRight()); + } catch (Exception e) { + throw new RuntimeException( + "Unable to instantiate new builder for: " + specificAvroBuilderClass.getName(), e); + } + } + + private static Pair newBuilderFor( + @Nonnull Class specificAvroBuilderClass) { + try { + Class specificAvroClass = + (Class) specificAvroBuilderClass.getEnclosingClass(); + Method newBuilderMethod = findBuilderMethod(specificAvroClass); + + final Constructor declaredConstructor = specificAvroBuilderClass.getDeclaredConstructor(); + declaredConstructor.setAccessible(true); + SpecificRecordBuilderBase builderModel = + (SpecificRecordBuilderBase) declaredConstructor.newInstance(); + + return Pair.of(newBuilderMethod, builderModel); + } catch (Exception e) { + throw new RuntimeException( + "Unable to instantiate the model builder: " + specificAvroBuilderClass.getName(), e); + } + } + + private static Method findBuilderMethod(Class specificAvroClass) { + return Arrays.stream(specificAvroClass.getDeclaredMethods()) + .filter(method -> method.getName().equals("newBuilder")) + .filter(method -> method.getParameterTypes().length == 1) + .filter( + method -> + SpecificRecordBuilderBase.class.isAssignableFrom(method.getParameterTypes()[0])) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + } +} diff --git a/data-model/src/main/java/org/hypertrace/core/datamodel/shared/SpanAttributeUtils.java b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/SpanAttributeUtils.java index 0579468..bff8b36 100644 --- a/data-model/src/main/java/org/hypertrace/core/datamodel/shared/SpanAttributeUtils.java +++ b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/SpanAttributeUtils.java @@ -1,5 +1,7 @@ package org.hypertrace.core.datamodel.shared; +import static org.hypertrace.core.datamodel.shared.AvroBuilderCache.fastNewBuilder; + import java.nio.ByteBuffer; import java.util.List; import java.util.Map; @@ -34,7 +36,7 @@ public static AttributeValue getAttributeValueWithDefault( Event event, String attributeKey, String defaultValue) { AttributeValue attributeValue = getAttributeValue(event, attributeKey); return attributeValue == null - ? AttributeValue.newBuilder().setValue(defaultValue).build() + ? fastNewBuilder(AttributeValue.Builder.class).setValue(defaultValue).build() : attributeValue; } diff --git a/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/AttributeValueCreator.java b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/AttributeValueCreator.java index 9937ded..90004b7 100644 --- a/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/AttributeValueCreator.java +++ b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/AttributeValueCreator.java @@ -1,5 +1,7 @@ package org.hypertrace.core.datamodel.shared.trace; +import static org.hypertrace.core.datamodel.shared.AvroBuilderCache.fastNewBuilder; + import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -10,19 +12,19 @@ public class AttributeValueCreator { public static AttributeValue create(String value) { - return AttributeValue.newBuilder().setValue(value).build(); + return fastNewBuilder(AttributeValue.Builder.class).setValue(value).build(); } public static AttributeValue create(boolean value) { - return AttributeValue.newBuilder().setValue(String.valueOf(value)).build(); + return fastNewBuilder(AttributeValue.Builder.class).setValue(String.valueOf(value)).build(); } public static AttributeValue create(int value) { - return AttributeValue.newBuilder().setValue(String.valueOf(value)).build(); + return fastNewBuilder(AttributeValue.Builder.class).setValue(String.valueOf(value)).build(); } public static AttributeValue create(List values) { - return AttributeValue.newBuilder().setValueList(values).build(); + return fastNewBuilder(AttributeValue.Builder.class).setValueList(values).build(); } public static AttributeValue createFromByteBuffers(Set values) { @@ -31,6 +33,6 @@ public static AttributeValue createFromByteBuffers(Set values) { value -> { list.add(new String(HexUtils.getBytes(value))); }); - return AttributeValue.newBuilder().setValueList(list).build(); + return fastNewBuilder(AttributeValue.Builder.class).setValueList(list).build(); } } diff --git a/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/MetricValueCreator.java b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/MetricValueCreator.java index 4b4c6f8..6751dd2 100644 --- a/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/MetricValueCreator.java +++ b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/MetricValueCreator.java @@ -1,18 +1,20 @@ package org.hypertrace.core.datamodel.shared.trace; +import static org.hypertrace.core.datamodel.shared.AvroBuilderCache.fastNewBuilder; + import org.hypertrace.core.datamodel.MetricValue; public class MetricValueCreator { public static MetricValue create(double value) { - return MetricValue.newBuilder().setValue(value).build(); + return fastNewBuilder(MetricValue.Builder.class).setValue(value).build(); } public static MetricValue create(long value) { - return MetricValue.newBuilder().setValue((double) value).build(); + return fastNewBuilder(MetricValue.Builder.class).setValue((double) value).build(); } public static MetricValue create(int value) { - return MetricValue.newBuilder().setValue((double) value).build(); + return fastNewBuilder(MetricValue.Builder.class).setValue((double) value).build(); } } diff --git a/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/StructuredTraceBuilder.java b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/StructuredTraceBuilder.java index 042fb4c..252184b 100644 --- a/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/StructuredTraceBuilder.java +++ b/data-model/src/main/java/org/hypertrace/core/datamodel/shared/trace/StructuredTraceBuilder.java @@ -1,6 +1,7 @@ package org.hypertrace.core.datamodel.shared.trace; import static java.util.Objects.nonNull; +import static org.hypertrace.core.datamodel.shared.AvroBuilderCache.fastNewBuilder; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -182,7 +183,7 @@ public StructuredTrace buildStructuredTrace() { private StructuredTrace build() { // start building the Structured Trace Proto object - Builder builder = StructuredTrace.newBuilder(); + Builder builder = fastNewBuilder(StructuredTrace.Builder.class); builder.setCustomerId(customerId); builder.setTraceId(traceId); builder.setEventList(new ArrayList<>()); @@ -255,7 +256,7 @@ private StructuredTrace build() { } private Edge buildEntityEdge(String parentEntityId, String childEntityId) { - Edge.Builder edgeBuilder = Edge.newBuilder(); + Edge.Builder edgeBuilder = fastNewBuilder(Edge.Builder.class); edgeBuilder.setEdgeType(EdgeType.ENTITY_ENTITY); edgeBuilder.setSrcIndex(entityIdMapping.get(parentEntityId)); edgeBuilder.setTgtIndex(entityIdMapping.get(childEntityId)); @@ -276,17 +277,19 @@ private Edge buildEntityEdge(String parentEntityId, String childEntityId) { } private Edge buildEntityEventEdge(String entityId, ByteBuffer eventId) { - Edge.Builder edgeBuilder = Edge.newBuilder(); + Edge.Builder edgeBuilder = fastNewBuilder(Edge.Builder.class); edgeBuilder.setEdgeType(EdgeType.ENTITY_EVENT); edgeBuilder.setSrcIndex(entityIdMapping.get(entityId)); edgeBuilder.setTgtIndex(eventIdMapping.get(eventId)); - edgeBuilder.setAttributes(Attributes.newBuilder().setAttributeMap(new HashMap<>()).build()); - edgeBuilder.setMetrics(Metrics.newBuilder().setMetricMap(new HashMap<>()).build()); + edgeBuilder.setAttributes( + fastNewBuilder(Attributes.Builder.class).setAttributeMap(new HashMap<>()).build()); + edgeBuilder.setMetrics( + fastNewBuilder(Metrics.Builder.class).setMetricMap(new HashMap<>()).build()); return edgeBuilder.build(); } private Edge buildEventEdge(ByteBuffer parentEventId, ByteBuffer childEventId) { - Edge.Builder edgeBuilder = Edge.newBuilder(); + Edge.Builder edgeBuilder = fastNewBuilder(Edge.Builder.class); edgeBuilder.setSrcIndex(eventIdMapping.get(parentEventId)); edgeBuilder.setTgtIndex(eventIdMapping.get(childEventId)); edgeBuilder.setEdgeType(EdgeType.EVENT_EVENT); diff --git a/data-model/src/test/java/org/hypertrace/core/datamodel/shared/AvroBuilderCacheTest.java b/data-model/src/test/java/org/hypertrace/core/datamodel/shared/AvroBuilderCacheTest.java new file mode 100644 index 0000000..c7b156b --- /dev/null +++ b/data-model/src/test/java/org/hypertrace/core/datamodel/shared/AvroBuilderCacheTest.java @@ -0,0 +1,16 @@ +package org.hypertrace.core.datamodel.shared; + +import org.hypertrace.core.datamodel.Metrics; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class AvroBuilderCacheTest { + + @Test + public void testGetBuilderFromCache() { + Metrics.Builder builder1 = AvroBuilderCache.fastNewBuilder(Metrics.Builder.class); + Metrics.Builder builder2 = AvroBuilderCache.fastNewBuilder(Metrics.Builder.class); + Assertions.assertNotSame( + builder1, builder2, "Builders can't be same. Every builder has to be a new instance"); + } +}