From f609dbb35c57d8bd38b49dbdde5aebfa6f5f42e6 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Tue, 10 Dec 2024 21:32:09 +1100 Subject: [PATCH 1/5] Remove commented using directive --- .../ProjectSystem/VS/Rename/RenamerTestsBase.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/Rename/RenamerTestsBase.cs b/tests/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/Rename/RenamerTestsBase.cs index aed1ccfcad..2c790feef5 100644 --- a/tests/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/Rename/RenamerTestsBase.cs +++ b/tests/Microsoft.VisualStudio.ProjectSystem.Managed.VS.UnitTests/ProjectSystem/VS/Rename/RenamerTestsBase.cs @@ -1,6 +1,5 @@ // 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. -//using System; using EnvDTE; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; From a85583c8c367b2a72d3118bdb6dd80ced448caed Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Tue, 10 Dec 2024 21:47:41 +1100 Subject: [PATCH 2/5] Protect concurrent dictionary access --- .../DesignTimeBuildLoggerProvider.cs | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs index 4479cddb01..693a546c4e 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs @@ -54,6 +54,8 @@ private sealed class DesignTimeTelemetryLogger(ITelemetryService telemetryServic /// /// The initial capacity here is based on an empty .NET Console App's DTB, which has /// around 120 targets, and aims to avoid resizing the dictionary during the build. + /// + /// Targets can run concurrently, so use of this collection must be protected by a lock. /// private readonly Dictionary _targetRecordById = new(capacity: 140); @@ -93,7 +95,10 @@ void OnTargetStarted(object sender, TargetStartedEventArgs e) e.TargetFile, file => ProjectFileClassifier.IsShippedByMicrosoft(e.TargetFile)); - _targetRecordById[id] = new TargetRecord(e.TargetName, IsMicrosoft: isMicrosoft, e.Timestamp); + lock (_targetRecordById) + { + _targetRecordById[id] = new TargetRecord(e.TargetName, IsMicrosoft: isMicrosoft, e.Timestamp); + } } } @@ -123,9 +128,12 @@ void OnBuildFinished(object sender, BuildFinishedEventArgs e) bool TryGetTargetRecord(BuildEventContext? context, [NotNullWhen(returnValue: true)] out TargetRecord? record) { - if (context is { TargetId: int id } && _targetRecordById.TryGetValue(id, out record)) + lock (_targetRecordById) { - return true; + if (context is { TargetId: int id } && _targetRecordById.TryGetValue(id, out record)) + { + return true; + } } record = null; @@ -141,14 +149,19 @@ void ILogger.Shutdown() void SendTelemetry() { - // Filter out very fast targets (to reduce the cost of ordering) then take the top ten by elapsed time. - // Note that targets can run multiple times, so the same target may appear more than once in the results. - object[][] targetDurations = _targetRecordById.Values - .Where(static record => record.Elapsed > new TimeSpan(ticks: 5 * TimeSpan.TicksPerMillisecond)) - .OrderByDescending(record => record.Elapsed) - .Take(10) - .Select(record => new object[] { GetHashedTargetName(record), record.Elapsed.Milliseconds }) - .ToArray(); + object[][] targetDurations; + + lock (_targetRecordById) + { + // Filter out very fast targets (to reduce the cost of ordering) then take the top ten by elapsed time. + // Note that targets can run multiple times, so the same target may appear more than once in the results. + targetDurations = _targetRecordById.Values + .Where(static record => record.Elapsed > new TimeSpan(ticks: 5 * TimeSpan.TicksPerMillisecond)) + .OrderByDescending(record => record.Elapsed) + .Take(10) + .Select(record => new object[] { GetHashedTargetName(record), record.Elapsed.Milliseconds }) + .ToArray(); + } telemetryService.PostProperties( TelemetryEventName.DesignTimeBuildComplete, From 3474213ac71c979a62a678c586151b8f6824f947 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Tue, 10 Dec 2024 21:48:05 +1100 Subject: [PATCH 3/5] Show info bars on build failures, not target errors --- .../VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs index 693a546c4e..a9565a10a3 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs @@ -175,8 +175,11 @@ void SendTelemetry() void ReportBuildErrors() { - if (_errorCount is 0) + if (_succeeded is not false) { + // Only report a failure if the build failed. Specific targets can have + // errors, yet if ContinueOnError is set accordingly they won't fail the + // build. We don't want to report those to the user. return; } From 2790bb5020a249c85758c451ecb562bdaa7fe7db Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Tue, 10 Dec 2024 21:52:32 +1100 Subject: [PATCH 4/5] Protect concurrent list access --- .../DesignTimeBuildLoggerProvider.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs index a9565a10a3..87d731519b 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs @@ -63,7 +63,10 @@ private sealed class DesignTimeTelemetryLogger(ITelemetryService telemetryServic /// The names of any targets that reported errors. Names may be hashed. /// May be empty even when errors exist, as not all errors come from a target. /// - private List? _errorTargets; + /// + /// Targets can run concurrently, so use of this collection must be protected by a lock. + /// + private readonly List _errorTargets = []; /// /// Whether the build succeeded. @@ -116,8 +119,10 @@ void OnErrorRaised(object sender, BuildErrorEventArgs e) if (TryGetTargetRecord(e.BuildEventContext, out TargetRecord? record)) { - _errorTargets ??= []; - _errorTargets.Add(GetHashedTargetName(record)); + lock (_errorTargets) + { + _errorTargets.Add(GetHashedTargetName(record)); + } } } @@ -150,6 +155,7 @@ void ILogger.Shutdown() void SendTelemetry() { object[][] targetDurations; + string[] errorTargets; lock (_targetRecordById) { @@ -161,6 +167,8 @@ void SendTelemetry() .Take(10) .Select(record => new object[] { GetHashedTargetName(record), record.Elapsed.Milliseconds }) .ToArray(); + + errorTargets = _errorTargets.ToArray(); } telemetryService.PostProperties( @@ -169,7 +177,7 @@ void SendTelemetry() (TelemetryPropertyName.DesignTimeBuildComplete.Succeeded, _succeeded), (TelemetryPropertyName.DesignTimeBuildComplete.Targets, new ComplexPropertyValue(targetDurations)), (TelemetryPropertyName.DesignTimeBuildComplete.ErrorCount, _errorCount), - (TelemetryPropertyName.DesignTimeBuildComplete.ErrorTargets, _errorTargets), + (TelemetryPropertyName.DesignTimeBuildComplete.ErrorTargets, errorTargets), ]); } From 63540409482dc11f78dd48e4cfb7cdf8f20dd7d1 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Tue, 10 Dec 2024 23:34:03 +1100 Subject: [PATCH 5/5] Add error code to DTB error telemetry --- .../DesignTimeBuildLoggerProvider.cs | 41 +++++++++++-------- .../Telemetry/TelemetryPropertyName.cs | 4 +- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs index 87d731519b..2f6bdbf4a0 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/DesignTimeBuilds/DesignTimeBuildLoggerProvider.cs @@ -60,24 +60,22 @@ private sealed class DesignTimeTelemetryLogger(ITelemetryService telemetryServic private readonly Dictionary _targetRecordById = new(capacity: 140); /// - /// The names of any targets that reported errors. Names may be hashed. - /// May be empty even when errors exist, as not all errors come from a target. + /// Data about any errors that occurred during the build. Note that design-time build + /// targets are supposed to be authored to respect ContinueOnError, so errors might + /// not fail the build. However it's still useful to know which targets reported errors. + /// + /// Similarly, this collection may be empty even when the build fails. /// /// /// Targets can run concurrently, so use of this collection must be protected by a lock. /// - private readonly List _errorTargets = []; + private readonly List _errors = []; /// /// Whether the build succeeded. /// private bool? _succeeded; - /// - /// The number of errors reported during the build. - /// - private int _errorCount; - LoggerVerbosity ILogger.Verbosity { get; set; } string? ILogger.Parameters { get; set; } @@ -115,14 +113,16 @@ void OnTargetFinished(object sender, TargetFinishedEventArgs e) void OnErrorRaised(object sender, BuildErrorEventArgs e) { - _errorCount++; + string? targetName = null; if (TryGetTargetRecord(e.BuildEventContext, out TargetRecord? record)) { - lock (_errorTargets) - { - _errorTargets.Add(GetHashedTargetName(record)); - } + targetName = GetHashedTargetName(record); + } + + lock (_errors) + { + _errors.Add(new(targetName, e.Code)); } } @@ -155,7 +155,7 @@ void ILogger.Shutdown() void SendTelemetry() { object[][] targetDurations; - string[] errorTargets; + ErrorData[] errors; lock (_targetRecordById) { @@ -168,7 +168,7 @@ void SendTelemetry() .Select(record => new object[] { GetHashedTargetName(record), record.Elapsed.Milliseconds }) .ToArray(); - errorTargets = _errorTargets.ToArray(); + errors = _errors.ToArray(); } telemetryService.PostProperties( @@ -176,8 +176,8 @@ void SendTelemetry() [ (TelemetryPropertyName.DesignTimeBuildComplete.Succeeded, _succeeded), (TelemetryPropertyName.DesignTimeBuildComplete.Targets, new ComplexPropertyValue(targetDurations)), - (TelemetryPropertyName.DesignTimeBuildComplete.ErrorCount, _errorCount), - (TelemetryPropertyName.DesignTimeBuildComplete.ErrorTargets, errorTargets), + (TelemetryPropertyName.DesignTimeBuildComplete.ErrorCount, errors.Length), + (TelemetryPropertyName.DesignTimeBuildComplete.Errors, new ComplexPropertyValue(errors)), ]); } @@ -232,5 +232,12 @@ private sealed record class TargetRecord(string TargetName, bool IsMicrosoft, Da public TimeSpan Elapsed => Ended - Started; } + + /// + /// Data about errors reported during design-time builds. + /// + /// Names of the targets that reported the error. May be hashed. if the target name could not be identified. + /// An error code that identifies the type of error. + private sealed record class ErrorData(string? TargetName, string ErrorCode); } } diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/Telemetry/TelemetryPropertyName.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/Telemetry/TelemetryPropertyName.cs index 5ae6d62343..0b9ff4e26e 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/Telemetry/TelemetryPropertyName.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/Telemetry/TelemetryPropertyName.cs @@ -199,9 +199,9 @@ public static class DesignTimeBuildComplete public const string ErrorCount = "vs.projectsystem.managed.designtimebuildcomplete.errorcount"; /// - /// The names of targets that failed during the design-time build. + /// Data about errors that occur during design-time builds. /// - public const string ErrorTargets = "vs.projectsystem.managed.designtimebuildcomplete.errortargets"; + public const string Errors = "vs.projectsystem.managed.designtimebuildcomplete.errors"; } public static class SDKVersion