Have AttributesMap extend HashMap instead of contain LinkedHashMap (#2906)
* Reduce HashMp allocations for AttributeMap. * Finish * Add comment on why wrapper is actually needed.
This commit is contained in:
parent
0b973d9a3c
commit
b393a587dd
|
|
@ -79,7 +79,7 @@ public interface Baggage extends ImplicitContextKeyed {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Iterates over all the entries in this {@link Baggage}. */
|
/** Iterates over all the entries in this {@link Baggage}. */
|
||||||
void forEach(BiConsumer<String, BaggageEntry> consumer);
|
void forEach(BiConsumer<? super String, ? super BaggageEntry> consumer);
|
||||||
|
|
||||||
/** Returns a read-only view of this {@link Baggage} as a {@link Map}. */
|
/** Returns a read-only view of this {@link Baggage} as a {@link Map}. */
|
||||||
Map<String, BaggageEntry> asMap();
|
Map<String, BaggageEntry> asMap();
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public interface Attributes {
|
||||||
<T> T get(AttributeKey<T> key);
|
<T> T get(AttributeKey<T> key);
|
||||||
|
|
||||||
/** Iterates over all the key-value pairs of attributes contained by this instance. */
|
/** Iterates over all the key-value pairs of attributes contained by this instance. */
|
||||||
void forEach(BiConsumer<AttributeKey<?>, Object> consumer);
|
void forEach(BiConsumer<? super AttributeKey<?>, ? super Object> consumer);
|
||||||
|
|
||||||
/** The number of attributes contained in this. */
|
/** The number of attributes contained in this. */
|
||||||
int size();
|
int size();
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ public abstract class ImmutableKeyValuePairs<K, V> {
|
||||||
|
|
||||||
/** Iterates over all the key-value pairs of labels contained by this instance. */
|
/** Iterates over all the key-value pairs of labels contained by this instance. */
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public final void forEach(BiConsumer<K, V> consumer) {
|
public final void forEach(BiConsumer<? super K, ? super V> consumer) {
|
||||||
if (consumer == null) {
|
if (consumer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ public interface Labels {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Iterates over all the key-value pairs of labels contained by this instance. */
|
/** Iterates over all the key-value pairs of labels contained by this instance. */
|
||||||
void forEach(BiConsumer<String, String> consumer);
|
void forEach(BiConsumer<? super String, ? super String> consumer);
|
||||||
|
|
||||||
/** The number of key-value pairs of labels in this instance. */
|
/** The number of key-value pairs of labels in this instance. */
|
||||||
int size();
|
int size();
|
||||||
|
|
|
||||||
|
|
@ -127,20 +127,22 @@ subprojects {
|
||||||
withType(JavaCompile::class) {
|
withType(JavaCompile::class) {
|
||||||
options.release.set(8)
|
options.release.set(8)
|
||||||
|
|
||||||
options.compilerArgs.addAll(listOf(
|
if (name != "jmhCompileGeneratedClasses") {
|
||||||
"-Xlint:all",
|
options.compilerArgs.addAll(listOf(
|
||||||
// We suppress the "try" warning because it disallows managing an auto-closeable with
|
"-Xlint:all",
|
||||||
// try-with-resources without referencing the auto-closeable within the try block.
|
// We suppress the "try" warning because it disallows managing an auto-closeable with
|
||||||
"-Xlint:-try",
|
// try-with-resources without referencing the auto-closeable within the try block.
|
||||||
// We suppress the "processing" warning as suggested in
|
"-Xlint:-try",
|
||||||
// https://groups.google.com/forum/#!topic/bazel-discuss/_R3A9TJSoPM
|
// We suppress the "processing" warning as suggested in
|
||||||
"-Xlint:-processing",
|
// https://groups.google.com/forum/#!topic/bazel-discuss/_R3A9TJSoPM
|
||||||
// We suppress the "options" warning because it prevents compilation on modern JDKs
|
"-Xlint:-processing",
|
||||||
"-Xlint:-options",
|
// We suppress the "options" warning because it prevents compilation on modern JDKs
|
||||||
|
"-Xlint:-options",
|
||||||
|
|
||||||
// Fail build on any warning
|
// Fail build on any warning
|
||||||
"-Werror"
|
"-Werror"
|
||||||
))
|
))
|
||||||
|
}
|
||||||
|
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
|
|
||||||
|
|
@ -383,6 +385,7 @@ subprojects {
|
||||||
// Always include the jmhreport plugin and run it after jmh task.
|
// Always include the jmhreport plugin and run it after jmh task.
|
||||||
plugins.apply("io.morethan.jmhreport")
|
plugins.apply("io.morethan.jmhreport")
|
||||||
dependencies {
|
dependencies {
|
||||||
|
add("jmh", platform(project(":dependencyManagement")))
|
||||||
add("jmh", "org.openjdk.jmh:jmh-core")
|
add("jmh", "org.openjdk.jmh:jmh-core")
|
||||||
add("jmh", "org.openjdk.jmh:jmh-generator-bytecode")
|
add("jmh", "org.openjdk.jmh:jmh-generator-bytecode")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ val DEPENDENCY_SETS = listOf(
|
||||||
),
|
),
|
||||||
DependencySet(
|
DependencySet(
|
||||||
"org.openjdk.jmh",
|
"org.openjdk.jmh",
|
||||||
"1.26",
|
"1.27",
|
||||||
listOf("jmh-core", "jmh-generator-bytecode")
|
listOf("jmh-core", "jmh-generator-bytecode")
|
||||||
),
|
),
|
||||||
DependencySet(
|
DependencySet(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.sdk.trace;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.common.AttributeKey;
|
||||||
|
import io.opentelemetry.api.trace.SpanBuilder;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Threads;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
|
||||||
|
@Threads(value = 1)
|
||||||
|
@Fork(3)
|
||||||
|
@Warmup(iterations = 10, time = 1)
|
||||||
|
@Measurement(iterations = 20, time = 1)
|
||||||
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
|
public class FillSpanBenchmark {
|
||||||
|
|
||||||
|
private static final SpanBuilder spanBuilder =
|
||||||
|
SdkTracerProvider.builder().build().get("benchmark").spanBuilder("benchmark");
|
||||||
|
|
||||||
|
private static final AttributeKey<String> KEY1 = AttributeKey.stringKey("key1");
|
||||||
|
private static final AttributeKey<String> KEY2 = AttributeKey.stringKey("key2");
|
||||||
|
private static final AttributeKey<String> KEY3 = AttributeKey.stringKey("key3");
|
||||||
|
private static final AttributeKey<String> KEY4 = AttributeKey.stringKey("key4");
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void setFourAttributes() {
|
||||||
|
spanBuilder
|
||||||
|
.startSpan()
|
||||||
|
.setAttribute(KEY1, "value1")
|
||||||
|
.setAttribute(KEY2, "value2")
|
||||||
|
.setAttribute(KEY3, "value3")
|
||||||
|
.setAttribute(KEY4, "value4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,40 +9,32 @@ import io.opentelemetry.api.common.AttributeKey;
|
||||||
import io.opentelemetry.api.common.Attributes;
|
import io.opentelemetry.api.common.Attributes;
|
||||||
import io.opentelemetry.api.common.AttributesBuilder;
|
import io.opentelemetry.api.common.AttributesBuilder;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
/**
|
/** A map with a fixed capacity that drops attributes when the map gets full. */
|
||||||
* A map with a fixed capacity that drops attributes when the map gets full.
|
|
||||||
*
|
|
||||||
* <p>Note: this doesn't implement the Map interface, but behaves very similarly to one.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
final class AttributesMap implements Attributes {
|
final class AttributesMap extends HashMap<AttributeKey<?>, Object> implements Attributes {
|
||||||
private final Map<AttributeKey<?>, Object> data;
|
|
||||||
|
private static final long serialVersionUID = -5072696312123632376L;
|
||||||
|
|
||||||
private final long capacity;
|
private final long capacity;
|
||||||
private int totalAddedValues = 0;
|
private int totalAddedValues = 0;
|
||||||
|
|
||||||
private AttributesMap(long capacity, Map<AttributeKey<?>, Object> data) {
|
|
||||||
this.capacity = capacity;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
AttributesMap(long capacity) {
|
AttributesMap(long capacity) {
|
||||||
this(capacity, new LinkedHashMap<>());
|
this.capacity = capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> void put(AttributeKey<T> key, T value) {
|
<T> void put(AttributeKey<T> key, T value) {
|
||||||
if (key == null || key.getKey() == null || value == null) {
|
if (key == null || key.getKey() == null || value == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
totalAddedValues++;
|
totalAddedValues++;
|
||||||
if (data.size() >= capacity && !data.containsKey(key)) {
|
if (size() >= capacity && !containsKey(key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
data.put(key, value);
|
super.put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
int getTotalAddedValues() {
|
int getTotalAddedValues() {
|
||||||
|
|
@ -52,32 +44,27 @@ final class AttributesMap implements Attributes {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public <T> T get(AttributeKey<T> key) {
|
public <T> T get(AttributeKey<T> key) {
|
||||||
return (T) data.get(key);
|
return (T) super.get(key);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return data.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return data.isEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
@Override
|
@Override
|
||||||
public void forEach(BiConsumer<AttributeKey<?>, Object> consumer) {
|
public void forEach(BiConsumer<? super AttributeKey<?>, ? super Object> consumer) {
|
||||||
for (Map.Entry<AttributeKey<?>, Object> entry : data.entrySet()) {
|
for (Map.Entry<AttributeKey<?>, Object> entry : entrySet()) {
|
||||||
AttributeKey key = entry.getKey();
|
AttributeKey key = entry.getKey();
|
||||||
Object value = entry.getValue();
|
Object value = entry.getValue();
|
||||||
consumer.accept(key, value);
|
consumer.accept((AttributeKey<?>) key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<AttributeKey<?>, Object> asMap() {
|
public Map<AttributeKey<?>, Object> asMap() {
|
||||||
return Collections.unmodifiableMap(data);
|
// Because Attributes is marked Immutable, IDEs may recognize this as redundant usage. However,
|
||||||
|
// this class is private and is actually mutable, so we need to wrap with unmodifiableMap
|
||||||
|
// anyways. We implement the immutable Attributes for this class to support the
|
||||||
|
// Attributes.builder().putAll usage - it is tricky but an implementation detail of this private
|
||||||
|
// class.
|
||||||
|
return Collections.unmodifiableMap(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -89,7 +76,7 @@ final class AttributesMap implements Attributes {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "AttributesMap{"
|
return "AttributesMap{"
|
||||||
+ "data="
|
+ "data="
|
||||||
+ data
|
+ super.toString()
|
||||||
+ ", capacity="
|
+ ", capacity="
|
||||||
+ capacity
|
+ capacity
|
||||||
+ ", totalAddedValues="
|
+ ", totalAddedValues="
|
||||||
|
|
@ -98,7 +85,6 @@ final class AttributesMap implements Attributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
Attributes immutableCopy() {
|
Attributes immutableCopy() {
|
||||||
Map<AttributeKey<?>, Object> dataCopy = new LinkedHashMap<>(data);
|
return Attributes.builder().putAll(this).build();
|
||||||
return new AttributesMap(capacity, Collections.unmodifiableMap(dataCopy));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,29 +9,9 @@ import static io.opentelemetry.api.common.AttributeKey.longKey;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.entry;
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
|
|
||||||
import io.opentelemetry.api.common.AttributeKey;
|
|
||||||
import io.opentelemetry.api.common.Attributes;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class AttributesMapTest {
|
class AttributesMapTest {
|
||||||
@Test
|
|
||||||
void attributesAreInOrder() {
|
|
||||||
AttributesMap attributesMap = new AttributesMap(5);
|
|
||||||
attributesMap.put(longKey("one"), 1L);
|
|
||||||
attributesMap.put(longKey("three"), 3L);
|
|
||||||
attributesMap.put(longKey("two"), 2L);
|
|
||||||
attributesMap.put(longKey("four"), 4L);
|
|
||||||
attributesMap.put(longKey("five"), 5L);
|
|
||||||
|
|
||||||
List<String> expectedKeyOrder = Arrays.asList("one", "three", "two", "four", "five");
|
|
||||||
List<Long> expectedValueOrder = Arrays.asList(1L, 3L, 2L, 4L, 5L);
|
|
||||||
|
|
||||||
assertOrdering(attributesMap, expectedKeyOrder, expectedValueOrder);
|
|
||||||
assertOrdering(attributesMap.immutableCopy(), expectedKeyOrder, expectedValueOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void asMap() {
|
void asMap() {
|
||||||
|
|
@ -40,22 +20,6 @@ class AttributesMapTest {
|
||||||
attributesMap.put(longKey("two"), 2L);
|
attributesMap.put(longKey("two"), 2L);
|
||||||
|
|
||||||
assertThat(attributesMap.asMap())
|
assertThat(attributesMap.asMap())
|
||||||
.containsExactly(entry(longKey("one"), 1L), entry(longKey("two"), 2L));
|
.containsOnly(entry(longKey("one"), 1L), entry(longKey("two"), 2L));
|
||||||
}
|
|
||||||
|
|
||||||
private void assertOrdering(
|
|
||||||
Attributes attributes, List<String> expectedKeyOrder, List<Long> expectedValueOrder) {
|
|
||||||
attributes.forEach(
|
|
||||||
new BiConsumer<AttributeKey<?>, Object>() {
|
|
||||||
private int counter = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void accept(AttributeKey<?> key, Object value) {
|
|
||||||
String k = key.getKey();
|
|
||||||
Long val = (Long) value;
|
|
||||||
assertThat(val).isEqualTo(expectedValueOrder.get(counter));
|
|
||||||
assertThat(k).isEqualTo(expectedKeyOrder.get(counter++));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -875,8 +875,7 @@ class SdkSpanBuilderTest {
|
||||||
+ "kind=INTERNAL, "
|
+ "kind=INTERNAL, "
|
||||||
+ "startEpochNanos=[0-9]+, "
|
+ "startEpochNanos=[0-9]+, "
|
||||||
+ "endEpochNanos=[0-9]+, "
|
+ "endEpochNanos=[0-9]+, "
|
||||||
+ "attributes=AttributesMap\\{data=\\{http.status_code=500, "
|
+ "attributes=AttributesMap\\{data=\\{[^}]*}, capacity=128, totalAddedValues=2}, "
|
||||||
+ "http.url=https://opentelemetry.io}, capacity=128, totalAddedValues=2}, "
|
|
||||||
+ "totalAttributeCount=2, "
|
+ "totalAttributeCount=2, "
|
||||||
+ "events=\\[], "
|
+ "events=\\[], "
|
||||||
+ "totalRecordedEvents=0, "
|
+ "totalRecordedEvents=0, "
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ pluginManagement {
|
||||||
id("de.undercouch.download") version "4.1.1"
|
id("de.undercouch.download") version "4.1.1"
|
||||||
id("io.codearte.nexus-staging") version "0.22.0"
|
id("io.codearte.nexus-staging") version "0.22.0"
|
||||||
id("io.morethan.jmhreport") version "0.9.0"
|
id("io.morethan.jmhreport") version "0.9.0"
|
||||||
id("me.champeau.gradle.jmh") version "0.5.2"
|
id("me.champeau.gradle.jmh") version "0.5.3"
|
||||||
id("nebula.release") version "15.3.0"
|
id("nebula.release") version "15.3.0"
|
||||||
id("net.ltgt.errorprone") version "1.3.0"
|
id("net.ltgt.errorprone") version "1.3.0"
|
||||||
id("org.jetbrains.kotlin.jvm") version "1.4.21"
|
id("org.jetbrains.kotlin.jvm") version "1.4.21"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue