Skip to content
Open
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 {

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor Author

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

@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
Expand Up @@ -10,6 +10,7 @@
import io.opentelemetry.exporter.internal.marshal.MarshalerWithSize;
import io.opentelemetry.exporter.internal.marshal.Serializer;
import io.opentelemetry.proto.resource.v1.internal.Resource;
import io.opentelemetry.sdk.resources.internal.EntityUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
Expand Down Expand Up @@ -37,7 +38,10 @@ public static ResourceMarshaler create(io.opentelemetry.sdk.resources.Resource r

RealResourceMarshaler realMarshaler =
new RealResourceMarshaler(
KeyValueMarshaler.createForAttributes(resource.getAttributes()));
KeyValueMarshaler.createForAttributes(resource.getAttributes()),
EntityUtil.getEntities(resource).stream()
.map(EntityRefMarshaler::createForEntity)
.toArray(MarshalerWithSize[]::new));

ByteArrayOutputStream binaryBos =
new ByteArrayOutputStream(realMarshaler.getBinarySerializedSize());
Expand Down Expand Up @@ -70,19 +74,26 @@ public void writeTo(Serializer output) throws IOException {

private static final class RealResourceMarshaler extends MarshalerWithSize {
private final KeyValueMarshaler[] attributes;
private final MarshalerWithSize[] entityRefs;

private RealResourceMarshaler(KeyValueMarshaler[] attributes) {
super(calculateSize(attributes));
private RealResourceMarshaler(KeyValueMarshaler[] attributes, MarshalerWithSize[] entityRefs) {
super(calculateSize(attributes, entityRefs));
this.attributes = attributes;
this.entityRefs = entityRefs;
}

@Override
protected void writeTo(Serializer output) throws IOException {
output.serializeRepeatedMessage(Resource.ATTRIBUTES, attributes);
output.serializeRepeatedMessage(Resource.ENTITY_REFS, entityRefs);
}

private static int calculateSize(KeyValueMarshaler[] attributeMarshalers) {
return MarshalerUtil.sizeRepeatedMessage(Resource.ATTRIBUTES, attributeMarshalers);
private static int calculateSize(
KeyValueMarshaler[] attributeMarshalers, MarshalerWithSize[] entityRefs) {
int size = 0;
size += MarshalerUtil.sizeRepeatedMessage(Resource.ATTRIBUTES, attributeMarshalers);
size += MarshalerUtil.sizeRepeatedMessage(Resource.ENTITY_REFS, entityRefs);
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);
}
}
2 changes: 2 additions & 0 deletions sdk-extensions/autoconfigure/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
api(project(":sdk-extensions:autoconfigure-spi"))

compileOnly(project(":api:incubator"))
compileOnly(project(":sdk-extensions:incubator"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 sdk-extensions:incubator.

compileOnly(project(":sdk-extensions:declarative-config"))

annotationProcessor("com.google.auto.value:auto-value")
Expand Down Expand Up @@ -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"))
Expand Down
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";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 - EnvironmentResrouce.

If its going to be used by resource detectors, should go in opentelemetry-sdk-extension-autoconfigure-spi because that's the dependency detectors take when implementing ResourceProvider


private EntityExperimentConstants() {}
}
Loading
Loading