forked from DataDog/dd-trace-dotnet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rule based sampling and rate limiting (DataDog#537)
* Rule based sampling and rate limiting * Regex sampling rule from config * Fix comment and fix whitespace problems * Use TotalMilliseconds * Make clear comments for metrics * add clarity comment back in * Consolidate constants * Merge miss * Rate limiter tests and fixes * More tests, less flake * Simplify RateLimiter, solidify tests * Remove lock * Sampling rule tests * Sampler tests * More safety mechanisms and better string comparison * Test fallback logic and rate limiter usage, fix them * Reduce the flake * Remove constructor log * even less flake * CI is fun with stats tests * Fix bad comment * Watch for poison regex * Thanks StyleCop, I don't know what I'd do without you * Better comment docs
- Loading branch information
1 parent
13f0706
commit 3983bdd
Showing
19 changed files
with
1,015 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Datadog.Trace.Sampling | ||
{ | ||
internal interface IRateLimiter | ||
{ | ||
bool Allowed(ulong traceId); | ||
|
||
float GetEffectiveRate(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace Datadog.Trace.Sampling | ||
{ | ||
internal interface ISamplingRule | ||
{ | ||
string Name { get; } | ||
|
||
int Priority { get; } | ||
|
||
bool IsMatch(Span span); | ||
|
||
float GetSamplingRate(); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Threading; | ||
using Datadog.Trace.Logging; | ||
|
||
namespace Datadog.Trace.Sampling | ||
{ | ||
internal class RateLimiter : IRateLimiter | ||
{ | ||
private static readonly Vendors.Serilog.ILogger Log = DatadogLogging.For<RateLimiter>(); | ||
|
||
private readonly ManualResetEventSlim _refreshEvent = new ManualResetEventSlim(initialState: true); | ||
private readonly ConcurrentQueue<DateTime> _intervalQueue = new ConcurrentQueue<DateTime>(); | ||
|
||
private readonly int _maxTracesPerInterval; | ||
private readonly int _intervalMilliseconds; | ||
private readonly TimeSpan _interval; | ||
|
||
private DateTime _windowBegin; | ||
private DateTime _lastRefresh; | ||
|
||
private int _windowChecks = 0; | ||
private int _windowAllowed = 0; | ||
|
||
private int _previousWindowChecks = 0; | ||
private int _previousWindowAllowed = 0; | ||
|
||
public RateLimiter(int? maxTracesPerInterval) | ||
{ | ||
_maxTracesPerInterval = maxTracesPerInterval ?? 100; | ||
_intervalMilliseconds = 1_000; | ||
_interval = TimeSpan.FromMilliseconds(_intervalMilliseconds); | ||
_windowBegin = _lastRefresh = DateTime.Now; | ||
} | ||
|
||
public bool Allowed(ulong traceId) | ||
{ | ||
if (_maxTracesPerInterval == 0) | ||
{ | ||
// Rate limit of 0 blocks everything | ||
return false; | ||
} | ||
|
||
if (_maxTracesPerInterval < 0) | ||
{ | ||
// Negative rate limit disables rate limiting | ||
return true; | ||
} | ||
|
||
WaitForRefresh(); | ||
|
||
// This must happen after the wait, because we check for window statistics, modifying this number | ||
Interlocked.Increment(ref _windowChecks); | ||
|
||
var count = _intervalQueue.Count; | ||
|
||
if (count >= _maxTracesPerInterval) | ||
{ | ||
Log.Debug("Dropping trace id {0} with count of {1} for last {2}ms.", traceId, count, _intervalMilliseconds); | ||
return false; | ||
} | ||
|
||
_intervalQueue.Enqueue(DateTime.Now); | ||
Interlocked.Increment(ref _windowAllowed); | ||
|
||
return true; | ||
} | ||
|
||
public float GetEffectiveRate() | ||
{ | ||
if (_maxTracesPerInterval == 0) | ||
{ | ||
// Rate limit of 0 blocks everything | ||
return 0; | ||
} | ||
|
||
if (_maxTracesPerInterval < 0) | ||
{ | ||
// Negative rate limit disables rate limiting | ||
return 1; | ||
} | ||
|
||
var totalChecksForLastTwoWindows = _windowChecks + _previousWindowChecks; | ||
|
||
if (totalChecksForLastTwoWindows == 0) | ||
{ | ||
// no checks, effectively 100%. don't divide by zero | ||
return 1; | ||
} | ||
|
||
// Current window + previous window to prevent burst-iness and low new window numbers from skewing the rate | ||
return (_windowAllowed + _previousWindowAllowed) / (float)totalChecksForLastTwoWindows; | ||
} | ||
|
||
private void WaitForRefresh() | ||
{ | ||
var previousRefresh = _lastRefresh; | ||
|
||
// Block if a refresh event is happening | ||
_refreshEvent.Wait(); | ||
|
||
if (previousRefresh != _lastRefresh) | ||
{ | ||
// Some other thread already did this very recently | ||
// Let's save some cycles and prevent contention | ||
return; | ||
} | ||
|
||
try | ||
{ | ||
// Block threads | ||
_refreshEvent.Reset(); | ||
|
||
var now = DateTime.Now; | ||
_lastRefresh = now; | ||
|
||
var timeSinceWindowStart = (now - _windowBegin).TotalMilliseconds; | ||
|
||
if (timeSinceWindowStart >= _intervalMilliseconds) | ||
{ | ||
// statistical window has passed, shift the counts | ||
_previousWindowAllowed = _windowAllowed; | ||
_previousWindowChecks = _windowChecks; | ||
_windowAllowed = 0; | ||
_windowChecks = 0; | ||
_windowBegin = now; | ||
} | ||
|
||
while (_intervalQueue.TryPeek(out var time) && now.Subtract(time) > _interval) | ||
{ | ||
_intervalQueue.TryDequeue(out _); | ||
} | ||
} | ||
finally | ||
{ | ||
// Resume threads | ||
_refreshEvent.Set(); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.