File: Telemetry\WorkerNodeTelemetryData.cs
Web Access
Project: Microsoft.Build.Framework.csproj (Microsoft.Build.Framework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;

namespace Microsoft.Build.Framework.Telemetry;

internal class WorkerNodeTelemetryData : IWorkerNodeTelemetryData
{
    public WorkerNodeTelemetryData(
        Dictionary<TaskOrTargetTelemetryKey, TaskExecutionStats> tasksExecutionData,
        Dictionary<TaskOrTargetTelemetryKey, TargetExecutionStats> targetsExecutionData,
        int upToDateInputOutputTargetsCount = 0,
        int executedInputOutputTargetsCount = 0,
        int executedTargetsCount = 0,
        int skippedTargetsCount = 0,
        int skippedDueToUpToDateCount = 0,
        int skippedDueToConditionCount = 0,
        int skippedDueToPreviouslyBuiltCount = 0)
    {
        TasksExecutionData = tasksExecutionData;
        TargetsExecutionData = targetsExecutionData;
        UpToDateInputOutputTargetsCount = upToDateInputOutputTargetsCount;
        ExecutedInputOutputTargetsCount = executedInputOutputTargetsCount;
        ExecutedTargetsCount = executedTargetsCount;
        SkippedTargetsCount = skippedTargetsCount;
        SkippedDueToUpToDateCount = skippedDueToUpToDateCount;
        SkippedDueToConditionCount = skippedDueToConditionCount;
        SkippedDueToPreviouslyBuiltCount = skippedDueToPreviouslyBuiltCount;
    }

    /// <summary>
    /// Merges all data from another <see cref="IWorkerNodeTelemetryData"/> into this instance.
    /// </summary>
    public void Add(IWorkerNodeTelemetryData other)
    {
        foreach (var task in other.TasksExecutionData)
        {
            AddTask(task.Key, task.Value.CumulativeExecutionTime, task.Value.ExecutionsCount, task.Value.TotalMemoryBytes, task.Value.TaskFactoryName, task.Value.TaskHostRuntime);
        }

        foreach (var target in other.TargetsExecutionData)
        {
            AddTarget(target.Key, target.Value.WasExecuted, target.Value.SkipReason);
        }

        UpToDateInputOutputTargetsCount += other.UpToDateInputOutputTargetsCount;
        ExecutedInputOutputTargetsCount += other.ExecutedInputOutputTargetsCount;
        ExecutedTargetsCount += other.ExecutedTargetsCount;
        SkippedTargetsCount += other.SkippedTargetsCount;
        SkippedDueToUpToDateCount += other.SkippedDueToUpToDateCount;
        SkippedDueToConditionCount += other.SkippedDueToConditionCount;
        SkippedDueToPreviouslyBuiltCount += other.SkippedDueToPreviouslyBuiltCount;
    }

    /// <summary>
    /// Adds or aggregates task execution data.
    /// </summary>
    public void AddTask(TaskOrTargetTelemetryKey task, TimeSpan cumulativeExecutionTime, int executionsCount, long? totalMemoryConsumption, string? factoryName, string? taskHostRuntime)
    {
        if (!TasksExecutionData.TryGetValue(task, out TaskExecutionStats? taskExecutionStats))
        {
            taskExecutionStats = new(cumulativeExecutionTime, executionsCount, totalMemoryConsumption, factoryName, taskHostRuntime);
            TasksExecutionData[task] = taskExecutionStats;
        }
        else
        {
            bool existingHasExecutions = taskExecutionStats.ExecutionsCount > 0;
            taskExecutionStats.CumulativeExecutionTime += cumulativeExecutionTime;
            taskExecutionStats.ExecutionsCount += executionsCount;
            if (totalMemoryConsumption.HasValue)
            {
                taskExecutionStats.TotalMemoryBytes = (taskExecutionStats.TotalMemoryBytes ?? 0) + totalMemoryConsumption.Value;
            }

            if (!existingHasExecutions && executionsCount > 0)
            {
                taskExecutionStats.TaskFactoryName = factoryName ?? taskExecutionStats.TaskFactoryName;
                taskExecutionStats.TaskHostRuntime = taskHostRuntime ?? taskExecutionStats.TaskHostRuntime;
            }
            else
            {
                taskExecutionStats.TaskFactoryName ??= factoryName;
                taskExecutionStats.TaskHostRuntime ??= taskHostRuntime;
            }
        }
    }

    /// <summary>
    /// Adds or updates target execution data.
    /// </summary>
    public void AddTarget(TaskOrTargetTelemetryKey target, bool wasExecuted, TargetSkipReason skipReason = TargetSkipReason.None)
    {
        if (TargetsExecutionData.TryGetValue(target, out var existingStats))
        {
            // If the target was ever executed, mark it as executed
            // Otherwise, keep the most informative skip reason (non-None preferred)
            if (wasExecuted || existingStats.WasExecuted)
            {
                TargetsExecutionData[target] = TargetExecutionStats.Executed();
            }
            else if (skipReason != TargetSkipReason.None)
            {
                TargetsExecutionData[target] = TargetExecutionStats.Skipped(skipReason);
            }
            // else keep existing stats
        }
        else
        {
            TargetsExecutionData[target] = wasExecuted
                ? TargetExecutionStats.Executed()
                : TargetExecutionStats.Skipped(skipReason);
        }
    }

    public WorkerNodeTelemetryData() : this([], []) { }

    public void AddTargetResult(bool wasExecuted, TargetSkipReason skipReason = TargetSkipReason.None)
    {
        if (wasExecuted)
        {
            ExecutedTargetsCount++;
            return;
        }

        SkippedTargetsCount++;
        switch (skipReason)
        {
            case TargetSkipReason.OutputsUpToDate:
                SkippedDueToUpToDateCount++;
                break;
            case TargetSkipReason.ConditionWasFalse:
                SkippedDueToConditionCount++;
                break;
            case TargetSkipReason.PreviouslyBuiltSuccessfully:
            case TargetSkipReason.PreviouslyBuiltUnsuccessfully:
                SkippedDueToPreviouslyBuiltCount++;
                break;
        }
    }

    public void AddInputOutputTarget(bool upToDate)
    {
        if (upToDate)
        {
            UpToDateInputOutputTargetsCount++;
        }
        else
        {
            ExecutedInputOutputTargetsCount++;
        }
    }

    public bool IsEmpty =>
        TasksExecutionData.Count == 0 &&
        TargetsExecutionData.Count == 0 &&
        ExecutedTargetsCount == 0 &&
        SkippedTargetsCount == 0 &&
        UpToDateInputOutputTargetsCount == 0 &&
        ExecutedInputOutputTargetsCount == 0;

    public Dictionary<TaskOrTargetTelemetryKey, TaskExecutionStats> TasksExecutionData { get; }

    public Dictionary<TaskOrTargetTelemetryKey, TargetExecutionStats> TargetsExecutionData { get; }

    public int ExecutedTargetsCount { get; private set; }

    public int SkippedTargetsCount { get; private set; }

    public int SkippedDueToUpToDateCount { get; private set; }

    public int SkippedDueToConditionCount { get; private set; }

    public int SkippedDueToPreviouslyBuiltCount { get; private set; }

    public int UpToDateInputOutputTargetsCount { get; private set; }

    public int ExecutedInputOutputTargetsCount { get; private set; }
}