-
Notifications
You must be signed in to change notification settings - Fork 988
Entity SDK - Initial opt-in SDK features #8464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7f67a89
f611175
2ccb300
509a7b9
b4953f4
bdcc5d7
279b1ea
da0b3fb
25e35e6
6d78602
d200481
c6fc930
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.exporter.internal.otlp; | ||
|
|
||
| import io.opentelemetry.api.internal.StringUtils; | ||
| import io.opentelemetry.exporter.internal.marshal.MarshalerUtil; | ||
| import io.opentelemetry.exporter.internal.marshal.MarshalerWithSize; | ||
| import io.opentelemetry.exporter.internal.marshal.Serializer; | ||
| import io.opentelemetry.proto.common.v1.internal.EntityRef; | ||
| import io.opentelemetry.sdk.resources.internal.Entity; | ||
| import java.io.IOException; | ||
| import java.nio.charset.StandardCharsets; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /** | ||
| * A Marshaler of {@link io.opentelemetry.sdk.resources.internal.Entity}. | ||
| * | ||
| * <p>This class is internal and is hence not for public use. Its APIs are unstable and can change | ||
| * at any time. | ||
| */ | ||
| final class EntityRefMarshaler extends MarshalerWithSize { | ||
| @Nullable private final byte[] schemaUrlUtf8; | ||
| private final byte[] typeUtf8; | ||
| private final byte[][] idKeysUtf8; | ||
| private final byte[][] descriptionKeysUtf8; | ||
|
|
||
| @Override | ||
| protected void writeTo(Serializer output) throws IOException { | ||
| if (schemaUrlUtf8 != null) { | ||
| output.writeString(EntityRef.SCHEMA_URL, schemaUrlUtf8); | ||
| } | ||
| output.writeString(EntityRef.TYPE, typeUtf8); | ||
| output.writeRepeatedString(EntityRef.ID_KEYS, idKeysUtf8); | ||
| output.writeRepeatedString(EntityRef.DESCRIPTION_KEYS, descriptionKeysUtf8); | ||
| } | ||
|
|
||
| /** Consttructs an entity reference marshaler from a full entity. */ | ||
| static EntityRefMarshaler createForEntity(Entity e) { | ||
| byte[] schemaUrlUtf8 = null; | ||
| if (!StringUtils.isNullOrEmpty(e.getSchemaUrl())) { | ||
| schemaUrlUtf8 = e.getSchemaUrl().getBytes(StandardCharsets.UTF_8); | ||
| } | ||
| return new EntityRefMarshaler( | ||
| schemaUrlUtf8, | ||
| e.getType().getBytes(StandardCharsets.UTF_8), | ||
| e.getId().asMap().keySet().stream() | ||
| .map(key -> key.getKey().getBytes(StandardCharsets.UTF_8)) | ||
| .toArray(byte[][]::new), | ||
| e.getDescription().asMap().keySet().stream() | ||
| .map(key -> key.getKey().getBytes(StandardCharsets.UTF_8)) | ||
| .toArray(byte[][]::new)); | ||
| } | ||
|
|
||
| private EntityRefMarshaler( | ||
| @Nullable byte[] schemaUrlUtf8, | ||
| byte[] typeUtf8, | ||
| byte[][] idKeysUtf8, | ||
| byte[][] descriptionKeysUtf8) { | ||
| super(calculateSize(schemaUrlUtf8, typeUtf8, idKeysUtf8, descriptionKeysUtf8)); | ||
| this.schemaUrlUtf8 = schemaUrlUtf8; | ||
| this.typeUtf8 = typeUtf8; | ||
| this.idKeysUtf8 = idKeysUtf8; | ||
| this.descriptionKeysUtf8 = descriptionKeysUtf8; | ||
| } | ||
|
|
||
| private static int calculateSize( | ||
| @Nullable byte[] schemaUrlUtf8, | ||
| byte[] typeUtf8, | ||
| byte[][] idKeysUtf8, | ||
| byte[][] descriptionKeysUtf8) { | ||
| int size = 0; | ||
| if (schemaUrlUtf8 != null) { | ||
| size += MarshalerUtil.sizeBytes(EntityRef.SCHEMA_URL, schemaUrlUtf8); | ||
| } | ||
| size += MarshalerUtil.sizeBytes(EntityRef.TYPE, typeUtf8); | ||
| size += MarshalerUtil.sizeRepeatedString(EntityRef.ID_KEYS, idKeysUtf8); | ||
| size += MarshalerUtil.sizeRepeatedString(EntityRef.DESCRIPTION_KEYS, descriptionKeysUtf8); | ||
| return size; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.exporter.internal.otlp; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| import com.google.protobuf.InvalidProtocolBufferException; | ||
| import com.google.protobuf.Message; | ||
| import com.google.protobuf.util.JsonFormat; | ||
| import io.opentelemetry.api.common.Attributes; | ||
| import io.opentelemetry.exporter.internal.marshal.Marshaler; | ||
| import io.opentelemetry.proto.common.v1.EntityRef; | ||
| import io.opentelemetry.sdk.resources.internal.Entity; | ||
| import java.io.ByteArrayOutputStream; | ||
| import java.io.IOException; | ||
| import java.io.UncheckedIOException; | ||
| import java.nio.charset.StandardCharsets; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class EntityRefMarshalerTest { | ||
| @Test | ||
| void toEntityRefs() { | ||
| Entity e = | ||
| Entity.builder("test") | ||
| .setSchemaUrl("test-url") | ||
| .setDescription(Attributes.builder().put("desc.key", "desc.value").build()) | ||
| .setId(Attributes.builder().put("id.key", "id.value").build()) | ||
| .build(); | ||
| EntityRef proto = parse(EntityRef.getDefaultInstance(), EntityRefMarshaler.createForEntity(e)); | ||
| assertThat(proto.getType()).isEqualTo("test"); | ||
| assertThat(proto.getSchemaUrl()).isEqualTo("test-url"); | ||
| assertThat(proto.getIdKeysList()).containsExactly("id.key"); | ||
| assertThat(proto.getDescriptionKeysList()).containsExactly("desc.key"); | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| private static <T extends Message> T parse(T prototype, Marshaler marshaler) { | ||
| byte[] serialized = toByteArray(marshaler); | ||
| T result; | ||
| try { | ||
| result = (T) prototype.newBuilderForType().mergeFrom(serialized).build(); | ||
| } catch (InvalidProtocolBufferException e) { | ||
| throw new UncheckedIOException(e); | ||
| } | ||
| // Our marshaler should produce the exact same length of serialized output (for example, field | ||
| // default values are not outputted), so we check that here. The output itself may have slightly | ||
| // different ordering, mostly due to the way we don't output oneof values in field order all the | ||
| // tieme. If the lengths are equal and the resulting protos are equal, the marshaling is | ||
| // guaranteed to be valid. | ||
| assertThat(result.getSerializedSize()).isEqualTo(serialized.length); | ||
|
|
||
| // Compare JSON | ||
| String json = toJson(marshaler); | ||
| Message.Builder builder = prototype.newBuilderForType(); | ||
| try { | ||
| JsonFormat.parser().merge(json, builder); | ||
| } catch (InvalidProtocolBufferException e) { | ||
| throw new UncheckedIOException(e); | ||
| } | ||
| assertThat(builder.build()).isEqualTo(result); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private static byte[] toByteArray(Marshaler marshaler) { | ||
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
| try { | ||
| marshaler.writeBinaryTo(bos); | ||
| } catch (IOException e) { | ||
| throw new UncheckedIOException(e); | ||
| } | ||
| return bos.toByteArray(); | ||
| } | ||
|
|
||
| private static String toJson(Marshaler marshaler) { | ||
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
| try { | ||
| marshaler.writeJsonTo(bos); | ||
| } catch (IOException e) { | ||
| throw new UncheckedIOException(e); | ||
| } | ||
| return new String(bos.toByteArray(), StandardCharsets.UTF_8); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.exporter.internal.otlp; | ||
|
|
||
| import static io.opentelemetry.api.common.AttributeKey.stringKey; | ||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| import com.google.protobuf.InvalidProtocolBufferException; | ||
| import com.google.protobuf.Message; | ||
| import com.google.protobuf.util.JsonFormat; | ||
| import io.opentelemetry.api.common.Attributes; | ||
| import io.opentelemetry.exporter.internal.marshal.Marshaler; | ||
| import io.opentelemetry.proto.resource.v1.Resource; | ||
| import io.opentelemetry.sdk.resources.ResourceBuilder; | ||
| import io.opentelemetry.sdk.resources.internal.Entity; | ||
| import io.opentelemetry.sdk.resources.internal.EntityUtil; | ||
| import java.io.ByteArrayOutputStream; | ||
| import java.io.IOException; | ||
| import java.io.UncheckedIOException; | ||
| import java.nio.charset.StandardCharsets; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class ResourceMarshalerTest { | ||
|
|
||
| @Test | ||
| void marshalResourceWithEntities() { | ||
| Entity entity = | ||
| Entity.builder("process") | ||
| .setId(Attributes.of(stringKey("process.pid"), "1234")) | ||
| .setDescription(Attributes.of(stringKey("process.executable.name"), "java")) | ||
| .setSchemaUrl("http://process.schema") | ||
| .build(); | ||
|
|
||
| ResourceBuilder builder = | ||
| io.opentelemetry.sdk.resources.Resource.builder().put("service.name", "my-service"); | ||
| EntityUtil.addEntity(builder, entity); | ||
| io.opentelemetry.sdk.resources.Resource resourceWithEntity = builder.build(); | ||
|
|
||
| Resource proto = | ||
| parse(Resource.getDefaultInstance(), ResourceMarshaler.create(resourceWithEntity)); | ||
|
|
||
| assertThat(proto.getAttributesList()).hasSize(3); | ||
| assertThat(proto.getAttributesList().stream().map(a -> a.getKey())) | ||
| .containsExactlyInAnyOrder("service.name", "process.pid", "process.executable.name"); | ||
|
|
||
| assertThat(proto.getEntityRefsList()).hasSize(1); | ||
| assertThat(proto.getEntityRefs(0).getType()).isEqualTo("process"); | ||
| assertThat(proto.getEntityRefs(0).getSchemaUrl()).isEqualTo("http://process.schema"); | ||
| assertThat(proto.getEntityRefs(0).getIdKeysList()).containsExactly("process.pid"); | ||
| assertThat(proto.getEntityRefs(0).getDescriptionKeysList()) | ||
| .containsExactly("process.executable.name"); | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| private static <T extends Message> T parse(T prototype, Marshaler marshaler) { | ||
| byte[] serialized = toByteArray(marshaler); | ||
| T result; | ||
| try { | ||
| result = (T) prototype.newBuilderForType().mergeFrom(serialized).build(); | ||
| } catch (InvalidProtocolBufferException e) { | ||
| throw new UncheckedIOException(e); | ||
| } | ||
| assertThat(result.getSerializedSize()).isEqualTo(serialized.length); | ||
|
|
||
| // Compare JSON | ||
| String json = toJson(marshaler); | ||
| Message.Builder protoBuilder = prototype.newBuilderForType(); | ||
| try { | ||
| JsonFormat.parser().merge(json, protoBuilder); | ||
| } catch (InvalidProtocolBufferException e) { | ||
| throw new UncheckedIOException(e); | ||
| } | ||
| assertThat(protoBuilder.build()).isEqualTo(result); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private static byte[] toByteArray(Marshaler marshaler) { | ||
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
| try { | ||
| marshaler.writeBinaryTo(bos); | ||
| } catch (IOException e) { | ||
| throw new UncheckedIOException(e); | ||
| } | ||
| return bos.toByteArray(); | ||
| } | ||
|
|
||
| private static String toJson(Marshaler marshaler) { | ||
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
| try { | ||
| marshaler.writeJsonTo(bos); | ||
| } catch (IOException e) { | ||
| throw new UncheckedIOException(e); | ||
| } | ||
| return new String(bos.toByteArray(), StandardCharsets.UTF_8); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ dependencies { | |
| api(project(":sdk-extensions:autoconfigure-spi")) | ||
|
|
||
| compileOnly(project(":api:incubator")) | ||
| compileOnly(project(":sdk-extensions:incubator")) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think both these changes in this file are remnant from when you still had work in the |
||
| compileOnly(project(":sdk-extensions:declarative-config")) | ||
|
|
||
| annotationProcessor("com.google.auto.value:auto-value") | ||
|
|
@@ -107,6 +108,7 @@ testing { | |
| register<JvmTestSuite>("testIncubating") { | ||
| dependencies { | ||
| implementation(project(":sdk-extensions:declarative-config")) | ||
| implementation(project(":sdk-extensions:incubator")) | ||
| implementation(project(":exporters:logging")) | ||
| implementation(project(":exporters:otlp:all")) | ||
| implementation(project(":sdk:testing")) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.sdk.autoconfigure; | ||
|
|
||
| /** Constants for experimental entity SDK features. */ | ||
| final class EntityExperimentConstants { | ||
|
|
||
| static final String EXPERIMENTAL_ENTITIES_ENABLED = "otel.experimental.entities.enabled"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just add this as a constant in EnvironmentResource - no need for a separate class.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is used by all the resource detectors - I can put it in each individually or do you think they should use different values?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean it will be used by all the resource detectors? Because right now I only see usage in one place - If its going to be used by resource detectors, should go in |
||
|
|
||
| private EntityExperimentConstants() {} | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this is wired into the ResourceMarshaler so it doesn't actually do anything. Let's either wire in, or pull until a future PR when we're wiring in. No use having unused code published.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad I think I accidentally reverted that - will fix