Skip to content

Commit dde70a4

Browse files
43jayclaude
andcommitted
fix(profiling): Fix meta_length not serialized in Perfetto envelope header
SentryEnvelopeItemHeader.serialize() checked the raw metaLength field instead of calling getMetaLength(), so the callable path used by Perfetto profile chunks was never invoked and meta_length was never written to the envelope header JSON. Refactor SentryEnvelopeItemHeader to remove the metaLength field entirely — all constructors now store a single calculateMetaLength callable. Eager constructors (deserializer) wrap the Integer in a lambda. All constructors delegate to one private primary constructor. In fromPerfettoProfileChunk, replace the round-trip through ProfileChunk.setMetaLength/getMetaLength with a local AtomicInteger shared between the CachedItem lambda and the header callable, keeping meta_length as an envelope transport concern rather than in ProfileChunk Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c2a4442 commit dde70a4

3 files changed

Lines changed: 53 additions & 81 deletions

File tree

sentry/src/main/java/io/sentry/ProfileChunk.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -138,20 +138,6 @@ public double getTimestamp() {
138138
return contentType;
139139
}
140140

141-
/**
142-
* The length of the JSON metadata prefix in the binary envelope payload. Set during lazy
143-
* serialization of Perfetto profile chunks so the envelope item header can read it.
144-
*/
145-
private transient int metaLength = 0;
146-
147-
public int getMetaLength() {
148-
return metaLength;
149-
}
150-
151-
public void setMetaLength(final int metaLength) {
152-
this.metaLength = metaLength;
153-
}
154-
155141
public @Nullable SentryProfile getSentryProfile() {
156142
return sentryProfile;
157143
}

sentry/src/main/java/io/sentry/SentryEnvelopeItem.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.LinkedHashMap;
2929
import java.util.Map;
3030
import java.util.concurrent.Callable;
31+
import java.util.concurrent.atomic.AtomicInteger;
3132
import org.jetbrains.annotations.ApiStatus;
3233
import org.jetbrains.annotations.NotNull;
3334
import org.jetbrains.annotations.Nullable;
@@ -385,6 +386,12 @@ private static void ensureAttachmentSizeLimit(
385386
traceFile != null ? traceFile.getName() : "null"));
386387
}
387388

389+
// Perfetto profile chunks are serialized as [JSON metadata][raw .pftrace bytes] with no
390+
// delimiter. The server needs meta_length in the envelope header to know where the JSON
391+
// ends and the binary begins. meta_length is not known until the CachedItem payload lambda
392+
// runs, so we track it here as an envelope serialization concern rather than on ProfileChunk.
393+
final AtomicInteger metaLength = new AtomicInteger(-1);
394+
388395
final CachedItem cachedItem =
389396
new CachedItem(
390397
() -> {
@@ -394,6 +401,7 @@ private static void ensureAttachmentSizeLimit(
394401
final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) {
395402
serializer.serialize(profileChunk, writer);
396403
metadataBytes = stream.toByteArray();
404+
metaLength.set(metadataBytes.length);
397405
} catch (IOException e) {
398406
throw new SentryEnvelopeException(
399407
String.format(
@@ -416,9 +424,6 @@ private static void ensureAttachmentSizeLimit(
416424
System.arraycopy(metadataBytes, 0, payload, 0, metadataBytes.length);
417425
System.arraycopy(traceBytes, 0, payload, metadataBytes.length, traceBytes.length);
418426

419-
// Store metaLength so the header callable can read it after lazy evaluation
420-
profileChunk.setMetaLength(metadataBytes.length);
421-
422427
return payload;
423428
});
424429

@@ -431,7 +436,7 @@ private static void ensureAttachmentSizeLimit(
431436
null,
432437
profileChunk.getPlatform(),
433438
null,
434-
(Callable<Integer>) () -> profileChunk.getMetaLength());
439+
(Callable<Integer>) metaLength::get);
435440

436441
// avoid method refs on Android due to some issues with older AGP setups
437442
// noinspection Convert2MethodRef

sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ public final class SentryEnvelopeItemHeader implements JsonSerializable, JsonUnk
2121
private final int length;
2222
@Nullable private final Callable<Integer> getLength;
2323
private final @Nullable String attachmentType;
24-
private final @Nullable Integer metaLength;
25-
@Nullable private final Callable<Integer> getMetaLength;
24+
@Nullable private final Callable<Integer> calculateMetaLength;
2625

2726
private @Nullable Map<String, Object> unknown;
2827

@@ -54,31 +53,41 @@ public int getLength() {
5453
return platform;
5554
}
5655

57-
public @Nullable Integer getMetaLength() {
58-
if (getMetaLength != null) {
56+
@Nullable Integer getMetaLength() {
57+
if (calculateMetaLength != null) {
5958
try {
60-
return getMetaLength.call();
59+
return calculateMetaLength.call();
6160
} catch (Throwable ignored) {
6261
return null;
6362
}
6463
}
65-
return metaLength;
64+
return null;
6665
}
6766

68-
@ApiStatus.Internal
69-
public SentryEnvelopeItemHeader(
67+
private SentryEnvelopeItemHeader(
7068
final @NotNull SentryItemType type,
7169
int length,
70+
final @Nullable Callable<Integer> getLength,
7271
final @Nullable String contentType,
7372
final @Nullable String fileName,
7473
final @Nullable String attachmentType,
7574
final @Nullable String platform,
76-
final @Nullable Integer itemCount) {
77-
this(type, length, contentType, fileName, attachmentType, platform, itemCount, null);
75+
final @Nullable Integer itemCount,
76+
final @Nullable Callable<Integer> calculateMetaLength) {
77+
this.type = Objects.requireNonNull(type, "type is required");
78+
this.contentType = contentType;
79+
this.length = length;
80+
this.fileName = fileName;
81+
this.getLength = getLength;
82+
this.attachmentType = attachmentType;
83+
this.platform = platform;
84+
this.itemCount = itemCount;
85+
this.calculateMetaLength = calculateMetaLength;
7886
}
7987

88+
/** Eager constructor used internally by {@link Deserializer#deserialize}. */
8089
@ApiStatus.Internal
81-
public SentryEnvelopeItemHeader(
90+
private SentryEnvelopeItemHeader(
8291
final @NotNull SentryItemType type,
8392
int length,
8493
final @Nullable String contentType,
@@ -87,44 +96,37 @@ public SentryEnvelopeItemHeader(
8796
final @Nullable String platform,
8897
final @Nullable Integer itemCount,
8998
final @Nullable Integer metaLength) {
90-
this.type = Objects.requireNonNull(type, "type is required");
91-
this.contentType = contentType;
92-
this.length = length;
93-
this.fileName = fileName;
94-
this.getLength = null;
95-
this.attachmentType = attachmentType;
96-
this.platform = platform;
97-
this.itemCount = itemCount;
98-
this.metaLength = metaLength;
99-
this.getMetaLength = null;
99+
this(
100+
type, length, null, contentType, fileName, attachmentType, platform, itemCount,
101+
metaLength != null ? () -> metaLength : null);
100102
}
101103

102-
SentryEnvelopeItemHeader(
104+
@ApiStatus.Internal
105+
public SentryEnvelopeItemHeader(
103106
final @NotNull SentryItemType type,
104-
final @Nullable Callable<Integer> getLength,
107+
int length,
105108
final @Nullable String contentType,
106109
final @Nullable String fileName,
107-
final @Nullable String attachmentType) {
108-
this(type, getLength, contentType, fileName, attachmentType, null, null);
110+
final @Nullable String attachmentType,
111+
final @Nullable String platform,
112+
final @Nullable Integer itemCount) {
113+
this(type, length, contentType, fileName, attachmentType, platform, itemCount, null);
109114
}
110115

116+
/**
117+
* Lazy constructor. Both length and metaLength are computed lazily as these depend on
118+
* the Item having been evaluated.
119+
*/
111120
SentryEnvelopeItemHeader(
112121
final @NotNull SentryItemType type,
113122
final @Nullable Callable<Integer> getLength,
114123
final @Nullable String contentType,
115124
final @Nullable String fileName,
116125
final @Nullable String attachmentType,
117126
final @Nullable String platform,
118-
final @Nullable Integer itemCount) {
119-
this(
120-
type,
121-
getLength,
122-
contentType,
123-
fileName,
124-
attachmentType,
125-
platform,
126-
itemCount,
127-
(Integer) null);
127+
final @Nullable Integer itemCount,
128+
final @Nullable Callable<Integer> calculateMetaLength) {
129+
this(type, -1, getLength, contentType, fileName, attachmentType, platform, itemCount, calculateMetaLength);
128130
}
129131

130132
SentryEnvelopeItemHeader(
@@ -134,39 +136,17 @@ public SentryEnvelopeItemHeader(
134136
final @Nullable String fileName,
135137
final @Nullable String attachmentType,
136138
final @Nullable String platform,
137-
final @Nullable Integer itemCount,
138-
final @Nullable Integer metaLength) {
139-
this.type = Objects.requireNonNull(type, "type is required");
140-
this.contentType = contentType;
141-
this.length = -1;
142-
this.fileName = fileName;
143-
this.getLength = getLength;
144-
this.attachmentType = attachmentType;
145-
this.platform = platform;
146-
this.itemCount = itemCount;
147-
this.metaLength = metaLength;
148-
this.getMetaLength = null;
139+
final @Nullable Integer itemCount) {
140+
this(type, getLength, contentType, fileName, attachmentType, platform, itemCount, null);
149141
}
150142

151143
SentryEnvelopeItemHeader(
152144
final @NotNull SentryItemType type,
153145
final @Nullable Callable<Integer> getLength,
154146
final @Nullable String contentType,
155147
final @Nullable String fileName,
156-
final @Nullable String attachmentType,
157-
final @Nullable String platform,
158-
final @Nullable Integer itemCount,
159-
final @Nullable Callable<Integer> getMetaLength) {
160-
this.type = Objects.requireNonNull(type, "type is required");
161-
this.contentType = contentType;
162-
this.length = -1;
163-
this.fileName = fileName;
164-
this.getLength = getLength;
165-
this.attachmentType = attachmentType;
166-
this.platform = platform;
167-
this.itemCount = itemCount;
168-
this.metaLength = null;
169-
this.getMetaLength = getMetaLength;
148+
final @Nullable String attachmentType) {
149+
this(type, getLength, contentType, fileName, attachmentType, null, null);
170150
}
171151

172152
SentryEnvelopeItemHeader(
@@ -219,8 +199,9 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger
219199
if (itemCount != null) {
220200
writer.name(JsonKeys.ITEM_COUNT).value(itemCount);
221201
}
222-
if (metaLength != null) {
223-
writer.name(JsonKeys.META_LENGTH).value(metaLength);
202+
final @Nullable Integer resolvedMetaLength = getMetaLength();
203+
if (resolvedMetaLength != null) {
204+
writer.name(JsonKeys.META_LENGTH).value(resolvedMetaLength);
224205
}
225206
writer.name(JsonKeys.LENGTH).value(getLength());
226207
if (unknown != null) {

0 commit comments

Comments
 (0)