Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;

namespace Microsoft.CodeAnalysis.Remote.Diagnostics;
namespace Microsoft.CodeAnalysis.Diagnostics;

internal interface IPerformanceTrackerService : IWorkspaceService
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,20 @@
using Microsoft.CodeAnalysis.Diagnostics.Telemetry;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Workspaces.Diagnostics;
using Roslyn.Utilities;
using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger;

namespace Microsoft.CodeAnalysis.Diagnostics;

internal sealed class InProcOrRemoteHostAnalyzerRunner
internal sealed class InProcOrRemoteHostAnalyzerRunner(
DiagnosticAnalyzerInfoCache analyzerInfoCache,
IAsynchronousOperationListener? operationListener = null)
{
private readonly IAsynchronousOperationListener _asyncOperationListener;
public DiagnosticAnalyzerInfoCache AnalyzerInfoCache { get; }

public InProcOrRemoteHostAnalyzerRunner(
DiagnosticAnalyzerInfoCache analyzerInfoCache,
IAsynchronousOperationListener? operationListener = null)
{
AnalyzerInfoCache = analyzerInfoCache;
_asyncOperationListener = operationListener ?? AsynchronousOperationListenerProvider.NullListener;
}
private readonly IAsynchronousOperationListener _asyncOperationListener = operationListener ?? AsynchronousOperationListenerProvider.NullListener;
public DiagnosticAnalyzerInfoCache AnalyzerInfoCache { get; } = analyzerInfoCache;

public Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>> AnalyzeDocumentAsync(
DocumentAnalysisScope documentAnalysisScope,
Expand All @@ -57,31 +51,17 @@ private async Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAna
bool getTelemetryInfo,
CancellationToken cancellationToken)
{
var result = await AnalyzeCoreAsync().ConfigureAwait(false);
var result = await AnalyzeInProcAsync(
documentAnalysisScope, project, compilationWithAnalyzers,
logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false);
Debug.Assert(getTelemetryInfo || result.TelemetryInfo.IsEmpty);
return result;

async Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>> AnalyzeCoreAsync()
{
Contract.ThrowIfFalse(compilationWithAnalyzers.HasAnalyzers);

var remoteHostClient = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
if (remoteHostClient != null)
{
return await AnalyzeOutOfProcAsync(documentAnalysisScope, project, compilationWithAnalyzers, remoteHostClient,
logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false);
}

return await AnalyzeInProcAsync(documentAnalysisScope, project, compilationWithAnalyzers,
client: null, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false);
}
}

private async Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>> AnalyzeInProcAsync(
DocumentAnalysisScope? documentAnalysisScope,
Project project,
CompilationWithAnalyzersPair compilationWithAnalyzers,
RemoteHostClient? client,
bool logPerformanceInfo,
bool getTelemetryInfo,
CancellationToken cancellationToken)
Expand All @@ -93,7 +73,9 @@ private async Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAna
{
// if remote host is there, report performance data
var asyncToken = _asyncOperationListener.BeginAsyncOperation(nameof(AnalyzeInProcAsync));
var _ = FireAndForgetReportAnalyzerPerformanceAsync(documentAnalysisScope, project, client, analysisResult, cancellationToken).CompletesAsyncOperation(asyncToken);
var _ = Task.Run(
() => ReportAnalyzerPerformance(documentAnalysisScope, project, analysisResult, cancellationToken),
cancellationToken).CompletesAsyncOperation(asyncToken);
}

var projectAnalyzers = documentAnalysisScope?.ProjectAnalyzers ?? compilationWithAnalyzers.ProjectAnalyzers;
Expand All @@ -120,18 +102,12 @@ private async Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAna
return DiagnosticAnalysisResultMap.Create(result, telemetry);
}

private async Task FireAndForgetReportAnalyzerPerformanceAsync(
private void ReportAnalyzerPerformance(
DocumentAnalysisScope? documentAnalysisScope,
Project project,
RemoteHostClient? client,
AnalysisResultPair? analysisResult,
CancellationToken cancellationToken)
{
if (client == null)
{
return;
}

try
{
// +1 for project itself
Expand All @@ -144,82 +120,15 @@ private async Task FireAndForgetReportAnalyzerPerformanceAsync(
performanceInfo = performanceInfo.AddRange(analysisResult.MergedAnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(AnalyzerInfoCache));
}

_ = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService>(
(service, cancellationToken) => service.ReportAnalyzerPerformanceAsync(performanceInfo, count, forSpanAnalysis, cancellationToken),
cancellationToken).ConfigureAwait(false);
using (RoslynLogger.LogBlock(FunctionId.CodeAnalysisService_ReportAnalyzerPerformance, cancellationToken))
{
var service = project.Solution.Services.GetService<IPerformanceTrackerService>();
service?.AddSnapshot(performanceInfo, count, forSpanAnalysis);
}
}
catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex, cancellationToken))
{
// ignore all, this is fire and forget method
}
}

private static async Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>> AnalyzeOutOfProcAsync(
DocumentAnalysisScope? documentAnalysisScope,
Project project,
CompilationWithAnalyzersPair compilationWithAnalyzers,
RemoteHostClient client,
bool logPerformanceInfo,
bool getTelemetryInfo,
CancellationToken cancellationToken)
{
using var pooledObject1 = SharedPools.Default<Dictionary<string, DiagnosticAnalyzer>>().GetPooledObject();
using var pooledObject2 = SharedPools.Default<Dictionary<string, DiagnosticAnalyzer>>().GetPooledObject();
var projectAnalyzerMap = pooledObject1.Object;
var hostAnalyzerMap = pooledObject2.Object;

var projectAnalyzers = documentAnalysisScope?.ProjectAnalyzers ?? compilationWithAnalyzers.ProjectAnalyzers;
var hostAnalyzers = documentAnalysisScope?.HostAnalyzers ?? compilationWithAnalyzers.HostAnalyzers;

projectAnalyzerMap.AppendAnalyzerMap(projectAnalyzers);
hostAnalyzerMap.AppendAnalyzerMap(hostAnalyzers);

if (projectAnalyzerMap.Count == 0 && hostAnalyzerMap.Count == 0)
{
return DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>.Empty;
}

var argument = new DiagnosticArguments(
logPerformanceInfo,
getTelemetryInfo,
documentAnalysisScope?.TextDocument.Id,
documentAnalysisScope?.Span,
documentAnalysisScope?.Kind,
project.Id,
[.. projectAnalyzerMap.Keys],
[.. hostAnalyzerMap.Keys]);

var result = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, SerializableDiagnosticAnalysisResults>(
project.Solution,
invocation: (service, solutionInfo, cancellationToken) => service.CalculateDiagnosticsAsync(solutionInfo, argument, cancellationToken),
cancellationToken).ConfigureAwait(false);

if (!result.HasValue)
return DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>.Empty;

return new DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>(
result.Value.Diagnostics.ToImmutableDictionary(
entry => IReadOnlyDictionaryExtensions.GetValueOrDefault(projectAnalyzerMap, entry.analyzerId) ?? hostAnalyzerMap[entry.analyzerId],
entry => DiagnosticAnalysisResult.Create(
project,
syntaxLocalMap: Hydrate(entry.diagnosticMap.Syntax, project),
semanticLocalMap: Hydrate(entry.diagnosticMap.Semantic, project),
nonLocalMap: Hydrate(entry.diagnosticMap.NonLocal, project),
others: entry.diagnosticMap.Other)),
result.Value.Telemetry.ToImmutableDictionary(
entry => IReadOnlyDictionaryExtensions.GetValueOrDefault(projectAnalyzerMap, entry.analyzerId) ?? hostAnalyzerMap[entry.analyzerId],
entry => entry.telemetry));
}

// TODO: filter in OOP https://github.com/dotnet/roslyn/issues/47859
private static ImmutableDictionary<DocumentId, ImmutableArray<DiagnosticData>> Hydrate(ImmutableArray<(DocumentId documentId, ImmutableArray<DiagnosticData> diagnostics)> diagnosticByDocument, Project project)
=> diagnosticByDocument
.Where(
entry =>
{
// Source generated documents (for which GetTextDocument returns null) support diagnostics. Only
// filter out diagnostics where the document is non-null and SupportDiagnostics() is false.
return project.GetTextDocument(entry.documentId)?.SupportsDiagnostics() != false;
})
.ToImmutableDictionary(entry => entry.documentId, entry => entry.diagnostics);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void Method()

diagnostics = analyzerResult.GetDocumentDiagnostics(project.DocumentIds.First(), AnalysisKind.Semantic);
Assert.Equal(IDEDiagnosticIds.UseExplicitTypeDiagnosticId, diagnostics[0].Id);
Assert.Equal(isHostAnalyzer ? DiagnosticSeverity.Info : DiagnosticSeverity.Hidden, diagnostics[0].Severity);
Assert.Equal(DiagnosticSeverity.Hidden, diagnostics[0].Severity);
}

[Theory, CombinatorialData]
Expand All @@ -84,25 +84,17 @@ End Class
var project = workspace.CurrentSolution.Projects.First();
var analyzerResult = await AnalyzeAsync(workspace, project.Id, analyzerType, isHostAnalyzer);

ImmutableArray<DiagnosticData> diagnostics;
if (isHostAnalyzer)
{
Assert.True(analyzerResult.GetAllDiagnostics().IsEmpty);
}
else
{
diagnostics = analyzerResult.GetDocumentDiagnostics(project.DocumentIds.First(), AnalysisKind.Semantic);
Assert.Equal(IDEDiagnosticIds.UseNullPropagationDiagnosticId, diagnostics[0].Id);
Assert.Equal(DiagnosticSeverity.Info, diagnostics[0].Severity);
}
var diagnostics = analyzerResult.GetDocumentDiagnostics(project.DocumentIds.First(), AnalysisKind.Semantic);
Assert.Equal(IDEDiagnosticIds.UseNullPropagationDiagnosticId, diagnostics[0].Id);
Assert.Equal(DiagnosticSeverity.Info, diagnostics[0].Severity);

workspace.SetAnalyzerFallbackOptions(LanguageNames.VisualBasic, ("dotnet_style_null_propagation", "true:error"));

analyzerResult = await AnalyzeAsync(workspace, project.Id, analyzerType, isHostAnalyzer);

diagnostics = analyzerResult.GetDocumentDiagnostics(project.DocumentIds.First(), AnalysisKind.Semantic);
Assert.Equal(IDEDiagnosticIds.UseNullPropagationDiagnosticId, diagnostics[0].Id);
Assert.Equal(isHostAnalyzer ? DiagnosticSeverity.Error : DiagnosticSeverity.Info, diagnostics[0].Severity);
Assert.Equal(DiagnosticSeverity.Info, diagnostics[0].Severity);
}

[Fact]
Expand Down
97 changes: 0 additions & 97 deletions src/Workspaces/Core/Portable/Diagnostics/DiagnosticArguments.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ internal interface IRemoteDiagnosticAnalyzerService
ValueTask<ImmutableHashSet<string>> GetDeprioritizationCandidatesAsync(
Checksum solutionChecksum, ProjectId projectId, ImmutableHashSet<string> analyzerIds, CancellationToken cancellationToken);

ValueTask<SerializableDiagnosticAnalysisResults> CalculateDiagnosticsAsync(Checksum solutionChecksum, DiagnosticArguments arguments, CancellationToken cancellationToken);

ValueTask<ImmutableArray<DiagnosticData>> ComputeDiagnosticsAsync(
Checksum solutionChecksum, DocumentId documentId, TextSpan? range,
ImmutableHashSet<string> allAnalyzerIds,
Expand All @@ -53,7 +51,6 @@ ValueTask<ImmutableArray<DiagnosticData>> ProduceProjectDiagnosticsAsync(
CancellationToken cancellationToken);

ValueTask<ImmutableArray<DiagnosticData>> GetSourceGeneratorDiagnosticsAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken);
ValueTask ReportAnalyzerPerformanceAsync(ImmutableArray<AnalyzerPerformanceInfo> snapshot, int unitCount, bool forSpanAnalysis, CancellationToken cancellationToken);

ValueTask<ImmutableArray<DiagnosticDescriptorData>> GetDiagnosticDescriptorsAsync(
Checksum solutionChecksum, ProjectId projectId, string analyzerReferenceFullPath, string language, CancellationToken cancellationToken);
Expand Down
Loading
Loading