From d71ad34d9638ba52efaf897d443c84ef27190fcd Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Thu, 5 Jun 2025 16:45:31 -0700 Subject: [PATCH 1/4] Add more ETW events to trace assembly loading --- .../CodeAnalysisEventSource.Common.cs | 85 +++++++++++++++++-- .../AnalyzerAssemblyLoader.Core.cs | 8 +- .../IAnalyzerPathResolver.cs | 2 +- .../ProjectSystem/ProjectSystemProject.cs | 6 ++ 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs b/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs index b8e9e1df6ab39..94aaac3d06fb0 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs +++ b/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs @@ -13,6 +13,7 @@ public static class Keywords { public const EventKeywords Performance = (EventKeywords)1; public const EventKeywords Correctness = (EventKeywords)2; + public const EventKeywords AnalyzerLoading = (EventKeywords)4; } public static class Tasks @@ -100,18 +101,88 @@ internal unsafe void NodeTransform(int nodeHashCode, string name, string tableTy [Event(8, Message = "Server compilation {0} completed", Keywords = Keywords.Performance, Level = EventLevel.Informational, Opcode = EventOpcode.Stop, Task = Tasks.Compilation)] internal void StopServerCompilation(string name) => WriteEvent(8, name); - [Event(9, Message = "ALC for directory '{0}' created", Keywords = Keywords.Performance, Level = EventLevel.Informational, Opcode = EventOpcode.Start, Task = Tasks.AnalyzerAssemblyLoader)] - internal void CreateAssemblyLoadContext(string directory) => WriteEvent(9, directory); + [Event(9, Message = "ALC for directory '{0}' created", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational, Opcode = EventOpcode.Start, Task = Tasks.AnalyzerAssemblyLoader)] + internal void CreateAssemblyLoadContext(string directory, string? alc) => WriteEvent(9, directory, alc); - [Event(10, Message = "ALC for directory '{0}' disposed", Keywords = Keywords.Performance, Level = EventLevel.Informational, Opcode = EventOpcode.Stop, Task = Tasks.AnalyzerAssemblyLoader)] - internal void DisposeAssemblyLoadContext(string directory) => WriteEvent(10, directory); + [Event(10, Message = "ALC for directory '{0}' disposed", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational, Opcode = EventOpcode.Stop, Task = Tasks.AnalyzerAssemblyLoader)] + internal void DisposeAssemblyLoadContext(string directory, string? alc) => WriteEvent(10, directory, alc); - [Event(11, Message = "ALC for directory '{0}' disposal failed with exception '{1}'", Keywords = Keywords.Performance, Level = EventLevel.Error, Opcode = EventOpcode.Stop, Task = Tasks.AnalyzerAssemblyLoader)] - internal void DisposeAssemblyLoadContextException(string directory, string errorMessage) => WriteEvent(11, directory, errorMessage); + [Event(11, Message = "ALC for directory '{0}' disposal failed with exception '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Error, Opcode = EventOpcode.Stop, Task = Tasks.AnalyzerAssemblyLoader)] + internal void DisposeAssemblyLoadContextException(string directory, string errorMessage, string? alc) => WriteEvent(11, directory, errorMessage, alc); - [Event(12, Message = "CreateNonLockingLoader", Keywords = Keywords.Performance, Level = EventLevel.Informational, Task = Tasks.AnalyzerAssemblyLoader)] + [Event(12, Message = "CreateNonLockingLoader", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational, Task = Tasks.AnalyzerAssemblyLoader)] internal void CreateNonLockingLoader(string directory) => WriteEvent(12, directory); + [Event(13, Message = "Request add Analyzer reference '{0}' to project '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal void AnalyzerReferenceRequestAddToProject(string path, string projectName) => WriteEvent(13, path, projectName); + + [Event(14, Message = "Analyzer reference '{0}' was added to project '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal void AnalyzerReferenceAddedToProject(string path, string projectName) => WriteEvent(14, path, projectName); + + [Event(15, Message = "Request remove Analyzer reference '{0}' from project '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal void AnalyzerReferenceRequestRemoveFromProject(string path, string projectName) => WriteEvent(15, path, projectName); + + [Event(16, Message = "Analyzer reference '{0}' was removed from project '{1}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal void AnalyzerReferenceRemovedFromProject(string path, string projectName) => WriteEvent(16, path, projectName); + + [Event(17, Message = "Analyzer reference was redirected by '{0}' from '{1}' to '{2}' for project '{3}'", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Verbose, Task = Tasks.BuildStateTable)] + internal unsafe void AnanlyzerReferenceRedirected(string redirectorType, string originalPath, string newPath, string project) + { + if (IsEnabled()) + { + fixed (char* redirectorTypeBytes = redirectorType) + fixed (char* originalPathBytes = originalPath) + fixed (char* newPathBytes = newPath) + fixed (char* projectBytes = project) + { + Span data = + [ + GetEventDataForString(redirectorType, redirectorTypeBytes), + GetEventDataForString(originalPath, originalPathBytes), + GetEventDataForString(newPath, newPathBytes), + GetEventDataForString(project, projectBytes), + ]; + + fixed (EventData* dataPtr = data) + { + WriteEventCore(eventId: 17, data.Length, dataPtr); + } + } + } + } + + [Event(18, Message = "ALC for directory '{0}': Assembly '{1}' was resolved by '{2}' ", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal unsafe void ResolvedAssembly(string directory, string assemblyName, string resolver, string filePath, string alc) + { + if (IsEnabled()) + { + fixed (char* directoryBytes = directory) + fixed (char* assemblyNameBytes = assemblyName) + fixed (char* resolverBytes = resolver) + fixed (char* filePathBytes = filePath) + fixed (char* alcBytes = alc) + { + Span data = + [ + GetEventDataForString(directory, directoryBytes), + GetEventDataForString(assemblyName, assemblyNameBytes), + GetEventDataForString(resolver, resolverBytes), + GetEventDataForString(filePath, filePathBytes), + GetEventDataForString(alc, alcBytes), + ]; + + fixed (EventData* dataPtr = data) + { + WriteEventCore(eventId: 18, data.Length, dataPtr); + } + } + } + } + + [Event(19, Message = "ALC for directory '{0}': Failed to resolve assembly '{1}' ", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] + internal unsafe void ResolveAssemblyFailed(string directory, string assemblyName) => WriteEvent(19, directory, assemblyName); + + private static unsafe EventData GetEventDataForString(string value, char* ptr) { fixed (char* ptr2 = value) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs index a4b6fb9a55513..82d84ace193ab 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs @@ -87,8 +87,8 @@ private partial Assembly Load(AssemblyName assemblyName, string resolvedPath) { if (!_loadContextByDirectory.TryGetValue(fullDirectoryPath, out loadContext)) { - CodeAnalysisEventSource.Log.CreateAssemblyLoadContext(fullDirectoryPath); loadContext = new DirectoryLoadContext(fullDirectoryPath, this); + CodeAnalysisEventSource.Log.CreateAssemblyLoadContext(fullDirectoryPath, loadContext.ToString()); _loadContextByDirectory[fullDirectoryPath] = loadContext; } } @@ -179,11 +179,11 @@ private partial void DisposeWorker() try { context.Unload(); - CodeAnalysisEventSource.Log.DisposeAssemblyLoadContext(context.Directory); + CodeAnalysisEventSource.Log.DisposeAssemblyLoadContext(context.Directory, ToString()); } catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) { - CodeAnalysisEventSource.Log.DisposeAssemblyLoadContextException(context.Directory, ex.ToString()); + CodeAnalysisEventSource.Log.DisposeAssemblyLoadContextException(context.Directory, ex.ToString(), ToString()); } } @@ -209,10 +209,12 @@ public DirectoryLoadContext(string directory, AnalyzerAssemblyLoader loader) var assembly = resolver.Resolve(_loader, assemblyName, this, Directory); if (assembly is not null) { + CodeAnalysisEventSource.Log.ResolvedAssembly(Directory, assemblyName.ToString(), resolver.GetType().Name, assembly.Location, GetLoadContext(assembly)!.ToString()); return assembly; } } + CodeAnalysisEventSource.Log.ResolveAssemblyFailed(Directory, assemblyName.ToString()); return null; } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerPathResolver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerPathResolver.cs index 513b7fd7d20eb..de498db0836d2 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerPathResolver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/IAnalyzerPathResolver.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis { /// - /// This interface gives the host the ability to control the actaul path used to load an analyzer into the + /// This interface gives the host the ability to control the actual path used to load an analyzer into the /// compiler. /// /// Instances of these types are considered in the order they are added to the . diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index a08fc177c1c92..4f6e9559d07bf 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -1054,6 +1054,7 @@ private void OnDynamicFileInfoUpdated(object? sender, string dynamicFilePath) public void AddAnalyzerReference(string fullPath) { CompilerPathUtilities.RequireAbsolutePath(fullPath, nameof(fullPath)); + CodeAnalysisEventSource.Log.AnalyzerReferenceRequestAddToProject(fullPath, DisplayName); var mappedPaths = GetMappedAnalyzerPaths(fullPath); @@ -1084,6 +1085,7 @@ public void AddAnalyzerReference(string fullPath) // Are we adding one we just recently removed? If so, we can just keep using that one, and avoid // removing it once we apply the batch _projectAnalyzerPaths.Add(mappedFullPath); + CodeAnalysisEventSource.Log.AnalyzerReferenceAddedToProject(mappedFullPath, DisplayName); if (!_analyzersRemovedInBatch.Remove(mappedFullPath)) _analyzersAddedInBatch.Add(mappedFullPath); @@ -1098,6 +1100,8 @@ public void RemoveAnalyzerReference(string fullPath) if (string.IsNullOrEmpty(fullPath)) throw new ArgumentException("message", nameof(fullPath)); + CodeAnalysisEventSource.Log.AnalyzerReferenceRequestRemoveFromProject(fullPath, DisplayName); + var mappedPaths = GetMappedAnalyzerPaths(fullPath); bool containsSdkCodeStyleAnalyzers; @@ -1125,6 +1129,7 @@ public void RemoveAnalyzerReference(string fullPath) foreach (var mappedFullPath in mappedPaths) { _projectAnalyzerPaths.Remove(mappedFullPath); + CodeAnalysisEventSource.Log.AnalyzerReferenceRemovedFromProject(fullPath, DisplayName); // This analyzer may be one we've just added in the same batch; in that case, just don't add it in // the first place. @@ -1175,6 +1180,7 @@ private OneOrMany GetMappedAnalyzerPaths(string fullPath) if (redirectedPath == null) { redirectedPath = currentlyRedirectedPath; + CodeAnalysisEventSource.Log.AnanlyzerReferenceRedirected(redirector.GetType().Name, fullPath, redirectedPath, DisplayName); } else if (redirectedPath != currentlyRedirectedPath) { From fe3b27ffe3b5b695d4832f75190c66537deb2466 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Fri, 6 Jun 2025 09:00:25 -0700 Subject: [PATCH 2/4] Update src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs Co-authored-by: Joey Robichaud --- src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs b/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs index 94aaac3d06fb0..852c3a348af75 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs +++ b/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs @@ -182,7 +182,6 @@ internal unsafe void ResolvedAssembly(string directory, string assemblyName, str [Event(19, Message = "ALC for directory '{0}': Failed to resolve assembly '{1}' ", Keywords = Keywords.AnalyzerLoading, Level = EventLevel.Informational)] internal unsafe void ResolveAssemblyFailed(string directory, string assemblyName) => WriteEvent(19, directory, assemblyName); - private static unsafe EventData GetEventDataForString(string value, char* ptr) { fixed (char* ptr2 = value) From 9d1b58a29818a993bed3e0a8617dc71b2f914f37 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Fri, 6 Jun 2025 09:02:09 -0700 Subject: [PATCH 3/4] Put correct alc reference --- .../DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs index 82d84ace193ab..1fe98873aa0da 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs @@ -179,11 +179,11 @@ private partial void DisposeWorker() try { context.Unload(); - CodeAnalysisEventSource.Log.DisposeAssemblyLoadContext(context.Directory, ToString()); + CodeAnalysisEventSource.Log.DisposeAssemblyLoadContext(context.Directory, context.ToString()); } catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) { - CodeAnalysisEventSource.Log.DisposeAssemblyLoadContextException(context.Directory, ex.ToString(), ToString()); + CodeAnalysisEventSource.Log.DisposeAssemblyLoadContextException(context.Directory, ex.ToString(), context.ToString()); } } From cac213fdf6b3411e268b6dabdc11c057505a3dfa Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Fri, 6 Jun 2025 12:04:31 -0700 Subject: [PATCH 4/4] Use binary literals --- .../Core/Portable/CodeAnalysisEventSource.Common.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs b/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs index 852c3a348af75..4e3dba61cd567 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs +++ b/src/Compilers/Core/Portable/CodeAnalysisEventSource.Common.cs @@ -11,9 +11,9 @@ internal sealed partial class CodeAnalysisEventSource : EventSource { public static class Keywords { - public const EventKeywords Performance = (EventKeywords)1; - public const EventKeywords Correctness = (EventKeywords)2; - public const EventKeywords AnalyzerLoading = (EventKeywords)4; + public const EventKeywords Performance = (EventKeywords)0b001; + public const EventKeywords Correctness = (EventKeywords)0b010; + public const EventKeywords AnalyzerLoading = (EventKeywords)0b100; } public static class Tasks