diff options
author | Brian Robbins <brianrob@microsoft.com> | 2017-05-06 12:36:08 -0700 |
---|---|---|
committer | Vance Morrison <vancem@microsoft.com> | 2017-05-06 12:36:08 -0700 |
commit | 72ac46450bec8ea88ed023d9c1faf5a04556c834 (patch) | |
tree | 59085824f4268257d041ed9870f038216cd8ed45 | |
parent | 3ababc21ab334a2e37c6ba4115c946ea26a6f2fb (diff) | |
download | coreclr-72ac46450bec8ea88ed023d9c1faf5a04556c834.tar.gz coreclr-72ac46450bec8ea88ed023d9c1faf5a04556c834.tar.bz2 coreclr-72ac46450bec8ea88ed023d9c1faf5a04556c834.zip |
Log Events to EventPipe on Linux (#11433)
* Implement the EventPipe object model for providers and events.
* Plumb Runtime Events into EventPipe (#11145)
Plumb runtime ETW events into the EventPipe.
* Fix bug where all events except for SampleProfiler events were never enabled.
* Plumb EventPipeEventInstance through the EventPipe.
* Implement EventPipeFile and FastSerializer.
* Write event contents to the EventPipeFile.
* Only build EventPipe on Linux.
* Conditionally add a sentinel value marking event end.
* Send SampleProfiler events to the EventPipeFile.
* Fix provider ID printing to JSON file.
* Write the start date/time, timestamp, and clock frequency into the trace file.
* Support unloading of EventPipeProviders.
* Handle failure cases when we can't walk the stack or are shutting down.
* Fix a bug where we pass a null src pointer to memcpy.
27 files changed, 2603 insertions, 85 deletions
@@ -154,15 +154,27 @@ generate_event_logging_sources() # Event Logging Infrastructure __GeneratedIntermediate="$__IntermediatesDir/Generated" __GeneratedIntermediateEventProvider="$__GeneratedIntermediate/eventprovider_new" + __GeneratedIntermediateEventPipe="$__GeneratedIntermediate/eventpipe_new" + if [[ -d "$__GeneratedIntermediateEventProvider" ]]; then rm -rf "$__GeneratedIntermediateEventProvider" fi + if [[ -d "$__GeneratedIntermediateEventPipe" ]]; then + rm -rf "$__GeneratedIntermediateEventPipe" + fi + if [[ ! -d "$__GeneratedIntermediate/eventprovider" ]]; then mkdir -p "$__GeneratedIntermediate/eventprovider" fi + if [[ ! -d "$__GeneratedIntermediate/eventpipe" ]]; then + mkdir -p "$__GeneratedIntermediate/eventpipe" + fi + mkdir -p "$__GeneratedIntermediateEventProvider" + mkdir -p "$__GeneratedIntermediateEventPipe" + if [[ $__SkipCoreCLR == 0 || $__ConfigureOnly == 1 ]]; then echo "Laying out dynamically generated files consumed by the build system " echo "Laying out dynamically generated Event Logging Test files" @@ -172,6 +184,18 @@ generate_event_logging_sources() exit fi + case $__BuildOS in + Linux) + echo "Laying out dynamically generated EventPipe Implementation" + $PYTHON -B -Wall -Werror "$__ProjectRoot/src/scripts/genEventPipe.py" --man "$__ProjectRoot/src/vm/ClrEtwAll.man" --intermediate "$__GeneratedIntermediateEventPipe" --exc "$__ProjectRoot/src/vm/ClrEtwAllMeta.lst" + if [[ $? != 0 ]]; then + exit + fi + ;; + *) + ;; + esac + #determine the logging system case $__BuildOS in Linux) @@ -193,6 +217,14 @@ generate_event_logging_sources() fi rm -rf "$__GeneratedIntermediateEventProvider" + + echo "Cleaning the temp folder of dynamically generated EventPipe files" + $PYTHON -B -Wall -Werror -c "import sys;sys.path.insert(0,\"$__ProjectRoot/src/scripts\"); from Utilities import *;UpdateDirectory(\"$__GeneratedIntermediate/eventpipe\",\"$__GeneratedIntermediateEventPipe\")" + if [[ $? != 0 ]]; then + exit + fi + + rm -rf "$__GeneratedIntermediateEventPipe" } build_native() diff --git a/clrdefinitions.cmake b/clrdefinitions.cmake index f15749e5d5..4dacae9428 100644 --- a/clrdefinitions.cmake +++ b/clrdefinitions.cmake @@ -111,7 +111,9 @@ endif(FEATURE_DBGIPC) if(FEATURE_EVENT_TRACE) add_definitions(-DFEATURE_EVENT_TRACE=1) endif(FEATURE_EVENT_TRACE) -add_definitions(-DFEATURE_PERFTRACING) +if(CLR_CMAKE_PLATFORM_LINUX) + add_definitions(-DFEATURE_PERFTRACING) +endif(CLR_CMAKE_PLATFORM_LINUX) if(CLR_CMAKE_PLATFORM_UNIX) add_definitions(-DFEATURE_EVENTSOURCE_XPLAT=1) endif(CLR_CMAKE_PLATFORM_UNIX) diff --git a/src/dlls/mscoree/coreclr/CMakeLists.txt b/src/dlls/mscoree/coreclr/CMakeLists.txt index afa253f08f..7a4617fc52 100644 --- a/src/dlls/mscoree/coreclr/CMakeLists.txt +++ b/src/dlls/mscoree/coreclr/CMakeLists.txt @@ -141,6 +141,12 @@ if(CLR_CMAKE_PLATFORM_UNIX AND FEATURE_EVENT_TRACE) ) endif(CLR_CMAKE_PLATFORM_UNIX AND FEATURE_EVENT_TRACE) +if(CLR_CMAKE_PLATFORM_LINUX) + list(APPEND CORECLR_LIBRARIES + eventpipe + ) +endif(CLR_CMAKE_PLATFORM_LINUX) + target_link_libraries(coreclr ${CORECLR_LIBRARIES}) if(WIN32) diff --git a/src/scripts/genEventPipe.py b/src/scripts/genEventPipe.py new file mode 100644 index 0000000000..3a818ce1e0 --- /dev/null +++ b/src/scripts/genEventPipe.py @@ -0,0 +1,500 @@ +from __future__ import print_function +from genXplatEventing import * +from genXplatLttng import * +import os +import xml.dom.minidom as DOM + +stdprolog = """// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/****************************************************************** + +DO NOT MODIFY. AUTOGENERATED FILE. +This file is generated using the logic from <root>/src/scripts/genEventPipe.py + +******************************************************************/ +""" + +stdprolog_cmake = """# +# +#****************************************************************** + +#DO NOT MODIFY. AUTOGENERATED FILE. +#This file is generated using the logic from <root>/src/scripts/genEventPipe.py + +#****************************************************************** +""" + + +def generateClrEventPipeWriteEventsImpl( + providerName, eventNodes, allTemplates, exclusionListFile): + providerPrettyName = providerName.replace("Windows-", '') + providerPrettyName = providerPrettyName.replace("Microsoft-", '') + providerPrettyName = providerPrettyName.replace('-', '_') + WriteEventImpl = [] + + # EventPipeEvent declaration + for eventNode in eventNodes: + eventName = eventNode.getAttribute('symbol') + WriteEventImpl.append( + "EventPipeEvent *EventPipeEvent" + + eventName + + " = nullptr;\n") + + for eventNode in eventNodes: + eventName = eventNode.getAttribute('symbol') + templateName = eventNode.getAttribute('template') + + # generate EventPipeEventEnabled function + eventEnabledImpl = """bool EventPipeEventEnabled%s() +{ + return EventPipeEvent%s->IsEnabled(); +} + +""" % (eventName, eventName) + WriteEventImpl.append(eventEnabledImpl) + + # generate EventPipeWriteEvent function + fnptype = [] + linefnptype = [] + fnptype.append("extern \"C\" ULONG EventPipeWriteEvent") + fnptype.append(eventName) + fnptype.append("(\n") + + if templateName: + template = allTemplates[templateName] + else: + template = None + + if template: + fnSig = template.signature + for paramName in fnSig.paramlist: + fnparam = fnSig.getParam(paramName) + wintypeName = fnparam.winType + typewName = palDataTypeMapping[wintypeName] + winCount = fnparam.count + countw = palDataTypeMapping[winCount] + + if paramName in template.structs: + linefnptype.append( + "%sint %s_ElementSize,\n" % + (lindent, paramName)) + + linefnptype.append(lindent) + linefnptype.append(typewName) + if countw != " ": + linefnptype.append(countw) + + linefnptype.append(" ") + linefnptype.append(fnparam.name) + linefnptype.append(",\n") + + if len(linefnptype) > 0: + del linefnptype[-1] + + fnptype.extend(linefnptype) + fnptype.append(")\n{\n") + checking = """ if (!EventPipeEventEnabled%s()) + return ERROR_SUCCESS; +""" % (eventName) + + fnptype.append(checking) + + WriteEventImpl.extend(fnptype) + + if template: + body = generateWriteEventBody(template, providerName, eventName) + WriteEventImpl.append(body) + else: + WriteEventImpl.append( + " EventPipe::WriteEvent(*EventPipeEvent" + + eventName + + ", nullptr, 0);\n") + + WriteEventImpl.append("\n return ERROR_SUCCESS;\n}\n\n") + + # EventPipeProvider and EventPipeEvent initialization + WriteEventImpl.append( + "extern \"C\" void Init" + + providerPrettyName + + "()\n{\n") + WriteEventImpl.append( + " EventPipeProvider" + + providerPrettyName + + " = new EventPipeProvider(" + + providerPrettyName + + "GUID);\n") + for eventNode in eventNodes: + eventName = eventNode.getAttribute('symbol') + templateName = eventNode.getAttribute('template') + eventKeywords = eventNode.getAttribute('keywords') + eventKeywordsMask = generateEventKeywords(eventKeywords) + eventValue = eventNode.getAttribute('value') + eventVersion = eventNode.getAttribute('version') + eventLevel = eventNode.getAttribute('level') + eventLevel = eventLevel.replace("win:", "EventPipeEventLevel::") + exclusionInfo = parseExclusionList(exclusionListFile) + taskName = eventNode.getAttribute('task') + noStack = getStackWalkBit( + providerName, + taskName, + eventName, + exclusionInfo.nostack) + + initEvent = """ EventPipeEvent%s = EventPipeProvider%s->AddEvent(%s,%s,%s,%s,%d); +""" % (eventName, providerPrettyName, eventKeywordsMask, eventValue, eventVersion, eventLevel, int(noStack)) + + WriteEventImpl.append(initEvent) + WriteEventImpl.append("}") + + return ''.join(WriteEventImpl) + + +def generateWriteEventBody(template, providerName, eventName): + header = """ + char stackBuffer[%s]; + char *buffer = stackBuffer; + unsigned int offset = 0; + unsigned int size = %s; + bool fixedBuffer = true; + + bool success = true; +""" % (template.estimated_size, template.estimated_size) + + fnSig = template.signature + pack_list = [] + for paramName in fnSig.paramlist: + parameter = fnSig.getParam(paramName) + + if paramName in template.structs: + size = "(int)%s_ElementSize * (int)%s" % ( + paramName, parameter.prop) + if template.name in specialCaseSizes and paramName in specialCaseSizes[template.name]: + size = "(int)(%s)" % specialCaseSizes[template.name][paramName] + pack_list.append( + " success &= WriteToBuffer((const BYTE *)%s, %s, buffer, offset, size, fixedBuffer);" % + (paramName, size)) + elif paramName in template.arrays: + size = "sizeof(%s) * (int)%s" % ( + lttngDataTypeMapping[parameter.winType], + parameter.prop) + if template.name in specialCaseSizes and paramName in specialCaseSizes[template.name]: + size = "(int)(%s)" % specialCaseSizes[template.name][paramName] + pack_list.append( + " success &= WriteToBuffer((const BYTE *)%s, %s, buffer, offset, size, fixedBuffer);" % + (paramName, size)) + elif parameter.winType == "win:GUID": + pack_list.append( + " success &= WriteToBuffer(*%s, buffer, offset, size, fixedBuffer);" % + (parameter.name,)) + else: + pack_list.append( + " success &= WriteToBuffer(%s, buffer, offset, size, fixedBuffer);" % + (parameter.name,)) + + code = "\n".join(pack_list) + "\n\n" + + checking = """ if (!success) + { + if (!fixedBuffer) + delete[] buffer; + return ERROR_WRITE_FAULT; + }\n\n""" + + body = " EventPipe::WriteEvent(*EventPipeEvent" + \ + eventName + ", (BYTE *)buffer, size);\n" + + footer = """ + if (!fixedBuffer) + delete[] buffer; +""" + + return header + code + checking + body + footer + +providerGUIDMap = {} +providerGUIDMap[ + "{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}"] = "{0xe13c0d23,0xccbc,0x4e12,{0x93,0x1b,0xd9,0xcc,0x2e,0xee,0x27,0xe4}}" +providerGUIDMap[ + "{A669021C-C450-4609-A035-5AF59AF4DF18}"] = "{0xA669021C,0xC450,0x4609,{0xA0,0x35,0x5A,0xF5,0x9A,0xF4,0xDF,0x18}}" +providerGUIDMap[ + "{CC2BCBBA-16B6-4cf3-8990-D74C2E8AF500}"] = "{0xCC2BCBBA,0x16B6,0x4cf3,{0x89,0x90,0xD7,0x4C,0x2E,0x8A,0xF5,0x00}}" +providerGUIDMap[ + "{763FD754-7086-4dfe-95EB-C01A46FAF4CA}"] = "{0x763FD754,0x7086,0x4dfe,{0x95,0xEB,0xC0,0x1A,0x46,0xFA,0xF4,0xCA}}" + + +def generateGUID(tmpGUID): + return providerGUIDMap[tmpGUID] + +keywordMap = {} + + +def generateEventKeywords(eventKeywords): + mask = 0 + # split keywords if there are multiple + allKeywords = eventKeywords.split() + + for singleKeyword in allKeywords: + mask = mask | keywordMap[singleKeyword] + + return mask + + +def generateEventPipeCmakeFile(etwmanifest, eventpipe_directory): + tree = DOM.parse(etwmanifest) + + with open(eventpipe_directory + "CMakeLists.txt", 'w') as topCmake: + topCmake.write(stdprolog_cmake + "\n") + topCmake.write("""cmake_minimum_required(VERSION 2.8.12.2) + + project(eventpipe) + + set(CMAKE_INCLUDE_CURRENT_DIR ON) + include_directories(${CLR_DIR}/src/vm) + + add_library(eventpipe + STATIC\n""") + + for providerNode in tree.getElementsByTagName('provider'): + providerName = providerNode.getAttribute('name') + providerName = providerName.replace("Windows-", '') + providerName = providerName.replace("Microsoft-", '') + + providerName_File = providerName.replace('-', '') + providerName_File = providerName_File.lower() + + topCmake.write(' "%s.cpp"\n' % (providerName_File)) + topCmake.write(' "eventpipehelpers.cpp"\n') + topCmake.write(""" ) + + # Install the static eventpipe library + install(TARGETS eventpipe DESTINATION lib) + """) + + topCmake.close() + + +def generateEventPipeHelperFile(etwmanifest, eventpipe_directory): + with open(eventpipe_directory + "eventpipehelpers.cpp", 'w') as helper: + helper.write(stdprolog) + helper.write(""" +#include "stdlib.h" + +bool ResizeBuffer(char *&buffer, unsigned int& size, unsigned int currLen, unsigned int newSize, bool &fixedBuffer) +{ + newSize *= 1.5; + _ASSERTE(newSize > size); // check for overflow + + if (newSize < 32) + newSize = 32; + + char *newBuffer = new char[newSize]; + + memcpy(newBuffer, buffer, currLen); + + if (!fixedBuffer) + delete[] buffer; + + buffer = newBuffer; + size = newSize; + fixedBuffer = false; + + return true; +} + +bool WriteToBuffer(const BYTE *src, unsigned int len, char *&buffer, unsigned int& offset, unsigned int& size, bool &fixedBuffer) +{ + if(!src) return true; + if (offset + len > size) + { + if (!ResizeBuffer(buffer, size, offset, size + len, fixedBuffer)) + return false; + } + + memcpy(buffer + offset, src, len); + offset += len; + return true; +} + +bool WriteToBuffer(PCWSTR str, char *&buffer, unsigned int& offset, unsigned int& size, bool &fixedBuffer) +{ + if(!str) return true; + unsigned int byteCount = (PAL_wcslen(str) + 1) * sizeof(*str); + + if (offset + byteCount > size) + { + if (!ResizeBuffer(buffer, size, offset, size + byteCount, fixedBuffer)) + return false; + } + + memcpy(buffer + offset, str, byteCount); + offset += byteCount; + return true; +} + +bool WriteToBuffer(const char *str, char *&buffer, unsigned int& offset, unsigned int& size, bool &fixedBuffer) +{ + if(!str) return true; + unsigned int len = strlen(str) + 1; + if (offset + len > size) + { + if (!ResizeBuffer(buffer, size, offset, size + len, fixedBuffer)) + return false; + } + + memcpy(buffer + offset, str, len); + offset += len; + return true; +} + +""") + + tree = DOM.parse(etwmanifest) + + for providerNode in tree.getElementsByTagName('provider'): + providerName = providerNode.getAttribute('name') + providerPrettyName = providerName.replace("Windows-", '') + providerPrettyName = providerPrettyName.replace("Microsoft-", '') + providerPrettyName = providerPrettyName.replace('-', '_') + helper.write( + "extern \"C\" void Init" + + providerPrettyName + + "();\n\n") + + helper.write("extern \"C\" void InitProvidersAndEvents()\n{\n") + for providerNode in tree.getElementsByTagName('provider'): + providerName = providerNode.getAttribute('name') + providerPrettyName = providerName.replace("Windows-", '') + providerPrettyName = providerPrettyName.replace("Microsoft-", '') + providerPrettyName = providerPrettyName.replace('-', '_') + helper.write(" Init" + providerPrettyName + "();\n") + helper.write("}") + + helper.close() + + +def generateEventPipeImplFiles( + etwmanifest, eventpipe_directory, exclusionListFile): + tree = DOM.parse(etwmanifest) + coreclrRoot = os.getcwd() + for providerNode in tree.getElementsByTagName('provider'): + providerGUID = providerNode.getAttribute('guid') + providerGUID = generateGUID(providerGUID) + providerName = providerNode.getAttribute('name') + + providerPrettyName = providerName.replace("Windows-", '') + providerPrettyName = providerPrettyName.replace("Microsoft-", '') + providerName_File = providerPrettyName.replace('-', '') + providerName_File = providerName_File.lower() + providerPrettyName = providerPrettyName.replace('-', '_') + eventpipefile = eventpipe_directory + providerName_File + ".cpp" + eventpipeImpl = open(eventpipefile, 'w') + eventpipeImpl.write(stdprolog) + + header = """ +#include \"%s/src/vm/common.h\" +#include \"%s/src/vm/eventpipeprovider.h\" +#include \"%s/src/vm/eventpipeevent.h\" +#include \"%s/src/vm/eventpipe.h\" + +bool ResizeBuffer(char *&buffer, unsigned int& size, unsigned int currLen, unsigned int newSize, bool &fixedBuffer); +bool WriteToBuffer(PCWSTR str, char *&buffer, unsigned int& offset, unsigned int& size, bool &fixedBuffer); +bool WriteToBuffer(const char *str, char *&buffer, unsigned int& offset, unsigned int& size, bool &fixedBuffer); +bool WriteToBuffer(const BYTE *src, unsigned int len, char *&buffer, unsigned int& offset, unsigned int& size, bool &fixedBuffer); + +template <typename T> +bool WriteToBuffer(const T &value, char *&buffer, unsigned int& offset, unsigned int& size, bool &fixedBuffer) +{ + if (sizeof(T) + offset > size) + { + if (!ResizeBuffer(buffer, size, offset, size + sizeof(T), fixedBuffer)) + return false; + } + + *(T *)(buffer + offset) = value; + offset += sizeof(T); + return true; +} + +""" % (coreclrRoot, coreclrRoot, coreclrRoot, coreclrRoot) + + eventpipeImpl.write(header) + eventpipeImpl.write( + "GUID const " + + providerPrettyName + + "GUID = " + + providerGUID + + ";\n") + eventpipeImpl.write( + "EventPipeProvider *EventPipeProvider" + + providerPrettyName + + " = nullptr;\n") + templateNodes = providerNode.getElementsByTagName('template') + allTemplates = parseTemplateNodes(templateNodes) + eventNodes = providerNode.getElementsByTagName('event') + eventpipeImpl.write( + generateClrEventPipeWriteEventsImpl( + providerName, + eventNodes, + allTemplates, + exclusionListFile) + "\n") + eventpipeImpl.close() + + +def generateEventPipeFiles( + etwmanifest, eventpipe_directory, exclusionListFile): + eventpipe_directory = eventpipe_directory + "/" + tree = DOM.parse(etwmanifest) + + if not os.path.exists(eventpipe_directory): + os.makedirs(eventpipe_directory) + + # generate Cmake file + generateEventPipeCmakeFile(etwmanifest, eventpipe_directory) + + # generate helper file + generateEventPipeHelperFile(etwmanifest, eventpipe_directory) + + # generate all keywords + for keywordNode in tree.getElementsByTagName('keyword'): + keywordName = keywordNode.getAttribute('name') + keywordMask = keywordNode.getAttribute('mask') + keywordMap[keywordName] = int(keywordMask, 0) + + # generate .cpp file for each provider + generateEventPipeImplFiles( + etwmanifest, + eventpipe_directory, + exclusionListFile) + +import argparse +import sys + + +def main(argv): + + # parse the command line + parser = argparse.ArgumentParser( + description="Generates the Code required to instrument eventpipe logging mechanism") + + required = parser.add_argument_group('required arguments') + required.add_argument('--man', type=str, required=True, + help='full path to manifest containig the description of events') + required.add_argument('--intermediate', type=str, required=True, + help='full path to eventprovider intermediate directory') + required.add_argument('--exc', type=str, required=True, + help='full path to exclusion list') + args, unknown = parser.parse_known_args(argv) + if unknown: + print('Unknown argument(s): ', ', '.join(unknown)) + return const.UnknownArguments + + sClrEtwAllMan = args.man + intermediate = args.intermediate + exclusionListFile = args.exc + + generateEventPipeFiles(sClrEtwAllMan, intermediate, exclusionListFile) + +if __name__ == '__main__': + return_code = main(sys.argv[1:]) + sys.exit(return_code) diff --git a/src/scripts/genXplatEventing.py b/src/scripts/genXplatEventing.py index 6c6498d3de..6968d293e9 100644 --- a/src/scripts/genXplatEventing.py +++ b/src/scripts/genXplatEventing.py @@ -39,7 +39,7 @@ stdprolog_cmake=""" #****************************************************************** """ -lindent = " "; +lindent = " "; palDataTypeMapping ={ #constructed types "win:null" :" ", @@ -282,18 +282,17 @@ def generateClrallEvents(eventNodes,allTemplates): #generate EventEnabled clrallEvents.append("inline BOOL EventEnabled") clrallEvents.append(eventName) - clrallEvents.append("() {return XplatEventLogger::IsEventLoggingEnabled() && EventXplatEnabled") - clrallEvents.append(eventName+"();}\n\n") + clrallEvents.append("() {return ") + clrallEvents.append("EventPipeEventEnabled" + eventName + "() || ") + clrallEvents.append("(XplatEventLogger::IsEventLoggingEnabled() && EventXplatEnabled") + clrallEvents.append(eventName+"());}\n\n") #generate FireEtw functions fnptype = [] fnbody = [] fnptype.append("inline ULONG FireEtw") fnptype.append(eventName) fnptype.append("(\n") - fnbody.append(lindent) - fnbody.append("if (!EventEnabled") - fnbody.append(eventName) - fnbody.append("()) {return ERROR_SUCCESS;}\n") + line = [] fnptypeline = [] @@ -339,11 +338,22 @@ def generateClrallEvents(eventNodes,allTemplates): fnptype.extend(fnptypeline) fnptype.append("\n)\n{\n") fnbody.append(lindent) - fnbody.append("return FireEtXplat") + fnbody.append("ULONG status = EventPipeWriteEvent" + eventName + "(" + ''.join(line) + ");\n") + fnbody.append(lindent) + fnbody.append("if(XplatEventLogger::IsEventLoggingEnabled())\n") + fnbody.append(lindent) + fnbody.append("{\n") + fnbody.append(lindent) + fnbody.append(lindent) + fnbody.append("status &= FireEtXplat") fnbody.append(eventName) fnbody.append("(") fnbody.extend(line) fnbody.append(");\n") + fnbody.append(lindent) + fnbody.append("}\n") + fnbody.append(lindent) + fnbody.append("return status;\n") fnbody.append("}\n\n") clrallEvents.extend(fnptype) @@ -400,6 +410,57 @@ def generateClrXplatEvents(eventNodes, allTemplates): return ''.join(clrallEvents) +def generateClrEventPipeWriteEvents(eventNodes, allTemplates): + clrallEvents = [] + for eventNode in eventNodes: + eventName = eventNode.getAttribute('symbol') + templateName = eventNode.getAttribute('template') + + #generate EventPipeEventEnabled and EventPipeWriteEvent functions + eventenabled = [] + writeevent = [] + fnptypeline = [] + + eventenabled.append("extern \"C\" bool EventPipeEventEnabled") + eventenabled.append(eventName) + eventenabled.append("();\n") + + writeevent.append("extern \"C\" ULONG EventPipeWriteEvent") + writeevent.append(eventName) + writeevent.append("(\n") + + if templateName: + template = allTemplates[templateName] + fnSig = template.signature + + for params in fnSig.paramlist: + fnparam = fnSig.getParam(params) + wintypeName = fnparam.winType + typewName = palDataTypeMapping[wintypeName] + winCount = fnparam.count + countw = palDataTypeMapping[winCount] + + if params in template.structs: + fnptypeline.append("%sint %s_ElementSize,\n" % (lindent, params)) + + fnptypeline.append(lindent) + fnptypeline.append(typewName) + fnptypeline.append(countw) + fnptypeline.append(" ") + fnptypeline.append(fnparam.name) + fnptypeline.append(",\n") + + #remove trailing commas + if len(fnptypeline) > 0: + del fnptypeline[-1] + + writeevent.extend(fnptypeline) + writeevent.append("\n);\n") + clrallEvents.extend(eventenabled) + clrallEvents.extend(writeevent) + + return ''.join(clrallEvents) + #generates the dummy header file which is used by the VM as entry point to the logging Functions def generateclrEtwDummy(eventNodes,allTemplates): clretmEvents = [] @@ -670,15 +731,19 @@ def generatePlformIndependentFiles(sClrEtwAllMan,incDir,etmDummyFile): clrallevents = incDir + "/clretwallmain.h" clrxplatevents = incDir + "/clrxplatevents.h" + clreventpipewriteevents = incDir + "/clreventpipewriteevents.h" Clrallevents = open(clrallevents,'w') Clrxplatevents = open(clrxplatevents,'w') + Clreventpipewriteevents = open(clreventpipewriteevents,'w') Clrallevents.write(stdprolog + "\n") Clrxplatevents.write(stdprolog + "\n") + Clreventpipewriteevents.write(stdprolog + "\n") - Clrallevents.write("\n#include \"clrxplatevents.h\"\n\n") - + Clrallevents.write("\n#include \"clrxplatevents.h\"\n") + Clrallevents.write("#include \"clreventpipewriteevents.h\"\n\n") + for providerNode in tree.getElementsByTagName('provider'): templateNodes = providerNode.getElementsByTagName('template') allTemplates = parseTemplateNodes(templateNodes) @@ -689,8 +754,12 @@ def generatePlformIndependentFiles(sClrEtwAllMan,incDir,etmDummyFile): #pal: create clrallevents.h Clrxplatevents.write(generateClrXplatEvents(eventNodes, allTemplates) + "\n") + #eventpipe: create clreventpipewriteevents.h + Clreventpipewriteevents.write(generateClrEventPipeWriteEvents(eventNodes, allTemplates) + "\n") + Clrxplatevents.close() Clrallevents.close() + Clreventpipewriteevents.close() class EventExclusions: def __init__(self): diff --git a/src/scripts/genXplatLttng.py b/src/scripts/genXplatLttng.py index 6dd60b0a0b..fae0e120da 100644 --- a/src/scripts/genXplatLttng.py +++ b/src/scripts/genXplatLttng.py @@ -594,7 +594,7 @@ bool ResizeBuffer(char *&buffer, int& size, int currLen, int newSize, bool &fixe bool WriteToBuffer(const BYTE *src, int len, char *&buffer, int& offset, int& size, bool &fixedBuffer) { if (!src) return true; - if (offset + len) + if (offset + len > size) { if (!ResizeBuffer(buffer, size, offset, size + len, fixedBuffer)) return false; @@ -610,7 +610,7 @@ bool WriteToBuffer(PCWSTR str, char *&buffer, int& offset, int& size, bool &fixe if (!str) return true; int byteCount = (PAL_wcslen(str) + 1) * sizeof(*str); - if (offset + byteCount) + if (offset + byteCount > size) { if (!ResizeBuffer(buffer, size, offset, size + byteCount, fixedBuffer)) return false; @@ -625,7 +625,7 @@ bool WriteToBuffer(const char *str, char *&buffer, int& offset, int& size, bool { if (!str) return true; int len = strlen(str) + 1; - if (offset + len) + if (offset + len > size) { if (!ResizeBuffer(buffer, size, offset, size + len, fixedBuffer)) return false; diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index ec4ff425d6..556eb0e93a 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -164,8 +164,14 @@ set(VM_SOURCES_WKS eepolicy.cpp eetoprofinterfaceimpl.cpp eventpipe.cpp + eventpipeconfiguration.cpp + eventpipeevent.cpp + eventpipeeventinstance.cpp + eventpipefile.cpp eventpipejsonfile.cpp + eventpipeprovider.cpp eventstore.cpp + fastserializer.cpp fcall.cpp fieldmarshaler.cpp finalizerthread.cpp @@ -481,3 +487,7 @@ convert_to_absolute_path(VM_SOURCES_DAC ${VM_SOURCES_DAC}) add_subdirectory(dac) add_subdirectory(wks) + +if(CLR_CMAKE_PLATFORM_LINUX) + add_subdirectory($ENV{__IntermediatesDir}/Generated/eventpipe ${CMAKE_CURRENT_BINARY_DIR}/eventpipe) +endif(CLR_CMAKE_PLATFORM_LINUX) diff --git a/src/vm/ceemain.cpp b/src/vm/ceemain.cpp index 9cce46d2f0..c82f7d4ee5 100644 --- a/src/vm/ceemain.cpp +++ b/src/vm/ceemain.cpp @@ -693,6 +693,11 @@ void EEStartupHelper(COINITIEE fFlags) InitThreadManager(); STRESS_LOG0(LF_STARTUP, LL_ALWAYS, "Returned successfully from InitThreadManager"); +#ifdef FEATURE_PERFTRACING + // Initialize the event pipe. + EventPipe::Initialize(); +#endif // FEATURE_PERFTRACING + #ifdef FEATURE_EVENT_TRACE // Initialize event tracing early so we can trace CLR startup time events. InitializeEventTracing(); @@ -1036,8 +1041,7 @@ void EEStartupHelper(COINITIEE fFlags) #endif #ifdef FEATURE_PERFTRACING - // Initialize the event pipe and start it if requested. - EventPipe::Initialize(); + // Start the event pipe if requested. EventPipe::EnableOnStartup(); #endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipe.cpp b/src/vm/eventpipe.cpp index 98d382ea17..8ea3f0867e 100644 --- a/src/vm/eventpipe.cpp +++ b/src/vm/eventpipe.cpp @@ -4,21 +4,46 @@ #include "common.h" #include "eventpipe.h" +#include "eventpipeconfiguration.h" +#include "eventpipeevent.h" +#include "eventpipefile.h" +#include "eventpipeprovider.h" #include "eventpipejsonfile.h" #include "sampleprofiler.h" -CrstStatic EventPipe::s_initCrst; +#ifdef FEATURE_PAL +#include "pal.h" +#endif // FEATURE_PAL + +#ifdef FEATURE_PERFTRACING + +CrstStatic EventPipe::s_configCrst; bool EventPipe::s_tracingInitialized = false; -bool EventPipe::s_tracingEnabled = false; +EventPipeConfiguration* EventPipe::s_pConfig = NULL; +EventPipeFile* EventPipe::s_pFile = NULL; EventPipeJsonFile* EventPipe::s_pJsonFile = NULL; +#ifdef FEATURE_PAL +// This function is auto-generated from /src/scripts/genEventPipe.py +extern "C" void InitProvidersAndEvents(); +#endif + void EventPipe::Initialize() { STANDARD_VM_CONTRACT; - s_tracingInitialized = s_initCrst.InitNoThrow( + s_tracingInitialized = s_configCrst.InitNoThrow( CrstEventPipe, - (CrstFlags)(CRST_TAKEN_DURING_SHUTDOWN)); + (CrstFlags)(CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN)); + + s_pConfig = new EventPipeConfiguration(); + s_pConfig->Initialize(); + +#ifdef FEATURE_PAL + // This calls into auto-generated code to initialize the runtime providers + // and events so that the EventPipe configuration lock isn't taken at runtime + InitProvidersAndEvents(); +#endif } void EventPipe::EnableOnStartup() @@ -66,9 +91,14 @@ void EventPipe::Enable() return; } - // Take the lock and enable tracing. - CrstHolder _crst(&s_initCrst); - s_tracingEnabled = true; + // Take the lock before enabling tracing. + CrstHolder _crst(GetLock()); + + // Create the event pipe file. + SString eventPipeFileOutputPath; + eventPipeFileOutputPath.Printf("Process-%d.netperf", GetCurrentProcessId()); + s_pFile = new EventPipeFile(eventPipeFileOutputPath); + if(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_PerformanceTracing) == 2) { // File placed in current working directory. @@ -77,7 +107,14 @@ void EventPipe::Enable() s_pJsonFile = new EventPipeJsonFile(outputFilePath); } + // Enable tracing. + s_pConfig->Enable(); + + // Enable the sample profiler SampleProfiler::Enable(); + + // TODO: Iterate through the set of providers, enable them as appropriate. + // This in-turn will iterate through all of the events and set their isEnabled bits. } void EventPipe::Disable() @@ -90,32 +127,38 @@ void EventPipe::Disable() } CONTRACTL_END; - CrstHolder _crst(&s_initCrst); - s_tracingEnabled = false; + // Don't block GC during clean-up. + GCX_PREEMP(); + + // Take the lock before disabling tracing. + CrstHolder _crst(GetLock()); + + // Disable the profiler. SampleProfiler::Disable(); + // Disable tracing. + s_pConfig->Disable(); + if(s_pJsonFile != NULL) { delete(s_pJsonFile); s_pJsonFile = NULL; } -} -bool EventPipe::EventEnabled(GUID& providerID, INT64 keyword) -{ - CONTRACTL + if(s_pFile != NULL) { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; + delete(s_pFile); + s_pFile = NULL; } - CONTRACTL_END; - // TODO: Implement filtering. - return false; + if(s_pConfig != NULL) + { + delete(s_pConfig); + s_pConfig = NULL; + } } -void EventPipe::WriteEvent(GUID& providerID, INT64 eventID, BYTE *pData, size_t length, bool sampleStack) +void EventPipe::WriteEvent(EventPipeEvent &event, BYTE *pData, unsigned int length) { CONTRACTL { @@ -125,41 +168,53 @@ void EventPipe::WriteEvent(GUID& providerID, INT64 eventID, BYTE *pData, size_t } CONTRACTL_END; - StackContents stackContents; - bool stackWalkSucceeded; - - if(sampleStack) + // Exit early if the event is not enabled. + if(!event.IsEnabled()) { - stackWalkSucceeded = WalkManagedStackForCurrentThread(stackContents); + return; } - // TODO: Write the event. + DWORD threadID = GetCurrentThreadId(); + + // Create an instance of the event. + EventPipeEventInstance instance( + event, + threadID, + pData, + length); + + // Write to the EventPipeFile. + _ASSERTE(s_pFile != NULL); + s_pFile->WriteEvent(instance); + + // Write to the EventPipeJsonFile if it exists. + if(s_pJsonFile != NULL) + { + s_pJsonFile->WriteEvent(instance); + } } -void EventPipe::WriteSampleProfileEvent(Thread *pThread, StackContents &stackContents) +void EventPipe::WriteSampleProfileEvent(SampleProfilerEventInstance &instance) { CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; - PRECONDITION(pThread != NULL); } CONTRACTL_END; - EX_TRY + // Write to the EventPipeFile. + if(s_pFile != NULL) { - if(s_pJsonFile != NULL) - { - CommonEventFields eventFields; - QueryPerformanceCounter(&eventFields.TimeStamp); - eventFields.ThreadID = pThread->GetOSThreadId(); + s_pFile->WriteEvent(instance); + } - static SString message(W("THREAD_TIME")); - s_pJsonFile->WriteEvent(eventFields, message, stackContents); - } + // Write to the EventPipeJsonFile if it exists. + if(s_pJsonFile != NULL) + { + s_pJsonFile->WriteEvent(instance); } - EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); } bool EventPipe::WalkManagedStackForCurrentThread(StackContents &stackContents) @@ -173,8 +228,12 @@ bool EventPipe::WalkManagedStackForCurrentThread(StackContents &stackContents) CONTRACTL_END; Thread *pThread = GetThread(); - _ASSERTE(pThread != NULL); - return WalkManagedStackForThread(pThread, stackContents); + if(pThread != NULL) + { + return WalkManagedStackForThread(pThread, stackContents); + } + + return false; } bool EventPipe::WalkManagedStackForThread(Thread *pThread, StackContents &stackContents) @@ -232,3 +291,19 @@ StackWalkAction EventPipe::StackWalkCallback(CrawlFrame *pCf, StackContents *pDa // Continue the stack walk. return SWA_CONTINUE; } + +EventPipeConfiguration* EventPipe::GetConfiguration() +{ + LIMITED_METHOD_CONTRACT; + + return s_pConfig; +} + +CrstStatic* EventPipe::GetLock() +{ + LIMITED_METHOD_CONTRACT; + + return &s_configCrst; +} + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipe.h b/src/vm/eventpipe.h index 2978412325..c6d7f61152 100644 --- a/src/vm/eventpipe.h +++ b/src/vm/eventpipe.h @@ -5,19 +5,17 @@ #ifndef __EVENTPIPE_H__ #define __EVENTPIPE_H__ -#include "common.h" +#ifdef FEATURE_PERFTRACING -class EventPipeJsonFile; - -// The data fields common to every event. -struct CommonEventFields -{ - // Timestamp generated by QueryPerformanceCounter. - LARGE_INTEGER TimeStamp; +#include "crst.h" +#include "stackwalk.h" - // Thread ID. - DWORD ThreadID; -}; +class EventPipeConfiguration; +class EventPipeEvent; +class EventPipeFile; +class EventPipeJsonFile; +class MethodDesc; +class SampleProfilerEventInstance; class StackContents { @@ -103,10 +101,30 @@ public: m_nextAvailableFrame++; } } + + BYTE* GetPointer() const + { + LIMITED_METHOD_CONTRACT; + + return (BYTE*)m_stackFrames; + } + + unsigned int GetSize() const + { + LIMITED_METHOD_CONTRACT; + + return (m_nextAvailableFrame * sizeof(UINT_PTR)); + } }; class EventPipe { + // Declare friends. + friend class EventPipeConfiguration; + friend class EventPipeFile; + friend class EventPipeProvider; + friend class SampleProfiler; + public: // Initialize the event pipe. @@ -124,15 +142,12 @@ class EventPipe // Disable tracing via the event pipe. static void Disable(); - // Determine whether or not the specified provider/keyword combination is enabled. - static bool EventEnabled(GUID& providerID, INT64 keyword); - - // Write out an event. The event is identified by the providerID/eventID pair. + // Write out an event. // Data is written as a serialized blob matching the ETW serialization conventions. - static void WriteEvent(GUID& providerID, INT64 eventID, BYTE *pData, size_t length, bool sampleStack); + static void WriteEvent(EventPipeEvent &event, BYTE *pData, unsigned int length); - // Write out a sample profile event with the specified stack. - static void WriteSampleProfileEvent(Thread *pThread, StackContents &stackContents); + // Write out a sample profile event. + static void WriteSampleProfileEvent(SampleProfilerEventInstance &instance); // Get the managed call stack for the current thread. static bool WalkManagedStackForCurrentThread(StackContents &stackContents); @@ -145,10 +160,20 @@ class EventPipe // Callback function for the stack walker. For each frame walked, this callback is invoked. static StackWalkAction StackWalkCallback(CrawlFrame *pCf, StackContents *pData); - static CrstStatic s_initCrst; + // Get the configuration object. + // This is called directly by the EventPipeProvider constructor to register the new provider. + static EventPipeConfiguration* GetConfiguration(); + + // Get the event pipe configuration lock. + static CrstStatic* GetLock(); + + static CrstStatic s_configCrst; static bool s_tracingInitialized; - static bool s_tracingEnabled; + static EventPipeConfiguration *s_pConfig; + static EventPipeFile *s_pFile; static EventPipeJsonFile *s_pJsonFile; }; +#endif // FEATURE_PERFTRACING + #endif // __EVENTPIPE_H__ diff --git a/src/vm/eventpipeconfiguration.cpp b/src/vm/eventpipeconfiguration.cpp new file mode 100644 index 0000000000..cfb96fc8d5 --- /dev/null +++ b/src/vm/eventpipeconfiguration.cpp @@ -0,0 +1,271 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "common.h" +#include "eventpipe.h" +#include "eventpipeconfiguration.h" +#include "eventpipeeventinstance.h" +#include "eventpipeprovider.h" + +#ifdef FEATURE_PERFTRACING + +// {5291C09C-2660-4D6A-83A3-C383FD020DEC} +const GUID EventPipeConfiguration::s_configurationProviderID = + { 0x5291c09c, 0x2660, 0x4d6a, { 0x83, 0xa3, 0xc3, 0x83, 0xfd, 0x2, 0xd, 0xec } }; + +EventPipeConfiguration::EventPipeConfiguration() +{ + STANDARD_VM_CONTRACT; + + m_pProviderList = new SList<SListElem<EventPipeProvider*>>(); +} + +EventPipeConfiguration::~EventPipeConfiguration() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + if(m_pProviderList != NULL) + { + delete(m_pProviderList); + m_pProviderList = NULL; + } +} + +void EventPipeConfiguration::Initialize() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Create the configuration provider. + m_pConfigProvider = new EventPipeProvider(s_configurationProviderID); + + // Create the metadata event. + m_pMetadataEvent = m_pConfigProvider->AddEvent( + 0, /* keywords */ + 0, /* eventID */ + 0, /* eventVersion */ + EventPipeEventLevel::Critical, + false); /* needStack */ +} + +bool EventPipeConfiguration::RegisterProvider(EventPipeProvider &provider) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Take the lock before manipulating the provider list. + CrstHolder _crst(EventPipe::GetLock()); + + // See if we've already registered this provider. + EventPipeProvider *pExistingProvider = GetProviderNoLock(provider.GetProviderID()); + if(pExistingProvider != NULL) + { + return false; + } + + // The provider has not been registered, so register it. + m_pProviderList->InsertTail(new SListElem<EventPipeProvider*>(&provider)); + + // TODO: Set the provider configuration and enable it if we know + // anything about the provider before it is registered. + + return true; +} + +bool EventPipeConfiguration::UnregisterProvider(EventPipeProvider &provider) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Take the lock before manipulating the provider list. + CrstHolder _crst(EventPipe::GetLock()); + + // Find the provider. + SListElem<EventPipeProvider*> *pElem = m_pProviderList->GetHead(); + while(pElem != NULL) + { + if(pElem->GetValue() == &provider) + { + break; + } + + pElem = m_pProviderList->GetNext(pElem); + } + + // If we found the provider, remove it. + if(pElem != NULL) + { + if(m_pProviderList->FindAndRemove(pElem) != NULL) + { + return true; + } + } + + return false; +} + +EventPipeProvider* EventPipeConfiguration::GetProvider(const GUID &providerID) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Take the lock before touching the provider list to ensure no one tries to + // modify the list. + CrstHolder _crst(EventPipe::GetLock()); + + return GetProviderNoLock(providerID); +} + +EventPipeProvider* EventPipeConfiguration::GetProviderNoLock(const GUID &providerID) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); + } + CONTRACTL_END; + + SListElem<EventPipeProvider*> *pElem = m_pProviderList->GetHead(); + while(pElem != NULL) + { + EventPipeProvider *pProvider = pElem->GetValue(); + if(pProvider->GetProviderID() == providerID) + { + return pProvider; + } + + pElem = m_pProviderList->GetNext(pElem); + } + + return NULL; +} + +void EventPipeConfiguration::Enable() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + // Lock must be held by EventPipe::Enable. + PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); + } + CONTRACTL_END; + + SListElem<EventPipeProvider*> *pElem = m_pProviderList->GetHead(); + while(pElem != NULL) + { + // TODO: Only enable the providers that have been explicitly enabled with specified keywords/level. + EventPipeProvider *pProvider = pElem->GetValue(); + pProvider->SetConfiguration(true /* providerEnabled */, 0xFFFFFFFFFFFFFFFF /* keywords */, EventPipeEventLevel::Verbose /* level */); + + pElem = m_pProviderList->GetNext(pElem); + } + +} + +void EventPipeConfiguration::Disable() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + // Lock must be held by EventPipe::Disable. + PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); + } + CONTRACTL_END; + + SListElem<EventPipeProvider*> *pElem = m_pProviderList->GetHead(); + while(pElem != NULL) + { + EventPipeProvider *pProvider = pElem->GetValue(); + pProvider->SetConfiguration(false /* providerEnabled */, 0 /* keywords */, EventPipeEventLevel::Critical /* level */); + + pElem = m_pProviderList->GetNext(pElem); + } +} + +EventPipeEventInstance* EventPipeConfiguration::BuildEventMetadataEvent(EventPipeEvent &sourceEvent, BYTE *pPayloadData, unsigned int payloadLength) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // The payload of the event should contain: + // - GUID ProviderID. + // - unsigned int EventID. + // - unsigned int EventVersion. + // - Optional event description payload. + + // Calculate the size of the event. + const GUID &providerID = sourceEvent.GetProvider()->GetProviderID(); + unsigned int eventID = sourceEvent.GetEventID(); + unsigned int eventVersion = sourceEvent.GetEventVersion(); + unsigned int instancePayloadSize = sizeof(providerID) + sizeof(eventID) + sizeof(eventVersion) + payloadLength; + + // Allocate the payload. + BYTE *pInstancePayload = new BYTE[instancePayloadSize]; + + // Fill the buffer with the payload. + BYTE *currentPtr = pInstancePayload; + + // Write the provider ID. + memcpy(currentPtr, (BYTE*)&providerID, sizeof(providerID)); + currentPtr += sizeof(providerID); + + // Write the event ID. + memcpy(currentPtr, &eventID, sizeof(eventID)); + currentPtr += sizeof(eventID); + + // Write the event version. + memcpy(currentPtr, &eventVersion, sizeof(eventVersion)); + currentPtr += sizeof(eventVersion); + + // Write the incoming payload data. + memcpy(currentPtr, pPayloadData, payloadLength); + + // Construct the event instance. + EventPipeEventInstance *pInstance = new EventPipeEventInstance( + *m_pMetadataEvent, + GetCurrentThreadId(), + pInstancePayload, + instancePayloadSize); + + return pInstance; +} + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipeconfiguration.h b/src/vm/eventpipeconfiguration.h new file mode 100644 index 0000000000..a2377757d4 --- /dev/null +++ b/src/vm/eventpipeconfiguration.h @@ -0,0 +1,64 @@ +// 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_CONFIGURATION_H__ +#define __EVENTPIPE_CONFIGURATION_H__ + +#ifdef FEATURE_PERFTRACING + +#include "slist.h" + +class EventPipeEvent; +class EventPipeEventInstance; +class EventPipeProvider; + +class EventPipeConfiguration +{ +public: + + EventPipeConfiguration(); + ~EventPipeConfiguration(); + + // Perform initialization that cannot be performed in the constructor. + void Initialize(); + + // Register a provider. + bool RegisterProvider(EventPipeProvider &provider); + + // Unregister a provider. + bool UnregisterProvider(EventPipeProvider &provider); + + // Get the provider with the specified provider ID if it exists. + EventPipeProvider* GetProvider(const GUID &providerID); + + // Enable the event pipe. + void Enable(); + + // Disable the event pipe. + void Disable(); + + // Get the event used to write metadata to the event stream. + EventPipeEventInstance* BuildEventMetadataEvent(EventPipeEvent &sourceEvent, BYTE *pPayloadData = NULL, unsigned int payloadLength = 0); + +private: + + // Get the provider without taking the lock. + EventPipeProvider* GetProviderNoLock(const GUID &providerID); + + // The list of event pipe providers. + SList<SListElem<EventPipeProvider*>> *m_pProviderList; + + // The provider used to write configuration events to the event stream. + EventPipeProvider *m_pConfigProvider; + + // The event used to write event information to the event stream. + EventPipeEvent *m_pMetadataEvent; + + // The provider ID for the configuration event pipe provider. + // This provider is used to emit configuration events. + static const GUID s_configurationProviderID; +}; + +#endif // FEATURE_PERFTRACING + +#endif // __EVENTPIPE_CONFIGURATION_H__ diff --git a/src/vm/eventpipeevent.cpp b/src/vm/eventpipeevent.cpp new file mode 100644 index 0000000000..3b9f36bc4b --- /dev/null +++ b/src/vm/eventpipeevent.cpp @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "common.h" +#include "eventpipeevent.h" +#include "eventpipeprovider.h" + +#ifdef FEATURE_PERFTRACING + +EventPipeEvent::EventPipeEvent(EventPipeProvider &provider, INT64 keywords, unsigned int eventID, unsigned int eventVersion, EventPipeEventLevel level, bool needStack) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + m_pProvider = &provider; + m_keywords = keywords; + m_eventID = eventID; + m_eventVersion = eventVersion; + m_level = level; + m_needStack = needStack; + m_enabled = false; +} + +EventPipeProvider* EventPipeEvent::GetProvider() const +{ + LIMITED_METHOD_CONTRACT; + + return m_pProvider; +} + +INT64 EventPipeEvent::GetKeywords() const +{ + LIMITED_METHOD_CONTRACT; + + return m_keywords; +} + +unsigned int EventPipeEvent::GetEventID() const +{ + LIMITED_METHOD_CONTRACT; + + return m_eventID; +} + +unsigned int EventPipeEvent::GetEventVersion() const +{ + LIMITED_METHOD_CONTRACT; + + return m_eventVersion; +} + +EventPipeEventLevel EventPipeEvent::GetLevel() const +{ + LIMITED_METHOD_CONTRACT; + + return m_level; +} + +bool EventPipeEvent::NeedStack() const +{ + LIMITED_METHOD_CONTRACT; + + return m_needStack; +} + +bool EventPipeEvent::IsEnabled() const +{ + LIMITED_METHOD_CONTRACT; + + return m_enabled; +} + +void EventPipeEvent::RefreshState() +{ + LIMITED_METHOD_CONTRACT; + + m_enabled = m_pProvider->EventEnabled(m_keywords, m_level); +} + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipeevent.h b/src/vm/eventpipeevent.h new file mode 100644 index 0000000000..9e42615ed9 --- /dev/null +++ b/src/vm/eventpipeevent.h @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __EVENTPIPE_EVENT_H__ +#define __EVENTPIPE_EVENT_H__ + +#ifdef FEATURE_PERFTRACING + +#include "eventpipeprovider.h" + +class EventPipeEvent +{ + // Declare friends. + friend class EventPipeProvider; + +private: + + // The provider that contains the event. + EventPipeProvider *m_pProvider; + + // Bit vector containing the keywords that enable the event. + INT64 m_keywords; + + // The ID (within the provider) of the event. + unsigned int m_eventID; + + // The version of the event. + unsigned int m_eventVersion; + + // The verbosity of the event. + EventPipeEventLevel m_level; + + // True if a call stack should be captured when writing the event. + bool m_needStack; + + // True if the event is current enabled. + bool m_enabled; + + // Refreshes the runtime state for this event. + // Called by EventPipeProvider when the provider configuration changes. + void RefreshState(); + + // Only EventPipeProvider can create events. + // The provider is responsible for allocating and freeing events. + EventPipeEvent(EventPipeProvider &provider, INT64 keywords, unsigned int eventID, unsigned int eventVersion, EventPipeEventLevel level, bool needStack); + +public: + + // Get the provider associated with this event. + EventPipeProvider* GetProvider() const; + + // Get the keywords that enable the event. + INT64 GetKeywords() const; + + // Get the ID (within the provider) of the event. + unsigned int GetEventID() const; + + // Get the version of the event. + unsigned int GetEventVersion() const; + + // Get the verbosity of the event. + EventPipeEventLevel GetLevel() const; + + // True if a call stack should be captured when writing the event. + bool NeedStack() const; + + // True if the event is currently enabled. + bool IsEnabled() const; +}; + +#endif // FEATURE_PERFTRACING + +#endif // __EVENTPIPE_EVENT_H__ diff --git a/src/vm/eventpipeeventinstance.cpp b/src/vm/eventpipeeventinstance.cpp new file mode 100644 index 0000000000..2bf500be70 --- /dev/null +++ b/src/vm/eventpipeeventinstance.cpp @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "common.h" +#include "eventpipeeventinstance.h" +#include "eventpipejsonfile.h" +#include "fastserializer.h" +#include "sampleprofiler.h" + +#ifdef FEATURE_PERFTRACING + +EventPipeEventInstance::EventPipeEventInstance( + EventPipeEvent &event, + DWORD threadID, + BYTE *pData, + unsigned int length) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + m_pEvent = &event; + m_threadID = threadID; + m_pData = pData; + m_dataLength = length; + QueryPerformanceCounter(&m_timeStamp); + + if(event.NeedStack()) + { + EventPipe::WalkManagedStackForCurrentThread(m_stackContents); + } +} + +StackContents* EventPipeEventInstance::GetStack() +{ + LIMITED_METHOD_CONTRACT; + + return &m_stackContents; +} + +EventPipeEvent* EventPipeEventInstance::GetEvent() const +{ + LIMITED_METHOD_CONTRACT; + + return m_pEvent; +} + +BYTE* EventPipeEventInstance::GetData() const +{ + LIMITED_METHOD_CONTRACT; + + return m_pData; +} + +unsigned int EventPipeEventInstance::GetLength() const +{ + LIMITED_METHOD_CONTRACT; + + return m_dataLength; +} + +void EventPipeEventInstance::FastSerialize(FastSerializer *pSerializer, StreamLabel metadataLabel) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + +#ifdef _DEBUG + // Useful for diagnosing serialization bugs. + const unsigned int value = 0xDEADBEEF; + pSerializer->WriteBuffer((BYTE*)&value, sizeof(value)); +#endif + + // Calculate the size of the total payload so that it can be written to the file. + unsigned int payloadLength = + sizeof(metadataLabel) + + sizeof(m_threadID) + // Thread ID + sizeof(m_timeStamp) + // TimeStamp + m_dataLength + // Event payload data length + m_stackContents.GetSize(); // Stack payload size + + // Write the size of the event to the file. + pSerializer->WriteBuffer((BYTE*)&payloadLength, sizeof(payloadLength)); + + // Write the metadata label. + pSerializer->WriteBuffer((BYTE*)&metadataLabel, sizeof(metadataLabel)); + + // Write the thread ID. + pSerializer->WriteBuffer((BYTE*)&m_threadID, sizeof(m_threadID)); + + // Write the timestamp. + pSerializer->WriteBuffer((BYTE*)&m_timeStamp, sizeof(m_timeStamp)); + + // Write the event data payload. + if(m_dataLength > 0) + { + pSerializer->WriteBuffer(m_pData, m_dataLength); + } + + // Write the stack if present. + if(m_stackContents.GetSize() > 0) + { + pSerializer->WriteBuffer(m_stackContents.GetPointer(), m_stackContents.GetSize()); + } +} + +void EventPipeEventInstance::SerializeToJsonFile(EventPipeJsonFile *pFile) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if(pFile == NULL) + { + return; + } + + EX_TRY + { + const unsigned int guidSize = 39; + WCHAR wszProviderID[guidSize]; + if(!StringFromGUID2(m_pEvent->GetProvider()->GetProviderID(), wszProviderID, guidSize)) + { + wszProviderID[0] = '\0'; + } + + // Strip off the {}. + StackScratchBuffer scratch; + SString guidStr(&wszProviderID[1], guidSize-3); + + SString message; + message.Printf("Provider=%s/EventID=%d/Version=%d", guidStr.GetANSI(scratch), m_pEvent->GetEventID(), m_pEvent->GetEventVersion()); + pFile->WriteEvent(m_timeStamp, m_threadID, message, m_stackContents); + } + EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); +} + +SampleProfilerEventInstance::SampleProfilerEventInstance(Thread *pThread) + :EventPipeEventInstance(*SampleProfiler::s_pThreadTimeEvent, pThread->GetOSThreadId(), NULL, 0) +{ + LIMITED_METHOD_CONTRACT; +} + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipeeventinstance.h b/src/vm/eventpipeeventinstance.h new file mode 100644 index 0000000000..84ad566489 --- /dev/null +++ b/src/vm/eventpipeeventinstance.h @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __EVENTPIPE_EVENTINSTANCE_H__ +#define __EVENTPIPE_EVENTINSTANCE_H__ + +#ifdef FEATURE_PERFTRACING + +#include "eventpipe.h" +#include "eventpipeevent.h" +#include "fastserializableobject.h" +#include "fastserializer.h" + +class EventPipeEventInstance +{ + +public: + + EventPipeEventInstance(EventPipeEvent &event, DWORD threadID, BYTE *pData, unsigned int length); + + // Get the event associated with this instance. + EventPipeEvent* GetEvent() const; + + // Get the stack contents object to either read or write to it. + StackContents* GetStack(); + + // Get a pointer to the data payload. + BYTE* GetData() const; + + // Get the length of the data. + unsigned int GetLength() const; + + // Serialize this object using FastSerialization. + void FastSerialize(FastSerializer *pSerializer, StreamLabel metadataLabel); + + // Serialize this event to the JSON file. + void SerializeToJsonFile(EventPipeJsonFile *pFile); + +protected: + + EventPipeEvent *m_pEvent; + DWORD m_threadID; + LARGE_INTEGER m_timeStamp; + + BYTE *m_pData; + unsigned int m_dataLength; + StackContents m_stackContents; +}; + +// A specific type of event instance for use by the SampleProfiler. +// This is needed because the SampleProfiler knows how to walk stacks belonging +// to threads other than the current thread. +class SampleProfilerEventInstance : public EventPipeEventInstance +{ + +public: + + SampleProfilerEventInstance(Thread *pThread); +}; + +#endif // FEATURE_PERFTRACING + +#endif // __EVENTPIPE_EVENTINSTANCE_H__ diff --git a/src/vm/eventpipefile.cpp b/src/vm/eventpipefile.cpp new file mode 100644 index 0000000000..895f732f51 --- /dev/null +++ b/src/vm/eventpipefile.cpp @@ -0,0 +1,146 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "common.h" +#include "eventpipeconfiguration.h" +#include "eventpipefile.h" + +#ifdef FEATURE_PERFTRACING + +EventPipeFile::EventPipeFile(SString &outputFilePath) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + m_pSerializer = new FastSerializer(outputFilePath, *this); + m_serializationLock.Init(LOCK_TYPE_DEFAULT); + m_pMetadataLabels = new MapSHashWithRemove<EventPipeEvent*, StreamLabel>(); + + // File start time information. + GetSystemTime(&m_fileOpenSystemTime); + QueryPerformanceCounter(&m_fileOpenTimeStamp); + QueryPerformanceFrequency(&m_timeStampFrequency); + + // Write a forward reference to the beginning of the event stream. + // This also allows readers to know where the event stream ends and skip it if needed. + m_beginEventsForwardReferenceIndex = m_pSerializer->AllocateForwardReference(); + m_pSerializer->WriteForwardReference(m_beginEventsForwardReferenceIndex); + + // Write the header information into the file. + + // Write the current date and time. + m_pSerializer->WriteBuffer((BYTE*)&m_fileOpenSystemTime, sizeof(m_fileOpenSystemTime)); + + // Write FileOpenTimeStamp + m_pSerializer->WriteBuffer((BYTE*)&m_fileOpenTimeStamp, sizeof(m_fileOpenTimeStamp)); + + // Write ClockFrequency + m_pSerializer->WriteBuffer((BYTE*)&m_timeStampFrequency, sizeof(m_timeStampFrequency)); +} + +EventPipeFile::~EventPipeFile() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // Mark the end of the event stream. + StreamLabel currentLabel = m_pSerializer->GetStreamLabel(); + + // Define the event start forward reference. + m_pSerializer->DefineForwardReference(m_beginEventsForwardReferenceIndex, currentLabel); + + // Close the serializer. + if(m_pSerializer != NULL) + { + delete(m_pSerializer); + m_pSerializer = NULL; + } +} + +void EventPipeFile::WriteEvent(EventPipeEventInstance &instance) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // Take the serialization lock. + SpinLockHolder _slh(&m_serializationLock); + + // Check to see if we've seen this event type before. + // If not, then write the event metadata to the event stream first. + StreamLabel metadataLabel = GetMetadataLabel(*instance.GetEvent()); + if(metadataLabel == 0) + { + EventPipeEventInstance* pMetadataInstance = EventPipe::GetConfiguration()->BuildEventMetadataEvent(*instance.GetEvent()); + + metadataLabel = m_pSerializer->GetStreamLabel(); + pMetadataInstance->FastSerialize(m_pSerializer, (StreamLabel)0); // 0 breaks recursion and represents the metadata event. + + SaveMetadataLabel(*instance.GetEvent(), metadataLabel); + + delete (pMetadataInstance->GetData()); + delete (pMetadataInstance); + } + + // Write the event to the stream. + instance.FastSerialize(m_pSerializer, metadataLabel); +} + +StreamLabel EventPipeFile::GetMetadataLabel(EventPipeEvent &event) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + StreamLabel outLabel; + if(m_pMetadataLabels->Lookup(&event, &outLabel)) + { + _ASSERTE(outLabel != 0); + return outLabel; + } + + return 0; +} + +void EventPipeFile::SaveMetadataLabel(EventPipeEvent &event, StreamLabel label) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(label > 0); + } + CONTRACTL_END; + + // If a pre-existing metadata label exists, remove it. + StreamLabel outLabel; + if(m_pMetadataLabels->Lookup(&event, &outLabel)) + { + m_pMetadataLabels->Remove(&event); + } + + // Add the metadata label. + m_pMetadataLabels->Add(&event, label); +} + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipefile.h b/src/vm/eventpipefile.h new file mode 100644 index 0000000000..1fbb4c0b79 --- /dev/null +++ b/src/vm/eventpipefile.h @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +#ifndef __EVENTPIPE_FILE_H__ +#define __EVENTPIPE_FILE_H__ + +#ifdef FEATURE_PERFTRACING + +#include "eventpipe.h" +#include "eventpipeeventinstance.h" +#include "fastserializableobject.h" +#include "fastserializer.h" + +class EventPipeFile : public FastSerializableObject +{ + public: + EventPipeFile(SString &outputFilePath); + ~EventPipeFile(); + + // Write an event to the file. + void WriteEvent(EventPipeEventInstance &instance); + + // Serialize this object. + // Not supported - this is the entry object for the trace, + // which means that the contents hasn't yet been created. + void FastSerialize(FastSerializer *pSerializer) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(!"This function should never be called!"); + } + + // Get the type name of this object. + const char* GetTypeName() + { + LIMITED_METHOD_CONTRACT; + return "Microsoft.DotNet.Runtime.EventPipeFile"; + } + + private: + + // Get the metadata address in the file for an event. + // The return value can be written into the file as a back-pointer to the event metadata. + StreamLabel GetMetadataLabel(EventPipeEvent &event); + + // Save the metadata address in the file for an event. + void SaveMetadataLabel(EventPipeEvent &event, StreamLabel label); + + // The object responsible for serialization. + FastSerializer *m_pSerializer; + + // 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 frequency of the timestamps used for this file. + LARGE_INTEGER m_timeStampFrequency; + + // The forward reference index that marks the beginning of the event stream. + unsigned int m_beginEventsForwardReferenceIndex; + + // 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<EventPipeEvent*, StreamLabel> *m_pMetadataLabels; +}; + +#endif // FEATURE_PERFTRACING + +#endif // __EVENTPIPE_FILE_H__ diff --git a/src/vm/eventpipejsonfile.cpp b/src/vm/eventpipejsonfile.cpp index 6353c917e7..ea2dd29fe0 100644 --- a/src/vm/eventpipejsonfile.cpp +++ b/src/vm/eventpipejsonfile.cpp @@ -5,6 +5,8 @@ #include "common.h" #include "eventpipejsonfile.h" +#ifdef FEATURE_PERFTRACING + EventPipeJsonFile::EventPipeJsonFile(SString &outFilePath) { CONTRACTL @@ -15,6 +17,7 @@ EventPipeJsonFile::EventPipeJsonFile(SString &outFilePath) } CONTRACTL_END; + m_writeErrorEncountered = false; m_pFileStream = new CFileStream(); if(FAILED(m_pFileStream->OpenForWrite(outFilePath))) { @@ -52,7 +55,14 @@ EventPipeJsonFile::~EventPipeJsonFile() } } -void EventPipeJsonFile::WriteEvent(CommonEventFields &commonFields, SString &message, StackContents &stackContents) +void EventPipeJsonFile::WriteEvent(EventPipeEventInstance &instance) +{ + STANDARD_VM_CONTRACT; + + instance.SerializeToJsonFile(this); +} + +void EventPipeJsonFile::WriteEvent(LARGE_INTEGER timeStamp, DWORD threadID, SString &message, StackContents &stackContents) { STANDARD_VM_CONTRACT; @@ -67,16 +77,16 @@ void EventPipeJsonFile::WriteEvent(CommonEventFields &commonFields, SString &mes // Convert the timestamp from a QPC value to a trace-relative timestamp. double millisecondsSinceTraceStart = 0.0; - if(commonFields.TimeStamp.QuadPart != m_fileOpenTimeStamp.QuadPart) + if(timeStamp.QuadPart != m_fileOpenTimeStamp.QuadPart) { LARGE_INTEGER elapsedNanoseconds; - elapsedNanoseconds.QuadPart = commonFields.TimeStamp.QuadPart - m_fileOpenTimeStamp.QuadPart; + elapsedNanoseconds.QuadPart = timeStamp.QuadPart - m_fileOpenTimeStamp.QuadPart; millisecondsSinceTraceStart = elapsedNanoseconds.QuadPart / 1000000.0; } StackScratchBuffer scratch; SString threadFrame; - threadFrame.Printf("Thread (%d)", commonFields.ThreadID); + threadFrame.Printf("Thread (%d)", threadID); SString event; event.Printf("{\"Time\" : \"%f\", \"Metric\" : \"1\",\n\"Stack\": [\n\"%s\",\n%s\"%s\"]},", millisecondsSinceTraceStart, message.GetANSI(scratch), strCallStack.GetANSI(scratch), threadFrame.GetANSI(scratch)); Write(event); @@ -129,3 +139,5 @@ void EventPipeJsonFile::FormatCallStack(StackContents &stackContents, SString &r resultStr.Append(frameStr); } } + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipejsonfile.h b/src/vm/eventpipejsonfile.h index b6e42def68..2b836d2c67 100644 --- a/src/vm/eventpipejsonfile.h +++ b/src/vm/eventpipejsonfile.h @@ -6,8 +6,11 @@ #ifndef __EVENTPIPE_JSONFILE_H__ #define __EVENTPIPE_JSONFILE_H__ +#ifdef FEATURE_PERFTRACING + #include "common.h" #include "eventpipe.h" +#include "eventpipeeventinstance.h" #include "fstream.h" class EventPipeJsonFile @@ -16,8 +19,11 @@ class EventPipeJsonFile EventPipeJsonFile(SString &outFilePath); ~EventPipeJsonFile(); + // Write an event instance. + void WriteEvent(EventPipeEventInstance &instance); + // Write an event with the specified message and stack. - void WriteEvent(CommonEventFields &commonFields, SString &message, StackContents &stackContents); + void WriteEvent(LARGE_INTEGER timeStamp, DWORD threadID, SString &message, StackContents &stackContents); private: @@ -37,4 +43,6 @@ class EventPipeJsonFile LARGE_INTEGER m_fileOpenTimeStamp; }; +#endif // FEATURE_PERFTRACING + #endif // __EVENTPIPE_JSONFILE_H__ diff --git a/src/vm/eventpipeprovider.cpp b/src/vm/eventpipeprovider.cpp new file mode 100644 index 0000000000..da185334a9 --- /dev/null +++ b/src/vm/eventpipeprovider.cpp @@ -0,0 +1,253 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "common.h" +#include "eventpipe.h" +#include "eventpipeconfiguration.h" +#include "eventpipeevent.h" +#include "eventpipeprovider.h" + +#ifdef FEATURE_PERFTRACING + +EventPipeProvider::EventPipeProvider(const GUID &providerID) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + m_providerID = providerID; + m_enabled = false; + m_keywords = 0; + m_providerLevel = EventPipeEventLevel::Critical; + m_pEventList = new SList<SListElem<EventPipeEvent*>>(); + m_pCallbackFunction = NULL; + m_pCallbackData = NULL; + + // Register the provider. + EventPipeConfiguration* pConfig = EventPipe::GetConfiguration(); + _ASSERTE(pConfig != NULL); + pConfig->RegisterProvider(*this); +} + +EventPipeProvider::~EventPipeProvider() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Unregister the provider. + // This call is re-entrant. + EventPipeConfiguration* pConfig = EventPipe::GetConfiguration(); + _ASSERTE(pConfig != NULL); + pConfig->UnregisterProvider(*this); + + // Free all of the events. + if(m_pEventList != NULL) + { + // Take the lock before manipulating the list. + CrstHolder _crst(EventPipe::GetLock()); + + SListElem<EventPipeEvent*> *pElem = m_pEventList->GetHead(); + while(pElem != NULL) + { + EventPipeEvent *pEvent = pElem->GetValue(); + delete pEvent; + + pElem = m_pEventList->GetNext(pElem); + } + + delete m_pEventList; + m_pEventList = NULL; + } +} + +const GUID& EventPipeProvider::GetProviderID() const +{ + LIMITED_METHOD_CONTRACT; + + return m_providerID; +} + +bool EventPipeProvider::Enabled() const +{ + LIMITED_METHOD_CONTRACT; + + return m_enabled; +} + +bool EventPipeProvider::EventEnabled(INT64 keywords) const +{ + LIMITED_METHOD_CONTRACT; + + // The event is enabled if: + // - The provider is enabled. + // - The event keywords are unspecified in the manifest (== 0) or when masked with the enabled config are != 0. + return (Enabled() && ((keywords == 0) || ((m_keywords & keywords) != 0))); +} + +bool EventPipeProvider::EventEnabled(INT64 keywords, EventPipeEventLevel eventLevel) const +{ + LIMITED_METHOD_CONTRACT; + + // The event is enabled if: + // - The provider is enabled. + // - The event keywords are unspecified in the manifest (== 0) or when masked with the enabled config are != 0. + // - The event level is LogAlways or the provider's verbosity level is set to greater than the event's verbosity level in the manifest. + return (EventEnabled(keywords) && + ((eventLevel == EventPipeEventLevel::LogAlways) || (m_providerLevel >= eventLevel))); +} + +void EventPipeProvider::SetConfiguration(bool providerEnabled, INT64 keywords, EventPipeEventLevel providerLevel) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); + } + CONTRACTL_END; + + m_enabled = providerEnabled; + m_keywords = keywords; + m_providerLevel = providerLevel; + + RefreshAllEvents(); + InvokeCallback(); +} + +EventPipeEvent* EventPipeProvider::AddEvent(INT64 keywords, unsigned int eventID, unsigned int eventVersion, EventPipeEventLevel level, bool needStack) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Create the event. + EventPipeEvent *pEvent = new EventPipeEvent( + *this, + keywords, + eventID, + eventVersion, + level, + needStack); + + // Add it to the list of events. + AddEvent(*pEvent); + return pEvent; +} + +void EventPipeProvider::AddEvent(EventPipeEvent &event) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Take the config lock before inserting a new event. + CrstHolder _crst(EventPipe::GetLock()); + + m_pEventList->InsertTail(new SListElem<EventPipeEvent*>(&event)); +} + +void EventPipeProvider::RegisterCallback(EventPipeCallback pCallbackFunction, void *pData) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Take the config lock before setting the callback. + CrstHolder _crst(EventPipe::GetLock()); + + if(m_pCallbackFunction == NULL) + { + m_pCallbackFunction = pCallbackFunction; + m_pCallbackData = pData; + } +} + +void EventPipeProvider::UnregisterCallback(EventPipeCallback pCallbackFunction) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Take the config lock before setting the callback. + CrstHolder _crst(EventPipe::GetLock()); + + if(m_pCallbackFunction == pCallbackFunction) + { + m_pCallbackFunction = NULL; + m_pCallbackData = NULL; + } +} + +void EventPipeProvider::InvokeCallback() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); + } + CONTRACTL_END; + + if(m_pCallbackFunction != NULL) + { + (*m_pCallbackFunction)( + &m_providerID, + m_enabled, + (UCHAR) m_providerLevel, + m_keywords, + 0 /* matchAllKeywords */, + NULL /* FilterData */, + m_pCallbackData /* CallbackContext */); + } +} + +void EventPipeProvider::RefreshAllEvents() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); + } + CONTRACTL_END; + + SListElem<EventPipeEvent*> *pElem = m_pEventList->GetHead(); + while(pElem != NULL) + { + EventPipeEvent *pEvent = pElem->GetValue(); + pEvent->RefreshState(); + + pElem = m_pEventList->GetNext(pElem); + } +} + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/eventpipeprovider.h b/src/vm/eventpipeprovider.h new file mode 100644 index 0000000000..610d76d8bf --- /dev/null +++ b/src/vm/eventpipeprovider.h @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __EVENTPIPE_PROVIDER_H__ +#define __EVENTPIPE_PROVIDER_H__ + +#ifdef FEATURE_PERFTRACING + +#include "slist.h" + +class EventPipeEvent; + +// Define the event pipe callback to match the ETW callback signature. +typedef void (*EventPipeCallback)( + LPCGUID SourceID, + ULONG IsEnabled, + UCHAR Level, + ULONGLONG MatchAnyKeywords, + ULONGLONG MatchAllKeywords, + void *FilterData, + void *CallbackContext); + +enum class EventPipeEventLevel +{ + LogAlways, + Critical, + Error, + Warning, + Informational, + Verbose +}; + +class EventPipeProvider +{ + // Declare friends. + friend class EventPipeConfiguration; + +private: + // The GUID of the provider. + GUID m_providerID; + + // True if the provider is enabled. + bool m_enabled; + + // Bit vector containing the currently enabled keywords. + INT64 m_keywords; + + // The current verbosity of the provider. + EventPipeEventLevel m_providerLevel; + + // List of every event currently associated with the provider. + // New events can be added on-the-fly. + SList<SListElem<EventPipeEvent*>> *m_pEventList; + + // The optional provider callback. + EventPipeCallback m_pCallbackFunction; + + // The optional provider callback data pointer. + void *m_pCallbackData; + +public: + + EventPipeProvider(const GUID &providerID); + ~EventPipeProvider(); + + // Get the provider ID. + const GUID& GetProviderID() const; + + // Determine if the provider is enabled. + bool Enabled() const; + + // Determine if the specified keywords are enabled. + bool EventEnabled(INT64 keywords) const; + + // Determine if the specified keywords and level match the configuration. + bool EventEnabled(INT64 keywords, EventPipeEventLevel eventLevel) const; + + // Create a new event. + EventPipeEvent* AddEvent(INT64 keywords, unsigned int eventID, unsigned int eventVersion, EventPipeEventLevel level, bool needStack); + + // Register a callback with the provider to be called on state change. + void RegisterCallback(EventPipeCallback pCallbackFunction, void *pData); + + // Unregister a callback. + void UnregisterCallback(EventPipeCallback pCallbackFunction); + +private: + + // Add an event to the provider. + void AddEvent(EventPipeEvent &event); + + // Set the provider configuration (enable and disable sets of events). + // This is called by EventPipeConfiguration. + void SetConfiguration(bool providerEnabled, INT64 keywords, EventPipeEventLevel providerLevel); + + // Refresh the runtime state of all events. + void RefreshAllEvents(); + + // Invoke the provider callback. + void InvokeCallback(); +}; + +#endif // FEATURE_PERFTRACING + +#endif // __EVENTPIPE_PROVIDER_H__ diff --git a/src/vm/fastserializableobject.h b/src/vm/fastserializableobject.h new file mode 100644 index 0000000000..cbfcfc9f0e --- /dev/null +++ b/src/vm/fastserializableobject.h @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __FASTSERIALIZABLE_OBJECT_H__ +#define __FASTSERIALIZABLE_OBJECT_H__ + +#ifdef FEATURE_PERFTRACING + +class FastSerializer; + +class FastSerializableObject +{ + +public: + + // Virtual destructor to ensure that derived class destructors get called. + virtual ~FastSerializableObject() + { + LIMITED_METHOD_CONTRACT; + } + + // Serialize the object using the specified serializer. + virtual void FastSerialize(FastSerializer *pSerializer) = 0; + + // Get the type name for the current object. + virtual const char* GetTypeName() = 0; +}; + +#endif // FEATURE_PERFTRACING + +#endif // _FASTSERIALIZABLE_OBJECT_H__ diff --git a/src/vm/fastserializer.cpp b/src/vm/fastserializer.cpp new file mode 100644 index 0000000000..7f9b4e20a6 --- /dev/null +++ b/src/vm/fastserializer.cpp @@ -0,0 +1,337 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "common.h" +#include "fastserializer.h" + +#ifdef FEATURE_PERFTRACING + +FastSerializer::FastSerializer(SString &outputFilePath, FastSerializableObject &object) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + m_writeErrorEncountered = false; + m_pEntryObject = &object; + m_currentPos = 0; + m_nextForwardReference = 0; + m_pFileStream = new CFileStream(); + if(FAILED(m_pFileStream->OpenForWrite(outputFilePath))) + { + delete(m_pFileStream); + m_pFileStream = NULL; + return; + } + + // Write the file header. + WriteFileHeader(); + + // Write the entry object. + WriteEntryObject(); +} + +FastSerializer::~FastSerializer() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // Write the end of the entry object. + WriteTag(FastSerializerTags::EndObject); + + // Write forward reference table. + StreamLabel forwardReferenceLabel = WriteForwardReferenceTable(); + + // Write trailer. + WriteTrailer(forwardReferenceLabel); + + if(m_pFileStream != NULL) + { + delete(m_pFileStream); + m_pFileStream = NULL; + } +} + +StreamLabel FastSerializer::GetStreamLabel() const +{ + LIMITED_METHOD_CONTRACT; + + return (StreamLabel)m_currentPos; +} + +void FastSerializer::WriteObject(FastSerializableObject *pObject) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(pObject != NULL); + } + CONTRACTL_END; + + // Write a BeginObject tag. + WriteTag(FastSerializerTags::BeginObject); + + // Write object begin tag. + WriteSerializationType(pObject); + + // Ask the object to serialize itself using the current serializer. + pObject->FastSerialize(this); + + // Write object end tag. + WriteTag(FastSerializerTags::EndObject); +} + +void FastSerializer::WriteBuffer(BYTE *pBuffer, unsigned int length) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pBuffer != NULL); + PRECONDITION(length > 0); + } + CONTRACTL_END; + + if(m_writeErrorEncountered || m_pFileStream == NULL) + { + return; + } + + EX_TRY + { + ULONG outCount; + m_pFileStream->Write(pBuffer, length, &outCount); + +#ifdef _DEBUG + size_t prevPos = m_currentPos; +#endif + m_currentPos += outCount; +#ifdef _DEBUG + _ASSERTE(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 + { + m_writeErrorEncountered = true; + } + EX_END_CATCH(SwallowAllExceptions); +} + +void FastSerializer::WriteEntryObject() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + // Write begin entry object tag. + WriteTag(FastSerializerTags::BeginObject); + + // Write the type information for the entry object. + WriteSerializationType(m_pEntryObject); + + // The object is now initialized. Fields or other objects can now be written. +} + +unsigned int FastSerializer::AllocateForwardReference() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(m_nextForwardReference < MaxForwardReferences); + } + CONTRACTL_END; + + // TODO: Handle failure. + + // Save the index. + int index = m_nextForwardReference; + + // Allocate the forward reference and zero-fill it so that the reader + // will know if it was not properly defined. + m_forwardReferences[m_nextForwardReference++] = 0; + + return index; +} + +void FastSerializer::DefineForwardReference(unsigned int index, StreamLabel value) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(index < MaxForwardReferences-1); + } + CONTRACTL_END; + + m_forwardReferences[index] = value; +} + +void FastSerializer::WriteForwardReference(unsigned int index) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(index < MaxForwardReferences-1); + } + CONTRACTL_END; + + WriteBuffer((BYTE*)&index, sizeof(index)); +} + +void FastSerializer::WriteSerializationType(FastSerializableObject *pObject) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pObject != NULL); + } + CONTRACTL_END; + + // Write the BeginObject tag. + WriteTag(FastSerializerTags::BeginObject); + + // Write a NullReferenceTag, which implies that the following fields belong to SerializationType. + WriteTag(FastSerializerTags::NullReference); + + // Write the SerializationType version fields. + int serializationType[2]; + serializationType[0] = 1; // Object Version. + serializationType[1] = 0; // Minimum Reader Version. + WriteBuffer((BYTE*) &serializationType, sizeof(serializationType)); + + // Write the SerializationType TypeName field. + const char *strTypeName = pObject->GetTypeName(); + unsigned int length = (unsigned int)strlen(strTypeName); + WriteString(strTypeName, length); + + // Write the EndObject tag. + WriteTag(FastSerializerTags::EndObject); +} + + +void FastSerializer::WriteTag(FastSerializerTags tag, BYTE *payload, unsigned int payloadLength) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + WriteBuffer((BYTE *)&tag, sizeof(tag)); + if(payload != NULL) + { + _ASSERTE(payloadLength > 0); + WriteBuffer(payload, payloadLength); + } +} + + +void FastSerializer::WriteFileHeader() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + const char *strSignature = "!FastSerialization.1"; + unsigned int length = (unsigned int)strlen(strSignature); + WriteString(strSignature, length); +} + +void FastSerializer::WriteString(const char *strContents, unsigned int length) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + // Write the string length . + WriteBuffer((BYTE*) &length, sizeof(length)); + + // Write the string contents. + WriteBuffer((BYTE*) strContents, length); +} + +StreamLabel FastSerializer::WriteForwardReferenceTable() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + // Save the position of the start of the forward references table. + StreamLabel current = GetStreamLabel(); + + // Write the count of allocated references. + WriteBuffer((BYTE*) &m_nextForwardReference, sizeof(m_nextForwardReference)); + + // Write each of the allocated references. + WriteBuffer((BYTE*) m_forwardReferences, sizeof(StreamLabel) * m_nextForwardReference); + + return current; +} + +void FastSerializer::WriteTrailer(StreamLabel forwardReferencesTableStart) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + // Get the current location to mark the beginning of the trailer. + StreamLabel current = GetStreamLabel(); + + // Write the trailer, which contains the start of the forward references table. + WriteBuffer((BYTE*) &forwardReferencesTableStart, sizeof(forwardReferencesTableStart)); + + // Write the location of the trailer. This is the final piece of data written to the file, + // so that it can be easily found by a reader that can seek to the end of the file. + WriteBuffer((BYTE*) ¤t, sizeof(current)); +} + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/fastserializer.h b/src/vm/fastserializer.h new file mode 100644 index 0000000000..5fd2cfd4a5 --- /dev/null +++ b/src/vm/fastserializer.h @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __FASTSERIALIZER_H__ +#define __FASTSERIALIZER_H__ + +#ifdef FEATURE_PERFTRACING + +#include "fastserializableobject.h" +#include "fstream.h" + +class FastSerializer; + +typedef unsigned int StreamLabel; + +enum class FastSerializerTags : BYTE +{ + Error, // To improve debugabilty, 0 is an illegal tag. + NullReference, // Tag for a null object forwardReference. + ObjectReference, // Followed by StreamLabel + ForwardReference, // Followed by an index (32-bit integer) into the Forward forwardReference array and a Type object + BeginObject, // Followed by Type object, object data, tagged EndObject + BeginPrivateObject, // Like beginObject, but not placed in interning table on deserialiation + EndObject, // Placed after an object to mark its end. + ForwardDefinition, // Followed by a forward forwardReference index and an object definition (BeginObject) + Byte, + Int16, + Int32, + Int64, + SkipRegion, + String, + Limit, // Just past the last valid tag, used for asserts. +}; + +class FastSerializer +{ +public: + + FastSerializer(SString &outputFilePath, FastSerializableObject &object); + ~FastSerializer(); + + StreamLabel GetStreamLabel() const; + + void WriteObject(FastSerializableObject *pObject); + void WriteBuffer(BYTE *pBuffer, unsigned int length); + void WriteTag(FastSerializerTags tag, BYTE *payload = NULL, unsigned int payloadLength = 0); + void WriteString(const char *strContents, unsigned int length); + + unsigned int AllocateForwardReference(); + void DefineForwardReference(unsigned int index, StreamLabel value); + void WriteForwardReference(unsigned int index); + +private: + + void WriteEntryObject(); + void WriteSerializationType(FastSerializableObject *pObject); + void WriteFileHeader(); + StreamLabel WriteForwardReferenceTable(); + void WriteTrailer(StreamLabel forwardReferencesTableStart); + + CFileStream *m_pFileStream; + bool m_writeErrorEncountered; + FastSerializableObject *m_pEntryObject; + size_t m_currentPos; + + static const unsigned int MaxForwardReferences = 100; + StreamLabel m_forwardReferences[MaxForwardReferences]; + unsigned int m_nextForwardReference; +}; + +#endif // FEATURE_PERFTRACING + +#endif // __FASTSERIALIZER_H__ diff --git a/src/vm/sampleprofiler.cpp b/src/vm/sampleprofiler.cpp index 004b3c68b0..6a6a23a03b 100644 --- a/src/vm/sampleprofiler.cpp +++ b/src/vm/sampleprofiler.cpp @@ -3,18 +3,23 @@ // See the LICENSE file in the project root for more information. #include "common.h" +#include "eventpipeeventinstance.h" #include "sampleprofiler.h" #include "hosting.h" #include "threadsuspend.h" +#ifdef FEATURE_PERFTRACING + Volatile<BOOL> SampleProfiler::s_profilingEnabled = false; Thread* SampleProfiler::s_pSamplingThread = NULL; +const GUID SampleProfiler::s_providerID = {0x3c530d44,0x97ae,0x513a,{0x1e,0x6d,0x78,0x3e,0x8f,0x8e,0x03,0xa9}}; // {3c530d44-97ae-513a-1e6d-783e8f8e03a9} +EventPipeProvider* SampleProfiler::s_pEventPipeProvider = NULL; +EventPipeEvent* SampleProfiler::s_pThreadTimeEvent = NULL; CLREventStatic SampleProfiler::s_threadShutdownEvent; #ifdef FEATURE_PAL long SampleProfiler::s_samplingRateInNs = 1000000; // 1ms #endif -// Synchronization of multiple callers occurs in EventPipe::Enable. void SampleProfiler::Enable() { CONTRACTL @@ -23,9 +28,22 @@ void SampleProfiler::Enable() GC_TRIGGERS; MODE_ANY; PRECONDITION(s_pSamplingThread == NULL); + // Synchronization of multiple callers occurs in EventPipe::Enable. + PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); } CONTRACTL_END; + if(s_pEventPipeProvider == NULL) + { + s_pEventPipeProvider = new EventPipeProvider(s_providerID); + s_pThreadTimeEvent = s_pEventPipeProvider->AddEvent( + 0, /* keywords */ + 0, /* eventID */ + 0, /* eventVersion */ + EventPipeEventLevel::Informational, + false /* NeedStack */); + } + s_profilingEnabled = true; s_pSamplingThread = SetupUnstartedThread(); if(s_pSamplingThread->CreateNewThread(0, ThreadProc, NULL)) @@ -40,7 +58,6 @@ void SampleProfiler::Enable() } } -// Synchronization of multiple callers occurs in EventPipe::Disable. void SampleProfiler::Disable() { CONTRACTL @@ -48,6 +65,8 @@ void SampleProfiler::Disable() THROWS; GC_TRIGGERS; MODE_ANY; + // Synchronization of multiple callers occurs in EventPipe::Disable. + PRECONDITION(EventPipe::GetLock()->OwnedByCurrentThread()); } CONTRACTL_END; @@ -140,16 +159,20 @@ void SampleProfiler::WalkManagedThreads() CONTRACTL_END; Thread *pThread = NULL; - StackContents stackContents; // Iterate over all managed threads. // Assumes that the ThreadStoreLock is held because we've suspended all threads. while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL) { + SampleProfilerEventInstance instance(pThread); + StackContents &stackContents = *(instance.GetStack()); + // Walk the stack and write it out as an event. if(EventPipe::WalkManagedStackForThread(pThread, stackContents) && !stackContents.IsEmpty()) { - EventPipe::WriteSampleProfileEvent(pThread, stackContents); + EventPipe::WriteSampleProfileEvent(instance); } } } + +#endif // FEATURE_PERFTRACING diff --git a/src/vm/sampleprofiler.h b/src/vm/sampleprofiler.h index a5c5fc3ffd..5ad388d8ff 100644 --- a/src/vm/sampleprofiler.h +++ b/src/vm/sampleprofiler.h @@ -5,11 +5,17 @@ #ifndef __SAMPLEPROFILER_H__ #define __SAMPLEPROFILER_H__ +#ifdef FEATURE_PERFTRACING + #include "common.h" #include "eventpipe.h" class SampleProfiler { + + // Declare friends. + friend class SampleProfilerEventInstance; + public: // Enable profiling. @@ -32,6 +38,11 @@ class SampleProfiler // The sampling thread. static Thread *s_pSamplingThread; + // The provider and event emitted by the profiler. + static const GUID s_providerID; + static EventPipeProvider *s_pEventPipeProvider; + static EventPipeEvent *s_pThreadTimeEvent; + // Thread shutdown event for synchronization between Disable() and the sampling thread. static CLREventStatic s_threadShutdownEvent; @@ -41,4 +52,6 @@ class SampleProfiler #endif }; +#endif // FEATURE_PERFTRACING + #endif // __SAMPLEPROFILER_H__ |