<commit_before>﻿// 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.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Internal.Log;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.SourceGeneratorTelemetry
{
    internal class SourceGeneratorTelemetryCollectorWorkspaceService : ISourceGeneratorTelemetryCollectorWorkspaceService
    {
        private sealed record GeneratorTelemetryKey
        {
            public GeneratorTelemetryKey(ISourceGenerator generator)
            {
                Identity = new SourceGeneratorIdentity(generator);
                FileVersion = FileVersionInfo.GetVersionInfo(generator.GetGeneratorType().Assembly.Location).FileVersion ?? "(null)";
            }

            // TODO: mark these 'required' when we have the attributes in place
            public SourceGeneratorIdentity Identity { get; }
            public string FileVersion { get; }
        }

        /// <summary>
        /// Cache of the <see cref="GeneratorTelemetryKey"/> for a generator to avoid repeatedly reading version information from disk;
        /// this is a ConditionalWeakTable so having telemetry for older runs doesn't keep the generator itself alive.
        /// </summary>
        private readonly ConditionalWeakTable<ISourceGenerator, GeneratorTelemetryKey> _generatorTelemetryKeys = new();

        private readonly StatisticLogAggregator<GeneratorTelemetryKey> _elapsedTimeByGenerator = new();
        private readonly StatisticLogAggregator<GeneratorTelemetryKey> _producedFilesByGenerator = new();

        private GeneratorTelemetryKey GetTelemetryKey(ISourceGenerator generator)
            => _generatorTelemetryKeys.GetValue(generator, static g => new GeneratorTelemetryKey(g));

        public void CollectRunResult(GeneratorDriverRunResult driverRunResult, GeneratorDriverTimingInfo driverTimingInfo)
        {
            foreach (var generatorTime in driverTimingInfo.GeneratorTimes)
            {
                _elapsedTimeByGenerator.AddDataPoint(GetTelemetryKey(generatorTime.Generator), generatorTime.ElapsedTime);
            }

            foreach (var generatorResult in driverRunResult.Results)
            {
                _producedFilesByGenerator.AddDataPoint(GetTelemetryKey(generatorResult.Generator), generatorResult.GeneratedSources.Length);
            }
        }

        public void ReportStatisticsAndClear(FunctionId functionId)
        {
            foreach (var (telemetryKey, elapsedTimeCounter) in _elapsedTimeByGenerator)
            {
                // We'll log one event per generator
                Logger.Log(functionId, KeyValueLogMessage.Create(map =>
                {
                    // TODO: have a policy for when we don't have to hash them
                    map[nameof(telemetryKey.Identity.AssemblyName) + "Hashed"] = AnalyzerNameForTelemetry.ComputeSha256Hash(telemetryKey.Identity.AssemblyName);
                    map[nameof(telemetryKey.Identity.AssemblyVersion)] = telemetryKey.Identity.AssemblyVersion.ToString();
                    map[nameof(telemetryKey.Identity.TypeName) + "Hashed"] = AnalyzerNameForTelemetry.ComputeSha256Hash(telemetryKey.Identity.TypeName);
                    map[nameof(telemetryKey.FileVersion)] = telemetryKey.FileVersion;

                    var result = elapsedTimeCounter.GetStatisticResult();
                    result.WriteTelemetryPropertiesTo(map, prefix: "ElapsedTimePerRun.");

                    var producedFileCount = _producedFilesByGenerator.GetStatisticResult(telemetryKey);
                    producedFileCount.WriteTelemetryPropertiesTo(map, prefix: "GeneratedFileCountPerRun.");
                }));
            }
        }
    }
}<commit_msg>Remove double periods in telemetry properties<commit_after>﻿// 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.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Internal.Log;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.SourceGeneratorTelemetry
{
    internal class SourceGeneratorTelemetryCollectorWorkspaceService : ISourceGeneratorTelemetryCollectorWorkspaceService
    {
        private sealed record GeneratorTelemetryKey
        {
            public GeneratorTelemetryKey(ISourceGenerator generator)
            {
                Identity = new SourceGeneratorIdentity(generator);
                FileVersion = FileVersionInfo.GetVersionInfo(generator.GetGeneratorType().Assembly.Location).FileVersion ?? "(null)";
            }

            // TODO: mark these 'required' when we have the attributes in place
            public SourceGeneratorIdentity Identity { get; }
            public string FileVersion { get; }
        }

        /// <summary>
        /// Cache of the <see cref="GeneratorTelemetryKey"/> for a generator to avoid repeatedly reading version information from disk;
        /// this is a ConditionalWeakTable so having telemetry for older runs doesn't keep the generator itself alive.
        /// </summary>
        private readonly ConditionalWeakTable<ISourceGenerator, GeneratorTelemetryKey> _generatorTelemetryKeys = new();

        private readonly StatisticLogAggregator<GeneratorTelemetryKey> _elapsedTimeByGenerator = new();
        private readonly StatisticLogAggregator<GeneratorTelemetryKey> _producedFilesByGenerator = new();

        private GeneratorTelemetryKey GetTelemetryKey(ISourceGenerator generator)
            => _generatorTelemetryKeys.GetValue(generator, static g => new GeneratorTelemetryKey(g));

        public void CollectRunResult(GeneratorDriverRunResult driverRunResult, GeneratorDriverTimingInfo driverTimingInfo)
        {
            foreach (var generatorTime in driverTimingInfo.GeneratorTimes)
            {
                _elapsedTimeByGenerator.AddDataPoint(GetTelemetryKey(generatorTime.Generator), generatorTime.ElapsedTime);
            }

            foreach (var generatorResult in driverRunResult.Results)
            {
                _producedFilesByGenerator.AddDataPoint(GetTelemetryKey(generatorResult.Generator), generatorResult.GeneratedSources.Length);
            }
        }

        public void ReportStatisticsAndClear(FunctionId functionId)
        {
            foreach (var (telemetryKey, elapsedTimeCounter) in _elapsedTimeByGenerator)
            {
                // We'll log one event per generator
                Logger.Log(functionId, KeyValueLogMessage.Create(map =>
                {
                    // TODO: have a policy for when we don't have to hash them
                    map[nameof(telemetryKey.Identity.AssemblyName) + "Hashed"] = AnalyzerNameForTelemetry.ComputeSha256Hash(telemetryKey.Identity.AssemblyName);
                    map[nameof(telemetryKey.Identity.AssemblyVersion)] = telemetryKey.Identity.AssemblyVersion.ToString();
                    map[nameof(telemetryKey.Identity.TypeName) + "Hashed"] = AnalyzerNameForTelemetry.ComputeSha256Hash(telemetryKey.Identity.TypeName);
                    map[nameof(telemetryKey.FileVersion)] = telemetryKey.FileVersion;

                    var result = elapsedTimeCounter.GetStatisticResult();
                    result.WriteTelemetryPropertiesTo(map, prefix: "ElapsedTimePerRun");

                    var producedFileCount = _producedFilesByGenerator.GetStatisticResult(telemetryKey);
                    producedFileCount.WriteTelemetryPropertiesTo(map, prefix: "GeneratedFileCountPerRun");
                }));
            }
        }
    }
}
{'fraction_non_alphanumeric': 0.06984450675503441, 'fraction_numerical': 0.0015294417537598777, 'mean_word_length': 2.9039800995024874, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3322213', 'n_tokens_mistral': 1857, 'n_tokens_neox': 1789, 'n_words': 431}