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 | */ |
39 | enum 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 | */ |
55 | class GCEventStatus |
56 | { |
57 | private: |
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 | |
68 | public: |
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 |
102 | private: |
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 | */ |
199 | template<typename... EventArgument> |
200 | void 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 | |