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 @@ -37,7 +37,7 @@ private static TestWorkspace CreateWorkspace(string workspaceKind = null, bool d
composition = composition.AddParts(typeof(TestOptionsServiceWithSharedGlobalOptionsServiceFactory));
}

return new TestWorkspace(exportProvider: null, composition, workspaceKind, disablePartialSolutions);
return new TestWorkspace(exportProvider: null, composition, workspaceKind, disablePartialSolutions: disablePartialSolutions);
}

private static async Task WaitForWorkspaceOperationsToComplete(TestWorkspace workspace)
Expand Down

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,19 @@ public partial class TestWorkspace : Workspace
private readonly Dictionary<string, ITextBuffer2> _createdTextBuffers = new();
private readonly string _workspaceKind;

public TestWorkspace(ExportProvider? exportProvider = null, TestComposition? composition = null, string? workspaceKind = WorkspaceKind.Host, bool disablePartialSolutions = true, bool ignoreUnchangeableDocumentsWhenApplyingChanges = true)
public TestWorkspace(
ExportProvider? exportProvider = null,
TestComposition? composition = null,
string? workspaceKind = WorkspaceKind.Host,
Guid solutionTelemetryId = default,
bool disablePartialSolutions = true,
bool ignoreUnchangeableDocumentsWhenApplyingChanges = true)
: base(GetHostServices(exportProvider, composition), workspaceKind ?? WorkspaceKind.Host)
{
Contract.ThrowIfTrue(exportProvider != null && composition != null);

SetCurrentSolution(CreateSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create()).WithTelemetryId(solutionTelemetryId)));

this.TestHookPartialSolutionsDisabled = disablePartialSolutions;
this.ExportProvider = exportProvider ?? GetComposition(composition).ExportProviderFactory.CreateExportProvider();
_workspaceKind = workspaceKind ?? WorkspaceKind.Host;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ internal sealed class DebuggingSession : IDisposable
/// </summary>
internal readonly bool ReportDiagnostics;

private readonly DebuggingSessionTelemetry _telemetry = new();
private readonly DebuggingSessionTelemetry _telemetry;
private readonly EditSessionTelemetry _editSessionTelemetry = new();

private PendingSolutionUpdate? _pendingUpdate;
Expand All @@ -105,6 +105,7 @@ internal DebuggingSession(
{
_compilationOutputsProvider = compilationOutputsProvider;
_reportTelemetry = ReportTelemetry;
_telemetry = new DebuggingSessionTelemetry(solution.State.SolutionAttributes.TelemetryId);

Id = id;
DebuggerService = debuggerService;
Expand Down Expand Up @@ -582,6 +583,8 @@ from region in moduleRegions.Regions

LastCommittedSolution.CommitSolution(pendingUpdate.Solution);

_editSessionTelemetry.LogCommitted();

// Restart edit session with no active statements (switching to run mode).
RestartEditSession(newNonRemappableRegions, inBreakState: false, out documentsToReanalyze);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Internal.Log;

namespace Microsoft.CodeAnalysis.EditAndContinue
Expand All @@ -13,12 +14,14 @@ internal sealed class DebuggingSessionTelemetry
{
internal readonly struct Data
{
public readonly Guid SolutionSessionId;
public readonly ImmutableArray<EditSessionTelemetry.Data> EditSessionData;
public readonly int EmptyEditSessionCount;
public readonly int EmptyHotReloadEditSessionCount;

public Data(DebuggingSessionTelemetry telemetry)
{
SolutionSessionId = telemetry._solutionSessionId;
EditSessionData = telemetry._editSessionData.ToImmutableArray();
EmptyEditSessionCount = telemetry._emptyEditSessionCount;
EmptyHotReloadEditSessionCount = telemetry._emptyHotReloadEditSessionCount;
Expand All @@ -27,10 +30,16 @@ public Data(DebuggingSessionTelemetry telemetry)

private readonly object _guard = new();

private readonly Guid _solutionSessionId;
private readonly List<EditSessionTelemetry.Data> _editSessionData = new();
private int _emptyEditSessionCount;
private int _emptyHotReloadEditSessionCount;

public DebuggingSessionTelemetry(Guid solutionSessionId)
{
_solutionSessionId = solutionSessionId;
}

public Data GetDataAndClear()
{
lock (_guard)
Expand Down Expand Up @@ -82,6 +91,7 @@ public static void Log(Data data, Action<FunctionId, LogMessage> log, Func<int>

log(FunctionId.Debugging_EncSession, KeyValueLogMessage.Create(map =>
{
map["SolutionSessionId"] = data.SolutionSessionId.ToString("B").ToUpperInvariant();
map[SessionId] = debugSessionId;
map["SessionCount"] = data.EditSessionData.Count(session => session.InBreakState);
map["EmptySessionCount"] = data.EmptyEditSessionCount;
Expand All @@ -100,13 +110,24 @@ public static void Log(Data data, Action<FunctionId, LogMessage> log, Func<int>

map["HadCompilationErrors"] = editSessionData.HadCompilationErrors;
map["HadRudeEdits"] = editSessionData.HadRudeEdits;

// Changes made to source code during the edit session were valid - they were significant and no rude edits were reported.
// The changes still might fail to emit (see EmitDeltaErrorIdCount).
map["HadValidChanges"] = editSessionData.HadValidChanges;
map["HadValidInsignificantChanges"] = editSessionData.HadValidInsignificantChanges;

map["RudeEditsCount"] = editSessionData.RudeEdits.Length;

// Number of emit errors.
map["EmitDeltaErrorIdCount"] = editSessionData.EmitErrorIds.Length;

// False for Hot Reload session, true or missing for EnC session (missing in older data that did not have this property).
map["InBreakState"] = editSessionData.InBreakState;

map["Capabilities"] = (int)editSessionData.Capabilities;

// Ids of all projects whose binaries were successfully updated during the session.
map["ProjectsWithAppliedChanges"] = editSessionData.Committed ? string.Join(",", editSessionData.ProjectsWithValidDelta.Select(id => id.ToString("B").ToUpperInvariant())) : "";
}));

foreach (var errorId in editSessionData.EmitErrorIds)
Expand Down
8 changes: 4 additions & 4 deletions src/Features/Core/Portable/EditAndContinue/EditSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ public async ValueTask<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution
// Bail before analyzing documents as the analysis needs to read the PDB which will likely fail if we can't even read the MVID.
diagnostics.Add((newProject.Id, ImmutableArray.Create(mvidReadError)));

Telemetry.LogProjectAnalysisSummary(ProjectAnalysisSummary.ValidChanges, ImmutableArray.Create(mvidReadError.Descriptor.Id), InBreakState);
Telemetry.LogProjectAnalysisSummary(ProjectAnalysisSummary.ValidChanges, newProject.State.ProjectInfo.Attributes.TelemetryId, ImmutableArray.Create(mvidReadError.Descriptor.Id), InBreakState);
isBlocked = true;
continue;
}
Expand Down Expand Up @@ -813,7 +813,7 @@ public async ValueTask<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution

if (isModuleEncBlocked || projectSummary != ProjectAnalysisSummary.ValidChanges)
{
Telemetry.LogProjectAnalysisSummary(projectSummary, moduleDiagnostics.NullToEmpty().SelectAsArray(d => d.Descriptor.Id), InBreakState);
Telemetry.LogProjectAnalysisSummary(projectSummary, newProject.State.ProjectInfo.Attributes.TelemetryId, moduleDiagnostics.NullToEmpty().SelectAsArray(d => d.Descriptor.Id), InBreakState);
continue;
}

Expand All @@ -824,7 +824,7 @@ public async ValueTask<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution
// Report diagnosics even when the module is never going to be loaded (e.g. in multi-targeting scenario, where only one framework being debugged).
// This is consistent with reporting compilation errors - the IDE reports them for all TFMs regardless of what framework the app is running on.
diagnostics.Add((newProject.Id, createBaselineDiagnostics));
Telemetry.LogProjectAnalysisSummary(projectSummary, createBaselineDiagnostics, InBreakState);
Telemetry.LogProjectAnalysisSummary(projectSummary, newProject.State.ProjectInfo.Attributes.TelemetryId, createBaselineDiagnostics, InBreakState);
isBlocked = true;
continue;
}
Expand Down Expand Up @@ -927,7 +927,7 @@ public async ValueTask<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution
diagnostics.Add((newProject.Id, emitResult.Diagnostics));
}

Telemetry.LogProjectAnalysisSummary(projectSummary, emitResult.Diagnostics, InBreakState);
Telemetry.LogProjectAnalysisSummary(projectSummary, newProject.State.ProjectInfo.Attributes.TelemetryId, emitResult.Diagnostics, InBreakState);
}

// log capabilities for edit sessions with changes or reported errors:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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;
Expand All @@ -16,38 +17,44 @@ 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)
{
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;
Capabilities = telemetry._capabilities;
IsEmpty = telemetry.IsEmpty;
Committed = telemetry._committed;
}
}

private readonly object _guard = new();

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;

Expand All @@ -58,19 +65,21 @@ public Data GetDataAndClear()
var data = new Data(this);
_rudeEdits.Clear();
_emitErrorIds.Clear();
_projectsWithValidDelta.Clear();
_hadCompilationErrors = false;
_hadRudeEdits = false;
_hadValidChanges = false;
_hadValidInsignificantChanges = false;
_inBreakState = false;
_capabilities = EditAndContinueCapabilities.None;
_committed = false;
return data;
}
}

public bool IsEmpty => !(_hadCompilationErrors || _hadRudeEdits || _hadValidChanges || _hadValidInsignificantChanges);

public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, ImmutableArray<string> errorsIds, bool inBreakState)
public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, Guid projectTelemetryId, ImmutableArray<string> errorsIds, bool inBreakState)
{
lock (_guard)
{
Expand All @@ -92,6 +101,12 @@ public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, ImmutableA

case ProjectAnalysisSummary.ValidChanges:
_hadValidChanges = true;

if (errorsIds.IsEmpty)
{
_projectsWithValidDelta.Add(projectTelemetryId);
}

break;

case ProjectAnalysisSummary.ValidInsignificantChanges:
Expand All @@ -104,8 +119,8 @@ public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, ImmutableA
}
}

public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, ImmutableArray<Diagnostic> emitDiagnostics, bool inBreakState)
=> LogProjectAnalysisSummary(summary, emitDiagnostics.SelectAsArray(d => d.Severity == DiagnosticSeverity.Error, d => d.Id), inBreakState);
public void LogProjectAnalysisSummary(ProjectAnalysisSummary summary, Guid projectTelemetryId, ImmutableArray<Diagnostic> emitDiagnostics, bool inBreakState)
=> LogProjectAnalysisSummary(summary, projectTelemetryId, emitDiagnostics.SelectAsArray(d => d.Severity == DiagnosticSeverity.Error, d => d.Id), inBreakState);

public void LogRudeEditDiagnostics(ImmutableArray<RudeEditDiagnostic> diagnostics)
{
Expand All @@ -126,5 +141,8 @@ public void LogRuntimeCapabilities(EditAndContinueCapabilities capabilities)
_capabilities = capabilities;
}
}

internal void LogCommitted()
=> _committed = true;
}
}