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
@@ -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 {
Comment thread
jsuereth marked this conversation as resolved.
@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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.autoconfigure.spi.internal;

/**
* Constants for experimental entity SDK features.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public final class EntityExperimentConstants {

/** The configuration key for enabling experimental entity support in resource detectors. */
public static final String EXPERIMENTAL_ENTITIES_ENABLED = "otel.experimental.entities.enabled";

private EntityExperimentConstants() {}
}
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"))
Comment thread
jsuereth marked this conversation as resolved.
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
Loading
Loading