From 40dda195590873d18236bfbc7742bb6fe7305730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rivero?= Date: Tue, 2 Apr 2019 07:06:42 -0700 Subject: [EventPipe] Streaming events out-of-proc using IPC (#23448) - There was a race condition on EventPipe::Disable where we deallocated the s_pSession and set it to NULL, but there was still a thread waiting to write and dereference the null pointer. Now, we check that the session is not null. - Added a new attach event to be handled to the Diagnostic server: Add streaming functionality (Syncronus) #23299 - On Linux, IPC was failing when delete was invoked because we were using new (std::nothrow) instead of new (nothrow) when allocating the newly connected clients/streams. I have replaced the call with new. This fixed #23580 - Move multiFileTraceLengthInSeconds out of the EventPipeSession. - Unlink previously existing socket. - EventPipeBlock: _ASSERTE was updated not to fail when data is not aligned if an error has already occurred. - Update _ASSERTE in fastserializer.cpp to take into account the the write operation could have failed. --- src/debug/debug-pal/unix/diagnosticsipc.cpp | 10 +- src/debug/debug-pal/win/diagnosticsipc.cpp | 12 +- src/vm/diagnosticserver.cpp | 67 ++++--- src/vm/diagnosticserver.h | 14 +- src/vm/eventpipe.cpp | 266 ++++++++++++++++++---------- src/vm/eventpipe.h | 58 ++++-- src/vm/eventpipeblock.cpp | 5 +- src/vm/eventpipeblock.h | 115 ++++++------ src/vm/eventpipebuffermanager.cpp | 6 +- src/vm/eventpipebuffermanager.h | 4 +- src/vm/eventpipeconfiguration.cpp | 6 +- src/vm/eventpipeconfiguration.h | 3 +- src/vm/eventpipefile.cpp | 32 +--- src/vm/eventpipefile.h | 112 ++++++------ src/vm/eventpipeprotocolhelper.cpp | 89 +++++++++- src/vm/eventpipeprotocolhelper.h | 12 +- src/vm/eventpipesession.cpp | 4 +- src/vm/eventpipesession.h | 12 +- src/vm/fastserializableobject.h | 32 +--- src/vm/fastserializer.cpp | 20 ++- src/vm/fastserializer.h | 6 + 21 files changed, 499 insertions(+), 386 deletions(-) diff --git a/src/debug/debug-pal/unix/diagnosticsipc.cpp b/src/debug/debug-pal/unix/diagnosticsipc.cpp index d9e0e94c9f..fafb06c56b 100644 --- a/src/debug/debug-pal/unix/diagnosticsipc.cpp +++ b/src/debug/debug-pal/unix/diagnosticsipc.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -14,7 +13,7 @@ IpcStream::DiagnosticsIpc::DiagnosticsIpc(const int serverSocket, sockaddr_un *const pServerAddress) : _serverSocket(serverSocket), - _pServerAddress(new (std::nothrow) sockaddr_un), + _pServerAddress(new sockaddr_un), _isUnlinked(false) { _ASSERTE(_pServerAddress != nullptr); @@ -105,10 +104,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) const return nullptr; } - auto pIpcStream = new (std::nothrow) IpcStream(clientSocket); - if (pIpcStream == nullptr && callback != nullptr) - callback("Failed to allocate an IpcStream object.", 1); - return pIpcStream; + return new IpcStream(clientSocket); } //! This helps remove the socket from the filesystem when the runtime exits. @@ -137,6 +133,8 @@ IpcStream::~IpcStream() { if (_clientSocket != -1) { + Flush(); + const int fSuccessClose = ::close(_clientSocket); _ASSERTE(fSuccessClose != -1); } diff --git a/src/debug/debug-pal/win/diagnosticsipc.cpp b/src/debug/debug-pal/win/diagnosticsipc.cpp index 46f212836b..5467581f4a 100644 --- a/src/debug/debug-pal/win/diagnosticsipc.cpp +++ b/src/debug/debug-pal/win/diagnosticsipc.cpp @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. #include -#include #include #include "diagnosticsipc.h" @@ -47,7 +46,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) const const uint32_t nOutBufferSize = 16 * 1024; HANDLE hPipe = ::CreateNamedPipeA( _pNamedPipeName, // pipe name - PIPE_ACCESS_DUPLEX/* | FILE_FLAG_OVERLAPPED*/, // read/write access + PIPE_ACCESS_DUPLEX, // read/write access PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, // message type pipe, message-read and blocking mode PIPE_UNLIMITED_INSTANCES, // max. instances nOutBufferSize, // output buffer size @@ -71,10 +70,7 @@ IpcStream *IpcStream::DiagnosticsIpc::Accept(ErrorCallback callback) const return nullptr; } - auto pIpcStream = new (std::nothrow) IpcStream(hPipe); - if (pIpcStream == nullptr && callback != nullptr) - callback("Failed to allocate an IpcStream object.", 1); - return pIpcStream; + return new IpcStream(hPipe); } void IpcStream::DiagnosticsIpc::Unlink(ErrorCallback callback) @@ -85,11 +81,13 @@ IpcStream::~IpcStream() { if (_hPipe != INVALID_HANDLE_VALUE) { + Flush(); + const BOOL fSuccessDisconnectNamedPipe = ::DisconnectNamedPipe(_hPipe); assert(fSuccessDisconnectNamedPipe != 0); const BOOL fSuccessCloseHandle = ::CloseHandle(_hPipe); - assert(CloseHandle != 0); + assert(fSuccessCloseHandle != 0); } } diff --git a/src/vm/diagnosticserver.cpp b/src/vm/diagnosticserver.cpp index 3a700383ac..81cdc25d5b 100644 --- a/src/vm/diagnosticserver.cpp +++ b/src/vm/diagnosticserver.cpp @@ -18,8 +18,7 @@ static DWORD WINAPI DiagnosticsServerThread(LPVOID lpThreadParameter) { CONTRACTL { - // TODO: Maybe this should not throw. - THROWS; + NOTHROW; GC_TRIGGERS; MODE_ANY; PRECONDITION(lpThreadParameter != nullptr); @@ -37,39 +36,51 @@ static DWORD WINAPI DiagnosticsServerThread(LPVOID lpThreadParameter) STRESS_LOG2(LF_DIAGNOSTICS_PORT, LL_WARNING, "warning (%d): %s.\n", code, szMessage); }; - while (true) + EX_TRY { - // FIXME: Ideally this would be something like a std::shared_ptr - IpcStream *pStream = pIpc->Accept(LoggingCallback); - if (pStream == nullptr) - continue; - - // TODO: Read operation should happen in a loop. - uint32_t nNumberOfBytesRead = 0; - MessageHeader header; - bool fSuccess = pStream->Read(&header, sizeof(header), nNumberOfBytesRead); - if (!fSuccess || nNumberOfBytesRead != sizeof(header)) + while (true) { - delete pStream; - continue; - } + // FIXME: Ideally this would be something like a std::shared_ptr + IpcStream *pStream = pIpc->Accept(LoggingCallback); + if (pStream == nullptr) + continue; + + // TODO: Read operation should happen in a loop. + uint32_t nNumberOfBytesRead = 0; + MessageHeader header; + bool fSuccess = pStream->Read(&header, sizeof(header), nNumberOfBytesRead); + if (!fSuccess || nNumberOfBytesRead != sizeof(header)) + { + delete pStream; + continue; + } - // TODO: Dispatch thread worker. - switch (header.RequestType) - { - case DiagnosticMessageType::EnableEventPipe: - EventPipeProtocolHelper::EnableFileTracingEventHandler(pStream); - break; + switch (header.RequestType) + { + case DiagnosticMessageType::EnableEventPipe: + EventPipeProtocolHelper::EnableFileTracingEventHandler(pStream); + break; - case DiagnosticMessageType::DisableEventPipe: - EventPipeProtocolHelper::DisableTracingEventHandler(pStream); - break; + case DiagnosticMessageType::DisableEventPipe: + EventPipeProtocolHelper::DisableFileTracingEventHandler(pStream); + break; - default: - LOG((LF_DIAGNOSTICS_PORT, LL_WARNING, "Received unknow request type (%d)\n", header.RequestType)); - break; + case DiagnosticMessageType::StreamEventPipe: + EventPipeProtocolHelper::AttachTracingEventHandler(pStream); + break; + + default: + LOG((LF_DIAGNOSTICS_PORT, LL_WARNING, "Received unknow request type (%d)\n", header.RequestType)); + break; + } } } + EX_CATCH + { + STRESS_LOG0(LF_DIAGNOSTICS_PORT, LL_ERROR, "Exception caught in diagnostic thread. Leaving thread now.\n"); + _ASSERTE(!"Hit an error in the diagnostic server thread\n."); + } + EX_END_CATCH(SwallowAllExceptions); return 0; } diff --git a/src/vm/diagnosticserver.h b/src/vm/diagnosticserver.h index 51f32ae09f..76ad3eff23 100644 --- a/src/vm/diagnosticserver.h +++ b/src/vm/diagnosticserver.h @@ -20,17 +20,9 @@ enum class DiagnosticMessageType : uint32_t // EventPipe EnableEventPipe = 1024, DisableEventPipe, - - // TODO: Define what else is available on the out-of-proc interface? - // GetSessionInfo, - // CreateProvider, - // DefineEvent, - // GetProvider, - // DeleteProvider, - // EventActivityIdControl, - // WriteEvent, - // WriteEventData, - // GetNextEvent, + StreamEventPipe, + AttachEventPipe, + DetachEventPipe, /////////////////////////////////////////////////////////////////////////// // Profiler = 2048 diff --git a/src/vm/eventpipe.cpp b/src/vm/eventpipe.cpp index 634f1d0d69..92b4793f2e 100644 --- a/src/vm/eventpipe.cpp +++ b/src/vm/eventpipe.cpp @@ -5,6 +5,7 @@ #include "common.h" #include "clrtypes.h" #include "safemath.h" +#include "diagnosticsipc.h" #include "eventpipe.h" #include "eventpipebuffermanager.h" #include "eventpipeconfiguration.h" @@ -36,7 +37,8 @@ EventPipeEventSource *EventPipe::s_pEventSource = NULL; LPCWSTR EventPipe::s_pCommandLine = NULL; unsigned long EventPipe::s_nextFileIndex; HANDLE EventPipe::s_fileSwitchTimerHandle = NULL; -ULONGLONG EventPipe::s_lastFileSwitchTime = 0; +ULONGLONG EventPipe::s_lastFlushSwitchTime = 0; +uint64_t EventPipe::s_multiFileTraceLengthInSeconds = 0; #ifdef FEATURE_PAL // This function is auto-generated from /src/scripts/genEventPipe.py @@ -45,24 +47,6 @@ extern "C" void InitProvidersAndEvents(); void InitProvidersAndEvents(); #endif -EventPipeEventPayload::EventPipeEventPayload(BYTE *pData, unsigned int length) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - m_pData = pData; - m_pEventData = NULL; - m_eventDataCount = 0; - m_allocatedData = false; - - m_size = length; -} - EventPipeEventPayload::EventPipeEventPayload(EventData *pEventData, unsigned int eventDataCount) { CONTRACTL @@ -272,33 +256,118 @@ EventPipeSessionID EventPipe::Enable( // Take the lock before enabling tracing. CrstHolder _crst(GetLock()); + s_multiFileTraceLengthInSeconds = multiFileTraceLengthInSeconds; + // Create a new session. SampleProfiler::SetSamplingRate((unsigned long)profilerSamplingRateInNanoseconds); EventPipeSession *pSession = s_pConfig->CreateSession( - (strOutputPath != NULL) ? EventPipeSessionType::File : EventPipeSessionType::Streaming, + (strOutputPath != nullptr) ? EventPipeSessionType::File : EventPipeSessionType::Streaming, circularBufferSizeInMB, pProviders, - numProviders, - multiFileTraceLengthInSeconds); + numProviders); + + // Initialize the next file index. + s_nextFileIndex = 1; + + // Initialize the last file switch time. + s_lastFlushSwitchTime = CLRGetTickCount64(); + + // Create the event pipe file. + // A NULL output path means that we should not write the results to a file. + // This is used in the EventListener streaming case. + if (strOutputPath != NULL) + { + + // Save the output file path. + SString outputPath(strOutputPath); + SIZE_T outputPathLen = outputPath.GetCount(); + WCHAR *pOutputPath = new WCHAR[outputPathLen + 1]; + wcsncpy(pOutputPath, outputPath.GetUnicode(), outputPathLen); + pOutputPath[outputPathLen] = '\0'; + s_pOutputPath = pOutputPath; + + SString nextTraceFilePath; + GetNextFilePath(nextTraceFilePath); + + s_pFile = new EventPipeFile(new FileStreamWriter(nextTraceFilePath)); + } + + const DWORD FileSwitchTimerPeriodMS = 1000; + return Enable(pSession, SwitchToNextFileTimerCallback, FileSwitchTimerPeriodMS, FileSwitchTimerPeriodMS); +} + +EventPipeSessionID EventPipe::Enable( + IpcStream *pStream, + uint32_t circularBufferSizeInMB, + uint64_t profilerSamplingRateInNanoseconds, + const EventPipeProviderConfiguration *pProviders, + uint32_t numProviders) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(pStream != nullptr); + PRECONDITION((numProviders == 0) || (numProviders > 0 && pProviders != nullptr)); + } + CONTRACTL_END; + + if (numProviders == 0 || pProviders == nullptr) + return (EventPipeSessionID) nullptr; + + // Take the lock before enabling tracing. + CrstHolder _crst(GetLock()); + + // Create a new session. + SampleProfiler::SetSamplingRate((unsigned long)profilerSamplingRateInNanoseconds); + EventPipeSession *pSession = s_pConfig->CreateSession( + EventPipeSessionType::IpcStream, + circularBufferSizeInMB, + pProviders, + numProviders); + + // Reply back to client with the SessionId + uint32_t nBytesWritten = 0; + EventPipeSessionID sessionId = (EventPipeSessionID) pSession; + bool fSuccess = pStream->Write(&sessionId, sizeof(sessionId), nBytesWritten); + if (!fSuccess) + { + // TODO: Add error handling. + s_pConfig->DeleteSession(pSession); + + delete pStream; + return (EventPipeSessionID) nullptr; + } + + s_pFile = new EventPipeFile(new IpcStreamWriter(pStream)); // Enable the session. - return Enable(strOutputPath, pSession); + const DWORD FlushTimerPeriodMS = 100; // TODO: Define a good number here for streaming. + return Enable(pSession, FlushTimer, FlushTimerPeriodMS, FlushTimerPeriodMS); } -EventPipeSessionID EventPipe::Enable(LPCWSTR strOutputPath, EventPipeSession *pSession) +EventPipeSessionID EventPipe::Enable( + EventPipeSession *const pSession, + WAITORTIMERCALLBACK callback, + DWORD dueTime, + DWORD period) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; - PRECONDITION(pSession != NULL); + PRECONDITION(pSession != nullptr); + PRECONDITION(callback != nullptr); + PRECONDITION(dueTime > 0); + PRECONDITION(period > 0); PRECONDITION(GetLock()->OwnedByCurrentThread()); } CONTRACTL_END; // If tracing is not initialized or is already enabled, bail here. - if (!s_tracingInitialized || s_pConfig == NULL || s_pConfig->Enabled()) + if (!s_tracingInitialized || s_pConfig == nullptr || s_pConfig->Enabled()) return 0; // If the state or arguments are invalid, bail here. @@ -308,31 +377,6 @@ EventPipeSessionID EventPipe::Enable(LPCWSTR strOutputPath, EventPipeSession *pS // Enable the EventPipe EventSource. s_pEventSource->Enable(pSession); - // Initialize the next file index. - s_nextFileIndex = 1; - - // Initialize the last file switch time. - s_lastFileSwitchTime = CLRGetTickCount64(); - - // Create the event pipe file. - // A NULL output path means that we should not write the results to a file. - // This is used in the EventListener streaming case. - if (strOutputPath != NULL) - { - // Save the output file path. - SString outputPath(strOutputPath); - SIZE_T outputPathLen = outputPath.GetCount(); - WCHAR *pOutputPath = new WCHAR[outputPathLen + 1]; - wcsncpy(pOutputPath, outputPath.GetUnicode(), outputPathLen); - pOutputPath[outputPathLen] = '\0'; - s_pOutputPath = pOutputPath; - - SString nextTraceFilePath; - GetNextFilePath(pSession, nextTraceFilePath); - - s_pFile = new EventPipeFile(nextTraceFilePath); - } - // Save the session. s_pSession = pSession; @@ -342,11 +386,7 @@ EventPipeSessionID EventPipe::Enable(LPCWSTR strOutputPath, EventPipeSession *pS // Enable the sample profiler SampleProfiler::Enable(); - // Enable the file switch timer if needed. - if (s_pSession->GetMultiFileTraceLengthInSeconds() > 0) - { - CreateFileSwitchTimer(); - } + CreateFlushTimerCallback(callback, dueTime, period); // Return the session ID. return (EventPipeSessionID)s_pSession; @@ -365,9 +405,7 @@ void EventPipe::Disable(EventPipeSessionID id) // Only perform the disable operation if the session ID // matches the current active session. if (id != (EventPipeSessionID)s_pSession) - { return; - } // Don't block GC during clean-up. GCX_PREEMP(); @@ -389,18 +427,20 @@ void EventPipe::Disable(EventPipeSessionID id) // Disable tracing. s_pConfig->Disable(s_pSession); + s_multiFileTraceLengthInSeconds = 0; + // Delete the session. s_pConfig->DeleteSession(s_pSession); s_pSession = NULL; // Delete the file switch timer. - DeleteFileSwitchTimer(); + DeleteFlushTimerCallback(); // Flush all write buffers to make sure that all threads see the change. FlushProcessWriteBuffers(); // Write to the file. - if (s_pFile != NULL) + if (s_pFile != nullptr) { LARGE_INTEGER disableTimeStamp; QueryPerformanceCounter(&disableTimeStamp); @@ -435,7 +475,7 @@ void EventPipe::Disable(EventPipeSessionID id) } delete s_pFile; - s_pFile = NULL; + s_pFile = nullptr; } // De-allocate buffers. @@ -447,22 +487,27 @@ void EventPipe::Disable(EventPipeSessionID id) } } -void EventPipe::CreateFileSwitchTimer() +void EventPipe::CreateFlushTimerCallback(WAITORTIMERCALLBACK callback, DWORD dueTime, DWORD period) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; + PRECONDITION(callback != nullptr); + PRECONDITION(dueTime > 0); + PRECONDITION(period > 0); PRECONDITION(GetLock()->OwnedByCurrentThread()); } CONTRACTL_END + if (s_pFile == nullptr) + return; + NewHolder timerContextHolder = new (nothrow) ThreadpoolMgr::TimerInfoContext(); if (timerContextHolder == NULL) - { return; - } + timerContextHolder->TimerId = 0; bool success = false; @@ -471,10 +516,10 @@ void EventPipe::CreateFileSwitchTimer() { if (ThreadpoolMgr::CreateTimerQueueTimer( &s_fileSwitchTimerHandle, - SwitchToNextFileTimerCallback, + callback, timerContextHolder, - FileSwitchTimerPeriodMS, - FileSwitchTimerPeriodMS, + dueTime, + period, 0 /* flags */)) { _ASSERTE(s_fileSwitchTimerHandle != NULL); @@ -485,6 +530,7 @@ void EventPipe::CreateFileSwitchTimer() { } EX_END_CATCH(RethrowTerminalExceptions); + if (!success) { _ASSERTE(s_fileSwitchTimerHandle == NULL); @@ -494,7 +540,7 @@ void EventPipe::CreateFileSwitchTimer() timerContextHolder.SuppressRelease(); // the timer context is automatically deleted by the timer infrastructure } -void EventPipe::DeleteFileSwitchTimer() +void EventPipe::DeleteFlushTimerCallback() { CONTRACTL { @@ -506,9 +552,7 @@ void EventPipe::DeleteFileSwitchTimer() CONTRACTL_END if ((s_fileSwitchTimerHandle != NULL) && (ThreadpoolMgr::DeleteTimerQueueTimer(s_fileSwitchTimerHandle, NULL))) - { s_fileSwitchTimerHandle = NULL; - } } void WINAPI EventPipe::SwitchToNextFileTimerCallback(PVOID parameter, BOOLEAN timerFired) @@ -525,19 +569,55 @@ void WINAPI EventPipe::SwitchToNextFileTimerCallback(PVOID parameter, BOOLEAN ti // Take the lock control lock to make sure that tracing isn't disabled during this operation. CrstHolder _crst(GetLock()); + if (s_pSession == nullptr || s_pFile == nullptr) + return; + // Make sure that we should actually switch files. - UINT64 multiFileTraceLengthInSeconds = s_pSession->GetMultiFileTraceLengthInSeconds(); - if (!Enabled() || s_pSession->GetSessionType() != EventPipeSessionType::File || multiFileTraceLengthInSeconds == 0) - { + if (!Enabled() || s_pSession->GetSessionType() != EventPipeSessionType::File || s_multiFileTraceLengthInSeconds == 0) return; - } GCX_PREEMP(); - if (CLRGetTickCount64() > (s_lastFileSwitchTime + (multiFileTraceLengthInSeconds * 1000))) + if (CLRGetTickCount64() > (s_lastFlushSwitchTime + (s_multiFileTraceLengthInSeconds * 1000))) { SwitchToNextFile(); - s_lastFileSwitchTime = CLRGetTickCount64(); + s_lastFlushSwitchTime = CLRGetTickCount64(); + } +} + +void WINAPI EventPipe::FlushTimer(PVOID parameter, BOOLEAN timerFired) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(timerFired); + } + CONTRACTL_END; + + // Take the lock control lock to make sure that tracing isn't disabled during this operation. + CrstHolder _crst(GetLock()); + + if (s_pSession == nullptr || s_pFile == nullptr) + return; + + // Make sure that we should actually switch files. + if (!Enabled() || s_pSession->GetSessionType() != EventPipeSessionType::IpcStream) + return; + + GCX_PREEMP(); + + if (CLRGetTickCount64() > (s_lastFlushSwitchTime + 100)) + { + // Get the current time stamp. + // WriteAllBuffersToFile will use this to ensure that no events after + // the current timestamp are written into the file. + LARGE_INTEGER stopTimeStamp; + QueryPerformanceCounter(&stopTimeStamp); + s_pBufferManager->WriteAllBuffersToFile(s_pFile, stopTimeStamp); + + s_lastFlushSwitchTime = CLRGetTickCount64(); } } @@ -548,7 +628,7 @@ void EventPipe::SwitchToNextFile() THROWS; GC_TRIGGERS; MODE_PREEMPTIVE; - PRECONDITION(s_pSession != NULL); + PRECONDITION(s_pFile != nullptr); PRECONDITION(GetLock()->OwnedByCurrentThread()); } CONTRACTL_END @@ -561,8 +641,16 @@ void EventPipe::SwitchToNextFile() // Open the new file. SString nextTraceFilePath; - GetNextFilePath(s_pSession, nextTraceFilePath); - EventPipeFile *pFile = new (nothrow) EventPipeFile(nextTraceFilePath); + GetNextFilePath(nextTraceFilePath); + + StreamWriter *pStreamWriter = new (nothrow) FileStreamWriter(nextTraceFilePath); + if (pStreamWriter == nullptr) + { + // TODO: Add error handling. + return; + } + + EventPipeFile *pFile = new (nothrow) EventPipeFile(pStreamWriter); if (pFile == NULL) { // TODO: Add error handling. @@ -576,14 +664,13 @@ void EventPipe::SwitchToNextFile() s_pFile = pFile; } -void EventPipe::GetNextFilePath(EventPipeSession *pSession, SString &nextTraceFilePath) +void EventPipe::GetNextFilePath(SString &nextTraceFilePath) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; - PRECONDITION(pSession != NULL); PRECONDITION(GetLock()->OwnedByCurrentThread()); } CONTRACTL_END; @@ -592,8 +679,7 @@ void EventPipe::GetNextFilePath(EventPipeSession *pSession, SString &nextTraceFi nextTraceFilePath.Set(s_pOutputPath); // If multiple files have been requested, then add a sequence number to the trace file name. - UINT64 multiFileTraceLengthInSeconds = pSession->GetMultiFileTraceLengthInSeconds(); - if (multiFileTraceLengthInSeconds > 0) + if (s_multiFileTraceLengthInSeconds > 0) { // Remove the ".netperf" file extension if it exists. SString::Iterator netPerfExtension = nextTraceFilePath.End(); @@ -915,20 +1001,6 @@ StackWalkAction EventPipe::StackWalkCallback(CrawlFrame *pCf, StackContents *pDa return SWA_CONTINUE; } -EventPipeConfiguration *EventPipe::GetConfiguration() -{ - LIMITED_METHOD_CONTRACT; - - return s_pConfig; -} - -CrstStatic *EventPipe::GetLock() -{ - LIMITED_METHOD_CONTRACT; - - return &s_configCrst; -} - void EventPipe::SaveCommandLine(LPCWSTR pwzAssemblyPath, int argc, LPCWSTR *argv) { CONTRACTL diff --git a/src/vm/eventpipe.h b/src/vm/eventpipe.h index 494a8c5c98..5129baf9a9 100644 --- a/src/vm/eventpipe.h +++ b/src/vm/eventpipe.h @@ -20,6 +20,7 @@ class EventPipeProvider; class MethodDesc; struct EventPipeProviderConfiguration; class EventPipeSession; +class IpcStream; // EVENT_FILTER_DESCRIPTOR (This type does not exist on non-Windows platforms.) // https://docs.microsoft.com/en-us/windows/desktop/api/evntprov/ns-evntprov-_event_filter_descriptor @@ -73,7 +74,15 @@ private: public: // Build this payload with a flat buffer inside - EventPipeEventPayload(BYTE *pData, unsigned int length); + EventPipeEventPayload(BYTE *pData, unsigned int length) : + m_pData(pData), + m_pEventData(nullptr), + m_eventDataCount(0), + m_size(length), + m_allocatedData(false) + { + LIMITED_METHOD_CONTRACT; + } // Build this payload to contain an array of EventData objects EventPipeEventPayload(EventData *pEventData, unsigned int eventDataCount); @@ -94,7 +103,6 @@ public: bool IsFlattened() const { LIMITED_METHOD_CONTRACT; - return m_pData != NULL; } @@ -102,14 +110,12 @@ public: unsigned int GetSize() const { LIMITED_METHOD_CONTRACT; - return m_size; } EventData *GetEventDataArray() const { LIMITED_METHOD_CONTRACT; - return m_pEventData; } }; @@ -136,7 +142,6 @@ public: StackContents() { LIMITED_METHOD_CONTRACT; - Reset(); } @@ -155,21 +160,18 @@ public: void Reset() { LIMITED_METHOD_CONTRACT; - m_nextAvailableFrame = 0; } bool IsEmpty() { LIMITED_METHOD_CONTRACT; - return (m_nextAvailableFrame == 0); } unsigned int GetLength() { LIMITED_METHOD_CONTRACT; - return m_nextAvailableFrame; } @@ -218,19 +220,18 @@ public: BYTE *GetPointer() const { LIMITED_METHOD_CONTRACT; - return (BYTE *)m_stackFrames; } unsigned int GetSize() const { LIMITED_METHOD_CONTRACT; - return (m_nextAvailableFrame * sizeof(UINT_PTR)); } }; typedef UINT64 EventPipeSessionID; +typedef void (*FlushTimerCallback)(); class EventPipe { @@ -257,6 +258,13 @@ public: uint32_t numProviders, uint64_t multiFileTraceLengthInSeconds); + static EventPipeSessionID Enable( + IpcStream *pStream, + uint32_t circularBufferSizeInMB, + uint64_t profilerSamplingRateInNanoseconds, + const EventPipeProviderConfiguration *pProviders, + uint32_t numProviders); + // Disable tracing via the event pipe. static void Disable(EventPipeSessionID id); @@ -303,33 +311,47 @@ private: static void WriteEventInternal(EventPipeEvent &event, EventPipeEventPayload &payload, LPCGUID pActivityId = NULL, LPCGUID pRelatedActivityId = NULL); // Enable the specified EventPipe session. - static EventPipeSessionID Enable(LPCWSTR strOutputPath, EventPipeSession *pSession); + static EventPipeSessionID Enable( + EventPipeSession *const pSession, + WAITORTIMERCALLBACK callback, + DWORD dueTime, + DWORD period); - static void CreateFileSwitchTimer(); + static void CreateFlushTimerCallback(WAITORTIMERCALLBACK Callback, DWORD DueTime, DWORD Period); - static void DeleteFileSwitchTimer(); + static void DeleteFlushTimerCallback(); // Performs one polling operation to determine if it is necessary to switch to a new file. // If the polling operation decides it is time, it will perform the switch. // Called directly from the timer when the timer is triggered. static void WINAPI SwitchToNextFileTimerCallback(PVOID parameter, BOOLEAN timerFired); + static void WINAPI FlushTimer(PVOID parameter, BOOLEAN timerFired); + // If event pipe has been configured to write multiple files, switch to the next file. static void SwitchToNextFile(); // Generate the file path for the next trace file. // This is used when event pipe has been configured to create multiple trace files with a specified maximum length of time. - static void GetNextFilePath(EventPipeSession *pSession, SString &nextTraceFilePath); + static void GetNextFilePath(SString &nextTraceFilePath); // Callback function for the stack walker. For each frame walked, this callback is invoked. static StackWalkAction StackWalkCallback(CrawlFrame *pCf, StackContents *pData); // Get the configuration object. // This is called directly by the EventPipeProvider constructor to register the new provider. - static EventPipeConfiguration *GetConfiguration(); + static EventPipeConfiguration *GetConfiguration() + { + LIMITED_METHOD_CONTRACT; + return s_pConfig; + } // Get the event pipe configuration lock. - static CrstStatic *GetLock(); + static CrstStatic *GetLock() + { + LIMITED_METHOD_CONTRACT; + return &s_configCrst; + } static CrstStatic s_configCrst; static bool s_tracingInitialized; @@ -341,9 +363,9 @@ private: static EventPipeFile *s_pFile; static EventPipeEventSource *s_pEventSource; static LPCWSTR s_pCommandLine; - const static DWORD FileSwitchTimerPeriodMS = 1000; static HANDLE s_fileSwitchTimerHandle; - static ULONGLONG s_lastFileSwitchTime; + static ULONGLONG s_lastFlushSwitchTime; + static uint64_t s_multiFileTraceLengthInSeconds; }; struct EventPipeProviderConfiguration diff --git a/src/vm/eventpipeblock.cpp b/src/vm/eventpipeblock.cpp index 5992b874de..d76cd3a71d 100644 --- a/src/vm/eventpipeblock.cpp +++ b/src/vm/eventpipeblock.cpp @@ -10,7 +10,8 @@ #ifdef FEATURE_PERFTRACING -EventPipeBlock::EventPipeBlock(unsigned int maxBlockSize) +EventPipeBlock::EventPipeBlock(unsigned int maxBlockSize) : + FastSerializableObject(1, 0) { CONTRACTL { @@ -71,7 +72,7 @@ bool EventPipeBlock::WriteEvent(EventPipeEventInstance &instance) return false; } - BYTE* alignedEnd = m_pWritePointer + totalSize + sizeof(totalSize); + BYTE* alignedEnd = m_pWritePointer + totalSize + sizeof(totalSize); memcpy(m_pWritePointer, &totalSize, sizeof(totalSize)); m_pWritePointer += sizeof(totalSize); diff --git a/src/vm/eventpipeblock.h b/src/vm/eventpipeblock.h index 30bd458f38..78b04af805 100644 --- a/src/vm/eventpipeblock.h +++ b/src/vm/eventpipeblock.h @@ -11,80 +11,69 @@ #include "fastserializableobject.h" #include "fastserializer.h" -class EventPipeBlock : public FastSerializableObject +class EventPipeBlock final : public FastSerializableObject { - public: - EventPipeBlock(unsigned int maxBlockSize); +public: + EventPipeBlock(unsigned int maxBlockSize); + ~EventPipeBlock(); + + // Write an event to the block. + // Returns: + // - true: The write succeeded. + // - false: The write failed. In this case, the block should be considered full. + bool WriteEvent(EventPipeEventInstance &instance); + + void Clear(); + + const char *GetTypeName() override + { + LIMITED_METHOD_CONTRACT; + return "EventBlock"; + } + + void FastSerialize(FastSerializer *pSerializer) override + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + PRECONDITION(pSerializer != NULL); + } + CONTRACTL_END; - ~EventPipeBlock(); + if (m_pBlock == NULL) + return; - // Write an event to the block. - // Returns: - // - true: The write succeeded. - // - false: The write failed. In this case, the block should be considered full. - bool WriteEvent(EventPipeEventInstance &instance); + unsigned int eventsSize = (unsigned int)(m_pWritePointer - m_pBlock); + pSerializer->WriteBuffer((BYTE *)&eventsSize, sizeof(eventsSize)); - void Clear(); + if (eventsSize == 0) + return; - const char* GetTypeName() + size_t currentPosition = pSerializer->GetCurrentPosition(); + if (currentPosition % ALIGNMENT_SIZE != 0) { - LIMITED_METHOD_CONTRACT; - return "EventBlock"; - } + BYTE maxPadding[ALIGNMENT_SIZE - 1] = {}; // it's longest possible padding, we are going to use only part of it + unsigned int paddingLength = ALIGNMENT_SIZE - (currentPosition % ALIGNMENT_SIZE); + pSerializer->WriteBuffer(maxPadding, paddingLength); // we write zeros here, the reader is going to always read from the first aligned address of the serialized content - void FastSerialize(FastSerializer *pSerializer) - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - PRECONDITION(pSerializer != NULL); - } - CONTRACTL_END; - - if (m_pBlock == NULL) - { - return; - } - - unsigned int eventsSize = (unsigned int)(m_pWritePointer - m_pBlock); - pSerializer->WriteBuffer((BYTE*)&eventsSize, sizeof(eventsSize)); - - if (eventsSize == 0) - { - return; - } - - size_t currentPosition = pSerializer->GetCurrentPosition(); - if (currentPosition % ALIGNMENT_SIZE != 0) - { - BYTE maxPadding[ALIGNMENT_SIZE - 1] = {}; // it's longest possible padding, we are going to use only part of it - unsigned int paddingLength = ALIGNMENT_SIZE - (currentPosition % ALIGNMENT_SIZE); - pSerializer->WriteBuffer(maxPadding, paddingLength); // we write zeros here, the reader is going to always read from the first aligned address of the serialized content - - _ASSERTE(pSerializer->GetCurrentPosition() % ALIGNMENT_SIZE == 0); - } - - pSerializer->WriteBuffer(m_pBlock, eventsSize); + _ASSERTE(pSerializer->HasWriteErrors() || (pSerializer->GetCurrentPosition() % ALIGNMENT_SIZE == 0)); } - private: - BYTE *m_pBlock; - BYTE *m_pWritePointer; - BYTE *m_pEndOfTheBuffer; + pSerializer->WriteBuffer(m_pBlock, eventsSize); + } - unsigned int GetSize() const - { - LIMITED_METHOD_CONTRACT; - - if (m_pBlock == NULL) - { - return 0; - } +private: + BYTE *m_pBlock; + BYTE *m_pWritePointer; + BYTE *m_pEndOfTheBuffer; - return (unsigned int)(m_pEndOfTheBuffer - m_pBlock); - } + unsigned int GetSize() const + { + LIMITED_METHOD_CONTRACT; + return m_pBlock == nullptr ? 0 : (unsigned int)(m_pEndOfTheBuffer - m_pBlock); + } }; #endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipebuffermanager.cpp b/src/vm/eventpipebuffermanager.cpp index 4194abc73c..e15676e15e 100644 --- a/src/vm/eventpipebuffermanager.cpp +++ b/src/vm/eventpipebuffermanager.cpp @@ -263,7 +263,7 @@ EventPipeBufferList* EventPipeBufferManager::FindThreadToStealFrom() pOldestContainingList = pCandidate; } // Otherwise, to replace the existing candidate, this candidate must have an older timestamp in its oldest buffer. - else if((pOldestContainingList->GetHead()->GetMostRecentTimeStamp().QuadPart) > + else if((pOldestContainingList->GetHead()->GetMostRecentTimeStamp().QuadPart) > (pCandidate->GetHead()->GetMostRecentTimeStamp().QuadPart)) { pOldestContainingList = pCandidate; @@ -414,7 +414,7 @@ void EventPipeBufferManager::WriteAllBuffersToFile(EventPipeFile *pFile, LARGE_I THROWS; GC_NOTRIGGER; MODE_ANY; - PRECONDITION(pFile != NULL); + PRECONDITION(pFile != nullptr); } CONTRACTL_END; @@ -451,7 +451,7 @@ void EventPipeBufferManager::WriteAllBuffersToFile(EventPipeFile *pFile, LARGE_I { // If it's the oldest event we've seen, then save it. if((pOldestInstance == NULL) || - (pOldestInstance->GetTimeStamp()->QuadPart > pNext->GetTimeStamp()->QuadPart)) + (pOldestInstance->GetTimeStamp()->QuadPart > pNext->GetTimeStamp()->QuadPart)) { pOldestInstance = pNext; pOldestContainingBuffer = pContainingBuffer; diff --git a/src/vm/eventpipebuffermanager.h b/src/vm/eventpipebuffermanager.h index e10018fe8e..a7584cce1d 100644 --- a/src/vm/eventpipebuffermanager.h +++ b/src/vm/eventpipebuffermanager.h @@ -18,7 +18,7 @@ class EventPipeBufferList; // This class is a TLS wrapper around a pointer to thread-specific EventPipeBufferList // The struct wrapper is present mainly because we need a way to free the EventPipeBufferList // when the thread that owns it dies. Placing this class as a TLS variable will call ~ThreadEventBufferList() -// when the thread dies so we can free EventPipeBufferList in the destructor. +// when the thread dies so we can free EventPipeBufferList in the destructor. class ThreadEventBufferList { #ifndef __GNUC__ @@ -103,7 +103,7 @@ public: // Write the contents of the managed buffers to the specified file. // The stopTimeStamp is used to determine when tracing was stopped to ensure that we // skip any events that might be partially written due to races when tracing is stopped. - void WriteAllBuffersToFile(EventPipeFile *pFile, LARGE_INTEGER stopTimeStamp); + void WriteAllBuffersToFile(EventPipeFile *pFastSerializableObject, LARGE_INTEGER stopTimeStamp); // Attempt to de-allocate resources as best we can. It is possible for some buffers to leak because // threads can be in the middle of a write operation and get blocked, and we may not get an opportunity diff --git a/src/vm/eventpipeconfiguration.cpp b/src/vm/eventpipeconfiguration.cpp index 4667276753..48899df16c 100644 --- a/src/vm/eventpipeconfiguration.cpp +++ b/src/vm/eventpipeconfiguration.cpp @@ -312,8 +312,7 @@ EventPipeSession *EventPipeConfiguration::CreateSession( EventPipeSessionType sessionType, unsigned int circularBufferSizeInMB, const EventPipeProviderConfiguration *pProviders, - uint32_t numProviders, - uint64_t multiFileTraceLengthInSeconds) + uint32_t numProviders) { CONTRACTL { @@ -328,8 +327,7 @@ EventPipeSession *EventPipeConfiguration::CreateSession( sessionType, circularBufferSizeInMB, pProviders, - numProviders, - multiFileTraceLengthInSeconds); + numProviders); } void EventPipeConfiguration::DeleteSession(EventPipeSession *pSession) diff --git a/src/vm/eventpipeconfiguration.h b/src/vm/eventpipeconfiguration.h index 6021305b82..270ed449fc 100644 --- a/src/vm/eventpipeconfiguration.h +++ b/src/vm/eventpipeconfiguration.h @@ -55,8 +55,7 @@ public: EventPipeSessionType sessionType, unsigned int circularBufferSizeInMB, const EventPipeProviderConfiguration *pProviders, - uint32_t numProviders, - uint64_t multiFileTraceLengthInSeconds = 0); + uint32_t numProviders); // Delete a session. void DeleteSession(EventPipeSession *pSession); diff --git a/src/vm/eventpipefile.cpp b/src/vm/eventpipefile.cpp index 2e98fd5ad7..73fb48e2e3 100644 --- a/src/vm/eventpipefile.cpp +++ b/src/vm/eventpipefile.cpp @@ -3,15 +3,13 @@ // See the LICENSE file in the project root for more information. #include "common.h" -#include "eventpipebuffer.h" #include "eventpipeblock.h" -#include "eventpipeconfiguration.h" #include "eventpipefile.h" #include "sampleprofiler.h" #ifdef FEATURE_PERFTRACING -EventPipeFile::EventPipeFile(SString &outputFilePath) +EventPipeFile::EventPipeFile(StreamWriter *pStreamWriter) : FastSerializableObject(3, 0) { CONTRACTL { @@ -21,9 +19,6 @@ EventPipeFile::EventPipeFile(SString &outputFilePath) } CONTRACTL_END; - SetObjectVersion(3); - SetMinReaderVersion(0); - m_pBlock = new EventPipeBlock(100 * 1024); // File start time information. @@ -42,7 +37,7 @@ EventPipeFile::EventPipeFile(SString &outputFilePath) m_samplingRateInNs = SampleProfiler::GetSamplingRate(); // Create the file stream and write the header. - m_pSerializer = new FastSerializer(new FileStreamWriter(outputFilePath)); + m_pSerializer = new FastSerializer(pStreamWriter); m_serializationLock.Init(LOCK_TYPE_DEFAULT); m_pMetadataIds = new MapSHashWithRemove(); @@ -65,21 +60,10 @@ EventPipeFile::~EventPipeFile() CONTRACTL_END; if (m_pBlock != NULL && m_pSerializer != NULL) - { WriteEnd(); - } - - if (m_pBlock != NULL) - { - delete(m_pBlock); - m_pBlock = NULL; - } - if(m_pSerializer != NULL) - { - delete(m_pSerializer); - m_pSerializer = NULL; - } + delete m_pBlock; + delete m_pSerializer; } void EventPipeFile::WriteEvent(EventPipeEventInstance &instance) @@ -105,8 +89,8 @@ void EventPipeFile::WriteEvent(EventPipeEventInstance &instance) SaveMetadataId(*instance.GetEvent(), metadataId); - delete[] (pMetadataInstance->GetData()); - delete (pMetadataInstance); + delete[] pMetadataInstance->GetData(); + delete pMetadataInstance; } WriteToBlock(instance, metadataId); @@ -144,9 +128,7 @@ void EventPipeFile::WriteToBlock(EventPipeEventInstance &instance, unsigned int instance.SetMetadataId(metadataId); if (m_pBlock->WriteEvent(instance)) - { return; // the block is not full, we added the event and continue - } // we can't write this event to the current block (it's full) // so we write what we have in the block to the serializer @@ -209,9 +191,7 @@ void EventPipeFile::SaveMetadataId(EventPipeEvent &event, unsigned int metadataI // If a pre-existing metadata label exists, remove it. unsigned int oldId; if(m_pMetadataIds->Lookup(&event, &oldId)) - { m_pMetadataIds->Remove(&event); - } // Add the metadata label. m_pMetadataIds->Add(&event, metadataId); diff --git a/src/vm/eventpipefile.h b/src/vm/eventpipefile.h index 29c2b19457..d99bcd1883 100644 --- a/src/vm/eventpipefile.h +++ b/src/vm/eventpipefile.h @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. - #ifndef __EVENTPIPE_FILE_H__ #define __EVENTPIPE_FILE_H__ @@ -12,87 +11,86 @@ #include "eventpipeblock.h" #include "eventpipeeventinstance.h" #include "fastserializableobject.h" -#include "fastserializer.h" -class EventPipeFile : public FastSerializableObject -{ - public: +class FastSerializer; - EventPipeFile(SString &outputFilePath); - ~EventPipeFile(); +class EventPipeFile final : public FastSerializableObject +{ +public: + EventPipeFile(StreamWriter *pStreamWriter); + ~EventPipeFile(); - void WriteEvent(EventPipeEventInstance &instance); + void WriteEvent(EventPipeEventInstance &instance); - void WriteEnd(); + const char *GetTypeName() override + { + LIMITED_METHOD_CONTRACT; + return "Trace"; + } - const char* GetTypeName() + void FastSerialize(FastSerializer *pSerializer) override + { + CONTRACTL { - LIMITED_METHOD_CONTRACT; - return "Trace"; + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + PRECONDITION(pSerializer != NULL); } + CONTRACTL_END; - void FastSerialize(FastSerializer *pSerializer) - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_PREEMPTIVE; - PRECONDITION(pSerializer != NULL); - } - CONTRACTL_END; - - pSerializer->WriteBuffer((BYTE*)&m_fileOpenSystemTime, sizeof(m_fileOpenSystemTime)); - pSerializer->WriteBuffer((BYTE*)&m_fileOpenTimeStamp, sizeof(m_fileOpenTimeStamp)); - pSerializer->WriteBuffer((BYTE*)&m_timeStampFrequency, sizeof(m_timeStampFrequency)); - - // the beginning of V3 - pSerializer->WriteBuffer((BYTE*)&m_pointerSize, sizeof(m_pointerSize)); - pSerializer->WriteBuffer((BYTE*)&m_currentProcessId, sizeof(m_currentProcessId)); - pSerializer->WriteBuffer((BYTE*)&m_numberOfProcessors, sizeof(m_numberOfProcessors)); - pSerializer->WriteBuffer((BYTE*)&m_samplingRateInNs, sizeof(m_samplingRateInNs)); - } + pSerializer->WriteBuffer((BYTE *)&m_fileOpenSystemTime, sizeof(m_fileOpenSystemTime)); + pSerializer->WriteBuffer((BYTE *)&m_fileOpenTimeStamp, sizeof(m_fileOpenTimeStamp)); + pSerializer->WriteBuffer((BYTE *)&m_timeStampFrequency, sizeof(m_timeStampFrequency)); + + // the beginning of V3 + pSerializer->WriteBuffer((BYTE *)&m_pointerSize, sizeof(m_pointerSize)); + pSerializer->WriteBuffer((BYTE *)&m_currentProcessId, sizeof(m_currentProcessId)); + pSerializer->WriteBuffer((BYTE *)&m_numberOfProcessors, sizeof(m_numberOfProcessors)); + pSerializer->WriteBuffer((BYTE *)&m_samplingRateInNs, sizeof(m_samplingRateInNs)); + } - private: +private: + void WriteEnd(); - unsigned int GenerateMetadataId(); + unsigned int GenerateMetadataId(); - unsigned int GetMetadataId(EventPipeEvent &event); + unsigned int GetMetadataId(EventPipeEvent &event); - void SaveMetadataId(EventPipeEvent &event, unsigned int metadataId); + void SaveMetadataId(EventPipeEvent &event, unsigned int metadataId); - void WriteToBlock(EventPipeEventInstance &instance, unsigned int metadataId); + void WriteToBlock(EventPipeEventInstance &instance, unsigned int metadataId); - // The object responsible for serialization. - FastSerializer *m_pSerializer; + // The object responsible for serialization. + FastSerializer *m_pSerializer; - EventPipeBlock *m_pBlock; + EventPipeBlock *m_pBlock; - // The system time when the file was opened. - SYSTEMTIME m_fileOpenSystemTime; + // The system time when the file was opened. + SYSTEMTIME m_fileOpenSystemTime; - // The timestamp when the file was opened. Used for calculating file-relative timestamps. - LARGE_INTEGER m_fileOpenTimeStamp; + // The timestamp when the file was opened. Used for calculating file-relative timestamps. + LARGE_INTEGER m_fileOpenTimeStamp; - // The frequency of the timestamps used for this file. - LARGE_INTEGER m_timeStampFrequency; + // The frequency of the timestamps used for this file. + LARGE_INTEGER m_timeStampFrequency; - unsigned int m_pointerSize; + unsigned int m_pointerSize; - unsigned int m_currentProcessId; + unsigned int m_currentProcessId; - unsigned int m_numberOfProcessors; + unsigned int m_numberOfProcessors; - unsigned int m_samplingRateInNs; + unsigned int m_samplingRateInNs; - // The serialization which is responsible for making sure only a single event - // or block of events gets written to the file at once. - SpinLock m_serializationLock; + // The serialization which is responsible for making sure only a single event + // or block of events gets written to the file at once. + SpinLock m_serializationLock; - // Hashtable of metadata labels. - MapSHashWithRemove *m_pMetadataIds; + // Hashtable of metadata labels. + MapSHashWithRemove *m_pMetadataIds; - Volatile m_metadataIdCounter; + Volatile m_metadataIdCounter; }; #endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipeprotocolhelper.cpp b/src/vm/eventpipeprotocolhelper.cpp index bfcdcfa2f8..c1fe613c05 100644 --- a/src/vm/eventpipeprotocolhelper.cpp +++ b/src/vm/eventpipeprotocolhelper.cpp @@ -9,7 +9,7 @@ #ifdef FEATURE_PERFTRACING -bool EventPipeProtocolHelper::TryParseProviderConfigurations(uint8_t *&bufferCursor, uint32_t &bufferLen, CQuickArray &result) +bool EventPipeProtocolHelper::TryParseProviderConfiguration(uint8_t *&bufferCursor, uint32_t &bufferLen, CQuickArray &result) { // Picking an arbitrary upper bound, // This should be larger than any reasonable client request. @@ -62,8 +62,7 @@ void EventPipeProtocolHelper::EnableFileTracingEventHandler(IpcStream *pStream) CONTRACTL_END; // TODO: Read within a loop. - const uint32_t BufferSize = 8192; - uint8_t buffer[BufferSize]{}; + uint8_t buffer[IpcStreamReadBufferSize]{}; uint32_t nNumberOfBytesRead = 0; bool fSuccess = pStream->Read(buffer, sizeof(buffer), nNumberOfBytesRead); if (!fSuccess) @@ -93,9 +92,11 @@ void EventPipeProtocolHelper::EnableFileTracingEventHandler(IpcStream *pStream) if (!TryParse(pBufferCursor, bufferLen, circularBufferSizeInMB) || !TryParse(pBufferCursor, bufferLen, multiFileTraceLengthInSeconds) || !TryParseString(pBufferCursor, bufferLen, strOutputPath) || - !TryParseProviderConfigurations(pBufferCursor, bufferLen, providerConfigs)) + !TryParseProviderConfiguration(pBufferCursor, bufferLen, providerConfigs)) { - return; // TODO: error handling + // TODO: error handling + delete pStream; + return; } EventPipeSessionID sessionId = (EventPipeSessionID) nullptr; @@ -127,7 +128,7 @@ void EventPipeProtocolHelper::EnableFileTracingEventHandler(IpcStream *pStream) delete pStream; } -void EventPipeProtocolHelper::DisableTracingEventHandler(IpcStream *pStream) +void EventPipeProtocolHelper::DisableFileTracingEventHandler(IpcStream *pStream) { CONTRACTL { @@ -140,7 +141,7 @@ void EventPipeProtocolHelper::DisableTracingEventHandler(IpcStream *pStream) uint32_t nNumberOfBytesRead = 0; EventPipeSessionID sessionId = (EventPipeSessionID) nullptr; - const bool fSuccess = pStream->Read(&sessionId, sizeof(sessionId), nNumberOfBytesRead); + bool fSuccess = pStream->Read(&sessionId, sizeof(sessionId), nNumberOfBytesRead); if (!fSuccess || nNumberOfBytesRead != sizeof(sessionId)) { // TODO: Add error handling. @@ -149,8 +150,80 @@ void EventPipeProtocolHelper::DisableTracingEventHandler(IpcStream *pStream) } EventPipe::Disable(sessionId); - // TODO: Should we acknowledge back? + uint32_t nBytesWritten = 0; + fSuccess = pStream->Write(&sessionId, sizeof(sessionId), nBytesWritten); + if (!fSuccess) + { + // TODO: Add error handling. + delete pStream; + return; + } + + fSuccess = pStream->Flush(); + if (!fSuccess) + { + // TODO: Add error handling. + } delete pStream; } +void EventPipeProtocolHelper::AttachTracingEventHandler(IpcStream *pStream) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(pStream != nullptr); + } + CONTRACTL_END; + + // TODO: Read within a loop. + uint8_t buffer[IpcStreamReadBufferSize]{}; + uint32_t nNumberOfBytesRead = 0; + bool fSuccess = pStream->Read(buffer, sizeof(buffer), nNumberOfBytesRead); + if (!fSuccess) + { + // TODO: Add error handling. + delete pStream; + return; + } + + // The protocol buffer is defined as: + // X, Y, Z means encode bytes for X followed by bytes for Y followed by bytes for Z + // message = uint circularBufferMB, ulong multiFileTraceLength, string outputPath, array providers + // uint = 4 little endian bytes + // wchar = 2 little endian bytes, UTF16 encoding + // array = uint length, length # of Ts + // string = (array where the last char must = 0) or (length = 0) + // provider_config = ulong keywords, uint logLevel, string provider_name, string filter_data + + LPCWSTR strOutputPath; + uint32_t circularBufferSizeInMB = EventPipeProtocolHelper::DefaultCircularBufferMB; + uint64_t multiFileTraceLengthInSeconds = EventPipeProtocolHelper::DefaultMultiFileTraceLengthInSeconds; + CQuickArray providerConfigs; + + uint8_t *pBufferCursor = buffer; + uint32_t bufferLen = nNumberOfBytesRead; + if (!TryParse(pBufferCursor, bufferLen, circularBufferSizeInMB) || + !TryParse(pBufferCursor, bufferLen, multiFileTraceLengthInSeconds) || + !TryParseString(pBufferCursor, bufferLen, strOutputPath) || + !TryParseProviderConfiguration(pBufferCursor, bufferLen, providerConfigs)) + { + // TODO: error handling + delete pStream; + return; + } + + if (providerConfigs.Size() > 0) + { + EventPipe::Enable( + pStream, // IPC stream + circularBufferSizeInMB, // circularBufferSizeInMB + DefaultProfilerSamplingRateInNanoseconds, // ProfilerSamplingRateInNanoseconds + providerConfigs.Ptr(), // pConfigs + static_cast(providerConfigs.Size())); // numConfigs + } +} + #endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipeprotocolhelper.h b/src/vm/eventpipeprotocolhelper.h index fb3f65a247..44192d724a 100644 --- a/src/vm/eventpipeprotocolhelper.h +++ b/src/vm/eventpipeprotocolhelper.h @@ -17,17 +17,17 @@ class EventPipeProtocolHelper public: // IPC event handlers. static void EnableFileTracingEventHandler(IpcStream *pStream); - static void DisableTracingEventHandler(IpcStream *pStream); + static void DisableFileTracingEventHandler(IpcStream *pStream); + + static void AttachTracingEventHandler(IpcStream *pStream); private: const static uint32_t DefaultCircularBufferMB = 1024; // 1 GB const static uint64_t DefaultMultiFileTraceLengthInSeconds = 0; - const static uint32_t DefaultProfilerSamplingRateInNanoseconds = 1000000; + const static uint32_t DefaultProfilerSamplingRateInNanoseconds = 1000000; // 1 msec. + const static uint32_t IpcStreamReadBufferSize = 8192; - //! Read a list of providers: "Provider[,Provider]" - //! Provider: "(GUID|KnownProviderName)[:Flags[:Level][:KeyValueArgs]]" - //! KeyValueArgs: "[key1=value1][;key2=value2]" - static bool TryParseProviderConfigurations(uint8_t *&bufferCursor, uint32_t &bufferLen, CQuickArray &result); + static bool TryParseProviderConfiguration(uint8_t *&bufferCursor, uint32_t &bufferLen, CQuickArray &result); }; #endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipesession.cpp b/src/vm/eventpipesession.cpp index 03bc6c681a..30c88fb807 100644 --- a/src/vm/eventpipesession.cpp +++ b/src/vm/eventpipesession.cpp @@ -14,8 +14,7 @@ EventPipeSession::EventPipeSession( EventPipeSessionType sessionType, unsigned int circularBufferSizeInMB, const EventPipeProviderConfiguration *pProviders, - uint32_t numProviders, - uint64_t multiFileTraceLengthInSeconds) + uint32_t numProviders) { CONTRACTL { @@ -30,7 +29,6 @@ EventPipeSession::EventPipeSession( m_circularBufferSizeInBytes = circularBufferSizeInMB * 1024 * 1024; // 1MB; m_rundownEnabled = false; m_pProviderList = new EventPipeSessionProviderList(pProviders, numProviders); - m_multiFileTraceLengthInSeconds = multiFileTraceLengthInSeconds; GetSystemTimeAsFileTime(&m_sessionStartTime); QueryPerformanceCounter(&m_sessionStartTimeStamp); } diff --git a/src/vm/eventpipesession.h b/src/vm/eventpipesession.h index 0474c7cafe..128ba77012 100644 --- a/src/vm/eventpipesession.h +++ b/src/vm/eventpipesession.h @@ -39,9 +39,6 @@ private: // Start timestamp. LARGE_INTEGER m_sessionStartTimeStamp; - // The maximum trace length in seconds. Used to determine when to flush the current file and start a new one. - UINT64 m_multiFileTraceLengthInSeconds; - public: // TODO: This needs to be exposed via EventPipe::CreateSession() and EventPipe::DeleteSession() to avoid memory ownership issues. @@ -49,8 +46,7 @@ public: EventPipeSessionType sessionType, unsigned int circularBufferSizeInMB, const EventPipeProviderConfiguration *pProviders, - uint32_t numProviders, - uint64_t multiFileTraceLengthInSeconds); + uint32_t numProviders); ~EventPipeSession(); // Determine if the session is valid or not. Invalid sessions can be detected before they are enabled. @@ -98,12 +94,6 @@ public: return m_sessionStartTimeStamp; } - UINT64 GetMultiFileTraceLengthInSeconds() const - { - LIMITED_METHOD_CONTRACT; - return m_multiFileTraceLengthInSeconds; - } - // Add a new provider to the session. void AddSessionProvider(EventPipeSessionProvider *pProvider); diff --git a/src/vm/fastserializableobject.h b/src/vm/fastserializableobject.h index ec162e3cc3..e411dff74c 100644 --- a/src/vm/fastserializableobject.h +++ b/src/vm/fastserializableobject.h @@ -11,10 +11,13 @@ class FastSerializer; class FastSerializableObject { - public: + FastSerializableObject(int objectVersion, int minReaderVersion) : + m_objectVersion(objectVersion), m_minReaderVersion(minReaderVersion) + { + LIMITED_METHOD_CONTRACT; + } - // Virtual destructor to ensure that derived class destructors get called. virtual ~FastSerializableObject() { LIMITED_METHOD_CONTRACT; @@ -24,42 +27,23 @@ public: virtual void FastSerialize(FastSerializer *pSerializer) = 0; // Get the type name for the current object. - virtual const char* GetTypeName() = 0; + virtual const char *GetTypeName() = 0; int GetObjectVersion() const { LIMITED_METHOD_CONTRACT; - return m_objectVersion; } int GetMinReaderVersion() const { LIMITED_METHOD_CONTRACT; - return m_minReaderVersion; } -protected: - - void SetObjectVersion(int version) - { - LIMITED_METHOD_CONTRACT; - - m_objectVersion = version; - } - - void SetMinReaderVersion(int version) - { - LIMITED_METHOD_CONTRACT; - - m_minReaderVersion = version; - } - private: - - int m_objectVersion = 1; - int m_minReaderVersion = 0; + const int m_objectVersion; + const int m_minReaderVersion; }; #endif // FEATURE_PERFTRACING diff --git a/src/vm/fastserializer.cpp b/src/vm/fastserializer.cpp index e02ec93010..2941f6ac6a 100644 --- a/src/vm/fastserializer.cpp +++ b/src/vm/fastserializer.cpp @@ -49,6 +49,8 @@ bool IpcStreamWriter::Write(const void *lpBuffer, const uint32_t nBytesToWrite, } CONTRACTL_END; + if (_pStream == nullptr) + return false; return _pStream->Write(lpBuffer, nBytesToWrite, nBytesWritten); } @@ -97,6 +99,9 @@ bool FileStreamWriter::Write(const void *lpBuffer, const uint32_t nBytesToWrite, } CONTRACTL_END; + if (m_pFileStream == nullptr) + return false; + ULONG outCount; HRESULT hResult = m_pFileStream->Write(lpBuffer, nBytesToWrite, &outCount); nBytesWritten = static_cast(outCount); @@ -177,16 +182,15 @@ void FastSerializer::WriteBuffer(BYTE *pBuffer, unsigned int length) size_t prevPos = m_currentPos; #endif m_currentPos += outCount; + + // This will cause us to stop writing to the file. + // The file will still remain open until shutdown so that we don't + // have to take a lock at this level when we touch the file stream. + m_writeErrorEncountered = (length != outCount); + #ifdef _DEBUG - _ASSERTE(prevPos < m_currentPos); + _ASSERTE(m_writeErrorEncountered || (prevPos < m_currentPos)); #endif - - if (length != outCount) - { - // This will cause us to stop writing to the file. - // The file will still remain open until shutdown so that we don't have to take a lock at this level when we touch the file stream. - m_writeErrorEncountered = true; - } } EX_CATCH { diff --git a/src/vm/fastserializer.h b/src/vm/fastserializer.h index 98208c0eb1..4d61adceeb 100644 --- a/src/vm/fastserializer.h +++ b/src/vm/fastserializer.h @@ -92,6 +92,12 @@ public: return m_currentPos; } + bool HasWriteErrors() const + { + LIMITED_METHOD_CONTRACT; + return m_writeErrorEncountered; + } + private: void WriteSerializationType(FastSerializableObject *pObject); void WriteFileHeader(); -- cgit v1.2.3