File: CommandLine\ReportAnalyzerUtil.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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 file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Microsoft.CodeAnalysis
{
    internal static class ReportAnalyzerUtil
    {
        public static void Report(
            TextWriter consoleOutput,
            AnalyzerDriver? analyzerDriver,
            GeneratorDriverTimingInfo? driverTimingInfo,
            CultureInfo culture,
            bool isConcurrentBuild)
        {
            if (isConcurrentBuild && (analyzerDriver is { } || driverTimingInfo is { }))
            {
                consoleOutput.WriteLine(CodeAnalysisResources.MultithreadedAnalyzerExecutionNote);
                consoleOutput.WriteLine();
            }
 
            if (analyzerDriver is { })
            {
                ReportAnalyzerExecutionTime(consoleOutput, analyzerDriver, culture);
            }
 
            if (driverTimingInfo is { } info)
            {
                ReportGeneratorExecutionTime(consoleOutput, info, culture);
            }
        }
 
        public static string GetFormattedAnalyzerExecutionTime(double executionTime, CultureInfo culture) =>
            executionTime < 0.001 ?
                string.Format(culture, "{0,8:<0.000}", 0.001) :
                string.Format(culture, "{0,8:##0.000}", executionTime);
 
        public static string GetFormattedAnalyzerExecutionPercentage(int percentage, CultureInfo culture) =>
            string.Format("{0,5}", percentage < 1 ? "<1" : percentage.ToString(culture));
 
        private static string GetColumnHeader(string kind)
        {
            var time = string.Format("{0,8}", CodeAnalysisResources.AnalyzerExecutionTimeColumnHeader);
            var percent = string.Format("{0,5}", "%");
            return time + percent + "   " + kind;
        }
 
        private static string GetColumnEntry(double totalSeconds, int percentage, string? name, CultureInfo culture)
        {
            var time = GetFormattedAnalyzerExecutionTime(totalSeconds, culture);
            var percent = GetFormattedAnalyzerExecutionPercentage(percentage, culture);
 
            return time + percent + "   " + name;
        }
 
        private static void ReportAnalyzerExecutionTime(TextWriter consoleOutput, AnalyzerDriver analyzerDriver, CultureInfo culture)
        {
            Debug.Assert(analyzerDriver.AnalyzerExecutionTimes != null);
            if (analyzerDriver.AnalyzerExecutionTimes.IsEmpty)
            {
                return;
            }
 
            var totalAnalyzerExecutionTime = analyzerDriver.AnalyzerExecutionTimes.Sum(kvp => kvp.Value.TotalSeconds);
            consoleOutput.WriteLine(string.Format(CodeAnalysisResources.AnalyzerTotalExecutionTime, totalAnalyzerExecutionTime.ToString("##0.000", culture)));
            consoleOutput.WriteLine();
 
            // Table header
            consoleOutput.WriteLine(GetColumnHeader(CodeAnalysisResources.AnalyzerNameColumnHeader));
 
            // Table rows grouped by assembly.
            var analyzersByAssembly = analyzerDriver.AnalyzerExecutionTimes
                .GroupBy(kvp => kvp.Key.GetType().Assembly)
                .OrderByDescending(kvp => kvp.Sum(entry => entry.Value.Ticks));
            foreach (var analyzerGroup in analyzersByAssembly)
            {
                var executionTime = analyzerGroup.Sum(kvp => kvp.Value.TotalSeconds);
                var percentage = (int)(executionTime * 100 / totalAnalyzerExecutionTime);
                consoleOutput.WriteLine(GetColumnEntry(executionTime, percentage, analyzerGroup.Key.FullName, culture));
 
                // Rows for each diagnostic analyzer in the assembly.
                foreach (var kvp in analyzerGroup.OrderByDescending(kvp => kvp.Value))
                {
                    executionTime = kvp.Value.TotalSeconds;
                    percentage = (int)(executionTime * 100 / totalAnalyzerExecutionTime);
 
                    var analyzerIds = string.Join(", ", GetSupportedIds(kvp.Key).Distinct().OrderBy(id => id));
                    var analyzerNameColumn = $"   {kvp.Key} ({analyzerIds})";
                    consoleOutput.WriteLine(GetColumnEntry(executionTime, percentage, analyzerNameColumn, culture));
                }
 
                consoleOutput.WriteLine();
            }
        }
 
        private static IEnumerable<string> GetSupportedIds(DiagnosticAnalyzer analyzer)
            => analyzer switch
            {
                DiagnosticSuppressor suppressor => suppressor.SupportedSuppressions.Select(s => s.Id),
                _ => analyzer.SupportedDiagnostics.Select(d => d.Id),
            };
 
        private static void ReportGeneratorExecutionTime(TextWriter consoleOutput, GeneratorDriverTimingInfo driverTimingInfo, CultureInfo culture)
        {
            if (driverTimingInfo.GeneratorTimes.IsEmpty)
            {
                return;
            }
 
            var totalTime = driverTimingInfo.ElapsedTime.TotalSeconds;
            consoleOutput.WriteLine(string.Format(CodeAnalysisResources.GeneratorTotalExecutionTime, totalTime.ToString("##0.000", culture)));
            consoleOutput.WriteLine();
 
            // Table header
            consoleOutput.WriteLine(GetColumnHeader(CodeAnalysisResources.GeneratorNameColumnHeader));
 
            // Table rows grouped by assembly.
            var generatorsByAssembly = driverTimingInfo.GeneratorTimes
                .GroupBy(t => t.Generator.GetGeneratorType().Assembly)
                .OrderByDescending(kvp => kvp.Sum(entry => entry.ElapsedTime.Ticks));
 
            foreach (var generatorGroup in generatorsByAssembly)
            {
                var executionTime = generatorGroup.Sum(x => x.ElapsedTime.TotalSeconds);
                var percentage = (int)(executionTime * 100 / totalTime);
                consoleOutput.WriteLine(GetColumnEntry(executionTime, percentage, generatorGroup.Key.FullName, culture));
 
                foreach (var timingInfo in generatorGroup.OrderByDescending(x => x.ElapsedTime))
                {
                    executionTime = timingInfo.ElapsedTime.TotalSeconds;
                    percentage = (int)(executionTime * 100 / totalTime);
                    consoleOutput.WriteLine(GetColumnEntry(executionTime, percentage, "   " + timingInfo.Generator.GetGeneratorType().FullName, culture));
                }
            }
        }
    }
}