From 15ed2d1faa8dfdb56a7fed7f1e8ef01bd7007ab6 Mon Sep 17 00:00:00 2001 From: Mario Hewardt Date: Mon, 17 Jul 2023 13:34:43 -0700 Subject: [PATCH 1/2] 189 - Generate a dump when the size of the generation exceeds threshold --- README.md | 14 ++- include/Monitor.h | 3 +- include/ProfilerCommon.h | 3 + procdump.1 | 4 +- profiler/inc/ProcDumpProfiler.h | 3 +- profiler/src/ProcDumpProfiler.cpp | 96 +++++++++++++------ src/Monitor.c | 55 ++++------- src/ProcDumpConfiguration.c | 67 ++++++++++++- src/ProfilerHelpers.c | 2 +- tests/integration/TestWebApi/Program.cs | 27 +++++- .../dotnet_LOH_thresholds_3_dumps.sh | 73 ++++++++++++++ .../dotnet_POH_thresholds_3_dumps.sh | 73 ++++++++++++++ .../dotnet_gen_2_thresholds_3_dumps.sh | 73 ++++++++++++++ 13 files changed, 412 insertions(+), 81 deletions(-) create mode 100755 tests/integration/scenarios/dotnet_LOH_thresholds_3_dumps.sh create mode 100755 tests/integration/scenarios/dotnet_POH_thresholds_3_dumps.sh create mode 100755 tests/integration/scenarios/dotnet_gen_2_thresholds_3_dumps.sh diff --git a/README.md b/README.md index cb52ca0..1cae927 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ procdump [-n Count] [-s Seconds] [-c|-cl CPU_Usage] [-m|-ml Commit_Usage1[,Commit_Usage2,...]] - [-gcm Memory_Usage1[,Memory_Usage2...]] + [-gcm [: | LOH: | POH:]Memory_Usage1[,Memory_Usage2...]] [-gcgen Generation] [-tc Thread_Threshold] [-fc FileDescriptor_Threshold] @@ -46,7 +46,7 @@ Options: -cl CPU threshold below which to create a dump of the process. -m Memory commit threshold(s) (MB) above which to create dumps. -ml Memory commit threshold(s) (MB) below which to create dumps. - -gcm [.NET] GC memory threshold(s) (MB) above which to create dumps. + -gcm [.NET] GC memory threshold(s) (MB) above which to create dumps for the specified generation or heap (default is total .NET memory usage). -gcgen [.NET] Create dump when the garbage collection of the specified generation starts and finishes. -tc Thread count threshold above which to create a dump of the process. -fc File descriptor count threshold above which to create a dump of the process. @@ -94,10 +94,18 @@ The following will create a core dump when memory usage is >= 100 MB followed by ``` sudo procdump -m 100,200 1234 ``` -The following will create a core dump when .NET memory usage is >= 100 MB followed by another dump when memory usage is >= 200MB. +The following will create a core dump when the total .NET memory usage is >= 100 MB followed by another dump when memory usage is >= 200MB. ``` sudo procdump -gcm 100,200 1234 ``` +The following will create a core dump when .NET memory usage for generation 1 is >= 1 MB followed by another dump when memory usage is >= 2MB. +``` +sudo procdump -gcm 1:1,2 1234 +``` +The following will create a core dump when .NET Large Object Heap memory usage is >= 100 MB followed by another dump when memory usage is >= 200MB. +``` +sudo procdump -gcm LOH:100,200 1234 +``` The following will create a core dump at the start and end of a .NET generation 1 garbage collection. ``` sudo procdump -gcgen 1 diff --git a/include/Monitor.h b/include/Monitor.h index cd0f862..77d0b40 100644 --- a/include/Monitor.h +++ b/include/Monitor.h @@ -33,8 +33,7 @@ bool BeginMonitoring(struct ProcDumpConfiguration *self); bool MonitorDotNet(struct ProcDumpConfiguration *self); char* GetThresholds(struct ProcDumpConfiguration *self); char* GetClientData(struct ProcDumpConfiguration *self, char* fullDumpPath); -char* GetClientDataInt(enum TriggerType triggerType, char* path, int value); -char* GetClientDataString(enum TriggerType triggerType, char* path, char* value); +char* GetClientDataHelper(enum TriggerType triggerType, char* path, const char* format, ...); bool ExitProcessMonitor(struct ProcDumpConfiguration* config, pthread_t processMonitor); // Monitor worker threads diff --git a/include/ProfilerCommon.h b/include/ProfilerCommon.h index 94f7c5e..3273e06 100644 --- a/include/ProfilerCommon.h +++ b/include/ProfilerCommon.h @@ -9,6 +9,9 @@ #ifndef PROFILERCOMMON_H #define PROFILERCOMMON_H +#define CUMULATIVE_GC_SIZE 2008 +#define MAX_GC_GEN 2 + enum TriggerType { Processor, diff --git a/procdump.1 b/procdump.1 index 2888500..2334a38 100644 --- a/procdump.1 +++ b/procdump.1 @@ -7,7 +7,7 @@ procdump [-n Count] [-s Seconds] [-c|-cl CPU_Usage] [-m|-ml Commit_Usage1[,Commit_Usage2,...]] - [-gcm Memory_Usage1[,Memory_Usage2...]] + [-gcm [: | LOH: | POH:]Memory_Usage1[,Memory_Usage2...]] [-gcgen Generation] [-tc Thread_Threshold] [-fc FileDescriptor_Threshold] @@ -28,7 +28,7 @@ Options: -cl CPU threshold below which to create a dump of the process. -m Memory commit thresholds (MB) above which to create dumps. -ml Memory commit thresholds (MB) below which to create dumps. - -gcm [.NET] GC memory threshold(s) (MB) above which to create dumps. + -gcm [.NET] GC memory threshold(s) (MB) above which to create dumps for the specified generation or heap (default is total .NET memory usage). -gcgen [.NET] Create dump when the garbage collection of the specified generation starts and finishes. -tc Thread count threshold above which to create a dump of the process. -fc File descriptor count threshold above which to create a dump of the process. diff --git a/profiler/inc/ProcDumpProfiler.h b/profiler/inc/ProcDumpProfiler.h index 337b5ef..e4be15f 100644 --- a/profiler/inc/ProcDumpProfiler.h +++ b/profiler/inc/ProcDumpProfiler.h @@ -111,7 +111,6 @@ class CorProfiler : public ICorProfilerCallback8 pthread_mutex_t endDumpCondition; enum TriggerType triggerType; int currentThresholdIndex; - bool gen2Collection; int gcGeneration; bool gcGenStarted; @@ -129,7 +128,7 @@ class CorProfiler : public ICorProfilerCallback8 int send_all(int socket, void* buffer, size_t length); int recv_all(int socket, void* buffer, size_t length); bool WildcardSearch(WCHAR*, WCHAR*); - u_int64_t GetGCHeapSize(); + u_int64_t GetGCHeapSize(int generation); bool WriteDumpHelper(std::string dumpName); bool IsHighPerfBasicGC(); diff --git a/profiler/src/ProcDumpProfiler.cpp b/profiler/src/ProcDumpProfiler.cpp index 937c589..f1c517f 100644 --- a/profiler/src/ProcDumpProfiler.cpp +++ b/profiler/src/ProcDumpProfiler.cpp @@ -207,7 +207,7 @@ bool CorProfiler::IsHighPerfBasicGC() //------------------------------------------------------------------------------------------------------------------------------------------------------ CorProfiler::CorProfiler() : refCount(0), corProfilerInfo8(nullptr), corProfilerInfo3(nullptr), corProfilerInfo(nullptr), - procDumpPid(0), currentThresholdIndex(0), gen2Collection(false), gcGeneration(-1), gcGenStarted(false) + procDumpPid(0), currentThresholdIndex(0), gcGeneration(-1), gcGenStarted(false) { // Configure logging el::Loggers::reconfigureAllLoggers(el::ConfigurationType::Filename, LOG_FILE); @@ -346,13 +346,13 @@ WCHAR* CorProfiler::GetUint16(char* buffer) // Syntax of client data: ;... // // DOTNET_EXCEPTION_TRIGGER;;;:;:,... -// DOTNET_GC_THRESHOLD_TRIGGER;;;Threshold1;Threshold2,... +// DOTNET_GC_THRESHOLD_TRIGGER;;;[ | 3(LOH) | 4(POH) | 2008 (total mem)];Threshold1;Threshold2,... // DOTNET_GC_GEN_TRIGGER;;;GCGeneration // //------------------------------------------------------------------------------------------------------------------------------------------------------ bool CorProfiler::ParseClientData(char* clientData) { - LOG(TRACE) << "CorProfiler::ParseClientData: Enter"; + LOG(TRACE) << "CorProfiler::ParseClientData: Enter clientData = " << clientData; std::stringstream clientDataStream(clientData); std::string segment; std::vector dataList; @@ -415,7 +415,16 @@ bool CorProfiler::ParseClientData(char* clientData) else if (triggerType == GCThreshold) { // GC threshold list - gcMemoryThresholdMonitorList.push_back(std::stoi(dataItem) << 20); + if(i == 3) + { + // first element is the generation + gcGeneration = std::stoi(dataItem); + i++; + } + else + { + gcMemoryThresholdMonitorList.push_back(std::stoi(dataItem) << 20); + } } else if (triggerType == GCGeneration) { @@ -442,7 +451,14 @@ bool CorProfiler::ParseClientData(char* clientData) if(gcGeneration != -1) { - LOG(TRACE) << "CorProfiler::ParseClientData:GCGeneration " << gcGeneration; + if( gcGeneration == CUMULATIVE_GC_SIZE) + { + LOG(TRACE) << "CorProfiler::ParseClientData:GCGeneration " << "Cumulative"; + } + else + { + LOG(TRACE) << "CorProfiler::ParseClientData:GCGeneration " << gcGeneration; + } } LOG(TRACE) << "CorProfiler::ParseClientData: Exit"; @@ -720,7 +736,7 @@ std::string CorProfiler::GetDumpName(u_int16_t dumpCount, std::string name) //------------------------------------------------------------------------------------------------------------------------------------------------------ // CorProfiler::GetGCHeapSize //------------------------------------------------------------------------------------------------------------------------------------------------------ -uint64_t CorProfiler::GetGCHeapSize() +uint64_t CorProfiler::GetGCHeapSize(int generation) { LOG(TRACE) << "CorProfiler::GetGCHeapSize: Enter"; uint64_t gcHeapSize = 0; @@ -766,7 +782,17 @@ uint64_t CorProfiler::GetGCHeapSize() for (int i = nObjectRanges - 1; i >= 0; i--) { - gcHeapSize += pObjectRanges[i].rangeLength; + // Uncomment this to help track down .NET memory usage while debugging. + //LOG(TRACE) << "Range Len: " << pObjectRanges[i].rangeLength << " Gen: " << pObjectRanges[i].generation; + + if(generation == CUMULATIVE_GC_SIZE) + { + gcHeapSize += pObjectRanges[i].rangeLength; + } + else if(pObjectRanges[i].generation == generation) + { + gcHeapSize += pObjectRanges[i].rangeLength; + } } if(fHeapAlloc == true) @@ -827,27 +853,29 @@ HRESULT STDMETHODCALLTYPE CorProfiler::GarbageCollectionStarted(int cGenerations { LOG(TRACE) << "CorProfiler::GarbageCollectionStarted: Enter"; - if(gcMemoryThresholdMonitorList.size() > 0 && generationCollected[2] == true) + if(gcGeneration != -1 && + gcGenStarted == false && + gcGeneration == CUMULATIVE_GC_SIZE || + (gcGeneration < cGenerations && + generationCollected[gcGeneration] == true)) { - // GC memory threshold dump - gen2Collection = true; - } - else if(gcGeneration != -1 && gcGenStarted == false && gcGeneration < cGenerations && generationCollected[gcGeneration] == true) - { - // GC Generation dump - LOG(TRACE) << "CorProfiler::GarbageCollectionStarted: Dump on generation: " << gcGeneration << " and cGenerations = " << cGenerations; - std::string dump = GetDumpName(1, convertString(L"gc_gen")); - if(WriteDumpHelper(dump) == false) + gcGenStarted = true; + + if(gcMemoryThresholdMonitorList.size() == 0) { - SendCatastrophicFailureStatus(); - return E_FAIL; + // GC Generation dump + LOG(TRACE) << "CorProfiler::GarbageCollectionStarted: Dump on generation: " << gcGeneration << " and cGenerations = " << cGenerations; + std::string dump = GetDumpName(1, convertString(L"gc_gen")); + if(WriteDumpHelper(dump) == false) + { + SendCatastrophicFailureStatus(); + return E_FAIL; + } } - - gcGenStarted = true; } else { - LOG(TRACE) << "CorProfiler::GarbageCollectionStarted: Invalid trigger data, trigger = " << triggerType << " cGenerations " << cGenerations; + LOG(TRACE) << "CorProfiler::GarbageCollectionStarted: Trigger = " << triggerType << " cGenerations " << cGenerations << " gcGeneration " << gcGeneration << " threshold size " << gcMemoryThresholdMonitorList.size(); } LOG(TRACE) << "CorProfiler::GarbageCollectionStarted: Exit"; @@ -861,12 +889,24 @@ HRESULT STDMETHODCALLTYPE CorProfiler::GarbageCollectionFinished() { LOG(TRACE) << "CorProfiler::GarbageCollectionFinished: Enter"; - if(gen2Collection == true) + if(gcGenStarted == true && gcMemoryThresholdMonitorList.size() > 0) { - // During a GC threshold trigger, we only want to check heap sizes and thresholds after a gen2 collection - gen2Collection = false; - uint64_t heapSize = GetGCHeapSize(); - LOG(TRACE) << "CorProfiler::GarbageCollectionFinished: Total heap size " << heapSize; + // During a GC threshold trigger, we only want to check heap sizes and thresholds after the gen collection + uint64_t heapSize = 0; + gcGenStarted = false; + + if(gcGeneration == CUMULATIVE_GC_SIZE) + { + // If a generation was not explicitly specified on the command line (for example: -gcm 10,20,30) we want to get _all_ the memory of + // the managed heap and hence we add up all generations, LOH and POH. + heapSize += GetGCHeapSize(CUMULATIVE_GC_SIZE); + LOG(TRACE) << "CorProfiler::GarbageCollectionFinished: Cumulative heap size " << heapSize; + } + else + { + heapSize = GetGCHeapSize(gcGeneration); + LOG(TRACE) << "CorProfiler::GarbageCollectionFinished: Generation " << gcGeneration << " heap size " << heapSize; + } if(currentThresholdIndex < gcMemoryThresholdMonitorList.size() && heapSize >= gcMemoryThresholdMonitorList[currentThresholdIndex]) { @@ -894,7 +934,7 @@ HRESULT STDMETHODCALLTYPE CorProfiler::GarbageCollectionFinished() } } } - else if(gcGenStarted == true) + else if(gcGenStarted == true && gcMemoryThresholdMonitorList.size() == 0) { // GC Generation dump LOG(TRACE) << "CorProfiler::GarbageCollectionFinished: Dump on generation: " << gcGeneration; diff --git a/src/Monitor.c b/src/Monitor.c index 920b116..06b9868 100644 --- a/src/Monitor.c +++ b/src/Monitor.c @@ -1381,7 +1381,7 @@ char* GetClientData(struct ProcDumpConfiguration *self, char* fullDumpPath) return NULL; } - clientData = GetClientDataString(Exception, fullDumpPath, exceptionFilter); + clientData = GetClientDataHelper(Exception, fullDumpPath, "%s", exceptionFilter); if(clientData == NULL) { Trace("GetClientData: Failed to get client data (-e)."); @@ -1390,7 +1390,7 @@ char* GetClientData(struct ProcDumpConfiguration *self, char* fullDumpPath) } else if (self->bMonitoringGCMemory) { - // GC Memory trigger (-gcm);;;Threshold1;Threshold2,... + // GC Memory trigger (-gcm);;;Generation:Threshold1;Threshold2,... thresholds = GetThresholds(self); if(thresholds == NULL) { @@ -1398,17 +1398,17 @@ char* GetClientData(struct ProcDumpConfiguration *self, char* fullDumpPath) return NULL; } - clientData = GetClientDataString(GCThreshold, fullDumpPath, thresholds); + clientData = GetClientDataHelper(GCThreshold, fullDumpPath, "%d;%s", self->DumpGCGeneration == -1 ? CUMULATIVE_GC_SIZE : self->DumpGCGeneration, thresholds); if(clientData == NULL) { Trace("GetClientData: Failed to get client data (-gcm)."); return NULL; } } - else if(self->DumpGCGeneration) + else if(self->DumpGCGeneration != -1 && self->MemoryThreshold == NULL) { // GC Generation (-gcgen);;;GCGeneration - clientData = GetClientDataInt(GCGeneration, fullDumpPath, self->DumpGCGeneration); + clientData = GetClientDataHelper(GCGeneration, fullDumpPath, "%d", self->DumpGCGeneration); if(clientData == NULL) { Trace("GetClientData: Failed to get client data (-gcgen)."); @@ -1427,51 +1427,36 @@ char* GetClientData(struct ProcDumpConfiguration *self, char* fullDumpPath) //------------------------------------------------------------------------------------- // -// GetClientDataString +// GetClientDataHelper // -// Helper that fetches client data where the value is an 'char*'. +// Helper that fetches client data based on format specified. // //------------------------------------------------------------------------------------- -char* GetClientDataString(enum TriggerType triggerType, char* path, char* value) +char* GetClientDataHelper(enum TriggerType triggerType, char* path, const char* format, ...) { unsigned int clientDataSize = 0; + unsigned int clientDataPrefixSize = 0; char* clientData = NULL; - clientDataSize = snprintf(NULL, 0, "%d;%s;%d;%s", triggerType, path, getpid(), value) + 1; - clientData = malloc(clientDataSize); - if(clientData == NULL) - { - Trace("GetClientDataString: Failed to allocate memory for client data."); - return NULL; - } - - sprintf(clientData, "%d;%s;%d;%s", triggerType, path, getpid(), value); - - return clientData; -} - -//------------------------------------------------------------------------------------- -// -// GetClientDataInt -// -// Helper that fetches client data where the value is an 'int'. -// -//------------------------------------------------------------------------------------- -char* GetClientDataInt(enum TriggerType triggerType, char* path, int value) -{ - unsigned int clientDataSize = 0; - char* clientData = NULL; + va_list args; + va_start(args, format); - clientDataSize = snprintf(NULL, 0, "%d;%s;%d;%d", triggerType, path, getpid(), value) + 1; + clientDataPrefixSize = snprintf(NULL, 0, "%d;%s;%d;", triggerType, path, getpid()); + va_list args_copy; + va_copy(args_copy, args); + clientDataSize = clientDataPrefixSize + vsnprintf(NULL, 0, format, args_copy) + 1; + va_end(args_copy); clientData = malloc(clientDataSize); if(clientData == NULL) { - Trace("GetClientDataInt: Failed to allocate memory for client data."); + Trace("GetClientDataHelper: Failed to allocate memory for client data."); return NULL; } - sprintf(clientData, "%d;%s;%d;%d", triggerType, path, getpid(), value); + sprintf(clientData, "%d;%s;%d;", triggerType, path, getpid()); + vsprintf(clientData+clientDataPrefixSize, format, args); + va_end(args); return clientData; } diff --git a/src/ProcDumpConfiguration.c b/src/ProcDumpConfiguration.c index e3cda96..724b3f9 100644 --- a/src/ProcDumpConfiguration.c +++ b/src/ProcDumpConfiguration.c @@ -403,9 +403,59 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) 0 == strcasecmp( argv[i], "-gcm" )) { if( i+1 >= argc || self->MemoryThresholdCount != -1) return PrintUsage(); - self->MemoryThreshold = GetSeparatedValues(argv[i+1], ",", &self->MemoryThresholdCount); + if(strchr(argv[i+1], ':') != NULL) + { + char* token = NULL; + char* copy = strdup(argv[i+1]); + if(copy == NULL) + { + Trace("Failed to strdup."); + Log(error, INTERNAL_ERROR); + return 1; + } - if(self->MemoryThreshold == NULL || self->MemoryThresholdCount == 0) return PrintUsage(); + token = strtok(copy, ":"); + if(token != NULL) + { + if(!ConvertToInt(token, &self->DumpGCGeneration)) + { + if(strcasecmp(token, "loh") == 0) + { + self->DumpGCGeneration = 3; + } + else if(strcasecmp(token, "poh") == 0) + { + self->DumpGCGeneration = 4; + } + else + { + free(copy); + return PrintUsage(); + } + } + + token = strtok(NULL, ":"); + if(token == NULL) + { + free(copy); + return PrintUsage(); + } + + self->MemoryThreshold = GetSeparatedValues(token, ",", &self->MemoryThresholdCount); + } + else + { + free(copy); + return PrintUsage(); + } + + free(copy); + } + else + { + self->DumpGCGeneration = CUMULATIVE_GC_SIZE; // Indicates that we want to check against total managed heap size (across all generations) + self->MemoryThreshold = GetSeparatedValues(argv[i+1], ",", &self->MemoryThresholdCount); + } for(int i = 0; i < self->MemoryThresholdCount; i++) { @@ -417,6 +467,13 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) } } + if(self->DumpGCGeneration < 0 || (self->DumpGCGeneration > MAX_GC_GEN+2 && self->DumpGCGeneration != CUMULATIVE_GC_SIZE)) // +2 for LOH and POH + { + Log(error, "Invalid GC generation or heap specified."); + free(self->MemoryThreshold); + return PrintUsage(); + } + dotnetTriggerCount++; self->bMonitoringGCMemory = true; i++; @@ -426,7 +483,7 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) { if( i+1 >= argc || self->DumpGCGeneration != -1 ) return PrintUsage(); if(!ConvertToInt(argv[i+1], &self->DumpGCGeneration)) return PrintUsage(); - if(self->DumpGCGeneration < 0) + if(self->DumpGCGeneration < 0 || self->DumpGCGeneration > MAX_GC_GEN) { Log(error, "Invalid GC generation specified."); return PrintUsage(); @@ -959,7 +1016,7 @@ int PrintUsage() printf(" [-s Seconds]\n"); printf(" [-c|-cl CPU_Usage]\n"); printf(" [-m|-ml Commit_Usage1[,Commit_Usage2...]]\n"); - printf(" [-gcm Memory_Usage1[,Memory_Usage2...]]\n"); + printf(" [-gcm [: | LOH: | POH:]Memory_Usage1[,Memory_Usage2...]]\n"); printf(" [-gcgen Generation\n"); printf(" [-tc Thread_Threshold]\n"); printf(" [-fc FileDescriptor_Threshold]\n"); @@ -980,7 +1037,7 @@ int PrintUsage() printf(" -cl CPU threshold below which to create a dump of the process.\n"); printf(" -m Memory commit threshold(s) (MB) above which to create dumps.\n"); printf(" -ml Memory commit threshold(s) (MB) below which to create dumps.\n"); - printf(" -gcm [.NET] GC memory threshold(s) (MB) above which to create dumps.\n"); + printf(" -gcm [.NET] GC memory threshold(s) (MB) above which to create dumps for the specified generation or heap (default is total .NET memory usage).\n"); printf(" -gcgen [.NET] Create dump when the garbage collection of the specified generation starts and finishes.\n"); printf(" -tc Thread count threshold above which to create a dump of the process.\n"); printf(" -fc File descriptor count threshold above which to create a dump of the process.\n"); diff --git a/src/ProfilerHelpers.c b/src/ProfilerHelpers.c index f0088cb..ccb232b 100644 --- a/src/ProfilerHelpers.c +++ b/src/ProfilerHelpers.c @@ -130,7 +130,7 @@ int LoadProfiler(pid_t pid, char* clientData) payloadSize += sizeof(clientDataSize); payloadSize += clientDataSize*sizeof(unsigned char); - Trace("LoadProfiler: Exception list: %s", clientData); + Trace("LoadProfiler: client data: %s", clientData); uint16_t totalPacketSize = sizeof(struct IpcHeader)+payloadSize; diff --git a/tests/integration/TestWebApi/Program.cs b/tests/integration/TestWebApi/Program.cs index 6fafb5b..5032ab1 100644 --- a/tests/integration/TestWebApi/Program.cs +++ b/tests/integration/TestWebApi/Program.cs @@ -1,3 +1,5 @@ +using System.Runtime.InteropServices; + var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); @@ -13,12 +15,31 @@ app.MapGet("/memincrease", () => { + // Gen2 var myList = new List(); - myList.Add(new byte[15000000]); + for(int i = 0; i < 1000; i++) + { + myList.Add(new byte[10000]); + } + // Promote to Gen2 + GC.Collect(); + GC.Collect(); + + // LOH + var myLOHList = new List(); + myLOHList.Add(new byte[15000000]); + System.GC.Collect(); + myLOHList.Add(new byte[15000000]); + System.GC.Collect(); + myLOHList.Add(new byte[15000000]); + System.GC.Collect(); + + // POH + var p1 = GC.AllocateArray(15000000, pinned: true); System.GC.Collect(); - myList.Add(new byte[15000000]); + var p2 = GC.AllocateArray(15000000, pinned: true); System.GC.Collect(); - myList.Add(new byte[15000000]); + var p3 = GC.AllocateArray(15000000, pinned: true); System.GC.Collect(); }); diff --git a/tests/integration/scenarios/dotnet_LOH_thresholds_3_dumps.sh b/tests/integration/scenarios/dotnet_LOH_thresholds_3_dumps.sh new file mode 100755 index 0000000..ae26053 --- /dev/null +++ b/tests/integration/scenarios/dotnet_LOH_thresholds_3_dumps.sh @@ -0,0 +1,73 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; +PROCDUMPPATH=$(readlink -m "$DIR/../../../bin/procdump"); +TESTWEBAPIPATH=$(readlink -m "$DIR/../TestWebApi"); +HELPERS=$(readlink -m "$DIR/../helpers.sh"); + +source $HELPERS + +pushd . +cd $TESTWEBAPIPATH +rm -rf *TestWebApi_*gc_size_* +dotnet run --urls=http://localhost:5032& + +# waiting TestWebApi ready to service +waitforurl http://localhost:5032/throwinvalidoperation +if [ $? -eq -1 ]; then + pkill -9 TestWebApi + popds + exit 1 +fi + +# Wait for 3 GC commit thresholds (10, 20 and 30MB) +sudo $PROCDUMPPATH -log -gcm LOH:10,20,30 -w TestWebApi& + +# waiting for procdump child process +PROCDUMPCHILDPID=-1 +waitforprocdump PROCDUMPCHILDPID +if [ $PROCDUMPCHILDPID -eq -1 ]; then + pkill -9 TestWebApi + pkill -9 procdump + popd + exit 1 +fi + +TESTCHILDPID=$(ps -o pid= -C "TestWebApi" | tr -d ' ') + +#make sure procdump ready to capture before throw exception by checking if socket created +SOCKETPATH=-1 +waitforprocdumpsocket $PROCDUMPCHILDPID $TESTCHILDPID SOCKETPATH +if [ $SOCKETPATH -eq -1 ]; then + pkill -9 TestWebApi + pkill -9 procdump + popd + exit 1 +fi +echo "SOCKETPATH: "$SOCKETPATH + +wget -O /dev/null http://localhost:5032/memincrease + +sudo pkill -9 procdump +COUNT=( $(ls *TestWebApi_*gc_size_* | wc -l) ) +if [ -S $SOCKETPATH ]; +then + rm $SOCKETPATH +fi + +if [[ "$COUNT" -eq 3 ]]; then + rm -rf *TestWebApi_*gc_size_* + popd + + #check to make sure profiler so is unloaded + PROF="$(cat /proc/${TESTCHILDPID}/maps | awk '{print $6}' | grep '\procdumpprofiler.so' | uniq)" + pkill -9 TestWebApi + if [[ "$PROF" == "procdumpprofiler.so" ]]; then + exit 1 + else + exit 0 + fi +else + pkill -9 TestWebApi + popd + exit 1 +fi diff --git a/tests/integration/scenarios/dotnet_POH_thresholds_3_dumps.sh b/tests/integration/scenarios/dotnet_POH_thresholds_3_dumps.sh new file mode 100755 index 0000000..6ac079f --- /dev/null +++ b/tests/integration/scenarios/dotnet_POH_thresholds_3_dumps.sh @@ -0,0 +1,73 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; +PROCDUMPPATH=$(readlink -m "$DIR/../../../bin/procdump"); +TESTWEBAPIPATH=$(readlink -m "$DIR/../TestWebApi"); +HELPERS=$(readlink -m "$DIR/../helpers.sh"); + +source $HELPERS + +pushd . +cd $TESTWEBAPIPATH +rm -rf *TestWebApi_*gc_size_* +dotnet run --urls=http://localhost:5032& + +# waiting TestWebApi ready to service +waitforurl http://localhost:5032/throwinvalidoperation +if [ $? -eq -1 ]; then + pkill -9 TestWebApi + popds + exit 1 +fi + +# Wait for 3 GC commit thresholds (10, 20 and 30MB) +sudo $PROCDUMPPATH -log -gcm POH:10,20,30 -w TestWebApi& + +# waiting for procdump child process +PROCDUMPCHILDPID=-1 +waitforprocdump PROCDUMPCHILDPID +if [ $PROCDUMPCHILDPID -eq -1 ]; then + pkill -9 TestWebApi + pkill -9 procdump + popd + exit 1 +fi + +TESTCHILDPID=$(ps -o pid= -C "TestWebApi" | tr -d ' ') + +#make sure procdump ready to capture before throw exception by checking if socket created +SOCKETPATH=-1 +waitforprocdumpsocket $PROCDUMPCHILDPID $TESTCHILDPID SOCKETPATH +if [ $SOCKETPATH -eq -1 ]; then + pkill -9 TestWebApi + pkill -9 procdump + popd + exit 1 +fi +echo "SOCKETPATH: "$SOCKETPATH + +wget -O /dev/null http://localhost:5032/memincrease + +sudo pkill -9 procdump +COUNT=( $(ls *TestWebApi_*gc_size_* | wc -l) ) +if [ -S $SOCKETPATH ]; +then + rm $SOCKETPATH +fi + +if [[ "$COUNT" -eq 3 ]]; then + rm -rf *TestWebApi_*gc_size_* + popd + + #check to make sure profiler so is unloaded + PROF="$(cat /proc/${TESTCHILDPID}/maps | awk '{print $6}' | grep '\procdumpprofiler.so' | uniq)" + pkill -9 TestWebApi + if [[ "$PROF" == "procdumpprofiler.so" ]]; then + exit 1 + else + exit 0 + fi +else + pkill -9 TestWebApi + popd + exit 1 +fi diff --git a/tests/integration/scenarios/dotnet_gen_2_thresholds_3_dumps.sh b/tests/integration/scenarios/dotnet_gen_2_thresholds_3_dumps.sh new file mode 100755 index 0000000..a42e57b --- /dev/null +++ b/tests/integration/scenarios/dotnet_gen_2_thresholds_3_dumps.sh @@ -0,0 +1,73 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; +PROCDUMPPATH=$(readlink -m "$DIR/../../../bin/procdump"); +TESTWEBAPIPATH=$(readlink -m "$DIR/../TestWebApi"); +HELPERS=$(readlink -m "$DIR/../helpers.sh"); + +source $HELPERS + +pushd . +cd $TESTWEBAPIPATH +rm -rf *TestWebApi_*gc_size_* +dotnet run --urls=http://localhost:5032& + +# waiting TestWebApi ready to service +waitforurl http://localhost:5032/throwinvalidoperation +if [ $? -eq -1 ]; then + pkill -9 TestWebApi + popds + exit 1 +fi + +# Wait for 3 GC commit thresholds (10, 20 and 30MB) +sudo $PROCDUMPPATH -log -gcm 2:4,6,8 -w TestWebApi& + +# waiting for procdump child process +PROCDUMPCHILDPID=-1 +waitforprocdump PROCDUMPCHILDPID +if [ $PROCDUMPCHILDPID -eq -1 ]; then + pkill -9 TestWebApi + pkill -9 procdump + popd + exit 1 +fi + +TESTCHILDPID=$(ps -o pid= -C "TestWebApi" | tr -d ' ') + +#make sure procdump ready to capture before throw exception by checking if socket created +SOCKETPATH=-1 +waitforprocdumpsocket $PROCDUMPCHILDPID $TESTCHILDPID SOCKETPATH +if [ $SOCKETPATH -eq -1 ]; then + pkill -9 TestWebApi + pkill -9 procdump + popd + exit 1 +fi +echo "SOCKETPATH: "$SOCKETPATH + +wget -O /dev/null http://localhost:5032/memincrease + +sudo pkill -9 procdump +COUNT=( $(ls *TestWebApi_*gc_size_* | wc -l) ) +if [ -S $SOCKETPATH ]; +then + rm $SOCKETPATH +fi + +if [[ "$COUNT" -eq 3 ]]; then + rm -rf *TestWebApi_*gc_size_* + popd + + #check to make sure profiler so is unloaded + PROF="$(cat /proc/${TESTCHILDPID}/maps | awk '{print $6}' | grep '\procdumpprofiler.so' | uniq)" + pkill -9 TestWebApi + if [[ "$PROF" == "procdumpprofiler.so" ]]; then + exit 1 + else + exit 0 + fi +else + pkill -9 TestWebApi + popd + exit 1 +fi From 375a8423bce563a0192149ff6897223f1b3fd4ee Mon Sep 17 00:00:00 2001 From: Mario Hewardt Date: Mon, 17 Jul 2023 14:20:57 -0700 Subject: [PATCH 2/2] Add another GC.Collect for test --- tests/integration/TestWebApi/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/TestWebApi/Program.cs b/tests/integration/TestWebApi/Program.cs index 5032ab1..f04750a 100644 --- a/tests/integration/TestWebApi/Program.cs +++ b/tests/integration/TestWebApi/Program.cs @@ -24,6 +24,7 @@ // Promote to Gen2 GC.Collect(); GC.Collect(); + GC.Collect(); // LOH var myLOHList = new List();