+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
- <PropertyGroup>
- <DisableProjectBuild>true</DisableProjectBuild>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">x64</Platform>
- <ProjectGuid>{07501894-7A01-4A0D-B9A2-3C72799A68BA}</ProjectGuid>
- <OutputType>Library</OutputType>
- <RootNamespace>GCPerfTestFramework</RootNamespace>
- <AssemblyName>GCPerfTestFramework</AssemblyName>
- <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
- <TargetFrameworkProfile />
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>full</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug\</OutputPath>
- <DefineConstants>TRACE;DEBUG;WINDOWS</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
- <DebugType>pdbonly</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\Release\</OutputPath>
- <DefineConstants>TRACE;WINDOWS</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <PlatformTarget>x64</PlatformTarget>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="Microsoft.Diagnostics.Tracing.TraceEvent, Version=1.0.41.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
- <HintPath>packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="System" />
- <Reference Include="System.ComponentModel.Composition" />
- <Reference Include="System.Core" />
- <Reference Include="System.IO.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
- <HintPath>packages\System.IO.FileSystem.4.0.0\lib\net46\System.IO.FileSystem.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="System.IO.FileSystem.Primitives, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
- <HintPath>packages\System.IO.FileSystem.Primitives.4.0.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="System.Xml.Linq" />
- <Reference Include="System.Data.DataSetExtensions" />
- <Reference Include="Microsoft.CSharp" />
- <Reference Include="System.Data" />
- <Reference Include="System.Net.Http" />
- <Reference Include="System.Xml" />
- <Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
- <HintPath>packages\Microsoft.DotNet.xunit.performance.metrics.1.0.0-alpha-build0040\lib\net46\xunit.abstractions.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="xunit.core, Version=2.2.0.3300, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
- <HintPath>packages\xunit.extensibility.core.2.2.0-beta2-build3300\lib\netstandard1.0\xunit.core.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="xunit.execution.desktop, Version=2.2.0.3300, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
- <HintPath>packages\xunit.extensibility.execution.2.2.0-beta2-build3300\lib\net45\xunit.execution.desktop.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="xunit.performance.core, Version=1.0.0.40, Culture=neutral, PublicKeyToken=67066efe964d3b03, processorArchitecture=MSIL">
- <HintPath>packages\Microsoft.DotNet.xunit.performance.metrics.1.0.0-alpha-build0040\lib\net46\xunit.performance.core.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="xunit.performance.execution.desktop, Version=1.0.0.40, Culture=neutral, PublicKeyToken=67066efe964d3b03, processorArchitecture=MSIL">
- <HintPath>packages\Microsoft.DotNet.xunit.performance.1.0.0-alpha-build0040\lib\net46\xunit.performance.execution.desktop.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="xunit.performance.metrics, Version=1.0.0.40, Culture=neutral, processorArchitecture=MSIL">
- <HintPath>packages\Microsoft.DotNet.xunit.performance.metrics.1.0.0-alpha-build0040\lib\net46\xunit.performance.metrics.dll</HintPath>
- <Private>True</Private>
- </Reference>
- </ItemGroup>
- <ItemGroup>
- <Compile Include="PerfTests.cs" />
- <Compile Include="ProcessFactory.cs" />
- <Compile Include="Metrics\CollectGCMetricsAttribute.cs" />
- <Compile Include="Metrics\GCMetricDiscoverer.cs" />
- <Compile Include="Metrics\GCMetrics.cs" />
- <Compile Include="Metrics\Builders\CircularBuffer.cs" />
- <Compile Include="Metrics\Builders\CondemnedReasonGroup.cs" />
- <Compile Include="Metrics\Builders\DictionaryExtensions.cs" />
- <Compile Include="Metrics\Builders\GCEvent.cs" />
- <Compile Include="Metrics\Builders\GCInfo.cs" />
- <Compile Include="Metrics\Builders\GCProcess.cs" />
- <Compile Include="Metrics\Builders\ThreadWorkSpan.cs" />
- <None Include="..\..\..\NuGet.config" />
- </ItemGroup>
- <ItemGroup>
- <Folder Include="Properties\" />
- </ItemGroup>
- <ItemGroup>
- <None Include="packages.config" />
- </ItemGroup>
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <Import Project="packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets" Condition="Exists('packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" />
- <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
- <PropertyGroup>
- <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
- </PropertyGroup>
- <Error Condition="!Exists('packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets'))" />
- </Target>
- <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
- Other similar extension points exist, see Microsoft.Common.targets.
- <Target Name="BeforeBuild">
- </Target>
- <Target Name="AfterBuild">
- </Target>
- -->
-</Project>
\ No newline at end of file
+++ /dev/null
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.24912.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCPerfTestFramework", "GCPerfTestFramework.csproj", "{07501894-7A01-4A0D-B9A2-3C72799A68BA}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {07501894-7A01-4A0D-B9A2-3C72799A68BA}.Debug|Any CPU.ActiveCfg = Debug|x64
- {07501894-7A01-4A0D-B9A2-3C72799A68BA}.Debug|Any CPU.Build.0 = Debug|x64
- {07501894-7A01-4A0D-B9A2-3C72799A68BA}.Release|Any CPU.ActiveCfg = Release|x64
- {07501894-7A01-4A0D-B9A2-3C72799A68BA}.Release|Any CPU.Build.0 = Release|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-
-namespace GCPerfTestFramework.Metrics.Builders
-{
- internal class CircularBuffer<T> : IEnumerable<T>
- where T : class
- {
- private int StartIndex, AfterEndIndex, Size;
- private T[] Items;
- public CircularBuffer(int size)
- {
- if (size < 1)
- throw new ArgumentException("size");
-
- StartIndex = 0;
- AfterEndIndex = 0;
- Size = size + 1;
- Items = new T[Size];
- }
-
- public void Add(T item)
- {
- if (Next(AfterEndIndex) == StartIndex)
- {
- Items[StartIndex] = null;
- StartIndex = Next(StartIndex);
- }
- Items[AfterEndIndex] = item;
- AfterEndIndex = Next(AfterEndIndex);
- }
-
- private int Next(int i)
- {
- return (i == Size - 1) ? 0 : i + 1;
- }
-
- public IEnumerator<T> GetEnumerator()
- {
- for (int i = StartIndex; i != AfterEndIndex; i = Next(i))
- {
- yield return Items[i];
- }
- }
-
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace GCPerfTestFramework.Metrics.Builders
-{
- // Condemned reasons are organized into the following groups.
- // Each group corresponds to one or more reasons.
- // Groups are organized in the way that they mean something to users.
- internal enum CondemnedReasonGroup
- {
- // The first 4 will have values of a number which is the generation.
- // Note that right now these 4 have the exact same value as what's in
- // Condemned_Reason_Generation.
- CRG_Initial_Generation = 0,
- CRG_Final_Generation = 1,
- CRG_Alloc_Exceeded = 2,
- CRG_Time_Tuning = 3,
-
- // The following are either true(1) or false(0). They are not
- // a 1:1 mapping from
- CRG_Induced = 4,
- CRG_Low_Ephemeral = 5,
- CRG_Expand_Heap = 6,
- CRG_Fragmented_Ephemeral = 7,
- CRG_Fragmented_Gen1_To_Gen2 = 8,
- CRG_Fragmented_Gen2 = 9,
- CRG_Fragmented_Gen2_High_Mem = 10,
- CRG_GC_Before_OOM = 11,
- CRG_Too_Small_For_BGC = 12,
- CRG_Ephemeral_Before_BGC = 13,
- CRG_Internal_Tuning = 14,
- CRG_Max = 15,
- }
-
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections.Generic;
-
-namespace GCPerfTestFramework.Metrics.Builders
-{
- internal static class DictionaryExtensions
- {
- public static V GetOrCreate<K, V>(this IDictionary<K, V> dict, K key) where V : new()
- {
- V value;
- if (dict.TryGetValue(key, out value))
- {
- return value;
- }
-
- value = new V();
- dict[key] = value;
- return value;
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#if WINDOWS
-
-using Microsoft.Diagnostics.Tracing.Parsers.Clr;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-
-namespace GCPerfTestFramework.Metrics.Builders
-{
- internal class GCEvent
- {
- #region Public Fields
- // Time it takes to do the suspension. Before 4.0 we didn't have a SuspendEnd event so we calculate it by just
- // substracting GC duration from pause duration. For concurrent GC this would be inaccurate so we just return 0.
- public double _SuspendDurationMSec;
-
- public double[] AllocedSinceLastGCBasedOnAllocTickMB = { 0.0, 0.0 };
- public long duplicatedPinningReports;
- public double DurationSinceLastRestartMSec;
- // The amount of CPU time this GC consumed.
- public float GCCpuMSec;
-
- public float[] GCCpuServerGCThreads = null;
- public double GCDurationMSec;
- public int GCGeneration;
- // Primary fields (set in the callbacks)
- public int GCNumber;
-
- public double GCStartRelativeMSec;
- public GCGlobalHeapHistoryTraceData GlobalHeapHistory;
- public bool HasAllocTickEvents = false;
- public GCHeapStatsTraceData HeapStats;
- public int Index;
- // In 2.0 we didn't have all the events. I try to keep the code not version specific and this is really
- // for debugging/verification purpose.
- public bool is20Event;
-
- // Did we get the complete event sequence for this GC?
- // For BGC this is the HeapStats event; for other GCs this means you have both the HeapStats and RestartEEEnd event.
- public bool isComplete;
-
- // Set in Start, does not include suspension.
- // Set in Stop This is JUST the GC time (not including suspension) That is Stop-Start.
- // This only applies to 2.0. I could just set the type to Background GC but I'd rather not disturb
- // the code that handles background GC.
- public bool isConcurrentGC;
-
- public GCProcess Parent; //process that did that GC
- public double PauseDurationMSec;
-
- public double PauseStartRelativeMSec;
-
- public List<GCPerHeapHistoryTraceData> PerHeapHistories;
-
- // The dictionary of heap number and info on time it takes to mark various roots.
- public Dictionary<int, MarkInfo> PerHeapMarkTimes;
-
- public Dictionary<ulong, long> PinnedObjects = new Dictionary<ulong, long>();
-
- public List<PinnedPlug> PinnedPlugs = new List<PinnedPlug>();
-
- // For background GC we need to remember when the GC before it ended because
- // when we get the GCStop event some foreground GCs may have happened.
- public float ProcessCpuAtLastGC;
-
- // Total time EE is suspended (can be less than GC time for background)
- // The amount of CPU time the process consumed since the last GC.
- public float ProcessCpuMSec;
-
- public GCReason Reason;
-
- public long totalPinnedPlugSize;
-
- public long totalUserPinnedPlugSize;
-
- // Set in GCStop(Generation 0, 1 or 2)
- public GCType Type;
-
- private GCCondemnedReasons[] _PerHeapCondemnedReasons;
-
- private GCPerHeapHistoryGenData[][] _PerHeapGenData;
-
- #endregion
-
- #region Private Fields
- // Set in GCStart
- private double _TotalGCTimeMSec = -1;
-
- // Set in GCStart
- // When we are using Server GC we store the CPU spent on each thread
- // so we can see if there's an imbalance. We concurrently don't do this
- // for server background GC as the imbalance there is much less important.
- int heapCount = -1;
-
- private long pinnedObjectSizes;
-
- // Set in GCStart
- // Set in GCStart
- //list of workload histories per server GC heap
- private List<ServerGcHistory> ServerGcHeapHistories = new List<ServerGcHistory>();
-
- private PerHeapEventVersion Version = PerHeapEventVersion.V0;
- #endregion
-
- #region Constructors
- public GCEvent(GCProcess owningProcess)
- {
- Parent = owningProcess;
- heapCount = owningProcess.heapCount;
-
- if (heapCount > 1)
- {
- GCCpuServerGCThreads = new float[heapCount];
- }
-
- pinnedObjectSizes = -1;
- totalPinnedPlugSize = -1;
- totalUserPinnedPlugSize = -1;
- duplicatedPinningReports = 0;
- }
-
- #endregion
-
- #region Private Enums
- private enum InducedType
- {
- Blocking = 1,
- NotForced = 2,
- }
-
- // TODO: get rid of the remaining version checking here - convert the leftover checks with using the Has* methods
- // to determine whether that particular data is available.
- private enum PerHeapEventVersion
- {
- V0, // Not set
- V4_0,
- V4_5,
- V4_6,
- }
-
- #endregion
-
- #region Public Properties
- public double AllocedSinceLastGCMB
- {
- get
- {
- return GetUserAllocated(Gens.Gen0) + GetUserAllocated(Gens.GenLargeObj);
- }
- }
-
- public double AllocRateMBSec { get { return AllocedSinceLastGCMB * 1000.0 / DurationSinceLastRestartMSec; } }
-
- public double CondemnedMB
- {
- get
- {
- double ret = GenSizeBeforeMB(0);
- if (1 <= GCGeneration)
- ret += GenSizeBeforeMB(Gens.Gen1);
- if (2 <= GCGeneration)
- ret += GenSizeBeforeMB(Gens.Gen2) + GenSizeBeforeMB(Gens.GenLargeObj);
- return ret;
- }
- }
-
- // Index into the list of GC events
- // The list that contains this event
- private List<GCEvent> Events
- {
- get
- {
- return Parent.Events.OfType<GCEvent>().ToList();
- }
- }
-
- public double FragmentationMB
- {
- get
- {
- double ret = 0;
- for (Gens gen = Gens.Gen0; gen <= Gens.GenLargeObj; gen++)
- ret += GenFragmentationMB(gen);
- return ret;
- }
- }
-
- public int Generation => GCNumber;
-
- public bool HasServerGcThreadingInfo
- {
- get
- {
- foreach (var heap in ServerGcHeapHistories)
- {
- if (heap.SampleSpans.Count > 0 || heap.SwitchSpans.Count > 0)
- return true;
- }
- return false;
- }
- }
-
- /// <summary>
- /// This include fragmentation
- /// </summary>
- public double HeapSizeAfterMB
- {
- get
- {
- if (null != HeapStats)
- {
- return (HeapStats.GenerationSize0 + HeapStats.GenerationSize1 + HeapStats.GenerationSize2 + HeapStats.GenerationSize3) / 1000000.0;
- }
- else
- {
- return -1.0;
- }
- }
- }
-
- public double HeapSizeBeforeMB
- {
- get
- {
- double ret = 0;
- for (Gens gen = Gens.Gen0; gen <= Gens.GenLargeObj; gen++)
- ret += GenSizeBeforeMB(gen);
- return ret;
- }
- }
-
- public double HeapSizePeakMB
- {
- get
- {
- var ret = HeapSizeBeforeMB;
- if (Type == GCType.BackgroundGC)
- {
- var BgGcEndedRelativeMSec = PauseStartRelativeMSec + GCDurationMSec;
- for (int i = Index + 1; i < Events.Count; i++)
- {
- var _event = Events[i];
- if (BgGcEndedRelativeMSec < _event.PauseStartRelativeMSec)
- break;
- ret = Math.Max(ret, _event.HeapSizeBeforeMB);
- }
- }
- return ret;
- }
- }
-
- // Set in GCStart (starts at 1, unique for process)
- // Of all the CPU, how much as a percentage is spent in the GC since end of last GC.
- public double PercentTimeInGC { get { return (GetTotalGCTime() * 100 / ProcessCpuMSec); } }
- public double PromotedMB
- {
- get
- {
- return (HeapStats.TotalPromotedSize0 + HeapStats.TotalPromotedSize1 +
- HeapStats.TotalPromotedSize2 + HeapStats.TotalPromotedSize3) / 1000000.0;
- }
- }
-
- public double RatioPeakAfter { get { if (HeapSizeAfterMB == 0) return 0; return HeapSizePeakMB / HeapSizeAfterMB; } }
-
- private GCCondemnedReasons[] PerHeapCondemnedReasons
- {
- get
- {
- if ((PerHeapHistories != null) && (_PerHeapCondemnedReasons == null))
- {
- GetVersion();
-
- int NumHeaps = PerHeapHistories.Count;
- _PerHeapCondemnedReasons = new GCCondemnedReasons[NumHeaps];
-
- for (int HeapIndex = 0; HeapIndex < NumHeaps; HeapIndex++)
- {
- _PerHeapCondemnedReasons[HeapIndex] = new GCCondemnedReasons();
- _PerHeapCondemnedReasons[HeapIndex].EncodedReasons.Reasons = PerHeapHistories[HeapIndex].CondemnReasons0;
- if (Version != PerHeapEventVersion.V4_0)
- {
- _PerHeapCondemnedReasons[HeapIndex].EncodedReasons.ReasonsEx = PerHeapHistories[HeapIndex].CondemnReasons1;
- }
- _PerHeapCondemnedReasons[HeapIndex].CondemnedReasonGroups = new byte[(int)CondemnedReasonGroup.CRG_Max];
- _PerHeapCondemnedReasons[HeapIndex].Decode(Version);
- }
- }
-
- return _PerHeapCondemnedReasons;
- }
- }
-
- // There's a list of things we need to get from the events we collected.
- // To increase the efficiency so we don't need to go back to TraceEvent
- // too often we construct the generation data all at once here.
- private GCPerHeapHistoryGenData[][] PerHeapGenData
- {
- get
- {
- if ((PerHeapHistories != null) && (_PerHeapGenData == null))
- {
- GetVersion();
-
- int NumHeaps = PerHeapHistories.Count;
- _PerHeapGenData = new GCPerHeapHistoryGenData[NumHeaps][];
- for (int HeapIndex = 0; HeapIndex < NumHeaps; HeapIndex++)
- {
- _PerHeapGenData[HeapIndex] = new GCPerHeapHistoryGenData[(int)Gens.GenLargeObj + 1];
- for (Gens GenIndex = Gens.Gen0; GenIndex <= Gens.GenLargeObj; GenIndex++)
- {
- _PerHeapGenData[HeapIndex][(int)GenIndex] = PerHeapHistories[HeapIndex].GenData(GenIndex);
- }
- }
- }
-
- return _PerHeapGenData;
- }
- }
-
- #endregion
-
- #region Public Methods
- public void AddLOHWaitThreadInfo(int TID, double time, int reason, bool IsStart)
- {
-#if HAS_PRIVATE_GC_EVENTS
- BGCAllocWaitReason ReasonLOHAlloc = (BGCAllocWaitReason)reason;
-
- if ((ReasonLOHAlloc == BGCAllocWaitReason.GetLOHSeg) ||
- (ReasonLOHAlloc == BGCAllocWaitReason.AllocDuringSweep))
- {
- if (LOHWaitThreads == null)
- {
- LOHWaitThreads = new Dictionary<int, BGCAllocWaitInfo>();
- }
-
- BGCAllocWaitInfo info;
-
- if (LOHWaitThreads.TryGetValue(TID, out info))
- {
- if (IsStart)
- {
- // If we are finding the value it means we are hitting the small
- // window where BGC sweep finished and BGC itself finished, discard
- // this.
- }
- else
- {
- Debug.Assert(info.Reason == ReasonLOHAlloc);
- info.WaitStopRelativeMSec = time;
- }
- }
- else
- {
- info = new BGCAllocWaitInfo();
- if (IsStart)
- {
- info.Reason = ReasonLOHAlloc;
- info.WaitStartRelativeMSec = time;
- }
- else
- {
- // We are currently not displaying this because it's incomplete but I am still adding
- // it so we could display if we want to.
- info.WaitStopRelativeMSec = time;
- }
-
- LOHWaitThreads.Add(TID, info);
- }
- }
-#endif
- }
-
- public void AddServerGcSample(ThreadWorkSpan sample)
- {
- if (sample.ProcessorNumber >= 0 && sample.ProcessorNumber < ServerGcHeapHistories.Count)
- ServerGcHeapHistories[sample.ProcessorNumber].AddSampleEvent(sample);
- }
-
- public void AddServerGcThreadSwitch(ThreadWorkSpan cswitch)
- {
- if (cswitch.ProcessorNumber >= 0 && cswitch.ProcessorNumber < ServerGcHeapHistories.Count)
- ServerGcHeapHistories[cswitch.ProcessorNumber].AddSwitchEvent(cswitch);
- }
-
- public void AddServerGCThreadTime(int heapIndex, float cpuMSec)
- {
- if (GCCpuServerGCThreads != null)
- GCCpuServerGCThreads[heapIndex] += cpuMSec;
- }
-
- // Unfortunately sometimes we just don't get mark events from all heaps, even for GCs that we have seen GCStart for.
- // So accommodating this scenario.
- public bool AllHeapsSeenMark()
- {
- if (PerHeapMarkTimes != null)
- return (heapCount == PerHeapMarkTimes.Count);
- else
- return false;
- }
-
- public bool DetailedGenDataAvailable()
- {
- return (PerHeapHistories != null);
- }
-
- public void GCEnd()
- {
- ConvertMarkTimes();
-
- if (ServerGcHeapHistories != null)
- {
- foreach (var serverHeap in ServerGcHeapHistories)
- {
- serverHeap.GCEnd();
- }
- }
- }
-
- public double GenBudgetMB(Gens gen)
- {
- if (PerHeapHistories == null)
- return double.NaN;
- double budget = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- budget += PerHeapGenData[HeapIndex][(int)gen].Budget / 1000000.0;
- return budget;
- }
-
- public double GenFragmentationMB(Gens gen)
- {
- if (PerHeapHistories == null)
- return double.NaN;
- double ret = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- ret += PerHeapGenData[HeapIndex][(int)gen].Fragmentation / 1000000.0;
- return ret;
- }
-
- public double GenFragmentationPercent(Gens gen)
- {
- return (GenFragmentationMB(gen) * 100.0 / GenSizeAfterMB(gen));
- }
-
- public double GenFreeListAfter(Gens gen)
- {
- if (PerHeapHistories == null)
- return double.NaN;
- double ret = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- ret += PerHeapGenData[HeapIndex][(int)gen].FreeListSpaceAfter;
- return ret;
- }
-
- public double GenFreeListBefore(Gens gen)
- {
- if (PerHeapHistories == null)
- return double.NaN;
- double ret = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- ret += PerHeapGenData[HeapIndex][(int)gen].FreeListSpaceBefore;
- return ret;
- }
-
- public double GenInMB(Gens gen)
- {
- if (PerHeapHistories == null)
- return double.NaN;
- double ret = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- ret += PerHeapGenData[HeapIndex][(int)gen].In / 1000000.0;
- return ret;
- }
-
- public double GenNonePinnedSurv(Gens gen)
- {
- if ((PerHeapHistories == null) || !(PerHeapGenData[0][0].HasNonePinnedSurv()))
- return double.NaN;
- double ret = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- ret += PerHeapGenData[HeapIndex][(int)gen].NonePinnedSurv;
- return ret;
- }
-
- public double GenOut(Gens gen)
- {
- if (PerHeapHistories == null)
- return double.NaN;
- double ret = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- ret += PerHeapGenData[HeapIndex][(int)gen].Out;
- return ret;
- }
-
- public double GenOutMB(Gens gen)
- {
- if (PerHeapHistories == null)
- return double.NaN;
- double ret = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- ret += PerHeapGenData[HeapIndex][(int)gen].Out / 1000000.0;
- return ret;
- }
-
- public double GenPinnedSurv(Gens gen)
- {
- if ((PerHeapHistories == null) || !(PerHeapGenData[0][0].HasPinnedSurv()))
- return double.NaN;
- double ret = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- ret += PerHeapGenData[HeapIndex][(int)gen].PinnedSurv;
- return ret;
- }
-
- // Note that in 4.0 TotalPromotedSize is not entirely accurate (since it doesn't
- // count the pins that got demoted. We could consider using the PerHeap event data
- // to compute the accurate promoted size.
- // In 4.5 this is accurate.
- public double GenPromotedMB(Gens gen)
- {
- if (gen == Gens.GenLargeObj)
- return HeapStats.TotalPromotedSize3 / 1000000.0;
- if (gen == Gens.Gen2)
- return HeapStats.TotalPromotedSize2 / 1000000.0;
- if (gen == Gens.Gen1)
- return HeapStats.TotalPromotedSize1 / 1000000.0;
- if (gen == Gens.Gen0)
- return HeapStats.TotalPromotedSize0 / 1000000.0;
- Debug.Assert(false);
- return double.NaN;
- }
-
- public double GenSizeAfterMB(Gens gen)
- {
- if (gen == Gens.GenLargeObj)
- return HeapStats.GenerationSize3 / 1000000.0;
- if (gen == Gens.Gen2)
- return HeapStats.GenerationSize2 / 1000000.0;
- if (gen == Gens.Gen1)
- return HeapStats.GenerationSize1 / 1000000.0;
- if (gen == Gens.Gen0)
- return HeapStats.GenerationSize0 / 1000000.0;
- Debug.Assert(false);
- return double.NaN;
- }
-
- // Per generation stats.
- public double GenSizeBeforeMB(Gens gen)
- {
- if (PerHeapHistories != null)
- {
- double ret = 0.0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- ret += PerHeapGenData[HeapIndex][(int)gen].SizeBefore / 1000000.0;
- return ret;
- }
-
- // When we don't have perheap history we can only estimate for gen0 and gen3.
- double Gen0SizeBeforeMB = 0;
- if (gen == Gens.Gen0)
- Gen0SizeBeforeMB = AllocedSinceLastGCBasedOnAllocTickMB[0];
- if (Index == 0)
- {
- return ((gen == Gens.Gen0) ? Gen0SizeBeforeMB : 0);
- }
-
- // Find a previous HeapStats.
- GCHeapStatsTraceData heapStats = null;
- for (int j = Index - 1; ; --j)
- {
- if (j == 0)
- return 0;
- heapStats = Events[j].HeapStats;
- if (heapStats != null)
- break;
- }
- if (gen == Gens.Gen0)
- return Math.Max((heapStats.GenerationSize0 / 1000000.0), Gen0SizeBeforeMB);
- if (gen == Gens.Gen1)
- return heapStats.GenerationSize1 / 1000000.0;
- if (gen == Gens.Gen2)
- return heapStats.GenerationSize2 / 1000000.0;
-
- Debug.Assert(gen == Gens.GenLargeObj);
-
- if (HeapStats != null)
- return Math.Max(heapStats.GenerationSize3, HeapStats.GenerationSize3) / 1000000.0;
- else
- return heapStats.GenerationSize3 / 1000000.0;
- }
-
- public void GetCondemnedReasons(Dictionary<CondemnedReasonGroup, int> ReasonsInfo)
- {
- // Older versions of the runtime does not have this event. So even for a complete GC, we may not have this
- // info.
- if (PerHeapCondemnedReasons == null)
- return;
-
- int HeapIndexHighestGen = 0;
- if (PerHeapCondemnedReasons.Length != 1)
- {
- HeapIndexHighestGen = FindFirstHighestCondemnedHeap();
- }
-
- byte[] ReasonGroups = PerHeapCondemnedReasons[HeapIndexHighestGen].CondemnedReasonGroups;
-
- // These 2 reasons indicate a gen number. If the number is the same as the condemned gen, we
- // include this reason.
- for (int i = (int)CondemnedReasonGroup.CRG_Alloc_Exceeded; i <= (int)CondemnedReasonGroup.CRG_Time_Tuning; i++)
- {
- if (ReasonGroups[i] == GCGeneration)
- AddCondemnedReason(ReasonsInfo, (CondemnedReasonGroup)i);
- }
-
- if (ReasonGroups[(int)CondemnedReasonGroup.CRG_Induced] != 0)
- {
- if (ReasonGroups[(int)CondemnedReasonGroup.CRG_Initial_Generation] == GCGeneration)
- {
- AddCondemnedReason(ReasonsInfo, CondemnedReasonGroup.CRG_Induced);
- }
- }
-
- // The rest of the reasons are conditions so include the ones that are set.
- for (int i = (int)CondemnedReasonGroup.CRG_Low_Ephemeral; i < (int)CondemnedReasonGroup.CRG_Max; i++)
- {
- if (ReasonGroups[i] != 0)
- AddCondemnedReason(ReasonsInfo, (CondemnedReasonGroup)i);
- }
- }
-
- //
- // Approximations we do in this function for V4_5 and prior:
- // On 4.0 we didn't seperate free list from free obj, so we just use fragmentation (which is the sum)
- // as an approximation. This makes the efficiency value a bit larger than it actually is.
- // We don't actually update in for the older gen - this means we only know the out for the younger
- // gen which isn't necessarily all allocated into the older gen. So we could see cases where the
- // out is > 0, yet the older gen's free list doesn't change. Using the younger gen's out as an
- // approximation makes the efficiency value larger than it actually is.
- //
- // For V4_6 this requires no approximation.
- //
- public bool GetFreeListEfficiency(Gens gen, ref double Allocated, ref double FreeListConsumed)
- {
- // I am not worried about gen0 or LOH's free list efficiency right now - it's
- // calculated differently.
- if ((PerHeapHistories == null) ||
- (gen == Gens.Gen0) ||
- (gen == Gens.GenLargeObj) ||
- (Index <= 0) ||
- !(PerHeapHistories[0].VersionRecognized))
- {
- return false;
- }
-
- int YoungerGen = (int)gen - 1;
-
- if (GCGeneration != YoungerGen)
- return false;
-
- if (PerHeapHistories[0].V4_6)
- {
- Allocated = 0;
- FreeListConsumed = 0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- {
- GCPerHeapHistoryTraceData3 hist = (GCPerHeapHistoryTraceData3)PerHeapHistories[HeapIndex];
- Allocated += hist.FreeListAllocated;
- FreeListConsumed += hist.FreeListAllocated + hist.FreeListRejected;
- }
- return true;
- }
-
- // I am not using MB here because what's promoted from gen1 can easily be less than a MB.
- double YoungerGenOut = 0;
- double FreeListBefore = 0;
- double FreeListAfter = 0;
- // Includes fragmentation. This lets us know if we had to expand the size.
- double GenSizeBefore = 0;
- double GenSizeAfter = 0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- {
- YoungerGenOut += PerHeapGenData[HeapIndex][YoungerGen].Out;
- GenSizeBefore += PerHeapGenData[HeapIndex][(int)gen].SizeBefore;
- GenSizeAfter += PerHeapGenData[HeapIndex][(int)gen].SizeAfter;
- if (Version == PerHeapEventVersion.V4_0)
- {
- // Occasionally I've seen a GC in the middle that simply missed some events,
- // some of which are PerHeap hist events so we don't have data.
- if (Events[Index - 1].PerHeapGenData == null)
- return false;
- FreeListBefore += Events[Index - 1].PerHeapGenData[HeapIndex][(int)gen].Fragmentation;
- FreeListAfter += PerHeapGenData[HeapIndex][(int)gen].Fragmentation;
- }
- else
- {
- FreeListBefore += PerHeapGenData[HeapIndex][(int)gen].FreeListSpaceBefore;
- FreeListAfter += PerHeapGenData[HeapIndex][(int)gen].FreeListSpaceAfter;
- }
- }
-
- double GenSizeGrown = GenSizeAfter - GenSizeBefore;
-
- // This is the most accurate situation we can calculuate (if it's not accurate it means
- // we are over estimating which is ok.
- if ((GenSizeGrown == 0) && ((FreeListBefore > 0) && (FreeListAfter >= 0)))
- {
- Allocated = YoungerGenOut;
- FreeListConsumed = FreeListBefore - FreeListAfter;
- // We don't know how much of the survived is pinned so we are overestimating here.
- if (Allocated < FreeListConsumed)
- return true;
- }
-
- return false;
- }
-
- public void GetGenDataObjSizeAfterMB(ref double[] GenData)
- {
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- {
- for (int GenIndex = 0; GenIndex <= (int)Gens.GenLargeObj; GenIndex++)
- GenData[GenIndex] += PerHeapGenData[HeapIndex][GenIndex].ObjSizeAfter / 1000000.0;
- }
- }
-
- public void GetGenDataSizeAfterMB(ref double[] GenData)
- {
- for (int GenIndex = 0; GenIndex <= (int)Gens.GenLargeObj; GenIndex++)
- GenData[GenIndex] = GenSizeAfterMB((Gens)GenIndex);
- }
-
- public double GetMaxGen0ObjSizeMB()
- {
- double MaxGen0ObjSize = 0;
- for (int HeapIndex = 0; HeapIndex < PerHeapHistories.Count; HeapIndex++)
- {
- MaxGen0ObjSize = Math.Max(MaxGen0ObjSize, PerHeapGenData[HeapIndex][(int)Gens.Gen0].ObjSizeAfter / 1000000.0);
- }
- return MaxGen0ObjSize;
- }
-
- // This represents the percentage time spent paused for this GC since the last GC completed.
- public double GetPauseTimePercentageSinceLastGC()
- {
- double pauseTimePercentage;
-
- if (Type == GCType.BackgroundGC)
- {
- // Find all GCs that occurred during the current background GC.
- double startTimeRelativeMSec = this.GCStartRelativeMSec;
- double endTimeRelativeMSec = this.GCStartRelativeMSec + this.GCDurationMSec;
-
- // Calculate the pause time for this BGC.
- // Pause time is defined as pause time for the BGC + pause time for all FGCs that ran during the BGC.
- double totalPauseTime = this.PauseDurationMSec;
-
- if (Index + 1 < Events.Count)
- {
- GCEvent gcEvent;
- for (int i = Index + 1; i < Events.Count; ++i)
- {
- gcEvent = Events[i];
- if ((gcEvent.GCStartRelativeMSec >= startTimeRelativeMSec) && (gcEvent.GCStartRelativeMSec < endTimeRelativeMSec))
- {
- totalPauseTime += gcEvent.PauseDurationMSec;
- }
- else
- {
- // We've finished processing all FGCs that occurred during this BGC.
- break;
- }
- }
- }
-
- // Get the elapsed time since the previous GC finished.
- int previousGCIndex = Index - 1;
- double previousGCStopTimeRelativeMSec;
- if (previousGCIndex >= 0)
- {
- GCEvent previousGCEvent = Events[previousGCIndex];
- previousGCStopTimeRelativeMSec = previousGCEvent.GCStartRelativeMSec + previousGCEvent.GCDurationMSec;
- }
- else
- {
- // Backstop in case this is the first GC.
- previousGCStopTimeRelativeMSec = Events[0].GCStartRelativeMSec;
- }
-
- double totalTime = (GCStartRelativeMSec + GCDurationMSec) - previousGCStopTimeRelativeMSec;
- pauseTimePercentage = (totalPauseTime * 100) / (totalTime);
- }
- else
- {
- double totalTime = PauseDurationMSec + DurationSinceLastRestartMSec;
- pauseTimePercentage = (PauseDurationMSec * 100) / (totalTime);
- }
-
- Debug.Assert(pauseTimePercentage <= 100);
- return pauseTimePercentage;
- }
-
- public int GetPinnedObjectPercentage()
- {
- if (totalPinnedPlugSize == -1)
- {
- totalPinnedPlugSize = 0;
- totalUserPinnedPlugSize = 0;
-
- foreach (KeyValuePair<ulong, long> item in PinnedObjects)
- {
- ulong Address = item.Key;
-
- for (int i = 0; i < PinnedPlugs.Count; i++)
- {
- if ((Address >= PinnedPlugs[i].Start) && (Address < PinnedPlugs[i].End))
- {
- PinnedPlugs[i].PinnedByUser = true;
- break;
- }
- }
- }
-
- for (int i = 0; i < PinnedPlugs.Count; i++)
- {
- long Size = (long)(PinnedPlugs[i].End - PinnedPlugs[i].Start);
- totalPinnedPlugSize += Size;
- if (PinnedPlugs[i].PinnedByUser)
- {
- totalUserPinnedPlugSize += Size;
- }
- }
- }
-
- return ((totalPinnedPlugSize == 0) ? -1 : (int)((double)pinnedObjectSizes * 100 / (double)totalPinnedPlugSize));
- }
-
- public long GetPinnedObjectSizes()
- {
- if (pinnedObjectSizes == -1)
- {
- pinnedObjectSizes = 0;
- foreach (KeyValuePair<ulong, long> item in PinnedObjects)
- {
- pinnedObjectSizes += item.Value;
- }
- }
- return pinnedObjectSizes;
- }
-
- public double GetTotalGCTime()
- {
- if (_TotalGCTimeMSec < 0)
- {
- _TotalGCTimeMSec = 0;
- if (GCCpuServerGCThreads != null)
- {
- for (int i = 0; i < GCCpuServerGCThreads.Length; i++)
- {
- _TotalGCTimeMSec += GCCpuServerGCThreads[i];
- }
- }
- _TotalGCTimeMSec += GCCpuMSec;
- }
-
- Debug.Assert(_TotalGCTimeMSec >= 0);
- return _TotalGCTimeMSec;
- }
-
- /// <summary>
- /// Get what's allocated into gen0 or gen3. For server GC this gets the total for
- /// all heaps.
- /// </summary>
- public double GetUserAllocated(Gens gen)
- {
- Debug.Assert((gen == Gens.Gen0) || (gen == Gens.GenLargeObj));
-
- if ((Type == GCType.BackgroundGC) && (gen == Gens.Gen0))
- {
- return AllocedSinceLastGCBasedOnAllocTickMB[(int)gen];
- }
-
- if (PerHeapHistories != null && Index > 0 && Events[Index - 1].PerHeapHistories != null)
- {
- double TotalAllocated = 0;
- if (Index > 0)
- {
- for (int i = 0; i < PerHeapHistories.Count; i++)
- {
- double Allocated = GetUserAllocatedPerHeap(i, gen);
-
- TotalAllocated += Allocated / 1000000.0;
- }
-
- return TotalAllocated;
- }
- else
- {
- return GenSizeBeforeMB(gen);
- }
- }
-
- return AllocedSinceLastGCBasedOnAllocTickMB[(gen == Gens.Gen0) ? 0 : 1];
- }
-
- public bool IsLowEphemeral()
- {
- return CondemnedReasonGroupSet(CondemnedReasonGroup.CRG_Low_Ephemeral);
- }
-
- public bool IsNotCompacting()
- {
- return ((GlobalHeapHistory.GlobalMechanisms & (GCGlobalMechanisms.Compaction)) != 0);
- }
-
- public double ObjSizeAfter(Gens gen)
- {
- double TotalObjSizeAfter = 0;
-
- if (PerHeapHistories != null)
- {
- for (int i = 0; i < PerHeapHistories.Count; i++)
- {
- TotalObjSizeAfter += PerHeapGenData[i][(int)gen].ObjSizeAfter;
- }
- }
-
- return TotalObjSizeAfter;
- }
-
- // Set in HeapStats
- public void SetHeapCount(int count)
- {
- if (heapCount == -1)
- {
- heapCount = count;
- }
- }
- public void SetUpServerGcHistory()
- {
- for (int i = 0; i < heapCount; i++)
- {
- int gcThreadId = 0;
- int gcThreadPriority = 0;
- Parent.ServerGcHeap2ThreadId.TryGetValue(i, out gcThreadId);
- Parent.ThreadId2Priority.TryGetValue(gcThreadId, out gcThreadPriority);
- ServerGcHeapHistories.Add(new ServerGcHistory
- {
- Parent = this,
- ProcessId = Parent.ProcessID,
- HeapId = i,
- GcWorkingThreadId = gcThreadId,
- GcWorkingThreadPriority = gcThreadPriority
- });
- }
- }
-
- public double SurvivalPercent(Gens gen)
- {
- double retSurvRate = double.NaN;
-
- long SurvRate = 0;
-
- if (gen == Gens.GenLargeObj)
- {
- if (GCGeneration < 2)
- {
- return retSurvRate;
- }
- }
- else if ((int)gen > GCGeneration)
- {
- return retSurvRate;
- }
-
- if (PerHeapHistories != null)
- {
- for (int i = 0; i < PerHeapHistories.Count; i++)
- {
- SurvRate += PerHeapGenData[i][(int)gen].SurvRate;
- }
-
- SurvRate /= PerHeapHistories.Count;
- }
-
- retSurvRate = SurvRate;
-
- return retSurvRate;
- }
-
-#endregion
-
- #region Internal Methods
- internal void AddGcJoin(GCJoinTraceData data)
- {
- if (data.Heap >= 0 && data.Heap < ServerGcHeapHistories.Count)
- ServerGcHeapHistories[data.Heap].AddJoin(data);
- else
- {
- foreach (var heap in ServerGcHeapHistories)
- heap.AddJoin(data);
- }
-
- }
- #endregion
-
- #region Private Methods
- private void AddCondemnedReason(Dictionary<CondemnedReasonGroup, int> ReasonsInfo, CondemnedReasonGroup Reason)
- {
- if (!ReasonsInfo.ContainsKey(Reason))
- ReasonsInfo.Add(Reason, 1);
- else
- (ReasonsInfo[Reason])++;
- }
-
- // For true/false groups, return whether that group is set.
- private bool CondemnedReasonGroupSet(CondemnedReasonGroup Group)
- {
- if (PerHeapCondemnedReasons == null)
- {
- return false;
- }
-
- int HeapIndexHighestGen = 0;
- if (PerHeapCondemnedReasons.Length != 1)
- {
- HeapIndexHighestGen = FindFirstHighestCondemnedHeap();
- }
-
- return (PerHeapCondemnedReasons[HeapIndexHighestGen].CondemnedReasonGroups[(int)Group] != 0);
- }
-
- // We recorded these as the timestamps when we saw the mark events, now convert them
- // to the actual time that it took for each mark.
- private void ConvertMarkTimes()
- {
- if (PerHeapMarkTimes != null)
- {
- foreach (KeyValuePair<int, MarkInfo> item in PerHeapMarkTimes)
- {
- if (item.Value.MarkTimes[(int)MarkRootType.MarkSizedRef] == 0.0)
- item.Value.MarkTimes[(int)MarkRootType.MarkSizedRef] = GCStartRelativeMSec;
-
- if (GCGeneration == 2)
- item.Value.MarkTimes[(int)MarkRootType.MarkOlder] = 0;
- else
- item.Value.MarkTimes[(int)MarkRootType.MarkOlder] -= item.Value.MarkTimes[(int)MarkRootType.MarkHandles];
-
- item.Value.MarkTimes[(int)MarkRootType.MarkHandles] -= item.Value.MarkTimes[(int)MarkRootType.MarkFQ];
- item.Value.MarkTimes[(int)MarkRootType.MarkFQ] -= item.Value.MarkTimes[(int)MarkRootType.MarkStack];
- item.Value.MarkTimes[(int)MarkRootType.MarkStack] -= item.Value.MarkTimes[(int)MarkRootType.MarkSizedRef];
- item.Value.MarkTimes[(int)MarkRootType.MarkSizedRef] -= GCStartRelativeMSec;
- }
- }
- }
-
- // When survival rate is 0, for certain releases (see comments for GetUserAllocatedPerHeap)
- // we need to estimate.
- private double EstimateAllocSurv0(int HeapIndex, Gens gen)
- {
- if (HasAllocTickEvents)
- {
- return AllocedSinceLastGCBasedOnAllocTickMB[(gen == Gens.Gen0) ? 0 : 1];
- }
- else
- {
- if (Index > 0)
- {
- // If the prevous GC has that heap get its size.
- var perHeapGenData = Events[Index - 1].PerHeapGenData;
- if (HeapIndex < perHeapGenData.Length)
- return perHeapGenData[HeapIndex][(int)gen].Budget;
- }
- return 0;
- }
- }
-
- private int FindFirstHighestCondemnedHeap()
- {
- int GenNumberHighest = (int)GCGeneration;
- for (int HeapIndex = 0; HeapIndex < PerHeapCondemnedReasons.Length; HeapIndex++)
- {
- int gen = PerHeapCondemnedReasons[HeapIndex].CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Final_Generation];
- if (gen == GenNumberHighest)
- {
- return HeapIndex;
- }
- }
-
- return 0;
- }
-
- /// <summary>
- /// For a given heap, get what's allocated into gen0 or gen3.
- /// We calculate this differently on 4.0, 4.5 Beta and 4.5 RC+.
- /// The caveat with 4.0 and 4.5 Beta is that when survival rate is 0,
- /// We don't know how to calculate the allocated - so we just use the
- /// last GC's budget (We should indicate this in the tool)
- /// </summary>
- private double GetUserAllocatedPerHeap(int HeapIndex, Gens gen)
- {
- long prevObjSize = 0;
- if (Index > 0)
- {
- // If the prevous GC has that heap get its size.
- var perHeapGenData = Events[Index - 1].PerHeapGenData;
- if (HeapIndex < perHeapGenData.Length)
- prevObjSize = perHeapGenData[HeapIndex][(int)gen].ObjSizeAfter;
- }
- GCPerHeapHistoryGenData currentGenData = PerHeapGenData[HeapIndex][(int)gen];
- long survRate = currentGenData.SurvRate;
- long currentObjSize = currentGenData.ObjSizeAfter;
- double Allocated;
-
- if (Version == PerHeapEventVersion.V4_0)
- {
- if (survRate == 0)
- Allocated = EstimateAllocSurv0(HeapIndex, gen);
- else
- Allocated = (currentGenData.Out + currentObjSize) * 100 / survRate - prevObjSize;
- }
- else
- {
- Allocated = currentGenData.ObjSpaceBefore - prevObjSize;
- }
-
- return Allocated;
- }
-
- private void GetVersion()
- {
- if (Version == PerHeapEventVersion.V0)
- {
- if (PerHeapHistories[0].V4_0)
- Version = PerHeapEventVersion.V4_0;
- else if (PerHeapHistories[0].V4_5)
- {
- Version = PerHeapEventVersion.V4_5;
- Debug.Assert(PerHeapHistories[0].Version == 2);
- }
- else
- {
- Version = PerHeapEventVersion.V4_6;
- Debug.Assert(PerHeapHistories[0].Version == 3);
- }
- }
- }
- #endregion
-
- #region Inner Private Structs
- private struct EncodedCondemnedReasons
- {
- public int Reasons;
- public int ReasonsEx;
- }
- #endregion
-
- #region Inner Public Classes
- public class MarkInfo
- {
- public long[] MarkPromoted;
-
- // Note that in 4.5 and prior (ie, from GCMark events, not GCMarkWithType), the first stage of the time
- // includes scanning sizedref handles(which can be very significant). We could distinguish that by interpreting
- // the Join events which I haven't done yet.
- public double[] MarkTimes;
- public MarkInfo(bool initPromoted = true)
- {
- MarkTimes = new double[(int)MarkRootType.MarkMax];
- if (initPromoted)
- MarkPromoted = new long[(int)MarkRootType.MarkMax];
- }
- };
-
- public class PinnedPlug
- {
- public ulong End;
- public bool PinnedByUser;
- public ulong Start;
- public PinnedPlug(ulong s, ulong e)
- {
- Start = s;
- End = e;
- PinnedByUser = false;
- }
- };
-
- public class ServerGcHistory
- {
- public int GcWorkingThreadId;
- public int GcWorkingThreadPriority;
- public int HeapId;
- public GCEvent Parent;
- public int ProcessId;
- public List<GcWorkSpan> SampleSpans = new List<GcWorkSpan>();
- public List<GcWorkSpan> SwitchSpans = new List<GcWorkSpan>();
-
- //list of times in msc starting from GC start when GCJoin events were fired for this heap
- private List<GcJoin> GcJoins = new List<GcJoin>();
- public enum WorkSpanType
- {
- GcThread,
- RivalThread,
- LowPriThread,
- Idle
- }
-
- public double TimeBaseMsc { get { return Parent.PauseStartRelativeMSec; } }
- public void AddSampleEvent(ThreadWorkSpan sample)
- {
- GcWorkSpan lastSpan = SampleSpans.Count > 0 ? SampleSpans[SampleSpans.Count - 1] : null;
- if (lastSpan != null && lastSpan.ThreadId == sample.ThreadId && lastSpan.ProcessId == sample.ProcessId)
- {
- lastSpan.DurationMsc++;
- }
- else
- {
- SampleSpans.Add(new GcWorkSpan(sample)
- {
- Type = GetSpanType(sample),
- RelativeTimestampMsc = sample.AbsoluteTimestampMsc - TimeBaseMsc,
- DurationMsc = 1
- });
- }
- }
-
- public void AddSwitchEvent(ThreadWorkSpan switchData)
- {
- GcWorkSpan lastSpan = SwitchSpans.Count > 0 ? SwitchSpans[SwitchSpans.Count - 1] : null;
- if (switchData.ThreadId == GcWorkingThreadId && switchData.ProcessId == ProcessId)
- {
- //update gc thread priority since we have new data
- GcWorkingThreadPriority = switchData.Priority;
- }
-
- if (lastSpan != null)
- {
- //updating duration of the last one, based on a timestamp from the new one
- lastSpan.DurationMsc = switchData.AbsoluteTimestampMsc - lastSpan.AbsoluteTimestampMsc;
-
- //updating wait readon of the last one
- lastSpan.WaitReason = switchData.WaitReason;
- }
-
- SwitchSpans.Add(new GcWorkSpan(switchData)
- {
- Type = GetSpanType(switchData),
- RelativeTimestampMsc = switchData.AbsoluteTimestampMsc - TimeBaseMsc,
- Priority = switchData.Priority
- });
- }
-
- internal void AddJoin(GCJoinTraceData data)
- {
- GcJoins.Add(new GcJoin
- {
- Heap = data.ProcessorNumber, //data.Heap is not reliable for reset events, so we use ProcessorNumber
- AbsoluteTimestampMsc = data.TimeStampRelativeMSec,
- RelativeTimestampMsc = data.TimeStampRelativeMSec - Parent.PauseStartRelativeMSec,
- Type = data.JoinType,
- Time = data.JoinTime,
- });
- }
-
- internal void GCEnd()
- {
- GcWorkSpan lastSpan = SwitchSpans.Count > 0 ? SwitchSpans[SwitchSpans.Count - 1] : null;
- if (lastSpan != null)
- {
- lastSpan.DurationMsc = Parent.PauseDurationMSec - lastSpan.RelativeTimestampMsc;
- }
- }
-
- private WorkSpanType GetSpanType(ThreadWorkSpan span)
- {
- if (span.ThreadId == GcWorkingThreadId && span.ProcessId == ProcessId)
- return WorkSpanType.GcThread;
- if (span.ProcessId == 0)
- return WorkSpanType.Idle;
-
- if (span.Priority >= GcWorkingThreadPriority || span.Priority == -1)
- return WorkSpanType.RivalThread;
- else
- return WorkSpanType.LowPriThread;
- }
-
- public class GcJoin
- {
- public double AbsoluteTimestampMsc;
- public int Heap;
- public double RelativeTimestampMsc;
- public GcJoinTime Time;
- public GcJoinType Type;
- }
-
- public class GcWorkSpan : ThreadWorkSpan
- {
- public double RelativeTimestampMsc;
- public WorkSpanType Type;
- public GcWorkSpan(ThreadWorkSpan span)
- : base(span)
- {
- }
- }
- }
-#endregion
-
- #region Inner Private Classes
- private class GCCondemnedReasons
- {
- /// <summary>
- /// This records which reasons are used and the value. Since the biggest value
- /// we need to record is the generation number a byte is sufficient.
- /// </summary>
- public byte[] CondemnedReasonGroups;
-
- public EncodedCondemnedReasons EncodedReasons;
-
-#if HAS_PRIVATE_GC_EVENTS
- public Dictionary<int, BGCAllocWaitInfo> LOHWaitThreads;
-#endif
-
- enum Condemned_Reason_Condition
- {
- CRC_induced_fullgc_p = 0,
- CRC_expand_fullgc_p = 1,
- CRC_high_mem_p = 2,
- CRC_very_high_mem_p = 3,
- CRC_low_ephemeral_p = 4,
- CRC_low_card_p = 5,
- CRC_eph_high_frag_p = 6,
- CRC_max_high_frag_p = 7,
- CRC_max_high_frag_e_p = 8,
- CRC_max_high_frag_m_p = 9,
- CRC_max_high_frag_vm_p = 10,
- CRC_max_gen1 = 11,
- CRC_before_oom = 12,
- CRC_gen2_too_small = 13,
- CRC_induced_noforce_p = 14,
- CRC_before_bgc = 15,
- CRC_max = 16,
- };
-
- // These values right now are the same as the first 4 in CondemnedReasonGroup.
- enum Condemned_Reason_Generation
- {
- CRG_initial = 0,
- CRG_final_per_heap = 1,
- CRG_alloc_budget = 2,
- CRG_time_tuning = 3,
- CRG_max = 4,
- };
- public void Decode(PerHeapEventVersion Version)
- {
- // First decode the reasons that return us a generation number.
- // It's the same in 4.0 and 4.5.
- for (Condemned_Reason_Generation i = 0; i < Condemned_Reason_Generation.CRG_max; i++)
- {
- CondemnedReasonGroups[(int)i] = (byte)GetReasonWithGenNumber(i);
- }
-
- // Then decode the reasons that just indicate true or false.
- for (Condemned_Reason_Condition i = 0; i < Condemned_Reason_Condition.CRC_max; i++)
- {
- if (GetReasonWithCondition(i, Version))
- {
- switch (i)
- {
- case Condemned_Reason_Condition.CRC_induced_fullgc_p:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Induced] = (byte)InducedType.Blocking;
- break;
- case Condemned_Reason_Condition.CRC_induced_noforce_p:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Induced] = (byte)InducedType.NotForced;
- break;
- case Condemned_Reason_Condition.CRC_low_ephemeral_p:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Low_Ephemeral] = 1;
- break;
- case Condemned_Reason_Condition.CRC_low_card_p:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Internal_Tuning] = 1;
- break;
- case Condemned_Reason_Condition.CRC_eph_high_frag_p:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Fragmented_Ephemeral] = 1;
- break;
- case Condemned_Reason_Condition.CRC_max_high_frag_p:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Fragmented_Gen2] = 1;
- break;
- case Condemned_Reason_Condition.CRC_max_high_frag_e_p:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Fragmented_Gen1_To_Gen2] = 1;
- break;
- case Condemned_Reason_Condition.CRC_max_high_frag_m_p:
- case Condemned_Reason_Condition.CRC_max_high_frag_vm_p:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Fragmented_Gen2_High_Mem] = 1;
- break;
- case Condemned_Reason_Condition.CRC_max_gen1:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Alloc_Exceeded] = 2;
- break;
- case Condemned_Reason_Condition.CRC_expand_fullgc_p:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Expand_Heap] = 1;
- break;
- case Condemned_Reason_Condition.CRC_before_oom:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_GC_Before_OOM] = 1;
- break;
- case Condemned_Reason_Condition.CRC_gen2_too_small:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Too_Small_For_BGC] = 1;
- break;
- case Condemned_Reason_Condition.CRC_before_bgc:
- CondemnedReasonGroups[(int)CondemnedReasonGroup.CRG_Ephemeral_Before_BGC] = 1;
- break;
- default:
- Debug.Assert(false, "Unexpected reason");
- break;
- }
- }
- }
- }
-
- private bool GetReasonWithCondition(Condemned_Reason_Condition Reason_Condition, PerHeapEventVersion Version)
- {
- bool ConditionIsSet = false;
- if (Version == PerHeapEventVersion.V4_0)
- {
- Debug.Assert((int)Reason_Condition < 16);
- ConditionIsSet = ((EncodedReasons.Reasons & (1 << (int)(Reason_Condition + 16))) != 0);
- }
- else
- {
- ConditionIsSet = ((EncodedReasons.ReasonsEx & (1 << (int)Reason_Condition)) != 0);
- }
- return ConditionIsSet;
- }
-
- private int GetReasonWithGenNumber(Condemned_Reason_Generation Reason_GenNumber)
- {
- int GenNumber = ((EncodedReasons.Reasons >> ((int)Reason_GenNumber * 2)) & 0x3);
- return GenNumber;
- }
- }
- #endregion
- }
-}
-
-#endif
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#if WINDOWS
-
-namespace GCPerfTestFramework.Metrics.Builders
-{
- /// <summary>
- /// GCInfo are accumulated statistics per generation.
- /// </summary>
- internal class GCInfo
- {
- public int GCCount;
- public int NumInduced;
- public long PinnedObjectSizes;
- public int PinnedObjectPercentage;
- public long NumGCWithPinEvents;
- public long NumGCWithPinPlugEvents;
- public double MaxPauseDurationMSec;
- public double MeanPauseDurationMSec { get { return TotalPauseTimeMSec / GCCount; } }
- public double MeanSizeAfterMB { get { return TotalSizeAfterMB / GCCount; } }
- public double MeanSizePeakMB { get { return TotalSizePeakMB / GCCount; } }
- public double MeanAllocatedMB { get { return TotalAllocatedMB / GCCount; } }
- public double RatioMeanPeakAfter { get { return MeanSizePeakMB / MeanSizeAfterMB; } }
- public double MeanGCCpuMSec { get { return TotalGCCpuMSec / GCCount; } }
-
- public double TotalPauseTimeMSec;
- public double MaxSuspendDurationMSec;
- public double MaxSizePeakMB;
- public double MaxAllocRateMBSec;
-
- public double TotalAllocatedMB;
- public double TotalGCCpuMSec;
- public double TotalPromotedMB;
-
- // these do not have a useful meaning so we hide them.
- internal double TotalSizeAfterMB;
- internal double TotalSizePeakMB;
- }
-}
-
-#endif
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#if WINDOWS
-
-using Microsoft.Diagnostics.Tracing;
-using Microsoft.Diagnostics.Tracing.Etlx;
-using Microsoft.Diagnostics.Tracing.Parsers.Clr;
-using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
-using Microsoft.Diagnostics.Tracing.Stacks;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-
-#if HAS_PRIVATE_GC_EVENTS
-using Microsoft.Diagnostics.Tracing.Parsers.ClrPrivate;
-#endif
-
-
-namespace GCPerfTestFramework.Metrics.Builders
-{
- /// <summary>
- /// GCProcess holds information about GCs in a particular process.
- /// </summary>
- ///
- // Notes on parsing GC events:
- // GC events need to be interpreted in sequence and if we attach we
- // may not get the whole sequence of a GC. We discard the incomplete
- // GC from the beginning and ending.
- //
- // We can make the assumption if we have the events from the private
- // provider it means we have events from public provider, but not the
- // other way around.
- //
- // All GC events are at informational level except the following:
- // AllocTick from the public provider
- // GCJoin from the private provider
- // We may only have events at the informational level, not verbose level.
- //
- internal class GCProcess
- {
- internal static IDictionary<int, GCProcess> Collect(
- TraceEventSource source,
- float sampleIntervalMSec,
- IDictionary<int, GCProcess> perProc = null,
- MutableTraceEventStackSource stackSource = null,
- Predicate<TraceEvent> filterFunc = null)
- {
- if (perProc == null)
- {
- perProc = new Dictionary<int, GCProcess>();
- }
-
- source.Kernel.AddCallbackForEvents(delegate (ProcessCtrTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- var stats = perProc.GetOrCreate(data.ProcessID);
- stats.PeakVirtualMB = ((double)data.PeakVirtualSize) / 1000000.0;
- stats.PeakWorkingSetMB = ((double)data.PeakWorkingSetSize) / 1000000.0;
- });
-
- Action<RuntimeInformationTraceData> doAtRuntimeStart = delegate (RuntimeInformationTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- var stats = perProc.GetOrCreate(data.ProcessID);
- stats.RuntimeVersion = "V " + data.VMMajorVersion.ToString() + "." + data.VMMinorVersion + "." + data.VMBuildNumber
- + "." + data.VMQfeNumber;
- stats.StartupFlags = data.StartupFlags;
- stats.Bitness = (data.RuntimeDllPath.ToLower().Contains("framework64") ? 64 : 32);
- if (stats.CommandLine == null)
- stats.CommandLine = data.CommandLine;
- };
-
- // log at both startup and rundown
- //var clrRundown = new ClrRundownTraceEventParser(source);
- //clrRundown.RuntimeStart += doAtRuntimeStart;
- source.Clr.AddCallbackForEvent("Runtime/Start", doAtRuntimeStart);
-
- Action<ProcessTraceData> processStartCallback = data =>
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- var stats = perProc.GetOrCreate(data.ProcessID);
-
- if (!string.IsNullOrEmpty(data.KernelImageFileName))
- {
- // When we just have an EventSource (eg, the source was created by
- // ETWTraceEventSource), we don't necessarily have the process names
- // decoded - it all depends on whether we have seen a ProcessStartGroup
- // event or not. When the trace was taken after the process started we
- // know we didn't see such an event.
- string name = GetImageName(data.KernelImageFileName);
-
- // Strictly speaking, this is not really fixing it 'cause
- // it doesn't handle when a process with the same name starts
- // with the same pid. The chance of that happening is really small.
- if (stats.isDead == true)
- {
- stats = new GCProcess();
- stats.Init(data);
- perProc[data.ProcessID] = stats;
- }
- }
-
- var commandLine = data.CommandLine;
- if (!string.IsNullOrEmpty(commandLine))
- stats.CommandLine = commandLine;
- };
-
- source.Kernel.AddCallbackForEvent("Process/Start", processStartCallback);
- source.Kernel.AddCallbackForEvent("Process/DCStart", processStartCallback);
-
- Action<ProcessTraceData> processEndCallback = delegate (ProcessTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- var stats = perProc.GetOrCreate(data.ProcessID);
-
- if (string.IsNullOrEmpty(stats.ProcessName))
- {
- stats.ProcessName = GetImageName(data.KernelImageFileName);
- }
-
- if (data.OpcodeName == "Stop")
- {
- stats.isDead = true;
- }
- };
-
- source.Kernel.AddCallbackForEvent("Process/Stop", processEndCallback);
- source.Kernel.AddCallbackForEvent("Process/DCStop", processEndCallback);
-
-#if (!CAP)
- CircularBuffer<ThreadWorkSpan> RecentThreadSwitches = new CircularBuffer<ThreadWorkSpan>(10000);
- source.Kernel.AddCallbackForEvent("Thread/CSwitch", delegate (CSwitchTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- RecentThreadSwitches.Add(new ThreadWorkSpan(data));
- GCProcess stats;
- if (perProc.TryGetValue(data.ProcessID, out stats))
- {
- stats.ThreadId2Priority[data.NewThreadID] = data.NewThreadPriority;
- if (stats.IsServerGCThread(data.ThreadID) > -1)
- {
- stats.ServerGcHeap2ThreadId[data.ProcessorNumber] = data.ThreadID;
- }
- }
-
- foreach (var gcProcess in perProc.Values)
- {
- GCEvent _event = gcProcess.GetCurrentGC();
- // If we are in the middle of a GC.
- if (_event != null)
- {
- if ((_event.Type != GCType.BackgroundGC) && (gcProcess.isServerGCUsed == 1))
- {
- _event.AddServerGcThreadSwitch(new ThreadWorkSpan(data));
- }
- }
- }
- });
-
- CircularBuffer<ThreadWorkSpan> RecentCpuSamples = new CircularBuffer<ThreadWorkSpan>(1000);
- StackSourceSample sample = new StackSourceSample(stackSource);
-
- source.Kernel.AddCallbackForEvent("PerfInfo/Sample", delegate (SampledProfileTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- RecentCpuSamples.Add(new ThreadWorkSpan(data));
- GCProcess processWithGc = null;
- foreach (var gcProcess in perProc.Values)
- {
- GCEvent e = gcProcess.GetCurrentGC();
- // If we are in the middle of a GC.
- if (e != null)
- {
- if ((e.Type != GCType.BackgroundGC) && (gcProcess.isServerGCUsed == 1))
- {
- e.AddServerGcSample(new ThreadWorkSpan(data));
- processWithGc = gcProcess;
- }
- }
- }
-
- if (stackSource != null && processWithGc != null)
- {
- GCEvent e = processWithGc.GetCurrentGC();
- sample.Metric = 1;
- sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
- var nodeName = string.Format("Server GCs #{0} in {1} (PID:{2})", e.GCNumber, processWithGc.ProcessName, processWithGc.ProcessID);
- var nodeIndex = stackSource.Interner.FrameIntern(nodeName);
- sample.StackIndex = stackSource.Interner.CallStackIntern(nodeIndex, stackSource.GetCallStack(data.CallStackIndex(), data));
- stackSource.AddSample(sample);
- }
-
- GCProcess stats;
- if (perProc.TryGetValue(data.ProcessID, out stats))
- {
- if (stats.IsServerGCThread(data.ThreadID) > -1)
- {
- stats.ServerGcHeap2ThreadId[data.ProcessorNumber] = data.ThreadID;
- }
-
- var cpuIncrement = sampleIntervalMSec;
- stats.ProcessCpuMSec += cpuIncrement;
-
- GCEvent _event = stats.GetCurrentGC();
- // If we are in the middle of a GC.
- if (_event != null)
- {
- bool isThreadDoingGC = false;
- if ((_event.Type != GCType.BackgroundGC) && (stats.isServerGCUsed == 1))
- {
- int heapIndex = stats.IsServerGCThread(data.ThreadID);
- if (heapIndex != -1)
- {
- _event.AddServerGCThreadTime(heapIndex, cpuIncrement);
- isThreadDoingGC = true;
- }
- }
- else if (data.ThreadID == stats.suspendThreadIDGC)
- {
- _event.GCCpuMSec += cpuIncrement;
- isThreadDoingGC = true;
- }
- else if (stats.IsBGCThread(data.ThreadID))
- {
- Debug.Assert(stats.currentBGC != null);
- if (stats.currentBGC != null)
- stats.currentBGC.GCCpuMSec += cpuIncrement;
- isThreadDoingGC = true;
- }
-
- if (isThreadDoingGC)
- {
- stats.GCCpuMSec += cpuIncrement;
- }
- }
- }
- });
-#endif
-
-
- source.Clr.AddCallbackForEvent("GC/SuspendEEStart", delegate (GCSuspendEETraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- var stats = perProc.GetOrCreate(data.ProcessID);
- switch (data.Reason)
- {
- case GCSuspendEEReason.SuspendForGC:
- stats.suspendThreadIDGC = data.ThreadID;
- break;
- case GCSuspendEEReason.SuspendForGCPrep:
- stats.suspendThreadIDBGC = data.ThreadID;
- break;
- default:
- stats.suspendThreadIDOther = data.ThreadID;
- break;
- }
-
- stats.suspendTimeRelativeMSec = data.TimeStampRelativeMSec;
- });
-
- // In 2.0 we didn't have this event.
-
- source.Clr.AddCallbackForEvent("GC/SuspendEEStop", delegate (GCNoUserDataTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
-
- if ((stats.suspendThreadIDBGC > 0) && (stats.currentBGC != null))
- {
- stats.currentBGC._SuspendDurationMSec += data.TimeStampRelativeMSec - stats.suspendTimeRelativeMSec;
- }
-
- stats.suspendEndTimeRelativeMSec = data.TimeStampRelativeMSec;
- });
-
- source.Clr.AddCallbackForEvent("GC/RestartEEStop", delegate (GCNoUserDataTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- if (_event.Type == GCType.BackgroundGC)
- {
- stats.AddConcurrentPauseTime(_event, data.TimeStampRelativeMSec);
- }
- else
- {
- Debug.Assert(_event.PauseStartRelativeMSec != 0);
- // In 2.0 Concurrent GC, since we don't know the GC's type we can't tell if it's concurrent
- // or not. But we know we don't have nested GCs there so simply check if we have received the
- // GCStop event; if we have it means it's a blocking GC; otherwise it's a concurrent GC so
- // simply add the pause time to the GC without making the GC complete.
- if (_event.GCDurationMSec == 0)
- {
- Debug.Assert(_event.is20Event);
- _event.isConcurrentGC = true;
- stats.AddConcurrentPauseTime(_event, data.TimeStampRelativeMSec);
- }
- else
- {
- _event.PauseDurationMSec = data.TimeStampRelativeMSec - _event.PauseStartRelativeMSec;
- if (_event.HeapStats != null)
- {
- _event.isComplete = true;
- stats.lastCompletedGC = _event;
- }
- }
- }
- }
-
- // We don't change between a GC end and the pause resume.
- //Debug.Assert(stats.allocTickAtLastGC == stats.allocTickCurrentMB);
- // Mark that we are not in suspension anymore.
- stats.suspendTimeRelativeMSec = -1;
- stats.suspendThreadIDOther = -1;
- stats.suspendThreadIDBGC = -1;
- stats.suspendThreadIDGC = -1;
- });
-
- source.Clr.AddCallbackForEvent("GC/AllocationTick", delegate (GCAllocationTickTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
-
- if (stats.HasAllocTickEvents == false)
- {
- stats.HasAllocTickEvents = true;
- }
-
- double valueMB = data.GetAllocAmount(ref stats.SeenBadAllocTick) / 1000000.0;
-
- if (data.AllocationKind == GCAllocationKind.Small)
- {
- // Would this do the right thing or is it always 0 for SOH since AllocationAmount
- // is an int???
- stats.allocTickCurrentMB[0] += valueMB;
- }
- else
- {
- stats.allocTickCurrentMB[1] += valueMB;
- }
- });
-
- source.Clr.AddCallbackForEvent("GC/Start", delegate (GCStartTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
-
- // We need to filter the scenario where we get 2 GCStart events for each GC.
- if ((stats.suspendThreadIDGC > 0) &&
- !((stats.events.Count > 0) && stats.events[stats.events.Count - 1].GCNumber == data.Count))
- {
- GCEvent _event = new GCEvent(stats);
- Debug.Assert(0 <= data.Depth && data.Depth <= 2);
- // _event.GCGeneration = data.Depth; Old style events only have this in the GCStop event.
- _event.Reason = data.Reason;
- _event.GCNumber = data.Count;
- _event.Type = data.Type;
- _event.Index = stats.events.Count;
- _event.is20Event = data.IsClassicProvider;
- bool isEphemeralGCAtBGCStart = false;
- // Detecting the ephemeral GC that happens at the beginning of a BGC.
- if (stats.events.Count > 0)
- {
- GCEvent lastGCEvent = stats.events[stats.events.Count - 1];
- if ((lastGCEvent.Type == GCType.BackgroundGC) &&
- (!lastGCEvent.isComplete) &&
- (data.Type == GCType.NonConcurrentGC))
- {
- isEphemeralGCAtBGCStart = true;
- }
- }
-
- Debug.Assert(stats.suspendTimeRelativeMSec != -1);
- if (isEphemeralGCAtBGCStart)
- {
- _event.PauseStartRelativeMSec = data.TimeStampRelativeMSec;
- }
- else
- {
- _event.PauseStartRelativeMSec = stats.suspendTimeRelativeMSec;
- if (stats.suspendEndTimeRelativeMSec == -1)
- {
- stats.suspendEndTimeRelativeMSec = data.TimeStampRelativeMSec;
- }
-
- _event._SuspendDurationMSec = stats.suspendEndTimeRelativeMSec - stats.suspendTimeRelativeMSec;
- }
-
- _event.GCStartRelativeMSec = data.TimeStampRelativeMSec;
- stats.events.Add(_event);
-
- if (_event.Type == GCType.BackgroundGC)
- {
- stats.currentBGC = _event;
- _event.ProcessCpuAtLastGC = stats.ProcessCpuAtLastGC;
- }
-
-#if (!CAP)
- if ((_event.Type != GCType.BackgroundGC) && (stats.isServerGCUsed == 1))
- {
- _event.SetUpServerGcHistory();
- foreach (var s in RecentCpuSamples)
- _event.AddServerGcSample(s);
- foreach (var s in RecentThreadSwitches)
- _event.AddServerGcThreadSwitch(s);
- }
-#endif
- }
- });
-
- source.Clr.AddCallbackForEvent("GC/PinObjectAtGCTime", delegate (PinObjectAtGCTimeTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- if (!_event.PinnedObjects.ContainsKey(data.ObjectID))
- {
- _event.PinnedObjects.Add(data.ObjectID, data.ObjectSize);
- }
- else
- {
- _event.duplicatedPinningReports++;
- }
- }
- });
-
- // Some builds have this as a public event, and some have it as a private event.
- // All will move to the private event, so we'll remove this code afterwards.
- source.Clr.AddCallbackForEvent("GC/PinPlugAtGCTime", delegate (PinPlugAtGCTimeTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- // ObjectID is supposed to be an IntPtr. But "Address" is defined as UInt64 in
- // TraceEvent.
- _event.PinnedPlugs.Add(new GCEvent.PinnedPlug(data.PlugStart, data.PlugEnd));
- }
- });
-
- source.Clr.AddCallbackForEvent("GC/Mark", delegate (GCMarkWithTypeTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
- stats.AddServerGCThreadFromMark(data.ThreadID, data.HeapNum);
-
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- if (_event.PerHeapMarkTimes == null)
- {
- _event.PerHeapMarkTimes = new Dictionary<int, GCEvent.MarkInfo>();
- }
-
- if (!_event.PerHeapMarkTimes.ContainsKey(data.HeapNum))
- {
- _event.PerHeapMarkTimes.Add(data.HeapNum, new GCEvent.MarkInfo());
- }
-
- _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)data.Type] = data.TimeStampRelativeMSec;
- _event.PerHeapMarkTimes[data.HeapNum].MarkPromoted[(int)data.Type] = data.Promoted;
- }
- });
-
- source.Clr.AddCallbackForEvent("GC/GlobalHeapHistory", delegate (GCGlobalHeapHistoryTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
- stats.ProcessGlobalHistory(data);
- });
-
- source.Clr.AddCallbackForEvent("GC/PerHeapHistory", delegate (GCPerHeapHistoryTraceData3 data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
- stats.ProcessPerHeapHistory(data);
- });
-
-#if HAS_PRIVATE_GC_EVENTS
- // See if the source knows about the CLR Private provider, if it does, then
- var gcPrivate = new ClrPrivateTraceEventParser(source);
-
- gcPrivate.GCPinPlugAtGCTime += delegate (PinPlugAtGCTimeTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- // ObjectID is supposed to be an IntPtr. But "Address" is defined as UInt64 in
- // TraceEvent.
- _event.PinnedPlugs.Add(new GCEvent.PinnedPlug(data.PlugStart, data.PlugEnd));
- }
- };
-
- // Sometimes at the end of a trace I see only some mark events are included in the trace and they
- // are not in order, so need to anticipate that scenario.
- gcPrivate.GCMarkStackRoots += delegate (GCMarkTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
- stats.AddServerGCThreadFromMark(data.ThreadID, data.HeapNum);
-
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- if (_event.PerHeapMarkTimes == null)
- {
- _event.PerHeapMarkTimes = new Dictionary<int, GCEvent.MarkInfo>();
- }
-
- if (!_event.PerHeapMarkTimes.ContainsKey(data.HeapNum))
- {
- _event.PerHeapMarkTimes.Add(data.HeapNum, new GCEvent.MarkInfo(false));
- }
-
- _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)MarkRootType.MarkStack] = data.TimeStampRelativeMSec;
- }
- };
-
- gcPrivate.GCMarkFinalizeQueueRoots += delegate (GCMarkTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- if ((_event.PerHeapMarkTimes != null) && _event.PerHeapMarkTimes.ContainsKey(data.HeapNum))
- {
- _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)MarkRootType.MarkFQ] =
- data.TimeStampRelativeMSec;
- }
- }
- };
-
- gcPrivate.GCMarkHandles += delegate (GCMarkTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- if ((_event.PerHeapMarkTimes != null) && _event.PerHeapMarkTimes.ContainsKey(data.HeapNum))
- {
- _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)MarkRootType.MarkHandles] =
- data.TimeStampRelativeMSec;
- }
- }
- };
-
- gcPrivate.GCMarkCards += delegate (GCMarkTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- if ((_event.PerHeapMarkTimes != null) && _event.PerHeapMarkTimes.ContainsKey(data.HeapNum))
- {
- _event.PerHeapMarkTimes[data.HeapNum].MarkTimes[(int)MarkRootType.MarkOlder] =
- data.TimeStampRelativeMSec;
- }
- }
- };
-
- gcPrivate.GCGlobalHeapHistory += delegate (GCGlobalHeapHistoryTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
- stats.ProcessGlobalHistory(data);
- };
-
- gcPrivate.GCPerHeapHistory += delegate (GCPerHeapHistoryTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
- stats.ProcessPerHeapHistory(data);
- };
-
- gcPrivate.GCBGCStart += delegate (GCNoUserDataTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
- if (stats.currentBGC != null)
- {
- if (stats.backgroundGCThreads == null)
- {
- stats.backgroundGCThreads = new Dictionary<int, object>(16);
- }
- stats.backgroundGCThreads[data.ThreadID] = null;
- }
- };
-#endif
-
- source.Clr.AddCallbackForEvent("GC/Stop", delegate (GCEndTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
- GCEvent _event = stats.GetCurrentGC();
- if (_event != null)
- {
- _event.GCDurationMSec = data.TimeStampRelativeMSec - _event.GCStartRelativeMSec;
- _event.GCGeneration = data.Depth;
- _event.GCEnd();
- }
- });
-
- source.Clr.AddCallbackForEvent("GC/HeapStats", delegate (GCHeapStatsTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
- GCEvent _event = stats.GetCurrentGC();
-
- var sizeAfterMB = (data.GenerationSize1 + data.GenerationSize2 + data.GenerationSize3) / 1000000.0;
- if (_event != null)
- {
- _event.HeapStats = (GCHeapStatsTraceData)data.Clone();
-
- if (_event.Type == GCType.BackgroundGC)
- {
- _event.ProcessCpuMSec = stats.ProcessCpuMSec - _event.ProcessCpuAtLastGC;
- _event.DurationSinceLastRestartMSec = data.TimeStampRelativeMSec - stats.lastRestartEndTimeRelativeMSec;
- }
- else
- {
- _event.ProcessCpuMSec = stats.ProcessCpuMSec - stats.ProcessCpuAtLastGC;
- _event.DurationSinceLastRestartMSec = _event.PauseStartRelativeMSec - stats.lastRestartEndTimeRelativeMSec;
- }
-
- if (stats.HasAllocTickEvents)
- {
- _event.HasAllocTickEvents = true;
- _event.AllocedSinceLastGCBasedOnAllocTickMB[0] = stats.allocTickCurrentMB[0] - stats.allocTickAtLastGC[0];
- _event.AllocedSinceLastGCBasedOnAllocTickMB[1] = stats.allocTickCurrentMB[1] - stats.allocTickAtLastGC[1];
- }
-
- // This is where a background GC ends.
- if ((_event.Type == GCType.BackgroundGC) && (stats.currentBGC != null))
- {
- stats.currentBGC.isComplete = true;
- stats.lastCompletedGC = stats.currentBGC;
- stats.currentBGC = null;
- }
-
- if (_event.isConcurrentGC)
- {
- Debug.Assert(_event.is20Event);
- _event.isComplete = true;
- stats.lastCompletedGC = _event;
- }
- }
-
- stats.ProcessCpuAtLastGC = stats.ProcessCpuMSec;
- stats.allocTickAtLastGC[0] = stats.allocTickCurrentMB[0];
- stats.allocTickAtLastGC[1] = stats.allocTickCurrentMB[1];
- stats.lastRestartEndTimeRelativeMSec = data.TimeStampRelativeMSec;
- });
-
- source.Clr.AddCallbackForEvent("GC/TerminateConcurrentThread", delegate (GCTerminateConcurrentThreadTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc.GetOrCreate(data.ProcessID);
- if (stats.backgroundGCThreads != null)
- {
- stats.backgroundGCThreads = null;
- }
- });
-
-#if HAS_PRIVATE_GC_EVENTS
- gcPrivate.GCBGCAllocWaitStart += delegate (BGCAllocWaitTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
- Debug.Assert(stats.currentBGC != null);
-
- if (stats.currentBGC != null)
- {
- stats.currentBGC.AddLOHWaitThreadInfo(data.ThreadID, data.TimeStampRelativeMSec, data.Reason, true);
- }
- };
-
- gcPrivate.GCBGCAllocWaitStop += delegate (BGCAllocWaitTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess stats = perProc[data];
-
- GCEvent _event = stats.GetLastBGC();
-
- if (_event != null)
- {
- _event.AddLOHWaitThreadInfo(data.ThreadID, data.TimeStampRelativeMSec, data.Reason, false);
- }
- };
-
- gcPrivate.GCJoin += delegate (GCJoinTraceData data)
- {
- if (filterFunc != null && !filterFunc.Invoke(data))
- {
- return;
- }
-
- GCProcess gcProcess = perProc[data];
- GCEvent _event = gcProcess.GetCurrentGC();
- if (_event != null)
- {
- _event.AddGcJoin(data);
- }
- };
-
- source.Process();
-#endif
- return perProc;
- }
-
- public int ProcessID { get; set; }
- public string ProcessName { get; set; }
- bool isDead;
- public bool Interesting { get { return Total.GCCount > 0 || RuntimeVersion != null; } }
- public bool InterestingForAnalysis
- {
- get
- {
- return ((Total.GCCount > 3) &&
- ((GetGCPauseTimePercentage() > 1.0) || (Total.MaxPauseDurationMSec > 200.0)));
- }
- }
-
- // A process can have one or more SBAs associated with it.
- public GCInfo[] Generations = new GCInfo[3] { new GCInfo(), new GCInfo(), new GCInfo() };
- public GCInfo Total = new GCInfo();
- public float ProcessCpuMSec; // Total CPU time used in process (approximate)
- public float GCCpuMSec; // CPU time used in the GC (approximate)
- public int NumberOfHeaps = 1;
-
- // Of all the CPU, how much as a percentage is spent in the GC.
- public float PercentTimeInGC { get { return GCCpuMSec * 100 / ProcessCpuMSec; } }
-
- public static string GetImageName(string path)
- {
- int startIdx = path.LastIndexOf('\\');
- if (0 <= startIdx)
- startIdx++;
- else
- startIdx = 0;
- int endIdx = path.LastIndexOf('.');
- if (endIdx <= startIdx)
- endIdx = path.Length;
- string name = path.Substring(startIdx, endIdx - startIdx);
- return name;
- }
-
- // This is calculated based on the GC events which is fine for the GC analysis purpose.
- public double ProcessDuration
- {
- get
- {
- double startRelativeMSec = 0.0;
-
- for (int i = 0; i < events.Count; i++)
- {
- if (events[i].isComplete)
- {
- startRelativeMSec = events[i].PauseStartRelativeMSec;
- break;
- }
- }
-
- if (startRelativeMSec == 0.0)
- return 0;
-
- // Get the end time of the last GC.
- double endRelativeMSec = lastRestartEndTimeRelativeMSec;
- return (endRelativeMSec - startRelativeMSec);
- }
- }
-
- public StartupFlags StartupFlags;
- public string RuntimeVersion;
- public int Bitness = -1;
- public Dictionary<int, int> ThreadId2Priority = new Dictionary<int, int>();
- public Dictionary<int, int> ServerGcHeap2ThreadId = new Dictionary<int, int>();
-
- public string CommandLine { get; set; }
-
- public double PeakWorkingSetMB { get; set; }
- public double PeakVirtualMB { get; set; }
-
- /// <summary>
- /// Means it detected that the ETW information is in a format it does not understand.
- /// </summary>
- public bool GCVersionInfoMismatch { get; private set; }
-
- public double GetGCPauseTimePercentage()
- {
- return ((ProcessDuration == 0) ? 0.0 : ((Total.TotalPauseTimeMSec * 100) / ProcessDuration));
- }
-
- internal static void ComputeRollup(IDictionary<int, GCProcess> perProc)
- {
- // Compute rollup information.
- foreach (GCProcess stats in perProc.Values)
- {
- for (int i = 0; i < stats.events.Count; i++)
- {
- GCEvent _event = stats.events[i];
- if (!_event.isComplete)
- {
- continue;
- }
-
- _event.Index = i;
- if (_event.DetailedGenDataAvailable()) //per heap histories is not null
- stats.m_detailedGCInfo = true;
-
- // Update the per-generation information
- stats.Generations[_event.GCGeneration].GCCount++;
- bool isInduced = ((_event.Reason == GCReason.Induced) || (_event.Reason == GCReason.InducedNotForced));
- if (isInduced)
- (stats.Generations[_event.GCGeneration].NumInduced)++;
-
- long PinnedObjectSizes = _event.GetPinnedObjectSizes();
- if (PinnedObjectSizes != 0)
- {
- stats.Generations[_event.GCGeneration].PinnedObjectSizes += PinnedObjectSizes;
- stats.Generations[_event.GCGeneration].NumGCWithPinEvents++;
- }
-
- int PinnedObjectPercentage = _event.GetPinnedObjectPercentage();
- if (PinnedObjectPercentage != -1)
- {
- stats.Generations[_event.GCGeneration].PinnedObjectPercentage += _event.GetPinnedObjectPercentage();
- stats.Generations[_event.GCGeneration].NumGCWithPinPlugEvents++;
- }
-
- stats.Generations[_event.GCGeneration].TotalGCCpuMSec += _event.GetTotalGCTime();
- stats.Generations[_event.GCGeneration].TotalSizeAfterMB += _event.HeapSizeAfterMB;
-
- stats.Generations[_event.GCGeneration].TotalSizePeakMB += _event.HeapSizePeakMB;
- stats.Generations[_event.GCGeneration].TotalPromotedMB += _event.PromotedMB;
- stats.Generations[_event.GCGeneration].TotalPauseTimeMSec += _event.PauseDurationMSec;
- stats.Generations[_event.GCGeneration].TotalAllocatedMB += _event.AllocedSinceLastGCMB;
- stats.Generations[_event.GCGeneration].MaxPauseDurationMSec = Math.Max(stats.Generations[_event.GCGeneration].MaxPauseDurationMSec, _event.PauseDurationMSec);
- stats.Generations[_event.GCGeneration].MaxSizePeakMB = Math.Max(stats.Generations[_event.GCGeneration].MaxSizePeakMB, _event.HeapSizePeakMB);
- stats.Generations[_event.GCGeneration].MaxAllocRateMBSec = Math.Max(stats.Generations[_event.GCGeneration].MaxAllocRateMBSec, _event.AllocRateMBSec);
- stats.Generations[_event.GCGeneration].MaxPauseDurationMSec = Math.Max(stats.Generations[_event.GCGeneration].MaxPauseDurationMSec, _event.PauseDurationMSec);
- stats.Generations[_event.GCGeneration].MaxSuspendDurationMSec = Math.Max(stats.Generations[_event.GCGeneration].MaxSuspendDurationMSec, _event._SuspendDurationMSec);
-
- // And the totals
- stats.Total.GCCount++;
- if (isInduced)
- stats.Total.NumInduced++;
- if (PinnedObjectSizes != 0)
- {
- stats.Total.PinnedObjectSizes += PinnedObjectSizes;
- stats.Total.NumGCWithPinEvents++;
- }
- if (PinnedObjectPercentage != -1)
- {
- stats.Total.PinnedObjectPercentage += _event.GetPinnedObjectPercentage();
- stats.Total.NumGCWithPinPlugEvents++;
- }
- stats.Total.TotalGCCpuMSec += _event.GetTotalGCTime();
- stats.Total.TotalSizeAfterMB += _event.HeapSizeAfterMB;
- stats.Total.TotalPromotedMB += _event.PromotedMB;
- stats.Total.TotalSizePeakMB += _event.HeapSizePeakMB;
- stats.Total.TotalPauseTimeMSec += _event.PauseDurationMSec;
- stats.Total.TotalAllocatedMB += _event.AllocedSinceLastGCMB;
- stats.Total.MaxPauseDurationMSec = Math.Max(stats.Total.MaxPauseDurationMSec, _event.PauseDurationMSec);
- stats.Total.MaxSizePeakMB = Math.Max(stats.Total.MaxSizePeakMB, _event.HeapSizePeakMB);
- stats.Total.MaxAllocRateMBSec = Math.Max(stats.Total.MaxAllocRateMBSec, _event.AllocRateMBSec);
- stats.Total.MaxSuspendDurationMSec = Math.Max(stats.Total.MaxSuspendDurationMSec, _event._SuspendDurationMSec);
- }
- }
- }
-
-#region private
- protected virtual void Init(TraceEvent data)
- {
- ProcessID = data.ProcessID;
- ProcessName = data.ProcessName;
- isDead = false;
- }
- private GCEvent GetCurrentGC()
- {
- if (events.Count > 0)
- {
- if (!events[events.Count - 1].isComplete)
- {
- return events[events.Count - 1];
- }
- else if (currentBGC != null)
- {
- return currentBGC;
- }
- }
-
- return null;
- }
-
- // This is the last GC in progress. We need this for server Background GC.
- // See comments for lastCompletedGC.
- private GCEvent GetLastGC()
- {
- GCEvent _event = GetCurrentGC();
- if ((isServerGCUsed == 1) &&
- (_event == null))
- {
- if (lastCompletedGC != null)
- {
- Debug.Assert(lastCompletedGC.Type == GCType.BackgroundGC);
- _event = lastCompletedGC;
- }
- }
-
- return _event;
- }
-
- private GCEvent GetLastBGC()
- {
- if (currentBGC != null)
- {
- return currentBGC;
- }
-
- if ((lastCompletedGC != null) && (lastCompletedGC.Type == GCType.BackgroundGC))
- {
- return lastCompletedGC;
- }
-
- // Otherwise we search till we find the last BGC if we have seen one.
- for (int i = (events.Count - 1); i >= 0; i--)
- {
- if (events[i].Type == GCType.BackgroundGC)
- {
- return events[i];
- }
- }
-
- return null;
- }
-
- private void AddConcurrentPauseTime(GCEvent _event, double RestartEEMSec)
- {
- if (suspendThreadIDBGC > 0)
- {
- _event.PauseDurationMSec += RestartEEMSec - suspendTimeRelativeMSec;
- }
- else
- {
- Debug.Assert(_event.PauseDurationMSec == 0);
- _event.PauseDurationMSec = RestartEEMSec - _event.PauseStartRelativeMSec;
- }
- }
-
- private void AddServerGCThreadFromMark(int ThreadID, int HeapNum)
- {
- if (isServerGCUsed == 1)
- {
- Debug.Assert(heapCount > 1);
-
- if (serverGCThreads.Count < heapCount)
- {
- // I am seeing that sometimes we are not getting these events from all heaps
- // for a complete GC so I have to check for that.
- if (!serverGCThreads.ContainsKey(ThreadID))
- {
- serverGCThreads.Add(ThreadID, HeapNum);
- }
- }
- }
- }
-
- private void ProcessGlobalHistory(GCGlobalHeapHistoryTraceData data)
- {
- if (isServerGCUsed == -1)
- {
- // We detected whether we are using Server GC now.
- isServerGCUsed = ((data.NumHeaps > 1) ? 1 : 0);
- if (heapCount == -1)
- {
- heapCount = data.NumHeaps;
- }
-
- if (isServerGCUsed == 1)
- {
- serverGCThreads = new Dictionary<int, int>(data.NumHeaps);
- }
- }
-
- GCEvent _event = GetLastGC();
- if (_event != null)
- {
- _event.GlobalHeapHistory = (GCGlobalHeapHistoryTraceData)data.Clone();
- _event.SetHeapCount(heapCount);
- }
- }
-
- private void ProcessPerHeapHistory(GCPerHeapHistoryTraceData data)
- {
- if (!data.VersionRecognized)
- {
- GCVersionInfoMismatch = true;
- return;
- }
-
- GCEvent _event = GetLastGC();
- if (_event != null)
- {
- if (_event.PerHeapHistories == null)
- _event.PerHeapHistories = new List<GCPerHeapHistoryTraceData>();
- _event.PerHeapHistories.Add((GCPerHeapHistoryTraceData)data.Clone());
- }
- }
-
- public IList<GCEvent> Events
- {
- get
- {
- return events;
- }
- }
-
- private List<GCEvent> events = new List<GCEvent>();
-
- // The amount of memory allocated by the user threads. So they are divided up into gen0 and LOH allocations.
- double[] allocTickCurrentMB = { 0.0, 0.0 };
- double[] allocTickAtLastGC = { 0.0, 0.0 };
- bool HasAllocTickEvents = false;
- bool SeenBadAllocTick = false;
-
- double lastRestartEndTimeRelativeMSec;
-
- // EE can be suspended via different reasons. The only ones we care about are
- // SuspendForGC(1) - suspending for GC start
- // SuspendForGCPrep(6) - BGC uses it in the middle of a BGC.
- // We need to filter out the rest of the suspend/resume events.
- // Keep track of the last time we started suspending the EE. Will use in 'Start' to set PauseStartRelativeMSec
- int suspendThreadIDOther = -1;
- int suspendThreadIDBGC = -1;
- // This is either the user thread (in workstation case) or a server GC thread that called SuspendEE to do a GC
- int suspendThreadIDGC = -1;
- double suspendTimeRelativeMSec = -1;
- double suspendEndTimeRelativeMSec = -1;
-
- // This records the amount of CPU time spent at the end of last GC.
- float ProcessCpuAtLastGC = 0;
-
- // This is the BGC that's in progress as we are parsing. We need to remember this
- // so we can correctly attribute the suspension time.
- GCEvent currentBGC = null;
- Dictionary<int, object> backgroundGCThreads = null;
- bool IsBGCThread(int threadID)
- {
- if (backgroundGCThreads != null)
- return backgroundGCThreads.ContainsKey(threadID);
- return false;
- }
-
- // I keep this for the purpose of server Background GC. Unfortunately for server background
- // GC we are firing the GCEnd/GCHeaps events and Global/Perheap events in the reversed order.
- // This is so that the Global/Perheap events can still be attributed to the right BGC.
- GCEvent lastCompletedGC = null;
- // We don't necessarily have the GCSettings event (only fired at the beginning if we attach)
- // So we have to detect whether we are running server GC or not.
- // Till we get our first GlobalHeapHistory event which indicates whether we use server GC
- // or not this remains -1.
- public int isServerGCUsed = -1;
- public int heapCount = -1;
- // This is the server GC threads. It's built up in the 2nd server GC we see.
- Dictionary<int, int> serverGCThreads = null;
-
-
- internal bool m_detailedGCInfo;
- int IsServerGCThread(int threadID)
- {
- int heapIndex;
- if (serverGCThreads != null)
- {
- if (serverGCThreads.TryGetValue(threadID, out heapIndex))
- {
- return heapIndex;
- }
- }
- return -1;
- }
-#endregion
- }
-
-#if HAS_PRIVATE_GC_EVENTS
- class BGCAllocWaitInfo
- {
- public double WaitStartRelativeMSec;
- public double WaitStopRelativeMSec;
- public BGCAllocWaitReason Reason;
-
- public bool GetWaitTime(ref double pauseMSec)
- {
- if ((WaitStartRelativeMSec != 0) &&
- (WaitStopRelativeMSec != 0))
- {
- pauseMSec = WaitStopRelativeMSec - WaitStartRelativeMSec;
- return true;
- }
- return false;
- }
-
- public bool IsLOHWaitLong(double pauseMSecMin)
- {
- double pauseMSec = 0;
- if (GetWaitTime(ref pauseMSec))
- {
- return (pauseMSec > pauseMSecMin);
- }
- return false;
- }
-
- public override string ToString()
- {
- if ((Reason == BGCAllocWaitReason.GetLOHSeg) ||
- (Reason == BGCAllocWaitReason.AllocDuringSweep))
- {
- return "Waiting for BGC to thread free lists";
- }
- else
- {
- Debug.Assert(Reason == BGCAllocWaitReason.AllocDuringBGC);
- return "Allocated too much during BGC, waiting for BGC to finish";
- }
- }
- }
-#endif
-}
-
-#endif
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#if WINDOWS
-
-using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
-
-namespace GCPerfTestFramework.Metrics.Builders
-{
- /// <summary>
- /// Span of thread work recorded by CSwitch or CPU Sample Profile events
- /// </summary>
- internal class ThreadWorkSpan
- {
- public int ThreadId;
- public int ProcessId;
- public string ProcessName;
- public int ProcessorNumber;
- public double AbsoluteTimestampMsc;
- public double DurationMsc;
- public int Priority = -1;
- public int WaitReason = -1;
-
- public ThreadWorkSpan(CSwitchTraceData switchData)
- {
- ProcessName = switchData.NewProcessName;
- ThreadId = switchData.NewThreadID;
- ProcessId = switchData.NewProcessID;
- ProcessorNumber = switchData.ProcessorNumber;
- AbsoluteTimestampMsc = switchData.TimeStampRelativeMSec;
- Priority = switchData.NewThreadPriority;
- WaitReason = (int)switchData.OldThreadWaitReason;
- }
-
- public ThreadWorkSpan(ThreadWorkSpan span)
- {
- ProcessName = span.ProcessName;
- ThreadId = span.ThreadId;
- ProcessId = span.ProcessId;
- ProcessorNumber = span.ProcessorNumber;
- AbsoluteTimestampMsc = span.AbsoluteTimestampMsc;
- DurationMsc = span.DurationMsc;
- Priority = span.Priority;
- WaitReason = span.WaitReason;
- }
-
- public ThreadWorkSpan(SampledProfileTraceData sample)
- {
- ProcessName = sample.ProcessName;
- ProcessId = sample.ProcessID;
- ThreadId = sample.ThreadID;
- ProcessorNumber = sample.ProcessorNumber;
- AbsoluteTimestampMsc = sample.TimeStampRelativeMSec;
- DurationMsc = 1;
- Priority = 0;
- }
- }
-}
-
-#endif
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.Xunit.Performance.Sdk;
-using System;
-
-namespace GCPerfTestFramework.Metrics
-{
- /// <summary>
- /// This attribute marks a xunit-performance test artifact as requiring GC metrics. When this attribute adorns
- /// a test artifact, xunit-performance creates an instance of <see cref="GCMetricDiscoverer"/> and uses
- /// it to populate the list of metrics provided for that artifact.
- /// </summary>
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
-#if WINDOWS
- [PerformanceMetricDiscoverer("GCPerfTestFramework.Metrics.GCMetricDiscoverer", "GCPerfTestFramework")]
-#endif
- public class CollectGCMetricsAttribute :
- Attribute
-#if WINDOWS
- , IPerformanceMetricAttribute
-#endif
- {
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#if WINDOWS
-
-using GCPerfTestFramework.Metrics.Builders;
-using Microsoft.Diagnostics.Tracing;
-using Microsoft.Diagnostics.Tracing.Parsers;
-using Microsoft.Xunit.Performance;
-using Microsoft.Xunit.Performance.Sdk;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using Xunit.Abstractions;
-
-namespace GCPerfTestFramework.Metrics
-{
- /// <summary>
- /// GCMetricDiscoverer is one of two publicly-exposed classes from the library and is the
- /// portion of this library that speaks directly to xunit-performance. When a
- /// <see cref="CollectGCMetricsAttribute"/> is observed when xunit-performance is enumerating
- /// the attributes on a test method, it instantiates an instance of this class and calls
- /// GetMetrics on it, which yields the list of metrics that this library provides.
- ///
- /// This class and <see cref="CollectGCMetricsAttribute"/> should be the *only* classes
- /// exposed by this namespace.
- /// </summary>
- public class GCMetricDiscoverer : IPerformanceMetricDiscoverer
- {
- /// <summary>
- /// Yields all current custom GC metrics.
- /// </summary>
- /// <param name="metricAttribute">Unused.</param>
- /// <returns>An enumerator yielding new instances of all of the existing custom GC metrics.</returns>
- public IEnumerable<PerformanceMetricInfo> GetMetrics(IAttributeInfo metricAttribute)
- {
- yield return new GCMaxPauseMetric();
- yield return new GCMeanPauseMetric();
- yield return new GCPeakVirtualMemoryMetric();
- yield return new GCPeakWorkingSetMetric();
- yield return new GCTotalPauseTimeMetric();
- yield return new GCCpuTimeInGCMetric();
- yield return new GCGenZeroMeanPauseDuration();
- yield return new GCGenOneMeanPauseDuration();
- yield return new GCGenTwoMeanPauseDuration();
- yield return new GCGenZeroCount();
- yield return new GCGenOneCount();
- yield return new GCGenTwoBGCCount();
- yield return new GCGenTwoGCCount();
- }
- }
-
- /// <summary>
- /// Base class for all GC-related metrics that handles provider registration for child metrics, since
- /// all GC-related metrics will be listening to the same trace providers.
- /// </summary>
- internal abstract class GCMetric : PerformanceMetric
- {
- /// <summary>
- /// Number of bytes in a megabyte, for convenience.
- /// </summary>
- public const int BytesInMegabyte = 1048576;
-
- /// <summary>
- /// Creates a new GCMetric with the given ID, display name, and unit.
- /// </summary>
- /// <param name="id">The ID of the metric</param>
- /// <param name="displayName">A human-friendly display name of the metric</param>
- /// <param name="unit">The unit of the metric</param>
- public GCMetric(string id, string displayName, string unit)
- : base(id, displayName, unit)
- {
- }
-
- /// <summary>
- /// Indicates to xunit-performance what trace providers that these metrics
- /// require.
- /// </summary>
- public override IEnumerable<ProviderInfo> ProviderInfo
- {
- get
- {
- yield return new KernelProviderInfo()
- {
- Keywords = (ulong)(KernelTraceEventParser.Keywords.ContextSwitch
- | KernelTraceEventParser.Keywords.Profile
- | KernelTraceEventParser.Keywords.ProcessCounters)
- };
- yield return new UserProviderInfo()
- {
- ProviderGuid = ClrTraceEventParser.ProviderGuid,
- Level = TraceEventLevel.Verbose,
- Keywords = (ulong)ClrTraceEventParser.Keywords.GC
- };
- }
- }
-
- /// <summary>
- /// Constructs a new PerformanceMetricEvaluator for this metric. Implementors of a custom metric must override
- /// this method and instruct it to instantiate the GCEvaluator for that custom metric.
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public abstract override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context);
- }
-
- /// <summary>
- /// Base class for all GC-related metric evaluators that handles the complexity of multiplexing possibly many
- /// GC metrics on top of a single "trace session" using a reference-counting strategy.
- /// </summary>
- internal abstract class GCEvaluator : PerformanceMetricEvaluator
- {
- /// <summary>
- /// The sample rate used by xunit-performance when collecting ETW traces. Used
- /// to infer the total time spent in GC based on CPU samples.
- /// </summary>
- const float SampleRate = 1.0f;
-
- // These three fields are part of a bit of a hack to avoid having to re-parse the ETL file
- // every time a new metric is evaluated.
- //
- // The idea here is that every class that derives from GCEvaluator increments the
- // reference count whenever an iteration begins and decrements it whenever an iteration ends.
- // When the reference count is zero, the session is nulled out for the next iteration.
- // If _session is null when an iteration begins, the first metric to reach it will set it up
- // to trace the session. In this way, the first metric in sets up the session and the last one
- // out tears it down in preparation for the next iteration.
- //
- // This scheme is not thread-safe and will break if xunit-performance ever runs benchmarks in
- // parallel, although that's pretty unlikely for a benchmarking framework.
- private static IDictionary<int, GCProcess> s_session;
- private static int s_sessionRefCount;
- private static bool s_hasComputedRollup;
-
- private readonly PerformanceMetricEvaluationContext _context;
-
- /// <summary>
- /// Property exposed to child metrics that automatically ensures that the session is valid and that
- /// rollup information has been calculated, calculating it if it has not happened already.
- /// </summary>
- /// <exception cref="InvalidOperationException">
- /// Thrown if this property is unable to determine an
- /// appropriate process for analysis. Usually this occurs when
- /// the test framework itself crashes and fails to launch a test.
- /// </exception>
- protected GCProcess ProcessInfo
- {
- get
- {
- if (!s_hasComputedRollup)
- {
- GCProcess.ComputeRollup(s_session);
- s_hasComputedRollup = true;
- }
-
- // Since we are spawning this process with UseShellExecute set to false,
- // the spawned process itself spawns an instance of "conhost.exe" on Windows.
- // We want to be sure we don't pick out that one for analysis.
- foreach (var candidate in s_session.Values)
- {
- if (candidate.CommandLine != null)
- {
- if (!candidate.CommandLine.Contains("conhost.exe"))
- {
- return candidate;
- }
- }
- }
-
- // This should never happen in GC-related tests, which are always required to spawn an additional process.
- throw new InvalidOperationException("Failed to find an appropriate target process for analysis!");
- }
- }
-
- /// <summary>
- /// Constructs a new GCEvaluator and sets its content to the given PerformanceMetricEvaluationContext.
- /// </summary>
- /// <param name="context">The context received from the test framework</param>
- public GCEvaluator(PerformanceMetricEvaluationContext context)
- {
- Debug.Assert(context.TraceEventSource is TraceEventDispatcher);
- _context = context;
- }
-
- /// <summary>
- /// Creates a session if it does not exist and increments the reference count on the session.
- /// </summary>
- /// <param name="beginEvent">Unused.</param>
- public override void BeginIteration(TraceEvent beginEvent)
- {
- if (s_session == null)
- {
- // The filter function here is to filter out events that we are not concerned with collecting, i.e. events from
- // processes not spawned by us.
- s_session = GCProcess.Collect(_context.TraceEventSource as TraceEventDispatcher, SampleRate, filterFunc: _context.IsTestEvent);
- s_hasComputedRollup = false;
- }
-
- s_sessionRefCount++;
- }
-
- /// <summary>
- /// Yields the metric and decrements the reference count on the session, disposing it
- /// if the reference count is zero.
- /// </summary>
- /// <param name="endEvent">Unused.</param>
- /// <returns>The value of the metric calculated by this class</returns>
- public override double EndIteration(TraceEvent endEvent)
- {
- var metric = YieldMetric();
- s_sessionRefCount--;
- if (s_sessionRefCount == 0)
- {
- s_session = null;
-
- // not doing this results in tremendous memory leaks!
- _context.TraceEventSource.Kernel.RemoveCallback<TraceEvent>(null);
- _context.TraceEventSource.Clr.RemoveCallback<TraceEvent>(null);
- }
-
- return metric;
- }
-
- /// <summary>
- /// Overriden by child metrics to determine how to yield the value of the metric
- /// that the child metric provides. In general, overriders of this method
- /// do something with the value of the <see cref="ProcessInfo"/> property.
- /// </summary>
- /// <returns>The value of this metric</returns>
- protected abstract double YieldMetric();
- }
-}
-
-#endif
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#if WINDOWS
-
-using System.Linq;
-using Microsoft.Xunit.Performance.Sdk;
-using Microsoft.Diagnostics.Tracing.Parsers.Clr;
-
-/// <summary>
-/// This file contains a number of GC-related metrics that are provided to xunit-performance.
-/// Each one of these derives from GCMetric, which manages the creation of the GC object model
-/// from an ETL trace - these classes are only responsible for using it to produce a meaningful
-/// metric.
-///
-/// Each one of these metrics should be fairly self-explanatory.
-/// </summary>
-namespace GCPerfTestFramework.Metrics
-{
- #region Maximum Pause Duration
- internal class GCMaxPauseMetric : GCMetric
- {
- public GCMaxPauseMetric()
- : base("GCMaxPause", "Maximum GC Pause Duraction", PerformanceMetricUnits.Milliseconds)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCMaxPauseEvaluator(context);
- }
- }
-
- internal class GCMaxPauseEvaluator : GCEvaluator
- {
- public GCMaxPauseEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Total.MaxPauseDurationMSec;
- }
- }
- #endregion
-
- #region Mean Pause Duration
- internal class GCMeanPauseMetric : GCMetric
- {
- public GCMeanPauseMetric()
- : base("GCMeanPause", "Mean GC Pause Duraction", PerformanceMetricUnits.Milliseconds)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCMeanPauseEvaluator(context);
- }
- }
-
- internal class GCMeanPauseEvaluator : GCEvaluator
- {
- public GCMeanPauseEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Total.MeanPauseDurationMSec;
- }
- }
- #endregion
-
- #region Peak Virtual Memory Size
- internal class GCPeakVirtualMemoryMetric : GCMetric
- {
- public GCPeakVirtualMemoryMetric()
- : base("GCPeakVirtualMemory", "Process Peak Virtual Memory", PerformanceMetricUnits.Bytes)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCPeakVirtualMemoryMetricEvaluator(context);
- }
- }
-
- internal class GCPeakVirtualMemoryMetricEvaluator : GCEvaluator
- {
- public GCPeakVirtualMemoryMetricEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.PeakVirtualMB * GCMetric.BytesInMegabyte;
- }
- }
- #endregion
-
- #region Peak Working Set Size
- internal class GCPeakWorkingSetMetric : GCMetric
- {
- public GCPeakWorkingSetMetric()
- : base("GCPeakWorkingSet", "Process Peak Working Set", PerformanceMetricUnits.Bytes)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCPeakWorkingSetMetricEvaluator(context);
- }
- }
-
- internal class GCPeakWorkingSetMetricEvaluator : GCEvaluator
- {
- public GCPeakWorkingSetMetricEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.PeakWorkingSetMB * GCMetric.BytesInMegabyte;
- }
- }
- #endregion
-
- #region Total Pause Time
- internal class GCTotalPauseTimeMetric : GCMetric
- {
- public GCTotalPauseTimeMetric()
- : base("GCTotalPauseTime", "Total time spent paused due to GC activity", PerformanceMetricUnits.Milliseconds)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCTotalPauseTimeMetricEvaluator(context);
- }
- }
-
- internal class GCTotalPauseTimeMetricEvaluator : GCEvaluator
- {
- public GCTotalPauseTimeMetricEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Total.TotalPauseTimeMSec;
- }
- }
- #endregion
-
- #region CPU time in GC
- internal class GCCpuTimeInGCMetric : GCMetric
- {
- public GCCpuTimeInGCMetric()
- : base("GCCpuTimeInGC", "Total CPU time spent in GC activity", PerformanceMetricUnits.Milliseconds)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCCpuTimeInGCMetricEvaluator(context);
- }
- }
-
- internal class GCCpuTimeInGCMetricEvaluator : GCEvaluator
- {
- public GCCpuTimeInGCMetricEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Total.TotalGCCpuMSec;
- }
- }
- #endregion
-
- #region Generation Zero Mean Pause Duration
- internal class GCGenZeroMeanPauseDuration : GCMetric
- {
- public GCGenZeroMeanPauseDuration()
- : base("GCGenZeroMeanPauseDuration", "Mean pause duration for Gen0 collections", PerformanceMetricUnits.Count)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCGenZeroMeanPauseDurationEvaluator(context);
- }
- }
-
- internal class GCGenZeroMeanPauseDurationEvaluator : GCEvaluator
- {
- public GCGenZeroMeanPauseDurationEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Generations[0].MeanPauseDurationMSec;
- }
- }
- #endregion
-
- #region Generation One Mean Pause Duration
- internal class GCGenOneMeanPauseDuration : GCMetric
- {
- public GCGenOneMeanPauseDuration()
- : base("GCGenOneMeanPauseDuration", "Mean pause duration for Gen1 collections", PerformanceMetricUnits.Count)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCGenOneMeanPauseDurationEvaluator(context);
- }
- }
-
- internal class GCGenOneMeanPauseDurationEvaluator : GCEvaluator
- {
- public GCGenOneMeanPauseDurationEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Generations[1].MeanPauseDurationMSec;
- }
- }
- #endregion
-
- #region Generation Two Mean Pause Duration
- internal class GCGenTwoMeanPauseDuration : GCMetric
- {
- public GCGenTwoMeanPauseDuration()
- : base("GCGenTwoMeanPauseDuration", "Mean pause duration for Gen2 collections", PerformanceMetricUnits.Count)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCGenTwoMeanPauseDurationEvaluator(context);
- }
- }
-
- internal class GCGenTwoMeanPauseDurationEvaluator : GCEvaluator
- {
- public GCGenTwoMeanPauseDurationEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Generations[2].MeanPauseDurationMSec;
- }
- }
- #endregion
-
- #region Generation Zero GC Count
- internal class GCGenZeroCount : GCMetric
- {
- public GCGenZeroCount()
- : base("GCGenZeroCount", "Number of Generation 0 GCs", PerformanceMetricUnits.Count)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCGenZeroCountEvaluator(context);
- }
- }
-
- internal class GCGenZeroCountEvaluator : GCEvaluator
- {
- public GCGenZeroCountEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Generations[0].GCCount;
- }
- }
- #endregion
-
- #region Generation One GC Count
- internal class GCGenOneCount : GCMetric
- {
- public GCGenOneCount()
- : base("GCGenOneCount", "Number of Generation 1 GCs", PerformanceMetricUnits.Count)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCGenOneCountEvaluator(context);
- }
- }
-
- internal class GCGenOneCountEvaluator : GCEvaluator
- {
- public GCGenOneCountEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Generations[1].GCCount;
- }
- }
- #endregion
-
- #region Generation 2 Background GC Count
- internal class GCGenTwoBGCCount : GCMetric
- {
- public GCGenTwoBGCCount()
- : base("GCGenTwoBGCCount", "Number of Generation 2 background GCs", PerformanceMetricUnits.Count)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCGenTwoBGCCountEvaluator(context);
- }
- }
-
- internal class GCGenTwoBGCCountEvaluator : GCEvaluator
- {
- public GCGenTwoBGCCountEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Events.Count(e => e.Generation == 2 && e.Type == GCType.BackgroundGC);
- }
- }
- #endregion
-
- #region Generation 2 Blocking GC Count
- internal class GCGenTwoGCCount : GCMetric
- {
- public GCGenTwoGCCount()
- : base("GCGenTwoGCCount", "Number of Generation 2 blocking GCs", PerformanceMetricUnits.Count)
- {
-
- }
-
- public override PerformanceMetricEvaluator CreateEvaluator(PerformanceMetricEvaluationContext context)
- {
- return new GCGenTwoGCCountEvaluator(context);
- }
- }
-
- internal class GCGenTwoGCCountEvaluator : GCEvaluator
- {
- public GCGenTwoGCCountEvaluator(PerformanceMetricEvaluationContext context)
- : base(context)
- {
-
- }
-
- protected override double YieldMetric()
- {
- return ProcessInfo.Events.Count(e => e.Generation == 2 && e.Type == GCType.NonConcurrentGC);
- }
- }
- #endregion
-}
-
-#endif
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using GCPerfTestFramework.Metrics;
-using Microsoft.Xunit.Performance;
-using System.Collections.Generic;
-
-[assembly: CollectGCMetrics]
-
-namespace GCPerfTestFramework
-{
- public class PerfTests
- {
- const string ConcurrentGC = "COMPLUS_gcConcurrent";
- const string ServerGC = "COMPLUS_gcServer";
-
- [Benchmark]
- public void ClientSimulator_Concurrent()
- {
- var exe = ProcessFactory.ProbeForFile("GCSimulator.exe");
- var env = new Dictionary<string, string>()
- {
- [ConcurrentGC] = "1"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, "-i 100", env);
- }
- }
- }
-
- [Benchmark]
- public void ClientSimulator_Server()
- {
- var exe = ProcessFactory.ProbeForFile("GCSimulator.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "1"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, "-i 100", env);
- }
- }
- }
-
- [Benchmark]
- public void ClientSimulator_Server_One_Thread()
- {
- var exe = ProcessFactory.ProbeForFile("GCSimulator.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "1"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, "-i 10 -notimer -dp 0.0", env);
- }
- }
- }
-
- [Benchmark]
- public void ClientSimulator_Server_Two_Threads()
- {
- var exe = ProcessFactory.ProbeForFile("GCSimulator.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "1"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, "-i 10 -notimer -dp 0.0 -t 2", env);
- }
- }
- }
-
- [Benchmark]
- public void ClientSimulator_Server_Four_Threads()
- {
- var exe = ProcessFactory.ProbeForFile("GCSimulator.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "1"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, "-i 10 -notimer -dp 0.0 -t 4", env);
- }
- }
- }
-
-
- [Benchmark]
- public void LargeStringConcat()
- {
- var exe = ProcessFactory.ProbeForFile("LargeStrings.exe");
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe);
- }
- }
- }
-
- [Benchmark]
- public void LargeStringConcat_Server()
- {
- var exe = ProcessFactory.ProbeForFile("LargeStrings.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "1"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, environmentVariables: env);
- }
- }
- }
-
- [Benchmark]
- public void LargeStringConcat_Workstation()
- {
- var exe = ProcessFactory.ProbeForFile("LargeStrings.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "0"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, environmentVariables: env);
- }
- }
- }
-
- [Benchmark]
- public void MidLife_Concurrent()
- {
- var exe = ProcessFactory.ProbeForFile("MidLife.exe");
- var env = new Dictionary<string, string>()
- {
- [ConcurrentGC] = "1"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, environmentVariables: env);
- }
- }
- }
-
- [Benchmark]
- public void MidLife_Server()
- {
- var exe = ProcessFactory.ProbeForFile("MidLife.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "1"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, environmentVariables: env);
- }
- }
- }
-
- [Benchmark]
- public void MidLife_Workstation()
- {
- var exe = ProcessFactory.ProbeForFile("MidLife.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "0"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, environmentVariables: env);
- }
- }
- }
-
- [Benchmark]
- public void ConcurrentSpin()
- {
- var exe = ProcessFactory.ProbeForFile("ConcurrentSpin.exe");
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe);
- }
- }
- }
-
- [Benchmark]
- public void ConcurrentSpin_Server()
- {
- var exe = ProcessFactory.ProbeForFile("ConcurrentSpin.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "1"
- };
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, environmentVariables: env);
- }
- }
- }
-
- [Benchmark]
- public void ConcurrentSpin_Server_NonConcurrent()
- {
- var exe = ProcessFactory.ProbeForFile("ConcurrentSpin.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "1",
- [ConcurrentGC] = "0"
- };
-
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, environmentVariables: env);
- }
- }
- }
-
- [Benchmark]
- public void ConcurrentSpin_Workstation()
- {
- var exe = ProcessFactory.ProbeForFile("ConcurrentSpin.exe");
- var env = new Dictionary<string, string>()
- {
- [ServerGC] = "0",
- };
-
- foreach (var iteration in Benchmark.Iterations)
- {
- using (iteration.StartMeasurement())
- {
- ProcessFactory.LaunchProcess(exe, environmentVariables: env);
- }
- }
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-
-namespace GCPerfTestFramework
-{
- public static class ProcessFactory
- {
- const string ProbePathEnvironmentVariable = "GC_PERF_TEST_PROBE_PATH";
- const string CoreRunProbePathEnvironmentVariable = "GC_PERF_TEST_CORE_RUN_PROBE_PATH";
- const string UseCoreCLREnvironmentVariable = "GC_PERF_TEST_CORECLR";
- const string ConcurrentGCVariable = "COMPLUS_gcConcurrent";
- const string ServerGCVariable = "COMPLUS_gcServer";
- const string CoreRunName = "CoreRun.exe";
- const string UnixCoreRunName = "corerun";
-
- // The default timeout for a test is half an hour. If a test takes that long, it is
- // definitely not responding.
- const int DefaultTimeout = 1800000 /* ms */;
-
- /// <summary>
- /// Location of the CoreRun hosting process, for use in CoreCLR performance runs
- /// when the operating system can't launch a managed assembly directly. This runs
- /// as part of the static constructor and so all tests will fail if CoreRun cannot
- /// be found.
- /// </summary>
- private static string s_coreRun = LocateCoreRun();
-
- /// <summary>
- /// Launches a process that is part of a test scenario, waits for it complete, and returns.
- /// This API does several things specific to this test framework:
- ///
- /// 1. The fileName argument is absolute, and must be resolved using executable probing:
- /// see <see cref="ProbeForFile"/> for more information. The reason why this function does not
- /// perform the probing is that this function is invoked while "on the clock" by the benchmarking process
- /// and file system probing is costly and only needs to be done once, before the test begins.
- /// The general pattern for perf tests is that the test executable is located before beginning the benchmark
- /// step to avoid doing file system lookups while on the clock.
- /// 2. The arguments argument is passed verbatim to the Process that is spawned,
- /// 3. The passed environment variables are set for the child process, replacing any variables
- /// in the existing process. xunit-performance by default turns off ConcurrentGC and ServerGC,
- /// and we need to restore that when our process is completed.
- /// 4. The timeout parameter controls how long this function will wait once a process is spawned.
- /// if the supplied timeout is less than or equal to zero, this function will wait indefinitely for the child process.
- /// If a process does timeout, this function throws a <see cref="TimeoutException"/>.
- ///
- /// This method delegates partially to a platform-specific implementation which determines whether or not the operating
- /// system is capable of executing a managed assembly directly or if a hosting process needs to be used.
- /// Currently, this means that the executable will be directly executed on Desktop CLR, running on Windows, while
- /// CoreCLR on any platform will need to invoke a hosting process.
- /// </summary>
- /// <param name="fileName">The absolute path to the executable to execute</param>
- /// <param name="arguments">The arguments to pass to the executable</param>
- /// <param name="environmentVariables">Any environment variables to pass to the child process</param>
- /// <param name="timeout">How long to wait, in milliseconds, on the child process. If less than or equal to zero,
- /// no timeout is used.</param>
- /// <exception cref="TimeoutException">Thrown if the process takes longer than timout to terminate.</exception>
- public static void LaunchProcess(
- string fileName,
- string arguments = "",
- IDictionary<string, string> environmentVariables = null,
- int timeout = DefaultTimeout)
- {
- var previousEnvironmentVars = new Dictionary<string, string>();
-
- if (environmentVariables != null)
- {
- foreach (var pair in environmentVariables)
- {
- var replacedValue = Environment.GetEnvironmentVariable(pair.Key);
- previousEnvironmentVars.Add(pair.Key, replacedValue);
- Environment.SetEnvironmentVariable(pair.Key, pair.Value);
- }
- }
-
- try
- {
- Process process;
-
- // for CoreCLR, we need to launch using the CoreRun hosting process.
- if (ShouldUseCoreRun())
- {
- process = LaunchProcessCoreClrImpl(fileName, arguments);
- }
- else
- {
- process = LaunchProcessDesktopImpl(fileName, arguments);
- }
-
- if (timeout > 0)
- {
- // the caller has specified a timeout. Use it.
- if (!process.WaitForExit(timeout))
- {
- process.Kill();
- throw new TimeoutException("Process did not complete within the allotted time");
- }
-
- return;
- }
-
- process.WaitForExit();
- }
- finally
- {
- // Restore the original environment variables
- if (environmentVariables != null)
- {
- foreach (var pair in previousEnvironmentVars)
- {
- Environment.SetEnvironmentVariable(pair.Key, pair.Value);
- }
- }
- }
- }
-
- /// <summary>
- /// Launches a process directly by allowing the underlying operating system to invoke the managed
- /// assembly.
- /// </summary>
- /// <param name="fileName">The absolute path of the executable to run</param>
- /// <param name="arguments">The arguments to the target executable</param>
- private static Process LaunchProcessDesktopImpl(string fileName, string arguments)
- {
- var process = new Process();
- process.StartInfo.FileName = fileName;
- process.StartInfo.Arguments = arguments;
- process.StartInfo.UseShellExecute = false;
- process.StartInfo.CreateNoWindow = false;
- process.Start();
- return process;
- }
-
- /// <summary>
- /// Launches a process indirectly by invoking a hosting process that will invoke the given managed assembly.
- /// This is usually called "corerun" for CoreCLR.
- /// </summary>
- /// <param name="fileName">The absolute path of the executable to run</param>
- /// <param name="arguments">The arguments to the target executable</param>
- private static Process LaunchProcessCoreClrImpl(string fileName, string arguments)
- {
- var process = new Process();
- process.StartInfo.FileName = s_coreRun;
- process.StartInfo.Arguments = fileName + " " + arguments;
- process.StartInfo.UseShellExecute = false;
- process.StartInfo.CreateNoWindow = false;
- process.Start();
- return process;
- }
-
- /// <summary>
- /// Locates the CoreRun executable based on the probe path given in the CoreRunProbePathEnvironmentVariable
- /// environment variable.
- /// </summary>
- /// <returns>The located path of CoreRun.exe</returns>
- /// <exception cref="InvalidOperationException">If CoreRun.exe cannot be found on the given path.</exception>
- private static string LocateCoreRun()
- {
- if (!ShouldUseCoreRun())
- {
- // no need to locate CoreRun if it won't be used.
- return string.Empty;
- }
-
- var coreRunProbePath = Environment.GetEnvironmentVariable(CoreRunProbePathEnvironmentVariable);
- if (coreRunProbePath == null)
- {
- throw new InvalidOperationException($"Environment variable {CoreRunProbePathEnvironmentVariable} must be set for CoreCLR performance runs!");
- }
-
- var path = ProbeForFileImpl(CoreRunName, coreRunProbePath);
-
-#if !WINDOWS
- // CoreRun.exe may not have the .exe extension on non-Windows platforms.
- if (path == null)
- {
- path = ProbeForFileImpl(UnixCoreRunName, coreRunProbePath);
- }
-#endif
-
- if (path == null)
- {
- throw new InvalidOperationException($"Failed to locate {CoreRunName} on search path {coreRunProbePath}");
- }
-
- return path;
- }
-
- private static bool ShouldUseCoreRun()
- {
-#if WINDOWS
- return Environment.GetEnvironmentVariable(UseCoreCLREnvironmentVariable) == "1";
-#else
- return true;
-#endif
- }
-
- /// <summary>
- /// Probes for a file named fileName starting recursively from the directory named in the ProbePathEnvironmentVariable.
- /// </summary>
- /// <param name="fileName">The filename to probe for</param>
- /// <returns>An absolute path to the located file</returns>
- /// <exception cref="InvalidOperationException">
- /// If the probe path environment variable is not set, or the named file cannot be found
- /// in the probe path.
- /// </exception>
- public static string ProbeForFile(string fileName)
- {
- var probePath = Environment.GetEnvironmentVariable(ProbePathEnvironmentVariable);
- if (probePath == null)
- {
- // fall back to the current working directory if the probe path is not set
- probePath = Directory.GetCurrentDirectory();
- }
-
- var path = ProbeForFileImpl(fileName, probePath);
- if (path == null)
- {
- throw new InvalidOperationException($"Failed to locate file \"{ fileName }\" on path \"{probePath}\"");
- }
-
- return path;
- }
-
- /// <summary>
- /// Starting at probePath, probe all files in that directory and all directories
- /// recursively for a file named fileName. The filename equality check is case-insensitive.
- /// </summary>
- /// <param name="fileName">The name of the file to search for</param>
- /// <param name="probePath">The directory to start the recursive search</param>
- /// <returns>An absolute path to the file if found, or null if the file is not found.</returns>
- private static string ProbeForFileImpl(string fileName, string probePath)
- {
- // probe from the top down - we don't want to waste lots of time doing a bottom up
- // search in a deep directory tree if the files we are looking for are at the top-level.
- foreach (var file in Directory.EnumerateFiles(probePath))
- {
- if (StringComparer.OrdinalIgnoreCase.Equals(Path.GetFileName(file), fileName))
- {
- return file;
- }
- }
-
- foreach (var directory in Directory.EnumerateDirectories(probePath))
- {
- var result = ProbeForFileImpl(fileName, directory);
- if (result != null) return result;
- }
-
- return null;
- }
- }
-}
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
- <package id="Microsoft.Diagnostics.Tracing.TraceEvent" version="1.0.41" targetFramework="net461" />
- <package id="Microsoft.DotNet.xunit.performance" version="1.0.0-alpha-build0040" targetFramework="net461" />
- <package id="Microsoft.DotNet.xunit.performance.metrics" version="1.0.0-alpha-build0040" targetFramework="net461" />
- <package id="System.Collections" version="4.0.10" targetFramework="net461" />
- <package id="System.Diagnostics.Debug" version="4.0.10" targetFramework="net461" />
- <package id="System.Diagnostics.Tracing" version="4.1.0" targetFramework="net461" />
- <package id="System.Globalization" version="4.0.10" targetFramework="net461" />
- <package id="System.IO" version="4.1.0" targetFramework="net461" />
- <package id="System.IO.FileSystem" version="4.0.0" targetFramework="net461" />
- <package id="System.IO.FileSystem.Primitives" version="4.0.0" targetFramework="net461" />
- <package id="System.Linq" version="4.1.0" targetFramework="net461" />
- <package id="System.Reflection" version="4.1.0" targetFramework="net461" />
- <package id="System.Runtime" version="4.1.0" targetFramework="net461" />
- <package id="System.Runtime.Extensions" version="4.1.0" targetFramework="net461" />
- <package id="System.Runtime.Handles" version="4.0.0" targetFramework="net461" />
- <package id="System.Text.Encoding" version="4.0.10" targetFramework="net461" />
- <package id="System.Threading" version="4.0.10" targetFramework="net461" />
- <package id="System.Threading.Tasks" version="4.0.10" targetFramework="net461" />
- <package id="xunit.abstractions" version="2.0.1-rc2" targetFramework="net461" />
- <package id="xunit.extensibility.core" version="2.2.0-beta2-build3300" targetFramework="net461" />
- <package id="xunit.extensibility.execution" version="2.2.0-beta2-build3300" targetFramework="net461" />
-</packages>
\ No newline at end of file
+++ /dev/null
-# CLR Garbage Collector Performance Tests
-This folder houses both the test framework and test artifacts for performance tests
-targeting the garbage collector. These tests are run using the
-[xunit-performance](https://github.com/Microsoft/xunit-performance) performance testing
-framework and can be used with the standard tools provided by that repository.
-
-The performance tests themselves, as defined in the `Framework` folder in `PerfTests.cs`,
-all invoke one of the test artifacts (as defined in the `Tests` assembly) and collects the duration
-in which the child process runs, as well as a number of other metrics on Windows platforms.
-
-## Building the test framework
-The test framework currently does not build as part of the CoreCLR test build. The
-framework targets Desktop CLR and compiles using msbuild.
-
-The Desktop (DNX46) target of the test framework contains a number of custom metrics that are given
-to `xunit.performance` to evaluate the test run. These metrics provide a number of interesting
-statistics about the performance of the GC, like the duration of the longest pause, the average pause
-durations, and number of garbage collections for each generation.
-
-The CoreCLR (DNXCORE5) target of the test framework consists only of the tests themselves and not
-the metrics. This is because metric definitions have a dependency on TraceEvent, which is itself
-not available currently on CoreCLR. This target is temporarily disabled for now.
-
-## Running the tests on Windows
-Since the Desktop CLR is already installed on Windows machines, we can use the host CLR to
-invoke the `xunit.performance.run` test runner, even if we are testing CoreCLR.
-
-Regardless of whether or not we are testing the Desktop CLR or CoreCLR, we first need to set up
-the coreclr repo by building a build that we will be testing:
-
-```
-build.cmd Release
-tests\runtest.cmd Release GenerateLayoutOnly
-```
-
-Then, we create a temporary directory somewhere on our system and set up all of our dependencies:
-
-```
-mkdir sandbox
-pushd sandbox
-
-REM Get the xunit-performance console runner
-xcopy /sy C:\<path_to_your_coreclr>\coreclr\packages\Microsoft.DotNet.xunit.performance.runner.Windows\1.0.0-alpha-build0035\tools\* .
-
-REM Get the xunit-performance analysis engine
-xcopy /sy C:\<path_to_your_coreclr>\coreclr\packages\Microsoft.DotNet.xunit.performance.analysis\1.0.0-alpha-build0035\tools\* .
-
-REM Get the xunit console runner
-xcopy /sy C:\<path_to_your_coreclr>\coreclr\packages\xunit.runner.console\2.1.0\tools\* .
-
-REM Get the test executables' dependencies
-xcopy /sy C:\<path_to_your_coreclr>\coreclr\bin\tests\Windows_NT.x64.Release\Tests\Core_Root\* .
-
-REM Get the test executables themselves
-for /r C:\<path_to_your_coreclr>\coreclr\bin\tests\Windows_NT.x64.Release\GC\Performance\Tests\ %%f in (*) do xcopy /sy "%%f" .
-
-REM Get the test framework assembly
-xcopy /sy C:\<path_to_your_coreclr>\coreclr\tests\src\GC\Performance\Framework\bin\Release\* .
-
-REM Instruct the framework to 1) run using CoreRun (coreclr) and 2) find CoreRun in the current directory
-REM If not set, the framework will test the currently running Desktop CLR instead.
-set GC_PERF_TEST_CORECLR=1
-set GC_PERF_TEST_CORE_RUN_PROBE_PATH=.
-```
-
-Once all of our dependencies are in the same place, we can run the tests:
-```
-xunit.performance.run.exe GCPerfTestFramework.dll -runner xunit.console.exe -verbose -runid PerformanceTest
-```
-
-The result of this invocation will be `PerformanceTest.etl`, an ETW trace, and `PerformanceTest.xml`, a file
-containing a summary of every test run and the metrics that were calculated for every test iteration. A summary
-XML file can be created using the analysis executable:
-
-```
-xunit.performance.analysis.exe PerformanceTest.xml -xml PerformanceTestSummary.xml
-```
-
-This summary XML only contains test durations and discards all custom metrics.
-
-## Running on other platforms
-The GC performance test framework is temporarily not available on non-Windows platform. It will be brought to
-non-Windows platforms in the very near future!
-
-## Environment Variables
-On Windows, the test runner respects the following environment variables:
-* `GC_PERF_TEST_PROBE_PATH`, a path used to probe for test executables,
-* `GC_PERF_TEST_CORE_RUN_PROBE_PATH`, a path used to probe for the CoreRun executable if running on CoreCLR,
-* `GC_PERF_TEST_CORECLR`, instructs the runner to use CoreCLR (and CoreRun) if set to 1.
-
-On Unixes, the test runner respects the same variables except for the final variable, which is assumed to
-always be 1 on non-Windows platforms.
\ No newline at end of file