[Tracer] Ignore more assemblies and don't resend them all at each run#2966
Conversation
andrewlock
left a comment
There was a problem hiding this comment.
I initially thought this implementation would be sufficient, but actually I don't think it will be. With this design we will still send duplicate assemblies if there are multiple domains. We still have the global uniqueness requirement, so maybe something like this would work:
private readonly ConcurrentDictionary<DependencyTelemetryData, bool> _allAssemblies = new();
private ConcurrentBag<DependencyTelemetryData> _currentAssemblies = new();
internal void AssemblyLoaded(AssemblyName assembly)
{
// ...
if(_allAssemblies.TryAdd(key, true))
{
_currentAssemblies.TryAdd(key))
}
}
public ICollection<DependencyTelemetryData> GetData()
{
var assemblies = Interlocked.Exchange(ref _currentAssemblies , new ConcurrentBag<DependencyTelemetryData>());
// ...
}Also this will break all the integration test telemetry checks I expect, so they will need updating too.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Never ever use |
6a0afb1 to
d52f09a
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Was a test. |
| } | ||
|
|
||
| return _assemblies.Keys; | ||
| var assembliesToRecord = new List<DependencyTelemetryData>(); |
There was a problem hiding this comment.
[heap allocations] Is this GetData() method called often? How hard would it be to track the count of new assemblies so you could initialize the list with the correct count? Or even use an array instead of a List<T>?
var assembliesToRecord = new List<DependencyTelemetryData>(newAssemblyCount);
// ...or...
var assembliesToRecord = new DependencyTelemetryData[newAssemblyCount];There was a problem hiding this comment.
Is this GetData() method called often?
Every 1 minute (max)
How hard would it be to track the count of new assemblies
Moderately hard I think with the current design due to concurrency. The trouble is that assemblies could be added to the underlying dictionary while the following loop is created.
Alternative suggestion - I believe (please double check) calls to this method are single-threaded. So you could do something like this:
private List<DependencyTelemetryData>? _toSend;
public ICollection<DependencyTelemetryData> GetData()
{
// ...
_toSend ??= new List<DependencyTelemetryData>(_assemblies.Count);
_toSend.Clear();
foreach (var assembly in _assemblies)
//...That way you initialize to the initial set, grow as necessary, but don't create a new list each time
andrewlock
left a comment
There was a problem hiding this comment.
Spotted an issue in how we handle errors :(
| foreach (var assembly in _assemblies) | ||
| { | ||
| if (assembly.Value == false) | ||
| { | ||
| _assemblies[assembly.Key] = true; | ||
| assembliesToRecord.Add(assembly.Key); | ||
| } | ||
| } |
There was a problem hiding this comment.
Just spotted an issue with this. The code in
assumes thatGetData() returns all the assemblies, so it can tolerate transport errors.
With this new approach, you could have the following:
- Initial
GetData()returns 50 assemblies - Sending fails.
TelemetryControllerremembers failed list - A new assembly is referenced
- Next clock,
TelemetryControllercallsGetData(). - This returns the single assembly.
TelemetryControllerthrows away the stale data. - We never record the original 50 assemblies
So something has to change 🙁
One possible option - keep this code the same, but change this line to something like this:
var dependencies = _dependencies.GetData();
if(dependencies is null)
{
dependencies = _circuitBreaker.PreviousDependencies;
}
else if(_circuitBreaker.PreviousDependencies is {} oldDeps)
{
dependencies.AddRange(oldDeps);
}There was a problem hiding this comment.
Thanks a lot for spotting this and for the suggestion.
Done in 66e8c76
2aa638d to
66e8c76
Compare
Benchmarks Report 🐌Benchmarks for #2966 compared to master:
The following thresholds were used for comparing the benchmark speeds:
Allocation changes below 0.5% are ignored. Benchmark detailsBenchmarks.Trace.AgentWriterBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.AppSecBodyBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.AspNetCoreBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.DbCommandBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.ElasticsearchBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.GraphQLBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.HttpClientBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.ILoggerBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.Log4netBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.NLogBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.RedisBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.SerilogBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.SpanBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.TraceAnnotationsBenchmark - Same speed ✔️ Same allocations ✔️Raw results
|
Code Coverage Report 📊✔️ Merging #2966 into master will not change line coverage
View the full report for further details: Datadog.Trace Breakdown ✔️
The following classes have significant coverage changes.
View the full reports for further details: |
andrewlock
left a comment
There was a problem hiding this comment.
LGTM, though could apply the optimization to reuse the list
Summary of changes
Ignore assembly names like
ℛ*fe48e1c0-b9fb-433d-a781-5f0f0b6555e4#2-0as there were 6000 of them. Also do not resend all assemblies at each run.Reason for change
There was a misunderstanding with R&P, we need to send them once, not resend the whole package every time. We'll need to do the same for config and integrations.
Implementation details
For the update, rebuild a ConcurrentDictionnary at each upload.
Test coverage
Added 2 test cases
Other details