Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.common.ValueType;
import io.opentelemetry.api.internal.ImmutableKeyValuePairs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

Expand Down Expand Up @@ -51,9 +55,121 @@ public ExtendedAttributesBuilder toBuilder() {
@Override
@Nullable
public <T> T get(ExtendedAttributeKey<T> key) {
if (key == null) {
return null;
}
if (key.getType() == ExtendedAttributeType.VALUE) {
return (T) getAsValue(key.getKey());
}
// Check if we're looking for an array type but have a VALUE with empty array
if (isArrayType(key.getType())) {
T value = (T) super.get(key);
if (value == null) {
// Check if there's a VALUE with the same key that contains an empty array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this bit. Is there special handling of empty somewhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for

   * <p>Further, if {@code put(ExtendedAttributeKey.valueKey("key"), Value.of(emptyList()))} was
   * called, then
   *
   * <ul>
   *   <li>{@code get(ExtendedAttributeKey.stringArrayKey("key"))}
   *   <li>{@code get(ExtendedAttributeKey.longArrayKey("key"))}
   *   <li>{@code get(ExtendedAttributeKey.booleanArrayKey("key"))}
   *   <li>{@code get(ExtendedAttributeKey.doubleArrayKey("key"))}
   * </ul>
   *
   * <p>all return an empty list (as opposed to {@code null}).

tested here:

  @Test
  void emptyValueArrayRetrievedAsAnyArrayType() {
    ExtendedAttributes attributes =
        ExtendedAttributes.builder()
            .put(valueKey("key"), Value.of(Collections.emptyList()))
            .build();
    assertThat(attributes.get(stringArrayKey("key"))).isEmpty();
    assertThat(attributes.get(longArrayKey("key"))).isEmpty();
    assertThat(attributes.get(doubleArrayKey("key"))).isEmpty();
    assertThat(attributes.get(booleanArrayKey("key"))).isEmpty();
  }

Value<?> valueAttr = getValueAttribute(key.getKey());
if (valueAttr != null && isEmptyArray(valueAttr)) {
return (T) Collections.emptyList();
}
}
return value;
}
return (T) super.get(key);
}

private static boolean isArrayType(ExtendedAttributeType type) {
return type == ExtendedAttributeType.STRING_ARRAY
|| type == ExtendedAttributeType.LONG_ARRAY
|| type == ExtendedAttributeType.DOUBLE_ARRAY
|| type == ExtendedAttributeType.BOOLEAN_ARRAY;
}

@Nullable
private Value<?> getValueAttribute(String keyName) {
List<Object> data = data();
for (int i = 0; i < data.size(); i += 2) {
ExtendedAttributeKey<?> currentKey = (ExtendedAttributeKey<?>) data.get(i);
if (currentKey.getKey().equals(keyName)
&& currentKey.getType() == ExtendedAttributeType.VALUE) {
return (Value<?>) data.get(i + 1);
}
}
return null;
}

private static boolean isEmptyArray(Value<?> value) {
if (value.getType() != ValueType.ARRAY) {
return false;
}
@SuppressWarnings("unchecked")
List<Value<?>> arrayValues = (List<Value<?>>) value.getValue();
return arrayValues.isEmpty();
}

@Nullable
private Value<?> getAsValue(String keyName) {
// Find any attribute with the same key name and convert it to Value
List<Object> data = data();
for (int i = 0; i < data.size(); i += 2) {
ExtendedAttributeKey<?> currentKey = (ExtendedAttributeKey<?>) data.get(i);
if (currentKey.getKey().equals(keyName)) {
Object value = data.get(i + 1);
return asValue(currentKey.getType(), value);
}
}
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this implementation benefit from #7076?

}

@SuppressWarnings("unchecked")
@Nullable
private static Value<?> asValue(ExtendedAttributeType type, Object value) {
switch (type) {
case STRING:
return Value.of((String) value);
case LONG:
return Value.of((Long) value);
case DOUBLE:
return Value.of((Double) value);
case BOOLEAN:
return Value.of((Boolean) value);
case STRING_ARRAY:
List<String> stringList = (List<String>) value;
Value<?>[] stringValues = new Value<?>[stringList.size()];
for (int i = 0; i < stringList.size(); i++) {
stringValues[i] = Value.of(stringList.get(i));
}
return Value.of(stringValues);
case LONG_ARRAY:
List<Long> longList = (List<Long>) value;
Value<?>[] longValues = new Value<?>[longList.size()];
for (int i = 0; i < longList.size(); i++) {
longValues[i] = Value.of(longList.get(i));
}
return Value.of(longValues);
case DOUBLE_ARRAY:
List<Double> doubleList = (List<Double>) value;
Value<?>[] doubleValues = new Value<?>[doubleList.size()];
for (int i = 0; i < doubleList.size(); i++) {
doubleValues[i] = Value.of(doubleList.get(i));
}
return Value.of(doubleValues);
case BOOLEAN_ARRAY:
List<Boolean> booleanList = (List<Boolean>) value;
Value<?>[] booleanValues = new Value<?>[booleanList.size()];
for (int i = 0; i < booleanList.size(); i++) {
booleanValues[i] = Value.of(booleanList.get(i));
}
return Value.of(booleanValues);
case VALUE:
// Already a Value
return (Value<?>) value;
case EXTENDED_ATTRIBUTES:
// Cannot convert EXTENDED_ATTRIBUTES to Value
return null;
}
// Should not reach here
return null;
}

@SuppressWarnings("unchecked")
@Override
public Attributes asAttributes() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@

package io.opentelemetry.api.incubator.common;

import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.booleanArrayKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.booleanKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.doubleArrayKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.doubleKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.longArrayKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.longKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.stringArrayKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.stringKey;

import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.common.ValueType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -36,11 +47,118 @@ public <T> ExtendedAttributesBuilder put(ExtendedAttributeKey<T> key, T value) {
if (key == null || key.getKey().isEmpty() || value == null) {
return this;
}
if (key.getType() == ExtendedAttributeType.VALUE && value instanceof Value) {
putValue(key, (Value<?>) value);
return this;
}
data.add(key);
data.add(value);
return this;
}

@SuppressWarnings("unchecked")
private void putValue(ExtendedAttributeKey<?> key, Value<?> valueObj) {
// Convert VALUE type to narrower type when possible
String keyName = key.getKey();
switch (valueObj.getType()) {
case STRING:
put(stringKey(keyName), ((Value<String>) valueObj).getValue());
return;
case LONG:
put(longKey(keyName), ((Value<Long>) valueObj).getValue());
return;
case DOUBLE:
put(doubleKey(keyName), ((Value<Double>) valueObj).getValue());
return;
case BOOLEAN:
put(booleanKey(keyName), ((Value<Boolean>) valueObj).getValue());
return;
case ARRAY:
List<Value<?>> arrayValues = (List<Value<?>>) valueObj.getValue();
ExtendedAttributeType attributeType = attributeType(arrayValues);
switch (attributeType) {
case STRING_ARRAY:
List<String> strings = new ArrayList<>(arrayValues.size());
for (Value<?> v : arrayValues) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're interested in premature performance optimization, there's probably a way to to merge this with attributeType function to iterate only once.

I'd say only pursue if it ends up making the code more concise with the perf improvement.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would require optimistically creating the array as we loop through and potentially throwing it away?

strings.add((String) v.getValue());
}
put(stringArrayKey(keyName), strings);
return;
case LONG_ARRAY:
List<Long> longs = new ArrayList<>(arrayValues.size());
for (Value<?> v : arrayValues) {
longs.add((Long) v.getValue());
}
put(longArrayKey(keyName), longs);
return;
case DOUBLE_ARRAY:
List<Double> doubles = new ArrayList<>(arrayValues.size());
for (Value<?> v : arrayValues) {
doubles.add((Double) v.getValue());
}
put(doubleArrayKey(keyName), doubles);
return;
case BOOLEAN_ARRAY:
List<Boolean> booleans = new ArrayList<>(arrayValues.size());
for (Value<?> v : arrayValues) {
booleans.add((Boolean) v.getValue());
}
put(booleanArrayKey(keyName), booleans);
return;
case VALUE:
// Not coercible (empty, non-homogeneous, or unsupported element type)
data.add(key);
data.add(valueObj);
return;
case EXTENDED_ATTRIBUTES:
// Not coercible
data.add(key);
data.add(valueObj);
return;
default:
throw new IllegalArgumentException("Unexpected array attribute type: " + attributeType);
}
case KEY_VALUE_LIST:
case BYTES:
// Keep as VALUE type
data.add(key);
data.add(valueObj);
return;
}
}

/**
* Returns the ExtendedAttributeType for a homogeneous array (STRING_ARRAY, LONG_ARRAY,
* DOUBLE_ARRAY, or BOOLEAN_ARRAY), or VALUE if the array is empty, non-homogeneous, or contains
* unsupported element types.
*/
private static ExtendedAttributeType attributeType(List<Value<?>> arrayValues) {
if (arrayValues.isEmpty()) {
return ExtendedAttributeType.VALUE;
}
ValueType elementType = arrayValues.get(0).getType();
for (Value<?> v : arrayValues) {
if (v.getType() != elementType) {
return ExtendedAttributeType.VALUE;
}
}
switch (elementType) {
case STRING:
return ExtendedAttributeType.STRING_ARRAY;
case LONG:
return ExtendedAttributeType.LONG_ARRAY;
case DOUBLE:
return ExtendedAttributeType.DOUBLE_ARRAY;
case BOOLEAN:
return ExtendedAttributeType.BOOLEAN_ARRAY;
case ARRAY:
case KEY_VALUE_LIST:
case BYTES:
return ExtendedAttributeType.VALUE;
}
throw new IllegalArgumentException("Unsupported element type: " + elementType);
}

@Override
public ExtendedAttributesBuilder removeIf(Predicate<ExtendedAttributeKey<?>> predicate) {
if (predicate == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package io.opentelemetry.api.incubator.common;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.incubator.internal.InternalExtendedAttributeKeyImpl;
import java.util.List;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -93,8 +94,31 @@ static ExtendedAttributeKey<List<Double>> doubleArrayKey(String key) {
return fromAttributeKey(AttributeKey.doubleArrayKey(key));
}

/** Returns a new ExtendedAttributeKey for Map valued attributes. */
/**
* Returns a new ExtendedAttributeKey for {@link ExtendedAttributes} valued attributes.
*
* @deprecated Use {@link #valueKey(String)} in combination with {@link Value#of(java.util.Map)}
* instead.
*/
@Deprecated
@SuppressWarnings("deprecation")
static ExtendedAttributeKey<ExtendedAttributes> extendedAttributesKey(String key) {
return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.EXTENDED_ATTRIBUTES);
}

/**
* Returns a new ExtendedAttributeKey for {@link Value} valued attributes.
*
* <p>Simple attributes ({@link ExtendedAttributeType#STRING}, {@link ExtendedAttributeType#LONG},
* {@link ExtendedAttributeType#DOUBLE}, {@link ExtendedAttributeType#BOOLEAN}, {@link
* ExtendedAttributeType#STRING_ARRAY}, {@link ExtendedAttributeType#LONG_ARRAY}, {@link
* ExtendedAttributeType#DOUBLE_ARRAY}, {@link ExtendedAttributeType#BOOLEAN_ARRAY}) SHOULD be
* used whenever possible. Instrumentations SHOULD assume that backends do not index individual
* properties of complex attributes, that querying or aggregating on such properties is
* inefficient and complicated, and that reporting complex attributes carries higher performance
* overhead.
*/
static ExtendedAttributeKey<Value<?>> valueKey(String key) {
return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.VALUE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,13 @@ public enum ExtendedAttributeType {
LONG_ARRAY,
DOUBLE_ARRAY,
// Extended types unique to ExtendedAttributes
EXTENDED_ATTRIBUTES;
/**
* Complex attribute type for {@link io.opentelemetry.api.common.Value}-based maps.
*
* @deprecated Use {@link #VALUE} with {@link io.opentelemetry.api.common.Value}-based maps
* instead.
*/
@Deprecated
EXTENDED_ATTRIBUTES,
VALUE;
}
Loading
Loading