|
// 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;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.Build.Construction;
using Microsoft.Build.Framework.Profiler;
using Microsoft.Build.Shared;
#nullable disable
namespace Microsoft.Build.Evaluation
{
/// <summary>
/// Tracks an assortment of evaluation elements in a stack-like fashion, keeping inclusive and exclusive times for each of them.
/// </summary>
internal sealed class EvaluationProfiler
{
private readonly bool _shouldTrackElements;
private readonly Stack<EvaluationFrame> _evaluationStack = new Stack<EvaluationFrame>();
private readonly Dictionary<EvaluationLocation, ProfiledLocation> _timeSpent = new Dictionary<EvaluationLocation, ProfiledLocation>();
private EvaluationLocation CurrentLocation => _evaluationStack.Count == 0 ? EvaluationLocation.EmptyLocation : _evaluationStack.Peek().Location;
/// <summary>
/// If <param name="shouldTrackElements"/> is false, then requesting to track a given element has no effect and a null <see cref="IDisposable"/> is returned.
/// </summary>
internal EvaluationProfiler(bool shouldTrackElements)
{
_shouldTrackElements = shouldTrackElements;
}
/// <summary>
/// Contains each evaluated location with its associated timed entry
/// </summary>
public ProfilerResult? ProfiledResult => _shouldTrackElements ? (ProfilerResult?)new ProfilerResult(_timeSpent) : null;
/// <nodoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDisposable TrackPass(EvaluationPass evaluationPass, string passDescription = null)
{
return _shouldTrackElements
? new EvaluationFrame(this,
CurrentLocation.WithEvaluationPass(evaluationPass, passDescription))
: null;
}
/// <nodoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDisposable TrackFile(string file)
{
return _shouldTrackElements ? new EvaluationFrame(this, CurrentLocation.WithFile(file)) : null;
}
/// <nodoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDisposable TrackGlob(string rootDirectory, string glob, ISet<string> excludePatterns)
{
return _shouldTrackElements
? new EvaluationFrame(this,
CurrentLocation.WithGlob(
$"root: '${rootDirectory}', pattern: '${glob}', excludes: '${string.Join(";", excludePatterns)}'"))
: null;
}
/// <nodoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDisposable TrackElement(ProjectElement element)
{
return _shouldTrackElements ? new EvaluationFrame(this, CurrentLocation.WithFileLineAndElement(element.Location.File, element.Location.Line, element)) : null;
}
/// <nodoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDisposable TrackCondition(IElementLocation location, string condition)
{
return _shouldTrackElements ? new EvaluationFrame(this, CurrentLocation.WithFileLineAndCondition(location.File, location.Line, condition)) : null;
}
/// <summary>
/// Returns true when the evaluation stack is empty.
/// </summary>
/// <returns></returns>
internal bool IsEmpty()
{
return _evaluationStack.Count == 0;
}
/// <summary>
/// A frame in the evaluation tracker
/// </summary>
/// <remarks>
/// Each frame keeps track of its inclusive and exclusive times
/// </remarks>
private sealed class EvaluationFrame : IDisposable
{
private readonly EvaluationProfiler _evaluationProfiler;
private readonly Stopwatch _inclusiveTime = new Stopwatch();
private readonly Stopwatch _exclusiveTime = new Stopwatch();
/// <nodoc/>
public EvaluationLocation Location { get; }
/// <summary>
/// Constructs a new evaluation frame and pushes it to the tracker stack
/// </summary>
public EvaluationFrame(EvaluationProfiler evaluationProfiler, EvaluationLocation location)
{
_evaluationProfiler = evaluationProfiler;
Location = location;
_inclusiveTime.Start();
_exclusiveTime.Start();
if (_evaluationProfiler._evaluationStack.Count > 0)
{
_evaluationProfiler._evaluationStack.Peek()._exclusiveTime.Stop();
}
_evaluationProfiler._evaluationStack.Push(this);
}
/// <summary>
/// Pops this from the tracker stack and computes inclusive and exclusive times
/// </summary>
public void Dispose()
{
_inclusiveTime.Stop();
_exclusiveTime.Stop();
if (_evaluationProfiler._evaluationStack.Pop() != this)
{
throw new InvalidOperationException("Evaluation frame disposed out of order");
}
if (_evaluationProfiler._evaluationStack.Count > 0)
{
_evaluationProfiler._evaluationStack.Peek()._exclusiveTime.Start();
}
// Add elapsed times to evaluation counter dictionaries
if (!_evaluationProfiler.ProfiledResult.Value.ProfiledLocations.TryGetValue(Location, out var previousTimeSpent))
{
previousTimeSpent = new ProfiledLocation(TimeSpan.Zero, TimeSpan.Zero, 0);
}
var updatedTimeSpent = new ProfiledLocation(
previousTimeSpent.InclusiveTime + _inclusiveTime.Elapsed,
previousTimeSpent.ExclusiveTime + _exclusiveTime.Elapsed,
0);
_evaluationProfiler._timeSpent[Location] = updatedTimeSpent;
}
}
}
}
|