--- /dev/null
+// 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__
+
#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
#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
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
// 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
#include "handletable.h"
#include "handletable.inl"
#include "gcenv.inl"
+#include "gceventstatus.h"
#define SERVER_GC 1
#include "handletable.h"
#include "handletable.inl"
#include "gcenv.inl"
+#include "gceventstatus.h"
#ifdef SERVER_GC
#undef SERVER_GC
<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" />
#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());
+}
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;