-
Notifications
You must be signed in to change notification settings - Fork 347
Refactored span events to avoid not needed JSON deserialization for v… #11855
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: master
Are you sure you want to change the base?
Changes from all commits
9a21f3b
4a30f9b
e3a08ac
5ca1807
e555a0b
3f1ef94
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 |
|---|---|---|
|
|
@@ -2,18 +2,20 @@ | |
|
|
||
| import datadog.trace.api.time.SystemTimeSource; | ||
| import datadog.trace.api.time.TimeSource; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpanEvent; | ||
| import io.opentelemetry.api.common.AttributeKey; | ||
| import io.opentelemetry.api.common.Attributes; | ||
| import io.opentelemetry.api.common.AttributesBuilder; | ||
| import java.io.PrintWriter; | ||
| import java.io.StringWriter; | ||
| import java.util.Collections; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.concurrent.TimeUnit; | ||
| import javax.annotation.Nonnull; | ||
|
|
||
| public class OtelSpanEvent { | ||
| public class OtelSpanEvent implements AgentSpanEvent { | ||
| public static final String EXCEPTION_SPAN_EVENT_NAME = "exception"; | ||
| public static final AttributeKey<String> EXCEPTION_MESSAGE_ATTRIBUTE_KEY = | ||
| AttributeKey.stringKey("exception.message"); | ||
|
|
@@ -26,33 +28,69 @@ public class OtelSpanEvent { | |
| private static TimeSource timeSource = SystemTimeSource.INSTANCE; | ||
|
|
||
| private final String name; | ||
| private final String attributes; | ||
| private final Attributes attributes; | ||
|
|
||
| /** Event timestamp in nanoseconds. */ | ||
| private final long timestamp; | ||
|
|
||
| public OtelSpanEvent(String name, Attributes attributes) { | ||
| this.name = name; | ||
| this.attributes = AttributesJsonParser.toJson(attributes); | ||
| this.timestamp = OtelSpanEvent.timeSource.getCurrentTimeNanos(); | ||
| this(name, attributes, OtelSpanEvent.timeSource.getCurrentTimeNanos()); | ||
| } | ||
|
|
||
| public OtelSpanEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) { | ||
| this(name, attributes, unit.toNanos(timestamp)); | ||
| } | ||
|
|
||
| private OtelSpanEvent(String name, Attributes attributes, long timestampNanos) { | ||
| this.name = name; | ||
| this.attributes = AttributesJsonParser.toJson(attributes); | ||
| this.timestamp = unit.toNanos(timestamp); | ||
| this.attributes = attributes; | ||
| this.timestamp = timestampNanos; | ||
| } | ||
|
|
||
| @Nonnull | ||
| public static String toTag(List<OtelSpanEvent> events) { | ||
| StringBuilder builder = new StringBuilder("["); | ||
| for (OtelSpanEvent event : events) { | ||
| if (builder.length() > 1) { | ||
| builder.append(','); | ||
| } | ||
| builder.append(event.toJson()); | ||
| @Override | ||
| public String name() { | ||
| return this.name; | ||
| } | ||
|
|
||
| @Override | ||
| public long timeNanos() { | ||
| return this.timestamp; | ||
| } | ||
|
|
||
| /** | ||
| * Exposes the event attributes as typed values for native (V1) encoding. OpenTelemetry attribute | ||
| * values are already {@link String}, {@link Boolean}, {@link Long}, {@link Double} or a {@link | ||
| * List} of those, so they are passed through unchanged. | ||
| */ | ||
| @Override | ||
| public Map<String, Object> attributes() { | ||
| if (this.attributes == null || this.attributes.isEmpty()) { | ||
| return Collections.emptyMap(); | ||
| } | ||
| return builder.append(']').toString(); | ||
| Map<String, Object> map = new LinkedHashMap<>(this.attributes.size()); | ||
| this.attributes.forEach((key, value) -> map.put(key.getKey(), value)); | ||
| return map; | ||
| } | ||
|
|
||
| @Override | ||
| public CharSequence toJson() { | ||
|
Contributor
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. 🔨 issue: I would rather avoid implementation v0 encoding logic into OTel classes. Additionally, there will be a way to avoid creating multiple |
||
| StringBuilder builder = new StringBuilder(128); | ||
| builder | ||
| .append('{') | ||
| .append("\"time_unix_nano\":") | ||
| .append(this.timestamp) | ||
| .append(',') | ||
| .append("\"name\":") | ||
| .append('"') | ||
| .append(this.name) | ||
| .append('"'); | ||
|
Comment on lines
+83
to
+86
|
||
|
|
||
| String attributesJson = AttributesJsonParser.toJson(this.attributes); | ||
| if (!attributesJson.isEmpty()) { | ||
| builder.append(",\"attributes\":").append(attributesJson); | ||
| } | ||
|
|
||
| return builder.append('}').toString(); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -170,24 +208,14 @@ public static void setTimeSource(TimeSource newTimeSource) { | |
| timeSource = newTimeSource; | ||
| } | ||
|
|
||
| public String toJson() { | ||
| StringBuilder builder = | ||
| new StringBuilder( | ||
| "{\"time_unix_nano\":" + this.timestamp + ",\"name\":\"" + this.name + "\""); | ||
| if (!this.attributes.isEmpty()) { | ||
| builder.append(",\"attributes\":").append(this.attributes); | ||
| } | ||
| return builder.append('}').toString(); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "OtelSpanEvent{timestamp=" | ||
| + this.timestamp | ||
| + ", name='" | ||
| + this.name | ||
| + "', attributes='" | ||
| + "', attributes=" | ||
| + this.attributes | ||
| + "'}"; | ||
| + '}'; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| import datadog.trace.api.ProcessTags; | ||
| import datadog.trace.api.TagMap; | ||
| import datadog.trace.api.sampling.SamplingMechanism; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpanEvent; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; | ||
| import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; | ||
| import datadog.trace.bootstrap.instrumentation.api.Tags; | ||
|
|
@@ -89,7 +90,7 @@ public void map(List<? extends CoreSpan<?>> trace, Writable writable) { | |
| } | ||
|
|
||
| CoreSpan<?> firstSpan = trace.get(0); | ||
| firstSpan.processTagsAndBaggageWithStructuredLinks(spanMetadata); | ||
| firstSpan.processTagsAndBaggageWithStructuredLinksAndEvents(spanMetadata); | ||
| Metadata firstSpanMeta = spanMetadata.metadata; | ||
|
|
||
| // encoded fields: 1..7, but skipping #5, as not required by tracers and set by the agent. | ||
|
|
@@ -128,7 +129,7 @@ private void encodeSpans(Writable writable, int fieldId, List<? extends CoreSpan | |
| Metadata meta = spanMetadata.metadata; | ||
| for (CoreSpan<?> span : spans) { | ||
| if (meta == null) { | ||
| span.processTagsAndBaggageWithStructuredLinks(spanMetadata); | ||
| span.processTagsAndBaggageWithStructuredLinksAndEvents(spanMetadata); | ||
| meta = spanMetadata.metadata; | ||
| } | ||
| TagMap tags = meta.getTags(); | ||
|
|
@@ -160,9 +161,9 @@ private void encodeSpans(Writable writable, int fieldId, List<? extends CoreSpan | |
| // (example values: web, db, lambda) | ||
| encodeString(writable, 10, span.getType()); | ||
| // links = 11, a collection of links to other spans | ||
| encodeSpanLinks(writable, 11, meta.getSpanLinks()); | ||
| encodeSpanLinks(writable, 11, meta.getLinks()); | ||
| // events = 12, a collection of events that occurred during this span | ||
| encodeSpanEvents(writable, 12, tags.getObject(DDTags.SPAN_EVENTS)); | ||
| encodeSpanEvents(writable, 12, meta.getEvents()); | ||
| // env = 13, the optional string environment of this span | ||
| encodeString(writable, 13, tags.getString(Tags.ENV)); | ||
| // version = 14, the optional string version of this span | ||
|
|
@@ -200,48 +201,21 @@ private void encodeSpanLinks( | |
| } | ||
| } | ||
|
|
||
| private void encodeSpanEvents(Writable writable, int fieldId, Object eventsObject) { | ||
| private void encodeSpanEvents( | ||
| Writable writable, int fieldId, List<? extends AgentSpanEvent> events) { | ||
| writable.writeInt(fieldId); | ||
| if (!(eventsObject instanceof List) || ((List<?>) eventsObject).isEmpty()) { | ||
| if (events == null || events.isEmpty()) { | ||
| writable.startArray(0); | ||
| return; | ||
| } | ||
|
|
||
| List<?> events = (List<?>) eventsObject; | ||
| int encodableCount = 0; | ||
| for (Object event : events) { | ||
| if (isEncodableSpanEvent(event)) { | ||
| encodableCount++; | ||
| } | ||
| } | ||
| writable.startArray(encodableCount); | ||
| for (Object event : events) { | ||
| if (!(event instanceof Map)) { | ||
| continue; | ||
| } | ||
| Map<?, ?> eventMap = (Map<?, ?>) event; | ||
| Long timeUnixNano = asLong(eventMap.get("time_unix_nano")); | ||
| Object nameObject = eventMap.get("name"); | ||
| if (timeUnixNano == null || nameObject == null) { | ||
| continue; | ||
| } | ||
|
|
||
| Map<?, ?> attributes = | ||
| eventMap.get("attributes") instanceof Map ? (Map<?, ?>) eventMap.get("attributes") : null; | ||
|
|
||
| writable.startArray(events.size()); | ||
| for (AgentSpanEvent event : events) { | ||
| writable.startMap(3); | ||
| encodeLong(writable, 1, timeUnixNano); | ||
| encodeString(writable, 2, String.valueOf(nameObject)); | ||
| encodeEventAttributes(writable, 3, attributes); | ||
| } | ||
| } | ||
|
|
||
| private boolean isEncodableSpanEvent(Object event) { | ||
| if (!(event instanceof Map)) { | ||
| return false; | ||
| encodeLong(writable, 1, event.timeNanos()); | ||
| encodeString(writable, 2, event.name()); | ||
| encodeEventAttributes(writable, 3, event.attributes()); | ||
| } | ||
| Map<?, ?> eventMap = (Map<?, ?>) event; | ||
| return eventMap.get("name") != null && asLong(eventMap.get("time_unix_nano")) != null; | ||
| } | ||
|
|
||
| private void encodeEventAttributes(Writable writable, int fieldId, Map<?, ?> attrs) { | ||
|
|
@@ -340,20 +314,6 @@ private boolean isIntegralNumber(Number number) { | |
| return !(number instanceof Float || number instanceof Double); | ||
| } | ||
|
|
||
| private Long asLong(Object value) { | ||
| if (value instanceof Number) { | ||
| return ((Number) value).longValue(); | ||
| } | ||
| if (value instanceof CharSequence) { | ||
| try { | ||
| return Long.parseLong(value.toString()); | ||
| } catch (NumberFormatException ignored) { | ||
| return null; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private void encodeSpanAttributes( | ||
| Writable writable, int fieldId, Metadata meta, Map<String, Object> metaStruct) { | ||
| TagMap tags = meta.getTags(); | ||
|
|
@@ -364,9 +324,7 @@ private void encodeSpanAttributes( | |
| boolean writeTopLevel = meta.topLevel(); | ||
| int tagCount = 0; | ||
| for (TagMap.EntryReader entry : tags) { | ||
| if (!DDTags.SPAN_EVENTS.equals(entry.tag())) { | ||
|
Contributor
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. If a user sets a |
||
| tagCount += getFlatAttributeCount(entry); | ||
| } | ||
| tagCount += getFlatAttributeCount(entry); | ||
| } | ||
|
|
||
| writable.writeInt(fieldId); | ||
|
|
@@ -387,9 +345,6 @@ private void encodeSpanAttributes( | |
| } | ||
|
|
||
| for (TagMap.EntryReader entry : tags) { | ||
| if (DDTags.SPAN_EVENTS.equals(entry.tag())) { | ||
| continue; | ||
| } | ||
| writeFlattenedTagAttribute(writable, entry); | ||
| } | ||
| if (writeHttpStatus) { | ||
|
|
@@ -487,7 +442,7 @@ private void writeAttribute(Writable writable, String key, Object value) { | |
| return; | ||
| } | ||
|
|
||
| if (!(value instanceof String) && value != null) { | ||
| if (!(value instanceof CharSequence) && value != null) { | ||
| log.debug("Not a string value for key: {}, value: {}", key, value); | ||
| } | ||
| writable.writeInt(VALUE_TYPE_STRING); | ||
|
|
||
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.
Is order of entries important? If not then this could be a
HashMapwhich uses less space