Skip to content

Commit 90b7879

Browse files
authored
Add feature-level telemetry for dotnet run command (#50793)
2 parents f83ad23 + 685b86f commit 90b7879

File tree

3 files changed

+665
-10
lines changed

3 files changed

+665
-10
lines changed

src/Cli/dotnet/Commands/Run/RunCommand.cs

Lines changed: 146 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,15 @@ public int Execute()
124124

125125
Func<ProjectCollection, ProjectInstance>? projectFactory = null;
126126
RunProperties? cachedRunProperties = null;
127+
VirtualProjectBuildingCommand? virtualCommand = null;
127128
if (ShouldBuild)
128129
{
129130
if (string.Equals("true", launchSettings?.DotNetRunMessages, StringComparison.OrdinalIgnoreCase))
130131
{
131132
Reporter.Output.WriteLine(CliCommandStrings.RunCommandBuilding);
132133
}
133134

134-
EnsureProjectIsBuilt(out projectFactory, out cachedRunProperties);
135+
EnsureProjectIsBuilt(out projectFactory, out cachedRunProperties, out virtualCommand);
135136
}
136137
else
137138
{
@@ -143,11 +144,11 @@ public int Execute()
143144
if (EntryPointFileFullPath is not null)
144145
{
145146
Debug.Assert(!ReadCodeFromStdin);
146-
var command = CreateVirtualCommand();
147-
command.MarkArtifactsFolderUsed();
147+
virtualCommand = CreateVirtualCommand();
148+
virtualCommand.MarkArtifactsFolderUsed();
148149

149-
var cacheEntry = command.GetPreviousCacheEntry();
150-
projectFactory = CanUseRunPropertiesForCscBuiltProgram(BuildLevel.None, cacheEntry) ? null : command.CreateProjectInstance;
150+
var cacheEntry = virtualCommand.GetPreviousCacheEntry();
151+
projectFactory = CanUseRunPropertiesForCscBuiltProgram(BuildLevel.None, cacheEntry) ? null : virtualCommand.CreateProjectInstance;
151152
cachedRunProperties = cacheEntry?.Run;
152153
}
153154
}
@@ -163,6 +164,9 @@ public int Execute()
163164
targetCommand.EnvironmentVariable(name, value);
164165
}
165166

167+
// Send telemetry about the run operation
168+
SendRunTelemetry(launchSettings, virtualCommand);
169+
166170
// Ignore Ctrl-C for the remainder of the command's execution
167171
Console.CancelKeyPress += (sender, e) => { e.Cancel = true; };
168172

@@ -297,22 +301,23 @@ internal bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel
297301
}
298302
}
299303

300-
private void EnsureProjectIsBuilt(out Func<ProjectCollection, ProjectInstance>? projectFactory, out RunProperties? cachedRunProperties)
304+
private void EnsureProjectIsBuilt(out Func<ProjectCollection, ProjectInstance>? projectFactory, out RunProperties? cachedRunProperties, out VirtualProjectBuildingCommand? virtualCommand)
301305
{
302306
int buildResult;
303307
if (EntryPointFileFullPath is not null)
304308
{
305-
var command = CreateVirtualCommand();
306-
buildResult = command.Execute();
307-
projectFactory = CanUseRunPropertiesForCscBuiltProgram(command.LastBuild.Level, command.LastBuild.Cache?.PreviousEntry) ? null : command.CreateProjectInstance;
308-
cachedRunProperties = command.LastBuild.Cache?.CurrentEntry.Run;
309+
virtualCommand = CreateVirtualCommand();
310+
buildResult = virtualCommand.Execute();
311+
projectFactory = CanUseRunPropertiesForCscBuiltProgram(virtualCommand.LastBuild.Level, virtualCommand.LastBuild.Cache?.PreviousEntry) ? null : virtualCommand.CreateProjectInstance;
312+
cachedRunProperties = virtualCommand.LastBuild.Cache?.CurrentEntry.Run;
309313
}
310314
else
311315
{
312316
Debug.Assert(ProjectFileFullPath is not null);
313317

314318
projectFactory = null;
315319
cachedRunProperties = null;
320+
virtualCommand = null;
316321
buildResult = new RestoringCommand(
317322
MSBuildArgs.CloneWithExplicitArgs([ProjectFileFullPath, .. MSBuildArgs.OtherMSBuildArgs]),
318323
NoRestore,
@@ -753,4 +758,135 @@ public static ParseResult ModifyParseResultForShorthandProjectOption(ParseResult
753758
var newParseResult = Parser.Parse(tokensToParse);
754759
return newParseResult;
755760
}
761+
762+
/// <summary>
763+
/// Sends telemetry about the run operation.
764+
/// </summary>
765+
private void SendRunTelemetry(
766+
ProjectLaunchSettingsModel? launchSettings,
767+
VirtualProjectBuildingCommand? virtualCommand)
768+
{
769+
try
770+
{
771+
if (virtualCommand != null)
772+
{
773+
SendFileBasedTelemetry(launchSettings, virtualCommand);
774+
}
775+
else
776+
{
777+
SendProjectBasedTelemetry(launchSettings);
778+
}
779+
}
780+
catch (Exception ex)
781+
{
782+
// Silently ignore telemetry errors to not affect the run operation
783+
if (CommandLoggingContext.IsVerbose)
784+
{
785+
Reporter.Verbose.WriteLine($"Failed to send run telemetry: {ex}");
786+
}
787+
}
788+
}
789+
790+
/// <summary>
791+
/// Builds and sends telemetry data for file-based app runs.
792+
/// </summary>
793+
private void SendFileBasedTelemetry(
794+
ProjectLaunchSettingsModel? launchSettings,
795+
VirtualProjectBuildingCommand virtualCommand)
796+
{
797+
Debug.Assert(EntryPointFileFullPath != null);
798+
var projectIdentifier = RunTelemetry.GetFileBasedIdentifier(EntryPointFileFullPath, Sha256Hasher.Hash);
799+
800+
var directives = virtualCommand.Directives;
801+
var sdkCount = RunTelemetry.CountSdks(directives);
802+
var packageReferenceCount = RunTelemetry.CountPackageReferences(directives);
803+
var projectReferenceCount = RunTelemetry.CountProjectReferences(directives);
804+
var additionalPropertiesCount = RunTelemetry.CountAdditionalProperties(directives);
805+
806+
RunTelemetry.TrackRunEvent(
807+
isFileBased: true,
808+
projectIdentifier: projectIdentifier,
809+
launchProfile: LaunchProfile,
810+
noLaunchProfile: NoLaunchProfile,
811+
launchSettings: launchSettings,
812+
sdkCount: sdkCount,
813+
packageReferenceCount: packageReferenceCount,
814+
projectReferenceCount: projectReferenceCount,
815+
additionalPropertiesCount: additionalPropertiesCount,
816+
usedMSBuild: virtualCommand.LastBuild.Level is BuildLevel.All,
817+
usedRoslynCompiler: virtualCommand.LastBuild.Level is BuildLevel.Csc);
818+
}
819+
820+
/// <summary>
821+
/// Builds and sends telemetry data for project-based app runs.
822+
/// </summary>
823+
private void SendProjectBasedTelemetry(ProjectLaunchSettingsModel? launchSettings)
824+
{
825+
Debug.Assert(ProjectFileFullPath != null);
826+
var projectIdentifier = RunTelemetry.GetProjectBasedIdentifier(ProjectFileFullPath, GetRepositoryRoot(), Sha256Hasher.Hash);
827+
828+
// Get package and project reference counts for project-based apps
829+
int packageReferenceCount = 0;
830+
int projectReferenceCount = 0;
831+
832+
// Try to get project information for telemetry if we built the project
833+
if (ShouldBuild)
834+
{
835+
try
836+
{
837+
var globalProperties = MSBuildArgs.GlobalProperties?.ToDictionary() ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
838+
globalProperties[Constants.EnableDefaultItems] = "false";
839+
globalProperties[Constants.MSBuildExtensionsPath] = AppContext.BaseDirectory;
840+
841+
using var collection = new ProjectCollection(globalProperties: globalProperties);
842+
var project = collection.LoadProject(ProjectFileFullPath).CreateProjectInstance();
843+
844+
packageReferenceCount = RunTelemetry.CountPackageReferences(project);
845+
projectReferenceCount = RunTelemetry.CountProjectReferences(project);
846+
}
847+
catch
848+
{
849+
// If project evaluation fails for telemetry, use defaults
850+
// We don't want telemetry collection to affect the run operation
851+
}
852+
}
853+
854+
RunTelemetry.TrackRunEvent(
855+
isFileBased: false,
856+
projectIdentifier: projectIdentifier,
857+
launchProfile: LaunchProfile,
858+
noLaunchProfile: NoLaunchProfile,
859+
launchSettings: launchSettings,
860+
packageReferenceCount: packageReferenceCount,
861+
projectReferenceCount: projectReferenceCount);
862+
}
863+
864+
/// <summary>
865+
/// Attempts to find the repository root directory.
866+
/// </summary>
867+
/// <returns>Repository root path if found, null otherwise</returns>
868+
private string? GetRepositoryRoot()
869+
{
870+
try
871+
{
872+
var currentDir = ProjectFileFullPath != null
873+
? Path.GetDirectoryName(ProjectFileFullPath)
874+
: Directory.GetCurrentDirectory();
875+
876+
while (currentDir != null)
877+
{
878+
if (Directory.Exists(Path.Combine(currentDir, ".git")))
879+
{
880+
return currentDir;
881+
}
882+
currentDir = Directory.GetParent(currentDir)?.FullName;
883+
}
884+
}
885+
catch
886+
{
887+
// Ignore errors when trying to find repo root
888+
}
889+
890+
return null;
891+
}
756892
}

0 commit comments

Comments
 (0)