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
61 changes: 57 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ permissions:
contents: write

jobs:
build-and-test:
name: Build, Test, and Benchmark
# 1. Первая работа: только сборка проекта
build:
name: Build Solution
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false || github.event_name == 'push'

Expand All @@ -38,10 +39,62 @@ jobs:
- name: Build solution
run: dotnet build --configuration Release --no-restore /p:TreatWarningsAsErrors=true

# Сохраняем скомпилированные файлы, чтобы не собирать их заново в других jobs
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: dotnet-build-artifacts
path: |
**/bin/Release/
**/obj/Release/
retention-days: 1

# 2. Вторая работа: запуск тестов (запустится только после успешной сборки)
test:
name: Run Tests
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

# Скачиваем скомпилированные файлы из шага Build
- name: Download Build Artifacts
uses: actions/download-artifact@v4
with:
name: dotnet-build-artifacts

- name: Run Tests
run: dotnet test --configuration Release --no-build --verbosity normal

# 3. Третья работа: запуск бенчмарков (запустится параллельно с тестами после сборки)
benchmark:
name: Run Benchmarks
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

# Скачиваем скомпилированные файлы из шага Build
- name: Download Build Artifacts
uses: actions/download-artifact@v4
with:
name: dotnet-build-artifacts

- name: Grant execute permissions
run: chmod +x src/EasyTrace.Benchmarks/bin/Release/net9.0/EasyTrace.Benchmarks

- name: Run Benchmarks
run: |
dotnet run --project src/EasyTrace.Benchmarks/EasyTrace.Benchmarks.csproj --configuration Release -- --filter '*'

dotnet run --project src/EasyTrace.Benchmarks/EasyTrace.Benchmarks.csproj --configuration Release --no-build -- --filter * --anyCategories AllowOnCI
70 changes: 51 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,64 @@
[![build](https://github.com/bugRanger/EasyTrace/actions/workflows/ci.yml/badge.svg)](https://github.com/bugRanger/EasyTrace/actions/workflows/ci.yml)

# EasyTrace

A lightweight project for tracing high-load systems.

## Description
The goal of the repository is to offer an alternative [System.Diagnostics.Activity](https://learn.microsoft.com/ru-ru/dotnet/api/system.diagnostics.activity?view=net-9.0) that is less expensive in terms of RAM and garbage collection load.

The goal of the repository is to offer an
alternative [System.Diagnostics.Activity](https://learn.microsoft.com/ru-ru/dotnet/api/system.diagnostics.activity?view=net-9.0)
that is less expensive in terms of RAM and garbage collection load.

### Benchmarks

```

BenchmarkDotNet v0.15.8, Windows 10 (10.0.19045.6456/22H2/2022Update)
AMD Ryzen 7 5700G with Radeon Graphics 3.80GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 10.0.103
[Host] : .NET 9.0.13 (9.0.13, 9.0.1326.6317), X64 RyuJIT x86-64-v3
DefaultJob : .NET 9.0.13 (9.0.13, 9.0.1326.6317), X64 RyuJIT x86-64-v3


| Method | Iterations | ParallelLimit | IsExporter | Mean | Error | StdDev | Median | Gen0 | Gen1 | Allocated |
|------------------- |----------- |-------------- |----------- |-------------:|-----------:|-----------:|-------------:|----------:|--------:|------------:|
| ActivitySource | 1000 | 4 | False | 45.394 us | 0.9058 us | 2.3054 us | 45.285 us | 0.2441 | - | 2.46 KB |
| TraceActivityScope | 1000 | 4 | False | 5.169 us | 0.0343 us | 0.0320 us | 5.181 us | 0.3052 | - | 2.5 KB |
| ActivitySource | 1000 | 4 | True | 698.665 us | 6.8543 us | 6.4115 us | 698.552 us | 588.8672 | 8.7891 | 4752.48 KB |
| TraceActivityScope | 1000 | 4 | True | 349.706 us | 6.9106 us | 15.8783 us | 341.561 us | - | - | 2.46 KB |
| ActivitySource | 10000 | 4 | False | 377.078 us | 7.3748 us | 9.8451 us | 374.799 us | - | - | 2.46 KB |
| TraceActivityScope | 10000 | 4 | False | 25.074 us | 0.4772 us | 0.4230 us | 25.096 us | 0.2747 | - | 2.45 KB |
| ActivitySource | 10000 | 4 | True | 7,855.249 us | 74.1372 us | 69.3480 us | 7,867.755 us | 5882.8125 | 46.8750 | 47502.54 KB |
| TraceActivityScope | 10000 | 4 | True | 3,491.807 us | 18.0259 us | 16.8614 us | 3,494.504 us | - | - | 2.52 KB |
AMD Ryzen 3 2200G with Radeon Vega Graphics 3.50GHz, 1 CPU, 4 logical and 4 physical cores
.NET SDK 10.0.302
[Host] : .NET 9.0.14 (9.0.14, 9.0.1426.11910), X64 RyuJIT x86-64-v3
DefaultJob : .NET 9.0.14 (9.0.14, 9.0.1426.11910), X64 RyuJIT x86-64-v3

```

| Method | Iterations | ParallelLimit | IsExporter | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------------------|------------|---------------|------------|----------------:|---------------:|---------------:|----------------:|--------:|--------------:|----------------:|----------------:|
| **ActivitySource** | **1000** | **4** | **False** | **1,780.34 μs** | **31.973 μs** | **28.343 μs** | **baseline** | **** | **2345.7031** | **4752.46 KB** | **** |
| TraceActivityScope | 1000 | 4 | False | 11.83 μs | 0.208 μs | 0.194 μs | 150.554x faster | 3.31x | 1.0681 | 2.16 KB | 2,205.036x less |
| | | | | | | | | | | | |
| **ActivitySource** | **1000** | **4** | **True** | **1,899.03 μs** | **37.965 μs** | **43.720 μs** | **baseline** | **** | **2343.7500** | **4752.46 KB** | **** |
| TraceActivityScope | 1000 | 4 | True | 1,839.84 μs | 35.923 μs | 53.769 μs | 1.03x faster | 0.04x | - | 3.73 KB | 1,273.290x less |
| | | | | | | | | | | | |
| **ActivitySource** | **1000** | **8** | **False** | **3,714.61 μs** | **72.337 μs** | **96.568 μs** | **baseline** | **** | **4683.5938** | **9502.8 KB** | **** |
| TraceActivityScope | 1000 | 8 | False | 17.41 μs | 0.246 μs | 0.205 μs | 213.402x faster | 5.95x | 1.1597 | 2.35 KB | 4,041.059x less |
| | | | | | | | | | | | |
| **ActivitySource** | **1000** | **8** | **True** | **4,131.53 μs** | **81.874 μs** | **114.776 μs** | **baseline** | **** | **4679.6875** | **9502.7 KB** | **** |
| TraceActivityScope | 1000 | 8 | True | 3,327.28 μs | 64.202 μs | 81.195 μs | 1.24x faster | 0.05x | - | 4.53 KB | 2,098.958x less |
| | | | | | | | | | | | |
| **ActivitySource** | **1000** | **16** | **False** | **7,640.13 μs** | **115.598 μs** | **102.475 μs** | **baseline** | **** | **9367.1875** | **19003.47 KB** | **** |
| TraceActivityScope | 1000 | 16 | False | 26.51 μs | 0.521 μs | 0.913 μs | 288.534x faster | 10.53x | 1.2817 | 2.64 KB | 7,209.911x less |
| | | | | | | | | | | | |
| **ActivitySource** | **1000** | **16** | **True** | **8,312.79 μs** | **161.504 μs** | **198.342 μs** | **baseline** | **** | **9359.3750** | **19003.05 KB** | **** |
| TraceActivityScope | 1000 | 16 | True | 6,747.51 μs | 114.312 μs | 106.928 μs | 1.23x faster | 0.03x | - | 6.88 KB | 2,762.510x less |

## 💡 Usage

Provide a quick example of how to use your code or run the application:

```csharp
var source = new TraceActivitySourceBuilder()
// set your resources.
.SetResources(new MyDefaultResources())
// add your resources.
.AddResources(new MyAdditionalResources())
// add intercept in your impl.
.AddInterceptor(new MyInterceptor())
// set batch processing for all exporters.
.SetBatchExportOptions(new BatchExportOptions())
// your impl for export.
// add export in OTLP (HTTP/1.1 + Protobuf)
.AddOtlpExporter(new HttpExportParameters())
// add export in your impl.
.AddExporter(new MyExport())
.Build("MySource");

Expand All @@ -47,14 +71,22 @@ using (var scope = source.Start())
```

## Authors

Contributors names and contact info

[@bugRanger](https://github.com/bugRanger)

## 🤝 Contributing

Contributions are welcome. Please fork the repository, create a feature branch, and submit a pull request.

## 💖 Acknowledgments

* A huge thanks to **[NetCoreServer](https://github.com/chronoxor/NetCoreServer)**, their library helped me achieve
maximum export efficiency 👍

## 📄 License
This project is licensed under the [MIT License](LICENSE-MIT)

This project is licensed under the [MIT License](LICENSE)


Binary file added docs/http2.pcapng
Binary file not shown.
3 changes: 2 additions & 1 deletion src/EasyTrace.Benchmarks/EasyTrace.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.8"/>
<PackageReference Include="OpenTelemetry" Version="1.18.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.18.0" />
</ItemGroup>
<ItemGroup>
<Content Include="bin\Release\net9.0\results\EasyTrace.Benchmarks.MemoryBenchmark-report-github.md" />
<Content Include="bin\Release\net9.0\results\EasyTrace.Benchmarks.MemoryBenchmark-report.html" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EasyTrace\EasyTrace.csproj" />
Expand Down
88 changes: 48 additions & 40 deletions src/EasyTrace.Benchmarks/MemoryBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
using System.Diagnostics;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using EasyTrace.Benchmarks.TestData;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace EasyTrace.Benchmarks;

[BenchmarkCategory("AllowOnCI")]
[MemoryDiagnoser]
[JsonExporterAttribute.Full]
[JsonExporterAttribute.FullCompressed]
Expand All @@ -15,53 +19,27 @@ public class MemoryBenchmark
public static void Run() => BenchmarkRunner.Run<MemoryBenchmark>();

private static ActivitySource? _activitySource;
private static TraceActivitySource? _traceActivitySource;
private static FakeProcessor? _activityProcessor;

private ActivityListener? _listener;
private ulong _activityCounter;
private static TraceActivitySource? _traceActivitySource;
private static FakeInterceptor? _traceActivityInterceptor;

[Params(1_000, 10_000)] public int Iterations { get; set; }
[Params(1_000)] public int Iterations { get; set; }

[Params(4)] public int ParallelLimit { get; set; }
[Params(4, 8, 16)] public int ParallelLimit { get; set; }

[Params(true, false)] public bool IsExporter { get; set; }

[GlobalSetup]
public void Setup()
{
_activitySource = new ActivitySource(nameof(MemoryBenchmark));
if (IsExporter)
{
_listener = new ActivityListener
{
ShouldListenTo = s => s.Name == nameof(MemoryBenchmark),
Sample = (ref ActivityCreationOptions<ActivityContext> _) =>
ActivitySamplingResult.AllData
};
_listener.ActivityStopped += _ => Interlocked.Increment(ref _activityCounter);
System.Diagnostics.ActivitySource.AddActivityListener(_listener);
}

var traceActivitySourceBuilder = new TraceActivitySourceBuilder();
if (IsExporter)
{
traceActivitySourceBuilder.AddExporter(new FakeExporter(() => Interlocked.Increment(ref _activityCounter)));
}

_traceActivitySource = traceActivitySourceBuilder.Build(nameof(MemoryBenchmark));
}

[GlobalCleanup]
public void Cleanup()
{
_listener?.Dispose();
SetupActivitySource();
SetupTraceActivitySource();
}

[Benchmark]
[Benchmark(Baseline = true)]
public ulong ActivitySource()
{
_activityCounter = 0;

Parallel.For(0,
ParallelLimit,
i =>
Expand All @@ -74,14 +52,12 @@ public ulong ActivitySource()
}
});

return Interlocked.Read(ref _activityCounter);
return _activityProcessor!.TotalEvents;
}

[Benchmark]
public ulong TraceActivityScope()
{
_activityCounter = 0;

Parallel.For(0,
ParallelLimit,
i =>
Expand All @@ -94,6 +70,38 @@ public ulong TraceActivityScope()
}
});

return Interlocked.Read(ref _activityCounter);
return _traceActivityInterceptor!.TotalEvents;
}

private void SetupActivitySource()
{
_activityProcessor = new FakeProcessor();
_activitySource = new ActivitySource(nameof(OtlpExportBenchmark));
var builder = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(ResourceBuilder.CreateDefault())
.AddSource(_activitySource.Name);

if (IsExporter)
{
// In their implementation, the processor acts as an exporter that listens to activity states.
builder.AddProcessor(_activityProcessor);
}

_ = builder.Build();
}

private void SetupTraceActivitySource()
{
_traceActivityInterceptor = new FakeInterceptor();

var builder = new TraceActivitySourceBuilder()
.AddInterceptor(_traceActivityInterceptor);

if (IsExporter)
{
builder.AddExporter(new FakeExporter());
}

_traceActivitySource = builder.Build(nameof(OtlpExportBenchmark));
}
}
Loading
Loading