Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<SentryVersion>6.0.0</SentryVersion>
<SentryVersion>6.1.0-alpha.1</SentryVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Sentry" Version="$(SentryVersion)" />
Expand Down
1 change: 1 addition & 0 deletions src/SymbolCollector.Android.Library/Host.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static IHost Init(Context context, string dsn, string? sentryTrace = null
{
o.CaptureFailedRequests = true;
o.EnableLogs = true;
o.Experimental.EnableMetrics = true;

This comment was marked as outdated.


o.SetBeforeSend(@event =>
{
Expand Down
3 changes: 2 additions & 1 deletion src/SymbolCollector.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace SymbolCollector.Console;

internal class Program
{
private static readonly ClientMetrics Metrics = new ClientMetrics();
private static readonly SentryClientMetrics Metrics = new SentryClientMetrics();

static async Task<int> Main(
string? upload = null,
Expand Down Expand Up @@ -221,6 +221,7 @@ private static void Bootstrap(Args args)
o.IsGlobalModeEnabled = true;
o.CaptureFailedRequests = true;
o.EnableLogs = true;
o.Experimental.EnableMetrics = true;

#if DEBUG
o.Environment = "development";
Expand Down
28 changes: 14 additions & 14 deletions src/SymbolCollector.Core/ClientMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,72 +40,72 @@ public class ClientMetrics : IClientMetrics
public int DirectoryDoesNotExistCount => _directoryDoesNotExistCount;
public int FileDoesNotExistCount => _fileDoesNotExistCount;

public void FileProcessed()
public virtual void FileProcessed()
{
Interlocked.Increment(ref _filesProcessedCount);
}

public void MachOFileFound()
public virtual void MachOFileFound()
{
Interlocked.Increment(ref _machOFileFoundCount);
}

public void ElfFileFound()
public virtual void ElfFileFound()
{
Interlocked.Increment(ref _elfFileFoundCount);
}

public void FatMachOFileFound()
public virtual void FatMachOFileFound()
{
Interlocked.Increment(ref _fatMachOFileFoundCount);
}

public void FailedToUpload()
public virtual void FailedToUpload()
{
Interlocked.Increment(ref _failedToUploadCount);
}

public void FailedToParse()
public virtual void FailedToParse()
{
Interlocked.Increment(ref _failedToParse);
}

public void SuccessfulUpload()
public virtual void SuccessfulUpload()
{
Interlocked.Increment(ref _successfullyUploadCount);
}

public void AlreadyExisted()
public virtual void AlreadyExisted()
{
Interlocked.Increment(ref _alreadyExistedCount);
}

public void JobsInFlightRemove(int tasksCount)
public virtual void JobsInFlightRemove(int tasksCount)
{
Interlocked.Add(ref _jobsInFlightCount, -tasksCount);
}

public void JobsInFlightAdd(int tasksCount)
public virtual void JobsInFlightAdd(int tasksCount)
{
Interlocked.Add(ref _jobsInFlightCount, tasksCount);
}

public void UploadedBytesAdd(long bytes)
public virtual void UploadedBytesAdd(long bytes)
{
Interlocked.Add(ref _uploadedBytesCount, bytes);
}

public void FileOrDirectoryUnauthorizedAccess()
public virtual void FileOrDirectoryUnauthorizedAccess()
{
Interlocked.Increment(ref _fileOrDirectoryUnauthorizedAccessCount);
}

public void DirectoryDoesNotExist()
public virtual void DirectoryDoesNotExist()
{
Interlocked.Increment(ref _directoryDoesNotExistCount);
}

public void FileDoesNotExist()
public virtual void FileDoesNotExist()
{
Interlocked.Increment(ref _fileDoesNotExistCount);
}
Expand Down
171 changes: 171 additions & 0 deletions src/SymbolCollector.Core/SentryClientMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#pragma warning disable SENTRYTRACECONNECTEDMETRICS // IHub.Metrics is experimental
using Sentry;
using Sentry.Extensibility;

namespace SymbolCollector.Core;

/// <summary>
/// A decorator for <see cref="ClientMetrics"/> that also emits metrics to Sentry's trace-connected metrics API.
/// </summary>
/// <remarks>
/// This integrates with Sentry SDK 6.1.0's new experimental trace-connected metrics feature.
/// Metrics are attached to the current trace/span for correlation in Sentry's UI.
/// Enable with <c>options.Experimental.EnableMetrics = true</c>.
/// </remarks>
public class SentryClientMetrics : ClientMetrics
{
private readonly IHub _hub;

/// <summary>
/// Creates a new instance using the default <see cref="HubAdapter.Instance"/>.
/// </summary>
public SentryClientMetrics() : this(HubAdapter.Instance)
{
}

/// <summary>
/// Creates a new instance with the specified hub for metrics emission.
/// </summary>
/// <param name="hub">The Sentry hub to use for emitting metrics.</param>
public SentryClientMetrics(IHub hub)
{
_hub = hub;
}

private SentryTraceMetrics Metrics => _hub.Metrics;

/// <summary>
/// Records a file processed event, incrementing both local and Sentry counters.
/// </summary>
public override void FileProcessed()
{
base.FileProcessed();
Metrics.AddCounter("symbol_collector.files_processed", 1);
}

/// <summary>
/// Records a Mach-O file discovery.
/// </summary>
public override void MachOFileFound()
{
base.MachOFileFound();
Metrics.AddCounter("symbol_collector.debug_images_found", 1,
[new KeyValuePair<string, object>("format", "macho")]);
}

/// <summary>
/// Records an ELF file discovery.
/// </summary>
public override void ElfFileFound()
{
base.ElfFileFound();
Metrics.AddCounter("symbol_collector.debug_images_found", 1,
[new KeyValuePair<string, object>("format", "elf")]);
}

/// <summary>
/// Records a Fat Mach-O file discovery.
/// </summary>
public override void FatMachOFileFound()
{
base.FatMachOFileFound();
Metrics.AddCounter("symbol_collector.debug_images_found", 1,
[new KeyValuePair<string, object>("format", "fat_macho")]);
}

/// <summary>
/// Records a failed upload.
/// </summary>
public override void FailedToUpload()
{
base.FailedToUpload();
Metrics.AddCounter("symbol_collector.uploads", 1,
[new KeyValuePair<string, object>("status", "failed")]);
}

/// <summary>
/// Records a parse failure.
/// </summary>
public override void FailedToParse()
{
base.FailedToParse();
Metrics.AddCounter("symbol_collector.parse_failures", 1);
}

/// <summary>
/// Records a successful upload.
/// </summary>
public override void SuccessfulUpload()
{
base.SuccessfulUpload();
Metrics.AddCounter("symbol_collector.uploads", 1,
[new KeyValuePair<string, object>("status", "success")]);
}

/// <summary>
/// Records when a file already existed on the server.
/// </summary>
public override void AlreadyExisted()
{
base.AlreadyExisted();
Metrics.AddCounter("symbol_collector.uploads", 1,
[new KeyValuePair<string, object>("status", "already_exists")]);
}

/// <summary>
/// Removes jobs from the in-flight count.
/// </summary>
public override void JobsInFlightRemove(int tasksCount)
{
base.JobsInFlightRemove(tasksCount);
Metrics.RecordGauge("symbol_collector.jobs_in_flight", JobsInFlightCount);
}

/// <summary>
/// Adds jobs to the in-flight count.
/// </summary>
public override void JobsInFlightAdd(int tasksCount)
{
base.JobsInFlightAdd(tasksCount);
Metrics.RecordGauge("symbol_collector.jobs_in_flight", JobsInFlightCount);
}

/// <summary>
/// Records bytes uploaded, emitting as a distribution for percentile analysis.
/// </summary>
public override void UploadedBytesAdd(long bytes)
{
base.UploadedBytesAdd(bytes);
Metrics.RecordDistribution("symbol_collector.uploaded_bytes", bytes, "byte");
}

/// <summary>
/// Records an unauthorized access error.
/// </summary>
public override void FileOrDirectoryUnauthorizedAccess()
{
base.FileOrDirectoryUnauthorizedAccess();
Metrics.AddCounter("symbol_collector.access_errors", 1,
[new KeyValuePair<string, object>("type", "unauthorized")]);
}

/// <summary>
/// Records a directory not found error.
/// </summary>
public override void DirectoryDoesNotExist()
{
base.DirectoryDoesNotExist();
Metrics.AddCounter("symbol_collector.access_errors", 1,
[new KeyValuePair<string, object>("type", "directory_not_found")]);
}

/// <summary>
/// Records a file not found error.
/// </summary>
public override void FileDoesNotExist()
{
base.FileDoesNotExist();
Metrics.AddCounter("symbol_collector.access_errors", 1,
[new KeyValuePair<string, object>("type", "file_not_found")]);
}
}
4 changes: 2 additions & 2 deletions src/SymbolCollector.Core/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ private static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Client>();
services.AddSingleton<ObjectFileParser>();
services.AddSingleton<ClientMetrics>();
services.AddSingleton<SentryClientMetrics>();
services.AddSingleton<ClientMetrics>(sp => sp.GetRequiredService<SentryClientMetrics>());
services.AddSingleton<FatBinaryReader>();
services.AddSingleton<ClientMetrics>();

services.AddOptions<SymbolClientOptions>()
.Configure<IConfiguration>((o, f) => f.Bind("SymbolClient", o))
Expand Down
1 change: 1 addition & 0 deletions src/SymbolCollector.Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ private static IHostBuilder CreateHostBuilder(string[] args) =>
o.MinimumBreadcrumbLevel = LogLevel.Debug;
o.CaptureFailedRequests = true;
o.EnableLogs = true;
o.Experimental.EnableMetrics = true;

// https://github.com/getsentry/symbol-collector/issues/205
// o.CaptureBlockingCalls = true;
Expand Down
3 changes: 2 additions & 1 deletion src/SymbolCollector.Server/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public void ConfigureServices(IServiceCollection services)

services.AddSingleton<ObjectFileParser>();
services.AddSingleton<FatBinaryReader>();
services.AddSingleton<ClientMetrics>();
services.AddSingleton<SentryClientMetrics>();
services.AddSingleton<ClientMetrics>(sp => sp.GetRequiredService<SentryClientMetrics>());
services.AddSingleton<IBatchFinalizer, SymsorterBatchFinalizer>();
services.AddSingleton<ISymbolGcsWriter, SymbolGcsWriter>();
services.AddSingleton<IStorageClientFactory, StorageClientFactory>();
Expand Down