Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
162ea2a
prepare traceevent integration
vaind Apr 23, 2023
68bafaa
wip: in-memory tracelog
vaind May 4, 2023
f6657f5
in-memory tracelog support
vaind May 5, 2023
9f8727f
profiler - remove temp dir
vaind May 8, 2023
3833b5f
tracelog streaming
vaind May 8, 2023
ad5e21f
fix sample collection
vaind May 9, 2023
1510734
migrate downsampling & fix tests
vaind May 9, 2023
886a2e9
fix: SparseArray
vaind May 10, 2023
2b70a79
update benchmark & profile builder
vaind May 10, 2023
ba3b4e1
minor changes rundown issue investigation
vaind May 11, 2023
079a72c
Merge branch 'main' into feat/streaming-tracelog
vaind May 18, 2023
25592ba
separate rundown session during tracelog init
vaind May 19, 2023
aa3ff4d
fixes
vaind May 20, 2023
3f7b063
Merge branch 'main' into feat/streaming-tracelog
vaind May 22, 2023
9a780eb
update profiling benchmark results
vaind May 22, 2023
61d2718
Merge branch 'main' into feat/streaming-tracelog
vaind May 22, 2023
cc6119f
update tests
vaind May 23, 2023
9005697
Merge branch 'main' into feat/streaming-tracelog
vaind May 23, 2023
f88a20d
update tests
vaind May 23, 2023
f864e7c
fixes
vaind May 23, 2023
04d38a5
update tests to support streaming tracelog
vaind May 30, 2023
8b3691d
Merge branch 'main' into feat/streaming-tracelog
vaind May 30, 2023
9b51611
Merge branch 'main' into feat/streaming-tracelog
vaind Jun 4, 2023
d171989
Merge branch 'main' into feat/streaming-tracelog
vaind Aug 4, 2023
c7f6929
update to latest perfview
vaind Aug 4, 2023
4ffb45a
submodule perfview sentry fork
bruno-garcia Aug 14, 2023
3a7a10f
Merge branch 'main' into chore/integrate-traceevent
bitsandfoxes Aug 16, 2023
5b159e1
fix test code
vaind Aug 16, 2023
f457e3a
Merge branch 'main' into feat/streaming-tracelog
vaind Aug 16, 2023
abc81db
Merge branch 'feat/streaming-tracelog' into chore/integrate-traceevent
vaind Aug 16, 2023
b4f9485
script & project updates
vaind Aug 16, 2023
5ff8f1a
profiling in core sln
bruno-garcia Aug 19, 2023
0bbb0bd
Merge branch 'chore/integrate-traceevent' into feat/profiling-low-ove…
bruno-garcia Aug 19, 2023
aee692b
perfview sampling
bruno-garcia Aug 20, 2023
3376bea
perfview under profiling
bruno-garcia Aug 20, 2023
6e2ae7c
remove profiling from sln/slnf
bruno-garcia Aug 20, 2023
790d774
internal perfview
bruno-garcia Aug 20, 2023
b346567
ignore build files from submodule perfview
bruno-garcia Aug 20, 2023
6cb6cd1
fix structure
bruno-garcia Aug 20, 2023
19e24a2
resolve errors
bruno-garcia Aug 20, 2023
1c98746
builds!
bruno-garcia Aug 20, 2023
40b6add
in SentryCore sln
bruno-garcia Aug 20, 2023
43f78a0
gitignore on module
bruno-garcia Aug 20, 2023
53715f4
set correct platform to profile
bruno-garcia Aug 20, 2023
67ef890
log on collection failure
bruno-garcia Aug 20, 2023
915a0f2
sample
bruno-garcia Aug 20, 2023
791cdc6
fixed sln file with all but mobile
bruno-garcia Aug 20, 2023
bec4641
ignore obj
bruno-garcia Aug 20, 2023
3a05994
fix benchmarks. Package it
bruno-garcia Aug 20, 2023
f1ea1ec
fix tfm in script
bruno-garcia Aug 20, 2023
7c7404d
verify
bruno-garcia Aug 20, 2023
20286f4
new full sln file
bruno-garcia Aug 20, 2023
aa01035
pack profiling
bruno-garcia Aug 20, 2023
b5cccee
add profile category
bruno-garcia Aug 20, 2023
1a593bc
fuck this slnf mess
bruno-garcia Aug 21, 2023
eaa320b
debugging on sample
bruno-garcia Aug 21, 2023
833a319
Merge branch 'main' into feat/profiling-low-overhead
vaind Aug 21, 2023
6a1df09
timeout update
vaind Aug 21, 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
Prev Previous commit
Next Next commit
fixes
  • Loading branch information
vaind committed May 20, 2023
commit aa3ff4d5afc3dca17acc2e85cce0e9657f261fb0
12 changes: 6 additions & 6 deletions benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class ProfilingBenchmarks
public void StartProfiler()
{
_factory = SamplingTransactionProfilerFactory.Create(new());
_profiler = _factory.Start(new TransactionTracer(_hub, "", ""), CancellationToken.None);
}

[GlobalCleanup(Targets = new string[] { nameof(Transaction), nameof(DoHardWorkWhileProfiling) })]
Expand All @@ -36,24 +35,24 @@ public IEnumerable<object[]> TransactionBenchmarkArguments()
{
foreach (var runtimeMs in new[] { 25, 100, 1000, 10000 })
{
foreach (var processing in new[] { true, false })
foreach (var collect in new[] { true, false })
{
yield return new object[] { runtimeMs, processing };
yield return new object[] { runtimeMs, collect };
}
}
}

// Run a profiled transaction. Profiler starts and stops for each transaction separately.
[Benchmark]
[ArgumentsSource(nameof(TransactionBenchmarkArguments))]
public long Transaction(int runtimeMs, bool processing)
public long Transaction(int runtimeMs, bool collect)
{
var tt = new TransactionTracer(_hub, "test", "");
tt.TransactionProfiler = _factory.Start(tt, CancellationToken.None);
var result = RunForMs(runtimeMs);
tt.TransactionProfiler?.Finish();
tt.TransactionProfiler.Finish();
var transaction = new Transaction(tt);
if (processing)
if (collect)
{
var collectTask = tt.TransactionProfiler.CollectAsync(transaction);
collectTask.Wait();
Expand Down Expand Up @@ -175,6 +174,7 @@ public long DoHardWork(int n)
[ArgumentsSource(nameof(OverheadRunArguments))]
public long DoHardWorkWhileProfiling(int n)
{
_profiler ??= _factory.Start(new TransactionTracer(_hub, "", ""), CancellationToken.None);
return FindPrimeNumber(n);
}
#endregion
Expand Down
274 changes: 15 additions & 259 deletions samples/Sentry.Samples.Console.Profiling/Program.cs
Original file line number Diff line number Diff line change
@@ -1,210 +1,45 @@
using System.Reflection;
using System.Xml.Xsl;
using Sentry;
using Sentry.Extensibility;
using Sentry.Profiling;

internal static class Program
{
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 =>
using (SentrySdk.Init(options =>
{
// A Sentry Data Source Name (DSN) is required.
// See https://docs.sentry.io/product/sentry-basics/dsn-explainer/
// You can set it in the SENTRY_DSN environment variable, or you can set it in code here.
// o.Dsn = "... Your DSN ...";

// 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.SetBeforeSend((@event, _) =>
{
// Drop an event altogether:
if (@event.Tags.ContainsKey("SomeTag"))
{
return null;
}

return @event;
}
);
// options.Dsn = "... Your DSN ...";

// Allows inspecting and modifying, returning a new or simply rejecting (returning null)
o.SetBeforeBreadcrumb((crumb, _) =>
{
// Don't add breadcrumbs with message containing:
if (crumb.Message?.Contains("bad breadcrumb") == true)
{
return null;
}
// When debug is enabled, the Sentry client will emit detailed debugging information to the console.
// This might be helpful, or might interfere with the normal operation of your application.
// We enable it here for demonstration purposes.
// You should not do this in your applications unless you are troubleshooting issues with Sentry.
options.Debug = true;

return crumb;
});
// This option is recommended, which enables Sentry's "Release Health" feature.
options.AutoSessionTracking = true;

// Ignore exception by its type:
o.AddExceptionFilterForType<XsltCompileException>();
// This option is recommended for client applications only. It ensures all threads use the same global scope.
// If you are writing a background service of any kind, you should remove this.
options.IsGlobalModeEnabled = true;

// 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);
// This option will enable Sentry's tracing features. You still need to start transactions and spans.
options.EnableTracing = true;

// 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()));
options.AddIntegration(new ProfilingIntegration());
}))
{
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 for a section of the app (i.e: admin)
const string AdminDsn = "https://[email protected]/259314";
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
}
Expand Down Expand Up @@ -234,83 +69,4 @@ private static long FindPrimeNumber(int n)
}
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";
}
}
10 changes: 9 additions & 1 deletion src/Sentry.Profiling/SamplingTransactionProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ private bool Stop(double? endTimeMs = null)
{
_stopped = true;
_endTimeMs = endTimeMs.Value;
OnFinish?.Invoke();
return true;
}
}
Expand All @@ -68,7 +69,14 @@ private void OnThreadSample(TraceEvent data)
{
if (timestampMs <= _endTimeMs)
{
_processor.AddSample(data, timestampMs - _startTimeMs);
try
{
_processor.AddSample(data, timestampMs - _startTimeMs);
}
catch (Exception e)
{
_options.LogWarning("Failed to process a profile sample.", e);
}
}
else
{
Expand Down
10 changes: 0 additions & 10 deletions src/Sentry.Profiling/SentryProfiling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,6 @@ namespace Sentry.Profiling;
/// </summary>
public class ProfilingIntegration : ISdkIntegration
{
private string _tempDirectoryPath { get; set; }

/// <summary>
/// Initializes the the profiling integration.
/// </summary>
public ProfilingIntegration(string tempDirectoryPath)
{
_tempDirectoryPath = tempDirectoryPath;
}

/// <inheritdoc/>
public void Register(IHub hub, SentryOptions options)
{
Expand Down
Loading