diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 834790a..f2948c4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,5 +5,14 @@ "build": { // If you want to use a different docker file (such as Dockerfile_Rocky) you can specify it here "dockerfile": "Dockerfile_Ubuntu" + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.makefile-tools" + ] + } } } diff --git a/Makefile b/Makefile index a464c5f..8e48ec4 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ install: cp procdump.1 $(DESTDIR)$(MANDIR) $(OBJDIR)/ProcDumpProfiler.so: $(PROFSRCDIR)/ClassFactory.cpp $(PROFSRCDIR)/ProcDumpProfiler.cpp $(PROFSRCDIR)/dllmain.cpp $(PROFSRCDIR)/corprof_i.cpp $(PROFSRCDIR)/easylogging++.cc | $(OBJDIR) - $(PROFCLANG) -o $@ $(PROFCXXFLAGS) -I $(PROFINCDIR) $^ + $(PROFCLANG) -o $@ $(PROFCXXFLAGS) -I $(PROFINCDIR) -I $(INCDIR) $^ ld -r -b binary -o $(OBJDIR)/ProcDumpProfiler.o $(OBJDIR)/ProcDumpProfiler.so $(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR) diff --git a/README.md b/README.md index 9cf05b0..a00a1d6 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ procdump [-n Count] [-s Seconds] [-c|-cl CPU_Usage] [-m|-ml Commit_Usage1[,Commit_Usage2,...]] + [-gcm Memory_Usage1[,Memory_Usage2...]] [-tc Thread_Threshold] [-fc FileDescriptor_Threshold] [-sig Signal_Number] @@ -42,8 +43,9 @@ Options: -s Consecutive seconds before dump is written (default is 10). -c CPU threshold above which to create a dump of the process. -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. + -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. -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. -sig Signal number to intercept to create a dump of the process. @@ -90,6 +92,10 @@ 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. +``` +sudo procdump -gcm 100,200 1234 +``` The following will create a core dump in the `/tmp` directory immediately. ``` sudo procdump 1234 /tmp diff --git a/include/Includes.h b/include/Includes.h index 931af82..b4e29b7 100644 --- a/include/Includes.h +++ b/include/Includes.h @@ -1,3 +1,4 @@ +#include "ProfilerCommon.h" #include "CoreDumpWriter.h" #include "Events.h" #include "GenHelpers.h" diff --git a/include/Monitor.h b/include/Monitor.h index 77c845a..98efcfb 100644 --- a/include/Monitor.h +++ b/include/Monitor.h @@ -30,6 +30,9 @@ bool IsQuit(struct ProcDumpConfiguration *self); int SetQuit(struct ProcDumpConfiguration *self, int quit); bool ContinueMonitoring(struct ProcDumpConfiguration *self); bool BeginMonitoring(struct ProcDumpConfiguration *self); +bool MonitorDotNet(struct ProcDumpConfiguration *self); +char* GetThresholds(struct ProcDumpConfiguration *self); +char* GetClientData(struct ProcDumpConfiguration *self, char* fullDumpPath); // Monitor worker threads void *CommitMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); @@ -38,7 +41,7 @@ void *ThreadCountMonitoringThread(void *thread_args /* struct ProcDumpConfigurat void *FileDescriptorCountMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); void *SignalMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); void *TimerThread(void *thread_args /* struct ProcDumpConfiguration* */); -void *ExceptionMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); +void *DotNetMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */); void *ProcessMonitor(void *thread_args /* struct ProcDumpConfiguration* */); void *WaitForProfilerCompletion(void *thread_args /* struct ProcDumpConfiguration* */); diff --git a/include/ProcDumpConfiguration.h b/include/ProcDumpConfiguration.h index eed347d..cd3b04d 100644 --- a/include/ProcDumpConfiguration.h +++ b/include/ProcDumpConfiguration.h @@ -43,17 +43,6 @@ // Structs // ------------------- -enum TriggerType -{ - Processor, - Commit, - Timer, - Signal, - ThreadCount, - FileDescriptorCount, - Exception -}; - struct TriggerThread { pthread_t thread; @@ -96,8 +85,10 @@ struct ProcDumpConfiguration { int CpuThreshold; // -c bool bCpuTriggerBelowValue; // -cl int* MemoryThreshold; // -m + int MemoryThresholdCount; int MemoryCurrentThreshold; bool bMemoryTriggerBelowValue; // -m or -ml + bool bMonitoringGCMemory; // -gcm int ThresholdSeconds; // -s bool bTimerThreshold; // -s int NumberOfDumpsToCollect; // -n diff --git a/include/ProfilerCommon.h b/include/ProfilerCommon.h new file mode 100644 index 0000000..5018552 --- /dev/null +++ b/include/ProfilerCommon.h @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +//-------------------------------------------------------------------- +// +// Profiler Common +// +//-------------------------------------------------------------------- +#ifndef PROFILERCOMMON_H +#define PROFILERCOMMON_H + +enum TriggerType +{ + Processor, + Commit, + Timer, + Signal, + ThreadCount, + FileDescriptorCount, + Exception, + GCThreshold +}; + +#endif // PROFILERCOMMON_H \ No newline at end of file diff --git a/include/ProfilerHelpers.h b/include/ProfilerHelpers.h index 1c065c3..e300b2d 100644 --- a/include/ProfilerHelpers.h +++ b/include/ProfilerHelpers.h @@ -14,8 +14,8 @@ #define PROFILER_FILE_NAME "procdumpprofiler.so" #define PROFILER_GUID "{cf0d821e-299b-5307-a3d8-b283c03916dd}" -int InjectProfiler(pid_t pid, char* filter, char* fullDumpPath); -int LoadProfiler(pid_t pid, char* filter, char* fullDumpPath); +int InjectProfiler(pid_t pid, char* clientData); +int LoadProfiler(pid_t pid, char* clientData); int ExtractProfiler(); char* GetEncodedExceptionFilter(char* exceptionFilterCmdLine, unsigned int numDumps); diff --git a/profiler/inc/ProcDumpProfiler.h b/profiler/inc/ProcDumpProfiler.h index 9f05441..d1f8596 100644 --- a/profiler/inc/ProcDumpProfiler.h +++ b/profiler/inc/ProcDumpProfiler.h @@ -19,6 +19,7 @@ #include "cor.h" #include "corprof.h" #include "profilerstring.h" +#include "ProfilerCommon.h" #include "easylogging++.h" #define DETACH_TIMEOUT 30000 @@ -103,16 +104,20 @@ class CorProfiler : public ICorProfilerCallback8 ICorProfilerInfo* corProfilerInfo; ICorProfilerInfo8* corProfilerInfo8; std::vector exceptionMonitorList; + std::vector gcMemoryThresholdMonitorList; pthread_t healthThread; std::string processName; std::string fullDumpPath; pthread_mutex_t endDumpCondition; + enum TriggerType triggerType; + int currentThresholdIndex; + bool gen2Collection; String GetExceptionName(ObjectID objectId); String GetExceptionMessage(ObjectID objectId); - bool ParseClientData(char* fw); + bool ParseClientData(char* clientData); WCHAR* GetUint16(char* buffer); - std::string GetDumpName(uint16_t dumpCount,std::string exceptionName); + std::string GetDumpName(uint16_t dumpCount, std::string name); std::string GetProcessName(); bool GenerateCoreClrDump(char* socketName, char* dumpFileName); bool IsCoreClrProcess(pid_t pid, char** socketName); @@ -122,6 +127,8 @@ 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(); + bool WriteDumpHelper(std::string dumpName); public: CorProfiler(); diff --git a/profiler/src/ProcDumpProfiler.cpp b/profiler/src/ProcDumpProfiler.cpp index fffb205..51b09e4 100644 --- a/profiler/src/ProcDumpProfiler.cpp +++ b/profiler/src/ProcDumpProfiler.cpp @@ -192,7 +192,7 @@ bool CorProfiler::WildcardSearch(WCHAR* szClassName, WCHAR* szSearch) //------------------------------------------------------------------------------------------------------------------------------------------------------ // CorProfiler::CorProfiler //------------------------------------------------------------------------------------------------------------------------------------------------------ -CorProfiler::CorProfiler() : refCount(0), corProfilerInfo8(nullptr), corProfilerInfo3(nullptr), corProfilerInfo(nullptr), procDumpPid(0) +CorProfiler::CorProfiler() : refCount(0), corProfilerInfo8(nullptr), corProfilerInfo3(nullptr), corProfilerInfo(nullptr), procDumpPid(0), currentThresholdIndex(0), gen2Collection(false) { // Configure logging el::Loggers::reconfigureAllLoggers(el::ConfigurationType::Filename, LOG_FILE); @@ -217,24 +217,24 @@ HRESULT STDMETHODCALLTYPE CorProfiler::InitializeForAttach(IUnknown *pCorProfile { LOG(TRACE) << "CorProfiler::InitializeForAttach: Enter"; - char* filter = new char[cbClientData+1]; - if(filter==NULL) + char* clientData = new char[cbClientData+1]; + if(clientData == NULL) { - LOG(TRACE) << "CorProfiler::InitializeForAttach: Failed to allocate memory for exception filter"; + LOG(TRACE) << "CorProfiler::InitializeForAttach: Failed to allocate memory for clientData filter"; return E_FAIL; } - memcpy(filter, pvClientData, cbClientData); - filter[cbClientData]='\0'; + memcpy(clientData, pvClientData, cbClientData); + clientData[cbClientData]='\0'; - if(ParseClientData(filter)==false) + if(ParseClientData(clientData)==false) { LOG(TRACE) << "CorProfiler::InitializeForAttach: Failed to parse client data"; - delete[] filter; + delete[] clientData; return E_FAIL; } - delete[] filter; + delete[] clientData; processName = GetProcessName(); if(processName.empty()) @@ -264,11 +264,27 @@ HRESULT STDMETHODCALLTYPE CorProfiler::InitializeForAttach(IUnknown *pCorProfile return E_FAIL; } - DWORD eventMask = COR_PRF_MONITOR_EXCEPTIONS; - HRESULT hr = this->corProfilerInfo8->SetEventMask(eventMask); - if(FAILED(hr)) + if(triggerType == Exception) { - LOG(TRACE) << "CorProfiler::InitializeForAttach: Failed to set event mask"; + HRESULT hr = this->corProfilerInfo8->SetEventMask(COR_PRF_MONITOR_EXCEPTIONS); + if(FAILED(hr)) + { + LOG(TRACE) << "CorProfiler::InitializeForAttach: Failed to set event mask: COR_PRF_MONITOR_EXCEPTIONS"; + return E_FAIL; + } + } + else if(triggerType == GCThreshold) + { + HRESULT hr = this->corProfilerInfo8->SetEventMask2(0, COR_PRF_HIGH_BASIC_GC); + if(FAILED(hr)) + { + LOG(TRACE) << "CorProfiler::InitializeForAttach: Failed to set event mask: COR_PRF_HIGH_BASIC_GC"; + return E_FAIL; + } + } + else + { + LOG(TRACE) << "CorProfiler::InitializeForAttach: Invalid trigger type found"; return E_FAIL; } @@ -310,66 +326,86 @@ WCHAR* CorProfiler::GetUint16(char* buffer) } //------------------------------------------------------------------------------------------------------------------------------------------------------ -// CorProfiler::ParseExceptionList +// CorProfiler::ParseClientData +// +// Syntax of client data: ;... // -// Syntax of client data: ;;:;:,... +// DOTNET_EXCEPTION_TRIGGER;;;:;:,... +// DOTNET_GC_THRESHOLD_TRIGGER;;;Threshold1;Threshold2,... // //------------------------------------------------------------------------------------------------------------------------------------------------------ -bool CorProfiler::ParseClientData(char* filter) +bool CorProfiler::ParseClientData(char* clientData) { LOG(TRACE) << "CorProfiler::ParseClientData: Enter"; - std::stringstream exceptionFilter(filter); + std::stringstream clientDataStream(clientData); std::string segment; - std::vector exclist; + std::vector dataList; - while(std::getline(exceptionFilter, segment, ';')) + while(std::getline(clientDataStream, segment, ';')) { - exclist.push_back(segment); + dataList.push_back(segment); } - std::vector exceptionList; int i=0; - for(std::string exception : exclist) + for(std::string dataItem : dataList) { - if(i==0) + if(i == 0) { // - // First part of the exception list is either: + // First part of list is the type of trigger that is being invoked. + // + triggerType = static_cast(std::stoi(dataItem)); + LOG(TRACE) << "CorProfiler::ParseClientData: trigger type = " << triggerType; + i++; + } + else if(i == 1) + { + // + // Second part of the list is either: // > base path to dump location (if it ends with '/') // > full path to dump file // - fullDumpPath = exception; + fullDumpPath = dataItem; LOG(TRACE) << "CorProfiler::ParseClientData: Full path to dump = " << fullDumpPath; i++; - continue; } - if(i==1) + else if(i == 2) { // - // Second part of the exception list is always the procdump pid. + // Third part of the list is always the procdump pid. // we we need this to communicate back status to procdump // - procDumpPid = std::stoi(exception); + procDumpPid = std::stoi(dataItem); LOG(TRACE) << "CorProfiler::ParseClientData: ProcDump PID = " << procDumpPid; i++; - continue; } - - - // exception filter - std::string segment2; - std::stringstream stream(exception); - ExceptionMonitorEntry entry; - entry.exceptionID = NULL; - entry.collectedDumps = 0; - std::getline(stream, segment2, ':'); - - entry.exception = segment2; - std::getline(stream, segment2, ':'); - entry.dumpsToCollect = std::stoi(segment2); - - exceptionMonitorList.push_back(entry); + else if(triggerType == Exception) + { + // exception filter + std::string segment2; + std::stringstream stream(dataItem); + ExceptionMonitorEntry entry; + entry.exceptionID = NULL; + entry.collectedDumps = 0; + std::getline(stream, segment2, ':'); + + entry.exception = segment2; + std::getline(stream, segment2, ':'); + entry.dumpsToCollect = std::stoi(segment2); + + exceptionMonitorList.push_back(entry); + } + else if (triggerType == GCThreshold) + { + // GC threshold list + gcMemoryThresholdMonitorList.push_back(std::stoi(dataItem) << 20); + } + else + { + LOG(TRACE) << "CorProfiler::ParseClientData Unrecognized trigger type"; + return false; + } } for (auto & element : exceptionMonitorList) @@ -377,6 +413,11 @@ bool CorProfiler::ParseClientData(char* filter) LOG(TRACE) << "CorProfiler::ParseClientData:Exception filter " << element.exception << " with dump count set to " << std::to_string(element.dumpsToCollect); } + for (auto & element : gcMemoryThresholdMonitorList) + { + LOG(TRACE) << "CorProfiler::ParseClientData:GCMemoryThreshold " << element; + } + LOG(TRACE) << "CorProfiler::ParseClientData: Exit"; return true; } @@ -604,14 +645,14 @@ std::string CorProfiler::GetProcessName() //------------------------------------------------------------------------------------------------------------------------------------------------------ // CorProfiler::GetDumpName //------------------------------------------------------------------------------------------------------------------------------------------------------ -std::string CorProfiler::GetDumpName(u_int16_t dumpCount,std::string exceptionName) +std::string CorProfiler::GetDumpName(u_int16_t dumpCount, std::string name) { LOG(TRACE) << "CorProfiler::GetDumpName: Enter"; std::ostringstream tmp; // // If the path ends in '/' it means we have a base path and need to create the full path according to: - // /_exception_ + // /___ // if(fullDumpPath[fullDumpPath.length()-1] == '/') { @@ -631,7 +672,7 @@ std::string CorProfiler::GetDumpName(u_int16_t dumpCount,std::string exceptionNa strftime(date, 26, "%Y-%m-%d_%H:%M:%S", timerInfo); LOG(TRACE) << "CorProfiler::GetDumpName: Date/time " << date; - tmp << fullDumpPath << processName.c_str() << "_" << dumpCount << "_" << exceptionName << "_" << date; + tmp << fullDumpPath << processName.c_str() << "_" << dumpCount << "_" << name << "_" << date; LOG(TRACE) << "CorProfiler::GetDumpName: Full path name " << tmp.str(); } else @@ -649,6 +690,170 @@ std::string CorProfiler::GetDumpName(u_int16_t dumpCount,std::string exceptionNa return tmp.str(); } +//------------------------------------------------------------------------------------------------------------------------------------------------------ +// CorProfiler::GetGCHeapSize +//------------------------------------------------------------------------------------------------------------------------------------------------------ +uint64_t CorProfiler::GetGCHeapSize() +{ + LOG(TRACE) << "CorProfiler::GetGCHeapSize: Enter"; + uint64_t gcHeapSize = 0; + + ULONG nObjectRanges = 0; + bool fHeapAlloc = false; + COR_PRF_GC_GENERATION_RANGE* pObjectRanges = NULL; + const ULONG cRanges = 32; + COR_PRF_GC_GENERATION_RANGE objectRangesStackBuffer[cRanges]; + + HRESULT hr = corProfilerInfo8->GetGenerationBounds(cRanges, &nObjectRanges, objectRangesStackBuffer); + if (FAILED(hr)) + { + LOG(TRACE) << "CorProfiler::GetGCHeapSize: Failed calling GetGenerationBounds " << hr; + return 0; + } + + if (nObjectRanges <= cRanges) + { + pObjectRanges = objectRangesStackBuffer; + } + + if (pObjectRanges == NULL) + { + pObjectRanges = new COR_PRF_GC_GENERATION_RANGE[nObjectRanges]; + if (pObjectRanges == NULL) + { + LOG(TRACE) << "CorProfiler::GetGCHeapSize: Failed to allocate memory for object ranges "; + return 0; + } + + fHeapAlloc = true; + + ULONG nObjectRanges2 = 0; + HRESULT hr = corProfilerInfo8->GetGenerationBounds(nObjectRanges, &nObjectRanges2, pObjectRanges); + if (FAILED(hr) || nObjectRanges != nObjectRanges2) + { + LOG(TRACE) << "CorProfiler::GetGCHeapSize: Failed to call GetGenerationBounds with allocated memory"; + delete[] pObjectRanges; + return 0; + } + } + + for (int i = nObjectRanges - 1; i >= 0; i--) + { + gcHeapSize += pObjectRanges[i].rangeLength; + } + + if(fHeapAlloc == true) + { + delete[] pObjectRanges; + } + + LOG(TRACE) << "CorProfiler::GetGCHeapSize: Exit"; + return gcHeapSize; +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------ +// CorProfiler::WriteDumpHelper +//------------------------------------------------------------------------------------------------------------------------------------------------------ +bool CorProfiler::WriteDumpHelper(std::string dumpName) +{ + LOG(TRACE) << "CorProfiler::WriteDumpHelper: Enter"; + AutoMutex lock = AutoMutex(&endDumpCondition); + + char* socketName = NULL; + if(IsCoreClrProcess(getpid(), &socketName)) + { + LOG(TRACE) << "CorProfiler::WriteDumpHelper: Target is .NET process"; + bool res = GenerateCoreClrDump(socketName, const_cast (dumpName.c_str())); + if(res == false) + { + LOG(TRACE) << "CorProfiler::WriteDumpHelper: Failed to generate core dump"; + delete[] socketName; + return false; + } + + // Notify procdump that a dump was generated + if(SendDumpCompletedStatus(dumpName, PROFILER_STATUS_SUCCESS) == -1) + { + LOG(TRACE) << "CorProfiler::WriteDumpHelper: Failed to notify procdump about dump creation"; + delete[] socketName; + return false; + } + + LOG(TRACE) << "CorProfiler::WriteDumpHelper: Generating core dump result: " << res; + + delete[] socketName; + } + else + { + LOG(TRACE) << "CorProfiler::WriteDumpHelper: Target process is not a .NET process"; + return false; + } + + LOG(TRACE) << "CorProfiler::WriteDumpHelper: Exit"; + return true; +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------ +// CorProfiler::GarbageCollectionStarted +//------------------------------------------------------------------------------------------------------------------------------------------------------ +HRESULT STDMETHODCALLTYPE CorProfiler::GarbageCollectionStarted(int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason) +{ + LOG(TRACE) << "CorProfiler::GarbageCollectionStarted: Enter"; + + if(gcMemoryThresholdMonitorList.size() > 0 && generationCollected[2] == true) + { + gen2Collection = true; + } + + LOG(TRACE) << "CorProfiler::GarbageCollectionStarted: Exit"; + return S_OK; +} + +//------------------------------------------------------------------------------------------------------------------------------------------------------ +// CorProfiler::GarbageCollectionFinished +//------------------------------------------------------------------------------------------------------------------------------------------------------ +HRESULT STDMETHODCALLTYPE CorProfiler::GarbageCollectionFinished() +{ + LOG(TRACE) << "CorProfiler::GarbageCollectionFinished: Enter"; + + if(gen2Collection == true) + { + // 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; + + if(currentThresholdIndex < gcMemoryThresholdMonitorList.size() && heapSize >= gcMemoryThresholdMonitorList[currentThresholdIndex]) + { + LOG(TRACE) << "CorProfiler::GarbageCollectionFinished: Current threshold value " << gcMemoryThresholdMonitorList[currentThresholdIndex]; + + std::string dump = GetDumpName(currentThresholdIndex + 1,convertString(L"gc_size")); + + // Generate dump + if(WriteDumpHelper(dump) == false) + { + SendCatastrophicFailureStatus(); + return E_FAIL; + } + else + { + currentThresholdIndex++; + } + + if(currentThresholdIndex >= gcMemoryThresholdMonitorList.size()) + { + // Stop monitoring + LOG(TRACE) << "CorProfiler::GarbageCollectionFinished: Reached last threshold "; + CleanupProfiler(); + UnloadProfiler(); + } + } + } + + LOG(TRACE) << "CorProfiler::GarbageCollectionFinished: Exit"; + return S_OK; +} + //------------------------------------------------------------------------------------------------------------------------------------------------------ // CorProfiler::ExceptionThrown //------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -685,7 +890,7 @@ HRESULT STDMETHODCALLTYPE CorProfiler::ExceptionThrown(ObjectID thrownObjectId) for (auto & element : exceptionMonitorList) { WCHAR* exception = GetUint16(const_cast (element.exception.c_str())); - if(exception==NULL) + if(exception == NULL) { LOG(TRACE) << "CorProfiler::ExceptionThrown: Unable to get exception name (WHCAR)."; SendCatastrophicFailureStatus(); @@ -694,13 +899,10 @@ HRESULT STDMETHODCALLTYPE CorProfiler::ExceptionThrown(ObjectID thrownObjectId) if(WildcardSearch(exceptionWCHARs,exception) && element.exceptionID != thrownObjectId) { - // - // We have to serialize calls to the diag pipe to avoid concurrency issues - // - AutoMutex lock = AutoMutex(&endDumpCondition); if(element.collectedDumps == element.dumpsToCollect) { LOG(TRACE) << "CorProfiler::ExceptionThrown: Dump count has been reached...exiting early"; + free(exception); return S_OK; } @@ -708,53 +910,21 @@ HRESULT STDMETHODCALLTYPE CorProfiler::ExceptionThrown(ObjectID thrownObjectId) std::string dump = GetDumpName(element.collectedDumps,convertString(exceptionName.ToWString())); - // Invoke coreclr dump generation - char* socketName = NULL; - if(IsCoreClrProcess(getpid(), &socketName)) - { - LOG(TRACE) << "CorProfiler::ExceptionThrown: Target is .NET process"; - bool res = GenerateCoreClrDump(socketName, const_cast (dump.c_str())); - if(res==false) - { - delete[] socketName; - - // If we fail to generate a core dump we consider that a catastrophic failure and terminate all monitoring - SendCatastrophicFailureStatus(); - return E_FAIL; - } - - // - // In asp.net, an exception thrown in the app is caught by asp.net and then rethrown and caught again. To avoid - // generating multiple dumps for the same exception instance we store the objectID. If we have already generated - // a dump for that object ID we simply ignore it - // - element.exceptionID = thrownObjectId; - - // Notify procdump that a dump was generated - if(SendDumpCompletedStatus(dump, PROFILER_STATUS_SUCCESS)==-1) - { - delete[] socketName; - - SendCatastrophicFailureStatus(); - return E_FAIL; - } - - LOG(TRACE) << "CorProfiler::ExceptionThrown: Generating core dump result: " << res; - - if(res) - { - element.collectedDumps++; - } - - delete[] socketName; - } - else + if(WriteDumpHelper(dump) == false) { - LOG(TRACE) << "CorProfiler::ExceptionThrown: Unable to create dump"; SendCatastrophicFailureStatus(); + free(exception); return E_FAIL; } + // + // In asp.net, an exception thrown in the app is caught by asp.net and then rethrown and caught again. To avoid + // generating multiple dumps for the same exception instance we store the objectID. If we have already generated + // a dump for that object ID we simply ignore it + // + element.exceptionID = thrownObjectId; + + element.collectedDumps++; if(element.collectedDumps == element.dumpsToCollect) { // We're done collecting dumps... @@ -1670,21 +1840,11 @@ HRESULT STDMETHODCALLTYPE CorProfiler::ThreadNameChanged(ThreadID threadId, ULON return S_OK; } -HRESULT STDMETHODCALLTYPE CorProfiler::GarbageCollectionStarted(int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason) -{ - return S_OK; -} - HRESULT STDMETHODCALLTYPE CorProfiler::SurvivingReferences(ULONG cSurvivingObjectIDRanges, ObjectID objectIDRangeStart[], ULONG cObjectIDRangeLength[]) { return S_OK; } -HRESULT STDMETHODCALLTYPE CorProfiler::GarbageCollectionFinished() -{ - return S_OK; -} - HRESULT STDMETHODCALLTYPE CorProfiler::FinalizeableObjectQueued(DWORD finalizerFlags, ObjectID objectID) { return S_OK; diff --git a/src/Monitor.c b/src/Monitor.c index 9afd7b7..5adabdf 100644 --- a/src/Monitor.c +++ b/src/Monitor.c @@ -500,6 +500,20 @@ void MonitorProcesses(struct ProcDumpConfiguration *self) } } +//-------------------------------------------------------------------- +// +// MonitorDotNet - Returns true if we are monitoring a dotnet process +// +//-------------------------------------------------------------------- +bool MonitorDotNet(struct ProcDumpConfiguration *self) +{ + if(self->bDumpOnException || self->bMonitoringGCMemory) + { + return true; + } + + return false; +} //-------------------------------------------------------------------- // @@ -513,13 +527,13 @@ int CreateMonitorThreads(struct ProcDumpConfiguration *self) bool tooManyTriggers = false; // create threads - if (self->bDumpOnException) + if (MonitorDotNet(self) == true) { if (self->nThreads < MAX_TRIGGERS) { - if ((rc = pthread_create(&self->Threads[self->nThreads].thread, NULL, ExceptionMonitoringThread, (void *)self)) != 0) + if ((rc = pthread_create(&self->Threads[self->nThreads].thread, NULL, DotNetMonitoringThread, (void *)self)) != 0) { - Trace("CreateMonitorThreads: failed to create ExceptionMonitoringThread."); + Trace("CreateMonitorThreads: failed to create DotNetMonitoringThread."); return rc; } @@ -552,7 +566,7 @@ int CreateMonitorThreads(struct ProcDumpConfiguration *self) } } - if (self->MemoryThreshold != NULL && !tooManyTriggers) + if (self->MemoryThreshold != NULL && !tooManyTriggers && self->bMonitoringGCMemory == false) { if (self->nThreads < MAX_TRIGGERS) { @@ -1231,38 +1245,36 @@ void *TimerThread(void *thread_args /* struct ProcDumpConfiguration* */) //-------------------------------------------------------------------- // -// ExceptionMonitoringThread - Thread that creates dumps based on -// exception filter. NOTE: .NET only. +// DotNetMonitoringThread - Thread that creates dumps based on +// dotnet triggers. +// +// NOTE: .NET only. +// NOTE: At the moment, .NET triggers are mutually exclusive meaning +// only one can be specified at a time. For example, you cannot specify +// both exception based monitoring and GC based monitoring. // //-------------------------------------------------------------------- -void *ExceptionMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) +void *DotNetMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) { - Trace("ExceptionMonitoring: Starting ExceptionMonitoring Thread"); + Trace("DotNetMonitoringThread: Starting DotNetMonitoringThread Thread"); struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; - auto_free char* exceptionFilter = NULL; auto_free char* fullDumpPath = NULL; auto_cancel_thread pthread_t waitForProfilerCompletion = -1; + auto_free char* clientData = NULL; int rc = 0; if ((rc = WaitForQuitOrEvent(config, &config->evtStartMonitoring, INFINITE_WAIT)) == WAIT_OBJECT_0 + 1) { - exceptionFilter = GetEncodedExceptionFilter(config->ExceptionFilter, config->NumberOfDumpsToCollect); - if(exceptionFilter==NULL) - { - Trace("ExceptionMonitoring: Failed to get exception filter."); - return NULL; - } - - if(config->CoreDumpName==NULL) + if(config->CoreDumpName == NULL) { // We don't have a dump name so we just use the path (append a '/' to indicate its a base path) if(config->CoreDumpPath[strlen(config->CoreDumpPath)-1] != '/') { fullDumpPath = malloc(strlen(config->CoreDumpPath) + 2); // +1 = '\0', +1 = '/' - if(fullDumpPath==NULL) + if(fullDumpPath == NULL) { - Trace("ExceptionMonitoring: Failed to allocate memory."); + Trace("DotNetMonitoringThread: Failed to allocate memory."); return NULL; } @@ -1271,9 +1283,9 @@ void *ExceptionMonitoringThread(void *thread_args /* struct ProcDumpConfiguratio else { fullDumpPath = malloc(strlen(config->CoreDumpPath) + 1); - if(fullDumpPath==NULL) + if(fullDumpPath == NULL) { - Trace("ExceptionMonitoring: Failed to allocate memory."); + Trace("DotNetMonitoringThread: Failed to allocate memory."); return NULL; } @@ -1286,9 +1298,9 @@ void *ExceptionMonitoringThread(void *thread_args /* struct ProcDumpConfiguratio if(config->CoreDumpPath[strlen(config->CoreDumpPath)] != '/') { fullDumpPath = malloc(strlen(config->CoreDumpPath) + strlen(config->CoreDumpName) + 2); // +1 = '\0', +1 = '/' - if(fullDumpPath==NULL) + if(fullDumpPath == NULL) { - Trace("ExceptionMonitoring: Failed to allocate memory."); + Trace("DotNetMonitoringThread: Failed to allocate memory."); return NULL; } @@ -1297,9 +1309,9 @@ void *ExceptionMonitoringThread(void *thread_args /* struct ProcDumpConfiguratio else { fullDumpPath = malloc(strlen(config->CoreDumpPath) + strlen(config->CoreDumpName) + 1); // +1 = '\0', +1 = '/' - if(fullDumpPath==NULL) + if(fullDumpPath == NULL) { - Trace("ExceptionMonitoring: Failed to allocate memory."); + Trace("DotNetMonitoringThread: Failed to allocate memory."); return NULL; } @@ -1310,7 +1322,7 @@ void *ExceptionMonitoringThread(void *thread_args /* struct ProcDumpConfiguratio // Create thread to wait for profiler completion if ((pthread_create(&waitForProfilerCompletion, NULL, WaitForProfilerCompletion, (void *) config)) != 0) { - Trace("ExceptionMonitoring: failed to create WaitForProfilerCompletion thread."); + Trace("DotNetMonitoringThread: failed to create WaitForProfilerCompletion thread."); return NULL; } @@ -1322,20 +1334,146 @@ void *ExceptionMonitoringThread(void *thread_args /* struct ProcDumpConfiguratio } pthread_mutex_unlock(&config->dotnetMutex); + // Get the corresponding client data to be sent to profiler + clientData = GetClientData(config, fullDumpPath); + if(clientData == NULL) + { + Trace("DotNetMonitoringThread: Failed to get client data."); + return NULL; + } + // Inject the profiler into the target process - if(InjectProfiler(config->ProcessId, exceptionFilter, fullDumpPath)!=0) + if(InjectProfiler(config->ProcessId, clientData)!=0) { - Trace("ExceptionMonitoring: Failed to inject the profiler."); + Trace("DotNetMonitoringThread: Failed to inject the profiler."); pthread_cancel(waitForProfilerCompletion); } pthread_join(waitForProfilerCompletion, NULL); } - Trace("ExceptionMonitoring: Exiting ExceptionMonitoring Thread"); + Trace("DotNetMonitoringThread: Exiting DotNetMonitoringThread Thread"); return NULL; } +//------------------------------------------------------------------------------------- +// +// GetClientData +// +// Gets the client data string depending on which triggers were requested in the +// specified config. +// +//------------------------------------------------------------------------------------- +char* GetClientData(struct ProcDumpConfiguration *self, char* fullDumpPath) +{ + Trace("GetClientData: Entering GetClientData"); + char* clientData = NULL; + auto_free char* exceptionFilter = NULL; + auto_free char* thresholds = NULL; + unsigned int clientDataSize = 0; + + if(self->bDumpOnException) + { + // exception_trigger;;;:;:,... + exceptionFilter = GetEncodedExceptionFilter(self->ExceptionFilter, self->NumberOfDumpsToCollect); + if(exceptionFilter == NULL) + { + Trace("GetClientData: Failed to get exception filter."); + return NULL; + } + + clientDataSize = snprintf(NULL, 0, "%d;%s;%d;%s", Exception, fullDumpPath, getpid(), exceptionFilter) + 1; + clientData = malloc(clientDataSize); + if(clientData == NULL) + { + Trace("GetClientData: Failed to allocate memory for client data."); + return NULL; + } + + sprintf(clientData, "%d;%s;%d;%s", Exception, fullDumpPath, getpid(), exceptionFilter); + } + else if (self->bMonitoringGCMemory) + { + // GC Memory trigger (-gcm);;;Threshold1;Threshold2,... + thresholds = GetThresholds(self); + if(thresholds == NULL) + { + Trace("GetClientData: Failed to get thresholds."); + return NULL; + } + + clientDataSize = snprintf(NULL, 0, "%d;%s;%d;%s", GCThreshold, fullDumpPath, getpid(), thresholds) + 1; + clientData = malloc(clientDataSize); + if(clientData == NULL) + { + Trace("GetClientData: Failed to allocate memory for client data."); + return NULL; + } + + sprintf(clientData, "%d;%s;%d;%s", GCThreshold, fullDumpPath, getpid(), thresholds); + } + else + { + Trace("GetClientData: Invalid trigger specified"); + return NULL; + } + + Trace("GetClientData: Exiting GetClientData"); + return clientData; +} + +//------------------------------------------------------------------------------------- +// +// GetThresholds +// +// Returns a ; separated string of GC mem thresholds specified in +// self->MemoryThreshold +// +//------------------------------------------------------------------------------------- +char* GetThresholds(struct ProcDumpConfiguration *self) +{ + Trace("GetThresholds: Entering GetThresholds"); + int thresholdLen = 0; + char* thresholds = NULL; + + for(int i = 0; i < self->MemoryThresholdCount; i++) + { + thresholdLen += snprintf(NULL, 0, "%d", self->MemoryThreshold[i]); + if(i != self->MemoryThresholdCount - 1) + { + thresholdLen++; // Comma + } + } + + thresholdLen++; // NULL terminator + + thresholds = malloc(thresholdLen); + if(thresholds != NULL) + { + char* writePos = thresholds; + if(thresholds != NULL) + { + for(int i = 0; i < self->MemoryThresholdCount; i++) + { + int len = snprintf(writePos, thresholdLen, "%d", self->MemoryThreshold[i]); + writePos += len; + thresholdLen -= len; + if(i != self->MemoryThresholdCount - 1) + { + *writePos = ';'; + writePos++; + thresholdLen--; + } + } + } + + *(writePos) = '\0'; + } + + Trace("GetThresholds: Exiting GetThresholds"); + return thresholds; +} + //------------------------------------------------------------------------------------- // // WaitForProfilerCompletion diff --git a/src/ProcDumpConfiguration.c b/src/ProcDumpConfiguration.c index f5993d4..252e6b8 100644 --- a/src/ProcDumpConfiguration.c +++ b/src/ProcDumpConfiguration.c @@ -148,7 +148,9 @@ void InitProcDumpConfiguration(struct ProcDumpConfiguration *self) self->CpuThreshold = -1; self->bCpuTriggerBelowValue = false; self->MemoryThreshold = NULL; + self->MemoryThresholdCount = -1; self->MemoryCurrentThreshold = 0; + self->bMonitoringGCMemory = false; self->ThreadThreshold = -1; self->FileDescriptorThreshold = -1; self->SignalNumber = -1; @@ -295,6 +297,8 @@ struct ProcDumpConfiguration * CopyProcDumpConfiguration(struct ProcDumpConfigur } copy->bMemoryTriggerBelowValue = self->bMemoryTriggerBelowValue; + copy->MemoryThresholdCount = self->MemoryThresholdCount; + copy->bMonitoringGCMemory = self->bMonitoringGCMemory; copy->ThresholdSeconds = self->ThresholdSeconds; copy->bTimerThreshold = self->bTimerThreshold; copy->NumberOfDumpsToCollect = self->NumberOfDumpsToCollect; @@ -329,7 +333,7 @@ struct ProcDumpConfiguration * CopyProcDumpConfiguration(struct ProcDumpConfigur int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) { bool bProcessSpecified = false; - int numThresholds = -1; + int dotnetTriggerCount = 0; if (argc < 2) { Trace("GetOptions: Invalid number of command line arguments."); @@ -368,12 +372,12 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) 0 == strcasecmp( argv[i], "/ml" ) || 0 == strcasecmp( argv[i], "-ml" )) { - if( i+1 >= argc || numThresholds != -1 ) return PrintUsage(); - self->MemoryThreshold = GetSeparatedValues(argv[i+1], ",", &numThresholds); + if( i+1 >= argc || self->MemoryThresholdCount != -1 ) return PrintUsage(); + self->MemoryThreshold = GetSeparatedValues(argv[i+1], ",", &self->MemoryThresholdCount); - if(self->MemoryThreshold == NULL || numThresholds == 0) return PrintUsage(); + if(self->MemoryThreshold == NULL || self->MemoryThresholdCount == 0) return PrintUsage(); - for(int i = 0; i < numThresholds; i++) + for(int i = 0; i < self->MemoryThresholdCount; i++) { if(self->MemoryThreshold[i] < 0) { @@ -390,6 +394,28 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) i++; } + else if( 0 == strcasecmp( argv[i], "/gcm" ) || + 0 == strcasecmp( argv[i], "-gcm" )) + { + if( i+1 >= argc || self->MemoryThresholdCount != -1) return PrintUsage(); + self->MemoryThreshold = GetSeparatedValues(argv[i+1], ",", &self->MemoryThresholdCount); + + if(self->MemoryThreshold == NULL || self->MemoryThresholdCount == 0) return PrintUsage(); + + for(int i = 0; i < self->MemoryThresholdCount; i++) + { + if(self->MemoryThreshold[i] < 0) + { + Log(error, "Invalid memory threshold specified."); + free(self->MemoryThreshold); + return PrintUsage(); + } + } + + dotnetTriggerCount++; + self->bMonitoringGCMemory = true; + i++; + } else if( 0 == strcasecmp( argv[i], "/tc" ) || 0 == strcasecmp( argv[i], "-tc" )) { @@ -482,6 +508,8 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) else if( 0 == strcasecmp( argv[i], "/e" ) || 0 == strcasecmp( argv[i], "-e" )) { + if( i+1 >= argc) return PrintUsage(); + dotnetTriggerCount++; self->bDumpOnException = true; } else if( 0 == strcasecmp( argv[i], "/f" ) || @@ -642,16 +670,23 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) // Validate multi arguments // + // .NET triggers are mutually exclusive + if(dotnetTriggerCount > 1) + { + Log(error, "Only one .NET trigger can be specified."); + return PrintUsage(); + } + // Ensure consistency between number of thresholds specified and the -n switch - if(numThresholds > 1 && self->NumberOfDumpsToCollect != -1) + if(self->MemoryThresholdCount > 1 && self->NumberOfDumpsToCollect != -1) { Log(error, "When specifying more than one memory threshold the number of dumps switch (-n) is invalid."); return PrintUsage(); } - if(numThresholds != -1) + if(self->MemoryThresholdCount != -1) { - self->NumberOfDumpsToCollect = numThresholds; + self->NumberOfDumpsToCollect = self->MemoryThresholdCount; } // If exception filter is provided with no -e switch exit @@ -776,7 +811,14 @@ bool PrintConfiguration(struct ProcDumpConfiguration *self) } else { - printf("%-40s>= ", "Commit Threshold:"); + if(self->bMonitoringGCMemory == true) + { + printf("%-40s>= ", ".NET Memory Threshold:"); + } + else + { + printf("%-40s>= ", "Commit Threshold:"); + } } for(int i=0; iNumberOfDumpsToCollect; i++) @@ -887,6 +929,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(" [-tc Thread_Threshold]\n"); printf(" [-fc FileDescriptor_Threshold]\n"); printf(" [-sig Signal_Number]\n"); @@ -904,8 +947,9 @@ int PrintUsage() printf(" -s Consecutive seconds before dump is written (default is 10).\n"); printf(" -c CPU threshold above which to create a dump of the process.\n"); printf(" -cl CPU threshold below which to create a dump of the process.\n"); - printf(" -m Memory commit threshold in MB at which to create a dump.\n"); - printf(" -ml Trigger when memory commit drops below specified MB value.\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(" -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"); printf(" -sig Signal number to intercept to create a dump of the process.\n"); diff --git a/src/ProfilerHelpers.c b/src/ProfilerHelpers.c index 6938f09..f0088cb 100644 --- a/src/ProfilerHelpers.c +++ b/src/ProfilerHelpers.c @@ -59,7 +59,7 @@ int ExtractProfiler() // process instructing the runtime to load the profiler. // //-------------------------------------------------------------------- -int LoadProfiler(pid_t pid, char* filter, char* fullDumpPath) +int LoadProfiler(pid_t pid, char* clientData) { struct sockaddr_un addr = {0}; uint32_t attachTimeout = 5000; @@ -68,7 +68,6 @@ int LoadProfiler(pid_t pid, char* filter, char* fullDumpPath) auto_free_fd int fd = -1; auto_free char* socketName = NULL; - auto_free char* clientData = NULL; auto_free char* dumpPath = NULL; auto_free uint16_t* profilerPathW = NULL; auto_free void* temp_buffer = NULL; @@ -119,19 +118,9 @@ int LoadProfiler(pid_t pid, char* filter, char* fullDumpPath) payloadSize += profilerPathLen*sizeof(uint16_t); // client data - if(filter) + if(clientData) { - // client data in the following format: - // ;;:;:,... - clientDataSize = snprintf(NULL, 0, "%s;%d;%s", fullDumpPath, getpid(), filter) + 1; - clientData = malloc(clientDataSize); - if(clientData==NULL) - { - Trace("LoadProfiler: Failed to allocate memory for client data."); - return -1; - } - - sprintf(clientData, "%s;%d;%s", fullDumpPath, getpid(), filter); + clientDataSize = strlen(clientData) + 1; } else { @@ -261,7 +250,7 @@ int LoadProfiler(pid_t pid, char* filter, char* fullDumpPath) // cancellation requests to. // //-------------------------------------------------------------------- -int InjectProfiler(pid_t pid, char* filter, char* fullDumpPath) +int InjectProfiler(pid_t pid, char* clientData) { int ret = ExtractProfiler(); if(ret != 0) @@ -271,7 +260,7 @@ int InjectProfiler(pid_t pid, char* filter, char* fullDumpPath) return ret; } - ret = LoadProfiler(pid, filter, fullDumpPath); + ret = LoadProfiler(pid, clientData); if(ret != 0) { Log(error, "Failed to load profiler. Please make sure you are running elevated and targetting a .NET process."); diff --git a/tests/integration/TestWebApi/Program.cs b/tests/integration/TestWebApi/Program.cs index 286baa1..6fafb5b 100644 --- a/tests/integration/TestWebApi/Program.cs +++ b/tests/integration/TestWebApi/Program.cs @@ -6,6 +6,23 @@ throw new System.InvalidOperationException(); }); +app.MapGet("/fullgc", () => +{ + System.GC.Collect(); +}); + +app.MapGet("/memincrease", () => +{ + var myList = new List(); + myList.Add(new byte[15000000]); + System.GC.Collect(); + myList.Add(new byte[15000000]); + System.GC.Collect(); + myList.Add(new byte[15000000]); + System.GC.Collect(); +}); + + app.MapGet("/throwandcatchinvalidoperation", () => { try diff --git a/tests/integration/helpers.sh b/tests/integration/helpers.sh old mode 100644 new mode 100755 diff --git a/tests/integration/scenarios/dotnet_1_gc_threshold_1_dump.sh b/tests/integration/scenarios/dotnet_1_gc_threshold_1_dump.sh new file mode 100755 index 0000000..f166eb6 --- /dev/null +++ b/tests/integration/scenarios/dotnet_1_gc_threshold_1_dump.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 1 GC commit thresholds (10MB) +sudo $PROCDUMPPATH -log -gcm 10 -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 1 ]]; 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_3_gc_thresholds_3_dumps.sh b/tests/integration/scenarios/dotnet_3_gc_thresholds_3_dumps.sh new file mode 100755 index 0000000..4b721c4 --- /dev/null +++ b/tests/integration/scenarios/dotnet_3_gc_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 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