Skip to content

Commit

Permalink
Merge pull request #24564 from sharwell/fix-timing
Browse files Browse the repository at this point in the history
Fix problems associated with analyzer timing
  • Loading branch information
sharwell committed Feb 2, 2018
2 parents 8b52fff + aedcef6 commit ec3ab3d
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 18 deletions.
40 changes: 28 additions & 12 deletions src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

using AnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.AnalyzerStateData;
using Microsoft.CodeAnalysis.PooledObjects;
using SyntaxNodeAnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.SyntaxNodeAnalyzerStateData;
using OperationAnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.OperationAnalyzerStateData;
using DeclarationAnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.DeclarationAnalyzerStateData;
using OperationAnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.OperationAnalyzerStateData;
using SyntaxNodeAnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.SyntaxNodeAnalyzerStateData;

namespace Microsoft.CodeAnalysis.Diagnostics
{
Expand Down Expand Up @@ -43,7 +43,10 @@ internal class AnalyzerExecutor
private readonly Func<DiagnosticAnalyzer, bool> _shouldSkipAnalysisOnGeneratedCode;
private readonly Func<Diagnostic, DiagnosticAnalyzer, Compilation, CancellationToken, bool> _shouldSuppressGeneratedCodeDiagnostic;
private readonly Func<SyntaxTree, TextSpan, bool> _isGeneratedCodeLocation;
private readonly ConcurrentDictionary<DiagnosticAnalyzer, TimeSpan> _analyzerExecutionTimeMapOpt;
/// <summary>
/// The values in this map convert to <see cref="TimeSpan"/> using <see cref="TimeSpan.FromTicks(long)"/>.
/// </summary>
private readonly ConcurrentDictionary<DiagnosticAnalyzer, StrongBox<long>> _analyzerExecutionTimeMapOpt;
private readonly CompilationAnalysisValueProviderFactory _compilationAnalysisValueProviderFactory;
private readonly CancellationToken _cancellationToken;

Expand Down Expand Up @@ -97,7 +100,7 @@ public static AnalyzerExecutor Create(
Debug.Assert((addNonCategorizedDiagnosticOpt != null) ^ (addCategorizedLocalDiagnosticOpt != null));
Debug.Assert((addCategorizedLocalDiagnosticOpt != null) == (addCategorizedNonLocalDiagnosticOpt != null));

var analyzerExecutionTimeMapOpt = logExecutionTime ? new ConcurrentDictionary<DiagnosticAnalyzer, TimeSpan>() : null;
var analyzerExecutionTimeMapOpt = logExecutionTime ? new ConcurrentDictionary<DiagnosticAnalyzer, StrongBox<long>>() : null;

return new AnalyzerExecutor(compilation, analyzerOptions, addNonCategorizedDiagnosticOpt, onAnalyzerException, analyzerExceptionFilter,
isCompilerAnalyzer, analyzerManager, shouldSkipAnalysisOnGeneratedCode, shouldSuppressGeneratedCodeDiagnostic, isGeneratedCodeLocation,
Expand Down Expand Up @@ -148,7 +151,7 @@ private AnalyzerExecutor(
Func<Diagnostic, DiagnosticAnalyzer, Compilation, CancellationToken, bool> shouldSuppressGeneratedCodeDiagnostic,
Func<SyntaxTree, TextSpan, bool> isGeneratedCodeLocation,
Func<DiagnosticAnalyzer, object> getAnalyzerGateOpt,
ConcurrentDictionary<DiagnosticAnalyzer, TimeSpan> analyzerExecutionTimeMapOpt,
ConcurrentDictionary<DiagnosticAnalyzer, StrongBox<long>> analyzerExecutionTimeMapOpt,
Action<Diagnostic, DiagnosticAnalyzer, bool> addCategorizedLocalDiagnosticOpt,
Action<Diagnostic, DiagnosticAnalyzer> addCategorizedNonLocalDiagnosticOpt,
CancellationToken cancellationToken)
Expand Down Expand Up @@ -188,7 +191,7 @@ public AnalyzerExecutor WithCancellationToken(CancellationToken cancellationToke
internal AnalyzerOptions AnalyzerOptions => _analyzerOptions;
internal CancellationToken CancellationToken => _cancellationToken;
internal Action<Exception, DiagnosticAnalyzer, Diagnostic> OnAnalyzerException => _onAnalyzerException;
internal ImmutableDictionary<DiagnosticAnalyzer, TimeSpan> AnalyzerExecutionTimes => _analyzerExecutionTimeMapOpt.ToImmutableDictionary();
internal ImmutableDictionary<DiagnosticAnalyzer, TimeSpan> AnalyzerExecutionTimes => _analyzerExecutionTimeMapOpt.ToImmutableDictionary(pair => pair.Key, pair => TimeSpan.FromTicks(pair.Value.Value));

/// <summary>
/// Executes the <see cref="DiagnosticAnalyzer.Initialize(AnalysisContext)"/> for the given analyzer.
Expand Down Expand Up @@ -1201,14 +1204,27 @@ private void ExecuteAndCatchIfThrows_NoLock<TArg>(DiagnosticAnalyzer analyzer, A
if (_analyzerExecutionTimeMapOpt != null)
{
timer = PooledStopwatch.StartInstance();

// This call to Restart isn't required by the API, but is included to avoid measurement errors which
// can occur during periods of high allocation activity. In some cases, calls to Stopwatch
// operations can block at their return point on the completion of a background GC operation. When
// this occurs, the GC wait time ends up included in the measured time span. In the event the first
// call to Restart blocked on a GC operation, this call to Restart will most likely occur when the
// GC is no longer active. In practice, a substantial improvement to the consistency of analyzer
// timing data was observed.
//
// Note that the call to Stopwatch.Stop() is not affected, because the GC wait will occur after the
// timer has already recorded its stop time.
timer.Restart();
}

analyze(argument);

if (timer != null)
{
timer.Stop();
_analyzerExecutionTimeMapOpt.AddOrUpdate(analyzer, timer.Elapsed, timer.UpdateValueFactory);
StrongBox<long> totalTicks = _analyzerExecutionTimeMapOpt.GetOrAdd(analyzer, _ => new StrongBox<long>(0));
Interlocked.Add(ref totalTicks.Value, timer.Elapsed.Ticks);
timer.Free();
}
}
Expand Down Expand Up @@ -1604,13 +1620,13 @@ private static bool IsDeclarationComplete(ISymbol symbol, int declarationIndex,
internal TimeSpan ResetAnalyzerExecutionTime(DiagnosticAnalyzer analyzer)
{
Debug.Assert(_analyzerExecutionTimeMapOpt != null);
TimeSpan executionTime;
StrongBox<long> executionTime;
if (!_analyzerExecutionTimeMapOpt.TryRemove(analyzer, out executionTime))
{
executionTime = default(TimeSpan);
return TimeSpan.Zero;
}

return executionTime;
return TimeSpan.FromTicks(executionTime.Value);
}
}
}
6 changes: 0 additions & 6 deletions src/Dependencies/PooledObjects/PooledStopwatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ internal class PooledStopwatch : Stopwatch
private PooledStopwatch(ObjectPool<PooledStopwatch> pool)
{
_pool = pool;
UpdateValueFactory = (_, accumulated) => accumulated + Elapsed;
}

public Func<object, TimeSpan, TimeSpan> UpdateValueFactory
{
get;
}

public void Free()
Expand Down

0 comments on commit ec3ab3d

Please sign in to comment.