Skip to content

Commit 3832bcc

Browse files
vaindbruno-garcia
andauthored
feat: options.AddProfilingIntegration() (#3660)
* feat: options.AddProfilingIntegration() * chore: update changelog * chore: update changelog * chore: add ProfilingIntegration to cocoa * check prior to adding the default integration --------- Co-authored-by: Bruno Garcia <[email protected]>
1 parent 86af8f0 commit 3832bcc

File tree

12 files changed

+204
-13
lines changed

12 files changed

+204
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
### Features
1818
- Added support for `.NET 9` (preview) ([#3699](https://github.com/getsentry/sentry-dotnet/pull/3699))
1919
- libsentrysupplemental.so now supports 16 KB page sizes on Android ([#3723](https://github.com/getsentry/sentry-dotnet/pull/3723))
20+
- Added `SentryOptions` extension for profiling: `options.AddProfilingIntegration()` ([#3660](https://github.com/getsentry/sentry-dotnet/pull/3660))
2021

2122
### Fixes
2223

samples/Sentry.Samples.AspNetCore.WebAPI.Profiling/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
builder.WebHost.UseSentry(o =>
44
{
55
o.Dsn = "https://[email protected]/5428537";
6-
o.AddIntegration(new ProfilingIntegration());
6+
o.AddProfilingIntegration();
77
o.ProfilesSampleRate = 0.1;
88
o.TracesSampleRate = 1.0;
99
});

samples/Sentry.Samples.Console.Profiling/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ private static void Main()
2222
// Debugging
2323
options.ShutdownTimeout = TimeSpan.FromMinutes(5);
2424

25-
options.AddIntegration(new ProfilingIntegration(TimeSpan.FromMilliseconds(500)));
25+
options.AddProfilingIntegration(TimeSpan.FromMilliseconds(500));
2626
}))
2727
{
2828
var tx = SentrySdk.StartTransaction("app", "run");

src/Sentry.Profiling/ProfilingIntegration.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,9 @@ public void Register(IHub hub, SentryOptions options)
4242
options.LogError(e, "Failed to initialize the profiler");
4343
}
4444
}
45+
else
46+
{
47+
options.LogInfo("Profiling Integration is disabled because profiling is disabled by configuration.");
48+
}
4549
}
4650
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Sentry.Extensibility;
2+
using Sentry.Profiling;
3+
4+
namespace Sentry;
5+
6+
/// <summary>
7+
/// The additional Sentry Options extensions from Sentry Profiling.
8+
/// </summary>
9+
[EditorBrowsable(EditorBrowsableState.Never)]
10+
public static class SentryOptionsProfilingExtensions
11+
{
12+
/// <summary>
13+
/// Adds ProfilingIntegration to Sentry.
14+
/// </summary>
15+
/// <param name="options">The Sentry options.</param>
16+
/// <param name="startupTimeout">
17+
/// If not given or TimeSpan.Zero, then the profiler initialization is asynchronous.
18+
/// This is useful for applications that need to start quickly. The profiler will start in the background
19+
/// and will be ready to capture transactions that have started after the profiler has started.
20+
///
21+
/// If given a non-zero timeout, profiling startup blocks up to the given amount of time. If the timeout is reached
22+
/// and the profiler session hasn't started yet, the execution is unblocked and behaves as the async startup,
23+
/// i.e. transactions will be profiled only after the session is eventually started.
24+
/// </param>
25+
public static void AddProfilingIntegration(this SentryOptions options, TimeSpan startupTimeout = default)
26+
{
27+
if (options.HasIntegration<ProfilingIntegration>())
28+
{
29+
options.LogWarning($"{nameof(ProfilingIntegration)} has already been added. The second call to {nameof(AddProfilingIntegration)} will be ignored.");
30+
return;
31+
}
32+
33+
options.AddIntegration(new ProfilingIntegration(startupTimeout));
34+
}
35+
36+
/// <summary>
37+
/// Disables the Profiling integration.
38+
/// </summary>
39+
/// <param name="options">The SentryOptions to remove the integration from.</param>
40+
public static void DisableProfilingIntegration(this SentryOptions options)
41+
{
42+
options.RemoveIntegration<ProfilingIntegration>();
43+
}
44+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Sentry.Extensibility;
2+
using Sentry.Integrations;
3+
4+
namespace Sentry.Cocoa;
5+
6+
/// <summary>
7+
/// Enables transaction performance profiling.
8+
/// </summary>
9+
public class ProfilingIntegration : ISdkIntegration
10+
{
11+
/// <inheritdoc/>
12+
public void Register(IHub hub, SentryOptions options)
13+
{
14+
if (options.IsProfilingEnabled)
15+
{
16+
try
17+
{
18+
options.LogDebug("Profiling is enabled, attaching native SDK profiler factory");
19+
options.TransactionProfilerFactory ??= new CocoaProfilerFactory(options);
20+
}
21+
catch (Exception e)
22+
{
23+
options.LogError(e, "Failed to initialize the profiler");
24+
}
25+
}
26+
else
27+
{
28+
options.LogInfo("Profiling Integration is disabled because profiling is disabled by configuration.");
29+
}
30+
}
31+
}

src/Sentry/Platforms/Cocoa/SentryOptions.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using ObjCRuntime;
2+
using Sentry.Cocoa;
3+
using Sentry.Extensibility;
24

35
// ReSharper disable once CheckNamespace
46
namespace Sentry;
@@ -215,4 +217,38 @@ public void AddInAppInclude(string prefix)
215217
InAppIncludes.Add(prefix);
216218
}
217219
}
220+
221+
// We actually add the profiling integration automatically in InitSentryCocoaSdk().
222+
// However, if user calls AddProfilingIntegration() multiple times, we print a warning, as usual.
223+
private bool _profilingIntegrationAddedByUser = false;
224+
225+
/// <summary>
226+
/// Adds ProfilingIntegration to Sentry.
227+
/// </summary>
228+
/// <param name="startupTimeout">
229+
/// Unused, only here so that the signature is the same as AddProfilingIntegration() from package Sentry.Profiling.
230+
/// </param>
231+
public void AddProfilingIntegration(TimeSpan startupTimeout = default)
232+
{
233+
if (HasIntegration<ProfilingIntegration>())
234+
{
235+
if (_profilingIntegrationAddedByUser)
236+
{
237+
DiagnosticLogger?.LogWarning($"{nameof(ProfilingIntegration)} has already been added. The second call to {nameof(AddProfilingIntegration)} will be ignored.");
238+
}
239+
return;
240+
}
241+
242+
_profilingIntegrationAddedByUser = true;
243+
AddIntegration(new ProfilingIntegration());
244+
}
245+
246+
/// <summary>
247+
/// Disables the Profiling integration.
248+
/// </summary>
249+
public void DisableProfilingIntegration()
250+
{
251+
_profilingIntegrationAddedByUser = false;
252+
RemoveIntegration<ProfilingIntegration>();
253+
}
218254
}

src/Sentry/Platforms/Cocoa/SentrySdk.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,11 @@ private static void InitSentryCocoaSdk(SentryOptions options)
185185
options.EnableScopeSync = true;
186186
options.ScopeObserver = new CocoaScopeObserver(options);
187187

188-
if (options.IsProfilingEnabled)
188+
// Note: don't use AddProfilingIntegration as it would print a warning if user used it too.
189+
if (!options.HasIntegration<ProfilingIntegration>())
189190
{
190-
options.LogDebug("Profiling is enabled, attaching native SDK profiler factory");
191-
options.TransactionProfilerFactory ??= new CocoaProfilerFactory(options);
191+
options.AddIntegration(new ProfilingIntegration());
192192
}
193-
194193
// TODO: Pause/Resume
195194
}
196195

src/Sentry/SentrySdk.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ internal static IHub InitHub(SentryOptions options)
102102
#endif
103103
{
104104
LogWarningIfProfilingMisconfigured(options, ", because ProfilingIntegration from package Sentry.Profiling" +
105-
" hasn't been registered. You can do that by calling 'options.AddIntegration(new ProfilingIntegration())'");
105+
" hasn't been registered. You can do that by calling 'options.AddProfilingIntegration()'");
106106
}
107107
#endif
108108

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
namespace Sentry.Profiling.Tests;
2+
3+
#nullable enable
4+
5+
[UsesVerify]
6+
7+
public class ProfilingSentryOptionsExtensionsTests
8+
{
9+
private readonly InMemoryDiagnosticLogger _logger = new();
10+
private readonly SentryOptions _options = new()
11+
{
12+
Dsn = ValidDsn,
13+
AutoSessionTracking = false,
14+
IsGlobalModeEnabled = true,
15+
BackgroundWorker = Substitute.For<IBackgroundWorker>(),
16+
Debug = true,
17+
18+
// Set explicitly for this test in case the defaults change in the future.
19+
TracesSampleRate = 0.0,
20+
TracesSampler = null
21+
};
22+
23+
public ProfilingSentryOptionsExtensionsTests()
24+
{
25+
_options.DiagnosticLogger = _logger;
26+
_options.AddProfilingIntegration();
27+
}
28+
29+
private Hub GetSut() => new(_options, Substitute.For<ISentryClient>());
30+
31+
private static IEnumerable<ISdkIntegration> GetIntegrations(ISentryClient hub) =>
32+
hub.GetSentryOptions()?.Integrations ?? Enumerable.Empty<ISdkIntegration>();
33+
34+
[Fact]
35+
public void Integration_DisabledWithDefaultOptions()
36+
{
37+
using var hub = GetSut();
38+
var integrations = GetIntegrations(hub);
39+
Assert.Contains(_logger.Entries, x => x.Message == "Profiling Integration is disabled because profiling is disabled by configuration."
40+
&& x.Level == SentryLevel.Info);
41+
}
42+
43+
[Fact]
44+
public void Integration_EnabledBySampleRate()
45+
{
46+
_options.TracesSampleRate = 1.0;
47+
_options.ProfilesSampleRate = 1.0;
48+
49+
using var hub = GetSut();
50+
var integrations = GetIntegrations(hub);
51+
Assert.Contains(integrations, i => i is ProfilingIntegration);
52+
}
53+
54+
[Fact]
55+
public void DisableProfilingIntegration_RemovesProfilingIntegration()
56+
{
57+
_options.TracesSampleRate = 1.0;
58+
_options.ProfilesSampleRate = 1.0;
59+
_options.DisableProfilingIntegration();
60+
61+
using var hub = GetSut();
62+
var integrations = GetIntegrations(hub);
63+
Assert.DoesNotContain(integrations, i => i is ProfilingIntegration);
64+
}
65+
66+
[Fact]
67+
public void AddProfilingIntegration_DoesntDuplicate()
68+
{
69+
var options = new SentryOptions();
70+
71+
options.AddProfilingIntegration();
72+
options.AddProfilingIntegration();
73+
74+
Assert.Single(options.Integrations, x => x is ProfilingIntegration);
75+
}
76+
}

0 commit comments

Comments
 (0)