-
Notifications
You must be signed in to change notification settings - Fork 904
[DO NOT MERGE] Entity Prototype for SDK Specification #7434
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
f056298
5460d5e
ce0e66d
cb74286
3369be3
2c4aaeb
8af2cd1
da525bd
b119561
ddf096d
9c222dd
2871ff0
34b6f60
0711350
ebf9557
73ef00a
8d4288e
f22ec8d
d220005
f9dcb52
18afb60
7ace665
d5219d1
cba0606
a38a544
d01f5a3
2560c51
bf72978
af3bbd9
2809916
912ba24
634c4a3
5c126c9
7a7c426
8ad0e5f
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,17 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.api.incubator; | ||
|
||
import io.opentelemetry.api.OpenTelemetry; | ||
import io.opentelemetry.api.incubator.entities.EntityProvider; | ||
|
||
/** Extension to {@link OpenTelemetry} that adds {@link EntityProvider}. */ | ||
public interface ExtendedOpenTelemetry extends OpenTelemetry { | ||
/** Returns the {@link EntityProvider} for this {@link OpenTelemetry}. */ | ||
default EntityProvider getEntityProvider() { | ||
return EntityProvider.noop(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.api.incubator.entities; | ||
|
||
import io.opentelemetry.api.common.Attributes; | ||
|
||
/** | ||
* A builder of an Entity that allows to add identifying or descriptive {@link Attributes}, as well | ||
* as type and schema_url. | ||
* | ||
* <p>Entity represents an object of interest associated with produced telemetry: traces, metrics or | ||
* logs. | ||
* | ||
* <p>For example, telemetry produced using OpenTelemetry SDK is normally associated with a Service | ||
* entity. Similarly, OpenTelemetry defines system metrics for a host. The Host is the entity we | ||
* want to associate metrics with in this case. | ||
* | ||
* <p>Entities may be also associated with produced telemetry indirectly. For example a service that | ||
* produces telemetry is also related with a process in which the service runs, so we say that the | ||
* Service entity is related to the Process entity. The process normally also runs on a host, so we | ||
* say that the Process entity is related to the Host entity. | ||
*/ | ||
public interface EntityBuilder { | ||
/** | ||
* Assign an OpenTelemetry schema URL to the resulting Entity. | ||
* | ||
* @param schemaUrl The URL of the OpenTelemetry schema being used to create this Entity. | ||
* @return this | ||
*/ | ||
EntityBuilder setSchemaUrl(String schemaUrl); | ||
|
||
/** | ||
* Modify the descriptive attributes of this Entity. | ||
* | ||
* @param description The {@link Attributes} which describe this Entity. | ||
* @return this | ||
*/ | ||
EntityBuilder withDescription(Attributes description); | ||
|
||
/** | ||
* Modify the identifying attributes of this Entity. | ||
* | ||
* @param id The {@link Attributes} which identify this Entity. | ||
* @return this | ||
*/ | ||
EntityBuilder withId(Attributes id); | ||
|
||
/** Emits the current entity. */ | ||
void emit(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.api.incubator.entities; | ||
|
||
/** | ||
* A registry for interacting with {@code Resource}s. The name <i>Provider</i> is for consistency | ||
* with other languages and it is <b>NOT</b> loaded using reflection. | ||
*/ | ||
public interface EntityProvider { | ||
/** | ||
* Returns a no-op {@link EntityProvider} which only creates no-op {@link EntityBuilder}s which do | ||
* not record nor are emitted. | ||
*/ | ||
static EntityProvider noop() { | ||
return NoopEntityProvider.INSTANCE; | ||
} | ||
|
||
/** | ||
* Removes an entity from this resource. | ||
* | ||
* @param entityType the type of entity to remove. | ||
* @return true if entity was found and removed. | ||
*/ | ||
boolean removeEntity(String entityType); | ||
|
||
/** | ||
* Attaches an entity to the current {@code Resource}. | ||
* | ||
* <p>This will only add new entities or update description of existing entities. | ||
* | ||
* @param entityType The type of the entity. | ||
* @return A builder that can construct an entity. | ||
*/ | ||
EntityBuilder attachOrUpdateEntity(String entityType); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.api.incubator.entities; | ||
|
||
import io.opentelemetry.api.common.Attributes; | ||
|
||
final class NoopEntityBuilder implements EntityBuilder { | ||
|
||
static final EntityBuilder INSTANCE = new NoopEntityBuilder(); | ||
|
||
@Override | ||
public EntityBuilder setSchemaUrl(String schemaUrl) { | ||
return this; | ||
} | ||
|
||
@Override | ||
public EntityBuilder withDescription(Attributes description) { | ||
return this; | ||
} | ||
|
||
@Override | ||
public EntityBuilder withId(Attributes id) { | ||
return this; | ||
} | ||
|
||
@Override | ||
public void emit() {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.api.incubator.entities; | ||
|
||
final class NoopEntityProvider implements EntityProvider { | ||
|
||
static final EntityProvider INSTANCE = new NoopEntityProvider(); | ||
|
||
@Override | ||
public boolean removeEntity(String entityType) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public EntityBuilder attachOrUpdateEntity(String entityType) { | ||
return NoopEntityBuilder.INSTANCE; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
Comparing source compatibility of opentelemetry-sdk-common-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-common-1.51.0.jar | ||
No changes. | ||
*** MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.resources.Resource (not serializable) | ||
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0 | ||
*** MODIFIED METHOD: PUBLIC NON_ABSTRACT (<- ABSTRACT) io.opentelemetry.api.common.Attributes getAttributes() |
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() | ||
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. Eventually we'll want to make this more efficient, avoiding 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. Agreed. This reminds me of the "dictonary" / "register an attribute set" API discussion from the spec meeting. 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. Also, if it wasn't apparent for now - This marshaller is only used ONCE per resource instance. So unless resource is being updated frequently (which we do not expect), then this cost is not paid often, if more than once. |
||
.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); | ||
MarshalerUtil.sizeRepeatedString(EntityRef.ID_KEYS, idKeysUtf8); | ||
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") | ||
.withDescription(Attributes.builder().put("desc.key", "desc.value").build()) | ||
.withId(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); | ||
} | ||
} |
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.
Maybe call this
attach()
since we haveResource#attacheEntity()
?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.
This is anticipation of having an EntityBuilder that just emits Entity without attaching to active resource.
I debated two things:
EntityBuilder
have two methods:emit
andattach
where this PR would only haveattach
.Resource
method you call to get the builder be namedattach
oremit
.I was planning to talk about this awkwardness in the next Entities SIG, but agree with only "attach" then this should be "attach".