1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5#ifndef __GCEVENTSTATUS_H__
6#define __GCEVENTSTATUS_H__
7
8
9/*
10 * gceventstatus.h - Eventing status for a standalone GC
11 *
12 * In order for a local GC to determine what events are enabled
13 * in an efficient manner, the GC maintains some local state about
14 * keywords and levels that are enabled for each eventing provider.
15 *
16 * The GC fires events from two providers: the "main" provider
17 * and the "private" provider. This file tracks keyword and level
18 * information for each provider separately.
19 *
20 * It is the responsibility of the EE to inform the GC of changes
21 * to eventing state. This is accomplished by invoking the
22 * `IGCHeap::ControlEvents` and `IGCHeap::ControlPrivateEvents` callbacks
23 * on the EE's heap instance, which ultimately will enable and disable keywords
24 * and levels within this file.
25 */
26
27#include "common.h"
28#include "gcenv.h"
29#include "gc.h"
30#include "gcevent_serializers.h"
31
32// Uncomment this define to print out event state changes to standard error.
33// #define TRACE_GC_EVENT_STATE 1
34
35/*
36 * GCEventProvider represents one of the two providers that the GC can
37 * fire events from: the default and private providers.
38 */
39enum GCEventProvider
40{
41 GCEventProvider_Default = 0,
42 GCEventProvider_Private = 1
43};
44
45/*
46 * GCEventStatus maintains all eventing state for the GC. It consists
47 * of a keyword bitmask and level for each provider that the GC can use
48 * to fire events.
49 *
50 * A level and event pair are considered to be "enabled" on a given provider
51 * if the given level is less than or equal to the current enabled level
52 * and if the keyword is present in the enabled keyword bitmask for that
53 * provider.
54 */
55class GCEventStatus
56{
57private:
58 /*
59 * The enabled level for each provider.
60 */
61 static Volatile<GCEventLevel> enabledLevels[2];
62
63 /*
64 * The bitmap of enabled keywords for each provider.
65 */
66 static Volatile<GCEventKeyword> enabledKeywords[2];
67
68public:
69 /*
70 * IsEnabled queries whether or not the given level and keyword are
71 * enabled on the given provider, returning true if they are.
72 */
73 __forceinline static bool IsEnabled(GCEventProvider provider, GCEventKeyword keyword, GCEventLevel level)
74 {
75 assert(level >= GCEventLevel_None && level < GCEventLevel_Max);
76
77 size_t index = static_cast<size_t>(provider);
78 return (enabledLevels[index].LoadWithoutBarrier() >= level)
79 && (enabledKeywords[index].LoadWithoutBarrier() & keyword);
80 }
81
82 /*
83 * Set sets the eventing state (level and keyword bitmap) for a given
84 * provider to the provided values.
85 */
86 static void Set(GCEventProvider provider, GCEventKeyword keywords, GCEventLevel level)
87 {
88 assert((level >= GCEventLevel_None && level < GCEventLevel_Max) || level == GCEventLevel_LogAlways);
89
90 size_t index = static_cast<size_t>(provider);
91
92 enabledLevels[index] = level;
93 enabledKeywords[index] = keywords;
94
95#if TRACE_GC_EVENT_STATE
96 fprintf(stderr, "event state change:\n");
97 DebugDumpState(provider);
98#endif // TRACE_GC_EVENT_STATE
99 }
100
101#if TRACE_GC_EVENT_STATE
102private:
103 static void DebugDumpState(GCEventProvider provider)
104 {
105 size_t index = static_cast<size_t>(provider);
106 GCEventLevel level = enabledLevels[index];
107 GCEventKeyword keyword = enabledKeywords[index];
108 if (provider == GCEventProvider_Default)
109 {
110 fprintf(stderr, "provider: default\n");
111 }
112 else
113 {
114 fprintf(stderr, "provider: private\n");
115 }
116
117 switch (level)
118 {
119 case GCEventLevel_None:
120 fprintf(stderr, " level: None\n");
121 break;
122 case GCEventLevel_Fatal:
123 fprintf(stderr, " level: Fatal\n");
124 break;
125 case GCEventLevel_Error:
126 fprintf(stderr, " level: Error\n");
127 break;
128 case GCEventLevel_Warning:
129 fprintf(stderr, " level: Warning\n");
130 break;
131 case GCEventLevel_Information:
132 fprintf(stderr, " level: Information\n");
133 break;
134 case GCEventLevel_Verbose:
135 fprintf(stderr, " level: Verbose\n");
136 break;
137 case GCEventLevel_LogAlways:
138 fprintf(stderr, " level: LogAlways");
139 break;
140 default:
141 fprintf(stderr, " level: %d?\n", level);
142 break;
143 }
144
145 fprintf(stderr, " keywords: ");
146 if (keyword & GCEventKeyword_GC)
147 {
148 fprintf(stderr, "GC ");
149 }
150
151 if (keyword & GCEventKeyword_GCHandle)
152 {
153 fprintf(stderr, "GCHandle ");
154 }
155
156 if (keyword & GCEventKeyword_GCHeapDump)
157 {
158 fprintf(stderr, "GCHeapDump ");
159 }
160
161 if (keyword & GCEventKeyword_GCSampledObjectAllocationHigh)
162 {
163 fprintf(stderr, "GCSampledObjectAllocationHigh ");
164 }
165
166 if (keyword & GCEventKeyword_GCHeapSurvivalAndMovement)
167 {
168 fprintf(stderr, "GCHeapSurvivalAndMovement ");
169 }
170
171 if (keyword & GCEventKeyword_GCHeapCollect)
172 {
173 fprintf(stderr, "GCHeapCollect ");
174 }
175
176 if (keyword & GCEventKeyword_GCHeapAndTypeNames)
177 {
178 fprintf(stderr, "GCHeapAndTypeNames ");
179 }
180
181 if (keyword & GCEventKeyword_GCSampledObjectAllocationLow)
182 {
183 fprintf(stderr, "GCSampledObjectAllocationLow ");
184 }
185
186 fprintf(stderr, "\n");
187 }
188#endif // TRACE_GC_EVENT_STATUS
189
190 // This class is a singleton and can't be instantiated.
191 GCEventStatus() = delete;
192};
193
194/*
195 * FireDynamicEvent is a variadic function that fires a dynamic event with the
196 * given name and event payload. This function serializes the arguments into
197 * a binary payload that is then passed to IGCToCLREventSink::FireDynamicEvent.
198 */
199template<typename... EventArgument>
200void FireDynamicEvent(const char* name, EventArgument... arguments)
201{
202 size_t size = gc_event::SerializedSize(arguments...);
203 if (size > UINT32_MAX)
204 {
205 // ETW can't handle anything this big.
206 // we shouldn't be firing events that big anyway.
207 return;
208 }
209
210 uint8_t* buf = new (nothrow) uint8_t[size];
211 if (!buf)
212 {
213 // best effort - if we're OOM, don't bother with the event.
214 return;
215 }
216
217 memset(buf, 0, size);
218 uint8_t* cursor = buf;
219 gc_event::Serialize(&cursor, arguments...);
220 IGCToCLREventSink* sink = GCToEEInterface::EventSink();
221 assert(sink != nullptr);
222 sink->FireDynamicEvent(name, buf, static_cast<uint32_t>(size));
223 delete[] buf;
224};
225
226/*
227 * In order to provide a consistent interface between known and dynamic events,
228 * two wrapper functions are generated for each known and dynamic event:
229 * GCEventEnabled##name() - Returns true if the event is enabled, false otherwise.
230 * GCEventFire##name(...) - Fires the event, with the event payload consisting of
231 * the arguments to the function.
232 *
233 * Because the schema of dynamic events comes from the DYNAMIC_EVENT xmacro, we use
234 * the arguments vector as the argument list to `FireDynamicEvent`, which will traverse
235 * the list of arguments and call `IGCToCLREventSink::FireDynamicEvent` with a serialized
236 * payload. Known events will delegate to IGCToCLREventSink::Fire##name.
237 */
238#if FEATURE_EVENT_TRACE
239#define KNOWN_EVENT(name, provider, level, keyword) \
240 inline bool GCEventEnabled##name() { return GCEventStatus::IsEnabled(provider, keyword, level); } \
241 template<typename... EventActualArgument> \
242 inline void GCEventFire##name(EventActualArgument... arguments) \
243 { \
244 if (GCEventEnabled##name()) \
245 { \
246 IGCToCLREventSink* sink = GCToEEInterface::EventSink(); \
247 assert(sink != nullptr); \
248 sink->Fire##name(arguments...); \
249 } \
250 }
251
252#define DYNAMIC_EVENT(name, level, keyword, ...) \
253 inline bool GCEventEnabled##name() { return GCEventStatus::IsEnabled(GCEventProvider_Default, keyword, level); } \
254 template<typename... EventActualArgument> \
255 inline void GCEventFire##name(EventActualArgument... arguments) { FireDynamicEvent<__VA_ARGS__>(#name, arguments...); }
256
257#include "gcevents.h"
258
259#define EVENT_ENABLED(name) GCEventEnabled##name()
260#define FIRE_EVENT(name, ...) GCEventFire##name(__VA_ARGS__)
261#else
262#define EVENT_ENABLED(name) false
263#define FIRE_EVENT(name, ...) 0
264#endif // FEATURE_EVENT_TRACE
265
266#endif // __GCEVENTSTATUS_H__
267