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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,3 @@ src/Mocha/benchmarks/Mocha.Mediator.Benchmarks/Generated/
Directory.Build.user.props
appsettings.user.json
.playwright-mcp/
.perf
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
using System.Collections.Concurrent;
using System.Diagnostics.Tracing;
using System.Text;

namespace eShop.Gateway;

/// <summary>
/// Listens to PathSegmentPool ETW events and logs aggregated usage metrics across all stripes.
/// Listens to PathSegmentPool ETW events and logs aggregated usage metrics.
/// </summary>
internal sealed class PathSegmentPoolDiagnostics : EventListener, IHostedService
{
private readonly Timer _timer;
private readonly ConcurrentDictionary<int, StripeCounters> _stripes = new();

private int _poolCount;
private int _poolId;
private int _segmentSize;
private long _maxArraysTotal;
private long _maxBytesTotal;
private int _maxArrays;
private long _maxBytes;

private long _rented;
private long _returned;
Expand All @@ -25,7 +22,7 @@ internal sealed class PathSegmentPoolDiagnostics : EventListener, IHostedService
private long _trimmedEvents;
private int _lastTrimRemaining;
private int _lastTrimInUse;
private int _peakInUsePerStripe;
private int _peakInUse;

public PathSegmentPoolDiagnostics()
{
Expand Down Expand Up @@ -55,19 +52,21 @@ protected override void OnEventWritten(EventWrittenEventArgs e)
case 1:
if (e.Payload is { Count: >= 4 })
{
Interlocked.Increment(ref _poolCount);

if (e.Payload[0] is int poolId)
{
_poolId = poolId;
}
if (e.Payload[1] is int segmentSize)
{
_segmentSize = segmentSize;
}
if (e.Payload[2] is int maxArrays)
{
Interlocked.Add(ref _maxArraysTotal, maxArrays);
_maxArrays = maxArrays;
}
if (e.Payload[3] is long maxBytes)
{
Interlocked.Add(ref _maxBytesTotal, maxBytes);
_maxBytes = maxBytes;
}
}
break;
Expand All @@ -76,7 +75,7 @@ protected override void OnEventWritten(EventWrittenEventArgs e)
Interlocked.Increment(ref _rented);
if (e.Payload is { Count: >= 4 } && e.Payload[3] is int inUseRent)
{
UpdatePeakInUsePerStripe(inUseRent);
UpdatePeakInUse(inUseRent);
}
break;

Expand All @@ -86,26 +85,14 @@ protected override void OnEventWritten(EventWrittenEventArgs e)

case 4:
Interlocked.Increment(ref _exhausted);
if (e.Payload is { Count: >= 1 } && e.Payload[0] is int exhaustedPoolId)
{
Interlocked.Increment(ref GetStripe(exhaustedPoolId).Exhausted);
}
break;

case 5:
Interlocked.Increment(ref _dropped);
if (e.Payload is { Count: >= 3 } && e.Payload[2] is int droppedPoolId)
{
Interlocked.Increment(ref GetStripe(droppedPoolId).Dropped);
}
break;

case 6:
Interlocked.Increment(ref _allocated);
if (e.Payload is { Count: >= 3 } && e.Payload[2] is int allocatedPoolId)
{
Interlocked.Increment(ref GetStripe(allocatedPoolId).Allocated);
}
break;

case 7:
Expand All @@ -125,21 +112,18 @@ protected override void OnEventWritten(EventWrittenEventArgs e)
}
}

private StripeCounters GetStripe(int poolId)
=> _stripes.GetOrAdd(poolId, static _ => new StripeCounters());

private void UpdatePeakInUsePerStripe(int inUse)
private void UpdatePeakInUse(int inUse)
{
int current;
do
{
current = _peakInUsePerStripe;
current = _peakInUse;
if (inUse <= current)
{
return;
}
}
while (Interlocked.CompareExchange(ref _peakInUsePerStripe, inUse, current) != current);
while (Interlocked.CompareExchange(ref _peakInUse, inUse, current) != current);
}

private void LogSnapshot(object? state)
Expand All @@ -150,51 +134,27 @@ private void LogSnapshot(object? state)
var dropped = Interlocked.Read(ref _dropped);
var allocated = Interlocked.Read(ref _allocated);
var trimmedEvents = Interlocked.Read(ref _trimmedEvents);
var maxArraysTotal = Interlocked.Read(ref _maxArraysTotal);
var maxBytesTotal = Interlocked.Read(ref _maxBytesTotal);
var outstanding = rented - returned;

Console.WriteLine(
"[PathSegmentPool] Pools={0}, SegmentSize={1}, MaxArraysTotal={2}, MaxBytesTotal={3}, "
+ "Rented={4}, Returned={5}, Outstanding={6}, PeakInUsePerStripe={7}, "
"[PathSegmentPool] PoolId={0}, SegmentSize={1}, MaxArrays={2}, MaxBytes={3}, "
+ "Rented={4}, Returned={5}, Outstanding={6}, PeakInUse={7}, "
+ "Exhausted={8}, Allocated={9}, Dropped={10}, "
+ "TrimmedEvents={11}, LastTrimRemaining(any stripe)={12}, LastTrimInUse(any stripe)={13}",
Volatile.Read(ref _poolCount),
+ "TrimmedEvents={11}, LastTrimRemaining={12}, LastTrimInUse={13}",
_poolId,
_segmentSize,
maxArraysTotal,
maxBytesTotal,
_maxArrays,
_maxBytes,
rented,
returned,
outstanding,
_peakInUsePerStripe,
_peakInUse,
exhausted,
allocated,
dropped,
trimmedEvents,
_lastTrimRemaining,
_lastTrimInUse);

// Per-stripe Dropped and Allocated near zero at steady state is the acceptance criterion:
// it proves rents and returns are matched on each pool so pooling is not being defeated.
var spread = new StringBuilder("[PathSegmentPool] Per-stripe spread:");
var first = true;
foreach (var poolId in _stripes.Keys.OrderBy(static id => id))
{
var stripe = _stripes[poolId];
spread.Append(first ? " " : ", ");
spread.Append(
$"#{poolId}(Dropped={Interlocked.Read(ref stripe.Dropped)}, "
+ $"Allocated={Interlocked.Read(ref stripe.Allocated)}, "
+ $"Exhausted={Interlocked.Read(ref stripe.Exhausted)})");
first = false;
}

if (first)
{
spread.Append(" none");
}

Console.WriteLine(spread.ToString());
}

public Task StartAsync(CancellationToken cancellationToken)
Expand All @@ -217,11 +177,4 @@ public override void Dispose()
_timer.Dispose();
base.Dispose();
}

private sealed class StripeCounters
{
public long Dropped;
public long Allocated;
public long Exhausted;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void Initialize(
_deferFlags = deferFlags;
_disposed = false;

_pathPool ??= new PathSegmentLocalPool(PathSegmentMemory.GetPool(), pathSegmentLocalPoolCapacity);
_pathPool ??= new PathSegmentLocalPool(pathSegmentLocalPoolCapacity);
_result = new CompositeResultDocument(arena, operation, includeFlags, deferFlags, _pathPool);

_valueCompletion = new ValueCompletion(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@ namespace HotChocolate.Fusion.Text.Json;

internal sealed class PathSegmentLocalPool : IDisposable
{
private readonly PathSegmentPool _pool;
private int[]?[] _buffers;
private int _index;
private int[]?[] _allRented;
private int _allRentedCount;
private bool _disposed;

public PathSegmentLocalPool(PathSegmentPool pool, int initialCapacity)
public PathSegmentLocalPool(int initialCapacity = 64)
{
ArgumentNullException.ThrowIfNull(pool);

var capacity = Math.Max(32, initialCapacity);

_pool = pool;
_buffers = ArrayPool<int[]?>.Shared.Rent(capacity);
_index = 0;
_allRented = ArrayPool<int[]?>.Shared.Rent(capacity * 2);
Expand All @@ -33,14 +29,14 @@ public int[] Rent()
return array;
}

var rented = _pool.Rent();
var rented = PathSegmentMemory.Rent();
TrackRented(rented);
return rented;
}

public void Return(int[] array)
{
if (array.Length != _pool._segmentArraySize)
if (array.Length != PathSegmentMemory.SegmentArraySize)
{
return;
}
Expand Down Expand Up @@ -88,7 +84,11 @@ public void Dispose()

_disposed = true;

_pool.Return(_allRented.AsSpan(0, _allRentedCount));
for (var i = 0; i < _allRentedCount; i++)
{
PathSegmentMemory.Return(_allRented[i]!);
_allRented[i] = null;
}
Comment on lines +87 to +91

_allRentedCount = 0;
_index = 0;
Expand Down
Loading
Loading