summaryrefslogtreecommitdiff
path: root/src/gc/gceventstatus.h
blob: 1f31d424bec359a41116764e99e2a5097ccc0135 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// 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 __GCEVENTSTATUS_H__
#define __GCEVENTSTATUS_H__


/*
 * gceventstatus.h - Eventing status for a standalone GC
 *
 * In order for a local GC to determine what events are enabled
 * in an efficient manner, the GC maintains some local state about
 * keywords and levels that are enabled for each eventing provider.
 *
 * The GC fires events from two providers: the "main" provider
 * and the "private" provider. This file tracks keyword and level
 * information for each provider separately.
 *
 * It is the responsibility of the EE to inform the GC of changes
 * to eventing state. This is accomplished by invoking the
 * `IGCHeap::ControlEvents` and `IGCHeap::ControlPrivateEvents` callbacks
 * on the EE's heap instance, which ultimately will enable and disable keywords
 * and levels within this file.
 */

#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 maintains all eventing state for the GC. It consists
 * of a keyword bitmask and level for each provider that the GC can use
 * to fire events.
 *
 * A level and event pair are considered to be "enabled" on a given provider
 * if the given level is less than or equal to the current enabled level
 * and if the keyword is present in the enabled keyword bitmask for that
 * provider.
 */
class GCEventStatus
{
private:
    /*
     * The enabled level for each provider.
     */
    static Volatile<GCEventLevel> enabledLevels[2];

    /*
     * The bitmap of enabled keywords for each provider.
     */
    static Volatile<GCEventKeyword> enabledKeywords[2];

public:
    /*
     * IsEnabled queries whether or not the given level and keyword are
     * enabled on the given provider, returning true if they are.
     */
    __forceinline static bool IsEnabled(GCEventProvider provider, GCEventKeyword keyword, GCEventLevel level)
    {
        assert(level >= GCEventLevel_None && level < GCEventLevel_Max);

        size_t index = static_cast<size_t>(provider);
        return (enabledLevels[index].LoadWithoutBarrier() >= level)
          && (enabledKeywords[index].LoadWithoutBarrier() & keyword);
    }

    /*
     * Set sets the eventing state (level and keyword bitmap) for a given
     * provider to the provided values.
     */
    static void Set(GCEventProvider provider, GCEventKeyword keywords, GCEventLevel level)
    {
        assert((level >= GCEventLevel_None && level < GCEventLevel_Max) || level == GCEventLevel_LogAlways);

        size_t index = static_cast<size_t>(provider);

        enabledLevels[index] = level;
        enabledKeywords[index] = keywords;

#if TRACE_GC_EVENT_STATE
        fprintf(stderr, "event state change:\n");
        DebugDumpState(provider);
#endif // TRACE_GC_EVENT_STATE
    }

    /*
     * Returns currently enabled levels
     */
    static inline GCEventLevel GetEnabledLevel(GCEventProvider provider)
    {
        return enabledLevels[static_cast<size_t>(provider)].LoadWithoutBarrier();
    }

    /*
     * Returns currently enabled keywords in GCPublic
     */
    static inline GCEventKeyword GetEnabledKeywords(GCEventProvider provider)
    {
        return enabledKeywords[static_cast<size_t>(provider)].LoadWithoutBarrier();
    }

#if TRACE_GC_EVENT_STATE
private:
    static void DebugDumpState(GCEventProvider provider)
    {
        size_t index = static_cast<size_t>(provider);
        GCEventLevel level = enabledLevels[index];
        GCEventKeyword keyword = enabledKeywords[index];
        if (provider == GCEventProvider_Default)
        {
            fprintf(stderr, "provider: default\n");
        }
        else
        {
            fprintf(stderr, "provider: private\n");
        }

        switch (level)
        {
        case GCEventLevel_None:
            fprintf(stderr, "  level: None\n");
            break;
        case GCEventLevel_Fatal:
            fprintf(stderr, "  level: Fatal\n");
            break;
        case GCEventLevel_Error:
            fprintf(stderr, "  level: Error\n");
            break;
        case GCEventLevel_Warning:
            fprintf(stderr, "  level: Warning\n");
            break;
        case GCEventLevel_Information:
            fprintf(stderr, "  level: Information\n");
            break;
        case GCEventLevel_Verbose:
            fprintf(stderr, "  level: Verbose\n");
            break;
        case GCEventLevel_LogAlways:
            fprintf(stderr, "  level: LogAlways");
            break;
        default:
            fprintf(stderr, "  level: %d?\n", level);
            break;
        }

        fprintf(stderr, "  keywords: ");
        if (keyword & GCEventKeyword_GC)
        {
            fprintf(stderr, "GC ");
        }

        if (keyword & GCEventKeyword_GCHandle)
        {
            fprintf(stderr, "GCHandle ");
        }

        if (keyword & GCEventKeyword_GCHeapDump)
        {
            fprintf(stderr, "GCHeapDump ");
        }

        if (keyword & GCEventKeyword_GCSampledObjectAllocationHigh)
        {
            fprintf(stderr, "GCSampledObjectAllocationHigh ");
        }

        if (keyword & GCEventKeyword_GCHeapSurvivalAndMovement)
        {
            fprintf(stderr, "GCHeapSurvivalAndMovement ");
        }

        if (keyword & GCEventKeyword_GCHeapCollect)
        {
            fprintf(stderr, "GCHeapCollect ");
        }

        if (keyword & GCEventKeyword_GCHeapAndTypeNames)
        {
            fprintf(stderr, "GCHeapAndTypeNames ");
        }

        if (keyword & GCEventKeyword_GCSampledObjectAllocationLow)
        {
            fprintf(stderr, "GCSampledObjectAllocationLow ");
        }

        fprintf(stderr, "\n");
    }
#endif // TRACE_GC_EVENT_STATUS

    // This class is a singleton and can't be instantiated.
    GCEventStatus() = delete;
};

/*
 * 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)
{
    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, keyword, level); } \
  template<typename... EventActualArgument>                       \
  inline void GCEventFire##name(EventActualArgument... arguments) \
  {                                                               \
      if (GCEventEnabled##name())                                 \
      {                                                           \
          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, ...) GCEventFire##name(__VA_ARGS__)

#else // FEATURE_EVENT_TRACE
#define EVENT_ENABLED(name) false
#define FIRE_EVENT(name, ...) 0
#endif // FEATURE_EVENT_TRACE

#endif // __GCEVENTSTATUS_H__