From b63e0b0d55b2039d64eccbae60e5e021cba21f46 Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Thu, 23 Jul 2020 13:55:34 -0700 Subject: [PATCH] Prototype --- src/coreclr/src/gc/gc.cpp | 8 + src/coreclr/src/gc/gc.h | 5 + src/coreclr/src/vm/CMakeLists.txt | 1 + src/coreclr/src/vm/ceemain.cpp | 4 +- .../src/vm/diagnosticstriggermanager.cpp | 386 ++++++++++++++++++ .../src/vm/diagnosticstriggermanager.h | 37 ++ .../src/vm/dumpdiagnosticprotocolhelper.cpp | 141 ++++++- .../src/vm/dumpdiagnosticprotocolhelper.h | 24 +- .../src/vm/eventpipeprotocolhelper.cpp | 80 +++- src/coreclr/src/vm/eventpipesession.cpp | 29 +- src/coreclr/src/vm/eventpipesession.h | 12 + src/coreclr/src/vm/eventtrace.cpp | 7 +- src/coreclr/src/vm/gcenv.ee.cpp | 6 + src/coreclr/src/vm/gcenv.ee.standalone.cpp | 1 + src/coreclr/src/vm/gcenv.ee.static.cpp | 1 + src/coreclr/src/vm/proftoeeinterfaceimpl.cpp | 2 +- 16 files changed, 717 insertions(+), 27 deletions(-) create mode 100644 src/coreclr/src/vm/diagnosticstriggermanager.cpp create mode 100644 src/coreclr/src/vm/diagnosticstriggermanager.h diff --git a/src/coreclr/src/gc/gc.cpp b/src/coreclr/src/gc/gc.cpp index 6bf8e51a6bcefb..0ef87ff25d544c 100644 --- a/src/coreclr/src/gc/gc.cpp +++ b/src/coreclr/src/gc/gc.cpp @@ -20636,7 +20636,15 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) { #ifdef HEAP_ANALYZE heap_analyze_enabled = FALSE; +#ifndef BUILD_AS_STANDALONE + s_forcedGCInProgress = true; +#endif + // TODO, andrewau, pass along the promoted byte count GCToEEInterface::AnalyzeSurvivorsFinished(condemned_gen_number); +#ifndef BUILD_AS_STANDALONE + s_forcedGCInProgress = false; +#endif + #endif // HEAP_ANALYZE GCToEEInterface::AfterGcScanRoots (condemned_gen_number, max_generation, &sc); diff --git a/src/coreclr/src/gc/gc.h b/src/coreclr/src/gc/gc.h index 52efaa454baeec..7813a220e9625c 100644 --- a/src/coreclr/src/gc/gc.h +++ b/src/coreclr/src/gc/gc.h @@ -169,6 +169,11 @@ extern VOLATILE(int32_t) g_fSuspensionPending; ::IGCHandleManager* CreateGCHandleManager(); +#ifndef BUILD_AS_STANDALONE +// TODO, andrewau, Make sure it also work for standalone GC +extern bool s_forcedGCInProgress; +#endif + namespace WKS { ::IGCHeapInternal* CreateGCHeap(); class GCHeap; diff --git a/src/coreclr/src/vm/CMakeLists.txt b/src/coreclr/src/vm/CMakeLists.txt index fb36f5ca9b6ee1..99c74ef75a3238 100644 --- a/src/coreclr/src/vm/CMakeLists.txt +++ b/src/coreclr/src/vm/CMakeLists.txt @@ -319,6 +319,7 @@ set(VM_SOURCES_WKS customattribute.cpp custommarshalerinfo.cpp autotrace.cpp + diagnosticstriggermanager.cpp diagnosticserver.cpp dllimportcallback.cpp dynamicinterfacecastable.cpp diff --git a/src/coreclr/src/vm/ceemain.cpp b/src/coreclr/src/vm/ceemain.cpp index d1212844c5cf1e..23a68492fe70b6 100644 --- a/src/coreclr/src/vm/ceemain.cpp +++ b/src/coreclr/src/vm/ceemain.cpp @@ -220,6 +220,8 @@ #include "gdbjit.h" #endif // FEATURE_GDBJIT +#include "diagnosticstriggermanager.h" + #ifndef CROSSGEN_COMPILE static int GetThreadUICultureId(__out LocaleIDValue* pLocale); // TODO: This shouldn't use the LCID. We should rely on name instead @@ -1205,7 +1207,7 @@ void STDMETHODCALLTYPE EEShutDownHelper(BOOL fIsDllUnloading) if (!fIsDllUnloading) { ETW::EnumerationLog::ProcessShutdown(); - + DiagnosticsTriggerManager::GetInstance().Shutdown(); #ifdef FEATURE_PERFTRACING EventPipe::Shutdown(); DiagnosticServer::Shutdown(); diff --git a/src/coreclr/src/vm/diagnosticstriggermanager.cpp b/src/coreclr/src/vm/diagnosticstriggermanager.cpp new file mode 100644 index 00000000000000..e4abd16644937d --- /dev/null +++ b/src/coreclr/src/vm/diagnosticstriggermanager.cpp @@ -0,0 +1,386 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "common.h" +#include "diagnosticstriggermanager.h" + +DiagnosticsTriggerManager DiagnosticsTriggerManager::s_instance; + +/* static */ DiagnosticsTriggerManager& DiagnosticsTriggerManager::GetInstance() +{ + return s_instance; +} + +DiagnosticsTriggerManager::DiagnosticsTriggerManager() +{ + this->survivorAnalyzers = nullptr; +} + +struct Property +{ + const WCHAR* propertyName; + const WCHAR* propertyValue; + struct Property* next; +}; +struct Property* ParsePropertyList(const WCHAR* condition); +void DeletePropertyList(struct Property* property); +const WCHAR* GetPropertyValue(struct Property* property, const WCHAR* propertyName); + +class PropertyListHolder +{ +public: + PropertyListHolder(struct Property* p) + { + this->p = p; + } + ~PropertyListHolder() + { + DeletePropertyList(this->p); + } +private: + struct Property* p; +}; + +void AndrewDebug() +{ + +} + +bool DiagnosticsTriggerManager::RegisterTrigger(const WCHAR* condition, const WCHAR* identity, IDiagnosticsTriggerAction* action) +{ + struct Property* property = ParsePropertyList(condition); + if (property == nullptr) + { + return false; + } + PropertyListHolder h(property); + + NewArrayHolder copiedIdentity = new (nothrow) WCHAR[wcslen(identity) + 1]; + if (copiedIdentity == nullptr) + { + return false; + } + wcscpy(copiedIdentity, identity); + + const WCHAR* when = GetPropertyValue(property, L"when"); + if (when == nullptr) + { + return false; + } + if (wcscmp(when, L"ongcmarkcomplete") == 0) + { + const WCHAR* genString = GetPropertyValue(property, L"gen"); + const WCHAR* promotedBytesThresholdString = GetPropertyValue(property, L"promoted_bytes_threshold"); + unsigned long gen; + if (genString == nullptr) + { + return false; + } + else + { + WCHAR* genEnd; + gen = wcstoul(genString, &genEnd, 10); + if (*genEnd != '\0') + { + return false; + } + } + unsigned long promotedBytesThreshold; + if (promotedBytesThresholdString == nullptr) + { + return false; + } + else + { + WCHAR* promotedBytesThresholdEnd; + promotedBytesThreshold = wcstoul(promotedBytesThresholdString, &promotedBytesThresholdEnd, 10); + if (*promotedBytesThresholdEnd != '\0') + { + return false; + } + } + struct SurvivorAnalyzer* temp = new (nothrow) SurvivorAnalyzer(); + if (temp == nullptr) + { + return false; + } + temp->gen = gen; + temp->promotedBytesThreshold = promotedBytesThreshold; + temp->action = action; + AndrewDebug(); + temp->identity = copiedIdentity.Extract(); + temp->next = this->survivorAnalyzers; + this->survivorAnalyzers = temp; + return true; + } + else + { + // Here is where we can add additional 'when' triggers + return false; + } +} + +bool DiagnosticsTriggerManager::UnregisterTrigger(const WCHAR* identity) +{ + AndrewDebug(); + struct SurvivorAnalyzer fakeHead; + fakeHead.next = this->survivorAnalyzers; + struct SurvivorAnalyzer* prev = &fakeHead; + struct SurvivorAnalyzer* curr = this->survivorAnalyzers; + while (curr != nullptr) + { + if (wcscmp(identity, curr->identity) == 0) + { + prev->next = curr->next; + curr->action->Cancel(); + delete curr->identity; + delete curr->action; + this->survivorAnalyzers = fakeHead.next; + return true; + } + prev = curr; + curr = curr->next; + } + return false; +} + +void DiagnosticsTriggerManager::AnalyzeSurvivors() +{ + CONTRACT_VIOLATION(ModeViolation); + struct SurvivorAnalyzer* survivorAnalyzer = this->survivorAnalyzers; + while (survivorAnalyzer != nullptr) + { + // Be careful, the variable survivorAnalyzer might be come invalid if + // the action unregister itself, therefore keeping the next pointer here + // first + struct SurvivorAnalyzer* next = survivorAnalyzer->next; + // TODO, andrewau, evaluate condition + survivorAnalyzer->action->Run(); + survivorAnalyzer = next; + } +} + +void DiagnosticsTriggerManager::Shutdown() +{ + CONTRACT_VIOLATION(ModeViolation); + AndrewDebug(); + struct SurvivorAnalyzer* survivorAnalyzer = this->survivorAnalyzers; + while (survivorAnalyzer != nullptr) + { + struct SurvivorAnalyzer* next = survivorAnalyzer->next; + survivorAnalyzer->action->Cancel(); + delete survivorAnalyzer; + survivorAnalyzer = next; + } +} + +const WCHAR* GetPropertyValue(struct Property* property, const WCHAR* propertyName) +{ + while (property != nullptr) + { + if (wcscmp(property->propertyName, propertyName) == 0) + { + return property->propertyValue; + } + property = property->next; + } + return nullptr; +} + +struct Property* ParsePropertyList(const WCHAR* condition) +{ + const WCHAR* p = condition; + struct Property* property = nullptr; + + const WCHAR* start = nullptr; + WCHAR* buffer; + int state = 0; + bool error = false; + + while (!error) + { + if (state == 0) + { + switch (*p) + { + case '\0': + p = nullptr; + break; + case ' ': + break; + case '=': + error = true; + break; + case ',': + error = true; + break; + default: + state = 1; + struct Property* temp = new (nothrow) Property(); + if (temp == nullptr) + { + error = true; + } + temp->propertyName = nullptr; + temp->propertyValue = nullptr; + temp->next = property; + property = temp; + start = p; + break; + } + } + else if (state == 1) + { + switch (*p) + { + case '\0': + error = true; + break; + case ' ': + case '=': + assert(start != nullptr); + buffer = new (nothrow) WCHAR[p - start + 1]; + if (buffer == nullptr) + { + error = true; + } + else + { + assert(property != nullptr); + memcpy(buffer, start, sizeof(WCHAR)* (p - start)); + buffer[p - start] = '\0'; + property->propertyName = buffer; + } + state = (*p) == '=' ? 3 : 2; + break; + case ',': + error = true; + break; + default: + break; + } + } + else if (state == 2) + { + switch (*p) + { + case '\0': + error = true; + break; + case ' ': + break; + case '=': + state = 3; + break; + case ',': + error = true; + break; + default: + error = true; + break; + } + } + else if (state == 3) + { + switch (*p) + { + case '\0': + error = true; + break; + case ' ': + break; + case '=': + error = true; + break; + case ',': + error = true; + break; + default: + state = 4; + start = p; + break; + } + } + else if (state == 4) + { + switch (*p) + { + case '\0': + case ' ': + case ',': + assert(start != nullptr); + buffer = new (nothrow) WCHAR[p - start + 1]; + if (buffer == nullptr) + { + error = true; + } + else + { + memcpy(buffer, start, sizeof(WCHAR)* (p - start)); + buffer[p - start] = '\0'; + property->propertyValue = buffer; + } + if (*p == '\0') + { + p = nullptr; + } + else + { + state = (*p) == ',' ? 0 : 5; + } + break; + default: + break; + } + } + else if (state == 5) + { + switch (*p) + { + case '\0': + p = nullptr; + break; + case ' ': + break; + case '=': + error = true; + break; + case ',': + state = 0; + break; + default: + error = true; + break; + } + } + if (p == nullptr) + { + break; + } + p++; + } + if (error) + { + DeletePropertyList(property); + property = nullptr; + } + return property; +} + +void DeletePropertyList(struct Property* property) +{ + while (property != nullptr) + { + struct Property* temp = property->next; + if (property->propertyName != nullptr) + { + delete[] property->propertyName; + } + if (property->propertyValue != nullptr) + { + delete[] property->propertyValue; + } + delete property; + property = temp; + } +} \ No newline at end of file diff --git a/src/coreclr/src/vm/diagnosticstriggermanager.h b/src/coreclr/src/vm/diagnosticstriggermanager.h new file mode 100644 index 00000000000000..758cec82c5c10f --- /dev/null +++ b/src/coreclr/src/vm/diagnosticstriggermanager.h @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __DIAGNOSTICS_TRIGGER_MANAGER_H__ +#define __DIAGNOSTICS_TRIGGER_MANAGER_H__ + +class IDiagnosticsTriggerAction +{ +public: + virtual void Run() = 0; + virtual void Cancel() = 0; +}; + +struct SurvivorAnalyzer +{ + int gen; + int promotedBytesThreshold; + IDiagnosticsTriggerAction* action; + WCHAR* identity; + struct SurvivorAnalyzer* next; +}; + +class DiagnosticsTriggerManager +{ +public: + static DiagnosticsTriggerManager& GetInstance(); + bool RegisterTrigger(const WCHAR* condition, const WCHAR* identity, IDiagnosticsTriggerAction* action); + bool UnregisterTrigger(const WCHAR* identity); + void AnalyzeSurvivors(); + void Shutdown(); +private: + DiagnosticsTriggerManager(); + static DiagnosticsTriggerManager s_instance; + struct SurvivorAnalyzer* survivorAnalyzers; +}; + +#endif __DIAGNOSTICS_TRIGGER_MANAGER_H__ \ No newline at end of file diff --git a/src/coreclr/src/vm/dumpdiagnosticprotocolhelper.cpp b/src/coreclr/src/vm/dumpdiagnosticprotocolhelper.cpp index 70caa5a9ac6af3..16bbccb7e8397b 100644 --- a/src/coreclr/src/vm/dumpdiagnosticprotocolhelper.cpp +++ b/src/coreclr/src/vm/dumpdiagnosticprotocolhelper.cpp @@ -6,6 +6,7 @@ #include "dumpdiagnosticprotocolhelper.h" #include "diagnosticsipc.h" #include "diagnosticsprotocol.h" +#include "diagnosticstriggermanager.h" #ifdef FEATURE_PERFTRACING @@ -25,6 +26,9 @@ void DumpDiagnosticProtocolHelper::HandleIpcMessage(DiagnosticsIpc::IpcMessage& case DumpCommandId::GenerateCoreDump: DumpDiagnosticProtocolHelper::GenerateCoreDump(message, pStream); break; + case DumpCommandId::GenerateCoreDumpV2: + DumpDiagnosticProtocolHelper::GenerateCoreDumpV2(message, pStream); + break; default: STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_WARNING, "Received unknown request type (%d)\n", message.GetHeader().CommandSet); @@ -66,6 +70,41 @@ const GenerateCoreDumpCommandPayload* GenerateCoreDumpCommandPayload::TryParse(B return payload; } +const GenerateCoreDumpCommandV2Payload* GenerateCoreDumpCommandV2Payload::TryParse(BYTE* lpBuffer, uint16_t& BufferSize) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(lpBuffer != nullptr); + } + CONTRACTL_END; + + GenerateCoreDumpCommandV2Payload* payload = new (nothrow) GenerateCoreDumpCommandV2Payload; + if (payload == nullptr) + { + // OOM + return nullptr; + } + + payload->incomingBuffer = lpBuffer; + uint8_t* pBufferCursor = payload->incomingBuffer; + uint32_t bufferLen = BufferSize; + if (!TryParseString(pBufferCursor, bufferLen, payload->dumpName) || + !::TryParse(pBufferCursor, bufferLen, payload->dumpType) || + !::TryParse(pBufferCursor, bufferLen, payload->diagnostics) || + !TryParseString(pBufferCursor, bufferLen, payload->condition) || + !TryParseString(pBufferCursor, bufferLen, payload->identity) + ) + { + delete payload; + return nullptr; + } + + return payload; +} + void DumpDiagnosticProtocolHelper::GenerateCoreDump(DiagnosticsIpc::IpcMessage& message, IpcStream* pStream) { CONTRACTL @@ -87,12 +126,108 @@ void DumpDiagnosticProtocolHelper::GenerateCoreDump(DiagnosticsIpc::IpcMessage& delete pStream; return; } + GenerateCoreDumpImpl(payload->dumpName, payload->dumpType, payload->diagnostics, pStream); +} + +class DumpAction : public IDiagnosticsTriggerAction +{ +public: + DumpAction(LPCWSTR dumpName, uint32_t dumpType, uint32_t diagnostics, LPCWSTR identity, IpcStream* pStream) + { + this->dumpName = new WCHAR[wcslen(dumpName) + 1]; + wcscpy(this->dumpName, dumpName); + this->identity = new WCHAR[wcslen(identity) + 1]; + wcscpy(this->identity, identity); + this->dumpType = dumpType; + this->diagnostics = diagnostics; + this->pStream = pStream; + } + + ~DumpAction() + { + delete[] dumpName; + delete[] identity; + } + virtual void Run() + { + DumpDiagnosticProtocolHelper::GenerateCoreDumpImpl(this->dumpName, this->dumpType, this->diagnostics, this->pStream); + this->pStream = nullptr; + DiagnosticsTriggerManager::GetInstance().UnregisterTrigger(this->identity); + } + + virtual void Cancel() + { + // TODO, andrewau, handle cancel on request + if (this->pStream != nullptr) + { + // TODO, andrewau, the correct error code is operation canceled + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); + delete pStream; + this->pStream = nullptr; + } + } +private: + WCHAR* dumpName; + uint32_t dumpType; + uint32_t diagnostics; + WCHAR* identity; + IpcStream* pStream; +}; + +void DumpDiagnosticProtocolHelper::GenerateCoreDumpV2(DiagnosticsIpc::IpcMessage& message, IpcStream* pStream) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + if (pStream == nullptr) + return; + + NewHolder payload = message.TryParsePayload(); + if (payload == nullptr) + { + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); + delete pStream; + return; + } + if (payload->condition != nullptr) + { + DumpAction* dumpAction = new (nothrow) DumpAction(payload->dumpName, payload->dumpType, payload->diagnostics, payload->identity, pStream); + if (dumpAction == nullptr) + { + // TODO, andrewau, the correct error code is OOM + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); + delete pStream; + return; + } + DiagnosticsTriggerManager& diagnosticsTriggerManager = DiagnosticsTriggerManager::GetInstance(); + if (!diagnosticsTriggerManager.RegisterTrigger(payload->condition, payload->identity, dumpAction)) + { + // TODO, andrewau, the correct error code is unrecognized condition + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); + delete pStream; + return; + } + } + else + { + GenerateCoreDumpImpl(payload->dumpName, payload->dumpType, payload->diagnostics, pStream); + } +} + +void DumpDiagnosticProtocolHelper::GenerateCoreDumpImpl(LPCWSTR dumpName, uint32_t dumpType, uint32_t diagnostics, IpcStream* pStream) +{ #ifdef HOST_UNIX - MAKE_UTF8PTR_FROMWIDE_NOTHROW(szDumpName, payload->dumpName); + MAKE_UTF8PTR_FROMWIDE_NOTHROW(szDumpName, dumpName); if (szDumpName != nullptr) { - if (!PAL_GenerateCoreDump(szDumpName, payload->dumpType, payload->diagnostics)) + if (!PAL_GenerateCoreDump(szDumpName, dumpType, diagnostics)) { DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, E_FAIL); delete pStream; @@ -106,7 +241,7 @@ void DumpDiagnosticProtocolHelper::GenerateCoreDump(DiagnosticsIpc::IpcMessage& return; } #else - if (!GenerateCrashDump(payload->dumpName, payload->dumpType, payload->diagnostics)) + if (!GenerateCrashDump(dumpName, dumpType, diagnostics)) { DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, E_FAIL); delete pStream; diff --git a/src/coreclr/src/vm/dumpdiagnosticprotocolhelper.h b/src/coreclr/src/vm/dumpdiagnosticprotocolhelper.h index be8f7596245ec0..015ade18d10368 100644 --- a/src/coreclr/src/vm/dumpdiagnosticprotocolhelper.h +++ b/src/coreclr/src/vm/dumpdiagnosticprotocolhelper.h @@ -17,6 +17,7 @@ enum class DumpCommandId : uint8_t { // reserved = 0x00, GenerateCoreDump = 0x01, + GenerateCoreDumpV2 = 0x02, // future }; @@ -36,13 +37,34 @@ struct GenerateCoreDumpCommandPayload static const GenerateCoreDumpCommandPayload* TryParse(BYTE* lpBuffer, uint16_t& BufferSize); }; +struct GenerateCoreDumpCommandV2Payload +{ + NewArrayHolder incomingBuffer; + + // The protocol buffer is defined as: + // string - dumpName (UTF16) + // int - dumpType + // int - diagnostics + // string - condition (UTF16) + // string - identity (UTF16) + // returns + // ulong - status + LPCWSTR dumpName; + uint32_t dumpType; + uint32_t diagnostics; + LPCWSTR condition; + LPCWSTR identity; + static const GenerateCoreDumpCommandV2Payload* TryParse(BYTE* lpBuffer, uint16_t& BufferSize); +}; + class DumpDiagnosticProtocolHelper { public: // IPC event handlers. static void GenerateCoreDump(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream); // `dotnet-dump collect` + static void GenerateCoreDumpV2(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream); // `dotnet-dump collect` static void HandleIpcMessage(DiagnosticsIpc::IpcMessage& message, IpcStream* pStream); - + static void GenerateCoreDumpImpl(LPCWSTR dumpName, uint32_t dumpType, uint32_t diagnostics, IpcStream* pStream); private: const static uint32_t IpcStreamReadBufferSize = 8192; }; diff --git a/src/coreclr/src/vm/eventpipeprotocolhelper.cpp b/src/coreclr/src/vm/eventpipeprotocolhelper.cpp index 07ccf9c8b0b730..721d458589ddae 100644 --- a/src/coreclr/src/vm/eventpipeprotocolhelper.cpp +++ b/src/coreclr/src/vm/eventpipeprotocolhelper.cpp @@ -8,6 +8,7 @@ #include "eventpipesession.h" #include "diagnosticsipc.h" #include "diagnosticsprotocol.h" +#include "diagnosticstriggermanager.h" #ifdef FEATURE_PERFTRACING @@ -255,6 +256,36 @@ void EventPipeProtocolHelper::CollectTracing(DiagnosticsIpc::IpcMessage& message } } +class DumpHeapAction : public IDiagnosticsTriggerAction +{ +public: + DumpHeapAction(EventPipeSession* pEventPipeSession, WCHAR* identity) + { + this->pEventPipeSession = pEventPipeSession; + this->identity = new WCHAR[wcslen(identity) + 1]; + wcscpy(this->identity, identity); + } + ~DumpHeapAction() + { + delete[] this->identity; + } + virtual void Run() + { + this->pEventPipeSession->Resume(); + // TODO, andrewau, call GCProfileWalkHeap() here, how? + DiagnosticsTriggerManager::GetInstance().UnregisterTrigger(this->identity); + } + virtual void Cancel() + { + // TODO, andrewau, do we need to do anything? + // It looks like EventPipe will handle the closing + // and the tool should handle the fact that the stream does not have the expected content + } +private: + EventPipeSession* pEventPipeSession; + WCHAR* identity; +}; + void EventPipeProtocolHelper::CollectTracing2(DiagnosticsIpc::IpcMessage& message, IpcStream *pStream) { CONTRACTL @@ -274,16 +305,16 @@ void EventPipeProtocolHelper::CollectTracing2(DiagnosticsIpc::IpcMessage& messag delete pStream; return; } - + auto sessionId = EventPipe::Enable( - nullptr, // strOutputPath (ignored in this scenario) - payload->circularBufferSizeInMB, // circularBufferSizeInMB - payload->providerConfigs.Ptr(), // pConfigs - static_cast(payload->providerConfigs.Size()), // numConfigs - EventPipeSessionType::IpcStream, // EventPipeSessionType - payload->serializationFormat, // EventPipeSerializationFormat - payload->rundownRequested, // rundownRequested - pStream); // IpcStream + nullptr, // strOutputPath (ignored in this scenario) + payload->circularBufferSizeInMB, // circularBufferSizeInMB + payload->providerConfigs.Ptr(), // pConfigs + static_cast(payload->providerConfigs.Size()), // numConfigs + EventPipeSessionType::IpcStream, // EventPipeSessionType + payload->serializationFormat, // EventPipeSerializationFormat + payload->rundownRequested, // rundownRequested + pStream); // IpcStream if (sessionId == 0) { @@ -293,6 +324,37 @@ void EventPipeProtocolHelper::CollectTracing2(DiagnosticsIpc::IpcMessage& messag } else { + // TODO, andrewau, this should check if there is a condition. + // the condition should come from the filter in the provider + // so that it will hopefully also work for ETW. + // If we want it to also work for ETW, then this is probably not + // the only or right place to hook. + if (payload->rundownRequested) + { + WCHAR identity[100]; + swprintf_s(identity, 100, L"%d", (int)sessionId); + EventPipeSession* pEventPipeSession = EventPipe::GetSession(sessionId); + pEventPipeSession->Pause(); + DumpHeapAction* dumpHeapAction = new (nothrow) DumpHeapAction(pEventPipeSession, identity); + if (dumpHeapAction == nullptr) + { + // TODO, andrewau, the correct error code is OOM + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); + delete pStream; + return; + } + DiagnosticsTriggerManager& diagnosticsTriggerManager = DiagnosticsTriggerManager::GetInstance(); + // TODO, andrewau, obtain the condition information from filter + // TODO, andrewau, the identity should be the session id + if (!diagnosticsTriggerManager.RegisterTrigger(L"when = ongcmarkcomplete, gen = 1, promoted_bytes_threshold = 1000", identity, dumpHeapAction)) + { + // TODO, andrewau, the correct error code is unrecognized condition + DiagnosticsIpc::IpcMessage::SendErrorMessage(pStream, CORDIAGIPC_E_BAD_ENCODING); + delete pStream; + return; + } + } + DiagnosticsIpc::IpcMessage successResponse; if (successResponse.Initialize(DiagnosticsIpc::GenericSuccessHeader, sessionId)) successResponse.Send(pStream); diff --git a/src/coreclr/src/vm/eventpipesession.cpp b/src/coreclr/src/vm/eventpipesession.cpp index 49b9d54b1c1523..45d15f213d07f1 100644 --- a/src/coreclr/src/vm/eventpipesession.cpp +++ b/src/coreclr/src/vm/eventpipesession.cpp @@ -81,6 +81,7 @@ EventPipeSession::EventPipeSession( GetSystemTimeAsFileTime(&m_sessionStartTime); QueryPerformanceCounter(&m_sessionStartTimeStamp); + this->m_paused = false; } EventPipeSession::~EventPipeSession() @@ -332,23 +333,29 @@ bool EventPipeSession::WriteEvent( } CONTRACTL_END; + if (this->m_paused) + { + // TODO, andrewau, it looks like the return value of this method is not consumed at all? + return true; + } + // Filter events specific to "this" session based on precomputed flag on provider/events. if (event.IsEnabled(GetMask())) { if (m_synchronousCallback != nullptr) { m_synchronousCallback(event.GetProvider(), - event.GetEventID(), - event.GetEventVersion(), - event.GetMetadataLength(), - event.GetMetadata(), - payload.GetSize(), - payload.GetFlatData(), - pActivityId, - pRelatedActivityId, - pEventThread, - pStack == nullptr ? 0 : pStack->GetSize(), - pStack == nullptr ? nullptr : reinterpret_cast(pStack->GetPointer())); + event.GetEventID(), + event.GetEventVersion(), + event.GetMetadataLength(), + event.GetMetadata(), + payload.GetSize(), + payload.GetFlatData(), + pActivityId, + pRelatedActivityId, + pEventThread, + pStack == nullptr ? 0 : pStack->GetSize(), + pStack == nullptr ? nullptr : reinterpret_cast(pStack->GetPointer())); return true; } else diff --git a/src/coreclr/src/vm/eventpipesession.h b/src/coreclr/src/vm/eventpipesession.h index 10974859c6b535..aad0e2ad11dbd0 100644 --- a/src/coreclr/src/vm/eventpipesession.h +++ b/src/coreclr/src/vm/eventpipesession.h @@ -96,6 +96,8 @@ class EventPipeSession void DisableIpcStreamingThread(); + bool m_paused; + public: EventPipeSession( uint32_t index, @@ -111,6 +113,16 @@ class EventPipeSession ~EventPipeSession(); + void Pause() + { + this->m_paused = true; + } + + void Resume() + { + this->m_paused = false; + } + uint64_t GetMask() const { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/src/vm/eventtrace.cpp b/src/coreclr/src/vm/eventtrace.cpp index 2fe726af2997a4..706fbd51dfd223 100644 --- a/src/coreclr/src/vm/eventtrace.cpp +++ b/src/coreclr/src/vm/eventtrace.cpp @@ -1463,7 +1463,7 @@ void BulkStaticsLogger::WriteEntry(AppDomain *domain, Object **address, Object * m_domain = domain; } - ULONGLONG th = (ULONGLONG)obj->GetTypeHandle().AsTAddr(); + ULONGLONG th = (ULONGLONG)obj->GetGCSafeTypeHandle().AsTAddr(); ETW::TypeSystemLog::LogTypeAndParametersIfNecessary(m_typeLogger, th, ETW::TypeSystemLog::kTypeLogBehaviorTakeLockAndLogIfFirstTime); // We should have at least 512 characters remaining in the buffer here. @@ -4378,6 +4378,10 @@ VOID EtwCallbackCommon( // Special check for the runtime provider's GCHeapCollectKeyword. Profilers // flick this to force a full GC. + // TODO, andrewau, this code need to stay. + // Right now it is commented out to avoid triggering gen 2 GC + // We should be able to avoid this by introducing another keyword here. + /* if (g_fEEStarted && !g_fEEShutDown && bIsPublicTraceHandle && ((MatchAnyKeyword & CLR_GCHEAPCOLLECT_KEYWORD) != 0)) { @@ -4395,6 +4399,7 @@ VOID EtwCallbackCommon( #endif // !defined(HOST_UNIX) ETW::GCLog::ForceGC(l64ClientSequenceNumber); } + */ // TypeSystemLog needs a notification when certain keywords are modified, so // give it a hook here. if (g_fEEStarted && !g_fEEShutDown && bIsPublicTraceHandle) diff --git a/src/coreclr/src/vm/gcenv.ee.cpp b/src/coreclr/src/vm/gcenv.ee.cpp index 394475996015ca..6fef2d8ff651e8 100644 --- a/src/coreclr/src/vm/gcenv.ee.cpp +++ b/src/coreclr/src/vm/gcenv.ee.cpp @@ -1596,6 +1596,12 @@ void GCToEEInterface::AnalyzeSurvivorsFinished(int condemnedGeneration) DACNotify::DoGCNotification(gea); } } + + DiagnosticsTriggerManager& diagnosticsTriggerManager = DiagnosticsTriggerManager::GetInstance(); + diagnosticsTriggerManager.AnalyzeSurvivors(); + + // TODO, andrewau, this should be part of AnalyzeSurvivors (how?) + GCProfileWalkHeap(); } void GCToEEInterface::VerifySyncTableEntry() diff --git a/src/coreclr/src/vm/gcenv.ee.standalone.cpp b/src/coreclr/src/vm/gcenv.ee.standalone.cpp index 8aa1d4893fea96..f661c557e17f28 100644 --- a/src/coreclr/src/vm/gcenv.ee.standalone.cpp +++ b/src/coreclr/src/vm/gcenv.ee.standalone.cpp @@ -15,6 +15,7 @@ #include "gctoclreventsink.h" #include "configuration.h" +#include "diagnosticstriggermanager.h" // the method table for the WeakReference class extern MethodTable* pWeakReferenceMT; diff --git a/src/coreclr/src/vm/gcenv.ee.static.cpp b/src/coreclr/src/vm/gcenv.ee.static.cpp index 5e1b452b256af6..e9629ec39a4e9d 100644 --- a/src/coreclr/src/vm/gcenv.ee.static.cpp +++ b/src/coreclr/src/vm/gcenv.ee.static.cpp @@ -15,6 +15,7 @@ #include "gctoclreventsink.h" #include "configuration.h" +#include "diagnosticstriggermanager.h" // the method table for the WeakReference class extern MethodTable* pWeakReferenceMT; diff --git a/src/coreclr/src/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/src/vm/proftoeeinterfaceimpl.cpp index a5e4206a5a1aac..487e12313708da 100644 --- a/src/coreclr/src/vm/proftoeeinterfaceimpl.cpp +++ b/src/coreclr/src/vm/proftoeeinterfaceimpl.cpp @@ -1091,7 +1091,7 @@ bool HeapWalkHelper(Object * pBO, void * pvContext) OBJECTREF * arrObjRef = NULL; size_t cNumRefs = 0; bool bOnStack = false; - MethodTable * pMT = pBO->GetMethodTable(); + MethodTable * pMT = pBO->GetGCSafeMethodTable(); ProfilerWalkHeapContext * pProfilerWalkHeapContext = (ProfilerWalkHeapContext *) pvContext;