File: TelemetryInfra\TasksDetailsTelemetry.cs
Web Access
Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// 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 System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Build.Framework.Telemetry;
using static Microsoft.Build.Framework.Telemetry.TelemetryDataUtils;

namespace Microsoft.Build.TelemetryInfra;

/// <summary>
/// Serializes per-task execution details into a TelemetryEventArgs-compatible
/// (<see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> of strings) shape for the CLI/SDK telemetry path.
///
/// Equivalent data is published to VS via <see cref="TelemetryDataUtils.AsActivityDataHolder"/>, where the
/// VS telemetry sink accepts arbitrary objects and serializes them itself. The CLI path requires strings
/// (see <see cref="Framework.TelemetryEventArgs"/>), so we serialize a JSON array here.
/// </summary>
internal static class TasksDetailsTelemetry
{
    /// <summary>
    /// Event name for per-task execution details emitted via TelemetryEventArgs.
    /// Uses a separate event name so per-task details are distinguishable from
    /// the per-project aggregated "build/tasks" telemetry event.
    /// </summary>
    internal const string TasksDetailsEventName = "build/tasks/details";

    /// <summary>
    /// Maximum number of individual task details to include in TelemetryEventArgs-based telemetry.
    /// </summary>
    private const int MaxTaskDetailsForTelemetryEvent = 100;

    internal static Dictionary<string, string>? GetTasksDetailsProperties(this IWorkerNodeTelemetryData? telemetryData)
    {
        if (telemetryData is null || telemetryData.TasksExecutionData.Count == 0)
        {
            return null;
        }

        List<TaskDetailInfo> allTasks = GetTasksDetails(telemetryData.TasksExecutionData);
        List<TaskDetailInfo> topTasks = allTasks
            .OrderByDescending(t => t.ExecutionsCount)
            .Take(MaxTaskDetailsForTelemetryEvent)
            .ToList();

        return new Dictionary<string, string>(3)
        {
            ["TaskCount"] = topTasks.Count.ToString(CultureInfo.InvariantCulture),
            ["TotalTaskCount"] = allTasks.Count.ToString(CultureInfo.InvariantCulture),
            ["Tasks"] = JsonSerializer.Serialize(topTasks, TasksDetailsJsonContext.Default.ListTaskDetailInfo),
        };
    }
}

[JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(List<TaskDetailInfo>))]
internal partial class TasksDetailsJsonContext : JsonSerializerContext;