Add remove method to AttributesBuilder (#3771)

* Add remove method to AttributesBuilder

* Improve test coverage

* Remove attributes by nulling out instead of removing from list

* feedback wip

* Add AttributesBuilder#remove(String) method

* Add removeIf() method

* AbstractFixedSizeExemplarReservoir uses more efficient removeIf() attribute filter
This commit is contained in:
jack-berg 2021-10-27 18:47:50 -05:00 committed by GitHub
parent 1e073fcff2
commit 5f259ee4ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 176 additions and 13 deletions

View File

@ -2,6 +2,11 @@
## Version 1.8.0 (unreleased):
### API
- New `AttributesBuilder#remove(String)` and `AttributeBuilder#removeIf(Predicate<AttributeKey<?>>)`
methods improve ergonomics of modifying attributes.
### Auto-configuration (alpha)
- BREAKING CHANGE: Remove deprecated `otel.experimental.exporter.otlp.protocol`,

View File

@ -8,6 +8,7 @@ package io.opentelemetry.api.common;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
class ArrayBackedAttributesBuilder implements AttributesBuilder {
private final List<Object> data;
@ -22,7 +23,9 @@ class ArrayBackedAttributesBuilder implements AttributesBuilder {
@Override
public Attributes build() {
if (data.size() == 2) {
// If only one key-value pair AND the entry hasn't been set to null (by #remove(AttributeKey<T>)
// or #removeIf(Predicate<AttributeKey<?>>)), then we can bypass sorting and filtering
if (data.size() == 2 && data.get(0) != null) {
return new ArrayBackedAttributes(data.toArray());
}
return ArrayBackedAttributes.sortAndFilterToAttributes(data.toArray());
@ -55,6 +58,32 @@ class ArrayBackedAttributesBuilder implements AttributesBuilder {
return this;
}
@Override
public <T> AttributesBuilder remove(AttributeKey<T> key) {
if (key == null || key.getKey().isEmpty()) {
return this;
}
return removeIf(
entryKey ->
key.getKey().equals(entryKey.getKey()) && key.getType().equals(entryKey.getType()));
}
@Override
public AttributesBuilder removeIf(Predicate<AttributeKey<?>> predicate) {
if (predicate == null) {
return this;
}
for (int i = 0; i < data.size() - 1; i += 2) {
Object entry = data.get(i);
if (entry instanceof AttributeKey && predicate.test((AttributeKey<?>) entry)) {
// null items are filtered out in ArrayBackedAttributes
data.set(i, null);
data.set(i + 1, null);
}
}
return this;
}
static List<Double> toList(double... values) {
Double[] boxed = new Double[values.length];
for (int i = 0; i < values.length; i++) {

View File

@ -16,6 +16,7 @@ import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import java.util.Arrays;
import java.util.function.Predicate;
/** A builder of {@link Attributes} supporting an arbitrary number of key-value pairs. */
public interface AttributesBuilder {
@ -150,4 +151,26 @@ public interface AttributesBuilder {
* @return this Builder
*/
AttributesBuilder putAll(Attributes attributes);
/**
* Remove all attributes where {@link AttributeKey#getKey()} and {@link AttributeKey#getType()}
* match the {@code key}.
*
* @return this Builder
*/
default <T> AttributesBuilder remove(AttributeKey<T> key) {
// default implementation is no-op
return this;
}
/**
* Remove all attributes that satisfy the given predicate. Errors or runtime exceptions thrown by
* the predicate are relayed to the caller.
*
* @return this Builder
*/
default AttributesBuilder removeIf(Predicate<AttributeKey<?>> filter) {
// default implementation is no-op
return this;
}
}

View File

@ -15,6 +15,7 @@ import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
@ -420,4 +421,115 @@ class AttributesTest {
assertThat(attributes.get(stringKey("animal"))).isEqualTo("cat");
assertThat(attributes.get(longKey("animal"))).isNull();
}
@Test
void remove() {
AttributesBuilder builder = Attributes.builder();
assertThat(builder.remove(stringKey(""))).isEqualTo(builder);
Attributes attributes = Attributes.builder().remove(stringKey("key1")).build();
assertThat(attributes).isEqualTo(Attributes.builder().build());
attributes =
Attributes.builder().put("key1", "value1").build().toBuilder()
.remove(stringKey("key1"))
.remove(stringKey("key1"))
.build();
assertThat(attributes).isEqualTo(Attributes.builder().build());
attributes =
Attributes.builder()
.put("key1", "value1")
.put("key1", "value2")
.put("key2", "value2")
.put("key3", "value3")
.remove(stringKey("key1"))
.build();
assertThat(attributes)
.isEqualTo(Attributes.builder().put("key2", "value2").put("key3", "value3").build());
attributes =
Attributes.builder()
.put("key1", "value1")
.put("key1", true)
.remove(stringKey("key1"))
.remove(stringKey("key1"))
.build();
assertThat(attributes).isEqualTo(Attributes.builder().put("key1", true).build());
}
@Test
void removeIf() {
AttributesBuilder builder = Attributes.builder();
assertThat(builder.removeIf(unused -> true)).isEqualTo(builder);
Attributes attributes =
Attributes.builder().removeIf(key -> key.getKey().equals("key1")).build();
assertThat(attributes).isEqualTo(Attributes.builder().build());
attributes =
Attributes.builder().put("key1", "value1").build().toBuilder()
.removeIf(key -> key.getKey().equals("key1"))
.removeIf(key -> key.getKey().equals("key1"))
.build();
assertThat(attributes).isEqualTo(Attributes.builder().build());
attributes =
Attributes.builder()
.put("key1", "value1")
.put("key1", "value2")
.put("key2", "value2")
.put("key3", "value3")
.removeIf(key -> key.getKey().equals("key1"))
.build();
assertThat(attributes)
.isEqualTo(Attributes.builder().put("key2", "value2").put("key3", "value3").build());
attributes =
Attributes.builder()
.put("key1", "value1A")
.put("key1", true)
.removeIf(
key -> key.getKey().equals("key1") && key.getType().equals(AttributeType.STRING))
.build();
assertThat(attributes).isEqualTo(Attributes.builder().put("key1", true).build());
attributes =
Attributes.builder()
.put("key1", "value1")
.put("key2", "value2")
.put("foo", "bar")
.removeIf(key -> key.getKey().matches("key.*"))
.build();
assertThat(attributes).isEqualTo(Attributes.builder().put("foo", "bar").build());
}
@Test
void remove_defaultImplementationDoesNotThrow() {
AttributesBuilder myAttributesBuilder =
new AttributesBuilder() {
@Override
public Attributes build() {
return null;
}
@Override
public <T> AttributesBuilder put(AttributeKey<Long> key, int value) {
return null;
}
@Override
public <T> AttributesBuilder put(AttributeKey<T> key, T value) {
return null;
}
@Override
public AttributesBuilder putAll(Attributes attributes) {
return null;
}
};
assertThatCode(() -> myAttributesBuilder.remove(stringKey("foo"))).doesNotThrowAnyException();
assertThatCode(() -> myAttributesBuilder.removeIf(unused -> false)).doesNotThrowAnyException();
}
}

View File

@ -1,2 +1,5 @@
Comparing source compatibility of against
No changes.
***! MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.common.AttributesBuilder (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+++! NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.AttributesBuilder remove(io.opentelemetry.api.common.AttributeKey)
+++! NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.AttributesBuilder removeIf(java.util.function.Predicate)

View File

@ -7,7 +7,6 @@ package io.opentelemetry.sdk.metrics.exemplar;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.Clock;
@ -134,19 +133,11 @@ abstract class AbstractFixedSizeExemplarReservoir implements ExemplarReservoir {
}
/** Returns filtered attributes for exemplars. */
@SuppressWarnings("unchecked")
private static Attributes filtered(Attributes original, Attributes metricPoint) {
if (metricPoint.isEmpty()) {
return original;
}
AttributesBuilder result = Attributes.builder();
Set<AttributeKey<?>> keys = metricPoint.asMap().keySet();
original.forEach(
(k, v) -> {
if (!keys.contains(k)) {
result.<Object>put((AttributeKey<Object>) k, v);
}
});
return result.build();
Set<AttributeKey<?>> metricPointKeys = metricPoint.asMap().keySet();
return original.toBuilder().removeIf(metricPointKeys::contains).build();
}
}