未验证 提交 6db1b7a5 编写于 作者: J Joey Robichaud 提交者: GitHub

Merge pull request #41443 from JoeRobich/add-telemetry-ids

Add ProjectGuid and SolutionSessionId to API telemetry
......@@ -14,12 +14,16 @@
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Internal.VisualStudio.Shell;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
[Export(typeof(VisualStudioProjectFactory))]
internal sealed class VisualStudioProjectFactory
{
private const string SolutionContextName = "Solution";
private const string SolutionSessionIdPropertyName = "SolutionSessionID";
private readonly VisualStudioWorkspaceImpl _visualStudioWorkspaceImpl;
private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource;
private readonly ImmutableArray<Lazy<IDynamicFileInfoProvider, FileExtensionsMetadata>> _dynamicFileInfoProviders;
......@@ -78,7 +82,8 @@ public VisualStudioProject CreateAndAddToWorkspace(string projectSystemName, str
language: language,
filePath: creationInfo.FilePath,
compilationOptions: creationInfo.CompilationOptions,
parseOptions: creationInfo.ParseOptions);
parseOptions: creationInfo.ParseOptions)
.WithTelemetryId(creationInfo.ProjectGuid);
// If we don't have any projects and this is our first project being added, then we'll create a new SolutionId
if (w.CurrentSolution.ProjectIds.Count == 0)
......@@ -95,13 +100,16 @@ public VisualStudioProject CreateAndAddToWorkspace(string projectSystemName, str
}
}
var solutionSessionId = GetSolutionSessionId();
w.OnSolutionAdded(
SolutionInfo.Create(
SolutionId.CreateNewId(solutionFilePath),
VersionStamp.Create(),
solutionFilePath,
projects: new[] { projectInfo },
analyzerReferences: w.CurrentSolution.AnalyzerReferences));
analyzerReferences: w.CurrentSolution.AnalyzerReferences)
.WithTelemetryId(solutionSessionId));
}
else
{
......@@ -112,6 +120,24 @@ public VisualStudioProject CreateAndAddToWorkspace(string projectSystemName, str
});
return project;
static Guid GetSolutionSessionId()
{
try
{
var solutionContext = TelemetryHelper.DataModelTelemetrySession.GetContext(SolutionContextName);
var sessionIdProperty = solutionContext is object
? (string)solutionContext.SharedProperties[SolutionSessionIdPropertyName]
: "";
_ = Guid.TryParse(sessionIdProperty, out var solutionSessionId);
return solutionSessionId;
}
catch (TypeInitializationException)
{
// The TelemetryHelper cannot be constructed during unittests.
return default;
}
}
}
}
}
......@@ -40,7 +40,7 @@ public async Task<(SolutionInfo, SerializableOptionSet)> CreateSolutionInfoAndOp
var analyzerReferences = await CreateCollectionAsync<AnalyzerReference>(solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false);
var info = SolutionInfo.Create(solutionAttributes.Id, solutionAttributes.Version, solutionAttributes.FilePath, projects, analyzerReferences);
var info = SolutionInfo.Create(solutionAttributes.Id, solutionAttributes.Version, solutionAttributes.FilePath, projects, analyzerReferences).WithTelemetryId(solutionAttributes.TelemetryId);
var options = await GetAssetAsync<SerializableOptionSet>(solutionChecksums.Options, cancellationToken).ConfigureAwait(false);
return (info, options);
}
......@@ -91,7 +91,8 @@ public async Task<ProjectInfo> CreateProjectInfoAsync(Checksum projectChecksum,
.WithHasAllInformation(projectInfo.HasAllInformation)
.WithRunAnalyzers(projectInfo.RunAnalyzers)
.WithDefaultNamespace(projectInfo.DefaultNamespace)
.WithAnalyzerConfigDocuments(analyzerConfigDocumentInfos);
.WithAnalyzerConfigDocuments(analyzerConfigDocumentInfos)
.WithTelemetryId(projectInfo.TelemetryId);
}
public async Task<DocumentInfo> CreateDocumentInfoAsync(Checksum documentChecksum, CancellationToken cancellationToken)
......
......@@ -69,13 +69,13 @@ public sealed class ProjectInfo
/// <summary>
/// The default namespace of the project ("" if not defined, which means global namespace),
/// or null if it is unknown or not applicable.
/// or null if it is unknown or not applicable.
/// </summary>
/// <remarks>
/// Right now VB doesn't have the concept of "default namespace", but we conjure one in workspace
/// by assigning the value of the project's root namespace to it. So various features can choose to
/// Right now VB doesn't have the concept of "default namespace", but we conjure one in workspace
/// by assigning the value of the project's root namespace to it. So various features can choose to
/// use it for their own purpose.
/// In the future, we might consider officially exposing "default namespace" for VB project
/// In the future, we might consider officially exposing "default namespace" for VB project
/// (e.g. through a "defaultnamespace" msbuild property)
/// </remarks>
internal string? DefaultNamespace => Attributes.DefaultNamespace;
......@@ -231,7 +231,8 @@ public sealed class ProjectInfo
defaultNamespace: null,
isSubmission,
hasAllInformation: true,
runAnalyzers: true),
runAnalyzers: true,
telemetryId: default),
compilationOptions,
parseOptions,
PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(documents, nameof(documents)),
......@@ -347,6 +348,11 @@ public ProjectInfo WithMetadataReferences(IEnumerable<MetadataReference>? metada
public ProjectInfo WithAnalyzerReferences(IEnumerable<AnalyzerReference>? analyzerReferences)
=> With(analyzerReferences: PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences)));
internal ProjectInfo WithTelemetryId(Guid telemetryId)
{
return With(attributes: Attributes.With(telemetryId: telemetryId));
}
internal string GetDebuggerDisplay()
=> nameof(ProjectInfo) + " " + Name + (!string.IsNullOrWhiteSpace(FilePath) ? " " + FilePath : "");
......@@ -425,6 +431,11 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable
/// </summary>
public bool RunAnalyzers { get; }
/// <summary>
/// The id report during telemetry events.
/// </summary>
public Guid TelemetryId { get; }
public ProjectAttributes(
ProjectId id,
VersionStamp version,
......@@ -438,7 +449,8 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable
string? defaultNamespace,
bool isSubmission,
bool hasAllInformation,
bool runAnalyzers)
bool runAnalyzers,
Guid telemetryId)
{
Id = id;
Name = name;
......@@ -454,6 +466,7 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable
IsSubmission = isSubmission;
HasAllInformation = hasAllInformation;
RunAnalyzers = runAnalyzers;
TelemetryId = telemetryId;
}
public ProjectAttributes With(
......@@ -468,7 +481,8 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable
Optional<string?> defaultNamespace = default,
Optional<bool> isSubmission = default,
Optional<bool> hasAllInformation = default,
Optional<bool> runAnalyzers = default)
Optional<bool> runAnalyzers = default,
Optional<Guid> telemetryId = default)
{
var newVersion = version ?? Version;
var newName = name ?? Name;
......@@ -482,6 +496,7 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable
var newIsSubmission = isSubmission.HasValue ? isSubmission.Value : IsSubmission;
var newHasAllInformation = hasAllInformation.HasValue ? hasAllInformation.Value : HasAllInformation;
var newRunAnalyzers = runAnalyzers.HasValue ? runAnalyzers.Value : RunAnalyzers;
var newTelemetryId = telemetryId.HasValue ? telemetryId.Value : TelemetryId;
if (newVersion == Version &&
newName == Name &&
......@@ -494,7 +509,8 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable
newDefaultNamespace == DefaultNamespace &&
newIsSubmission == IsSubmission &&
newHasAllInformation == HasAllInformation &&
newRunAnalyzers == RunAnalyzers)
newRunAnalyzers == RunAnalyzers &&
newTelemetryId == TelemetryId)
{
return this;
}
......@@ -512,7 +528,8 @@ internal sealed class ProjectAttributes : IChecksummedObject, IObjectWritable
newDefaultNamespace,
newIsSubmission,
newHasAllInformation,
newRunAnalyzers);
newRunAnalyzers,
newTelemetryId);
}
bool IObjectWritable.ShouldReuseInSerialization => true;
......@@ -535,6 +552,7 @@ public void WriteTo(ObjectWriter writer)
writer.WriteBoolean(IsSubmission);
writer.WriteBoolean(HasAllInformation);
writer.WriteBoolean(RunAnalyzers);
writer.WriteGuid(TelemetryId);
// TODO: once CompilationOptions, ParseOptions, ProjectReference, MetadataReference, AnalyzerReference supports
// serialization, we should include those here as well.
......@@ -556,6 +574,7 @@ public static ProjectAttributes ReadFrom(ObjectReader reader)
var isSubmission = reader.ReadBoolean();
var hasAllInformation = reader.ReadBoolean();
var runAnalyzers = reader.ReadBoolean();
var telemetryId = reader.ReadGuid();
return new ProjectAttributes(
projectId,
......@@ -570,7 +589,8 @@ public static ProjectAttributes ReadFrom(ObjectReader reader)
defaultNamespace,
isSubmission,
hasAllInformation,
runAnalyzers);
runAnalyzers,
telemetryId);
}
Checksum IChecksummedObject.Checksum
......
......@@ -81,7 +81,8 @@ private SolutionInfo(SolutionAttributes attributes, IReadOnlyList<ProjectInfo> p
new SolutionAttributes(
id ?? throw new ArgumentNullException(nameof(id)),
version,
filePath),
filePath,
telemetryId: default),
PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(projects, nameof(projects)),
PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences)));
}
......@@ -89,6 +90,9 @@ private SolutionInfo(SolutionAttributes attributes, IReadOnlyList<ProjectInfo> p
internal ImmutableHashSet<string> GetProjectLanguages()
=> Projects.Select(p => p.Language).ToImmutableHashSet();
internal SolutionInfo WithTelemetryId(Guid telemetryId)
=> new SolutionInfo(Attributes.With(telemetryId: telemetryId), Projects, AnalyzerReferences);
/// <summary>
/// type that contains information regarding this solution itself but
/// no tree information such as project info
......@@ -112,15 +116,37 @@ internal sealed class SolutionAttributes : IChecksummedObject, IObjectWritable
/// </summary>
public string? FilePath { get; }
public SolutionAttributes(SolutionId id, VersionStamp version, string? filePath)
/// <summary>
/// The id report during telemetry events.
/// </summary>
public Guid TelemetryId { get; }
public SolutionAttributes(SolutionId id, VersionStamp version, string? filePath, Guid telemetryId)
{
Id = id;
Version = version;
FilePath = filePath;
TelemetryId = telemetryId;
}
public SolutionAttributes WithVersion(VersionStamp versionStamp)
=> new SolutionAttributes(Id, versionStamp, FilePath);
public SolutionAttributes With(
VersionStamp? version = null,
Optional<string?> filePath = default,
Optional<Guid> telemetryId = default)
{
var newVersion = version ?? Version;
var newFilePath = filePath.HasValue ? filePath.Value : FilePath;
var newTelemetryId = telemetryId.HasValue ? telemetryId.Value : TelemetryId;
if (newVersion == Version &&
newFilePath == FilePath &&
newTelemetryId == TelemetryId)
{
return this;
}
return new SolutionAttributes(Id, newVersion, newFilePath, newTelemetryId);
}
bool IObjectWritable.ShouldReuseInSerialization => true;
......@@ -133,6 +159,7 @@ public void WriteTo(ObjectWriter writer)
// info.Version.WriteTo(writer);
writer.WriteString(FilePath);
writer.WriteGuid(TelemetryId);
}
public static SolutionAttributes ReadFrom(ObjectReader reader)
......@@ -140,8 +167,9 @@ public static SolutionAttributes ReadFrom(ObjectReader reader)
var solutionId = SolutionId.ReadFrom(reader);
// var version = VersionStamp.ReadFrom(reader);
var filePath = reader.ReadString();
var telemetryId = reader.ReadGuid();
return new SolutionAttributes(solutionId, VersionStamp.Create(), filePath);
return new SolutionAttributes(solutionId, VersionStamp.Create(), filePath, telemetryId);
}
Checksum IChecksummedObject.Checksum
......
......@@ -26,9 +26,9 @@
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Represents a set of projects and their source code documents.
///
/// this is a green node of Solution like ProjectState/DocumentState are for
/// Represents a set of projects and their source code documents.
///
/// this is a green node of Solution like ProjectState/DocumentState are for
/// Project and Document.
/// </summary>
internal partial class SolutionState
......@@ -148,13 +148,13 @@ public SolutionState WithNewWorkspace(Workspace workspace, int workspaceVersion)
/// <summary>
/// branch id of this solution
///
///
/// currently, it only supports one level of branching. there is a primary branch of a workspace and all other
/// branches that are branched from the primary branch.
///
///
/// one still can create multiple forked solutions from an already branched solution, but versions among those
/// can't be reliably used and compared.
///
/// can't be reliably used and compared.
///
/// version only has a meaning between primary solution and branched one or between solutions from same branch.
/// </summary>
public BranchId BranchId => _branchId;
......@@ -278,7 +278,7 @@ private void CheckInvariants()
private BranchId GetBranchId()
{
// currently we only support one level branching.
// currently we only support one level branching.
// my reasonings are
// 1. it seems there is no-one who needs sub branches.
// 2. this lets us to branch without explicit branch API
......@@ -440,7 +440,7 @@ private CompilationTracker GetCompilationTracker(ProjectId projectId)
private SolutionState AddProject(ProjectId projectId, ProjectState projectState)
{
// changed project list so, increment version.
var newSolutionAttributes = _solutionAttributes.WithVersion(Version.GetNewerVersion());
var newSolutionAttributes = _solutionAttributes.With(version: Version.GetNewerVersion());
var newProjectIds = ProjectIds.ToImmutableArray().Add(projectId);
var newStateMap = _projectIdToProjectStateMap.Add(projectId, projectState);
......@@ -552,7 +552,7 @@ public SolutionState RemoveProject(ProjectId projectId)
CheckContainsProject(projectId);
// changed project list so, increment version.
var newSolutionAttributes = _solutionAttributes.WithVersion(this.Version.GetNewerVersion());
var newSolutionAttributes = _solutionAttributes.With(version: this.Version.GetNewerVersion());
var newProjectIds = ProjectIds.ToImmutableArray().Remove(projectId);
var newStateMap = _projectIdToProjectStateMap.Remove(projectId);
......@@ -773,7 +773,7 @@ public SolutionState WithProjectParseOptions(ProjectId projectId, ParseOptions o
/// <summary>
/// Update a new solution instance with a fork of the specified project.
///
///
/// TODO: https://github.com/dotnet/roslyn/issues/42448
/// this is a temporary workaround until editorconfig becomes real part of roslyn solution snapshot.
/// until then, this will explicitly fork current solution snapshot
......@@ -862,9 +862,9 @@ public SolutionState RemoveProjectReference(ProjectId projectId, ProjectReferenc
!_projectIdToProjectStateMap.ContainsKey(projectReference.ProjectId))
{
// Two cases:
// 1) The project contained multiple non-equivalent references to the project,
// 1) The project contained multiple non-equivalent references to the project,
// and not all of them were removed. The dependency graph doesn't change.
// Note that there might be two references to the same project, one with
// Note that there might be two references to the same project, one with
// extern alias and the other without. These are not considered duplicates.
// 2) The referenced project is not part of the solution and hence not included
// in the dependency graph.
......@@ -1031,7 +1031,7 @@ public SolutionState WithProjectAnalyzerReferences(ProjectId projectId, IEnumera
}
/// <summary>
/// Create a new solution instance with the corresponding projects updated to include new
/// Create a new solution instance with the corresponding projects updated to include new
/// documents defined by the document info.
/// </summary>
public SolutionState AddDocuments(ImmutableArray<DocumentInfo> documentInfos)
......@@ -1549,9 +1549,9 @@ bool CanReuse(ProjectId id)
/// <summary>
/// Gets a copy of the solution isolated from the original so that they do not share computed state.
///
///
/// Use isolated solutions when doing operations that are likely to access a lot of text,
/// syntax trees or compilations that are unlikely to be needed again after the operation is done.
/// syntax trees or compilations that are unlikely to be needed again after the operation is done.
/// When the isolated solution is reclaimed so will the computed state.
/// </summary>
public SolutionState GetIsolatedSolution()
......@@ -1618,9 +1618,9 @@ private NonReentrantLock StateLock
/// <summary>
/// Creates a branch of the solution that has its compilations frozen in whatever state they are in at the time, assuming a background compiler is
/// busy building this compilations.
///
///
/// A compilation for the project containing the specified document id will be guaranteed to exist with at least the syntax tree for the document.
///
///
/// This not intended to be the public API, use Document.WithFrozenPartialSemantics() instead.
/// </summary>
public SolutionState WithFrozenPartialCompilationIncludingSpecificDocument(DocumentId documentId, CancellationToken cancellationToken)
......@@ -1749,7 +1749,7 @@ public bool TryGetCompilation(ProjectId projectId, [NotNullWhen(returnValue: tru
/// </summary>
public Task<bool> HasSuccessfullyLoadedAsync(ProjectState project, CancellationToken cancellationToken)
{
// return HasAllInformation when compilation is not supported.
// return HasAllInformation when compilation is not supported.
// regardless whether project support compilation or not, if projectInfo is not complete, we can't guarantee its reference completeness
return project.SupportsCompilation
? this.GetCompilationTracker(project.Id).HasSuccessfullyLoadedAsync(this, cancellationToken)
......@@ -1813,7 +1813,7 @@ public Task<MetadataReference> GetMetadataReferenceAsync(ProjectReference projec
ProjectState fromProject)
{
// Try to get the compilation state for this project. If it doesn't exist, don't do any
// more work.
// more work.
if (!_projectIdToTrackerMap.TryGetValue(projectReference.ProjectId, out var state))
{
return null;
......
......@@ -38,7 +38,9 @@ private sealed class Analyzer : IIncrementalAnalyzer
private const int Max = 2000;
private const string EventName = "vs/compilers/api";
private const string PropertyName = "vs.compilers.api.pii";
private const string ApiPropertyName = "vs.compilers.api.pii";
private const string ProjectIdPropertyName = "vs.solution.project.projectid";
private const string SessionIdPropertyName = "vs.solution.solutionsessionid";
private readonly HashSet<ProjectId> _reported = new HashSet<ProjectId>();
......@@ -124,9 +126,14 @@ public async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, In
{
if (_reported.Add(project.Id))
{
var solutionSessionId = project.Solution.State.SolutionAttributes.TelemetryId.ToString("B");
var projectGuid = project.State.ProjectInfo.Attributes.TelemetryId.ToString("B");
// use telemetry API directly rather than Logger abstraction for PII data
var telemetryEvent = new TelemetryEvent(EventName);
telemetryEvent.Properties[PropertyName] = new TelemetryComplexProperty(apiPerAssembly);
telemetryEvent.Properties[ApiPropertyName] = new TelemetryComplexProperty(apiPerAssembly);
telemetryEvent.Properties[SessionIdPropertyName] = new TelemetryPiiProperty(solutionSessionId);
telemetryEvent.Properties[ProjectIdPropertyName] = new TelemetryPiiProperty(projectGuid);
try
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册