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
2 changes: 1 addition & 1 deletion eng/Version.Details.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Dependencies>
<Source Uri="https://github.com/dotnet/dotnet" Mapping="msbuild" Sha="7f2a07b481a3d24677ebcf6a45e7e27c8ff95a4e" BarId="279809" />
<Source Uri="https://github.com/dotnet/dotnet" Mapping="msbuild" Sha="e72b5bbe719d747036ce9c36582a205df9f1c361" BarId="285185" />
<ProductDependencies>
<!-- Necessary for source-build. This allows the live version of the package to be used by source-build. -->
<Dependency Name="System.CodeDom" Version="9.0.0">
Expand Down
10 changes: 10 additions & 0 deletions src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ internal class ProjectLoggingContext : BuildLoggingContext
/// </summary>
private string _projectFullPath;

/// <summary>
/// Telemetry data for a project
/// </summary>
private readonly ProjectTelemetry _projectTelemetry = new ProjectTelemetry();

/// <summary>
/// Constructs a project logging context.
/// </summary>
Expand Down Expand Up @@ -253,6 +258,11 @@ private static ProjectStartedEventArgs CreateProjectStarted(
projectContextId);
}

/// <summary>
/// Telemetry data for a project
/// </summary>
internal ProjectTelemetry ProjectTelemetry => _projectTelemetry;

/// <summary>
/// Log that the project has finished
/// </summary>
Expand Down
186 changes: 186 additions & 0 deletions src/Build/BackEnd/Components/Logging/ProjectTelemetry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Globalization;
using Microsoft.Build.Framework;

namespace Microsoft.Build.BackEnd.Logging
{
/// <summary>
/// Tracks telemetry data for a project build.
/// </summary>
internal class ProjectTelemetry
{
// We do not have dependency on Microsoft.Build.Tasks assembly, so using hard-coded type names
private const string AssemblyTaskFactoryTypeName = "Microsoft.Build.BackEnd.AssemblyTaskFactory";
private const string IntrinsicTaskFactoryTypeName = "Microsoft.Build.BackEnd.IntrinsicTaskFactory";
private const string CodeTaskFactoryTypeName = "Microsoft.Build.Tasks.CodeTaskFactory";
private const string RoslynCodeTaskFactoryTypeName = "Microsoft.Build.Tasks.RoslynCodeTaskFactory";
private const string XamlTaskFactoryTypeName = "Microsoft.Build.Tasks.XamlTaskFactory";

// Important Note: these two telemetry events are not logged directly.
// SDK aggregates them per build in https://github.com/dotnet/sdk/blob/main/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs.
// Aggregation logic is very basic. Only integer properties are aggregated by summing values. Non-integer properties are ignored.
// This means that if we ever add logging non-integer properties for these events, they will not be included in the telemetry.
private const string TaskFactoryEventName = "build/tasks/taskfactory";
private const string TasksEventName = "build/tasks";

private int _assemblyTaskFactoryTasksExecutedCount = 0;
private int _intrinsicTaskFactoryTasksExecutedCount = 0;
private int _codeTaskFactoryTasksExecutedCount = 0;
private int _roslynCodeTaskFactoryTasksExecutedCount = 0;
private int _xamlTaskFactoryTasksExecutedCount = 0;
private int _customTaskFactoryTasksExecutedCount = 0;

private int _taskHostTasksExecutedCount = 0;

/// <summary>
/// Adds a task execution to the telemetry data.
/// </summary>
public void AddTaskExecution(string taskFactoryTypeName, bool isTaskHost)
{
if (isTaskHost)
{
_taskHostTasksExecutedCount++;
}

switch (taskFactoryTypeName)
{
case AssemblyTaskFactoryTypeName:
_assemblyTaskFactoryTasksExecutedCount++;
break;

case IntrinsicTaskFactoryTypeName:
_intrinsicTaskFactoryTasksExecutedCount++;
break;

case CodeTaskFactoryTypeName:
_codeTaskFactoryTasksExecutedCount++;
break;

case RoslynCodeTaskFactoryTypeName:
_roslynCodeTaskFactoryTasksExecutedCount++;
break;

case XamlTaskFactoryTypeName:
_xamlTaskFactoryTasksExecutedCount++;
break;

default:
_customTaskFactoryTasksExecutedCount++;
break;
}
}

/// <summary>
/// Logs telemetry data for a project
/// </summary>
public void LogProjectTelemetry(ILoggingService loggingService, BuildEventContext buildEventContext)
{
if (loggingService == null)
{
return;
}

try
{
Dictionary<string, string> taskFactoryProperties = GetTaskFactoryProperties();
if (taskFactoryProperties.Count > 0)
{
loggingService.LogTelemetry(buildEventContext, TaskFactoryEventName, taskFactoryProperties);
}

Dictionary<string, string> taskTotalProperties = GetTaskProperties();
if (taskTotalProperties.Count > 0)
{
loggingService.LogTelemetry(buildEventContext, TasksEventName, taskTotalProperties);
}
}
catch
{
// Ignore telemetry logging errors to avoid breaking builds
}
finally
{
// Reset counts after logging.
// ProjectLoggingContext context and, as a result, this class should not be reused between projects builds,
// however it is better to defensively clean up the stats if it ever happens in future.
Clean();
}
}

private void Clean()
{
_assemblyTaskFactoryTasksExecutedCount = 0;
_intrinsicTaskFactoryTasksExecutedCount = 0;
_codeTaskFactoryTasksExecutedCount = 0;
_roslynCodeTaskFactoryTasksExecutedCount = 0;
_xamlTaskFactoryTasksExecutedCount = 0;
_customTaskFactoryTasksExecutedCount = 0;

_taskHostTasksExecutedCount = 0;
}

private Dictionary<string, string> GetTaskFactoryProperties()
{
Dictionary<string, string> properties = new();

if (_assemblyTaskFactoryTasksExecutedCount > 0)
{
properties["AssemblyTaskFactoryTasksExecutedCount"] = _assemblyTaskFactoryTasksExecutedCount.ToString(CultureInfo.InvariantCulture);
}

if (_intrinsicTaskFactoryTasksExecutedCount > 0)
{
properties["IntrinsicTaskFactoryTasksExecutedCount"] = _intrinsicTaskFactoryTasksExecutedCount.ToString(CultureInfo.InvariantCulture);
}

if (_codeTaskFactoryTasksExecutedCount > 0)
{
properties["CodeTaskFactoryTasksExecutedCount"] = _codeTaskFactoryTasksExecutedCount.ToString(CultureInfo.InvariantCulture);
}

if (_roslynCodeTaskFactoryTasksExecutedCount > 0)
{
properties["RoslynCodeTaskFactoryTasksExecutedCount"] = _roslynCodeTaskFactoryTasksExecutedCount.ToString(CultureInfo.InvariantCulture);
}

if (_xamlTaskFactoryTasksExecutedCount > 0)
{
properties["XamlTaskFactoryTasksExecutedCount"] = _xamlTaskFactoryTasksExecutedCount.ToString(CultureInfo.InvariantCulture);
}

if (_customTaskFactoryTasksExecutedCount > 0)
{
properties["CustomTaskFactoryTasksExecutedCount"] = _customTaskFactoryTasksExecutedCount.ToString(CultureInfo.InvariantCulture);
}

return properties;
}

private Dictionary<string, string> GetTaskProperties()
{
Dictionary<string, string> properties = new();

var totalTasksExecuted = _assemblyTaskFactoryTasksExecutedCount +
_intrinsicTaskFactoryTasksExecutedCount +
_codeTaskFactoryTasksExecutedCount +
_roslynCodeTaskFactoryTasksExecutedCount +
_xamlTaskFactoryTasksExecutedCount +
_customTaskFactoryTasksExecutedCount;

if (totalTasksExecuted > 0)
{
properties["TasksExecutedCount"] = totalTasksExecuted.ToString(CultureInfo.InvariantCulture);
}

if (_taskHostTasksExecutedCount > 0)
{
properties["TaskHostTasksExecutedCount"] = _taskHostTasksExecutedCount.ToString(CultureInfo.InvariantCulture);
}

return properties;
}
}
}
2 changes: 2 additions & 0 deletions src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,8 @@ private void ReportResultAndCleanUp(BuildResult result)
{
try
{
_projectLoggingContext.ProjectTelemetry.LogProjectTelemetry(_projectLoggingContext.LoggingService, _projectLoggingContext.BuildEventContext);

_projectLoggingContext.LogProjectFinished(result.OverallResult == BuildResultCode.Success);
}
catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
Expand Down
3 changes: 3 additions & 0 deletions src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,9 @@ private ITask InstantiateTask(IDictionary<string, string> taskIdentityParameters
task = _taskFactoryWrapper.TaskFactory is ITaskFactory2 taskFactory2 ?
taskFactory2.CreateTask(loggingHost, taskIdentityParameters) :
_taskFactoryWrapper.TaskFactory.CreateTask(loggingHost);

// Track telemetry for non-AssemblyTaskFactory task factories. No task can go to the task host.
_taskLoggingContext?.TargetLoggingContext?.ProjectLoggingContext?.ProjectTelemetry?.AddTaskExecution(_taskFactoryWrapper.TaskFactory.GetType().FullName, isTaskHost: false);
}
finally
{
Expand Down
2 changes: 2 additions & 0 deletions src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ internal ITask CreateTaskInstance(
useTaskFactory = _taskHostFactoryExplicitlyRequested;
}

_taskLoggingContext?.TargetLoggingContext?.ProjectLoggingContext?.ProjectTelemetry?.AddTaskExecution(GetType().FullName, isTaskHost: useTaskFactory);

if (useTaskFactory)
{
ErrorUtilities.VerifyThrowInternalNull(buildComponentHost);
Expand Down
1 change: 1 addition & 0 deletions src/Build/Microsoft.Build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
<Compile Include="BackEnd\Components\Logging\LoggingServiceLogMethods.cs" />
<Compile Include="BackEnd\Components\Logging\NodeLoggingContext.cs" />
<Compile Include="BackEnd\Components\Logging\ProjectLoggingContext.cs" />
<Compile Include="BackEnd\Components\Logging\ProjectTelemetry.cs" />
<Compile Include="BackEnd\Components\Logging\TargetLoggingContext.cs" />
<Compile Include="BackEnd\Components\Logging\TaskLoggingContext.cs" />
<Compile Include="BackEnd\Components\RequestBuilder\IntrinsicTasks\CallTarget.cs" />
Expand Down
Loading