Skip to content

Commit d688c60

Browse files
authored
Merge pull request #9809 from drewnoakes/package-restore-etw-events
Publish ETW events for package restore nominations
2 parents 698c90f + 70cb7df commit d688c60

File tree

16 files changed

+414
-210
lines changed

16 files changed

+414
-210
lines changed

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/PackageRestore/Snapshots/VsReferenceItem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ internal class VsReferenceItem(ReferenceItem referenceItem) : IVsReferenceItem2
1515
{
1616
public string Name => referenceItem.Name;
1717

18-
public IReadOnlyDictionary<string, string>? Metadata => referenceItem.Properties;
18+
public IReadOnlyDictionary<string, string>? Metadata => referenceItem.Metadata;
1919
}

src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/PackageRestore/PackageRestoreDataSource.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks.Dataflow;
44
using Microsoft.VisualStudio.Composition;
55
using Microsoft.VisualStudio.IO;
6+
using Microsoft.VisualStudio.ProjectSystem.Telemetry;
67
using Microsoft.VisualStudio.Text;
78
using Microsoft.VisualStudio.Threading;
89

@@ -58,6 +59,7 @@ internal partial class PackageRestoreDataSource : ChainedProjectValueDataSourceB
5859
private readonly IManagedProjectDiagnosticOutputService _logger;
5960
private readonly INuGetRestoreService _nuGetRestoreService;
6061
private Hash? _lastHash;
62+
private ProjectRestoreInfo? _lastRestoreInfo;
6163
private bool _enabled;
6264
private bool _wasSourceBlockContinuationSet;
6365

@@ -109,10 +111,7 @@ internal async Task<IEnumerable<IProjectVersionedValue<RestoreData>>> RestoreAsy
109111

110112
RestoreData restoreData = CreateRestoreData(e.Value.RestoreInfo, succeeded);
111113

112-
return new[]
113-
{
114-
new ProjectVersionedValue<RestoreData>(restoreData, e.DataSourceVersions)
115-
};
114+
return [new ProjectVersionedValue<RestoreData>(restoreData, e.DataSourceVersions)];
116115
}
117116

118117
private async Task<bool> RestoreCoreAsync(PackageRestoreUnconfiguredInput value)
@@ -125,22 +124,43 @@ private async Task<bool> RestoreCoreAsync(PackageRestoreUnconfiguredInput value)
125124

126125
// Restore service always does work regardless of whether the value we pass
127126
// them to actually contains changes, only nominate if there are any.
128-
Hash hash = RestoreHasher.CalculateHash(restoreInfo);
127+
Hash hash = CalculateHash();
129128

130129
if (await _cycleDetector.IsCycleDetectedAsync(hash, value.ActiveConfiguration, token))
131130
{
132131
_lastHash = hash;
132+
_lastRestoreInfo = restoreInfo;
133133
return false;
134134
}
135135

136136
if (_lastHash?.Equals(hash) == true)
137137
{
138138
await _nuGetRestoreService.UpdateWithoutNominationAsync(value.ConfiguredInputs);
139+
_lastRestoreInfo = restoreInfo;
139140
return true;
140141
}
141142

142143
_lastHash = hash;
143144

145+
if (DotNetProjectSystemEventSource.Instance.IsEnabled())
146+
{
147+
string changes;
148+
if (_lastRestoreInfo is null)
149+
{
150+
changes = "Initial restore.";
151+
}
152+
else
153+
{
154+
RestoreStateComparisonBuilder builder = new();
155+
_lastRestoreInfo.DescribeChanges(builder, restoreInfo);
156+
changes = builder.ToString();
157+
}
158+
159+
DotNetProjectSystemEventSource.Instance.NominateForRestore(projectFilePath: _project.FullPath, changes: changes);
160+
}
161+
162+
_lastRestoreInfo = restoreInfo;
163+
144164
JoinableTask<bool> joinableTask = JoinableFactory.RunAsync(() =>
145165
{
146166
return NominateForRestoreAsync(restoreInfo, value.ConfiguredInputs, token);
@@ -152,6 +172,13 @@ private async Task<bool> RestoreCoreAsync(PackageRestoreUnconfiguredInput value)
152172
registerFaultHandler: true);
153173

154174
return await joinableTask;
175+
176+
Hash CalculateHash()
177+
{
178+
using var hasher = new IncrementalHasher();
179+
restoreInfo.AddToHash(hasher);
180+
return hasher.GetHashAndReset();
181+
}
155182
}
156183

157184
private async Task<bool> NominateForRestoreAsync(ProjectRestoreInfo restoreInfo, IReadOnlyCollection<PackageRestoreConfiguredInput> versions, CancellationToken cancellationToken)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
2+
3+
using Microsoft.VisualStudio.Text;
4+
5+
namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;
6+
7+
/// <summary>
8+
/// A data object at some level within the restore state snapshot, <see cref="ProjectRestoreInfo"/>.
9+
/// </summary>
10+
/// <typeparam name="T"></typeparam>
11+
internal interface IRestoreState<T> where T : class
12+
{
13+
/// <summary>
14+
/// Adds state from this object to <paramref name="hasher"/>.
15+
/// </summary>
16+
/// <param name="hasher"></param>
17+
void AddToHash(IncrementalHasher hasher);
18+
19+
/// <summary>
20+
/// Compares all state between this instance and <paramref name="after"/>, and logs details of any changes.
21+
/// </summary>
22+
void DescribeChanges(RestoreStateComparisonBuilder builder, T after);
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
2+
3+
using Microsoft.VisualStudio.Text;
4+
5+
namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;
6+
7+
internal static class IncrementalHasherExtensions
8+
{
9+
public static void AppendProperty(this IncrementalHasher hasher, string name, string value)
10+
{
11+
hasher.Append(name);
12+
hasher.Append("|");
13+
hasher.Append(value);
14+
}
15+
16+
public static void AppendArray<T>(this IncrementalHasher hasher, ImmutableArray<T> items) where T : class, IRestoreState<T>
17+
{
18+
foreach (T item in items)
19+
{
20+
item.AddToHash(hasher);
21+
}
22+
}
23+
}
Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,49 @@
11
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
22

3+
using Microsoft.VisualStudio.Text;
4+
35
namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;
46

57
/// <summary>
6-
/// A complete set of restore data for a project.
8+
/// A complete set of restore data for a project.
79
/// </summary>
8-
internal class ProjectRestoreInfo
10+
internal sealed class ProjectRestoreInfo(
11+
string msbuildProjectExtensionsPath,
12+
string projectAssetsFilePath,
13+
string originalTargetFrameworks,
14+
ImmutableArray<TargetFrameworkInfo> targetFrameworks,
15+
ImmutableArray<ReferenceItem> toolReferences)
16+
: IRestoreState<ProjectRestoreInfo>
917
{
10-
// If additional fields/properties are added to this class, please update RestoreHasher
18+
// IMPORTANT: If additional state is added, update AddToHash and DescribeChanges below.
1119

12-
public ProjectRestoreInfo(string msbuildProjectExtensionsPath, string projectAssetsFilePath, string originalTargetFrameworks, ImmutableArray<TargetFrameworkInfo> targetFrameworks, ImmutableArray<ReferenceItem> toolReferences)
13-
{
14-
MSBuildProjectExtensionsPath = msbuildProjectExtensionsPath;
15-
ProjectAssetsFilePath = projectAssetsFilePath;
16-
OriginalTargetFrameworks = originalTargetFrameworks;
17-
TargetFrameworks = targetFrameworks;
18-
ToolReferences = toolReferences;
19-
}
20+
public string MSBuildProjectExtensionsPath { get; } = msbuildProjectExtensionsPath;
21+
22+
public string ProjectAssetsFilePath { get; } = projectAssetsFilePath;
23+
24+
public string OriginalTargetFrameworks { get; } = originalTargetFrameworks;
2025

21-
public string MSBuildProjectExtensionsPath { get; }
26+
public ImmutableArray<TargetFrameworkInfo> TargetFrameworks { get; } = targetFrameworks;
2227

23-
public string ProjectAssetsFilePath { get; }
28+
public ImmutableArray<ReferenceItem> ToolReferences { get; } = toolReferences;
2429

25-
public string OriginalTargetFrameworks { get; }
30+
public void AddToHash(IncrementalHasher hasher)
31+
{
32+
hasher.AppendProperty(nameof(ProjectAssetsFilePath), ProjectAssetsFilePath);
33+
hasher.AppendProperty(nameof(MSBuildProjectExtensionsPath), MSBuildProjectExtensionsPath);
34+
hasher.AppendProperty(nameof(OriginalTargetFrameworks), OriginalTargetFrameworks);
2635

27-
public ImmutableArray<TargetFrameworkInfo> TargetFrameworks { get; }
36+
hasher.AppendArray(TargetFrameworks);
37+
hasher.AppendArray(ToolReferences);
38+
}
2839

29-
public ImmutableArray<ReferenceItem> ToolReferences { get; }
40+
public void DescribeChanges(RestoreStateComparisonBuilder builder, ProjectRestoreInfo after)
41+
{
42+
builder.CompareString(MSBuildProjectExtensionsPath, after.MSBuildProjectExtensionsPath, nameof(MSBuildProjectExtensionsPath));
43+
builder.CompareString(ProjectAssetsFilePath, after.ProjectAssetsFilePath, nameof(ProjectAssetsFilePath));
44+
builder.CompareString(OriginalTargetFrameworks, after.OriginalTargetFrameworks, nameof(OriginalTargetFrameworks));
45+
46+
builder.CompareArray(TargetFrameworks, after.TargetFrameworks, nameof(TargetFrameworks));
47+
builder.CompareArray(ToolReferences, after.ToolReferences, nameof(ToolReferences));
48+
}
3049
}
Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,49 @@
11
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
22

33
using System.Diagnostics;
4+
using Microsoft.VisualStudio.Text;
45

56
namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;
67

78
/// <summary>
8-
/// Represents a single package, tool or project reference.
9+
/// Represents a reference item involved in package restore, with its associated metadata.
910
/// </summary>
1011
[DebuggerDisplay("Name = {Name}")]
11-
internal class ReferenceItem
12+
internal sealed class ReferenceItem(
13+
string name,
14+
IImmutableDictionary<string, string> metadata)
15+
: IRestoreState<ReferenceItem>
1216
{
13-
// If additional fields/properties are added to this class, please update RestoreHasher
17+
// IMPORTANT: If additional state is added, update AddToHash and DescribeChanges below.
1418

15-
public ReferenceItem(string name, IImmutableDictionary<string, string> properties)
19+
/// <summary>
20+
/// Gets the name (item spec) of the reference.
21+
/// </summary>
22+
public string Name { get; } = name;
23+
24+
/// <summary>
25+
/// Gets the name/value pair metadata associated with the reference.
26+
/// </summary>
27+
public IImmutableDictionary<string, string> Metadata { get; } = metadata;
28+
29+
public void AddToHash(IncrementalHasher hasher)
1630
{
17-
Requires.NotNullOrEmpty(name);
31+
hasher.AppendProperty(nameof(Name), Name);
1832

19-
Name = name;
20-
Properties = properties;
33+
foreach ((string key, string value) in Metadata)
34+
{
35+
hasher.AppendProperty(key, value);
36+
}
2137
}
2238

23-
public string Name { get; }
39+
public void DescribeChanges(RestoreStateComparisonBuilder builder, ReferenceItem after)
40+
{
41+
builder.PushScope(Name);
42+
43+
builder.CompareString(Name, after.Name, name: "%(Identity)");
44+
45+
builder.CompareDictionary(Metadata, after.Metadata);
2446

25-
public IImmutableDictionary<string, string> Properties { get; }
47+
builder.PopScope();
48+
}
2649
}

src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/PackageRestore/Snapshots/RestoreBuilder.cs

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,44 @@ namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;
99
/// </summary>
1010
internal static class RestoreBuilder
1111
{
12-
public static readonly ImmutableArray<TargetFrameworkInfo> EmptyTargetFrameworks = [];
13-
public static readonly ImmutableArray<ReferenceItem> EmptyReferences = [];
14-
1512
/// <summary>
1613
/// Converts an immutable dictionary of rule snapshot data into an <see cref="ProjectRestoreInfo"/> instance.
1714
/// </summary>
1815
public static ProjectRestoreInfo ToProjectRestoreInfo(IImmutableDictionary<string, IProjectRuleSnapshot> update)
1916
{
2017
IImmutableDictionary<string, string> properties = update.GetSnapshotOrEmpty(NuGetRestore.SchemaName).Properties;
21-
IProjectRuleSnapshot frameworkReferences = update.GetSnapshotOrEmpty(CollectedFrameworkReference.SchemaName);
22-
IProjectRuleSnapshot packageDownloads = update.GetSnapshotOrEmpty(CollectedPackageDownload.SchemaName);
23-
IProjectRuleSnapshot projectReferences = update.GetSnapshotOrEmpty(EvaluatedProjectReference.SchemaName);
24-
IProjectRuleSnapshot packageReferences = update.GetSnapshotOrEmpty(CollectedPackageReference.SchemaName);
25-
IProjectRuleSnapshot packageVersions = update.GetSnapshotOrEmpty(CollectedPackageVersion.SchemaName);
26-
IProjectRuleSnapshot nuGetAuditSuppress = update.GetSnapshotOrEmpty(CollectedNuGetAuditSuppressions.SchemaName);
27-
IProjectRuleSnapshot prunePackageReferences = update.GetSnapshotOrEmpty(CollectedPrunePackageReference.SchemaName);
28-
IProjectRuleSnapshot toolReferences = update.GetSnapshotOrEmpty(DotNetCliToolReference.SchemaName);
2918

3019
// For certain project types such as UWP, "TargetFrameworkMoniker" != the moniker that restore uses
3120
string targetMoniker = properties.GetPropertyOrEmpty(NuGetRestore.NuGetTargetMonikerProperty);
3221
if (targetMoniker.Length == 0)
3322
targetMoniker = properties.GetPropertyOrEmpty(NuGetRestore.TargetFrameworkMonikerProperty);
3423

35-
TargetFrameworkInfo frameworkInfo = new TargetFrameworkInfo(
24+
TargetFrameworkInfo frameworkInfo = new(
3625
targetMoniker,
37-
ToReferenceItems(frameworkReferences.Items),
38-
ToReferenceItems(packageDownloads.Items),
39-
ToReferenceItems(projectReferences.Items),
40-
ToReferenceItems(packageReferences.Items),
41-
ToReferenceItems(packageVersions.Items),
42-
ToReferenceItems(nuGetAuditSuppress.Items),
43-
ToReferenceItems(prunePackageReferences.Items),
44-
properties);
26+
frameworkReferences: GetReferenceItems(CollectedFrameworkReference.SchemaName),
27+
packageDownloads: GetReferenceItems(CollectedPackageDownload.SchemaName),
28+
projectReferences: GetReferenceItems(EvaluatedProjectReference.SchemaName),
29+
packageReferences: GetReferenceItems(CollectedPackageReference.SchemaName),
30+
centralPackageVersions: GetReferenceItems(CollectedPackageVersion.SchemaName),
31+
nuGetAuditSuppress: GetReferenceItems(CollectedNuGetAuditSuppressions.SchemaName),
32+
prunePackageReferences: GetReferenceItems(CollectedPrunePackageReference.SchemaName),
33+
properties: properties);
4534

4635
return new ProjectRestoreInfo(
47-
properties.GetPropertyOrEmpty(NuGetRestore.MSBuildProjectExtensionsPathProperty),
48-
properties.GetPropertyOrEmpty(NuGetRestore.ProjectAssetsFileProperty),
49-
properties.GetPropertyOrEmpty(NuGetRestore.TargetFrameworksProperty),
50-
EmptyTargetFrameworks.Add(frameworkInfo),
51-
ToReferenceItems(toolReferences.Items));
36+
msbuildProjectExtensionsPath: properties.GetPropertyOrEmpty(NuGetRestore.MSBuildProjectExtensionsPathProperty),
37+
projectAssetsFilePath: properties.GetPropertyOrEmpty(NuGetRestore.ProjectAssetsFileProperty),
38+
originalTargetFrameworks: properties.GetPropertyOrEmpty(NuGetRestore.TargetFrameworksProperty),
39+
targetFrameworks: [frameworkInfo],
40+
toolReferences: GetReferenceItems(DotNetCliToolReference.SchemaName));
5241

53-
static ImmutableArray<ReferenceItem> ToReferenceItems(IImmutableDictionary<string, IImmutableDictionary<string, string>> items)
42+
ImmutableArray<ReferenceItem> GetReferenceItems(string schemaName)
5443
{
55-
return items.ToImmutableArray(static (name, metadata) => new ReferenceItem(name, metadata));
44+
if (!update.TryGetValue(schemaName, out IProjectRuleSnapshot? result))
45+
{
46+
return [];
47+
}
48+
49+
return result.Items.ToImmutableArray(static (name, metadata) => new ReferenceItem(name, metadata));
5650
}
5751
}
5852
}

src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/PackageRestore/Snapshots/RestoreComparer.ReferenceItemEqualityComparer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public override bool Equals(ReferenceItem? x, ReferenceItem? y)
1414
if (!StringComparers.ItemNames.Equals(x.Name, y.Name))
1515
return false;
1616

17-
if (!PropertiesAreEqual(x.Properties, y.Properties))
17+
if (!PropertiesAreEqual(x.Metadata, y.Metadata))
1818
return false;
1919

2020
return true;

0 commit comments

Comments
 (0)