Skip to content

Commit b6bac81

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

28 files changed

+120
-116
lines changed

docs/schema/manifest.schema.json

+4-2
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

+24-34
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

+1-1
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

+4-2
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

+2-2
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

+2-2
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

+1-1
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

+1-1
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

+1-1
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

+2-1
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
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ private GitComponent()
2626

2727
public override ComponentType Type => ComponentType.Git;
2828

29-
public override string Id => $"{this.RepositoryUrl} : {this.CommitHash} - {this.Type}";
29+
protected override string ComputeId() => $"{this.RepositoryUrl} : {this.CommitHash} - {this.Type}";
3030
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ private GoComponent()
3636

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

39-
public override string Id => $"{this.Name} {this.Version} - {this.Type}";
39+
protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}";
4040

4141
public override bool Equals(object obj)
4242
{

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ public LinuxComponent(string distribution, string release, string name, string v
3636

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

39-
public override string Id => $"{this.Distribution} {this.Release} {this.Name} {this.Version} - {this.Type}";
40-
4139
public override PackageURL PackageUrl
4240
{
4341
get
@@ -62,6 +60,8 @@ public override PackageURL PackageUrl
6260
}
6361
}
6462

63+
protected override string ComputeId() => $"{this.Distribution} {this.Release} {this.Name} {this.Version} - {this.Type}";
64+
6565
private bool IsUbuntu()
6666
{
6767
return this.Distribution.Equals("UBUNTU", StringComparison.OrdinalIgnoreCase);

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private MavenComponent()
2424

2525
public override ComponentType Type => ComponentType.Maven;
2626

27-
public override string Id => $"{this.GroupId} {this.ArtifactId} {this.Version} - {this.Type}";
28-
2927
public override PackageURL PackageUrl => new PackageURL("maven", this.GroupId, this.ArtifactId, this.Version, null, null);
28+
29+
protected override string ComputeId() => $"{this.GroupId} {this.ArtifactId} {this.Version} - {this.Type}";
3030
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public NpmComponent(string name, string version, string hash = null, NpmAuthor a
2828

2929
public override ComponentType Type => ComponentType.Npm;
3030

31-
public override string Id => $"{this.Name} {this.Version} - {this.Type}";
32-
3331
public override PackageURL PackageUrl => new PackageURL("npm", null, this.Name, this.Version, null, null);
32+
33+
protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}";
3434
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public NuGetComponent(string name, string version, string[] authors = null)
2424

2525
public override ComponentType Type => ComponentType.NuGet;
2626

27-
public override string Id => $"{this.Name} {this.Version} - {this.Type}";
28-
2927
public override PackageURL PackageUrl => new PackageURL("nuget", null, this.Name, this.Version, null, null);
28+
29+
protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}";
3030
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ public OtherComponent(string name, string version, Uri downloadUrl, string hash)
2727

2828
public override ComponentType Type => ComponentType.Other;
2929

30-
public override string Id => $"{this.Name} {this.Version} {this.DownloadUrl} - {this.Type}";
30+
protected override string ComputeId() => $"{this.Name} {this.Version} {this.DownloadUrl} - {this.Type}";
3131
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ public PipComponent(string name, string version, string author = null, string li
3333

3434
public override ComponentType Type => ComponentType.Pip;
3535

36-
[SuppressMessage("Usage", "CA1308:Normalize String to Uppercase", Justification = "Casing cannot be overwritten.")]
37-
public override string Id => $"{this.Name} {this.Version} - {this.Type}".ToLowerInvariant();
38-
3936
public override PackageURL PackageUrl => new PackageURL("pypi", null, this.Name, this.Version, null, null);
37+
38+
[SuppressMessage("Usage", "CA1308:Normalize String to Uppercase", Justification = "Casing cannot be overwritten.")]
39+
protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}".ToLowerInvariant();
4040
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ public PodComponent(string name, string version, string specRepo = "")
2525

2626
public override ComponentType Type => ComponentType.Pod;
2727

28-
public override string Id => $"{this.Name} {this.Version} - {this.Type}";
29-
3028
public override PackageURL PackageUrl
3129
{
3230
get
@@ -40,4 +38,6 @@ public override PackageURL PackageUrl
4038
return new PackageURL("cocoapods", null, this.Name, this.Version, qualifiers, null);
4139
}
4240
}
41+
42+
protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}";
4343
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public RubyGemsComponent(string name, string version, string source = "")
2424

2525
public override ComponentType Type => ComponentType.RubyGems;
2626

27-
public override string Id => $"{this.Name} {this.Version} - {this.Type}";
28-
2927
public override PackageURL PackageUrl => new PackageURL("gem", null, this.Name, this.Version, null, null);
28+
29+
protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}";
3030
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ public SpdxComponent(string spdxVersion, Uri documentNamespace, string name, str
3333

3434
public string Path { get; set; }
3535

36-
public override string Id => $"{this.Name}-{this.SpdxVersion}-{this.Checksum}";
36+
protected override string ComputeId() => $"{this.Name}-{this.SpdxVersion}-{this.Checksum}";
3737
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ public SwiftComponent(string name, string version, string packageUrl, string has
3535

3636
public override ComponentType Type => ComponentType.Swift;
3737

38-
public override string Id => $"{this.Name} {this.Version} - {this.Type}";
39-
4038
// Example PackageURL -> pkg:swift/github.com/apple/swift-asn1
4139
// type: swift
4240
// namespace: github.com/apple
@@ -52,6 +50,8 @@ public SwiftComponent(string name, string version, string packageUrl, string has
5250
},
5351
subpath: null);
5452

53+
protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}";
54+
5555
private string GetNamespaceFromPackageUrl()
5656
{
5757
// In the case of github.com, the namespace should contain the user/organization

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ namespace Microsoft.ComponentDetection.Contracts.TypedComponent;
1414
[DebuggerDisplay("{DebuggerDisplay,nq}")]
1515
public abstract class TypedComponent
1616
{
17+
[JsonIgnore]
18+
private string id;
19+
1720
internal TypedComponent()
1821
{
1922
// Reserved for deserialization.
@@ -23,7 +26,8 @@ internal TypedComponent()
2326
[JsonConverter(typeof(StringEnumConverter))]
2427
public abstract ComponentType Type { get; }
2528

26-
public abstract string Id { get; }
29+
/// <summary>Gets the id of the component.</summary>
30+
public string Id => this.id ??= this.ComputeId();
2731

2832
public virtual PackageURL PackageUrl { get; }
2933

@@ -47,4 +51,6 @@ protected string NullPropertyExceptionMessage(string propertyName, string compon
4751
{
4852
return $"Property {propertyName} of component type {componentType} is required";
4953
}
54+
55+
protected abstract string ComputeId();
5056
}

0 commit comments

Comments
 (0)