Skip to content

Commit b01b03c

Browse files
authored
Tag Helper Discovery Telemetry (#9602)
As discussed at various times, here is some telemetry to _maybe_ help us track down tag helper issues. I'm no telemetry expert here, but my hope is with these queries we'll be able to find things like: * Occurances of tag helper discovery never finishing (i've seen this on my machine, but maybe nowhere else?) * Where tag helper discovery results in us losing tag helper information * Sessions that never get a non-zero number of tag helpers * How often we're trying to discover tag helpers in general (with/without any changes) Please keep that in mind when reviewing. I don't know how to write the Kusto queries for those, I'm just assuming that I, or someone smarter than I, will be able to work it out later :) Also removing some old telemetry which I don't think we've ever used, and looking at the data, I'm not sure what problem we thought we had that we were looking for, but we don't seem to have it. Either the response is instant, or the data is bad. Neither is useful. <img width="127" alt="image" src="https://github.com/dotnet/razor/assets/754264/c99d78e1-b2ca-4474-a33e-9807288e9910">
2 parents 2c944fa + c78ccbd commit b01b03c

File tree

3 files changed

+39
-42
lines changed

3 files changed

+39
-42
lines changed

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectWorkspaceStateGenerator.cs

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
using System.Diagnostics;
1010
using System.Threading;
1111
using System.Threading.Tasks;
12+
using Microsoft.AspNetCore.Razor.PooledObjects;
1213
using Microsoft.AspNetCore.Razor.ProjectSystem;
14+
using Microsoft.AspNetCore.Razor.Telemetry;
1315
using Microsoft.CodeAnalysis.CSharp;
1416
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
1517
using Microsoft.CodeAnalysis.Razor.Workspaces;
@@ -19,31 +21,20 @@ namespace Microsoft.CodeAnalysis.Razor;
1921
[Shared]
2022
[Export(typeof(ProjectWorkspaceStateGenerator))]
2123
[Export(typeof(IProjectSnapshotChangeTrigger))]
22-
internal class DefaultProjectWorkspaceStateGenerator : ProjectWorkspaceStateGenerator, IDisposable
24+
[method: ImportingConstructor]
25+
internal class DefaultProjectWorkspaceStateGenerator(ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher, ITelemetryReporter telemetryReporter) : ProjectWorkspaceStateGenerator, IDisposable
2326
{
2427
// Internal for testing
25-
internal readonly Dictionary<ProjectKey, UpdateItem> Updates;
28+
internal readonly Dictionary<ProjectKey, UpdateItem> Updates = new();
29+
30+
private readonly ProjectSnapshotManagerDispatcher _projectSnapshotManagerDispatcher = projectSnapshotManagerDispatcher ?? throw new ArgumentNullException(nameof(projectSnapshotManagerDispatcher));
31+
private readonly ITelemetryReporter _telemetryReporter = telemetryReporter ?? throw new ArgumentNullException(nameof(telemetryReporter));
32+
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(initialCount: 1);
2633

27-
private readonly ProjectSnapshotManagerDispatcher _projectSnapshotManagerDispatcher;
28-
private readonly SemaphoreSlim _semaphore;
2934
private ProjectSnapshotManagerBase _projectManager;
3035
private ITagHelperResolver _tagHelperResolver;
3136
private bool _disposed;
3237

33-
[ImportingConstructor]
34-
public DefaultProjectWorkspaceStateGenerator(ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher)
35-
{
36-
if (projectSnapshotManagerDispatcher is null)
37-
{
38-
throw new ArgumentNullException(nameof(projectSnapshotManagerDispatcher));
39-
}
40-
41-
_projectSnapshotManagerDispatcher = projectSnapshotManagerDispatcher;
42-
43-
_semaphore = new SemaphoreSlim(initialCount: 1);
44-
Updates = new Dictionary<ProjectKey, UpdateItem>();
45-
}
46-
4738
// Used in unit tests to ensure we can control when background work starts.
4839
public ManualResetEventSlim BlockBackgroundWorkStart { get; set; }
4940

@@ -134,6 +125,12 @@ private async Task UpdateWorkspaceStateAsync(Project workspaceProject, IProjectS
134125
return;
135126
}
136127

128+
// Specifically not using BeginBlock because we want to capture cases where tag helper discovery never finishes.
129+
var telemetryId = Guid.NewGuid();
130+
_telemetryReporter.ReportEvent("taghelperresolve/begin", Severity.Normal,
131+
new Property("id", telemetryId),
132+
new Property("tagHelperCount", projectSnapshot.ProjectWorkspaceState?.TagHelpers.Length ?? 0));
133+
137134
try
138135
{
139136
// Only allow a single TagHelper resolver request to process at a time in order to reduce Visual Studio memory pressure. Typically a TagHelper resolution result can be upwards of 10mb+.
@@ -173,17 +170,35 @@ private async Task UpdateWorkspaceStateAsync(Project workspaceProject, IProjectS
173170
csharpLanguageVersion = csharpParseOptions.LanguageVersion;
174171
}
175172

173+
using var _ = StopwatchPool.GetPooledObject(out var watch);
174+
175+
watch.Restart();
176176
var tagHelpers = await _tagHelperResolver.GetTagHelpersAsync(workspaceProject, projectSnapshot, cancellationToken).ConfigureAwait(false);
177+
watch.Stop();
178+
179+
_telemetryReporter.ReportEvent("taghelperresolve/end", Severity.Normal,
180+
new Property("id", telemetryId),
181+
new Property("ellapsedms", watch.ElapsedMilliseconds),
182+
new Property("result", "success"),
183+
new Property("tagHelperCount", tagHelpers.Length));
184+
177185
workspaceState = new ProjectWorkspaceState(tagHelpers, csharpLanguageVersion);
178186
}
179187
}
180188
catch (OperationCanceledException)
181189
{
182190
// Abort work if we get a task cancelled exception
191+
_telemetryReporter.ReportEvent("taghelperresolve/end", Severity.Normal,
192+
new Property("id", telemetryId),
193+
new Property("result", "cancel"));
183194
return;
184195
}
185196
catch (Exception ex)
186197
{
198+
_telemetryReporter.ReportEvent("taghelperresolve/end", Severity.Normal,
199+
new Property("id", telemetryId),
200+
new Property("result", "error"));
201+
187202
await _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(
188203
() => _projectManager.ReportError(ex, projectSnapshot),
189204
// Don't allow errors to be cancelled

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/OOPTagHelperResolver.cs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,12 @@ internal class OOPTagHelperResolver : ITagHelperResolver
2525
private readonly ProjectSnapshotProjectEngineFactory _factory;
2626
private readonly IErrorReporter _errorReporter;
2727
private readonly Workspace _workspace;
28-
private readonly ITelemetryReporter _telemetryReporter;
2928

3029
public OOPTagHelperResolver(ProjectSnapshotProjectEngineFactory factory, IErrorReporter errorReporter, Workspace workspace, ITelemetryReporter telemetryReporter)
3130
{
3231
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
3332
_errorReporter = errorReporter ?? throw new ArgumentNullException(nameof(errorReporter));
3433
_workspace = workspace ?? throw new ArgumentNullException(nameof(workspace));
35-
_telemetryReporter = telemetryReporter ?? throw new ArgumentNullException(nameof(telemetryReporter));
3634

3735
_innerResolver = new CompilationTagHelperResolver(telemetryReporter);
3836
_resultCache = new TagHelperResultCache();
@@ -186,16 +184,10 @@ protected virtual async ValueTask<ImmutableArray<TagHelperDescriptor>> ResolveTa
186184
// Protected virtual for testing
187185
protected virtual ImmutableArray<Checksum> ProduceChecksumsFromDelta(ProjectId projectId, int lastResultId, TagHelperDeltaResult deltaResult)
188186
{
189-
using var _ = StopwatchPool.GetPooledObject(out var stopWatch);
190-
stopWatch.Restart();
191-
192-
var fromCache = true;
193-
194187
if (!_resultCache.TryGet(projectId, lastResultId, out var checksums))
195188
{
196189
// We most likely haven't made a request to the server yet so there's no delta to apply
197190
checksums = ImmutableArray<Checksum>.Empty;
198-
fromCache = false;
199191

200192
if (deltaResult.IsDelta)
201193
{
@@ -208,24 +200,13 @@ protected virtual ImmutableArray<Checksum> ProduceChecksumsFromDelta(ProjectId p
208200
{
209201
// Not a delta based response, we should treat it as a "refresh"
210202
checksums = ImmutableArray<Checksum>.Empty;
211-
fromCache = false;
212203
}
213204

214205
if (deltaResult.ResultId != lastResultId)
215206
{
216207
// New results, lets build a coherent TagHelper collection and then cache it
217208
checksums = deltaResult.Apply(checksums);
218209
_resultCache.Set(projectId, deltaResult.ResultId, checksums);
219-
fromCache = false;
220-
}
221-
222-
stopWatch.Stop();
223-
if (fromCache)
224-
{
225-
_telemetryReporter.ReportEvent(
226-
"taghelpers.fromcache",
227-
Severity.Normal,
228-
new Property("taghelper.cachedresult.ellapsedms", stopWatch.ElapsedMilliseconds));
229210
}
230211

231212
return checksums;

src/Razor/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectWorkspaceStateGeneratorTest.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Threading.Tasks;
1212
using Microsoft.AspNetCore.Razor.Language;
1313
using Microsoft.AspNetCore.Razor.ProjectSystem;
14+
using Microsoft.AspNetCore.Razor.Telemetry;
1415
using Microsoft.CodeAnalysis.Host;
1516
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
1617
using Xunit;
@@ -58,7 +59,7 @@ public DefaultProjectWorkspaceStateGeneratorTest(ITestOutputHelper testOutput)
5859
public void Dispose_MakesUpdateNoop()
5960
{
6061
// Arrange
61-
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher))
62+
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance))
6263
{
6364
stateGenerator.BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false);
6465

@@ -75,7 +76,7 @@ public void Dispose_MakesUpdateNoop()
7576
public void Update_StartsUpdateTask()
7677
{
7778
// Arrange
78-
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher))
79+
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance))
7980
{
8081
stateGenerator.BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false);
8182

@@ -92,7 +93,7 @@ public void Update_StartsUpdateTask()
9293
public void Update_SoftCancelsIncompleteTaskForSameProject()
9394
{
9495
// Arrange
95-
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher))
96+
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance))
9697
{
9798
stateGenerator.BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false);
9899
stateGenerator.Update(_workspaceProject, _projectSnapshot, DisposalToken);
@@ -110,7 +111,7 @@ public void Update_SoftCancelsIncompleteTaskForSameProject()
110111
public async Task Update_NullWorkspaceProject_ClearsProjectWorkspaceState()
111112
{
112113
// Arrange
113-
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher))
114+
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance))
114115
{
115116
stateGenerator.NotifyBackgroundWorkCompleted = new ManualResetEventSlim(initialState: false);
116117
var projectManager = new TestProjectSnapshotManager(_workspace, Dispatcher);
@@ -134,7 +135,7 @@ public async Task Update_NullWorkspaceProject_ClearsProjectWorkspaceState()
134135
public async Task Update_ResolvesTagHelpersAndUpdatesWorkspaceState()
135136
{
136137
// Arrange
137-
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher))
138+
using (var stateGenerator = new DefaultProjectWorkspaceStateGenerator(Dispatcher, NoOpTelemetryReporter.Instance))
138139
{
139140
stateGenerator.NotifyBackgroundWorkCompleted = new ManualResetEventSlim(initialState: false);
140141
var projectManager = new TestProjectSnapshotManager(_workspace, Dispatcher);

0 commit comments

Comments
 (0)