Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce allocations in telemetry #1321

Merged
merged 5 commits into from
Jun 19, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Job=MediumRun Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=2 WarmupCount=10

```
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|--------------------------- |---------:|----------:|----------:|------:|-------:|----------:|------------:|
| ExecuteStrategyPipeline_V7 | 2.227 μs | 0.0077 μs | 0.0116 μs | 1.00 | 0.1106 | 2824 B | 1.00 |
| ExecuteStrategyPipeline_V8 | 1.750 μs | 0.0060 μs | 0.0084 μs | 0.79 | - | 40 B | 0.01 |
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|------------------------------------- |---------:|----------:|----------:|------:|-------:|----------:|------------:|
| ExecuteStrategyPipeline_V7 | 2.269 μs | 0.0136 μs | 0.0204 μs | 1.00 | 0.1106 | 2824 B | 1.00 |
| ExecuteStrategyPipeline_V8 | 1.861 μs | 0.0111 μs | 0.0155 μs | 0.82 | - | 40 B | 0.01 |
| ExecuteStrategyPipeline_Telemetry_V8 | 2.402 μs | 0.0104 μs | 0.0156 μs | 1.06 | - | 40 B | 0.01 |
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
``` ini

BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1702/22H2/2022Update/SunValley2), VM=Hyper-V
BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1848/22H2/2022Update/SunValley2), VM=Hyper-V
Intel Xeon Platinum 8370C CPU 2.80GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.304
[Host] : .NET 7.0.7 (7.0.723.27404), X64 RyuJIT AVX2
Expand All @@ -9,9 +9,9 @@ Job=MediumRun Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=2 WarmupCount=10

```
| Method | Telemetry | Enrichment | Mean | Error | StdDev | Gen0 | Allocated |
|-------- |---------- |----------- |------------:|---------:|----------:|-------:|----------:|
| **Execute** | **False** | **False** | **80.13 ns** | **0.324 ns** | **0.486 ns** | **-** | **-** |
| **Execute** | **False** | **True** | **74.33 ns** | **0.286 ns** | **0.392 ns** | **-** | **-** |
| **Execute** | **True** | **False** | **494.33 ns** | **3.973 ns** | **5.698 ns** | **0.0029** | **72 B** |
| **Execute** | **True** | **True** | **1,157.27 ns** | **7.130 ns** | **10.450 ns** | **0.0286** | **728 B** |
| Method | Telemetry | Enrichment | Mean | Error | StdDev | Allocated |
|-------- |---------- |----------- |------------:|---------:|---------:|----------:|
| **Execute** | **False** | **False** | **80.27 ns** | **1.992 ns** | **2.920 ns** | **-** |
| **Execute** | **False** | **True** | **79.68 ns** | **1.324 ns** | **1.982 ns** | **-** |
| **Execute** | **True** | **False** | **750.41 ns** | **4.875 ns** | **6.673 ns** | **-** |
| **Execute** | **True** | **True** | **1,034.73 ns** | **4.941 ns** | **7.242 ns** | **-** |
16 changes: 14 additions & 2 deletions bench/Polly.Core.Benchmarks/MultipleStrategiesBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
using System.Diagnostics.Metrics;

namespace Polly.Core.Benchmarks;

public class MultipleStrategiesBenchmark
{
private MeterListener? _meterListener;
private object? _strategyV7;
private object? _strategyV8;
private object? _strategyTelemetryV8;

[GlobalSetup]
public void Setup()
{
_strategyV7 = Helper.CreateStrategyPipeline(PollyVersion.V7);
_strategyV8 = Helper.CreateStrategyPipeline(PollyVersion.V8);
_meterListener = MeteringUtil.ListenPollyMetrics();
_strategyV7 = Helper.CreateStrategyPipeline(PollyVersion.V7, false);
_strategyV8 = Helper.CreateStrategyPipeline(PollyVersion.V8, false);
_strategyTelemetryV8 = Helper.CreateStrategyPipeline(PollyVersion.V8, true);
}

[GlobalCleanup]
public void Cleanup() => _meterListener?.Dispose();

[Benchmark(Baseline = true)]
public ValueTask ExecuteStrategyPipeline_V7() => _strategyV7!.ExecuteAsync(PollyVersion.V7);

[Benchmark]
public ValueTask ExecuteStrategyPipeline_V8() => _strategyV8!.ExecuteAsync(PollyVersion.V8);

[Benchmark]
public ValueTask ExecuteStrategyPipeline_Telemetry_V8() => _strategyTelemetryV8!.ExecuteAsync(PollyVersion.V8);
}
11 changes: 10 additions & 1 deletion bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.Metrics;
using Microsoft.Extensions.Logging.Abstractions;
using Polly.Extensions.Telemetry;
using Polly.Telemetry;
Expand All @@ -7,13 +8,22 @@ namespace Polly.Core.Benchmarks;
public class TelemetryBenchmark
{
private ResilienceStrategy? _strategy;
private MeterListener? _meterListener;

[GlobalSetup]
public void Prepare()
{
_strategy = Build(new ResilienceStrategyBuilder());

if (Telemetry)
{
_meterListener = MeteringUtil.ListenPollyMetrics();
}
}

[GlobalCleanup]
public void Cleanup() => _meterListener?.Dispose();

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

Expand Down Expand Up @@ -72,5 +82,4 @@ protected override ValueTask<Outcome<TResult>> ExecuteCoreAsync<TResult, TState>
return callback(context, state);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.Threading.RateLimiting;
using Microsoft.Extensions.Logging.Abstractions;

namespace Polly.Core.Benchmarks.Utils;

internal static partial class Helper
{
public static object CreateStrategyPipeline(PollyVersion technology) => technology switch
public static object CreateStrategyPipeline(PollyVersion technology, bool telemetry) => technology switch
{
PollyVersion.V7 => Policy.WrapAsync(
Policy.HandleResult(Failure).Or<InvalidOperationException>().AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(30), 10, TimeSpan.FromSeconds(5)),
Expand Down Expand Up @@ -40,6 +41,11 @@ internal static partial class Helper
_ => PredicateResult.False
}
});

if (telemetry)
{
builder.EnableTelemetry(NullLoggerFactory.Instance);
}
}),
_ => throw new NotSupportedException()
};
Expand Down
36 changes: 36 additions & 0 deletions bench/Polly.Core.Benchmarks/Utils/MeteringUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;

namespace Polly.Core.Benchmarks.Utils;

internal class MeteringUtil
{
public static MeterListener ListenPollyMetrics()
{
var meterListener = new MeterListener
{
InstrumentPublished = (instrument, listener) =>
{
if (instrument.Meter.Name is "Polly")
{
listener.EnableMeasurementEvents(instrument);
}
}
};

meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
meterListener.Start();

static void OnMeasurementRecorded<T>(
Instrument instrument,
T measurement,
ReadOnlySpan<KeyValuePair<string, object?>> tags,
object? state)
{
// do nothing
}

return meterListener;
}
}
12 changes: 10 additions & 2 deletions src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ public void Report<TArgs>(string eventName, ResilienceContext context, TArgs arg
return;
}

DiagnosticSource.Write(eventName, new TelemetryEventArguments(TelemetrySource, eventName, context, null, args!));
var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, eventName, context, null, args!);

DiagnosticSource.Write(eventName, telemetryArgs);

TelemetryEventArguments.Return(telemetryArgs);
}

/// <summary>
Expand All @@ -60,7 +64,11 @@ public void Report<TArgs, TResult>(string eventName, OutcomeArguments<TResult, T
return;
}

DiagnosticSource.Write(eventName, new TelemetryEventArguments(TelemetrySource, eventName, args.Context, args.Outcome.AsOutcome(), args.Arguments!));
var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, eventName, args.Context, args.Outcome.AsOutcome(), args.Arguments!);

DiagnosticSource.Write(eventName, telemetryArgs);

TelemetryEventArguments.Return(telemetryArgs);
}
}

33 changes: 33 additions & 0 deletions src/Polly.Core/Telemetry/TelemetryEventArguments.Pool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Polly.Telemetry;

public sealed partial record class TelemetryEventArguments
{
private static readonly ObjectPool<TelemetryEventArguments> Pool = new(() => new TelemetryEventArguments(), args =>
{
args.Source = null!;
args.EventName = null!;
args.Context = null!;
args.Outcome = default;
args.Arguments = null!;
});

internal static TelemetryEventArguments Get(
ResilienceTelemetrySource source,
string eventName,
ResilienceContext context,
Outcome<object>? outcome,
object arguments)
{
var args = Pool.Get();

args.Source = source;
args.EventName = eventName;
args.Context = context;
args.Outcome = outcome;
args.Arguments = arguments;

return args;
}

internal static void Return(TelemetryEventArguments args) => Pool.Return(args);
}
40 changes: 29 additions & 11 deletions src/Polly.Core/Telemetry/TelemetryEventArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,35 @@ namespace Polly.Telemetry;
/// <summary>
/// The arguments of the telemetry event.
/// </summary>
/// <param name="Source">The source of the event.</param>
/// <param name="EventName">The event name.</param>
/// <param name="Context">The resilience context.</param>
/// <param name="Outcome">The outcome of an execution.</param>
/// <param name="Arguments">The arguments associated with the event.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed record class TelemetryEventArguments(
ResilienceTelemetrySource Source,
string EventName,
ResilienceContext Context,
Outcome<object>? Outcome,
object Arguments)
public sealed partial record class TelemetryEventArguments
{
private TelemetryEventArguments()
{
}

/// <summary>
/// Gets the source of the event.
/// </summary>
public ResilienceTelemetrySource Source { get; private set; } = null!;

/// <summary>
/// Gets the event name.
/// </summary>
public string EventName { get; private set; } = null!;

/// <summary>
/// Gets the resilience context.
/// </summary>
public ResilienceContext Context { get; private set; } = null!;

/// <summary>
/// Gets the outcome of an execution.
/// </summary>
public Outcome<object>? Outcome { get; private set; }

/// <summary>
/// Gets the arguments associated with the event.
/// </summary>
public object Arguments { get; private set; } = null!;
}
5 changes: 5 additions & 0 deletions src/Polly.Core/Utils/ObjectPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ internal sealed class ObjectPool<T>
private T? _fastItem;
private int _numItems;

public ObjectPool(Func<T> createFunc, Action<T> reset)
: this(createFunc, o => { reset(o); return true; })
{
}

public ObjectPool(Func<T> createFunc, Func<T, bool> returnFunc)
: this(_ => createFunc(), returnFunc)
{
Expand Down
1 change: 1 addition & 0 deletions src/Polly.Extensions/Telemetry/EnrichmentContext.Pool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal static EnrichmentContext Get(ResilienceContext resilienceContext, objec

internal static void Return(EnrichmentContext context)
{
Array.Clear(context._tagsArray, 0, context.Tags.Count);
context.Tags.Clear();
ContextPool.Return(context);
}
Expand Down
25 changes: 24 additions & 1 deletion src/Polly.Extensions/Telemetry/EnrichmentContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ namespace Polly.Extensions.Telemetry;
/// </summary>
public sealed partial class EnrichmentContext
{
private const int InitialArraySize = 20;
martincostello marked this conversation as resolved.
Show resolved Hide resolved

private KeyValuePair<string, object?>[] _tagsArray = new KeyValuePair<string, object?>[InitialArraySize];

private EnrichmentContext()
{
}
Expand All @@ -27,5 +31,24 @@ private EnrichmentContext()
/// <summary>
/// Gets the tags associated with the resilience event.
/// </summary>
public ICollection<KeyValuePair<string, object?>> Tags { get; } = new List<KeyValuePair<string, object?>>();
public IList<KeyValuePair<string, object?>> Tags { get; } = new List<KeyValuePair<string, object?>>();

internal ReadOnlySpan<KeyValuePair<string, object?>> TagsSpan
{
get
{
// stryker disable once equality : no means to test this
if (Tags.Count > _tagsArray.Length)
{
Array.Resize(ref _tagsArray, Tags.Count);
}

for (int i = 0; i < Tags.Count; i++)
{
_tagsArray[i] = Tags[i];
}

return _tagsArray.AsSpan(0, Tags.Count);
}
}
}
18 changes: 3 additions & 15 deletions src/Polly.Extensions/Telemetry/EnrichmentUtil.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,19 @@
using System.Collections.Generic;

namespace Polly.Extensions.Telemetry;

internal static class EnrichmentUtil
{
public static void Enrich(
ref TagList tags,
List<Action<EnrichmentContext>> enrichers,
ResilienceContext resilienceContext,
Outcome<object>? outcome,
object? resilienceArguments)
public static void Enrich(EnrichmentContext context, List<Action<EnrichmentContext>> enrichers)
{
if (enrichers.Count == 0)
{
return;
}

var context = EnrichmentContext.Get(resilienceContext, resilienceArguments, outcome);

foreach (var enricher in enrichers)
{
enricher(context);
}

foreach (var pair in context.Tags)
{
tags.Add(pair.Key, pair.Value);
}

EnrichmentContext.Return(context);
}
}
Loading