[Local GC] FEATURE_EVENT_TRACE 3/n: Defining and Firing Dynamic Events (#16000)
authorSean Gillespie <segilles@microsoft.com>
Fri, 26 Jan 2018 02:17:57 +0000 (18:17 -0800)
committerGitHub <noreply@github.com>
Fri, 26 Jan 2018 02:17:57 +0000 (18:17 -0800)
src/gc/gcevent_serializers.h [new file with mode: 0644]
src/gc/gcevents.h
src/gc/gceventstatus.h
src/gc/gcinterface.ee.h
src/gc/gcsvr.cpp
src/gc/gcwks.cpp
src/vm/ClrEtwAll.man
src/vm/gctoclreventsink.cpp
src/vm/gctoclreventsink.h

diff --git a/src/gc/gcevent_serializers.h b/src/gc/gcevent_serializers.h
new file mode 100644 (file)
index 0000000..643ab82
--- /dev/null
@@ -0,0 +1,141 @@
+// 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 __GCEVENT_SERIALIZERS_H__
+#define __GCEVENT_SERIALIZERS_H__
+
+/*
+ * gcevent_serializers.h - Serialization traits and plumbing for
+ * serializing dynamic events.
+ *
+ * Dynamic events are events that can be fired by the GC without prior
+ * knowledge of the EE. In order to accomplish this, the GC sends raw
+ * bytes to the EE using the `IGCToCLR::FireDynamicEvent` callback, which
+ * the EE will then fire as its own event.
+ *
+ * In order to keep the friction of adding new dynamic events low, this
+ * file defines a simple ETW-style binary serialization format that
+ * is efficient and easy to both serialize and deserialize.
+ *
+ * ## Serializing Types
+ *
+ * This file makes use of `EventSerializationTraits` to serialize
+ * types. A type can opt-in to serialization using the mechanisms
+ * in this file by specializing the `EventSerializationTraits` template,
+ * providing implementations of `Serialize` and `SerializedSize`.
+ * 
+ * If you attempt to serialize a type that does not implement this trait,
+ * you will receive an error message like this:
+ *
+ * bool gc_event::EventSerializationTraits<Head>::Serialize(const T&,uint8_t **)': attempting to reference a deleted function
+ * with
+ *  [
+ *      Head=<your type you tried to serialize>,
+ *      T=<your type you tried to serialize>
+ *  ] 
+ * 
+ * If you get this message, you will need to specialize `EventSerializationTraits`
+ * for the type you want to serialize.
+ */ 
+
+#ifdef _MSC_VER
+#define ByteSwap32 _byteswap_ulong
+#define ByteSwap64 _byteswap_uint64
+#else
+#define ByteSwap32 __bulitin_bswap32
+#define ByteSwap64 __builtin_bswap64
+#endif // MSC_VER
+
+namespace gc_event
+{
+
+/*
+ * `EventSerializatonTraits` is a trait implemented by types that
+ * can be serialized to the payload of a dynamic event.
+ */
+template<class T>
+struct EventSerializationTraits
+{
+    /*
+     * Serializes the value `value` to the buffer `buffer`, incrementing
+     * the buffer double-pointer to point to the next byte to be written.
+     * 
+     * It is the responsibility of the caller to ensure that the buffer is
+     * large enough to accomodate the serialized form of T.
+     */
+    static void Serialize(const T& value, uint8_t** buffer) = delete;
+
+    /*
+     * Returns the size of the value `value` if it were to be serialized.
+     */
+    static size_t SerializedSize(const T& value) = delete;
+};
+
+/*
+ * EventSerializationTraits implementation for uint32_t. Other integral types
+ * can follow this pattern.
+ *
+ * The convention here is that integral types are always serialized as
+ * little-endian.
+ */
+template<>
+struct EventSerializationTraits<uint32_t>
+{
+    static void Serialize(const uint32_t& value, uint8_t** buffer)
+    {
+#if defined(BIGENDIAN)
+        **((uint32_t**)buffer) = ByteSwap32(value);
+#else
+        **((uint32_t**)buffer) = value;
+#endif // BIGENDIAN
+        *buffer += sizeof(uint32_t);
+    }
+
+    static size_t SerializedSize(const uint32_t& value)
+    {
+        return sizeof(uint32_t);
+    }
+};
+
+/*
+ * Helper routines for serializing lists of arguments.
+ */
+
+/*
+ * Given a list of arguments , returns the total size of
+ * the buffer required to fully serialize the list of arguments.
+ */
+template<class Head, class... Tail>
+size_t SerializedSize(Head head, Tail... tail)
+{
+    return EventSerializationTraits<Head>::SerializedSize(head) + SerializedSize(tail...);
+}
+
+template<class Head>
+size_t SerializedSize(Head head)
+{
+    return EventSerializationTraits<Head>::SerializedSize(head);
+}
+
+/*
+ * Given a list of arguments and a list of actual parameters, serialize
+ * the arguments into the buffer that's given to us.
+ */
+template<class Head, class... Tail>
+void Serialize(uint8_t** buf, Head head, Tail... tail)
+{
+    EventSerializationTraits<Head>::Serialize(head, buf);
+    Serialize(buf, tail...);
+}
+
+template<class Head>
+void Serialize(uint8_t** buf, Head head)
+{
+    EventSerializationTraits<Head>::Serialize(head, buf);
+}
+
+} // namespace gc_event
+
+#endif // __GCEVENT_SERIALIZERS_H__
+
index cfc1571..94087d7 100644 (file)
@@ -6,7 +6,7 @@
 #endif // KNOWN_EVENT
 
 #ifndef DYNAMIC_EVENT
- #define DYNAMIC_EVENT(name, provider, level, keyword, ...)
+ #define DYNAMIC_EVENT(name, level, keyword, ...)
 #endif // DYNAMIC_EVENT
 
 #undef KNOWN_EVENT
index 1d992fc..95ed337 100644 (file)
@@ -27,6 +27,7 @@
 #include "common.h"
 #include "gcenv.h"
 #include "gc.h"
+#include "gcevent_serializers.h"
 
 // Uncomment this define to print out event state changes to standard error.
 // #define TRACE_GC_EVENT_STATE 1
@@ -187,23 +188,70 @@ private:
     GCEventStatus() = delete;
 };
 
-class GCDynamicEvent
+/*
+ * FireDynamicEvent is a variadic function that fires a dynamic event with the
+ * given name and event payload. This function serializes the arguments into
+ * a binary payload that is then passed to IGCToCLREventSink::FireDynamicEvent.
+ */
+template<typename... EventArgument>
+void FireDynamicEvent(const char* name, EventArgument... arguments)
 {
-    /* TODO(segilles) - Not Yet Implemented */
+    size_t size = gc_event::SerializedSize(arguments...);
+    if (size > UINT32_MAX)
+    {
+        // ETW can't handle anything this big.
+        // we shouldn't be firing events that big anyway.
+        return;
+    }
+
+    uint8_t* buf = new (nothrow) uint8_t[size];
+    if (!buf)
+    {
+        // best effort - if we're OOM, don't bother with the event.
+        return;
+    }
+
+    memset(buf, 0, size);
+    uint8_t* cursor = buf;
+    gc_event::Serialize(&cursor, arguments...);
+    IGCToCLREventSink* sink = GCToEEInterface::EventSink();
+    assert(sink != nullptr);
+    sink->FireDynamicEvent(name, buf, static_cast<uint32_t>(size));
+    delete[] buf;
 };
 
+/*
+ * In order to provide a consistent interface between known and dynamic events,
+ * two wrapper functions are generated for each known and dynamic event:
+ *   GCEventEnabled##name() - Returns true if the event is enabled, false otherwise.
+ *   GCEventFire##name(...) - Fires the event, with the event payload consisting of
+ *                            the arguments to the function.
+ *
+ * Because the schema of dynamic events comes from the DYNAMIC_EVENT xmacro, we use
+ * the arguments vector as the argument list to `FireDynamicEvent`, which will traverse
+ * the list of arguments and call `IGCToCLREventSink::FireDynamicEvent` with a serialized
+ * payload. Known events will delegate to IGCToCLREventSink::Fire##name.
+ */
 #if FEATURE_EVENT_TRACE
-#define KNOWN_EVENT(name, _provider, _level, _keyword)   \
-  inline bool GCEventEnabled##name() { return GCEventStatus::IsEnabled(_provider, _level, _keyword); }
+#define KNOWN_EVENT(name, provider, level, keyword)               \
+  inline bool GCEventEnabled##name() { return GCEventStatus::IsEnabled(provider, keyword, level); } \
+  template<typename... EventActualArgument>                       \
+  inline void GCEventFire##name(EventActualArgument... arguments) \
+  {                                                               \
+      IGCToCLREventSink* sink = GCToEEInterface::EventSink();     \
+      assert(sink != nullptr);                                    \
+      sink->Fire##name(arguments...);                             \
+  }
+
+#define DYNAMIC_EVENT(name, level, keyword, ...)                                                                   \
+  inline bool GCEventEnabled##name() { return GCEventStatus::IsEnabled(GCEventProvider_Default, keyword, level); } \
+  template<typename... EventActualArgument>                                                                        \
+  inline void GCEventFire##name(EventActualArgument... arguments) { FireDynamicEvent<__VA_ARGS__>(#name, arguments...); }
+
 #include "gcevents.h"
 
 #define EVENT_ENABLED(name) GCEventEnabled##name()
-#define FIRE_EVENT(name, ...) \
-  do {                                                      \
-    IGCToCLREventSink* sink = GCToEEInterface::EventSink(); \
-    assert(sink != nullptr);                                \
-    sink->Fire##name(__VA_ARGS__);                          \
-  } while(0)
+#define FIRE_EVENT(name, ...) GCEventFire##name(__VA_ARGS__)
 #else
 #define EVENT_ENABLED(name) false
 #define FIRE_EVENT(name, ...) 0
index f765362..657e16e 100644 (file)
 // to the EE. ([LOCALGC TODO dynamic event implementation])
 class IGCToCLREventSink
 {
-    /* [LOCALGC TODO] This will be filled with events as they get ported */
+public:
+    // Fires a dynamic event with the given event name and payload. Dynamic
+    // events are not known to the EE and are fired as an unschematized event
+    // to the underlying eventing implementation.
+    virtual
+    void FireDynamicEvent(
+        const char* eventName,
+        void* payload,
+        uint32_t payloadSize) = 0;
 };
 
 // This interface provides the interface that the GC will use to speak to the rest
index db67aa3..e85555c 100644 (file)
@@ -17,6 +17,7 @@
 #include "handletable.h"
 #include "handletable.inl"
 #include "gcenv.inl"
+#include "gceventstatus.h"
 
 #define SERVER_GC 1
 
index 3357556..ad66dd6 100644 (file)
@@ -15,6 +15,7 @@
 #include "handletable.h"
 #include "handletable.inl"
 #include "gcenv.inl"
+#include "gceventstatus.h"
 
 #ifdef SERVER_GC
 #undef SERVER_GC
index de9d196..3a35684 100644 (file)
                             <opcode name="GCBulkRootCCW" message="$(string.RuntimePublisher.GCBulkRootCCWOpcodeMessage)" symbol="CLR_GC_BULKROOTCCW_OPCODE" value="38"> </opcode>
                             <opcode name="GCBulkRCW" message="$(string.RuntimePublisher.GCBulkRCWOpcodeMessage)" symbol="CLR_GC_BULKRCW_OPCODE" value="39"> </opcode>
                             <opcode name="GCBulkRootStaticVar" message="$(string.RuntimePublisher.GCBulkRootStaticVarOpcodeMessage)" symbol="CLR_GC_BULKROOTSTATICVAR_OPCODE" value="40"> </opcode>
+                            <opcode name="GCDynamicEvent" message="$(string.RuntimePublisher.GCDynamicEventOpcodeMessage)" symbol="CLR_GC_DYNAMICEVENT_OPCODE" value="41"> </opcode>
                             <opcode name="IncreaseMemoryPressure" message="$(string.RuntimePublisher.IncreaseMemoryPressureOpcodeMessage)" symbol="CLR_GC_INCREASEMEMORYPRESSURE_OPCODE" value="200"> </opcode>
                             <opcode name="DecreaseMemoryPressure" message="$(string.RuntimePublisher.DecreaseMemoryPressureOpcodeMessage)" symbol="CLR_GC_DECREASEMEMORYPRESSURE_OPCODE" value="201"> </opcode>
                             <opcode name="GCMarkWithType" message="$(string.RuntimePublisher.GCMarkOpcodeMessage)" symbol="CLR_GC_MARK_OPCODE" value="202"> </opcode>
                         <data name="TypeName" inType="win:UnicodeString" />
                         <data name="ClrInstanceID" inType="win:UInt16" />
                     </template>
+
+                    <template tid="GCDynamicEvent">
+                        <data name="Name" inType="win:UnicodeString" />
+                        <data name="DataSize" inType="win:UInt32" />
+                        <data name="Data" inType="win:Binary" length="DataSize" />
+                        <data name="ClrInstanceID" inType="win:UInt16" />
+                    </template>
                     
                     <template tid="IncreaseMemoryPressure">
                         <data name="BytesAllocated" inType="win:UInt64" />
                            task="GarbageCollection"
                            symbol="GCBulkRootStaticVar" message="$(string.RuntimePublisher.GCBulkRootStaticVarEventMessage)"/>
 
+                    <event value="39" version="0" level="win:LogAlways" template="GCDynamicEvent"
+                           keywords= "GCKeyword GCHandleKeyword GCHeapDumpKeyword GCSampledObjectAllocationHighKeyword GCHeapSurvivalAndMovementKeyword GCHeapCollectKeyword GCHeapAndTypeNamesKeyword GCSampledObjectAllocationLowKeyword"
+                           opcode="GCDynamicEvent"
+                           task="GarbageCollection"
+                           symbol="GCDynamicEvent"/>
+
                     <!-- CLR Threading events, value reserved from 40 to 79 -->
                     <event value="40" version="0" level="win:Informational"  template="ClrWorkerThread"
                            keywords="ThreadingKeyword" opcode="win:Start"
                 <string id="RuntimePublisher.GCBulkRootCCWEventMessage" value="ClrInstanceID=%1;%nIndex=%2;%nCount=%3" />
                 <string id="RuntimePublisher.GCBulkRCWEventMessage" value="ClrInstanceID=%1;%nIndex=%2;%nCount=%3" />
                 <string id="RuntimePublisher.GCBulkRootStaticVarEventMessage" value="ClrInstanceID=%1;%nIndex=%2;%nCount=%3" />
+                <string id="RuntimePublisher.GCDynamicEventMessage" value="ClrInstanceID=%1;Data=%2;Size=%3" />
                 <string id="RuntimePublisher.GCBulkRootConditionalWeakTableElementEdgeEventMessage" value="ClrInstanceID=%1;%nIndex=%2;%nCount=%3" />
                 <string id="RuntimePublisher.GCBulkNodeEventMessage" value="ClrInstanceID=%1;%nIndex=%2;%nCount=%3" />
                 <string id="RuntimePublisher.GCBulkEdgeEventMessage" value="ClrInstanceID=%1;%nIndex=%2;%nCount=%3" />
                 <string id="RuntimePublisher.GCBulkRootCCWOpcodeMessage" value="GCBulkRootCCW" />
                 <string id="RuntimePublisher.GCBulkRCWOpcodeMessage" value="GCBulkRCW" />
                 <string id="RuntimePublisher.GCBulkRootStaticVarOpcodeMessage" value="GCBulkRootStaticVar" />
+                <string id="RuntimePublisher.GCDynamicEventOpcodeMessage" value="GCDynamicEvent" />
                 <string id="RuntimePublisher.GCBulkRootConditionalWeakTableElementEdgeOpcodeMessage" value="GCBulkRootConditionalWeakTableElementEdge" />
                 <string id="RuntimePublisher.GCBulkNodeOpcodeMessage" value="GCBulkNode" />
                 <string id="RuntimePublisher.GCBulkEdgeOpcodeMessage" value="GCBulkEdge" />
index d305b5e..001dafe 100644 (file)
@@ -6,3 +6,18 @@
 #include "gctoclreventsink.h"
 
 GCToCLREventSink g_gcToClrEventSink;
+
+void GCToCLREventSink::FireDynamicEvent(const char* eventName, void* payload, uint32_t payloadSize)
+{
+    LIMITED_METHOD_CONTRACT;
+
+    const size_t EventNameMaxSize = 255;
+
+    WCHAR wideEventName[EventNameMaxSize];
+    if (MultiByteToWideChar(CP_ACP, 0, eventName, -1, wideEventName, EventNameMaxSize) == 0)
+    {
+        return;
+    }
+
+    FireEtwGCDynamicEvent(wideEventName, payloadSize, (const BYTE*)payload, GetClrInstanceId());
+}
index 32d12e8..87e6659 100644 (file)
@@ -9,7 +9,8 @@
 
 class GCToCLREventSink : public IGCToCLREventSink
 {
-    /* [LOCALGC TODO] This will be filled with events as they get ported */
+public:
+    void FireDynamicEvent(const char* eventName, void* payload, uint32_t payloadSize);
 };
 
 extern GCToCLREventSink g_gcToClrEventSink;