From 5083b8b330ae22e71421ef590fb33160de80c40b Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 19 Nov 2019 18:36:19 -0800 Subject: [PATCH] Add GC.GetConfigVariable API, and support more GC settings in runtimeconfig Currently, a few GC settings are only available as environment variables. This adds support for a few more as runtimeconfig settings. They are: * "System.GC.CpuGroup": Same as `COMPlus_GCCpuGroup` * "System.GC.HighMemoryPercent": Same as `COMPlus_GCHighMemPercent` * "System.GC.LargePages": Same as `COMPlus_GCLargePages` * "System.GC.HeapAffinitizeRnages": Same as `COMPlus_GCHeapAffinitizeRanges` This is similar to dotnet/coreclr#25574, which did this for "System.GC.HeapHardLimit" and "System.GC.HeapHardLimitPercent". This PR also adds a new API described in dotnet/corefx#42499 . --- .../System.Private.CoreLib/src/System/GC.cs | 41 +++++++ src/coreclr/src/gc/gc.cpp | 115 ++++++++++++++++++ src/coreclr/src/gc/gcconfig.h | 8 ++ src/coreclr/src/gc/gcimpl.h | 3 + src/coreclr/src/gc/gcinterface.h | 29 +++++ src/coreclr/src/inc/clrconfigvalues.h | 2 +- src/coreclr/src/inc/holder.h | 1 + src/coreclr/src/utilcode/util.cpp | 4 +- src/coreclr/src/vm/comutilnative.cpp | 37 ++++++ src/coreclr/src/vm/comutilnative.h | 3 + src/coreclr/src/vm/ecalllist.h | 2 + src/coreclr/src/vm/eeconfig.cpp | 8 +- src/coreclr/src/vm/eeconfig.h | 9 ++ src/coreclr/src/vm/gcenv.ee.cpp | 106 +++++++++++----- .../GetConfigurationVariables_common.cs | 62 ++++++++++ .../GetConfigurationVariables_defaults.cs | 21 ++++ .../GetConfigurationVariables_defaults.csproj | 11 ++ .../GetConfigurationVariables_withEnvVars.cs | 23 ++++ ...tConfigurationVariables_withEnvVars.csproj | 26 ++++ 19 files changed, 479 insertions(+), 32 deletions(-) create mode 100644 src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_common.cs create mode 100644 src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.cs create mode 100644 src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.csproj create mode 100644 src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.cs create mode 100644 src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.csproj diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/GC.cs b/src/coreclr/src/System.Private.CoreLib/src/System/GC.cs index 01fa70d2653c16..80a6121f836563 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/GC.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/GC.cs @@ -78,6 +78,47 @@ public static GCMemoryInfo GetGCMemoryInfo() fragmentedBytes: (long)(ulong)lastRecordedFragmentationBytes); } + [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Ansi)] + private static extern void GetGCConfigurationVariable(string name, StringHandleOnStack retString); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string[] GetGCConfigurationVariables(); + + /// + /// Given the name of a GC configuration variable, get the set or default value. + /// + /// + /// Returns `null` for an invalid name. + /// For a list of possible names, see the keys of `GetConfigurationVariables`. + /// + /// Numeric are in decimal even though environment variables are specified using hexadecimal. + /// + /// This reflects actually used values, not just explicit settings. + /// For example, if you set `System.GC.HeapCount` but not `System.GC.Server`, + /// the result for "HeapCount" would be "1" as workstation GC only uses 1 heap. + /// Similarly, "NoAffinitize" may be true if "HeapHardLimit" is set and HeapAffinitizeRanges is not. + /// + public static string? GetConfigurationVariable(string name) + { + string result = ""; + GetGCConfigurationVariable(name, new StringHandleOnStack(ref result)); + return result == "" ? null : result; + } + + /// + /// Returns all possible GC configuration variables and their values. + /// + /// + /// See `GetConfigurationVariable` for description of how we get values. + /// + public static IEnumerable> GetConfigurationVariables() + { + foreach (string v in GetGCConfigurationVariables()) + { + yield return new KeyValuePair(v, GetConfigurationVariable(v)!); + } + } + [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] internal static extern int _StartNoGCRegion(long totalSize, bool lohSizeKnown, long lohSize, bool disallowFullBlockingGC); diff --git a/src/coreclr/src/gc/gc.cpp b/src/coreclr/src/gc/gc.cpp index c79e1de5a6741b..b283d2d851b22b 100644 --- a/src/coreclr/src/gc/gc.cpp +++ b/src/coreclr/src/gc/gc.cpp @@ -38815,6 +38815,121 @@ void GCHeap::GetMemoryInfo(uint64_t* highMemLoadThresholdBytes, *lastRecordedFragmentationBytes = gc_heap::last_gc_fragmentation; } +static const char* bool_to_str(const bool value) +{ + const char* content = value ? "true" : "false"; + const size_t len = strlen(content); + // + 1 for the '\0' + char* out = new (nothrow) char[len + 1]; + if (out != nullptr) + { + strncpy(out, content, len + 1); + assert(out[len] == '\0' && strcmp(out, content) == 0); + } + return out; +} + +static const char* int64_t_to_str(const int64_t value) +{ + // 2**64 in base ten has 20 characters at most, + 1 for the '\0' + const size_t max_size = 21; + char* out = new (nothrow) char[max_size]; + if (out != nullptr) + { + int n = _snprintf_s (out, max_size, _TRUNCATE, "%llu", (long long) value); + // -1 because n does not include the '\0' + assert (n <= max_size - 1); + } + return out; +} + +static char const* const configuration_variables[11] = +{ + "CpuGroup", + "HeapAffinitizeMask", + "HeapAffinitizeRanges", + "HeapCount", + "HeapHardLimit", + "HeapHardLimitPercent", + "HighMemoryPercent", + "LargePages", + "LOHThreshold", + "NoAffinitize", + "Server" +}; + +slice GCHeap::GetGCConfigurationVariables() +{ + return slice::FromArray<11>(configuration_variables); +} + +const char* GCHeap::GetGCConfigurationVariable(const char* key) +{ + if (strcmp(key, "CpuGroup") == 0) + { + return bool_to_str(GCConfig::GetGCCpuGroup()); + } + else if (strcmp(key, "HeapAffinitizeMask") == 0) + { + return int64_t_to_str(GCConfig::GetGCHeapAffinitizeMask()); + } + else if (strcmp(key, "HeapAffinitizeRanges") == 0) + { + // GetGCHeapAffinitizeRanges returns a GCConfigStringHolder which will delete the string, but not if we Extract() it. + return GCConfig::GetGCHeapAffinitizeRanges().Extract(); + } + else if (strcmp(key, "HeapCount") == 0) + { +#ifdef MULTIPLE_HEAPS + const size_t heapCount = gc_heap::n_heaps; +#else + const size_t heapCount = 1; +#endif + return int64_t_to_str(heapCount); + } + else if (strcmp(key, "HeapHardLimit") == 0) + { + return int64_t_to_str(gc_heap::heap_hard_limit); + } + else if (strcmp(key, "HeapHardLimitPercent") == 0) + { + return int64_t_to_str(GCConfig::GetGCHeapHardLimitPercent()); + } + else if (strcmp(key, "HighMemoryPercent") == 0) + { + return int64_t_to_str(gc_heap::high_memory_load_th); + } + else if (strcmp(key, "LargePages") == 0) + { + return bool_to_str(gc_heap::use_large_pages_p); + } + else if (strcmp(key, "LOHThreshold") == 0) + { + return int64_t_to_str(loh_size_threshold); + } + else if (strcmp(key, "NoAffinitize") == 0) + { +#ifdef MULTIPLE_HEAPS + const bool noAffinitize = gc_heap::gc_thread_no_affinitize_p; +#else + const bool noAffinitize = false; +#endif + return bool_to_str(noAffinitize); + } + else if (strcmp(key, "Server") == 0) + { +#ifdef MULTIPLE_HEAPS + return bool_to_str(true); +#else + return bool_to_str(false); +#endif + } + else + { + return nullptr; + } +} + int GCHeap::GetGcLatencyMode() { return (int)(pGenGCHeap->settings.pause_mode); diff --git a/src/coreclr/src/gc/gcconfig.h b/src/coreclr/src/gc/gcconfig.h index ba8db0f77af911..8c3390bda6967d 100644 --- a/src/coreclr/src/gc/gcconfig.h +++ b/src/coreclr/src/gc/gcconfig.h @@ -48,6 +48,14 @@ class GCConfigStringHolder // Retrieves the wrapped config string. const char* Get() const { return m_str; } + + // Returns the wrapped config string and sets to null. + // Now the caller is responsible for freeing it. + const char* Extract() { + const char* result = m_str; + m_str = nullptr; + return result; + } }; // Note that the configs starting BGCFLTuningEnabled ending BGCG2RatioStep are for BGC servo diff --git a/src/coreclr/src/gc/gcimpl.h b/src/coreclr/src/gc/gcimpl.h index c9fcdb8fed5b5f..34b67ed71e800e 100644 --- a/src/coreclr/src/gc/gcimpl.h +++ b/src/coreclr/src/gc/gcimpl.h @@ -177,6 +177,9 @@ class GCHeap : public IGCHeapInternal uint32_t* lastRecordedMemLoadPct, size_t* lastRecordedHeapSizeBytes, size_t* lastRecordedFragmentationBytes); + + const char* GetGCConfigurationVariable(const char* key); + slice GetGCConfigurationVariables(); int GetGcLatencyMode(); int SetGcLatencyMode(int newLatencyMode); diff --git a/src/coreclr/src/gc/gcinterface.h b/src/coreclr/src/gc/gcinterface.h index 7d22d30d5b0738..c266fde93cb7b6 100644 --- a/src/coreclr/src/gc/gcinterface.h +++ b/src/coreclr/src/gc/gcinterface.h @@ -14,6 +14,32 @@ // mismatches can still interopate correctly, with some care. #define GC_INTERFACE_MINOR_VERSION 1 +template +class slice { + T* _begin; + size_t _length; + + slice(T* begin, size_t length) : _begin{begin}, _length{length} {} + +public: + template + static slice FromArray(T data[size]) + { + return slice { data, size }; + } + + size_t size() const + { + return _length; + } + + const T& operator[](size_t index) const + { + assert(index < _length); + return _begin[index]; + } +}; + struct ScanContext; struct gc_alloc_context; class CrawlFrame; @@ -612,6 +638,9 @@ class IGCHeap { size_t* lastRecordedHeapSizeBytes, size_t* lastRecordedFragmentationBytes) = 0; + virtual const char* GetGCConfigurationVariable(const char* key) = 0; + virtual slice GetGCConfigurationVariables() = 0; + // Gets the current GC latency mode. virtual int GetGcLatencyMode() = 0; diff --git a/src/coreclr/src/inc/clrconfigvalues.h b/src/coreclr/src/inc/clrconfigvalues.h index e0ce6f39052b18..643c19000634be 100644 --- a/src/coreclr/src/inc/clrconfigvalues.h +++ b/src/coreclr/src/inc/clrconfigvalues.h @@ -315,7 +315,7 @@ RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCName, W("GCName"), "") RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(EXTERNAL_GCHeapHardLimit, W("GCHeapHardLimit"), "Specifies the maximum commit size for the GC heap") RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(EXTERNAL_GCHeapHardLimitPercent, W("GCHeapHardLimitPercent"), "Specifies the GC heap usage as a percentage of the total memory") RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCHeapAffinitizeRanges, W("GCHeapAffinitizeRanges"), "Specifies list of processors for Server GC threads. The format is a comma separated list of processor numbers or ranges of processor numbers. Example: 1,3,5,7-9,12") -RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(EXTERNAL_GCLargePages, W("GCLargePages"), "Specifies whether large pages should be used when a heap hard limit is set") +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_GCLargePages, W("GCLargePages"), 0, "Specifies whether large pages should be used when a heap hard limit is set") /// /// IBC diff --git a/src/coreclr/src/inc/holder.h b/src/coreclr/src/inc/holder.h index f49f342bd34e3b..d390ff4b7cdee3 100644 --- a/src/coreclr/src/inc/holder.h +++ b/src/coreclr/src/inc/holder.h @@ -1028,6 +1028,7 @@ FORCEINLINE void DeleteArray(TYPE *value) NEW_WRAPPER_TEMPLATE1(NewArrayHolder, DeleteArray<_TYPE>); typedef NewArrayHolder AStringHolder; +typedef NewArrayHolder AConstStringHolder; typedef NewArrayHolder WStringHolder; //----------------------------------------------------------------------------- diff --git a/src/coreclr/src/utilcode/util.cpp b/src/coreclr/src/utilcode/util.cpp index 80cd00fd0e4e81..c4c76a8b32dd08 100644 --- a/src/coreclr/src/utilcode/util.cpp +++ b/src/coreclr/src/utilcode/util.cpp @@ -20,6 +20,8 @@ #include "corinfo.h" #include "volatile.h" +#include "../inc/configuration.h" + #ifndef DACCESS_COMPILE UINT32 g_nClrInstanceId = 0; #endif //!DACCESS_COMPILE @@ -989,7 +991,7 @@ DWORD LCM(DWORD u, DWORD v) CONTRACTL_END; #if !defined(FEATURE_REDHAWK) && (defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)) - BOOL enableGCCPUGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_GCCpuGroup) != 0; + BOOL enableGCCPUGroups = Configuration::GetKnobBooleanValue(W("System.GC.CpuGroup"), CLRConfig::EXTERNAL_GCCpuGroup); BOOL threadUseAllCpuGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_Thread_UseAllCpuGroups) != 0; if (!enableGCCPUGroups) diff --git a/src/coreclr/src/vm/comutilnative.cpp b/src/coreclr/src/vm/comutilnative.cpp index fa0d8a28b7a54f..9dcda70f3d2f92 100644 --- a/src/coreclr/src/vm/comutilnative.cpp +++ b/src/coreclr/src/vm/comutilnative.cpp @@ -696,6 +696,43 @@ FCIMPL6(void, GCInterface::GetMemoryInfo, UINT64* highMemLoadThreshold, UINT64* } FCIMPLEND +void QCALLTYPE GCInterface::GetGCConfigurationVariable(const char* key, QCall::StringHandleOnStack result) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + AConstStringHolder value; + value = GCHeapUtilities::GetGCHeap()->GetGCConfigurationVariable(key); + if (value) + { + result.Set(value); + } + + END_QCALL; +} + +FCIMPL0(Object*, GCInterface::GetGCConfigurationVariables) +{ + FCALL_CONTRACT; + + slice configVariables = GCHeapUtilities::GetGCHeap()->GetGCConfigurationVariables(); + + PTRARRAYREF strArray = NULL; + HELPER_METHOD_FRAME_BEGIN_RET_1(strArray); + strArray = (PTRARRAYREF) AllocateObjectArray(static_cast(configVariables.size()), g_pStringClass); + for (unsigned int i = 0; i < configVariables.size(); i++) + { + STRINGREF str = StringObject::NewString(configVariables[i]); // This copies it + STRINGREF * destData = ((STRINGREF*)(strArray->GetDataPtr())) + i; + SetObjectReference((OBJECTREF*)destData, (OBJECTREF)str); + } + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(strArray); +} +FCIMPLEND + FCIMPL0(int, GCInterface::GetGcLatencyMode) { FCALL_CONTRACT; diff --git a/src/coreclr/src/vm/comutilnative.h b/src/coreclr/src/vm/comutilnative.h index 17442d91431c96..c5a74c85a02a4f 100644 --- a/src/coreclr/src/vm/comutilnative.h +++ b/src/coreclr/src/vm/comutilnative.h @@ -97,6 +97,9 @@ class GCInterface { static FORCEINLINE UINT64 InterlockedSub(UINT64 *pMinuend, UINT64 subtrahend); static FCDECL6(void, GetMemoryInfo, UINT64* highMemLoadThresholdBytes, UINT64* totalAvailableMemoryBytes, UINT64* lastRecordedMemLoadBytes, UINT32* lastRecordedMemLoadPct, size_t* lastRecordedHeapSizBytes, size_t* lastRecordedFragmentationBytes); + static void QCALLTYPE GetGCConfigurationVariable(const char* key, QCall::StringHandleOnStack result); + // The object will be a string[] + static FCDECL0(Object*, GetGCConfigurationVariables); static FCDECL0(int, GetGcLatencyMode); static FCDECL1(int, SetGcLatencyMode, int newLatencyMode); static FCDECL0(int, GetLOHCompactionMode); diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index b591f5c627f993..2e149774d7427f 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -741,6 +741,8 @@ FCFuncStart(gGCInterfaceFuncs) QCFuncElement("_StartNoGCRegion", GCInterface::StartNoGCRegion) QCFuncElement("_EndNoGCRegion", GCInterface::EndNoGCRegion) FCFuncElement("GetSegmentSize", GCInterface::GetSegmentSize) + QCFuncElement("GetGCConfigurationVariable", GCInterface::GetGCConfigurationVariable) + FCFuncElement("GetGCConfigurationVariables", GCInterface::GetGCConfigurationVariables) FCFuncElement("GetLastGCPercentTimeInGC", GCInterface::GetLastGCPercentTimeInGC) FCFuncElement("GetGenerationSize", GCInterface::GetGenerationSize) QCFuncElement("_AddMemoryPressure", GCInterface::_AddMemoryPressure) diff --git a/src/coreclr/src/vm/eeconfig.cpp b/src/coreclr/src/vm/eeconfig.cpp index a942add909fc57..c0aea823e04e42 100644 --- a/src/coreclr/src/vm/eeconfig.cpp +++ b/src/coreclr/src/vm/eeconfig.cpp @@ -814,12 +814,18 @@ fTrackDynamicMethodDebugInfo = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_ if (!iGCgen0size) iGCgen0size = GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_GCgen0size, iGCgen0size); #endif //BIT64 - const ULONGLONG ullHeapHardLimit = Configuration::GetKnobULONGLONGValue(W("System.GC.HeapHardLimit")); + ULONGLONG ullHeapHardLimit = Configuration::GetKnobULONGLONGValue(W("System.GC.HeapHardLimit")); + if (!ullHeapHardLimit) ullHeapHardLimit = GetConfigULONGLONG_DontUse_(CLRConfig::EXTERNAL_GCHeapHardLimit, 0); iGCHeapHardLimit = FitsIn(ullHeapHardLimit) ? static_cast(ullHeapHardLimit) : ClrSafeInt::MaxInt(); iGCHeapHardLimitPercent = Configuration::GetKnobDWORDValue(W("System.GC.HeapHardLimitPercent"), 0); + iGCCpuGroup = Configuration::GetKnobBooleanValue(W("System.GC.CpuGroup"), CLRConfig::EXTERNAL_GCCpuGroup); + iGCHighMemoryPercent = Configuration::GetKnobDWORDValue(W("System.GC.HighMemoryPercent"), CLRConfig::EXTERNAL_GCHighMemPercent); + fGCLargePages = Configuration::GetKnobBooleanValue(W("System.GC.LargePages"), CLRConfig::EXTERNAL_GCLargePages); + lpszGCHeapAffinitizeRanges = Configuration::GetKnobStringValue(W("System.GC.HeapAffinitizeRanges"), CLRConfig::EXTERNAL_GCHeapAffinitizeRanges); + if (g_IGCHoardVM) iGCHoardVM = g_IGCHoardVM; else diff --git a/src/coreclr/src/vm/eeconfig.h b/src/coreclr/src/vm/eeconfig.h index ceecfbfe4c6c0a..17596bfeac0295 100644 --- a/src/coreclr/src/vm/eeconfig.h +++ b/src/coreclr/src/vm/eeconfig.h @@ -612,8 +612,12 @@ class EEConfig int GetGCHeapCount() const {LIMITED_METHOD_CONTRACT; return iGCHeapCount;} int GetGCNoAffinitize () const {LIMITED_METHOD_CONTRACT; return iGCNoAffinitize;} size_t GetGCAffinityMask() const {LIMITED_METHOD_CONTRACT; return iGCAffinityMask;} + LPCWSTR GetGCHeapAffinitizeRanges() const {LIMITED_METHOD_CONTRACT; return lpszGCHeapAffinitizeRanges;} + int GetGCHighMemoryPercent() const {LIMITED_METHOD_CONTRACT; return iGCHighMemoryPercent;} size_t GetGCHeapHardLimit() const {LIMITED_METHOD_CONTRACT; return iGCHeapHardLimit;} int GetGCHeapHardLimitPercent() const {LIMITED_METHOD_CONTRACT; return iGCHeapHardLimitPercent;} + bool GetGCLargePages() const {LIMITED_METHOD_CONTRACT; return fGCLargePages;} + bool GetGCCpuGroup() const {LIMITED_METHOD_CONTRACT; return iGCCpuGroup;} #ifdef GCTRIMCOMMIT @@ -917,8 +921,13 @@ class EEConfig int iGCHeapCount; int iGCNoAffinitize; size_t iGCAffinityMask; + bool iGCCpuGroup; + // this points to a string from GetKnobStringValue and should not be freed. + LPCWSTR lpszGCHeapAffinitizeRanges; + int iGCHighMemoryPercent; size_t iGCHeapHardLimit; int iGCHeapHardLimitPercent; + bool fGCLargePages; #ifdef GCTRIMCOMMIT diff --git a/src/coreclr/src/vm/gcenv.ee.cpp b/src/coreclr/src/vm/gcenv.ee.cpp index 34a1ec6994780a..8b34a69c6599d9 100644 --- a/src/coreclr/src/vm/gcenv.ee.cpp +++ b/src/coreclr/src/vm/gcenv.ee.cpp @@ -1057,6 +1057,24 @@ bool GCToEEInterface::GetBooleanConfigValue(const char* key, bool* value) return true; } + if (strcmp(key, "GCCpuGroup") == 0) + { + *value = g_pConfig->GetGCCpuGroup(); + return true; + } + + if (strcmp(key, "GCLargePages") == 0) + { + *value = g_pConfig->GetGCLargePages(); + return true; + } + + if (strcmp(key, "GCNoAffinitize") == 0) + { + *value = g_pConfig->GetGCNoAffinitize(); + return true; + } + WCHAR configKey[MaxConfigKeyLength]; if (MultiByteToWideChar(CP_ACP, 0, key, -1 /* key is null-terminated */, configKey, MaxConfigKeyLength) == 0) { @@ -1082,15 +1100,21 @@ bool GCToEEInterface::GetIntConfigValue(const char* key, int64_t* value) GC_NOTRIGGER; } CONTRACTL_END; - if (strcmp(key, "GCSegmentSize") == 0) + if (strcmp(key, "GCgen0size") == 0) { - *value = g_pConfig->GetSegmentSize(); + *value = g_pConfig->GetGCgen0size(); return true; } - if (strcmp(key, "GCgen0size") == 0) + if (strcmp(key, "GCHeapAffinitizeMask") == 0) { - *value = g_pConfig->GetGCgen0size(); + *value = g_pConfig->GetGCAffinityMask(); + return true; + } + + if (strcmp(key, "GCHeapCount") == 0) + { + *value = g_pConfig->GetGCHeapCount(); return true; } @@ -1106,12 +1130,24 @@ bool GCToEEInterface::GetIntConfigValue(const char* key, int64_t* value) return true; } + if (strcmp(key, "GCHighMemPercent") == 0) + { + *value = g_pConfig->GetGCHighMemoryPercent(); + return true; + } + if (strcmp(key, "GCLOHThreshold") == 0) { *value = g_pConfig->GetGCLOHThreshold(); return true; } + if (strcmp(key, "GCSegmentSize") == 0) + { + *value = g_pConfig->GetSegmentSize(); + return true; + } + WCHAR configKey[MaxConfigKeyLength]; if (MultiByteToWideChar(CP_ACP, 0, key, -1 /* key is null-terminated */, configKey, MaxConfigKeyLength) == 0) { @@ -1152,34 +1188,13 @@ bool GCToEEInterface::GetIntConfigValue(const char* key, int64_t* value) return false; } -bool GCToEEInterface::GetStringConfigValue(const char* key, const char** value) +static bool GetStringConfigValueHelper(LPCWSTR out, const char** value) { - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - } CONTRACTL_END; - - WCHAR configKey[MaxConfigKeyLength]; - if (MultiByteToWideChar(CP_ACP, 0, key, -1 /* key is null-terminated */, configKey, MaxConfigKeyLength) == 0) - { - // whatever this is... it's not something we care about. (It was too long, wasn't unicode, etc.) - return false; - } - - CLRConfig::ConfigStringInfo info { configKey, CLRConfig::EEConfig_default }; - LPWSTR out = CLRConfig::GetConfigValue(info); - if (!out) - { - // config not found - return false; - } - int charCount = WideCharToMultiByte(CP_ACP, 0, out, -1 /* out is null-terminated */, NULL, 0, nullptr, nullptr); if (charCount == 0) { // this should only happen if the config subsystem gives us a string that's not valid // unicode. - CLRConfig::FreeConfigString(out); return false; } @@ -1187,7 +1202,6 @@ bool GCToEEInterface::GetStringConfigValue(const char* key, const char** value) AStringHolder configResult = new (nothrow) char[charCount]; if (!configResult) { - CLRConfig::FreeConfigString(out); return false; } @@ -1197,15 +1211,49 @@ bool GCToEEInterface::GetStringConfigValue(const char* key, const char** value) // this should never happen, the previous call to WideCharToMultiByte that computed the charCount should // have caught all issues. assert(false); - CLRConfig::FreeConfigString(out); return false; } *value = configResult.Extract(); - CLRConfig::FreeConfigString(out); return true; } +bool GCToEEInterface::GetStringConfigValue(const char* key, const char** value) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + WCHAR configKey[MaxConfigKeyLength]; + if (MultiByteToWideChar(CP_ACP, 0, key, -1 /* key is null-terminated */, configKey, MaxConfigKeyLength) == 0) + { + // whatever this is... it's not something we care about. (It was too long, wasn't unicode, etc.) + return false; + } + + CLRConfig::ConfigStringInfo info { configKey, CLRConfig::EEConfig_default }; + + if (strcmp(key, "GCHeapAffinitizeRanges") == 0) + { + // Do not free, the one we get from eeconfig is just a reference to its own string and not a copy + LPCWSTR out = g_pConfig->GetGCHeapAffinitizeRanges(); + return out != nullptr && GetStringConfigValueHelper(out, value); + } + else + { + LPWSTR out = CLRConfig::GetConfigValue(info); + if (!out) + { + // config not found + return false; + } + bool result = GetStringConfigValueHelper(out, value); + CLRConfig::FreeConfigString(out); + return result; + } +} + void GCToEEInterface::FreeStringConfigValue(const char* value) { delete [] value; diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_common.cs b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_common.cs new file mode 100644 index 00000000000000..df72b094ece120 --- /dev/null +++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_common.cs @@ -0,0 +1,62 @@ +// 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.Linq; +using System.Reflection; + +public static class GetConfigurationVariables_common +{ + public static int AssertConfigurationVariables(IReadOnlyList> expected) + { + // TODO: shouldn't need reflection once this is a public API + Type gcType = typeof(GC); + MethodInfo getConfigurationVariables = GetMethod(typeof(GC), "GetConfigurationVariables"); + // TODO: GC.GetConfigurationVariables().ToList(); + IReadOnlyList> actual = ((IEnumerable>) getConfigurationVariables.Invoke(null, new object[] { })!).ToList(); + + if (!ArraysEqual(expected, actual, new KeyValuePairComparer(EqualityComparer.Default, EqualityComparer.Default))) + { + // Expected + // Actual + Console.Error.WriteLine($"Expected : {ShowPairs(expected)}\nActual : {ShowPairs(actual)}"); + return 1; + } + + return 100; + } + + public static KeyValuePair Pair(K k, V v) => + new KeyValuePair(k, v); + + private static string ShowPairs(IEnumerable> pairs) => + string.Join(", ", from pair in pairs select $"{pair.Key} => {pair.Value}"); + + private static MethodInfo GetMethod(Type t, string name) => + t.GetMethod(name, BindingFlags.Public | BindingFlags.Static) + ?? throw new Exception($"Type {t} has no public static method {name}"); + + private static bool ArraysEqual(IReadOnlyList a, IReadOnlyList b, IEqualityComparer cmp) => + a.Count == b.Count + && a.Zip(b).All(ab => cmp.Equals(ab.First, ab.Second)); + + private class KeyValuePairComparer : IEqualityComparer> + { + private IEqualityComparer compareK; + private IEqualityComparer compareV; + + public KeyValuePairComparer(IEqualityComparer compareK, IEqualityComparer compareV) + { + this.compareK = compareK; + this.compareV = compareV; + } + + bool IEqualityComparer>.Equals(KeyValuePair x, KeyValuePair y) => + compareK.Equals(x.Key, y.Key) && compareV.Equals(x.Value, y.Value); + + int IEqualityComparer>.GetHashCode(KeyValuePair obj) => + throw new NotImplementedException(); + } +} diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.cs b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.cs new file mode 100644 index 00000000000000..3f8be07bc45369 --- /dev/null +++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +using static GetConfigurationVariables_common; + +public static class GetConfigurationVariables_defaults +{ + public static int Main() => + AssertConfigurationVariables(new KeyValuePair[] + { + Pair("CpuGroup", "false"), + Pair("HeapAffinitizeMask", "0"), + Pair("HeapCount", "1"), + Pair("HeapHardLimit", "0"), + Pair("HeapHardLimitPercent", "0"), + Pair("HighMemoryPercent", "90"), + Pair("LargePages", "false"), + Pair("LOHThreshold", "85000"), + Pair("NoAffinitize", "false"), + Pair("Server", "false"), + }); +} diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.csproj b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.csproj new file mode 100644 index 00000000000000..783913c8d2b7ec --- /dev/null +++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.csproj @@ -0,0 +1,11 @@ + + + Exe + + true + + + + + + diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.cs b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.cs new file mode 100644 index 00000000000000..75e950ded9fabe --- /dev/null +++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +using static GetConfigurationVariables_common; + +public static class GetConfigurationVariables_withEnvVars +{ + public static int Main() => + AssertConfigurationVariables(new KeyValuePair[] + { + Pair("CpuGroup", "false"), + Pair("HeapAffinitizeMask", "0"), + Pair("HeapCount", "1"), + Pair("HeapHardLimit", "123456789"), + Pair("HeapHardLimitPercent", "0"), + Pair("HighMemoryPercent", "90"), + Pair("LargePages", "false"), + Pair("LOHThreshold", "85000"), + // We did not explicitly set this, but due to the HeapHardLimit this is true. + // See gc.cpp: `gc_heap::gc_thread_no_affinitize_p = (gc_heap::heap_hard_limit ? !affinity_config_specified_p : (GCConfig::GetNoAffinitize() != 0));` + Pair("NoAffinitize", "true"), + Pair("Server", "true"), + }); +} diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.csproj b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.csproj new file mode 100644 index 00000000000000..bcf34fdf12d01e --- /dev/null +++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.csproj @@ -0,0 +1,26 @@ + + + Exe + + true + + + + + + + + + + + + + + + + + +