Skip to content

Commit

Permalink
[Dynamic Instrumentation] DEBUG-3088 Add object pool (#6105)
Browse files Browse the repository at this point in the history
## Summary of changes
Add object pool (IPoolable) to use in probe processor and snapshot
creator.

## Implementation details
Object pool with size limitation.
  • Loading branch information
dudikeleti authored Nov 7, 2024
1 parent a509060 commit b80b1ae
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private string CreateSnapshot()

if (members == null)
{
members = new MethodScopeMembers(0, 0);
members = new MethodScopeMembers(new MethodScopeMembersParameters(0, 0));
}

var limitInfo = new CaptureLimitInfo(
Expand Down Expand Up @@ -164,7 +164,7 @@ internal void AddScopeMember<T>(string name, Type type, T value, ScopeMemberKind
{
if (Members == null)
{
Members = new MethodScopeMembers(0, 0);
Members = new MethodScopeMembers(new MethodScopeMembersParameters(0, 0));
}

type = (type.IsGenericTypeDefinition ? value?.GetType() : type) ?? type;
Expand Down
43 changes: 29 additions & 14 deletions tracer/src/Datadog.Trace/Debugger/Expressions/MethodScopeMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,25 @@

namespace Datadog.Trace.Debugger.Expressions;

internal class MethodScopeMembers
internal class MethodScopeMembers : IPoolable<MethodScopeMembersParameters>
{
private readonly int _initialSize;
private int _index;

internal MethodScopeMembers(int numberOfLocals, int numberOfArguments)
public MethodScopeMembers()
{
_initialSize = numberOfLocals + numberOfArguments;
if (_initialSize == 0)
{
_initialSize = 1;
}
}

Members = ArrayPool<ScopeMember>.Shared.Rent(_initialSize);
Array.Clear(Members, 0, Members.Length);
Exception = null;
Return = default;
InvocationTarget = default;
internal MethodScopeMembers(MethodScopeMembersParameters parameters)
{
Set(parameters);
}

internal ScopeMember[] Members { get; private set; }

internal Exception Exception { get; set; }

// food for thought:
// we can save Return and InvocationTarget as T if we will change the native side so we will have MethodDebuggerState<T, TReturn> instead MethodDebuggerState.
// we can save Return and InvocationTarget as T if we will change the native side, so we will have MethodDebuggerState<T, TReturn> instead MethodDebuggerState.
internal ScopeMember Return { get; set; }

internal ScopeMember InvocationTarget { get; set; }
Expand All @@ -59,4 +52,26 @@ internal void Dispose()
ArrayPool<ScopeMember>.Shared.Return(Members);
}
}

public void Set(MethodScopeMembersParameters parameters)
{
var initialSize = parameters.NumberOfLocals + parameters.NumberOfArguments;
if (initialSize == 0)
{
initialSize = 1;
}

Members = ArrayPool<ScopeMember>.Shared.Rent(initialSize * 2);
Array.Clear(Members, 0, Members.Length);
Exception = null;
Duration = default;
Return = default;
InvocationTarget = default;
_index = 0;
}

public void Reset()
{
Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// <copyright file="MethodScopeMembersParameters.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

namespace Datadog.Trace.Debugger.Expressions;

#nullable enable
internal record struct MethodScopeMembersParameters(int NumberOfLocals, int NumberOfArguments);
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ private ProbeExpressionEvaluator GetOrCreateEvaluator()

public bool ShouldProcess(in ProbeData probeData)
{
return HasCondition() || probeData.Sampler.Sample();
return HasCondition() || (probeData.Sampler.Sample());
}

public bool Process<TCapture>(ref CaptureInfo<TCapture> info, IDebuggerSnapshotCreator inSnapshotCreator, in ProbeData probeData)
Expand Down
15 changes: 15 additions & 0 deletions tracer/src/Datadog.Trace/Debugger/Helpers/IPoolable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// <copyright file="IPoolable.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable
namespace Datadog.Trace.Debugger.Helpers
{
internal interface IPoolable<in TSetParameters>
{
void Set(TSetParameters parameters);

void Reset();
}
}
51 changes: 51 additions & 0 deletions tracer/src/Datadog.Trace/Debugger/Helpers/ObjectPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// <copyright file="ObjectPool.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable
using System;
using System.Collections.Concurrent;

namespace Datadog.Trace.Debugger.Helpers
{
internal class ObjectPool<T, TSetParameters>
where T : class, IPoolable<TSetParameters>, new()
{
private readonly ConcurrentBag<T> _objects;
private readonly Func<T> _objectFactory;
private readonly int _maxSize;

public ObjectPool(Func<T>? objectFactory = null, int maxSize = 100)
{
if (maxSize <= 0)
{
throw new ArgumentException("Maximum pool size must be greater than zero.", nameof(maxSize));
}

_objects = new ConcurrentBag<T>();
_objectFactory = objectFactory ?? (() => new T());
_maxSize = maxSize;
}

public int Count => _objects.Count;

public T? Get() => _objects.TryTake(out var item) ? item : _objectFactory();

public T? Get(TSetParameters parameters)
{
var item = _objects.TryTake(out var obj) ? obj : _objectFactory();
item?.Set(parameters);
return item;
}

public void Return(T? item)
{
item?.Reset();
if (item != null && _objects.Count < _maxSize)
{
_objects.Add(item);
}
}
}
}
10 changes: 0 additions & 10 deletions tracer/src/Datadog.Trace/Debugger/RateLimiting/ProbeRateLimiter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ namespace Datadog.Trace.Debugger.RateLimiting
internal class ProbeRateLimiter
{
private const int DefaultSamplesPerSecond = 1;
private const int DefaultGlobalSamplesPerSecond = DefaultSamplesPerSecond * 100;

private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(ProbeRateLimiter));

Expand All @@ -23,8 +22,6 @@ internal class ProbeRateLimiter

private static ProbeRateLimiter _instance;

private readonly AdaptiveSampler _globalSampler = CreateSampler(DefaultGlobalSamplesPerSecond);

private readonly ConcurrentDictionary<string, IAdaptiveSampler> _samplers = new();

internal static ProbeRateLimiter Instance
Expand All @@ -51,13 +48,6 @@ public bool TryAddSampler(string probeId, IAdaptiveSampler sampler)
return _samplers.TryAdd(probeId, sampler);
}

public bool Sample(string probeId)
{
// Rate limiter is engaged at ~1 probe per second (1 probes per 1s time window)
var probeSampler = _samplers.GetOrAdd(probeId, _ => CreateSampler(1));
return probeSampler.Sample() && _globalSampler.Sample();
}

public void SetRate(string probeId, int samplesPerSecond)
{
// We currently don't support updating the probe rate limit, and that is fine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ internal class DebuggerSnapshotCreator : IDebuggerSnapshotCreator, IDisposable
private string _message;
private List<EvaluationError> _errors;
private string _snapshotId;
private ObjectPool<MethodScopeMembers, MethodScopeMembersParameters> _scopeMembersPool;

public DebuggerSnapshotCreator(bool isFullSnapshot, ProbeLocation location, bool hasCondition, string[] tags, CaptureLimitInfo limitInfo)
{
Expand All @@ -55,6 +56,7 @@ public DebuggerSnapshotCreator(bool isFullSnapshot, ProbeLocation location, bool
Tags = tags;
_limitInfo = limitInfo;
_accumulatedDuration = new TimeSpan(0, 0, 0, 0, 0);
_scopeMembersPool = new ObjectPool<MethodScopeMembers, MethodScopeMembersParameters>();
Initialize();
}

Expand Down Expand Up @@ -182,11 +184,14 @@ internal void CreateMethodScopeMembers<T>(ref CaptureInfo<T> info)
{
if (info.IsAsyncCapture())
{
MethodScopeMembers = new MethodScopeMembers(info.AsyncCaptureInfo.HoistedLocals.Length + (info.LocalsCount ?? 0), info.AsyncCaptureInfo.HoistedArguments.Length + (info.ArgumentsCount ?? 0));
MethodScopeMembers = _scopeMembersPool.Get(
new MethodScopeMembersParameters(
info.AsyncCaptureInfo.HoistedLocals.Length + (info.LocalsCount ?? 0),
info.AsyncCaptureInfo.HoistedArguments.Length + (info.ArgumentsCount ?? 0)));
}
else
{
MethodScopeMembers = new MethodScopeMembers(info.LocalsCount.Value, info.ArgumentsCount.Value);
MethodScopeMembers = _scopeMembersPool.Get(new MethodScopeMembersParameters(info.LocalsCount.Value, info.ArgumentsCount.Value));
}
}

Expand Down Expand Up @@ -901,8 +906,7 @@ public void Dispose()
try
{
Stop();
MethodScopeMembers?.Dispose();
MethodScopeMembers = null;
_scopeMembersPool.Return(MethodScopeMembers);
_jsonWriter?.Close();
}
catch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ private string GetJsonPart(string json)

private MethodScopeMembers CreateScopeMembers()
{
var scope = new MethodScopeMembers(10, 5);

var scope = new MethodScopeMembers();
scope.Set(new MethodScopeMembersParameters(10, 5));
// Add locals
scope.AddMember(new ScopeMember("IntLocal", TestObject.IntNumber.GetType(), TestObject.IntNumber, ScopeMemberKind.Local));
scope.AddMember(new ScopeMember("DoubleLocal", TestObject.DoubleNumber.GetType(), TestObject.DoubleNumber, ScopeMemberKind.Local));
Expand Down
Loading

0 comments on commit b80b1ae

Please sign in to comment.