未验证 提交 89358407 编写于 作者: M msftbot[bot] 提交者: GitHub

Merge pull request #42383 from CyrusNajmabadi/telemetryOOP

Move the project telemetry collection out of process.
......@@ -10,6 +10,7 @@
<ServiceHubService Include="roslynRemoteHost" ClassName="Microsoft.CodeAnalysis.Remote.RemoteHostService" />
<ServiceHubService Include="roslynSnapshot" ClassName="Microsoft.CodeAnalysis.Remote.SnapshotService" />
<ServiceHubService Include="roslynRemoteDesignerAttributeService" ClassName="Microsoft.CodeAnalysis.Remote.RemoteDesignerAttributeService" />
<ServiceHubService Include="roslynRemoteProjectTelemetryService" ClassName="Microsoft.CodeAnalysis.Remote.RemoteProjectTelemetryService" />
<ServiceHubService Include="roslynRemoteSymbolSearchUpdateEngine" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSymbolSearchUpdateEngine" />
<ServiceHubService Include="roslynLanguageServer" ClassName="Microsoft.CodeAnalysis.Remote.LanguageServer" />
</ItemGroup>
......@@ -56,4 +57,4 @@
<VSIXSourceItem Include="@(_JsonFile->'%(Identity)')" />
</ItemGroup>
</Target>
</Project>
\ No newline at end of file
</Project>
......@@ -168,6 +168,7 @@ public InProcRemoteServices(bool runCacheCleanup)
RegisterService(WellKnownServiceHubServices.SnapshotService, (s, p) => new SnapshotService(s, p));
RegisterService(WellKnownServiceHubServices.RemoteSymbolSearchUpdateEngine, (s, p) => new RemoteSymbolSearchUpdateEngine(s, p));
RegisterService(WellKnownServiceHubServices.RemoteDesignerAttributeService, (s, p) => new RemoteDesignerAttributeService(s, p));
RegisterService(WellKnownServiceHubServices.RemoteProjectTelemetryService, (s, p) => new RemoteProjectTelemetryService(s, p));
RegisterService(WellKnownServiceHubServices.LanguageServer, (s, p) => new LanguageServer(s, p));
}
......
......@@ -7,6 +7,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
......@@ -63,35 +64,7 @@ internal class VisualStudioDesignerAttributeService
// We'll get notifications from the OOP server about new attribute arguments. Batch those
// notifications up and deliver them to VS every second.
#region protected by lock
/// <summary>
/// Lock we will use to ensure the remainder of these fields can be accessed in a threadsafe
/// manner. When OOP calls back into us, we'll place the data it produced into
/// <see cref="_updatedInfos"/>. We'll then kick of a task to process this in the future if
/// we don't already have an existing task in flight for that.
/// </summary>
private readonly object _gate = new object();
/// <summary>
/// Data produced by OOP that we want to process in our next update task.
/// </summary>
private readonly List<DesignerInfo> _updatedInfos = new List<DesignerInfo>();
/// <summary>
/// Task kicked off to do the next batch of processing of <see cref="_updatedInfos"/>. These
/// tasks form a chain so that the next task only processes when the previous one completes.
/// </summary>
private Task _updateTask = Task.CompletedTask;
/// <summary>
/// Whether or not there is an existing task in flight that will process the current batch
/// of <see cref="_updatedInfos"/>. If there is an existing in flight task, we don't need
/// to kick off a new one if we receive more notifications before it runs.
/// </summary>
private bool _taskInFlight = false;
#endregion
private AsyncBatchingWorkQueue<DesignerInfo> _workQueue = null!;
public VisualStudioDesignerAttributeService(
VisualStudioWorkspaceImpl workspace,
......@@ -117,6 +90,11 @@ void IDesignerAttributeService.Start(CancellationToken cancellationToken)
private async Task StartAsync(CancellationToken cancellationToken)
{
_workQueue = new AsyncBatchingWorkQueue<DesignerInfo>(
TimeSpan.FromSeconds(1),
this.NotifyProjectSystemAsync,
cancellationToken);
// Have to catch all exceptions coming through here as this is called from a
// fire-and-forget method and we want to make sure nothing leaks out.
try
......@@ -159,42 +137,23 @@ private async Task StartWorkerAsync(CancellationToken cancellationToken)
/// <summary>
/// Callback from the OOP service back into us.
/// </summary>
public Task RegisterDesignerAttributesAsync(
IList<DesignerInfo> attributeInfos, CancellationToken cancellationToken)
public Task RegisterDesignerAttributesAsync(IList<DesignerInfo> attributeInfos, CancellationToken cancellationToken)
{
lock (_gate)
{
// add our work to the set we'll process in the next batch.
_updatedInfos.AddRange(attributeInfos);
if (!_taskInFlight)
{
// No in-flight task. Kick one off to process these messages a second from now.
// We always attach the task to the previous one so that notifications to the ui
// follow the same order as the notification the OOP server sent to us.
_updateTask = _updateTask.ContinueWithAfterDelayFromAsync(
_ => NotifyProjectSystemAsync(cancellationToken),
cancellationToken,
1000/*ms*/,
TaskContinuationOptions.RunContinuationsAsynchronously,
TaskScheduler.Default);
_taskInFlight = true;
}
}
_workQueue.AddWork(attributeInfos);
return Task.CompletedTask;
}
private async Task NotifyProjectSystemAsync(CancellationToken cancellationToken)
private async Task NotifyProjectSystemAsync(
ImmutableArray<DesignerInfo> infos, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using var _1 = ArrayBuilder<DesignerInfo>.GetInstance(out var attributeInfos);
AddInfosAndResetQueue(attributeInfos);
using var _1 = ArrayBuilder<DesignerInfo>.GetInstance(out var filteredInfos);
AddFilteredInfos(infos, filteredInfos);
// Now, group all the notifications by project and update all the projects in parallel.
using var _2 = ArrayBuilder<Task>.GetInstance(out var tasks);
foreach (var group in attributeInfos.GroupBy(a => a.DocumentId.ProjectId))
foreach (var group in filteredInfos.GroupBy(a => a.DocumentId.ProjectId))
{
cancellationToken.ThrowIfCancellationRequested();
tasks.Add(NotifyProjectSystemAsync(group.Key, group, cancellationToken));
......@@ -204,27 +163,18 @@ private async Task NotifyProjectSystemAsync(CancellationToken cancellationToken)
await Task.WhenAll(tasks).ConfigureAwait(false);
}
private void AddInfosAndResetQueue(ArrayBuilder<DesignerInfo> attributeInfos)
private void AddFilteredInfos(ImmutableArray<DesignerInfo> infos, ArrayBuilder<DesignerInfo> filteredInfos)
{
using var _ = PooledHashSet<DocumentId>.GetInstance(out var seenDocumentIds);
lock (_gate)
// Walk the list of designer items in reverse, and skip any items for a project once
// we've already seen it once. That way, we're only reporting the most up to date
// information for a project, and we're skipping the stale information.
for (var i = infos.Length - 1; i >= 0; i--)
{
// walk the set of updates in reverse, and ignore documents if we see them a second
// time. This ensures that if we're batching up multiple notifications for the same
// document, that we only bother processing the last one since it should beat out
// all the prior ones.
for (var i = _updatedInfos.Count - 1; i >= 0; i--)
{
var designerArg = _updatedInfos[i];
if (seenDocumentIds.Add(designerArg.DocumentId))
attributeInfos.Add(designerArg);
}
// mark there being no existing update task so that the next OOP notification will
// kick one off.
_updatedInfos.Clear();
_taskInFlight = false;
var info = infos[i];
if (seenDocumentIds.Add(info.DocumentId))
filteredInfos.Add(info);
}
}
......
// 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.
#nullable enable
using System.Threading;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectTelemetry
{
/// <summary>
/// In process service responsible for listening to OOP telemetry notifications.
/// </summary>
internal interface IProjectTelemetryService : IWorkspaceService
{
/// <summary>
/// Called by a host to let this service know that it should start background
/// analysis of the workspace to determine project telemetry.
/// </summary>
void Start(CancellationToken cancellationToken);
}
}
// 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.
#nullable enable
using System;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.ProjectTelemetry;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.Internal.VisualStudio.Shell;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectTelemetry
{
internal class VisualStudioProjectTelemetryService
: ForegroundThreadAffinitizedObject, IProjectTelemetryService, IProjectTelemetryServiceCallback
{
private const string EventPrefix = "VS/Compilers/Compilation/";
private const string PropertyPrefix = "VS.Compilers.Compilation.Inputs.";
private const string TelemetryEventPath = EventPrefix + "Inputs";
private const string TelemetryExceptionEventPath = EventPrefix + "TelemetryUnhandledException";
private const string TelemetryProjectIdName = PropertyPrefix + "ProjectId";
private const string TelemetryProjectGuidName = PropertyPrefix + "ProjectGuid";
private const string TelemetryLanguageName = PropertyPrefix + "Language";
private const string TelemetryAnalyzerReferencesCountName = PropertyPrefix + "AnalyzerReferences.Count";
private const string TelemetryProjectReferencesCountName = PropertyPrefix + "ProjectReferences.Count";
private const string TelemetryMetadataReferencesCountName = PropertyPrefix + "MetadataReferences.Count";
private const string TelemetryDocumentsCountName = PropertyPrefix + "Documents.Count";
private const string TelemetryAdditionalDocumentsCountName = PropertyPrefix + "AdditionalDocuments.Count";
private readonly VisualStudioWorkspaceImpl _workspace;
/// <summary>
/// Our connections to the remote OOP server. Created on demand when we startup and then
/// kept around for the lifetime of this service.
/// </summary>
private KeepAliveSession? _keepAliveSession;
/// <summary>
/// Queue where we enqueue the information we get from OOP to process in batch in the future.
/// </summary>
private AsyncBatchingWorkQueue<ProjectTelemetryInfo> _workQueue = null!;
public VisualStudioProjectTelemetryService(VisualStudioWorkspaceImpl workspace, IThreadingContext threadingContext) : base(threadingContext)
=> _workspace = workspace;
void IProjectTelemetryService.Start(CancellationToken cancellationToken)
=> _ = StartAsync(cancellationToken);
private async Task StartAsync(CancellationToken cancellationToken)
{
// Have to catch all exceptions coming through here as this is called from a
// fire-and-forget method and we want to make sure nothing leaks out.
try
{
await StartWorkerAsync(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Cancellation is normal (during VS closing). Just ignore.
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
// Otherwise report a watson for any other exception. Don't bring down VS. This is
// a BG service we don't want impacting the user experience.
}
}
private async Task StartWorkerAsync(CancellationToken cancellationToken)
{
_workQueue = new AsyncBatchingWorkQueue<ProjectTelemetryInfo>(
TimeSpan.FromSeconds(1),
NotifyTelemetryServiceAsync,
cancellationToken);
var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false);
if (client == null)
return;
// Pass ourselves in as the callback target for the OOP service. As it discovers
// designer attributes it will call back into us to notify VS about it.
_keepAliveSession = await client.TryCreateKeepAliveSessionAsync(
WellKnownServiceHubServices.RemoteProjectTelemetryService,
callbackTarget: this, cancellationToken).ConfigureAwait(false);
if (_keepAliveSession == null)
return;
// Now kick off scanning in the OOP process.
var success = await _keepAliveSession.TryInvokeAsync(
nameof(IRemoteProjectTelemetryService.ComputeProjectTelemetryAsync),
solution: null,
arguments: Array.Empty<object>(),
cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Callback from the OOP service back into us.
/// </summary>
public Task RegisterProjectTelemetryInfoAsync(ProjectTelemetryInfo info, CancellationToken cancellationToken)
{
_workQueue.AddWork(info);
return Task.CompletedTask;
}
private async Task NotifyTelemetryServiceAsync(
ImmutableArray<ProjectTelemetryInfo> infos, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using var _1 = ArrayBuilder<ProjectTelemetryInfo>.GetInstance(out var filteredInfos);
AddFilteredInfos(infos, filteredInfos);
using var _2 = ArrayBuilder<Task>.GetInstance(out var tasks);
foreach (var info in filteredInfos)
tasks.Add(Task.Run(() => NotifyTelemetryService(info), cancellationToken));
await Task.WhenAll(tasks).ConfigureAwait(false);
}
private void AddFilteredInfos(ImmutableArray<ProjectTelemetryInfo> infos, ArrayBuilder<ProjectTelemetryInfo> filteredInfos)
{
using var _ = PooledHashSet<ProjectId>.GetInstance(out var seenProjectIds);
// Walk the list of telemetry items in reverse, and skip any items for a project once
// we've already seen it once. That way, we're only reporting the most up to date
// information for a project, and we're skipping the stale information.
for (var i = infos.Length - 1; i >= 0; i--)
{
var info = infos[i];
if (seenProjectIds.Add(info.ProjectId))
filteredInfos.Add(info);
}
}
private void NotifyTelemetryService(ProjectTelemetryInfo info)
{
try
{
var telemetryEvent = TelemetryHelper.TelemetryService.CreateEvent(TelemetryEventPath);
telemetryEvent.SetStringProperty(TelemetryProjectIdName, info.ProjectId.Id.ToString());
telemetryEvent.SetStringProperty(TelemetryProjectGuidName, Guid.Empty.ToString());
telemetryEvent.SetStringProperty(TelemetryLanguageName, info.Language);
telemetryEvent.SetIntProperty(TelemetryAnalyzerReferencesCountName, info.AnalyzerReferencesCount);
telemetryEvent.SetIntProperty(TelemetryProjectReferencesCountName, info.ProjectReferencesCount);
telemetryEvent.SetIntProperty(TelemetryMetadataReferencesCountName, info.MetadataReferencesCount);
telemetryEvent.SetIntProperty(TelemetryDocumentsCountName, info.DocumentsCount);
telemetryEvent.SetIntProperty(TelemetryAdditionalDocumentsCountName, info.AdditionalDocumentsCount);
TelemetryHelper.DefaultTelemetrySession.PostEvent(telemetryEvent);
}
catch (Exception e)
{
// The telemetry service itself can throw.
// So, to be very careful, put this in a try/catch too.
try
{
var exceptionEvent = TelemetryHelper.TelemetryService.CreateEvent(TelemetryExceptionEventPath);
exceptionEvent.SetStringProperty("Type", e.GetTypeDisplayName());
exceptionEvent.SetStringProperty("Message", e.Message);
exceptionEvent.SetStringProperty("StackTrace", e.StackTrace);
TelemetryHelper.DefaultTelemetrySession.PostEvent(exceptionEvent);
}
catch
{
}
}
}
}
}
// 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.
#nullable enable
using System;
using System.Composition;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectTelemetry
{
[ExportWorkspaceServiceFactory(typeof(IProjectTelemetryService), ServiceLayer.Host), Shared]
internal class VisualStudioProjectTelemetryServiceFactory : IWorkspaceServiceFactory
{
private readonly IThreadingContext _threadingContext;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public VisualStudioProjectTelemetryServiceFactory(IThreadingContext threadingContext)
=> _threadingContext = threadingContext;
public IWorkspaceService? CreateService(HostWorkspaceServices workspaceServices)
{
if (!(workspaceServices.Workspace is VisualStudioWorkspaceImpl workspace))
return null;
return new VisualStudioProjectTelemetryService(workspace, _threadingContext);
}
}
}
......@@ -4,7 +4,6 @@
using System;
using System.ComponentModel.Design;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
......@@ -28,6 +27,7 @@
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.RuleSets;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectTelemetry;
using Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource;
using Microsoft.VisualStudio.LanguageServices.Telemetry;
using Microsoft.VisualStudio.PlatformUI;
......@@ -158,8 +158,12 @@ private async Task LoadComponentsBackgroundAsync(CancellationToken cancellationT
// Load the designer attribute service and tell it to start watching the solution for
// designable files.
var designerAttributeService = _workspace.Services.GetService<IDesignerAttributeService>();
var designerAttributeService = _workspace.Services.GetRequiredService<IDesignerAttributeService>();
designerAttributeService.Start(this.DisposalToken);
// Load the telemetry service and tell it to start watching the solution for project info.
var projectTelemetryService = _workspace.Services.GetRequiredService<IProjectTelemetryService>();
projectTelemetryService.Start(this.DisposalToken);
}
private async Task LoadInteractiveMenusAsync(CancellationToken cancellationToken)
......
// 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.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.Internal.VisualStudio.Shell;
using Microsoft.Internal.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
namespace Microsoft.VisualStudio.LanguageServices.Telemetry
{
/// <summary>
/// Creates an <see cref="IIncrementalAnalyzer"/> that collects basic information on <see cref="Project"/> inputs
/// and reports it to the <see cref="IVsTelemetryService"/>.
/// </summary>
/// <remarks>
/// This includes data such an source file counts, project, metadata, and analyzer reference counts, and so on.
/// </remarks>
[ExportIncrementalAnalyzerProvider(nameof(ProjectTelemetryIncrementalAnalyzerProvider), new[] { WorkspaceKind.Host }), Shared]
internal sealed class ProjectTelemetryIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider
{
[ImportingConstructor]
public ProjectTelemetryIncrementalAnalyzerProvider()
{
}
public IIncrementalAnalyzer CreateIncrementalAnalyzer(Microsoft.CodeAnalysis.Workspace workspace)
{
return new Analyzer();
}
private sealed class Analyzer : IIncrementalAnalyzer
{
/// <summary>
/// For a given <see cref="ProjectId"/>, stores the most recent set of data reported to the
/// telemetry service.
/// </summary>
private sealed class Cache
{
private class Inputs
{
public string Language;
public int AnalyzerReferencesCount;
public int ProjectReferencesCount;
public int MetadataReferencesCount;
public int DocumentsCount;
public int AdditionalDocumentsCount;
public Inputs(string language, int analyzerReferencesCount, int projectReferencesCount, int metadataReferencesCount, int documentsCount, int additionalDocumentsCount)
{
this.Language = language;
this.AnalyzerReferencesCount = analyzerReferencesCount;
this.ProjectReferencesCount = projectReferencesCount;
this.MetadataReferencesCount = metadataReferencesCount;
this.DocumentsCount = documentsCount;
this.AdditionalDocumentsCount = additionalDocumentsCount;
}
public bool Equals(Inputs other)
{
return this.Language.Equals(other.Language) &&
this.AnalyzerReferencesCount == other.AnalyzerReferencesCount &&
this.ProjectReferencesCount == other.ProjectReferencesCount &&
this.MetadataReferencesCount == other.MetadataReferencesCount &&
this.DocumentsCount == other.DocumentsCount &&
this.AdditionalDocumentsCount == other.AdditionalDocumentsCount;
}
}
private readonly object _lockObject = new object();
private readonly Dictionary<ProjectId, Inputs> _items = new Dictionary<ProjectId, Inputs>();
/// <summary>
/// Adds or updates the data for the <see cref="Project"/> indicated by <paramref name="projectId"/>.
/// </summary>
/// <returns>
/// True if the data was added or updated, false if the data matches what is already in the cache.
/// </returns>
public bool TryAddOrUpdate(ProjectId projectId, string language, int analyzerReferenceCount, int projectReferencesCount, int metadataReferencesCount, int documentsCount, int additionalDocumentsCount)
{
lock (_lockObject)
{
var newInputs = new Inputs(
language,
analyzerReferenceCount,
projectReferencesCount,
metadataReferencesCount,
documentsCount,
additionalDocumentsCount);
if (!_items.TryGetValue(projectId, out var existingInputs) ||
!existingInputs.Equals(newInputs))
{
_items[projectId] = newInputs;
return true;
}
return false;
}
}
/// <summary>
/// Removes all data associated with <paramref name="projectId"/>.
/// </summary>
public void Remove(ProjectId projectId)
{
lock (_lockObject)
{
_items.Remove(projectId);
}
}
}
private const string EventPrefix = "VS/Compilers/Compilation/";
private const string PropertyPrefix = "VS.Compilers.Compilation.Inputs.";
private const string TelemetryEventPath = EventPrefix + "Inputs";
private const string TelemetryExceptionEventPath = EventPrefix + "TelemetryUnhandledException";
private const string TelemetryProjectIdName = PropertyPrefix + "ProjectId";
private const string TelemetryProjectGuidName = PropertyPrefix + "ProjectGuid";
private const string TelemetryLanguageName = PropertyPrefix + "Language";
private const string TelemetryAnalyzerReferencesCountName = PropertyPrefix + "AnalyzerReferences.Count";
private const string TelemetryProjectReferencesCountName = PropertyPrefix + "ProjectReferences.Count";
private const string TelemetryMetadataReferencesCountName = PropertyPrefix + "MetadataReferences.Count";
private const string TelemetryDocumentsCountName = PropertyPrefix + "Documents.Count";
private const string TelemetryAdditionalDocumentsCountName = PropertyPrefix + "AdditionalDocuments.Count";
private readonly Cache _cache = new Cache();
public Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <summary>
/// Collects data from <paramref name="project"/> and reports it to the telemetry service.
/// </summary>
/// <remarks>
/// Only sends data to the telemetry service when one of the collected data points changes,
/// not necessarily every time this code is called.
/// </remarks>
public Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken)
{
if (!semanticsChanged)
{
return Task.CompletedTask;
}
var projectId = project.Id;
var language = project.Language;
var analyzerReferencesCount = project.AnalyzerReferences.Count;
var projectReferencesCount = project.AllProjectReferences.Count;
var metadataReferencesCount = project.MetadataReferences.Count;
var documentsCount = project.DocumentIds.Count;
var additionalDocumentsCount = project.AdditionalDocumentIds.Count;
if (_cache.TryAddOrUpdate(projectId, language, analyzerReferencesCount, projectReferencesCount, metadataReferencesCount, documentsCount, additionalDocumentsCount))
{
try
{
var workspace = (VisualStudioWorkspaceImpl)project.Solution.Workspace;
var telemetryEvent = TelemetryHelper.TelemetryService.CreateEvent(TelemetryEventPath);
telemetryEvent.SetStringProperty(TelemetryProjectIdName, projectId.Id.ToString());
// TODO: reconnect project GUID
telemetryEvent.SetStringProperty(TelemetryProjectGuidName, Guid.Empty.ToString());
telemetryEvent.SetStringProperty(TelemetryLanguageName, language);
telemetryEvent.SetIntProperty(TelemetryAnalyzerReferencesCountName, analyzerReferencesCount);
telemetryEvent.SetIntProperty(TelemetryProjectReferencesCountName, projectReferencesCount);
telemetryEvent.SetIntProperty(TelemetryMetadataReferencesCountName, metadataReferencesCount);
telemetryEvent.SetIntProperty(TelemetryDocumentsCountName, documentsCount);
telemetryEvent.SetIntProperty(TelemetryAdditionalDocumentsCountName, additionalDocumentsCount);
TelemetryHelper.DefaultTelemetrySession.PostEvent(telemetryEvent);
}
catch (Exception e)
{
// The telemetry service itself can throw.
// So, to be very careful, put this in a try/catch too.
try
{
var exceptionEvent = TelemetryHelper.TelemetryService.CreateEvent(TelemetryExceptionEventPath);
exceptionEvent.SetStringProperty("Type", e.GetTypeDisplayName());
exceptionEvent.SetStringProperty("Message", e.Message);
exceptionEvent.SetStringProperty("StackTrace", e.StackTrace);
TelemetryHelper.DefaultTelemetrySession.PostEvent(exceptionEvent);
}
catch
{
}
}
}
return Task.CompletedTask;
}
public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task DocumentResetAsync(Document document, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e)
{
return false;
}
public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public void RemoveDocument(DocumentId documentId)
{
}
public void RemoveProject(ProjectId projectId)
{
_cache.Remove(projectId);
}
}
}
}
......@@ -35,12 +35,14 @@
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynSnapshot.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynCodeAnalysis.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteDesignerAttributeService.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteProjectTelemetryService.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteSymbolSearchUpdateEngine.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynLanguageServer.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteHost64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynSnapshot64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynCodeAnalysis64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteDesignerAttributeService64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteProjectTelemetryService64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteSymbolSearchUpdateEngine64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynLanguageServer64.servicehub.service.json" />
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="BasicVisualStudio" Path="|BasicVisualStudio|" />
......
// 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.
#nullable enable
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.ProjectTelemetry
{
/// <summary>
/// Callback the host (VS) passes to the OOP service to allow it to send batch notifications
/// about telemetry.
/// </summary>
internal interface IProjectTelemetryServiceCallback
{
Task RegisterProjectTelemetryInfoAsync(ProjectTelemetryInfo infos, CancellationToken cancellationToken);
}
}
// 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.
#nullable enable
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.ProjectTelemetry
{
/// <summary>
/// Interface to allow host (VS) to inform the OOP service to start incrementally analyzing and
/// reporting results back to the host.
/// </summary>
internal interface IRemoteProjectTelemetryService
{
Task ComputeProjectTelemetryAsync(CancellationToken cancellation);
}
}
// 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.
#nullable enable
namespace Microsoft.CodeAnalysis.ProjectTelemetry
{
/// <summary>
/// Serialization typed used to pass information to/from OOP and VS.
/// </summary>
internal struct ProjectTelemetryInfo
{
public ProjectId ProjectId;
public string Language;
public int AnalyzerReferencesCount;
public int ProjectReferencesCount;
public int MetadataReferencesCount;
public int DocumentsCount;
public int AdditionalDocumentsCount;
public bool Equals(ProjectTelemetryInfo other)
=> Language.Equals(other.Language) &&
AnalyzerReferencesCount == other.AnalyzerReferencesCount &&
ProjectReferencesCount == other.ProjectReferencesCount &&
MetadataReferencesCount == other.MetadataReferencesCount &&
DocumentsCount == other.DocumentsCount &&
AdditionalDocumentsCount == other.AdditionalDocumentsCount;
}
}
......@@ -13,6 +13,7 @@ public static void Set64bit(bool x64)
SnapshotService = "roslynSnapshot" + bit;
CodeAnalysisService = "roslynCodeAnalysis" + bit;
RemoteDesignerAttributeService = "roslynRemoteDesignerAttributeService" + bit;
RemoteProjectTelemetryService = "roslynRemoteProjectTelemetryService" + bit;
RemoteSymbolSearchUpdateEngine = "roslynRemoteSymbolSearchUpdateEngine" + bit;
LanguageServer = "roslynLanguageServer" + bit;
}
......@@ -21,6 +22,7 @@ public static void Set64bit(bool x64)
public static string CodeAnalysisService { get; private set; } = "roslynCodeAnalysis";
public static string RemoteSymbolSearchUpdateEngine { get; private set; } = "roslynRemoteSymbolSearchUpdateEngine";
public static string RemoteDesignerAttributeService { get; private set; } = "roslynRemoteDesignerAttributeService";
public static string RemoteProjectTelemetryService { get; private set; } = "roslynRemoteProjectTelemetryService";
public static string LanguageServer { get; private set; } = "roslynLanguageServer";
// these are OOP implementation itself should care. not features that consume OOP care
......
// 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.
#nullable enable
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ProjectTelemetry;
using Microsoft.CodeAnalysis.SolutionCrawler;
namespace Microsoft.CodeAnalysis.Remote
{
internal class RemoteProjectTelemetryIncrementalAnalyzer : IncrementalAnalyzerBase
{
/// <summary>
/// Channel back to VS to inform it of the designer attributes we discover.
/// </summary>
private readonly RemoteEndPoint _endPoint;
private readonly object _gate = new object();
private readonly Dictionary<ProjectId, ProjectTelemetryInfo> _projectToInfo = new Dictionary<ProjectId, ProjectTelemetryInfo>();
public RemoteProjectTelemetryIncrementalAnalyzer(RemoteEndPoint endPoint)
=> _endPoint = endPoint;
/// <summary>
/// Collects data from <paramref name="project"/> and reports it to the telemetry service.
/// </summary>
/// <remarks>
/// Only sends data to the telemetry service when one of the collected data points changes,
/// not necessarily every time this code is called.
/// </remarks>
public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken)
{
if (!semanticsChanged)
return;
var projectId = project.Id;
var language = project.Language;
var analyzerReferencesCount = project.AnalyzerReferences.Count;
var projectReferencesCount = project.AllProjectReferences.Count;
var metadataReferencesCount = project.MetadataReferences.Count;
var documentsCount = project.DocumentIds.Count;
var additionalDocumentsCount = project.AdditionalDocumentIds.Count;
var info = new ProjectTelemetryInfo
{
ProjectId = projectId,
Language = language,
AnalyzerReferencesCount = analyzerReferencesCount,
ProjectReferencesCount = projectReferencesCount,
MetadataReferencesCount = metadataReferencesCount,
DocumentsCount = documentsCount,
AdditionalDocumentsCount = additionalDocumentsCount,
};
lock (_gate)
{
if (_projectToInfo.TryGetValue(projectId, out var existingInfo) &&
existingInfo.Equals(info))
{
// already have reported this. No need to notify VS.
return;
}
_projectToInfo[projectId] = info;
}
await _endPoint.InvokeAsync(
nameof(IProjectTelemetryServiceCallback.RegisterProjectTelemetryInfoAsync),
new object[] { info },
cancellationToken).ConfigureAwait(false);
}
public override void RemoveProject(ProjectId projectId)
{
lock (_gate)
{
_projectToInfo.Remove(projectId);
}
}
}
}
// 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.
#nullable enable
using Microsoft.CodeAnalysis.SolutionCrawler;
namespace Microsoft.CodeAnalysis.Remote
{
/// <remarks>Note: this is explicitly <b>not</b> exported. We don't want the <see
/// cref="RemoteWorkspace"/> to automatically load this. Instead, VS waits until it is ready
/// and then calls into OOP to tell it to start analyzing the solution. At that point we'll get
/// created and added to the solution crawler.
/// </remarks>
internal class RemoteProjectTelemetryIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider
{
private readonly RemoteEndPoint _endPoint;
public RemoteProjectTelemetryIncrementalAnalyzerProvider(RemoteEndPoint endPoint)
{
_endPoint = endPoint;
}
public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace)
=> new RemoteProjectTelemetryIncrementalAnalyzer(_endPoint);
}
}
// 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.
#nullable enable
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ProjectTelemetry;
using Microsoft.CodeAnalysis.SolutionCrawler;
namespace Microsoft.CodeAnalysis.Remote
{
internal partial class RemoteProjectTelemetryService : ServiceBase, IRemoteProjectTelemetryService
{
public RemoteProjectTelemetryService(
Stream stream, IServiceProvider serviceProvider)
: base(serviceProvider, stream)
{
StartService();
}
public Task ComputeProjectTelemetryAsync(CancellationToken cancellation)
{
return RunServiceAsync(() =>
{
var workspace = SolutionService.PrimaryWorkspace;
var endpoint = this.EndPoint;
var registrationService = workspace.Services.GetRequiredService<ISolutionCrawlerRegistrationService>();
var analyzerProvider = new RemoteProjectTelemetryIncrementalAnalyzerProvider(endpoint);
registrationService.AddAnalyzerProvider(
analyzerProvider,
new IncrementalAnalyzerProviderMetadata(
nameof(RemoteProjectTelemetryIncrementalAnalyzerProvider),
highPriorityForActiveFile: false,
workspaceKinds: WorkspaceKind.RemoteWorkspace));
return Task.CompletedTask;
}, cancellation);
}
}
}
......@@ -318,6 +318,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Utilities\AbstractSpeculationAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\AliasSymbolCache.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\AnnotationTable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\AsyncBatchingWorkQueue.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\AsyncLazy`1.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\BidirectionalMap.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\BKTree.Builder.cs" />
......
// 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.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Roslyn.Utilities
{
/// <summary>
/// A queue where items can be added to to be processed in batches after some delay has passed.
/// When processing happens, all the items added since the last processing point will be passed
/// along to be worked on. Rounds of processing happen serially, only starting up after a
/// previous round has completed.
/// </summary>
internal class AsyncBatchingWorkQueue<TItem>
{
/// <summary>
/// Delay we wait after finishing the processing of one batch and starting up on then.
/// </summary>
private readonly TimeSpan _delay;
/// <summary>
/// Callback to actually perform the processing of the next batch of work.
/// </summary>
private readonly Func<ImmutableArray<TItem>, CancellationToken, Task> _processBatchAsync;
private readonly CancellationToken _cancellationToken;
#region protected by lock
/// <summary>
/// Lock we will use to ensure the remainder of these fields can be accessed in a threadsafe
/// manner. When work is added we'll place the data into <see cref="_nextBatch"/>.
/// We'll then kick of a task to process this in the future if we don't already have an
/// existing task in flight for that.
/// </summary>
private readonly object _gate = new object();
/// <summary>
/// Data added that we want to process in our next update task.
/// </summary>
private readonly List<TItem> _nextBatch = new List<TItem>();
/// <summary>
/// Task kicked off to do the next batch of processing of <see cref="_nextBatch"/>. These
/// tasks form a chain so that the next task only processes when the previous one completes.
/// </summary>
private Task _updateTask = Task.CompletedTask;
/// <summary>
/// Whether or not there is an existing task in flight that will process the current batch
/// of <see cref="_nextBatch"/>. If there is an existing in flight task, we don't need to
/// kick off a new one if we receive more work before it runs.
/// </summary>
private bool _taskInFlight = false;
#endregion
public AsyncBatchingWorkQueue(
TimeSpan delay,
Func<ImmutableArray<TItem>, CancellationToken, Task> processBatchAsync,
CancellationToken cancellationToken)
{
_delay = delay;
_processBatchAsync = processBatchAsync;
_cancellationToken = cancellationToken;
}
public void AddWork(TItem item)
{
using var _ = ArrayBuilder<TItem>.GetInstance(out var items);
items.Add(item);
AddWork(items);
}
public void AddWork(IEnumerable<TItem> items)
{
// Don't do any more work if we've been asked to shutdown.
if (_cancellationToken.IsCancellationRequested)
return;
lock (_gate)
{
// add our work to the set we'll process in the next batch.
_nextBatch.AddRange(items);
if (!_taskInFlight)
{
// No in-flight task. Kick one off to process these messages a second from now.
// We always attach the task to the previous one so that notifications to the ui
// follow the same order as the notification the OOP server sent to us.
_updateTask = _updateTask.ContinueWithAfterDelayFromAsync(
_ => ProcessNextBatchAsync(_cancellationToken),
_cancellationToken,
(int)_delay.TotalMilliseconds,
TaskContinuationOptions.RunContinuationsAsynchronously,
TaskScheduler.Default);
_taskInFlight = true;
}
}
}
private Task ProcessNextBatchAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return _processBatchAsync(GetNextBatchAndResetQueue(), _cancellationToken);
}
private ImmutableArray<TItem> GetNextBatchAndResetQueue()
{
lock (_gate)
{
var result = ArrayBuilder<TItem>.GetInstance();
result.AddRange(_nextBatch);
// mark there being no existing update task so that the next OOP notification will
// kick one off.
_nextBatch.Clear();
_taskInFlight = false;
return result.ToImmutableAndFree();
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册