File: EditAndContinue\EditSessionTelemetry.cs
Web Access
Project: ..\..\..\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Collections.Immutable;
using System.Diagnostics;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.EditAndContinue
{
    // EncEditSessionInfo is populated on a background thread and then read from the UI thread
    internal sealed class EditSessionTelemetry
    {
        internal readonly struct Data
        {
            public readonly ImmutableArray<(ushort EditKind, ushort SyntaxKind)> RudeEdits;
            public readonly ImmutableArray<string> EmitErrorIds;
            public readonly ImmutableArray<Guid> ProjectsWithValidDelta;
            public readonly EditAndContinueCapabilities Capabilities;
            public readonly bool HadCompilationErrors;
            public readonly bool HadRudeEdits;
            public readonly bool HadValidChanges;
            public readonly bool HadValidInsignificantChanges;
            public readonly bool InBreakState;
            public readonly bool IsEmpty;
            public readonly bool Committed;
 
            public Data(EditSessionTelemetry telemetry)
            {
                Contract.ThrowIfNull(telemetry._inBreakState);
 
                RudeEdits = telemetry._rudeEdits.AsImmutable();
                EmitErrorIds = telemetry._emitErrorIds.AsImmutable();
                ProjectsWithValidDelta = telemetry._projectsWithValidDelta.AsImmutable();
                HadCompilationErrors = telemetry._hadCompilationErrors;
                HadRudeEdits = telemetry._hadRudeEdits;
                HadValidChanges = telemetry._hadValidChanges;
                HadValidInsignificantChanges = telemetry._hadValidInsignificantChanges;
                InBreakState = telemetry._inBreakState.Value;
                Capabilities = telemetry._capabilities;
                IsEmpty = telemetry.IsEmpty;
                Committed = telemetry._committed;
            }
        }
 
        private readonly object _guard = new();
 
        // Limit the number of reported items to limit the size of the telemetry event (max total size is 64K).
        private const int MaxReportedProjectIds = 20;
 
        private readonly HashSet<(ushort, ushort)> _rudeEdits = new();
        private readonly HashSet<string> _emitErrorIds = new();
        private readonly HashSet<Guid> _projectsWithValidDelta = new();
 
        private bool _hadCompilationErrors;
        private bool _hadRudeEdits;
        private bool _hadValidChanges;
        private bool _hadValidInsignificantChanges;
        private bool? _inBreakState;
        private bool _committed;
 
        private EditAndContinueCapabilities _capabilities;
 
        public Data GetDataAndClear()
        {
            lock (_guard)
            {
                var data = new Data(this);
                _rudeEdits.Clear();
                _emitErrorIds.Clear();
                _projectsWithValidDelta.Clear();
                _hadCompilationErrors = false;
                _hadRudeEdits = false;
                _hadValidChanges = false;
                _hadValidInsignificantChanges = false;
                _inBreakState = null;
                _capabilities = EditAndContinueCapabilities.None;
                _committed = false;
                return data;
            }
        }
 
        public bool IsEmpty => !(_hadCompilationErrors || _hadRudeEdits || _hadValidChanges || _hadValidInsignificantChanges);
 
        public void SetBreakState(bool value)
            => _inBreakState = value;
 
        public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, Guid projectTelemetryId, ImmutableArray<string> errorsIds)
        {
            lock (_guard)
            {
                _emitErrorIds.AddRange(errorsIds);
 
                switch (summary)
                {
                    case ProjectAnalysisSummary.NoChanges:
                        break;
 
                    case ProjectAnalysisSummary.CompilationErrors:
                        _hadCompilationErrors = true;
                        break;
 
                    case ProjectAnalysisSummary.RudeEdits:
                        _hadRudeEdits = true;
                        break;
 
                    case ProjectAnalysisSummary.ValidChanges:
                        _hadValidChanges = true;
 
                        if (errorsIds.IsEmpty && _projectsWithValidDelta.Count < MaxReportedProjectIds)
                        {
                            _projectsWithValidDelta.Add(projectTelemetryId);
                        }
 
                        break;
 
                    case ProjectAnalysisSummary.ValidInsignificantChanges:
                        _hadValidInsignificantChanges = true;
                        break;
 
                    default:
                        throw ExceptionUtilities.UnexpectedValue(summary);
                }
            }
        }
 
        public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, Guid projectTelemetryId, ImmutableArray<Diagnostic> emitDiagnostics)
            => LogProjectAnalysisSummary(summary, projectTelemetryId, emitDiagnostics.SelectAsArray(d => d.Severity == DiagnosticSeverity.Error, d => d.Id));
 
        public void LogRudeEditDiagnostics(ImmutableArray<RudeEditDiagnostic> diagnostics)
        {
            lock (_guard)
            {
                foreach (var diagnostic in diagnostics)
                {
                    _rudeEdits.Add(((ushort)diagnostic.Kind, diagnostic.SyntaxKind));
                }
            }
        }
 
        public void LogRuntimeCapabilities(EditAndContinueCapabilities capabilities)
        {
            lock (_guard)
            {
                Debug.Assert(_capabilities == EditAndContinueCapabilities.None || _capabilities == capabilities);
                _capabilities = capabilities;
            }
        }
 
        internal void LogCommitted()
            => _committed = true;
    }
}