diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java index 680220b5e3e..6170e1925d2 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java @@ -122,6 +122,22 @@ public void writeString( generator.writeString(string); } + @Override + public void writeRepeatedString(ProtoFieldInfo field, byte[][] utf8Bytes) throws IOException { + generator.writeArrayFieldStart(field.getJsonName()); + for (byte[] value : utf8Bytes) { + // Marshalers encoded String into UTF-8 bytes to optimize for binary serialization where + // we are able to avoid the encoding process happening twice, one for size computation and one + // for actual writing. JsonGenerator actually has a writeUTF8String that would be able to + // accept + // this, but it only works when writing to an OutputStream, but not to a String like we do for + // writing to logs. It's wasteful to take a String, convert it to bytes, and convert back to + // the same String but we can see if this can be improved in the future. + generator.writeString(new String(value, StandardCharsets.UTF_8)); + } + generator.writeEndArray(); + } + @Override public void writeBytes(ProtoFieldInfo field, byte[] value) throws IOException { generator.writeBinaryField(field.getJsonName(), value); diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/MarshalerUtil.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/MarshalerUtil.java index d7f6d44c871..92db2fa4271 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/MarshalerUtil.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/MarshalerUtil.java @@ -110,6 +110,16 @@ private static int sizeRepeatedFixed64(ProtoFieldInfo field, int numValues) { return size; } + /** Returns the size of a repeated string field. */ + @SuppressWarnings("AvoidObjectArrays") + public static int sizeRepeatedString(ProtoFieldInfo field, byte[][] utf8Bytes) { + int size = 0; + for (byte[] i : utf8Bytes) { + size += MarshalerUtil.sizeBytes(field, i); + } + return size; + } + /** * Returns the size of a repeated uint64 field. * diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/ProtoSerializer.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/ProtoSerializer.java index 62f4a175982..694cec8b2b9 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/ProtoSerializer.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/ProtoSerializer.java @@ -163,6 +163,13 @@ public void writeString( StatelessMarshalerUtil.writeUtf8(output, string, utf8Length, context); } + @Override + public void writeRepeatedString(ProtoFieldInfo field, byte[][] utf8Bytes) throws IOException { + for (byte[] value : utf8Bytes) { + writeString(field, value); + } + } + @Override public void writeBytes(ProtoFieldInfo field, byte[] value) throws IOException { output.writeUInt32NoTag(field.getTag()); diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Serializer.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Serializer.java index e7970d57491..069fa3a6b58 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Serializer.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Serializer.java @@ -220,6 +220,18 @@ public void serializeString(ProtoFieldInfo field, byte[] utf8Bytes) throws IOExc writeString(field, utf8Bytes); } + /** + * Serializes a protobuf {@code repeated string} field. {@code utf8Bytes} is the UTF8 encoded + * bytes of the strings to serialize. + */ + @SuppressWarnings("AvoidObjectArrays") + public void serializeRepeatedString(ProtoFieldInfo field, byte[][] utf8Bytes) throws IOException { + if (utf8Bytes.length == 0) { + return; + } + writeRepeatedString(field, utf8Bytes); + } + /** * Serializes a protobuf {@code string} field. {@code string} is the value to be serialized and * {@code utf8Length} is the length of the string after it is encoded in UTF8. This method reads @@ -246,6 +258,11 @@ public abstract void writeString( ProtoFieldInfo field, String string, int utf8Length, MarshalerContext context) throws IOException; + /** Writes a protobuf {@code repeated string} field, even if it matches the default value. */ + @SuppressWarnings("AvoidObjectArrays") + public abstract void writeRepeatedString(ProtoFieldInfo field, byte[][] utf8Bytes) + throws IOException; + /** Serializes a protobuf {@code bytes} field. */ public void serializeBytes(ProtoFieldInfo field, byte[] value) throws IOException { if (value.length == 0) { diff --git a/exporters/otlp/profiles/src/main/java/io/opentelemetry/exporter/otlp/profiles/ProfileMarshaler.java b/exporters/otlp/profiles/src/main/java/io/opentelemetry/exporter/otlp/profiles/ProfileMarshaler.java index 6553c598c1d..bd6bc7521c8 100644 --- a/exporters/otlp/profiles/src/main/java/io/opentelemetry/exporter/otlp/profiles/ProfileMarshaler.java +++ b/exporters/otlp/profiles/src/main/java/io/opentelemetry/exporter/otlp/profiles/ProfileMarshaler.java @@ -149,9 +149,7 @@ protected void writeTo(Serializer output) throws IOException { output.serializeRepeatedMessage(Profile.ATTRIBUTE_TABLE, attributeMarshalers); output.serializeRepeatedMessage(Profile.ATTRIBUTE_UNITS, attributeUnitMarshalers); output.serializeRepeatedMessage(Profile.LINK_TABLE, linkMarshalers); - for (byte[] i : stringTable) { - output.serializeString(Profile.STRING_TABLE, i); - } + output.serializeRepeatedString(Profile.STRING_TABLE, stringTable); output.serializeInt64(Profile.DROP_FRAMES, dropFrames); output.serializeInt64(Profile.KEEP_FRAMES, keepFrames); output.serializeInt64(Profile.TIME_NANOS, timeNanos); @@ -192,9 +190,7 @@ private static int calculateSize( size += MarshalerUtil.sizeRepeatedMessage(Profile.ATTRIBUTE_TABLE, attributeMarshalers); size += MarshalerUtil.sizeRepeatedMessage(Profile.ATTRIBUTE_UNITS, attributeUnitMarshalers); size += MarshalerUtil.sizeRepeatedMessage(Profile.LINK_TABLE, linkMarshalers); - for (byte[] i : stringTable) { - size += MarshalerUtil.sizeBytes(Profile.STRING_TABLE, i); - } + size += MarshalerUtil.sizeRepeatedString(Profile.STRING_TABLE, stringTable); size += MarshalerUtil.sizeInt64(Profile.DROP_FRAMES, dropFrames); size += MarshalerUtil.sizeInt64(Profile.KEEP_FRAMES, keepFrames); size += MarshalerUtil.sizeInt64(Profile.TIME_NANOS, timeNanos); diff --git a/exporters/otlp/profiles/src/test/java/io/opentelemetry/exporter/otlp/profiles/ProfilesRequestMarshalerTest.java b/exporters/otlp/profiles/src/test/java/io/opentelemetry/exporter/otlp/profiles/ProfilesRequestMarshalerTest.java index 927c403da17..3d0ca5dda53 100644 --- a/exporters/otlp/profiles/src/test/java/io/opentelemetry/exporter/otlp/profiles/ProfilesRequestMarshalerTest.java +++ b/exporters/otlp/profiles/src/test/java/io/opentelemetry/exporter/otlp/profiles/ProfilesRequestMarshalerTest.java @@ -275,7 +275,7 @@ private static ProfileData sampleProfileData() { Attributes.empty(), Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), + listOf("foo", "bar"), 3, 4, 5, @@ -304,6 +304,8 @@ private static Profile.Builder sampleProfileBuilder() { .AGGREGATION_TEMPORALITY_CUMULATIVE) .build()) .addAllComment(listOf(8L, 9L)) + .addStringTable("foo") + .addStringTable("bar") .setDefaultSampleType(10); }