Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
16984f6
feat: internal array types
vaind Feb 28, 2023
f6791c7
chore: update JsonExtensions to work with GrowableArray
vaind Feb 28, 2023
ef87833
feat: SampleProfile
vaind Feb 28, 2023
a460fa5
extract DebugMeta to a separate class
vaind Feb 28, 2023
ac10206
feat: add ProfileInfo class
vaind Feb 28, 2023
74d48ad
feat: add Sentry.Extensions.Profiling project
vaind Mar 1, 2023
3fd4941
feat: add ITransactionProfiler
vaind Mar 1, 2023
13924dc
feat: profile with DiagnosticsClient
vaind Mar 1, 2023
81ef851
feat: test & fix profiling
vaind Mar 1, 2023
052c65d
more changes to try & make profile acceptable for relay
vaind Mar 1, 2023
57dbc6c
profiling fixes and sample changes
vaind Mar 2, 2023
55af72c
basic profiler verify test
vaind Mar 3, 2023
a315175
remove "uknkown" frames from sample stack traces
vaind Mar 5, 2023
8cab1de
code cleanup
vaind Mar 5, 2023
3dd8837
add optimization tier to the output
vaind Mar 5, 2023
f0130a5
tmp: store envelopes and nettrace logs
vaind Mar 6, 2023
7afad64
testing
vaind Mar 6, 2023
332d323
testing
vaind Mar 7, 2023
32e4da1
refactor profile collection to be async
vaind Mar 8, 2023
0bdb8a0
stop profiling after 30 seconds
vaind Mar 8, 2023
a40ab11
temporary changes
vaind Mar 8, 2023
6230b63
downsampling
vaind Mar 8, 2023
101d7bc
fixes
vaind Mar 8, 2023
2c5c367
backup: in-memory etlx conversion
vaind Mar 10, 2023
f3be4b8
switch back to etlx in file
vaind Mar 10, 2023
5908f8e
ProfileInfo verify tests
vaind Mar 10, 2023
e50243d
move downsampling earlier
vaind Mar 10, 2023
e7f64db
move async processing to Envelope
vaind Mar 10, 2023
06f634b
lints
vaind Mar 10, 2023
a603547
make setting profiling temp dir path explicit
vaind Mar 11, 2023
c068d06
tests, logging and fixes
vaind Mar 13, 2023
c7aa1a3
handle exceptions gracefully
vaind Mar 13, 2023
8b75a6b
fix verify tests
vaind Mar 13, 2023
9ad578c
Apply suggestions from code review
vaind Mar 14, 2023
d3aeefe
minor fixes
vaind Mar 13, 2023
dc53d02
rename Sentry.Extensions.Profiling to Sentry.Profiling
vaind Mar 14, 2023
0df15fd
fix: sampling profiler parallel tests
vaind Mar 14, 2023
bd20d8e
cleanup
vaind Mar 14, 2023
4f9711c
set in-app for frames
vaind Mar 14, 2023
743ad5c
fix: active thread id
vaind Mar 14, 2023
3bcabfc
post-rebase fix
vaind Mar 14, 2023
a8c5a29
show real thread id to make it possible to compare to nettrace output
vaind Mar 14, 2023
68e88f0
fix tracelog processor stack assignment
vaind Mar 15, 2023
d6b3ed5
fix tests
vaind Mar 15, 2023
05cd3ab
cleanups
vaind Mar 15, 2023
8634d26
feat: activity-based groupping
vaind Mar 16, 2023
1a606af
Merge branch 'main' into feat/profiling
mattjohnsonpint Apr 3, 2023
542e66b
Merge branch 'main' into feat/profiling
mattjohnsonpint Apr 4, 2023
b464be6
chore: remove unused SparseArray
vaind Apr 5, 2023
c06ab79
rename OnTransactionStart to Start
vaind Apr 5, 2023
014da60
rename OnTransactionFinish to Finish
vaind Apr 5, 2023
3f0de3b
rename Collect to CollectAsync
vaind Apr 5, 2023
ff3ad6e
Update src/Sentry.Profiling/TraceLogProcessor.cs
vaind Apr 5, 2023
a324d76
cleanup
vaind Apr 5, 2023
fb18f63
WriteArrayIfNotEmpty<T>
vaind Apr 5, 2023
5ba8f31
don't send debug-meta images if empty
vaind Apr 5, 2023
a412953
more review changes
vaind Apr 5, 2023
c76122f
more review changes
vaind Apr 5, 2023
44c5f01
profiler - use internal stopwatch
vaind Apr 12, 2023
a677a84
review changes
vaind Apr 12, 2023
221dcdc
Merge branch 'main' into feat/profiling
vaind Apr 13, 2023
863a4a9
review changes
vaind Apr 13, 2023
864c013
don't add empty envelope items in case of an error
vaind Apr 13, 2023
c9888ff
Update src/Sentry.Profiling/SamplingTransactionProfiler.cs
vaind Apr 13, 2023
25a4482
test throwing envelope item
vaind Apr 13, 2023
edf1be0
changelog & fixes
vaind Apr 13, 2023
23388d0
attribution.txt
vaind Apr 13, 2023
0fd1735
fix samplerprofile
vaind Apr 13, 2023
175300f
Merge branch 'main' into feat/profiling
vaind Apr 13, 2023
9f89b92
Workaround STJ bug
mattjohnsonpint Apr 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Initial work to support profiling in a future release. ([#2206](https://github.com/getsentry/sentry-dotnet/pull/2206))

### Fixes

- Buffer payloads asynchronously when appropriate ([#2297](https://github.com/getsentry/sentry-dotnet/pull/2297))
Expand Down
21 changes: 21 additions & 0 deletions Sentry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AspNetCore.TestUtils
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Android.AssemblyReader", "src\Sentry.Android.AssemblyReader\Sentry.Android.AssemblyReader.csproj", "{ED5E4F7E-8267-4F3C-BD2A-779AC9BF3D7C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Profiling", "src\Sentry.Profiling\Sentry.Profiling.csproj", "{BD6CEF44-E05E-4C22-8D2F-0558A93DD2D6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Profiling.Tests", "test\Sentry.Profiling.Tests\Sentry.Profiling.Tests.csproj", "{2E750A7C-561D-4959-B967-042755139D84}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Samples.Console.Profiling", "samples\Sentry.Samples.Console.Profiling\Sentry.Samples.Console.Profiling.csproj", "{0F84C0BB-FDD4-43A9-B594-923EB10C8E3F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -415,6 +421,18 @@ Global
{ED5E4F7E-8267-4F3C-BD2A-779AC9BF3D7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED5E4F7E-8267-4F3C-BD2A-779AC9BF3D7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED5E4F7E-8267-4F3C-BD2A-779AC9BF3D7C}.Release|Any CPU.Build.0 = Release|Any CPU
{BD6CEF44-E05E-4C22-8D2F-0558A93DD2D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD6CEF44-E05E-4C22-8D2F-0558A93DD2D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD6CEF44-E05E-4C22-8D2F-0558A93DD2D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD6CEF44-E05E-4C22-8D2F-0558A93DD2D6}.Release|Any CPU.Build.0 = Release|Any CPU
{2E750A7C-561D-4959-B967-042755139D84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E750A7C-561D-4959-B967-042755139D84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E750A7C-561D-4959-B967-042755139D84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E750A7C-561D-4959-B967-042755139D84}.Release|Any CPU.Build.0 = Release|Any CPU
{0F84C0BB-FDD4-43A9-B594-923EB10C8E3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F84C0BB-FDD4-43A9-B594-923EB10C8E3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F84C0BB-FDD4-43A9-B594-923EB10C8E3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F84C0BB-FDD4-43A9-B594-923EB10C8E3F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -481,6 +499,9 @@ Global
{5E12E053-22AF-4184-8581-3FCFD225617D} = {83263231-1A2A-4733-B759-EEFF14E8C5D5}
{C96CB65D-3D2D-404E-85C0-69A3FC03B48F} = {83263231-1A2A-4733-B759-EEFF14E8C5D5}
{ED5E4F7E-8267-4F3C-BD2A-779AC9BF3D7C} = {AF6AF4C7-8AA2-4D59-8064-2D79560904EB}
{BD6CEF44-E05E-4C22-8D2F-0558A93DD2D6} = {AF6AF4C7-8AA2-4D59-8064-2D79560904EB}
{2E750A7C-561D-4959-B967-042755139D84} = {83263231-1A2A-4733-B759-EEFF14E8C5D5}
{0F84C0BB-FDD4-43A9-B594-923EB10C8E3F} = {77454495-55EE-4B40-A089-71B9E8F82E89}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0C652B1A-DF72-4EE5-A98B-194FE2C054F6}
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Sentry.Benchmarks/StackFrameBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public void ConfigureAppFrame()
Module ="Sentry.Extensions.Profiling"
},
new SentryStackFrame() {
Function ="SamplingTransactionProfiler.OnTransactionStart(class Sentry.ITransaction) {QuickJitted}",
Function ="SamplingTransactionProfiler.Start(class Sentry.ITransaction) {QuickJitted}",
Module ="Sentry.Extensions.Profiling"
},
new SentryStackFrame() {
Expand Down
323 changes: 323 additions & 0 deletions samples/Sentry.Samples.Console.Profiling/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
using System.Reflection;
using System.Xml.Xsl;
using Sentry;
using Sentry.Extensibility;
using Sentry.Profiling;

// One of the ways to set your DSN is via an attribute:
// It could be set via AssemblyInfo.cs and patched via CI.
// Other ways are via environment variable, configuration files and explicitly via parameter to Init
[assembly: Dsn(Program.DefaultDsn)]
// Tracks the release which sent the event and enables more features: https://docs.sentry.io/learn/releases/
// Much like the DSN above, this is only one of the ways to define the release.
// If not set here, it can also be defined via appsettings.json, environment variable 'SENTRY_RELEASE' and AssemblyVersion
// STANDARD_CI_SOURCE_REVISION_ID -> TeamCity: %build.vcs.number%, VSTS: BUILD_SOURCEVERSION, Travis-CI: TRAVIS_COMMIT, AppVeyor: APPVEYOR_REPO_COMMIT, CircleCI: CIRCLE_SHA1
[assembly: AssemblyInformationalVersion("e386dfd")]

internal static class Program
{
public const string DefaultDsn = "https://[email protected]/5428537";
// A different DSN for a section of the app (i.e: admin)
public const string AdminDsn = "https://[email protected]/259314";

private static async Task Main()
{
// When the SDK is disabled, no callback is executed:
await SentrySdk.ConfigureScopeAsync(async scope =>
{
// Never executed:
// This could be any async I/O operation, like a DB query
await Task.Yield();
scope.SetExtra("Key", "Value");
});

// Enable the SDK
using (SentrySdk.Init(o =>
{
// Send stack trace for events that were not created from an exception
// e.g: CaptureMessage, log.LogDebug, log.LogInformation ...
o.AttachStacktrace = true;

// Sentry won't consider code from namespace LibraryX.* as part of the app code and will hide it from the stacktrace by default
// To see the lines from non `AppCode`, select `Full`. Will include non App code like System.*, Microsoft.* and LibraryX.*
o.AddInAppExclude("LibraryX.");

// Before excluding all prefixed 'LibraryX.', any stack trace from a type namespaced 'LibraryX.Core' will be considered InApp.
o.AddInAppInclude("LibraryX.Core");

// Send personal identifiable information like the username logged on to the computer and machine name
o.SendDefaultPii = true;

// To enable event sampling, uncomment:
// o.SampleRate = 0.5f; // Randomly drop (don't send to Sentry) half of events

// Modifications to event before it goes out. Could replace the event altogether
o.BeforeSend = @event =>
{
// Drop an event altogether:
if (@event.Tags.ContainsKey("SomeTag"))
{
return null;
}

return @event;
};

// Allows inspecting and modifying, returning a new or simply rejecting (returning null)
o.BeforeBreadcrumb = crumb =>
{
// Don't add breadcrumbs with message containing:
if (crumb.Message?.Contains("bad breadcrumb") == true)
{
return null;
}

return crumb;
};

// Ignore exception by its type:
o.AddExceptionFilterForType<XsltCompileException>();

// Configure the background worker which sends events to sentry:
// Wait up to 5 seconds before shutdown while there are events to send.
o.ShutdownTimeout = TimeSpan.FromSeconds(5);

// Enable SDK logging with Debug level
o.Debug = true;
// To change the verbosity, use:
// o.DiagnosticLevel = SentryLevel.Info;
// To use a custom logger:
// o.DiagnosticLogger = ...

// Using a proxy:
o.HttpProxy = null; //new WebProxy("https://localhost:3128");

// Example customizing the HttpClientHandlers created
o.CreateHttpClientHandler = () => new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, certificate, _, _) =>
!certificate.Archived
};

// Access to the HttpClient created to serve the SentryClint
o.ConfigureClient = client => client.DefaultRequestHeaders.TryAddWithoutValidation("CustomHeader", new[] { "my value" });

// Control/override how to apply the State object into the scope
o.SentryScopeStateProcessor = new MyCustomerScopeStateProcessor();

o.TracesSampleRate = 1.0;

o.AddIntegration(new ProfilingIntegration(Path.GetTempPath()));
}))
{
var tx = SentrySdk.StartTransaction("app", "run");
// Ignored by its type due to the setting above
SentrySdk.CaptureException(new XsltCompileException());

SentrySdk.AddBreadcrumb(
"A 'bad breadcrumb' that will be rejected because of 'BeforeBreadcrumb callback above.'");

// Data added to the root scope (no PushScope called up to this point)
// The modifications done here will affect all events sent and will propagate to child scopes.
await SentrySdk.ConfigureScopeAsync(async scope =>
{
scope.AddEventProcessor(new SomeEventProcessor());
scope.AddExceptionProcessor(new ArgumentExceptionProcessor());

// This could be any async I/O operation, like a DB query
await Task.Yield();
scope.SetExtra("SomeExtraInfo",
new
{
Data = "Value fetched asynchronously",
ManaLevel = 199
});
});

// Configures a scope which is only valid within the callback
SentrySdk.CaptureMessage("Fatal message!", s =>
{
s.Level = SentryLevel.Fatal;
s.TransactionName = "main";
s.Environment = "SpecialEnvironment";

// Add a file attachment for upload
s.AddAttachment(typeof(Program).Assembly.Location);
});

FindPrimeNumber(100000);
var eventId = SentrySdk.CaptureMessage("Some warning!", SentryLevel.Warning);

// Send an user feedback linked to the warning.
var timestamp = DateTime.Now.Ticks;
var user = $"user{timestamp}";
var email = $"user{timestamp}@user{timestamp}.com";

SentrySdk.CaptureUserFeedback(new UserFeedback(eventId, user, email, "this is a sample user feedback"));

var error = new Exception("Attempting to send this multiple times");

// Only the first capture will be sent to Sentry
for (var i = 0; i < 3; i++)
{
// The SDK is able to detect duplicate events:
// This is useful, for example, when multiple loggers log the same exception. Or exception is re-thrown and recaptured.
SentrySdk.CaptureException(error);
}

var count = 10;
for (var i = 0; i < count; i++)
{
const string msg = "{0} of {1} items we'll wait to flush to Sentry!";
SentrySdk.CaptureEvent(new SentryEvent
{
Message = new SentryMessage
{
Message = msg,
Formatted = string.Format(msg, i, count)
},
Level = SentryLevel.Debug
});

FindPrimeNumber(100000);
}
// Console output will show queue being flushed.
await SentrySdk.FlushAsync();

// -------------------------

// A custom made client, that could be registered with DI,
// would get disposed by the container on app shutdown

var evt = new SentryEvent
{
Message = "Starting new client"
};
evt.AddBreadcrumb("Breadcrumb directly to the event");
evt.User.Username = "some@user";
// Group all events with the following fingerprint:
evt.SetFingerprint("NewClientDebug");
evt.Level = SentryLevel.Debug;
SentrySdk.CaptureEvent(evt);

// Using a different DSN:
using (var adminClient = new SentryClient(new SentryOptions { Dsn = AdminDsn }))
{
// Make believe web framework middleware
var middleware = new AdminPartMiddleware(adminClient, null);
var request = new { Path = "/admin" }; // made up request
middleware.Invoke(request);
} // Dispose the client which flushes any queued events

SentrySdk.CaptureException(
new Exception("Error outside of the admin section: Goes to the default DSN"));

tx.Finish();
} // On Dispose: SDK closed, events queued are flushed/sent to Sentry
}

private static long FindPrimeNumber(int n)
{
int count = 0;
long a = 2;
while (count < n)
{
long b = 2;
int prime = 1;// to check if found a prime
while (b * b <= a)
{
if (a % b == 0)
{
prime = 0;
break;
}
b++;
}
if (prime > 0)
{
count++;
}
a++;
}
return (--a);
}

private class AdminPartMiddleware
{
private readonly ISentryClient _adminClient;
private readonly dynamic _middleware;

public AdminPartMiddleware(ISentryClient adminClient, dynamic middleware)
{
_adminClient = adminClient;
_middleware = middleware;
}

public void Invoke(dynamic request)
{
using (SentrySdk.PushScope(new SpecialContextObject()))
{
SentrySdk.AddBreadcrumb(request.Path, "request-path");

// Change the SentryClient in case the request is to the admin part:
if (request.Path.StartsWith("/admin"))
{
// Within this scope, the _adminClient will be used instead of whatever
// client was defined before this point:
SentrySdk.BindClient(_adminClient);
}

SentrySdk.CaptureException(new Exception("Error at the admin section"));
// Else it uses the default client

_middleware?.Invoke(request);
} // Scope is disposed.
}
}

private class SomeEventProcessor : ISentryEventProcessor
{
public SentryEvent Process(SentryEvent @event)
{
// Here you can modify the event as you need
if (@event.Level > SentryLevel.Info)
{
@event.AddBreadcrumb("Processed by " + nameof(SomeEventProcessor));
}

return @event;
}
}

private class ArgumentExceptionProcessor : SentryEventExceptionProcessor<ArgumentException>
{
protected override void ProcessException(ArgumentException exception, SentryEvent sentryEvent)
{
// Handle specific types of exceptions and add more data to the event
sentryEvent.SetTag("parameter-name", exception.ParamName);
}
}

private class MyCustomerScopeStateProcessor : ISentryScopeStateProcessor
{
private readonly ISentryScopeStateProcessor _fallback = new DefaultSentryScopeStateProcessor();

public void Apply(Scope scope, object state)
{
if (state is SpecialContextObject specialState)
{
scope.SetTag("SpecialContextObject", specialState.A + specialState.B);
}
else
{
_fallback.Apply(scope, state);
}
}
}

private class SpecialContextObject
{
public string A { get; } = "hello";
public string B { get; } = "world";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Sentry\Sentry.csproj" />
<ProjectReference Include="..\..\src\Sentry.Profiling\Sentry.Profiling.csproj" />
</ItemGroup>
</Project>
Loading