Skip to content

Commit 38b5c64

Browse files
Improve serialization perf and fix memory leak in SentryEvent (#1693)
1 parent 7c48687 commit 38b5c64

File tree

15 files changed

+41
-39
lines changed

15 files changed

+41
-39
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
- Improve timestamp precision of transactions and spans ([#1680](https://github.com/getsentry/sentry-dotnet/pull/1680))
88
- Flatten AggregateException ([#1672](https://github.com/getsentry/sentry-dotnet/pull/1672))
9-
- NOTE: This can affect grouping. You can keep the original behavior by setting the option `KeepAggregateException` to `true`.
9+
- NOTE: This can affect grouping. You can keep the original behavior by setting the option `KeepAggregateException` to `true`.
1010
- Serialize stack frame addresses as strings. ([#1692](https://github.com/getsentry/sentry-dotnet/pull/1692))
11+
- Improve serialization perf and fix memory leak in `SentryEvent` ([#1693](https://github.com/getsentry/sentry-dotnet/pull/1693))
1112

1213
### Features
1314

src/Sentry/Envelopes/Envelope.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public static Envelope FromSession(SessionUpdate sessionUpdate)
232232
}
233233

234234
return
235-
Json.Parse(buffer.ToArray()).GetDictionaryOrNull()
235+
Json.Parse(buffer.ToArray(), JsonExtensions.GetDictionaryOrNull)
236236
?? throw new InvalidOperationException("Envelope header is malformed.");
237237
}
238238

src/Sentry/Envelopes/EnvelopeItem.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ internal static EnvelopeItem FromClientReport(ClientReport report)
317317
}
318318

319319
return
320-
Json.Parse(buffer.ToArray()).GetDictionaryOrNull()
320+
Json.Parse(buffer.ToArray(), JsonExtensions.GetDictionaryOrNull)
321321
?? throw new InvalidOperationException("Envelope item header is malformed.");
322322
}
323323

@@ -339,49 +339,49 @@ private static async Task<ISerializable> DeserializePayloadAsync(
339339
{
340340
var bufferLength = (int)(payloadLength ?? stream.Length);
341341
var buffer = await stream.ReadByteChunkAsync(bufferLength, cancellationToken).ConfigureAwait(false);
342-
var json = Json.Parse(buffer);
342+
var sentryEvent = Json.Parse(buffer, SentryEvent.FromJson);
343343

344-
return new JsonSerializable(SentryEvent.FromJson(json));
344+
return new JsonSerializable(sentryEvent);
345345
}
346346

347347
// User report
348348
if (string.Equals(payloadType, TypeValueUserReport, StringComparison.OrdinalIgnoreCase))
349349
{
350350
var bufferLength = (int)(payloadLength ?? stream.Length);
351351
var buffer = await stream.ReadByteChunkAsync(bufferLength, cancellationToken).ConfigureAwait(false);
352-
var json = Json.Parse(buffer);
352+
var userFeedback = Json.Parse(buffer, UserFeedback.FromJson);
353353

354-
return new JsonSerializable(UserFeedback.FromJson(json));
354+
return new JsonSerializable(userFeedback);
355355
}
356356

357357
// Transaction
358358
if (string.Equals(payloadType, TypeValueTransaction, StringComparison.OrdinalIgnoreCase))
359359
{
360360
var bufferLength = (int)(payloadLength ?? stream.Length);
361361
var buffer = await stream.ReadByteChunkAsync(bufferLength, cancellationToken).ConfigureAwait(false);
362-
var json = Json.Parse(buffer);
362+
var transaction = Json.Parse(buffer, Transaction.FromJson);
363363

364-
return new JsonSerializable(Transaction.FromJson(json));
364+
return new JsonSerializable(transaction);
365365
}
366366

367367
// Session
368368
if (string.Equals(payloadType, TypeValueSession, StringComparison.OrdinalIgnoreCase))
369369
{
370370
var bufferLength = (int)(payloadLength ?? stream.Length);
371371
var buffer = await stream.ReadByteChunkAsync(bufferLength, cancellationToken).ConfigureAwait(false);
372-
var json = Json.Parse(buffer);
372+
var sessionUpdate = Json.Parse(buffer, SessionUpdate.FromJson);
373373

374-
return new JsonSerializable(SessionUpdate.FromJson(json));
374+
return new JsonSerializable(sessionUpdate);
375375
}
376376

377377
// Client Report
378378
if (string.Equals(payloadType, TypeValueClientReport, StringComparison.OrdinalIgnoreCase))
379379
{
380380
var bufferLength = (int)(payloadLength ?? stream.Length);
381381
var buffer = await stream.ReadByteChunkAsync(bufferLength, cancellationToken).ConfigureAwait(false);
382-
var json = Json.Parse(buffer);
382+
var clientReport = Json.Parse(buffer, ClientReport.FromJson);
383383

384-
return new JsonSerializable(ClientReport.FromJson(json));
384+
return new JsonSerializable(clientReport);
385385
}
386386

387387
// Arbitrary payload

src/Sentry/GlobalSessionManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public GlobalSessionManager(
4141
_options = options;
4242
_clock = clock ?? SystemClock.Clock;
4343
_persistedSessionProvider = persistedSessionProvider
44-
?? (filePath => PersistedSessionUpdate.FromJson(Json.Load(filePath)));
44+
?? (filePath => Json.Load(filePath, PersistedSessionUpdate.FromJson));
4545

4646
// TODO: session file should really be process-isolated, but we
4747
// don't have a proper mechanism for that right now.

src/Sentry/Internal/Json.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
1+
using System;
12
using System.IO;
23
using System.Text.Json;
34

45
namespace Sentry.Internal
56
{
67
internal static class Json
78
{
8-
public static JsonElement Parse(byte[] json)
9+
public static T Parse<T>(byte[] json, Func<JsonElement, T> factory)
910
{
1011
using var jsonDocument = JsonDocument.Parse(json);
11-
return jsonDocument.RootElement.Clone();
12+
return factory.Invoke(jsonDocument.RootElement);
1213
}
1314

14-
public static JsonElement Parse(string json)
15+
public static T Parse<T>(string json, Func<JsonElement, T> factory)
1516
{
1617
using var jsonDocument = JsonDocument.Parse(json);
17-
return jsonDocument.RootElement.Clone();
18+
return factory.Invoke(jsonDocument.RootElement);
1819
}
1920

20-
public static JsonElement Load(string filePath)
21+
public static T Load<T>(string filePath, Func<JsonElement, T> factory)
2122
{
2223
using var file = File.OpenRead(filePath);
2324
using var jsonDocument = JsonDocument.Parse(file);
24-
return jsonDocument.RootElement.Clone();
25+
return factory.Invoke(jsonDocument.RootElement);
2526
}
2627
}
2728
}

src/Sentry/SentryEvent.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,8 @@ public static SentryEvent FromJson(JsonElement json)
273273
var logger = json.GetPropertyOrNull("logger")?.GetString();
274274
var serverName = json.GetPropertyOrNull("server_name")?.GetString();
275275
var release = json.GetPropertyOrNull("release")?.GetString();
276-
var exceptionValues = json.GetPropertyOrNull("exception")?.GetPropertyOrNull("values")?.EnumerateArray().Select(SentryException.FromJson).Pipe(v => new SentryValues<SentryException>(v));
277-
var threadValues = json.GetPropertyOrNull("threads")?.GetPropertyOrNull("values")?.EnumerateArray().Select(SentryThread.FromJson).Pipe(v => new SentryValues<SentryThread>(v));
276+
var exceptionValues = json.GetPropertyOrNull("exception")?.GetPropertyOrNull("values")?.EnumerateArray().Select(SentryException.FromJson).ToList().Pipe(v => new SentryValues<SentryException>(v));
277+
var threadValues = json.GetPropertyOrNull("threads")?.GetPropertyOrNull("values")?.EnumerateArray().Select(SentryThread.FromJson).ToList().Pipe(v => new SentryValues<SentryThread>(v));
278278
var level = json.GetPropertyOrNull("level")?.GetString()?.ParseEnum<SentryLevel>();
279279
var transaction = json.GetPropertyOrNull("transaction")?.GetString();
280280
var request = json.GetPropertyOrNull("request")?.Pipe(Request.FromJson);

test/Sentry.Tests/Protocol/BreadcrumbTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public void SerializeObject_ParameterlessConstructor_IncludesTimestamp()
88
var sut = new Breadcrumb("test", "unit");
99

1010
var actualJson = sut.ToJsonString();
11-
var actual = Breadcrumb.FromJson(Json.Parse(actualJson));
11+
var actual = Json.Parse(actualJson, Breadcrumb.FromJson);
1212

1313
Assert.NotEqual(default, actual.Timestamp);
1414
}

test/Sentry.Tests/Protocol/Context/AppTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public void SerializeObject_AllPropertiesSetToNonDefault_SerializesValidObject()
1818

1919
var actualString = sut.ToJsonString();
2020

21-
var actual = App.FromJson(Json.Parse(actualString));
21+
var actual = Json.Parse(actualString, App.FromJson);
2222
actual.Should().BeEquivalentTo(sut);
2323
}
2424

test/Sentry.Tests/Protocol/Context/BrowserTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public void SerializeObject_AllPropertiesSetToNonDefault_SerializesValidObject()
1414

1515
var actualString = sut.ToJsonString();
1616

17-
var actual = Browser.FromJson(Json.Parse(actualString));
17+
var actual = Json.Parse(actualString, Browser.FromJson);
1818
actual.Should().BeEquivalentTo(sut);
1919
}
2020

test/Sentry.Tests/Protocol/Context/ContextsTests.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public void SerializeObject_NoPropertyFilled_SerializesEmptyObject()
1010

1111
var actualString = sut.ToJsonString();
1212

13-
var actual = Contexts.FromJson(Json.Parse(actualString));
13+
var actual = Json.Parse(actualString, Contexts.FromJson);
1414
actual.Should().BeEquivalentTo(sut);
1515

1616
Assert.Equal("{}", actualString);
@@ -28,7 +28,7 @@ public void SerializeObject_SingleUserDefinedKeyPropertySet_SerializeSinglePrope
2828

2929
var actualString = sut.ToJsonString();
3030

31-
var actual = Contexts.FromJson(Json.Parse(actualString));
31+
var actual = Json.Parse(actualString, Contexts.FromJson);
3232
actual.Should().BeEquivalentTo(sut);
3333

3434
Assert.Equal("{\"server\":{\"type\":\"os\",\"name\":\"Linux\"}}", actualString);
@@ -47,7 +47,7 @@ public void SerializeObject_SingleDevicePropertySet_SerializeSingleProperty()
4747

4848
var actualString = sut.ToJsonString();
4949

50-
var actual = Contexts.FromJson(Json.Parse(actualString));
50+
var actual = Json.Parse(actualString, Contexts.FromJson);
5151
actual.Should().BeEquivalentTo(sut);
5252

5353
Assert.Equal("{\"device\":{\"type\":\"device\",\"arch\":\"x86\"}}", actualString);
@@ -66,7 +66,7 @@ public void SerializeObject_SingleAppPropertySet_SerializeSingleProperty()
6666

6767
var actualString = sut.ToJsonString();
6868

69-
var actual = Contexts.FromJson(Json.Parse(actualString));
69+
var actual = Json.Parse(actualString, Contexts.FromJson);
7070
actual.Should().BeEquivalentTo(sut);
7171

7272
Assert.Equal("{\"app\":{\"type\":\"app\",\"app_name\":\"My.App\"}}", actualString);
@@ -85,7 +85,7 @@ public void SerializeObject_SingleGpuPropertySet_SerializeSingleProperty()
8585

8686
var actualString = sut.ToJsonString();
8787

88-
var actual = Contexts.FromJson(Json.Parse(actualString));
88+
var actual = Json.Parse(actualString, Contexts.FromJson);
8989
actual.Should().BeEquivalentTo(sut);
9090

9191
Assert.Equal("{\"gpu\":{\"type\":\"gpu\",\"name\":\"My.Gpu\"}}", actualString);
@@ -104,7 +104,7 @@ public void SerializeObject_SingleRuntimePropertySet_SerializeSingleProperty()
104104

105105
var actualString = sut.ToJsonString();
106106

107-
var actual = Contexts.FromJson(Json.Parse(actualString));
107+
var actual = Json.Parse(actualString, Contexts.FromJson);
108108
actual.Should().BeEquivalentTo(sut);
109109

110110
Assert.Equal("{\"runtime\":{\"type\":\"runtime\",\"version\":\"2.1.1.100\"}}", actualString);
@@ -121,7 +121,7 @@ public void SerializeObject_AnonymousObject_SerializedCorrectly()
121121

122122
// Act
123123
var json = contexts.ToJsonString();
124-
var roundtrip = Contexts.FromJson(Json.Parse(json));
124+
var roundtrip = Json.Parse(json, Contexts.FromJson);
125125

126126
// Assert
127127
json.Should().Be("{\"foo\":{\"Bar\":42,\"Baz\":\"kek\"}}");
@@ -146,7 +146,7 @@ public void Ctor_SingleBrowserPropertySet_SerializeSingleProperty()
146146

147147
var actualString = sut.ToJsonString();
148148

149-
var actual = Contexts.FromJson(Json.Parse(actualString));
149+
var actual = Json.Parse(actualString, Contexts.FromJson);
150150
actual.Should().BeEquivalentTo(sut);
151151

152152
Assert.Equal("{\"browser\":{\"type\":\"browser\",\"name\":\"Netscape 1\"}}", actualString);
@@ -165,7 +165,7 @@ public void Ctor_SingleOperatingSystemPropertySet_SerializeSingleProperty()
165165

166166
var actualString = sut.ToJsonString();
167167

168-
var actual = Contexts.FromJson(Json.Parse(actualString));
168+
var actual = Json.Parse(actualString, Contexts.FromJson);
169169
actual.Should().BeEquivalentTo(sut);
170170

171171
Assert.Equal("{\"os\":{\"type\":\"os\",\"name\":\"BeOS 1\"}}", actualString);

0 commit comments

Comments
 (0)