diff --git a/src/Common/src/Interop/Windows/NtDll/Interop.NtQuerySystemInformation.cs b/src/Common/src/Interop/Windows/NtDll/Interop.NtQuerySystemInformation.cs index 35f6baa8bd..4226795a25 100644 --- a/src/Common/src/Interop/Windows/NtDll/Interop.NtQuerySystemInformation.cs +++ b/src/Common/src/Interop/Windows/NtDll/Interop.NtQuerySystemInformation.cs @@ -10,7 +10,7 @@ internal partial class Interop internal partial class NtDll { [DllImport(Libraries.NtDll, CharSet = CharSet.Unicode)] - internal static extern int NtQuerySystemInformation(int query, IntPtr dataPtr, int size, out int returnedSize); + internal static unsafe extern int NtQuerySystemInformation(int query, void* dataPtr, int size, out int returnedSize); internal const int NtQuerySystemProcessInformation = 5; internal const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; diff --git a/src/Common/src/Interop/Windows/advapi32/Interop.PERF_INFO.cs b/src/Common/src/Interop/Windows/advapi32/Interop.PERF_INFO.cs index 1add13bbb6..4ff59587b0 100644 --- a/src/Common/src/Interop/Windows/advapi32/Interop.PERF_INFO.cs +++ b/src/Common/src/Interop/Windows/advapi32/Interop.PERF_INFO.cs @@ -2,6 +2,7 @@ // 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.Globalization; using System.Runtime.InteropServices; @@ -10,80 +11,83 @@ internal partial class Interop internal partial class Advapi32 { [StructLayout(LayoutKind.Sequential)] - internal class PERF_COUNTER_BLOCK + internal struct PERF_COUNTER_BLOCK { - internal int ByteLength = 0; + internal int ByteLength; } [StructLayout(LayoutKind.Sequential)] - internal class PERF_COUNTER_DEFINITION + internal struct PERF_COUNTER_DEFINITION { - internal int ByteLength = 0; - internal int CounterNameTitleIndex = 0; - internal int CounterNameTitlePtr = 0; - internal int CounterHelpTitleIndex = 0; - internal int CounterHelpTitlePtr = 0; - internal int DefaultScale = 0; - internal int DetailLevel = 0; - internal int CounterType = 0; - internal int CounterSize = 0; - internal int CounterOffset = 0; + internal int ByteLength; + internal int CounterNameTitleIndex; + internal int CounterNameTitlePtr; + internal int CounterHelpTitleIndex; + internal int CounterHelpTitlePtr; + internal int DefaultScale; + internal int DetailLevel; + internal int CounterType; + internal int CounterSize; + internal int CounterOffset; } [StructLayout(LayoutKind.Sequential)] - internal class PERF_DATA_BLOCK + internal struct PERF_DATA_BLOCK { - internal int Signature1 = 0; - internal int Signature2 = 0; - internal int LittleEndian = 0; - internal int Version = 0; - internal int Revision = 0; - internal int TotalByteLength = 0; - internal int HeaderLength = 0; - internal int NumObjectTypes = 0; - internal int DefaultObject = 0; - internal SYSTEMTIME SystemTime = null; - internal int pad1 = 0; // Need to pad the struct to get quadword alignment for the 'long' after SystemTime - internal long PerfTime = 0; - internal long PerfFreq = 0; - internal long PerfTime100nSec = 0; - internal int SystemNameLength = 0; - internal int SystemNameOffset = 0; + internal int Signature1; + internal int Signature2; + internal int LittleEndian; + internal int Version; + internal int Revision; + internal int TotalByteLength; + internal int HeaderLength; + internal int NumObjectTypes; + internal int DefaultObject; + internal SYSTEMTIME SystemTime; + internal int pad1; // Need to pad the struct to get quadword alignment for the 'long' after SystemTime + internal long PerfTime; + internal long PerfFreq; + internal long PerfTime100nSec; + internal int SystemNameLength; + internal int SystemNameOffset; } [StructLayout(LayoutKind.Sequential)] - internal class PERF_INSTANCE_DEFINITION + internal struct PERF_INSTANCE_DEFINITION { - internal int ByteLength = 0; - internal int ParentObjectTitleIndex = 0; - internal int ParentObjectInstance = 0; - internal int UniqueID = 0; - internal int NameOffset = 0; - internal int NameLength = 0; + internal int ByteLength; + internal int ParentObjectTitleIndex; + internal int ParentObjectInstance; + internal int UniqueID; + internal int NameOffset; + internal int NameLength; + + internal static ReadOnlySpan GetName(in PERF_INSTANCE_DEFINITION instance, ReadOnlySpan data) + => (instance.NameLength == 0) ? default + : MemoryMarshal.Cast(data.Slice(instance.NameOffset, instance.NameLength - sizeof(char))); // NameLength includes the null-terminator } [StructLayout(LayoutKind.Sequential)] - internal class PERF_OBJECT_TYPE + internal struct PERF_OBJECT_TYPE { - internal int TotalByteLength = 0; - internal int DefinitionLength = 0; - internal int HeaderLength = 0; - internal int ObjectNameTitleIndex = 0; - internal int ObjectNameTitlePtr = 0; - internal int ObjectHelpTitleIndex = 0; - internal int ObjectHelpTitlePtr = 0; - internal int DetailLevel = 0; - internal int NumCounters = 0; - internal int DefaultCounter = 0; - internal int NumInstances = 0; - internal int CodePage = 0; - internal long PerfTime = 0; - internal long PerfFreq = 0; + internal int TotalByteLength; + internal int DefinitionLength; + internal int HeaderLength; + internal int ObjectNameTitleIndex; + internal int ObjectNameTitlePtr; + internal int ObjectHelpTitleIndex; + internal int ObjectHelpTitlePtr; + internal int DetailLevel; + internal int NumCounters; + internal int DefaultCounter; + internal int NumInstances; + internal int CodePage; + internal long PerfTime; + internal long PerfFreq; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] [StructLayout(LayoutKind.Sequential)] - internal class SYSTEMTIME + internal struct SYSTEMTIME { internal short wYear; internal short wMonth; diff --git a/src/System.Diagnostics.PerformanceCounter/src/System/Diagnostics/PerformanceCounterLib.cs b/src/System.Diagnostics.PerformanceCounter/src/System/Diagnostics/PerformanceCounterLib.cs index 7c52419dc5..a6b9ed97bc 100644 --- a/src/System.Diagnostics.PerformanceCounter/src/System/Diagnostics/PerformanceCounterLib.cs +++ b/src/System.Diagnostics.PerformanceCounter/src/System/Diagnostics/PerformanceCounterLib.cs @@ -12,6 +12,8 @@ using System.ComponentModel; using Microsoft.Win32; using System.IO; +using static Interop.Advapi32; + namespace System.Diagnostics { internal class PerformanceCounterLib @@ -100,7 +102,11 @@ namespace System.Diagnostics } } - private unsafe Hashtable CategoryTable + // TODO: Replace with https://github.com/dotnet/corefx/issues/30613 + private static ref readonly T AsStruct(ReadOnlySpan span) where T : struct + => ref MemoryMarshal.Cast(span)[0]; + + private Hashtable CategoryTable { get { @@ -110,70 +116,62 @@ namespace System.Diagnostics { if (_categoryTable == null) { - byte[] perfData = GetPerformanceData("Global"); + ReadOnlySpan data = GetPerformanceData("Global"); + + ref readonly PERF_DATA_BLOCK dataBlock = ref AsStruct(data); + int pos = dataBlock.HeaderLength; - fixed (byte* perfDataPtr = perfData) + int numPerfObjects = dataBlock.NumObjectTypes; + + // on some machines MSMQ claims to have 4 categories, even though it only has 2. + // This causes us to walk past the end of our data, potentially crashing or reading + // data we shouldn't. We use dataBlock.TotalByteLength to make sure we don't go past the end + // of the perf data. + Hashtable tempCategoryTable = new Hashtable(numPerfObjects, StringComparer.OrdinalIgnoreCase); + for (int index = 0; index < numPerfObjects && pos < dataBlock.TotalByteLength; index++) { - IntPtr dataRef = new IntPtr((void*)perfDataPtr); - Interop.Advapi32.PERF_DATA_BLOCK dataBlock = new Interop.Advapi32.PERF_DATA_BLOCK(); - Marshal.PtrToStructure(dataRef, dataBlock); - dataRef = (IntPtr)((long)dataRef + dataBlock.HeaderLength); - int categoryNumber = dataBlock.NumObjectTypes; + ref readonly PERF_OBJECT_TYPE perfObject = ref AsStruct(data.Slice(pos)); - // on some machines MSMQ claims to have 4 categories, even though it only has 2. - // This causes us to walk past the end of our data, potentially crashing or reading - // data we shouldn't. We use endPerfData to make sure we don't go past the end - // of the perf data. - long endPerfData = (long)(new IntPtr((void*)perfDataPtr)) + dataBlock.TotalByteLength; - Hashtable tempCategoryTable = new Hashtable(categoryNumber, StringComparer.OrdinalIgnoreCase); - for (int index = 0; index < categoryNumber && ((long)dataRef < endPerfData); index++) + CategoryEntry newCategoryEntry = new CategoryEntry(in perfObject); + int nextPos = pos + perfObject.TotalByteLength; + pos += perfObject.HeaderLength; + + int index3 = 0; + int previousCounterIndex = -1; + //Need to filter out counters that are repeated, some providers might + //return several adyacent copies of the same counter. + for (int index2 = 0; index2 < newCategoryEntry.CounterIndexes.Length; ++index2) { - Interop.Advapi32.PERF_OBJECT_TYPE perfObject = new Interop.Advapi32.PERF_OBJECT_TYPE(); - - Marshal.PtrToStructure(dataRef, perfObject); - - CategoryEntry newCategoryEntry = new CategoryEntry(perfObject); - IntPtr nextRef = (IntPtr)((long)dataRef + perfObject.TotalByteLength); - dataRef = (IntPtr)((long)dataRef + perfObject.HeaderLength); - - int index3 = 0; - int previousCounterIndex = -1; - //Need to filter out counters that are repeated, some providers might - //return several adyacent copies of the same counter. - for (int index2 = 0; index2 < newCategoryEntry.CounterIndexes.Length; ++index2) + ref readonly PERF_COUNTER_DEFINITION perfCounter = ref AsStruct(data.Slice(pos)); + if (perfCounter.CounterNameTitleIndex != previousCounterIndex) { - Interop.Advapi32.PERF_COUNTER_DEFINITION perfCounter = new Interop.Advapi32.PERF_COUNTER_DEFINITION(); - Marshal.PtrToStructure(dataRef, perfCounter); - if (perfCounter.CounterNameTitleIndex != previousCounterIndex) - { - newCategoryEntry.CounterIndexes[index3] = perfCounter.CounterNameTitleIndex; - newCategoryEntry.HelpIndexes[index3] = perfCounter.CounterHelpTitleIndex; - previousCounterIndex = perfCounter.CounterNameTitleIndex; - ++index3; - } - dataRef = (IntPtr)((long)dataRef + perfCounter.ByteLength); + newCategoryEntry.CounterIndexes[index3] = perfCounter.CounterNameTitleIndex; + newCategoryEntry.HelpIndexes[index3] = perfCounter.CounterHelpTitleIndex; + previousCounterIndex = perfCounter.CounterNameTitleIndex; + ++index3; } - - //Lets adjust the entry counter arrays in case there were repeated copies - if (index3 < newCategoryEntry.CounterIndexes.Length) - { - int[] adjustedCounterIndexes = new int[index3]; - int[] adjustedHelpIndexes = new int[index3]; - Array.Copy(newCategoryEntry.CounterIndexes, adjustedCounterIndexes, index3); - Array.Copy(newCategoryEntry.HelpIndexes, adjustedHelpIndexes, index3); - newCategoryEntry.CounterIndexes = adjustedCounterIndexes; - newCategoryEntry.HelpIndexes = adjustedHelpIndexes; - } - - string categoryName = (string)NameTable[newCategoryEntry.NameIndex]; - if (categoryName != null) - tempCategoryTable[categoryName] = newCategoryEntry; - - dataRef = nextRef; + pos += perfCounter.ByteLength; } - _categoryTable = tempCategoryTable; + //Lets adjust the entry counter arrays in case there were repeated copies + if (index3 < newCategoryEntry.CounterIndexes.Length) + { + int[] adjustedCounterIndexes = new int[index3]; + int[] adjustedHelpIndexes = new int[index3]; + Array.Copy(newCategoryEntry.CounterIndexes, adjustedCounterIndexes, index3); + Array.Copy(newCategoryEntry.HelpIndexes, adjustedHelpIndexes, index3); + newCategoryEntry.CounterIndexes = adjustedCounterIndexes; + newCategoryEntry.HelpIndexes = adjustedHelpIndexes; + } + + string categoryName = (string)NameTable[newCategoryEntry.NameIndex]; + if (categoryName != null) + tempCategoryTable[categoryName] = newCategoryEntry; + + pos = nextPos; } + + _categoryTable = tempCategoryTable; } } } @@ -1369,7 +1367,7 @@ namespace System.Diagnostics internal int[] CounterIndexes; internal int[] HelpIndexes; - internal CategoryEntry(Interop.Advapi32.PERF_OBJECT_TYPE perfObject) + internal CategoryEntry(in PERF_OBJECT_TYPE perfObject) { NameIndex = perfObject.ObjectNameTitleIndex; HelpIndex = perfObject.ObjectHelpTitleIndex; @@ -1391,203 +1389,193 @@ namespace System.Diagnostics private CategoryEntry _entry; private PerformanceCounterLib _library; - internal unsafe CategorySample(byte[] data, CategoryEntry entry, PerformanceCounterLib library) + // TODO: Replace with https://github.com/dotnet/corefx/issues/30613 + private static ref readonly T AsStruct(ReadOnlySpan span) where T : struct + => ref MemoryMarshal.Cast(span)[0]; + + internal CategorySample(ReadOnlySpan data, CategoryEntry entry, PerformanceCounterLib library) { _entry = entry; _library = library; int categoryIndex = entry.NameIndex; - Interop.Advapi32.PERF_DATA_BLOCK dataBlock = new Interop.Advapi32.PERF_DATA_BLOCK(); - fixed (byte* dataPtr = data) + + ref readonly PERF_DATA_BLOCK dataBlock = ref AsStruct(data); + + _systemFrequency = dataBlock.PerfFreq; + _timeStamp = dataBlock.PerfTime; + _timeStamp100nSec = dataBlock.PerfTime100nSec; + int pos = dataBlock.HeaderLength; + int numPerfObjects = dataBlock.NumObjectTypes; + if (numPerfObjects == 0) { - IntPtr dataRef = new IntPtr((void*)dataPtr); + _counterTable = new Hashtable(); + _instanceNameTable = new Hashtable(StringComparer.OrdinalIgnoreCase); + return; + } - Marshal.PtrToStructure(dataRef, dataBlock); - _systemFrequency = dataBlock.PerfFreq; - _timeStamp = dataBlock.PerfTime; - _timeStamp100nSec = dataBlock.PerfTime100nSec; - dataRef = (IntPtr)((long)dataRef + dataBlock.HeaderLength); - int numPerfObjects = dataBlock.NumObjectTypes; - if (numPerfObjects == 0) + //Need to find the right category, GetPerformanceData might return + //several of them. + bool foundCategory = false; + for (int index = 0; index < numPerfObjects; index++) + { + ref readonly PERF_OBJECT_TYPE perfObjectType = ref AsStruct(data.Slice(pos)); + + if (perfObjectType.ObjectNameTitleIndex == categoryIndex) { - _counterTable = new Hashtable(); - _instanceNameTable = new Hashtable(StringComparer.OrdinalIgnoreCase); - return; + foundCategory = true; + break; } - //Need to find the right category, GetPerformanceData might return - //several of them. - Interop.Advapi32.PERF_OBJECT_TYPE perfObject = null; - bool foundCategory = false; - for (int index = 0; index < numPerfObjects; index++) + pos += perfObjectType.TotalByteLength; + } + + if (!foundCategory) + throw new InvalidOperationException(SR.Format(SR.CantReadCategoryIndex, categoryIndex.ToString(CultureInfo.CurrentCulture))); + + ref readonly PERF_OBJECT_TYPE perfObject = ref AsStruct(data.Slice(pos)); + + _counterFrequency = perfObject.PerfFreq; + _counterTimeStamp = perfObject.PerfTime; + int counterNumber = perfObject.NumCounters; + int instanceNumber = perfObject.NumInstances; + + if (instanceNumber == -1) + _isMultiInstance = false; + else + _isMultiInstance = true; + + // Move pointer forward to end of PERF_OBJECT_TYPE + pos += perfObject.HeaderLength; + + CounterDefinitionSample[] samples = new CounterDefinitionSample[counterNumber]; + _counterTable = new Hashtable(counterNumber); + for (int index = 0; index < samples.Length; ++index) + { + ref readonly PERF_COUNTER_DEFINITION perfCounter = ref AsStruct(data.Slice(pos)); + samples[index] = new CounterDefinitionSample(in perfCounter, this, instanceNumber); + pos += perfCounter.ByteLength; + + int currentSampleType = samples[index]._counterType; + if (!PerformanceCounterLib.IsBaseCounter(currentSampleType)) { - perfObject = new Interop.Advapi32.PERF_OBJECT_TYPE(); - Marshal.PtrToStructure(dataRef, perfObject); - - if (perfObject.ObjectNameTitleIndex == categoryIndex) - { - foundCategory = true; - break; - } - - dataRef = (IntPtr)((long)dataRef + perfObject.TotalByteLength); + // We'll put only non-base counters in the table. + if (currentSampleType != Interop.Kernel32.PerformanceCounterOptions.PERF_COUNTER_NODATA) + _counterTable[samples[index]._nameIndex] = samples[index]; } - - if (!foundCategory) - throw new InvalidOperationException(SR.Format(SR.CantReadCategoryIndex, categoryIndex.ToString(CultureInfo.CurrentCulture))); - - _counterFrequency = perfObject.PerfFreq; - _counterTimeStamp = perfObject.PerfTime; - int counterNumber = perfObject.NumCounters; - int instanceNumber = perfObject.NumInstances; - - if (instanceNumber == -1) - _isMultiInstance = false; else - _isMultiInstance = true; + { + // it's a base counter, try to hook it up to the main counter. + Debug.Assert(index > 0, "Index > 0 because base counters should never be at index 0"); + if (index > 0) + samples[index - 1]._baseCounterDefinitionSample = samples[index]; + } + } - // Move pointer forward to end of PERF_OBJECT_TYPE - dataRef = (IntPtr)((long)dataRef + perfObject.HeaderLength); + // now set up the InstanceNameTable. + if (!_isMultiInstance) + { + _instanceNameTable = new Hashtable(1, StringComparer.OrdinalIgnoreCase); + _instanceNameTable[PerformanceCounterLib.SingleInstanceName] = 0; - CounterDefinitionSample[] samples = new CounterDefinitionSample[counterNumber]; - _counterTable = new Hashtable(counterNumber); for (int index = 0; index < samples.Length; ++index) { - Interop.Advapi32.PERF_COUNTER_DEFINITION perfCounter = new Interop.Advapi32.PERF_COUNTER_DEFINITION(); - Marshal.PtrToStructure(dataRef, perfCounter); - samples[index] = new CounterDefinitionSample(perfCounter, this, instanceNumber); - dataRef = (IntPtr)((long)dataRef + perfCounter.ByteLength); - - int currentSampleType = samples[index]._counterType; - if (!PerformanceCounterLib.IsBaseCounter(currentSampleType)) - { - // We'll put only non-base counters in the table. - if (currentSampleType != Interop.Kernel32.PerformanceCounterOptions.PERF_COUNTER_NODATA) - _counterTable[samples[index]._nameIndex] = samples[index]; - } - else - { - // it's a base counter, try to hook it up to the main counter. - Debug.Assert(index > 0, "Index > 0 because base counters should never be at index 0"); - if (index > 0) - samples[index - 1]._baseCounterDefinitionSample = samples[index]; - } + samples[index].SetInstanceValue(0, data.Slice(pos)); } - - // now set up the InstanceNameTable. - if (!_isMultiInstance) + } + else + { + string[] parentInstanceNames = null; + _instanceNameTable = new Hashtable(instanceNumber, StringComparer.OrdinalIgnoreCase); + for (int i = 0; i < instanceNumber; i++) { - _instanceNameTable = new Hashtable(1, StringComparer.OrdinalIgnoreCase); - _instanceNameTable[PerformanceCounterLib.SingleInstanceName] = 0; + ref readonly PERF_INSTANCE_DEFINITION perfInstance = ref AsStruct(data.Slice(pos)); + if (perfInstance.ParentObjectTitleIndex > 0 && parentInstanceNames == null) + parentInstanceNames = GetInstanceNamesFromIndex(perfInstance.ParentObjectTitleIndex); + + string instanceName = PERF_INSTANCE_DEFINITION.GetName(in perfInstance, data.Slice(pos)).ToString(); + if (parentInstanceNames != null && perfInstance.ParentObjectInstance >= 0 && perfInstance.ParentObjectInstance < parentInstanceNames.Length - 1) + instanceName = parentInstanceNames[perfInstance.ParentObjectInstance] + "/" + instanceName; + + //In some cases instance names are not unique (Process), same as perfmon + //generate a unique name. + string newInstanceName = instanceName; + int newInstanceNumber = 1; + while (true) + { + if (!_instanceNameTable.ContainsKey(newInstanceName)) + { + _instanceNameTable[newInstanceName] = i; + break; + } + else + { + newInstanceName = instanceName + "#" + newInstanceNumber.ToString(CultureInfo.InvariantCulture); + ++newInstanceNumber; + } + } + + + pos += perfInstance.ByteLength; for (int index = 0; index < samples.Length; ++index) - { - samples[index].SetInstanceValue(0, dataRef); - } - } - else - { - string[] parentInstanceNames = null; - _instanceNameTable = new Hashtable(instanceNumber, StringComparer.OrdinalIgnoreCase); - for (int i = 0; i < instanceNumber; i++) - { - Interop.Advapi32.PERF_INSTANCE_DEFINITION perfInstance = new Interop.Advapi32.PERF_INSTANCE_DEFINITION(); - Marshal.PtrToStructure(dataRef, perfInstance); - if (perfInstance.ParentObjectTitleIndex > 0 && parentInstanceNames == null) - parentInstanceNames = GetInstanceNamesFromIndex(perfInstance.ParentObjectTitleIndex); + samples[index].SetInstanceValue(i, data.Slice(pos)); - string instanceName; - if (parentInstanceNames != null && perfInstance.ParentObjectInstance >= 0 && perfInstance.ParentObjectInstance < parentInstanceNames.Length - 1) - instanceName = parentInstanceNames[perfInstance.ParentObjectInstance] + "/" + Marshal.PtrToStringUni((IntPtr)((long)dataRef + perfInstance.NameOffset)); - else - instanceName = Marshal.PtrToStringUni((IntPtr)((long)dataRef + perfInstance.NameOffset)); - - //In some cases instance names are not unique (Process), same as perfmon - //generate a unique name. - string newInstanceName = instanceName; - int newInstanceNumber = 1; - while (true) - { - if (!_instanceNameTable.ContainsKey(newInstanceName)) - { - _instanceNameTable[newInstanceName] = i; - break; - } - else - { - newInstanceName = instanceName + "#" + newInstanceNumber.ToString(CultureInfo.InvariantCulture); - ++newInstanceNumber; - } - } - - - dataRef = (IntPtr)((long)dataRef + perfInstance.ByteLength); - for (int index = 0; index < samples.Length; ++index) - samples[index].SetInstanceValue(i, dataRef); - - dataRef = (IntPtr)((long)dataRef + Marshal.ReadInt32(dataRef)); - } + pos += AsStruct(data.Slice(pos)).ByteLength; } } } - internal unsafe string[] GetInstanceNamesFromIndex(int categoryIndex) + internal string[] GetInstanceNamesFromIndex(int categoryIndex) { - byte[] data = _library.GetPerformanceData(categoryIndex.ToString(CultureInfo.InvariantCulture)); - fixed (byte* dataPtr = data) + ReadOnlySpan data = _library.GetPerformanceData(categoryIndex.ToString(CultureInfo.InvariantCulture)); + + ref readonly PERF_DATA_BLOCK dataBlock = ref AsStruct(data); + int pos = dataBlock.HeaderLength; + int numPerfObjects = dataBlock.NumObjectTypes; + + bool foundCategory = false; + for (int index = 0; index < numPerfObjects; index++) { - IntPtr dataRef = new IntPtr((void*)dataPtr); + ref readonly PERF_OBJECT_TYPE type = ref AsStruct(data.Slice(pos)); - Interop.Advapi32.PERF_DATA_BLOCK dataBlock = new Interop.Advapi32.PERF_DATA_BLOCK(); - Marshal.PtrToStructure(dataRef, dataBlock); - dataRef = (IntPtr)((long)dataRef + dataBlock.HeaderLength); - int numPerfObjects = dataBlock.NumObjectTypes; - - Interop.Advapi32.PERF_OBJECT_TYPE perfObject = null; - bool foundCategory = false; - for (int index = 0; index < numPerfObjects; index++) + if (type.ObjectNameTitleIndex == categoryIndex) { - perfObject = new Interop.Advapi32.PERF_OBJECT_TYPE(); - Marshal.PtrToStructure(dataRef, perfObject); - - if (perfObject.ObjectNameTitleIndex == categoryIndex) - { - foundCategory = true; - break; - } - - dataRef = (IntPtr)((long)dataRef + perfObject.TotalByteLength); + foundCategory = true; + break; } - if (!foundCategory) - return Array.Empty(); - - int counterNumber = perfObject.NumCounters; - int instanceNumber = perfObject.NumInstances; - dataRef = (IntPtr)((long)dataRef + perfObject.HeaderLength); - - if (instanceNumber == -1) - return Array.Empty(); - - CounterDefinitionSample[] samples = new CounterDefinitionSample[counterNumber]; - for (int index = 0; index < samples.Length; ++index) - { - Interop.Advapi32.PERF_COUNTER_DEFINITION perfCounter = new Interop.Advapi32.PERF_COUNTER_DEFINITION(); - Marshal.PtrToStructure(dataRef, perfCounter); - dataRef = (IntPtr)((long)dataRef + perfCounter.ByteLength); - } - - string[] instanceNames = new string[instanceNumber]; - for (int i = 0; i < instanceNumber; i++) - { - Interop.Advapi32.PERF_INSTANCE_DEFINITION perfInstance = new Interop.Advapi32.PERF_INSTANCE_DEFINITION(); - Marshal.PtrToStructure(dataRef, perfInstance); - instanceNames[i] = Marshal.PtrToStringUni((IntPtr)((long)dataRef + perfInstance.NameOffset)); - dataRef = (IntPtr)((long)dataRef + perfInstance.ByteLength); - dataRef = (IntPtr)((long)dataRef + Marshal.ReadInt32(dataRef)); - } - - return instanceNames; + pos += type.TotalByteLength; } + + if (!foundCategory) + return Array.Empty(); + + ref readonly PERF_OBJECT_TYPE perfObject = ref AsStruct(data.Slice(pos)); + + int counterNumber = perfObject.NumCounters; + int instanceNumber = perfObject.NumInstances; + pos += perfObject.HeaderLength; + + if (instanceNumber == -1) + return Array.Empty(); + + CounterDefinitionSample[] samples = new CounterDefinitionSample[counterNumber]; + for (int index = 0; index < samples.Length; ++index) + { + pos += AsStruct(data.Slice(pos)).ByteLength; + } + + string[] instanceNames = new string[instanceNumber]; + for (int i = 0; i < instanceNumber; i++) + { + ref readonly PERF_INSTANCE_DEFINITION perfInstance = ref AsStruct(data.Slice(pos)); + instanceNames[i] = PERF_INSTANCE_DEFINITION.GetName(in perfInstance, data.Slice(pos)).ToString(); + pos += perfInstance.ByteLength; + + pos += AsStruct(data.Slice(pos)).ByteLength; + } + + return instanceNames; } internal CounterDefinitionSample GetCounterDefinitionSample(string counter) @@ -1657,7 +1645,7 @@ namespace System.Diagnostics private long[] _instanceValues; private CategorySample _categorySample; - internal CounterDefinitionSample(Interop.Advapi32.PERF_COUNTER_DEFINITION perfCounter, CategorySample categorySample, int instanceNumber) + internal CounterDefinitionSample(in PERF_COUNTER_DEFINITION perfCounter, CategorySample categorySample, int instanceNumber) { _nameIndex = perfCounter.CounterNameTitleIndex; _counterType = perfCounter.CounterType; @@ -1673,15 +1661,15 @@ namespace System.Diagnostics _categorySample = categorySample; } - private long ReadValue(IntPtr pointer) + private long ReadValue(ReadOnlySpan data) { if (_size == 4) { - return (long)(uint)Marshal.ReadInt32((IntPtr)((long)pointer + _offset)); + return (long)MemoryMarshal.Read(data.Slice(_offset)); } else if (_size == 8) { - return (long)Marshal.ReadInt64((IntPtr)((long)pointer + _offset)); + return MemoryMarshal.Read(data.Slice(_offset)); } return -1; @@ -1774,9 +1762,9 @@ namespace System.Diagnostics _categorySample._counterTimeStamp); } - internal void SetInstanceValue(int index, IntPtr dataRef) + internal void SetInstanceValue(int index, ReadOnlySpan data) { - long rawValue = ReadValue(dataRef); + long rawValue = ReadValue(data); _instanceValues[index] = rawValue; } } diff --git a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs index 151fa184ab..a2181395d8 100644 --- a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs +++ b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs @@ -735,9 +735,6 @@ namespace System.Diagnostics } finally { -#if FEATURE_TRACESWITCH - Debug.WriteLineIf(_processTracing.TraceVerbose, "Process - CloseHandle(processToken)"); -#endif if (hToken != null) { hToken.Dispose(); @@ -753,15 +750,6 @@ namespace System.Diagnostics /// private SafeProcessHandle GetProcessHandle(int access, bool throwIfExited) { -#if FEATURE_TRACESWITCH - Debug.WriteLineIf(_processTracing.TraceVerbose, "GetProcessHandle(access = 0x" + access.ToString("X8", CultureInfo.InvariantCulture) + ", throwIfExited = " + throwIfExited + ")"); -#if DEBUG - if (_processTracing.TraceVerbose) { - StackFrame calledFrom = new StackTrace(true).GetFrame(0); - Debug.WriteLine(" called from " + calledFrom.GetFileName() + ", line " + calledFrom.GetFileLineNumber()); - } -#endif -#endif if (_haveProcessHandle) { if (throwIfExited) diff --git a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.cs b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.cs index bd6b873979..c881531922 100644 --- a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.cs +++ b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.cs @@ -80,14 +80,6 @@ namespace System.Diagnostics internal AsyncStreamReader _error; internal bool _pendingOutputRead; internal bool _pendingErrorRead; -#if FEATURE_TRACESWITCH - internal static TraceSwitch _processTracing = -#if DEBUG - new TraceSwitch("processTracing", "Controls debug output from Process component"); -#else - null; -#endif -#endif /// /// @@ -853,9 +845,6 @@ namespace System.Diagnostics // This sets _waitHandle to null which causes CompletionCallback to not emit events. StopWatchingForExit(); } -#if FEATURE_TRACESWITCH - Debug.WriteLineIf(_processTracing.TraceVerbose, "Process - CloseHandle(process) in Close()"); -#endif _processHandle.Dispose(); _processHandle = null; _haveProcessHandle = false; @@ -1067,18 +1056,6 @@ namespace System.Diagnostics ProcessInfo processInfo = processInfos[i]; processes[i] = new Process(machineName, isRemoteMachine, processInfo.ProcessId, processInfo); } -#if FEATURE_TRACESWITCH - Debug.WriteLineIf(_processTracing.TraceVerbose, "Process.GetProcesses(" + machineName + ")"); -#if DEBUG - if (_processTracing.TraceVerbose) { - Debug.Indent(); - for (int i = 0; i < processInfos.Length; i++) { - Debug.WriteLine(processes[i].Id + ": " + processes[i].ProcessName); - } - Debug.Unindent(); - } -#endif -#endif return processes; } diff --git a/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Uap.cs b/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Uap.cs index a9b22ca5a9..3b6dddffaf 100644 --- a/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Uap.cs +++ b/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Uap.cs @@ -75,7 +75,7 @@ namespace System.Diagnostics } } - internal static partial class NtProcessInfoHelper + internal static class NtProcessInfoHelper { internal static ProcessInfo[] GetProcessInfos(Predicate processIdFilter = null) { diff --git a/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Win32.cs b/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Win32.cs index 96e6c46d61..3df29bcb60 100644 --- a/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Win32.cs +++ b/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Win32.cs @@ -227,9 +227,6 @@ namespace System.Diagnostics } finally { -#if FEATURE_TRACESWITCH - Debug.WriteLineIf(Process._processTracing.TraceVerbose, "Process - CloseHandle(process)"); -#endif if (!processHandle.IsInvalid) { processHandle.Dispose(); @@ -238,18 +235,24 @@ namespace System.Diagnostics } } - internal static partial class NtProcessInfoHelper + internal static class NtProcessInfoHelper { // Cache a single buffer for use in GetProcessInfos(). private static long[] CachedBuffer; + // Use a smaller buffer size on debug to ensure we hit the retry path. +#if DEBUG + private const int DefaultCachedBufferSize = 1024; +#else + private const int DefaultCachedBufferSize = 128 * 1024; +#endif + internal static ProcessInfo[] GetProcessInfos(Predicate processIdFilter = null) { int requiredSize = 0; int status; ProcessInfo[] processInfos; - GCHandle bufferHandle = new GCHandle(); // Start with the default buffer size. int bufferSize = DefaultCachedBufferSize; @@ -272,17 +275,20 @@ namespace System.Diagnostics bufferSize = buffer.Length * sizeof(long); } - bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); - - status = Interop.NtDll.NtQuerySystemInformation( - Interop.NtDll.NtQuerySystemProcessInformation, - bufferHandle.AddrOfPinnedObject(), - bufferSize, - out requiredSize); + unsafe + { + fixed (long* bufferPtr = buffer) + { + status = Interop.NtDll.NtQuerySystemInformation( + Interop.NtDll.NtQuerySystemProcessInformation, + bufferPtr, + bufferSize, + out requiredSize); + } + } if (unchecked((uint)status) == Interop.NtDll.STATUS_INFO_LENGTH_MISMATCH) { - if (bufferHandle.IsAllocated) bufferHandle.Free(); buffer = null; bufferSize = GetNewBufferSize(bufferSize, requiredSize); } @@ -294,17 +300,236 @@ namespace System.Diagnostics } // Parse the data block to get process information - processInfos = GetProcessInfos(bufferHandle.AddrOfPinnedObject(), processIdFilter); + processInfos = GetProcessInfos(MemoryMarshal.AsBytes(buffer), processIdFilter); } finally { // Cache the final buffer for use on the next call. Interlocked.Exchange(ref CachedBuffer, buffer); - - if (bufferHandle.IsAllocated) bufferHandle.Free(); } return processInfos; } + + private static int GetNewBufferSize(int existingBufferSize, int requiredSize) + { + if (requiredSize == 0) + { + // + // On some old OS like win2000, requiredSize will not be set if the buffer + // passed to NtQuerySystemInformation is not enough. + // + int newSize = existingBufferSize * 2; + if (newSize < existingBufferSize) + { + // In reality, we should never overflow. + // Adding the code here just in case it happens. + throw new OutOfMemoryException(); + } + return newSize; + } + else + { + // allocating a few more kilo bytes just in case there are some new process + // kicked in since new call to NtQuerySystemInformation + int newSize = requiredSize + 1024 * 10; + if (newSize < requiredSize) + { + throw new OutOfMemoryException(); + } + return newSize; + } + } + + // TODO: Replace with https://github.com/dotnet/corefx/issues/30613 + private static ref readonly T AsStruct(ReadOnlySpan span) where T : struct + => ref MemoryMarshal.Cast(span)[0]; + + private static unsafe ProcessInfo[] GetProcessInfos(ReadOnlySpan data, Predicate processIdFilter) + { + // Use a dictionary to avoid duplicate entries if any + // 60 is a reasonable number for processes on a normal machine. + Dictionary processInfos = new Dictionary(60); + + int processInformationOffset = 0; + + while (true) + { + ref readonly SystemProcessInformation pi = ref AsStruct(data.Slice(processInformationOffset)); + + // Process ID shouldn't overflow. OS API GetCurrentProcessID returns DWORD. + int processInfoProcessId = pi.UniqueProcessId.ToInt32(); + if (processIdFilter == null || processIdFilter(processInfoProcessId)) + { + // get information for a process + ProcessInfo processInfo = new ProcessInfo(); + processInfo.ProcessId = processInfoProcessId; + processInfo.SessionId = (int)pi.SessionId; + processInfo.PoolPagedBytes = (long)pi.QuotaPagedPoolUsage; + processInfo.PoolNonPagedBytes = (long)pi.QuotaNonPagedPoolUsage; + processInfo.VirtualBytes = (long)pi.VirtualSize; + processInfo.VirtualBytesPeak = (long)pi.PeakVirtualSize; + processInfo.WorkingSetPeak = (long)pi.PeakWorkingSetSize; + processInfo.WorkingSet = (long)pi.WorkingSetSize; + processInfo.PageFileBytesPeak = (long)pi.PeakPagefileUsage; + processInfo.PageFileBytes = (long)pi.PagefileUsage; + processInfo.PrivateBytes = (long)pi.PrivatePageCount; + processInfo.BasePriority = pi.BasePriority; + processInfo.HandleCount = (int)pi.HandleCount; + + if (pi.ImageName.Buffer == IntPtr.Zero) + { + if (processInfo.ProcessId == NtProcessManager.SystemProcessID) + { + processInfo.ProcessName = "System"; + } + else if (processInfo.ProcessId == NtProcessManager.IdleProcessID) + { + processInfo.ProcessName = "Idle"; + } + else + { + // for normal process without name, using the process ID. + processInfo.ProcessName = processInfo.ProcessId.ToString(CultureInfo.InvariantCulture); + } + } + else + { + string processName = GetProcessShortName(Marshal.PtrToStringUni(pi.ImageName.Buffer, pi.ImageName.Length / sizeof(char))); + processInfo.ProcessName = processName; + } + + // get the threads for current process + processInfos[processInfo.ProcessId] = processInfo; + + int threadInformationOffset = processInformationOffset + sizeof(SystemProcessInformation); + for (int i = 0; i < pi.NumberOfThreads; i++) + { + ref readonly SystemThreadInformation ti = ref AsStruct(data.Slice(threadInformationOffset)); + + ThreadInfo threadInfo = new ThreadInfo(); + + threadInfo._processId = (int)ti.ClientId.UniqueProcess; + threadInfo._threadId = (ulong)ti.ClientId.UniqueThread; + threadInfo._basePriority = ti.BasePriority; + threadInfo._currentPriority = ti.Priority; + threadInfo._startAddress = ti.StartAddress; + threadInfo._threadState = (ThreadState)ti.ThreadState; + threadInfo._threadWaitReason = NtProcessManager.GetThreadWaitReason((int)ti.WaitReason); + + processInfo._threadInfoList.Add(threadInfo); + + threadInformationOffset += sizeof(SystemThreadInformation); + } + } + + if (pi.NextEntryOffset == 0) + { + break; + } + processInformationOffset += (int)pi.NextEntryOffset; + } + + ProcessInfo[] temp = new ProcessInfo[processInfos.Values.Count]; + processInfos.Values.CopyTo(temp, 0); + return temp; + } + + // This function generates the short form of process name. + // + // This is from GetProcessShortName in NT code base. + // Check base\screg\winreg\perfdlls\process\perfsprc.c for details. + internal static string GetProcessShortName(String name) + { + if (String.IsNullOrEmpty(name)) + { + return String.Empty; + } + + int slash = -1; + int period = -1; + + for (int i = 0; i < name.Length; i++) + { + if (name[i] == '\\') + slash = i; + else if (name[i] == '.') + period = i; + } + + if (period == -1) + period = name.Length - 1; // set to end of string + else + { + // if a period was found, then see if the extension is + // .EXE, if so drop it, if not, then use end of string + // (i.e. include extension in name) + ReadOnlySpan extension = name.AsSpan(period); + + if (extension.Equals(".exe", StringComparison.OrdinalIgnoreCase)) + period--; // point to character before period + else + period = name.Length - 1; // set to end of string + } + + if (slash == -1) + slash = 0; // set to start of string + else + slash++; // point to character next to slash + + // copy characters between period (or end of string) and + // slash (or start of string) to make image name + return name.Substring(slash, period - slash + 1); + } + + // native struct defined in ntexapi.h + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct SystemProcessInformation + { + internal uint NextEntryOffset; + internal uint NumberOfThreads; + private fixed byte Reserved1[48]; + internal Interop.UNICODE_STRING ImageName; + internal int BasePriority; + internal IntPtr UniqueProcessId; + private UIntPtr Reserved2; + internal uint HandleCount; + internal uint SessionId; + private UIntPtr Reserved3; + internal UIntPtr PeakVirtualSize; // SIZE_T + internal UIntPtr VirtualSize; + private uint Reserved4; + internal UIntPtr PeakWorkingSetSize; // SIZE_T + internal UIntPtr WorkingSetSize; // SIZE_T + private UIntPtr Reserved5; + internal UIntPtr QuotaPagedPoolUsage; // SIZE_T + private UIntPtr Reserved6; + internal UIntPtr QuotaNonPagedPoolUsage; // SIZE_T + internal UIntPtr PagefileUsage; // SIZE_T + internal UIntPtr PeakPagefileUsage; // SIZE_T + internal UIntPtr PrivatePageCount; // SIZE_T + private fixed long Reserved7[6]; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct SystemThreadInformation + { + private fixed long Reserved1[3]; + private uint Reserved2; + internal IntPtr StartAddress; + internal CLIENT_ID ClientId; + internal int Priority; + internal int BasePriority; + private uint Reserved3; + internal uint ThreadState; + internal uint WaitReason; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CLIENT_ID + { + internal IntPtr UniqueProcess; + internal IntPtr UniqueThread; + } } } diff --git a/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs b/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs index cb282f1aa9..86b252201b 100644 --- a/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs +++ b/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs @@ -9,6 +9,8 @@ using System.Runtime.InteropServices; using System.Threading; using Microsoft.Win32.SafeHandles; +using static Interop.Advapi32; + namespace System.Diagnostics { internal static partial class ProcessManager @@ -32,7 +34,7 @@ namespace System.Diagnostics // Otherwise enumerate all processes and compare ids if (!IsRemoteMachine(machineName)) { - using (SafeProcessHandle processHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION, false, processId)) + using (SafeProcessHandle processHandle = Interop.Kernel32.OpenProcess(ProcessOptions.PROCESS_QUERY_INFORMATION, false, processId)) { if (!processHandle.IsInvalid) { @@ -349,7 +351,7 @@ namespace System.Diagnostics // On the next call to GetProcessInfos, we'd have to load them all up again, which is SLOW! } - static ProcessInfo[] GetProcessInfos(PerformanceCounterLib library) + private static ProcessInfo[] GetProcessInfos(PerformanceCounterLib library) { ProcessInfo[] processInfos; @@ -376,117 +378,102 @@ namespace System.Diagnostics return processInfos; } - static ProcessInfo[] GetProcessInfos(PerformanceCounterLib library, int processIndex, int threadIndex, byte[] data) + // TODO: Replace with https://github.com/dotnet/corefx/issues/30613 + private static ref readonly T AsStruct(ReadOnlySpan span) where T:struct + => ref MemoryMarshal.Cast(span)[0]; + + private static ProcessInfo[] GetProcessInfos(PerformanceCounterLib library, int processIndex, int threadIndex, ReadOnlySpan data) { -#if FEATURE_TRACESWITCH - Debug.WriteLineIf(Process._processTracing.TraceVerbose, "GetProcessInfos()"); -#endif Dictionary processInfos = new Dictionary(); List threadInfos = new List(); - GCHandle dataHandle = new GCHandle(); - try + ref readonly PERF_DATA_BLOCK dataBlock = ref AsStruct(data); + + int typePos = dataBlock.HeaderLength; + for (int i = 0; i < dataBlock.NumObjectTypes; i++) { - dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned); - IntPtr dataBlockPtr = dataHandle.AddrOfPinnedObject(); - Interop.Advapi32.PERF_DATA_BLOCK dataBlock = new Interop.Advapi32.PERF_DATA_BLOCK(); - Marshal.PtrToStructure(dataBlockPtr, dataBlock); - IntPtr typePtr = (IntPtr)((long)dataBlockPtr + dataBlock.HeaderLength); - Interop.Advapi32.PERF_INSTANCE_DEFINITION instance = new Interop.Advapi32.PERF_INSTANCE_DEFINITION(); - Interop.Advapi32.PERF_COUNTER_BLOCK counterBlock = new Interop.Advapi32.PERF_COUNTER_BLOCK(); - for (int i = 0; i < dataBlock.NumObjectTypes; i++) + ref readonly PERF_OBJECT_TYPE type = ref AsStruct(data.Slice(typePos)); + + PERF_COUNTER_DEFINITION[] counters = new PERF_COUNTER_DEFINITION[type.NumCounters]; + + int counterPos = typePos + type.HeaderLength; + for (int j = 0; j < type.NumCounters; j++) { - Interop.Advapi32.PERF_OBJECT_TYPE type = new Interop.Advapi32.PERF_OBJECT_TYPE(); - Marshal.PtrToStructure(typePtr, type); - IntPtr instancePtr = (IntPtr)((long)typePtr + type.DefinitionLength); - IntPtr counterPtr = (IntPtr)((long)typePtr + type.HeaderLength); - List counterList = new List(); + ref readonly PERF_COUNTER_DEFINITION counter = ref AsStruct(data.Slice(counterPos)); - for (int j = 0; j < type.NumCounters; j++) + string counterName = library.GetCounterName(counter.CounterNameTitleIndex); + + counters[j] = counter; + if (type.ObjectNameTitleIndex == processIndex) + counters[j].CounterNameTitlePtr = (int)GetValueId(counterName); + else if (type.ObjectNameTitleIndex == threadIndex) + counters[j].CounterNameTitlePtr = (int)GetValueId(counterName); + + counterPos += counter.ByteLength; + } + + int instancePos = typePos + type.DefinitionLength; + for (int j = 0; j < type.NumInstances; j++) + { + ref readonly PERF_INSTANCE_DEFINITION instance = ref AsStruct(data.Slice(instancePos)); + + ReadOnlySpan instanceName = PERF_INSTANCE_DEFINITION.GetName(in instance, data.Slice(instancePos)); + + if (instanceName.Equals("_Total", StringComparison.Ordinal)) { - Interop.Advapi32.PERF_COUNTER_DEFINITION counter = new Interop.Advapi32.PERF_COUNTER_DEFINITION(); - Marshal.PtrToStructure(counterPtr, counter); - string counterName = library.GetCounterName(counter.CounterNameTitleIndex); - - if (type.ObjectNameTitleIndex == processIndex) - counter.CounterNameTitlePtr = (int)GetValueId(counterName); - else if (type.ObjectNameTitleIndex == threadIndex) - counter.CounterNameTitlePtr = (int)GetValueId(counterName); - counterList.Add(counter); - counterPtr = (IntPtr)((long)counterPtr + counter.ByteLength); + // continue } - - Interop.Advapi32.PERF_COUNTER_DEFINITION[] counters = counterList.ToArray(); - - for (int j = 0; j < type.NumInstances; j++) + else if (type.ObjectNameTitleIndex == processIndex) { - Marshal.PtrToStructure(instancePtr, instance); - IntPtr namePtr = (IntPtr)((long)instancePtr + instance.NameOffset); - string instanceName = Marshal.PtrToStringUni(namePtr); - if (instanceName.Equals("_Total")) continue; - IntPtr counterBlockPtr = (IntPtr)((long)instancePtr + instance.ByteLength); - Marshal.PtrToStructure(counterBlockPtr, counterBlock); - if (type.ObjectNameTitleIndex == processIndex) + ProcessInfo processInfo = GetProcessInfo(in type, data.Slice(instancePos + instance.ByteLength), counters); + if (processInfo.ProcessId == 0 && !instanceName.Equals("Idle", StringComparison.OrdinalIgnoreCase)) { - ProcessInfo processInfo = GetProcessInfo(type, (IntPtr)((long)instancePtr + instance.ByteLength), counters); - if (processInfo.ProcessId == 0 && !string.Equals(instanceName, "Idle", StringComparison.OrdinalIgnoreCase)) + // Sometimes we'll get a process structure that is not completely filled in. + // We can catch some of these by looking for non-"idle" processes that have id 0 + // and ignoring those. + } + else + { + if (processInfos.ContainsKey(processInfo.ProcessId)) { - // Sometimes we'll get a process structure that is not completely filled in. - // We can catch some of these by looking for non-"idle" processes that have id 0 - // and ignoring those. -#if FEATURE_TRACESWITCH - Debug.WriteLineIf(Process._processTracing.TraceVerbose, "GetProcessInfos() - found a non-idle process with id 0; ignoring."); -#endif + // We've found two entries in the perfcounters that claim to be the + // same process. We throw an exception. Is this really going to be + // helpful to the user? Should we just ignore? } else { - if (processInfos.ContainsKey(processInfo.ProcessId)) + // the performance counters keep a 15 character prefix of the exe name, and then delete the ".exe", + // if it's in the first 15. The problem is that sometimes that will leave us with part of ".exe" + // at the end. If instanceName ends in ".", ".e", or ".ex" we remove it. + if (instanceName.Length == 15) { - // We've found two entries in the perfcounters that claim to be the - // same process. We throw an exception. Is this really going to be - // helpful to the user? Should we just ignore? -#if FEATURE_TRACESWITCH - Debug.WriteLineIf(Process._processTracing.TraceVerbose, "GetProcessInfos() - found a duplicate process id"); -#endif - } - else - { - // the performance counters keep a 15 character prefix of the exe name, and then delete the ".exe", - // if it's in the first 15. The problem is that sometimes that will leave us with part of ".exe" - // at the end. If instanceName ends in ".", ".e", or ".ex" we remove it. - string processName = instanceName; - if (processName.Length == 15) - { - if (instanceName.EndsWith(".", StringComparison.Ordinal)) processName = instanceName.Substring(0, 14); - else if (instanceName.EndsWith(".e", StringComparison.Ordinal)) processName = instanceName.Substring(0, 13); - else if (instanceName.EndsWith(".ex", StringComparison.Ordinal)) processName = instanceName.Substring(0, 12); - } - processInfo.ProcessName = processName; - processInfos.Add(processInfo.ProcessId, processInfo); + if (instanceName.EndsWith(".", StringComparison.Ordinal)) instanceName = instanceName.Slice(0, 14); + else if (instanceName.EndsWith(".e", StringComparison.Ordinal)) instanceName = instanceName.Slice(0, 13); + else if (instanceName.EndsWith(".ex", StringComparison.Ordinal)) instanceName = instanceName.Slice(0, 12); } + processInfo.ProcessName = instanceName.ToString(); + processInfos.Add(processInfo.ProcessId, processInfo); } } - else if (type.ObjectNameTitleIndex == threadIndex) - { - ThreadInfo threadInfo = GetThreadInfo(type, (IntPtr)((long)instancePtr + instance.ByteLength), counters); - if (threadInfo._threadId != 0) threadInfos.Add(threadInfo); - } - instancePtr = (IntPtr)((long)instancePtr + instance.ByteLength + counterBlock.ByteLength); + } + else if (type.ObjectNameTitleIndex == threadIndex) + { + ThreadInfo threadInfo = GetThreadInfo(in type, data.Slice(instancePos + instance.ByteLength), counters); + if (threadInfo._threadId != 0) threadInfos.Add(threadInfo); } - typePtr = (IntPtr)((long)typePtr + type.TotalByteLength); + instancePos += instance.ByteLength; + + instancePos += AsStruct(data.Slice(instancePos)).ByteLength; } - } - finally - { - if (dataHandle.IsAllocated) dataHandle.Free(); + + typePos += type.TotalByteLength; } for (int i = 0; i < threadInfos.Count; i++) { - ThreadInfo threadInfo = (ThreadInfo)threadInfos[i]; - ProcessInfo processInfo; - if (processInfos.TryGetValue(threadInfo._processId, out processInfo)) + ThreadInfo threadInfo = threadInfos[i]; + if (processInfos.TryGetValue(threadInfo._processId, out ProcessInfo processInfo)) { processInfo._threadInfoList.Add(threadInfo); } @@ -497,13 +484,13 @@ namespace System.Diagnostics return temp; } - static ThreadInfo GetThreadInfo(Interop.Advapi32.PERF_OBJECT_TYPE type, IntPtr instancePtr, Interop.Advapi32.PERF_COUNTER_DEFINITION[] counters) + private static ThreadInfo GetThreadInfo(in PERF_OBJECT_TYPE type, ReadOnlySpan instanceData, PERF_COUNTER_DEFINITION[] counters) { ThreadInfo threadInfo = new ThreadInfo(); for (int i = 0; i < counters.Length; i++) { - Interop.Advapi32.PERF_COUNTER_DEFINITION counter = counters[i]; - long value = ReadCounterValue(counter.CounterType, (IntPtr)((long)instancePtr + counter.CounterOffset)); + PERF_COUNTER_DEFINITION counter = counters[i]; + long value = ReadCounterValue(counter.CounterType, instanceData.Slice(counter.CounterOffset)); switch ((ValueId)counter.CounterNameTitlePtr) { case ValueId.ProcessId: @@ -561,13 +548,13 @@ namespace System.Diagnostics } } - static ProcessInfo GetProcessInfo(Interop.Advapi32.PERF_OBJECT_TYPE type, IntPtr instancePtr, Interop.Advapi32.PERF_COUNTER_DEFINITION[] counters) + private static ProcessInfo GetProcessInfo(in PERF_OBJECT_TYPE type, ReadOnlySpan instanceData, PERF_COUNTER_DEFINITION[] counters) { ProcessInfo processInfo = new ProcessInfo(); for (int i = 0; i < counters.Length; i++) { - Interop.Advapi32.PERF_COUNTER_DEFINITION counter = counters[i]; - long value = ReadCounterValue(counter.CounterType, (IntPtr)((long)instancePtr + counter.CounterOffset)); + PERF_COUNTER_DEFINITION counter = counters[i]; + long value = ReadCounterValue(counter.CounterType, instanceData.Slice(counter.CounterOffset)); switch ((ValueId)counter.CounterNameTitlePtr) { case ValueId.ProcessId: @@ -605,13 +592,13 @@ namespace System.Diagnostics break; case ValueId.HandleCount: processInfo.HandleCount = (int)value; - break; + break; } } return processInfo; } - static ValueId GetValueId(string counterName) + private static ValueId GetValueId(string counterName) { if (counterName != null) { @@ -623,18 +610,18 @@ namespace System.Diagnostics return ValueId.Unknown; } - static long ReadCounterValue(int counterType, IntPtr dataPtr) + private static long ReadCounterValue(int counterType, ReadOnlySpan data) { - if ((counterType & Interop.Advapi32.PerfCounterOptions.NtPerfCounterSizeLarge) != 0) - return Marshal.ReadInt64(dataPtr); + if ((counterType & PerfCounterOptions.NtPerfCounterSizeLarge) != 0) + return MemoryMarshal.Read(data); else - return (long)Marshal.ReadInt32(dataPtr); + return (long)MemoryMarshal.Read(data); } enum ValueId { Unknown = -1, - HandleCount, + HandleCount, PoolPagedBytes, PoolNonpagedBytes, ElapsedTime, @@ -656,235 +643,4 @@ namespace System.Diagnostics ThreadWaitReason } } - - internal static partial class NtProcessInfoHelper - { - private static int GetNewBufferSize(int existingBufferSize, int requiredSize) - { - if (requiredSize == 0) - { - // - // On some old OS like win2000, requiredSize will not be set if the buffer - // passed to NtQuerySystemInformation is not enough. - // - int newSize = existingBufferSize * 2; - if (newSize < existingBufferSize) - { - // In reality, we should never overflow. - // Adding the code here just in case it happens. - throw new OutOfMemoryException(); - } - return newSize; - } - else - { - // allocating a few more kilo bytes just in case there are some new process - // kicked in since new call to NtQuerySystemInformation - int newSize = requiredSize + 1024 * 10; - if (newSize < requiredSize) - { - throw new OutOfMemoryException(); - } - return newSize; - } - } - - // Use a smaller buffer size on debug to ensure we hit the retry path. -#if DEBUG - private const int DefaultCachedBufferSize = 1024; -#else - private const int DefaultCachedBufferSize = 128 * 1024; -#endif - - private static unsafe ProcessInfo[] GetProcessInfos(IntPtr dataPtr, Predicate processIdFilter) - { - // Use a dictionary to avoid duplicate entries if any - // 60 is a reasonable number for processes on a normal machine. - Dictionary processInfos = new Dictionary(60); - - long totalOffset = 0; - - while (true) - { - IntPtr currentPtr = (IntPtr)((long)dataPtr + totalOffset); - ref SystemProcessInformation pi = ref *(SystemProcessInformation *)(currentPtr); - - // Process ID shouldn't overflow. OS API GetCurrentProcessID returns DWORD. - var processInfoProcessId = pi.UniqueProcessId.ToInt32(); - if (processIdFilter == null || processIdFilter(processInfoProcessId)) - { - // get information for a process - ProcessInfo processInfo = new ProcessInfo(); - processInfo.ProcessId = processInfoProcessId; - processInfo.SessionId = (int)pi.SessionId; - processInfo.PoolPagedBytes = (long)pi.QuotaPagedPoolUsage; - processInfo.PoolNonPagedBytes = (long)pi.QuotaNonPagedPoolUsage; - processInfo.VirtualBytes = (long)pi.VirtualSize; - processInfo.VirtualBytesPeak = (long)pi.PeakVirtualSize; - processInfo.WorkingSetPeak = (long)pi.PeakWorkingSetSize; - processInfo.WorkingSet = (long)pi.WorkingSetSize; - processInfo.PageFileBytesPeak = (long)pi.PeakPagefileUsage; - processInfo.PageFileBytes = (long)pi.PagefileUsage; - processInfo.PrivateBytes = (long)pi.PrivatePageCount; - processInfo.BasePriority = pi.BasePriority; - processInfo.HandleCount = (int)pi.HandleCount; - - if (pi.ImageName.Buffer == IntPtr.Zero) - { - if (processInfo.ProcessId == NtProcessManager.SystemProcessID) - { - processInfo.ProcessName = "System"; - } - else if (processInfo.ProcessId == NtProcessManager.IdleProcessID) - { - processInfo.ProcessName = "Idle"; - } - else - { - // for normal process without name, using the process ID. - processInfo.ProcessName = processInfo.ProcessId.ToString(CultureInfo.InvariantCulture); - } - } - else - { - string processName = GetProcessShortName(Marshal.PtrToStringUni(pi.ImageName.Buffer, pi.ImageName.Length / sizeof(char))); - processInfo.ProcessName = processName; - } - - // get the threads for current process - processInfos[processInfo.ProcessId] = processInfo; - - currentPtr = (IntPtr)((long)currentPtr + sizeof(SystemProcessInformation)); - int i = 0; - while (i < pi.NumberOfThreads) - { - ref SystemThreadInformation ti = ref *(SystemThreadInformation *)(currentPtr); - ThreadInfo threadInfo = new ThreadInfo(); - - threadInfo._processId = (int)ti.ClientId.UniqueProcess; - threadInfo._threadId = (ulong)ti.ClientId.UniqueThread; - threadInfo._basePriority = ti.BasePriority; - threadInfo._currentPriority = ti.Priority; - threadInfo._startAddress = ti.StartAddress; - threadInfo._threadState = (ThreadState)ti.ThreadState; - threadInfo._threadWaitReason = NtProcessManager.GetThreadWaitReason((int)ti.WaitReason); - - processInfo._threadInfoList.Add(threadInfo); - currentPtr = (IntPtr)((long)currentPtr + sizeof(SystemThreadInformation)); - i++; - } - } - - if (pi.NextEntryOffset == 0) - { - break; - } - totalOffset += pi.NextEntryOffset; - } - - ProcessInfo[] temp = new ProcessInfo[processInfos.Values.Count]; - processInfos.Values.CopyTo(temp, 0); - return temp; - } - - // This function generates the short form of process name. - // - // This is from GetProcessShortName in NT code base. - // Check base\screg\winreg\perfdlls\process\perfsprc.c for details. - internal static string GetProcessShortName(String name) - { - if (String.IsNullOrEmpty(name)) - { -#if FEATURE_TRACESWITCH - Debug.WriteLineIf(Process._processTracing.TraceVerbose, "GetProcessInfos() - unexpected blank ProcessName"); -#endif - return String.Empty; - } - - int slash = -1; - int period = -1; - - for (int i = 0; i < name.Length; i++) - { - if (name[i] == '\\') - slash = i; - else if (name[i] == '.') - period = i; - } - - if (period == -1) - period = name.Length - 1; // set to end of string - else - { - // if a period was found, then see if the extension is - // .EXE, if so drop it, if not, then use end of string - // (i.e. include extension in name) - ReadOnlySpan extension = name.AsSpan(period); - - if (extension.Equals(".exe", StringComparison.OrdinalIgnoreCase)) - period--; // point to character before period - else - period = name.Length - 1; // set to end of string - } - - if (slash == -1) - slash = 0; // set to start of string - else - slash++; // point to character next to slash - - // copy characters between period (or end of string) and - // slash (or start of string) to make image name - return name.Substring(slash, period - slash + 1); - } - - // native struct defined in ntexapi.h - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct SystemProcessInformation - { - internal uint NextEntryOffset; - internal uint NumberOfThreads; - private fixed byte Reserved1[48]; - internal Interop.UNICODE_STRING ImageName; - internal int BasePriority; - internal IntPtr UniqueProcessId; - private UIntPtr Reserved2; - internal uint HandleCount; - internal uint SessionId; - private UIntPtr Reserved3; - internal UIntPtr PeakVirtualSize; // SIZE_T - internal UIntPtr VirtualSize; - private uint Reserved4; - internal UIntPtr PeakWorkingSetSize; // SIZE_T - internal UIntPtr WorkingSetSize; // SIZE_T - private UIntPtr Reserved5; - internal UIntPtr QuotaPagedPoolUsage; // SIZE_T - private UIntPtr Reserved6; - internal UIntPtr QuotaNonPagedPoolUsage; // SIZE_T - internal UIntPtr PagefileUsage; // SIZE_T - internal UIntPtr PeakPagefileUsage; // SIZE_T - internal UIntPtr PrivatePageCount; // SIZE_T - private fixed long Reserved7[6]; - } - - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct SystemThreadInformation - { - private fixed long Reserved1[3]; - private uint Reserved2; - internal IntPtr StartAddress; - internal CLIENT_ID ClientId; - internal int Priority; - internal int BasePriority; - private uint Reserved3; - internal uint ThreadState; - internal uint WaitReason; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct CLIENT_ID - { - internal IntPtr UniqueProcess; - internal IntPtr UniqueThread; - } - } } diff --git a/src/System.Net.Ping/src/System/Net/NetworkInformation/Ping.Unix.cs b/src/System.Net.Ping/src/System/Net/NetworkInformation/Ping.Unix.cs index 58f5a49603..6feedb0899 100644 --- a/src/System.Net.Ping/src/System/Net/NetworkInformation/Ping.Unix.cs +++ b/src/System.Net.Ping/src/System/Net/NetworkInformation/Ping.Unix.cs @@ -116,7 +116,7 @@ namespace System.Net.NetworkInformation int icmpHeaderOffset = ipHeaderLength; // Skip IP header. - IcmpHeader receivedHeader = MemoryMarshal.Cast(receiveBuffer.AsSpan(icmpHeaderOffset))[0]; + IcmpHeader receivedHeader = MemoryMarshal.Read(receiveBuffer.AsSpan(icmpHeaderOffset)); type = receivedHeader.Type; code = receivedHeader.Code;