Skip to content

Commit b6bac81

Browse files
authored
Some optimizations for component detection (#1384)
* Optimize component detection scanning
1 parent 43a7487 commit b6bac81

File tree

28 files changed

+120
-116
lines changed

28 files changed

+120
-116
lines changed

docs/schema/manifest.schema.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@
118118
"Spdx",
119119
"Vcpkg",
120120
"DockerReference",
121-
"DotNet"
121+
"DotNet",
122+
"Conan"
122123
]
123124
}
124125
}
@@ -345,7 +346,8 @@
345346
"Spdx",
346347
"Vcpkg",
347348
"DockerReference",
348-
"DotNet"
349+
"DotNet",
350+
"Conan"
349351
]
350352
},
351353
"id": {

src/Microsoft.ComponentDetection.Common/DependencyGraph/ComponentRecorder.cs

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212

1313
namespace Microsoft.ComponentDetection.Common.DependencyGraph;
1414

15+
using System.Collections.ObjectModel;
1516
using Microsoft.Extensions.Logging;
1617

1718
public class ComponentRecorder : IComponentRecorder
1819
{
19-
private readonly ConcurrentBag<SingleFileComponentRecorder> singleFileRecorders = [];
20+
private readonly ConcurrentDictionary<string, SingleFileComponentRecorder> singleFileRecorders = [];
2021

2122
private readonly bool enableManualTrackingOfExplicitReferences;
2223

@@ -30,7 +31,7 @@ public ComponentRecorder(ILogger logger = null, bool enableManualTrackingOfExpli
3031

3132
public TypedComponent GetComponent(string componentId)
3233
{
33-
return this.singleFileRecorders.Select(x => x.GetComponent(componentId)?.Component).FirstOrDefault(x => x != null);
34+
return this.singleFileRecorders.Values.Select(x => x.GetComponent(componentId)?.Component).FirstOrDefault(x => x != null);
3435
}
3536

3637
public IEnumerable<DetectedComponent> GetDetectedComponents()
@@ -41,25 +42,21 @@ public IEnumerable<DetectedComponent> GetDetectedComponents()
4142
return [];
4243
}
4344

44-
detectedComponents = this.singleFileRecorders
45-
.Select(singleFileRecorder => singleFileRecorder.GetDetectedComponents().Values)
46-
.SelectMany(x => x)
45+
detectedComponents = this.singleFileRecorders.Values
46+
.SelectMany(singleFileRecorder => singleFileRecorder.GetDetectedComponents().Values)
4747
.GroupBy(x => x.Component.Id)
4848
.Select(grouping =>
4949
{
5050
// We pick a winner here -- any stateful props could get lost at this point. Only stateful prop still outstanding is ContainerDetails.
5151
var winningDetectedComponent = grouping.First();
52-
foreach (var component in grouping)
52+
foreach (var component in grouping.Skip(1))
5353
{
54-
foreach (var containerDetailId in component.ContainerDetailIds)
55-
{
56-
winningDetectedComponent.ContainerDetailIds.Add(containerDetailId);
57-
}
54+
winningDetectedComponent.ContainerDetailIds.UnionWith(component.ContainerDetailIds);
5855
}
5956

6057
return winningDetectedComponent;
6158
})
62-
.ToImmutableList();
59+
.ToArray();
6360

6461
return detectedComponents;
6562
}
@@ -71,11 +68,10 @@ public IEnumerable<string> GetSkippedComponents()
7168
return [];
7269
}
7370

74-
return this.singleFileRecorders
75-
.Select(x => x.GetSkippedComponents().Keys)
76-
.SelectMany(x => x)
71+
return this.singleFileRecorders.Values
72+
.SelectMany(x => x.GetSkippedComponents().Keys)
7773
.Distinct()
78-
.ToImmutableList();
74+
.ToArray();
7975
}
8076

8177
public ISingleFileComponentRecorder CreateSingleFileComponentRecorder(string location)
@@ -85,25 +81,20 @@ public ISingleFileComponentRecorder CreateSingleFileComponentRecorder(string loc
8581
throw new ArgumentNullException(nameof(location));
8682
}
8783

88-
var matching = this.singleFileRecorders.FirstOrDefault(x => x.ManifestFileLocation == location);
89-
if (matching == null)
90-
{
91-
matching = new SingleFileComponentRecorder(location, this, this.enableManualTrackingOfExplicitReferences, this.logger);
92-
this.singleFileRecorders.Add(matching);
93-
}
94-
95-
return matching;
84+
return this.singleFileRecorders.GetOrAdd(location, loc => new SingleFileComponentRecorder(loc, this, this.enableManualTrackingOfExplicitReferences, this.logger));
9685
}
9786

9887
public IReadOnlyDictionary<string, IDependencyGraph> GetDependencyGraphsByLocation()
9988
{
100-
return this.singleFileRecorders.Where(x => x.DependencyGraph.HasComponents())
101-
.ToImmutableDictionary(x => x.ManifestFileLocation, x => x.DependencyGraph as IDependencyGraph);
89+
return new ReadOnlyDictionary<string, IDependencyGraph>(
90+
this.singleFileRecorders.Values
91+
.Where(x => x.DependencyGraph.HasComponents())
92+
.ToDictionary(x => x.ManifestFileLocation, x => x.DependencyGraph as IDependencyGraph));
10293
}
10394

10495
internal DependencyGraph GetDependencyGraphForLocation(string location)
10596
{
106-
return this.singleFileRecorders.Single(x => x.ManifestFileLocation == location).DependencyGraph;
97+
return this.singleFileRecorders[location].DependencyGraph;
10798
}
10899

109100
public sealed class SingleFileComponentRecorder : ISingleFileComponentRecorder
@@ -183,16 +174,15 @@ public void RegisterUsage(
183174
#endif
184175

185176
var componentId = detectedComponent.Component.Id;
186-
DetectedComponent storedComponent = null;
187-
lock (this.registerUsageLock)
188-
{
189-
storedComponent = this.detectedComponentsInternal.GetOrAdd(componentId, detectedComponent);
177+
var storedComponent = this.detectedComponentsInternal.GetOrAdd(componentId, detectedComponent);
190178

191-
if (!string.IsNullOrWhiteSpace(targetFramework))
192-
{
193-
storedComponent.TargetFrameworks.Add(targetFramework.Trim());
194-
}
179+
if (!string.IsNullOrWhiteSpace(targetFramework))
180+
{
181+
storedComponent.TargetFrameworks.Add(targetFramework.Trim());
182+
}
195183

184+
lock (this.registerUsageLock)
185+
{
196186
this.AddComponentToGraph(this.ManifestFileLocation, detectedComponent, isExplicitReferencedDependency, parentComponentId, isDevelopmentDependency, dependencyScope);
197187
}
198188
}

src/Microsoft.ComponentDetection.Common/DependencyGraph/DependencyGraph.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public void AddAdditionalRelatedFile(string additionalRelatedFile)
9797

9898
public HashSet<string> GetAdditionalRelatedFiles()
9999
{
100-
return this.AdditionalRelatedFiles.Keys.ToImmutableHashSet().ToHashSet();
100+
return this.AdditionalRelatedFiles.Keys.ToHashSet();
101101
}
102102

103103
public bool HasComponents()

src/Microsoft.ComponentDetection.Contracts/FileComponentDetector.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
namespace Microsoft.ComponentDetection.Contracts;
22

33
using System;
4+
using System.Collections.Concurrent;
45
using System.Collections.Generic;
56
using System.IO;
7+
using System.Linq;
68
using System.Reactive.Linq;
79
using System.Threading;
810
using System.Threading.Tasks;
@@ -56,7 +58,7 @@ public abstract class FileComponentDetector : IComponentDetector
5658

5759
public virtual bool NeedsAutomaticRootDependencyCalculation { get; protected set; }
5860

59-
protected Dictionary<string, string> Telemetry { get; set; } = [];
61+
protected ConcurrentDictionary<string, string> Telemetry { get; set; } = [];
6062

6163
/// <summary>
6264
/// List of any any additional properties as key-value pairs that we would like to capture for the detector.
@@ -135,7 +137,7 @@ private async Task<IndividualDetectorScanResult> ProcessAsync(
135137
return new IndividualDetectorScanResult
136138
{
137139
ResultCode = ProcessingResultCode.Success,
138-
AdditionalTelemetryDetails = this.Telemetry,
140+
AdditionalTelemetryDetails = this.Telemetry.ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
139141
};
140142
}
141143

src/Microsoft.ComponentDetection.Contracts/TypedComponent/CargoComponent.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public CargoComponent(string name, string version, string author = null, string
3636

3737
public override ComponentType Type => ComponentType.Cargo;
3838

39-
public override string Id => $"{this.Name} {this.Version} - {this.Type}";
40-
4139
public override PackageURL PackageUrl => new PackageURL("cargo", string.Empty, this.Name, this.Version, null, string.Empty);
40+
41+
protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}";
4242
}

src/Microsoft.ComponentDetection.Contracts/TypedComponent/ConanComponent.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public ConanComponent(string name, string version, string previous, string packa
2929

3030
public override ComponentType Type => ComponentType.Conan;
3131

32-
public override string Id => $"{this.Name} {this.Version} - {this.Type}";
33-
3432
public override PackageURL PackageUrl => new PackageURL("conan", string.Empty, this.Name, this.Version, null, string.Empty);
33+
34+
protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}";
3535
}

src/Microsoft.ComponentDetection.Contracts/TypedComponent/CondaComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ private CondaComponent()
3737

3838
public override ComponentType Type => ComponentType.Conda;
3939

40-
public override string Id => $"{this.Name} {this.Version} {this.Build} {this.Channel} {this.Subdir} {this.Namespace} {this.Url} {this.MD5} - {this.Type}";
40+
protected override string ComputeId() => $"{this.Name} {this.Version} {this.Build} {this.Channel} {this.Subdir} {this.Namespace} {this.Url} {this.MD5} - {this.Type}";
4141
}

src/Microsoft.ComponentDetection.Contracts/TypedComponent/DockerImageComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ public DockerImageComponent(string hash, string name = null, string tag = null)
2222

2323
public override ComponentType Type => ComponentType.DockerImage;
2424

25-
public override string Id => $"{this.Name} {this.Tag} {this.Digest}";
25+
protected override string ComputeId() => $"{this.Name} {this.Tag} {this.Digest}";
2626
}

src/Microsoft.ComponentDetection.Contracts/TypedComponent/DockerReferenceComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ public DockerReference FullReference
3636
}
3737
}
3838

39-
public override string Id => $"{this.Repository} {this.Tag} {this.Digest}";
39+
protected override string ComputeId() => $"{this.Repository} {this.Tag} {this.Digest}";
4040
}

src/Microsoft.ComponentDetection.Contracts/TypedComponent/DotNetComponent.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,6 @@ public DotNetComponent(string sdkVersion, string targetFramework = null, string
4343
/// <summary>
4444
/// Provides an id like `{SdkVersion} - {TargetFramework} - {ProjectType} - dotnet` where unspecified values are represented as 'unknown'.
4545
/// </summary>
46-
public override string Id => $"{this.SdkVersion} {this.TargetFramework} {this.ProjectType} - {this.Type}";
46+
/// <returns>Id of the component.</returns>
47+
protected override string ComputeId() => $"{this.SdkVersion} {this.TargetFramework} {this.ProjectType} - {this.Type}";
4748
}

0 commit comments

Comments
 (0)