Skip to content

Commit

Permalink
Add fallback logic for rust CLI detector and additional telemetry (#990)
Browse files Browse the repository at this point in the history
* Add fallback logic for rust CLI detector and additional telemetry tracing

* Fix build tests

* Add fallback tests

* resolve comments
  • Loading branch information
FernandoRojo authored Jan 31, 2024
1 parent 88939b5 commit 45431f1
Show file tree
Hide file tree
Showing 8 changed files with 843 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;

public class RustGraphTelemetryRecord : BaseDetectionTelemetryRecord
{
public override string RecordName => "RustGraph";

public string CargoTomlLocation { get; set; }

public bool WasRustFallbackStrategyUsed { get; set; }

public string FallbackReason { get; set; }

public bool FallbackCargoLockFound { get; set; }

public string FallbackCargoLockLocation { get; set; }

public bool DidRustCliCommandFail { get; set; }

public string RustCliCommandError { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public abstract class FileComponentDetector : IComponentDetector

protected Dictionary<string, string> Telemetry { get; set; } = new Dictionary<string, string>();

/// <summary>
/// List of any any additional properties as key-value pairs that we would like to capture for the detector.
/// </summary>
public List<(string PropertyKey, string PropertyValue)> AdditionalProperties { get; set; } = new List<(string PropertyKey, string PropertyValue)>();

protected IObservable<IComponentStream> ComponentStreams { get; private set; }

/// <inheritdoc />
Expand Down
353 changes: 317 additions & 36 deletions src/Microsoft.ComponentDetection.Detectors/rust/RustCliDetector.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private static bool ParseDependency(string dependency, out string packageName, o
version = versionMatch.Success ? versionMatch.Value : null;
source = sourceMatch.Success ? sourceMatch.Value : null;

if (string.IsNullOrEmpty(source))
if (string.IsNullOrWhiteSpace(source))
{
source = null;
}
Expand All @@ -68,17 +68,20 @@ protected override async Task OnFileFoundAsync(ProcessRequest processRequest, ID
{
var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder;
var cargoLockFile = processRequest.ComponentStream;
var reader = new StreamReader(cargoLockFile.Stream);
var options = new TomlModelOptions
{
IgnoreMissingProperties = true,
};
var cargoLock = Toml.ToModel<CargoLock>(await reader.ReadToEndAsync(), options: options);
this.RecordLockfileVersion(cargoLock.Version);
this.ProcessCargoLock(cargoLock, singleFileComponentRecorder, cargoLockFile);
}

private void ProcessCargoLock(CargoLock cargoLock, ISingleFileComponentRecorder singleFileComponentRecorder, IComponentStream cargoLockFile)
{
try
{
var reader = new StreamReader(cargoLockFile.Stream);
var options = new TomlModelOptions
{
IgnoreMissingProperties = true,
};
var cargoLock = Toml.ToModel<CargoLock>(await reader.ReadToEndAsync(), options: options);
this.RecordLockfileVersion(cargoLock.Version);

var seenAsDependency = new HashSet<CargoPackage>();

// Pass 1: Create typed components and allow lookup by name.
Expand Down Expand Up @@ -152,8 +155,6 @@ protected override async Task OnFileFoundAsync(ProcessRequest processRequest, ID
// If something went wrong, just ignore the file
this.Logger.LogError(e, "Failed to process Cargo.lock file '{CargoLockLocation}'", cargoLockFile.Location);
}

await Task.CompletedTask;
}

private void ProcessDependency(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public void RecordDetectorRun(
detector.Id,
config.Name);
}

if (detector is FileComponentDetector fileDetector)
{
experimentResults.AddAdditionalPropertiesToExperiment(fileDetector.AdditionalProperties);
}
}
}
catch (Exception e)
Expand Down Expand Up @@ -169,6 +174,7 @@ public async Task FinishAsync()
var experimentComponents = experiment.ExperimentGroupComponents;
var controlDetectors = experiment.ControlDetectors;
var experimentDetectors = experiment.ExperimentalDetectors;
var additionalProperties = experiment.AdditionalProperties;
this.logger.LogInformation(
"Experiment {Experiment} finished with {ControlCount} components in the control group and {ExperimentCount} components in the experiment group",
config.Name,
Expand All @@ -185,7 +191,7 @@ public async Task FinishAsync()

try
{
var diff = new ExperimentDiff(controlComponents, experimentComponents, controlDetectors, experimentDetectors);
var diff = new ExperimentDiff(controlComponents, experimentComponents, controlDetectors, experimentDetectors, additionalProperties);
var tasks = this.experimentProcessors.Select(x => x.ProcessExperimentAsync(config, diff));
await Task.WhenAll(tasks);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ public class ExperimentDiff
/// <param name="experimentGroupComponents">A set of components from the experimental group.</param>
/// <param name="controlDetectors">The set of control detectors.</param>
/// <param name="experimentalDetectors">The set of experimental detectors.</param>
/// <param name="additionalProperties">The set of additional metrics to be captured.</param>
public ExperimentDiff(
IEnumerable<ExperimentComponent> controlGroupComponents,
IEnumerable<ExperimentComponent> experimentGroupComponents,
IEnumerable<(string DetectorId, TimeSpan DetectorRunTime)> controlDetectors = null,
IEnumerable<(string DetectorId, TimeSpan DetectorRunTime)> experimentalDetectors = null)
IEnumerable<(string DetectorId, TimeSpan DetectorRunTime)> experimentalDetectors = null,
IEnumerable<(string PropertyKey, string PropertyValue)> additionalProperties = null)
{
var oldComponentDictionary = controlGroupComponents.DistinctBy(x => x.Id).ToDictionary(x => x.Id);
var newComponentDictionary = experimentGroupComponents.DistinctBy(x => x.Id).ToDictionary(x => x.Id);
additionalProperties ??= Array.Empty<(string PropertyKey, string PropertyValue)>();
this.AdditionalProperties = additionalProperties?.Select(kv => new KeyValuePair<string, string>(kv.PropertyKey, kv.PropertyValue)).ToImmutableList();

this.AddedIds = newComponentDictionary.Keys.Except(oldComponentDictionary.Keys).ToImmutableList();
this.RemovedIds = oldComponentDictionary.Keys.Except(newComponentDictionary.Keys).ToImmutableList();
Expand Down Expand Up @@ -132,6 +136,11 @@ public ExperimentDiff()
/// </summary>
public IReadOnlyDictionary<string, IReadOnlySet<string>> RemovedRootIds { get; init; }

/// <summary>
/// Any additional metrics that were captured for the experiment.
/// </summary>
public IReadOnlyCollection<KeyValuePair<string, string>> AdditionalProperties { get; init; }

/// <summary>
/// Stores information about a change to the development dependency status of a component.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class ExperimentResults

private readonly ConcurrentDictionary<string, TimeSpan> experimentalDetectors = new();

private readonly ConcurrentBag<(string, string)> additionalProperties = new();

/// <summary>
/// The set of components in the control group.
/// </summary>
Expand All @@ -47,6 +49,12 @@ public class ExperimentResults
public IImmutableSet<(string DetectorId, TimeSpan DetectorRunTime)> ExperimentalDetectors =>
this.experimentalDetectors.Select(x => (x.Key, x.Value)).ToImmutableHashSet();

/// <summary>
/// The set of experimental detectors.
/// </summary>
public IImmutableSet<(string PropertyKey, string PropertyValue)> AdditionalProperties =>
this.additionalProperties.ToImmutableHashSet();

/// <summary>
/// Adds the components to the control group.
/// </summary>
Expand Down Expand Up @@ -77,6 +85,18 @@ public void AddExperimentalDetectorTime(string experimentalDetectorId, TimeSpan
public void AddComponentsToExperimentalGroup(IEnumerable<ScannedComponent> components) =>
AddComponents(this.experimentGroupComponents, components);

/// <summary>
/// Adds a custom metric to the experiment.
/// </summary>
/// <param name="properties">list of (key, value) tuples to be captured as additionalProperties. </param>
public void AddAdditionalPropertiesToExperiment(IEnumerable<(string PropertyKey, string PropertyValue)> properties)
{
foreach (var (propertyKey, propertyValue) in properties)
{
AddAdditionalProperty(this.additionalProperties, propertyKey, propertyValue);
}
}

private static void AddComponents(ConcurrentDictionary<ExperimentComponent, byte> group, IEnumerable<ScannedComponent> components)
{
foreach (var experimentComponent in components.Select(x => new ExperimentComponent(x)))
Expand All @@ -89,4 +109,9 @@ private static void AddRunTime(ConcurrentDictionary<string, TimeSpan> group, str
{
_ = group.TryAdd(detectorId, runTime);
}

private static void AddAdditionalProperty(ConcurrentBag<(string PropertyKey, string PropertyValue)> group, string propertyKey, string propertyValue)
{
group.Add((propertyKey, propertyValue));
}
}
Loading

0 comments on commit 45431f1

Please sign in to comment.