| 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 | // File: eventtrace.h |
| 6 | // Abstract: This module implements Event Tracing support. This includes |
| 7 | // eventtracebase.h, and adds VM-specific ETW helpers to support features like type |
| 8 | // logging, allocation logging, and gc heap walk logging. |
| 9 | // |
| 10 | |
| 11 | // |
| 12 | |
| 13 | // |
| 14 | // |
| 15 | // #EventTracing |
| 16 | // Windows |
| 17 | // ETW (Event Tracing for Windows) is a high-performance, low overhead and highly scalable |
| 18 | // tracing facility provided by the Windows Operating System. ETW is available on Win2K and above. There are |
| 19 | // four main types of components in ETW: event providers, controllers, consumers, and event trace sessions. |
| 20 | // An event provider is a logical entity that writes events to ETW sessions. The event provider must register |
| 21 | // a provider ID with ETW through the registration API. A provider first registers with ETW and writes events |
| 22 | // from various points in the code by invoking the ETW logging API. When a provider is enabled dynamically by |
| 23 | // the ETW controller application, calls to the logging API sends events to a specific trace session |
| 24 | // designated by the controller. Each event sent by the event provider to the trace session consists of a |
| 25 | // fixed header that includes event metadata and additional variable user-context data. CLR is an event |
| 26 | // provider. |
| 27 | |
| 28 | // Mac |
| 29 | // DTrace is similar to ETW and has been made to look like ETW at most of the places. |
| 30 | // For convenience, it is called ETM (Event Tracing for Mac) and exists only on the Mac Leopard OS |
| 31 | // ============================================================================ |
| 32 | |
| 33 | #ifndef _VMEVENTTRACE_H_ |
| 34 | #define _VMEVENTTRACE_H_ |
| 35 | |
| 36 | #include "eventtracebase.h" |
| 37 | #include "gcinterface.h" |
| 38 | |
| 39 | #if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) |
| 40 | struct ProfilingScanContext : ScanContext |
| 41 | { |
| 42 | BOOL fProfilerPinned; |
| 43 | void * pvEtwContext; |
| 44 | void *pHeapId; |
| 45 | |
| 46 | ProfilingScanContext(BOOL fProfilerPinnedParam) : ScanContext() |
| 47 | { |
| 48 | LIMITED_METHOD_CONTRACT; |
| 49 | |
| 50 | pHeapId = NULL; |
| 51 | fProfilerPinned = fProfilerPinnedParam; |
| 52 | pvEtwContext = NULL; |
| 53 | #ifdef FEATURE_CONSERVATIVE_GC |
| 54 | // To not confuse GCScan::GcScanRoots |
| 55 | promotion = g_pConfig->GetGCConservative(); |
| 56 | #endif |
| 57 | } |
| 58 | }; |
| 59 | #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) |
| 60 | |
| 61 | #ifndef FEATURE_REDHAWK |
| 62 | |
| 63 | namespace ETW |
| 64 | { |
| 65 | class LoggedTypesFromModule; |
| 66 | |
| 67 | // We keep a hash of these to keep track of: |
| 68 | // * Which types have been logged through ETW (so we can avoid logging dupe Type |
| 69 | // events), and |
| 70 | // * GCSampledObjectAllocation stats to help with "smart sampling" which |
| 71 | // dynamically adjusts sampling rate of objects by type. |
| 72 | // See code:LoggedTypesFromModuleTraits |
| 73 | struct TypeLoggingInfo |
| 74 | { |
| 75 | public: |
| 76 | TypeLoggingInfo(TypeHandle thParam) |
| 77 | { |
| 78 | Init(thParam); |
| 79 | } |
| 80 | |
| 81 | TypeLoggingInfo() |
| 82 | { |
| 83 | Init(TypeHandle()); |
| 84 | } |
| 85 | |
| 86 | void Init(TypeHandle thParam) |
| 87 | { |
| 88 | th = thParam; |
| 89 | dwTickOfCurrentTimeBucket = 0; |
| 90 | dwAllocCountInCurrentBucket = 0; |
| 91 | flAllocPerMSec = 0; |
| 92 | |
| 93 | dwAllocsToSkipPerSample = 0; |
| 94 | dwAllocsSkippedForSample = 0; |
| 95 | cbIgnoredSizeForSample = 0; |
| 96 | }; |
| 97 | |
| 98 | // The type this TypeLoggingInfo represents |
| 99 | TypeHandle th; |
| 100 | |
| 101 | // Smart sampling |
| 102 | |
| 103 | // These bucket values remember stats of a particular time slice that are used to |
| 104 | // help adjust the sampling rate |
| 105 | DWORD dwTickOfCurrentTimeBucket; |
| 106 | DWORD dwAllocCountInCurrentBucket; |
| 107 | float flAllocPerMSec; |
| 108 | |
| 109 | // The number of data points to ignore before taking a "sample" (i.e., logging a |
| 110 | // GCSampledObjectAllocation ETW event for this type) |
| 111 | DWORD dwAllocsToSkipPerSample; |
| 112 | |
| 113 | // The current number of data points actually ignored for the current sample |
| 114 | DWORD dwAllocsSkippedForSample; |
| 115 | |
| 116 | // The current count of bytes of objects of this type actually allocated (and |
| 117 | // ignored) for the current sample |
| 118 | SIZE_T cbIgnoredSizeForSample; |
| 119 | }; |
| 120 | |
| 121 | // Class to wrap all type system logic for ETW |
| 122 | class TypeSystemLog |
| 123 | { |
| 124 | private: |
| 125 | // Global type hash |
| 126 | static AllLoggedTypes *s_pAllLoggedTypes; |
| 127 | |
| 128 | // An unsigned value that gets incremented whenever a global change is made. |
| 129 | // When this occurs, threads must synchronize themselves with the global state. |
| 130 | // Examples include unloading of modules and disabling of allocation sampling. |
| 131 | static unsigned int s_nEpoch; |
| 132 | |
| 133 | // See code:ETW::TypeSystemLog::PostRegistrationInit |
| 134 | static BOOL s_fHeapAllocEventEnabledOnStartup; |
| 135 | static BOOL s_fHeapAllocHighEventEnabledNow; |
| 136 | static BOOL s_fHeapAllocLowEventEnabledNow; |
| 137 | |
| 138 | // If COMPLUS_UNSUPPORTED_ETW_ObjectAllocationEventsPerTypePerSec is set, then |
| 139 | // this is used to determine the event frequency, overriding |
| 140 | // s_nDefaultMsBetweenEvents above (regardless of which |
| 141 | // GCSampledObjectAllocation*Keyword was used) |
| 142 | static int s_nCustomMsBetweenEvents; |
| 143 | |
| 144 | public: |
| 145 | // This customizes the type logging behavior in LogTypeAndParametersIfNecessary |
| 146 | enum TypeLogBehavior |
| 147 | { |
| 148 | // Take lock, and consult hash table to see if this is the first time we've |
| 149 | // encountered the type, in which case, log it |
| 150 | kTypeLogBehaviorTakeLockAndLogIfFirstTime, |
| 151 | |
| 152 | // Don't take lock, don't consult hash table. Just log the type. (This is |
| 153 | // used in cases when checking for dupe type logging isn't worth it, such as |
| 154 | // when logging the finalization of an object.) |
| 155 | kTypeLogBehaviorAlwaysLog, |
| 156 | |
| 157 | // When logging the type for GCSampledObjectAllocation events, |
| 158 | // we already know we need to log the type (since we already |
| 159 | // looked it up in the hash). But we would still need to consult the hash |
| 160 | // for any type parameters, so kTypeLogBehaviorAlwaysLog isn't appropriate, |
| 161 | // and this is used instead. |
| 162 | kTypeLogBehaviorAlwaysLogTopLevelType, |
| 163 | }; |
| 164 | |
| 165 | static HRESULT PreRegistrationInit(); |
| 166 | static void PostRegistrationInit(); |
| 167 | static BOOL IsHeapAllocEventEnabled(); |
| 168 | static void SendObjectAllocatedEvent(Object * pObject); |
| 169 | static CrstBase * GetHashCrst(); |
| 170 | static VOID LogTypeAndParametersIfNecessary(BulkTypeEventLogger * pBulkTypeEventLogger, ULONGLONG thAsAddr, TypeLogBehavior typeLogBehavior); |
| 171 | static VOID OnModuleUnload(Module * pModule); |
| 172 | static void OnKeywordsChanged(); |
| 173 | static void Cleanup(); |
| 174 | static VOID DeleteTypeHashNoLock(AllLoggedTypes **ppAllLoggedTypes); |
| 175 | static VOID FlushObjectAllocationEvents(); |
| 176 | |
| 177 | private: |
| 178 | static BOOL ShouldLogType(TypeHandle th); |
| 179 | static TypeLoggingInfo LookupOrCreateTypeLoggingInfo(TypeHandle th, BOOL * pfCreatedNew, LoggedTypesFromModule ** ppLoggedTypesFromModule = NULL); |
| 180 | static BOOL AddTypeToGlobalCacheIfNotExists(TypeHandle th, BOOL * pfCreatedNew); |
| 181 | static BOOL AddOrReplaceTypeLoggingInfo(ETW::LoggedTypesFromModule * pLoggedTypesFromModule, const ETW::TypeLoggingInfo * pTypeLoggingInfo); |
| 182 | static int GetDefaultMsBetweenEvents(); |
| 183 | static VOID OnTypesKeywordTurnedOff(); |
| 184 | }; |
| 185 | |
| 186 | #endif // FEATURE_REDHAWK |
| 187 | |
| 188 | |
| 189 | // Class to wrap all GC logic for ETW |
| 190 | class GCLog |
| 191 | { |
| 192 | private: |
| 193 | // When WPA triggers a GC, it gives us this unique number to append to our |
| 194 | // GCStart event so WPA can correlate the CLR's GC with the JScript GC they |
| 195 | // triggered at the same time. |
| 196 | // |
| 197 | // We set this value when the GC is triggered, and then retrieve the value on the |
| 198 | // first subsequent FireGcStart() method call for a full, induced GC, assuming |
| 199 | // that that's the GC that WPA triggered. This is imperfect, and if we were in |
| 200 | // the act of beginning another full, induced GC (for some other reason), then |
| 201 | // we'll attach this sequence number to that GC instead of to the WPA-induced GC, |
| 202 | // but who cares? When parsing ETW logs later on, it's indistinguishable if both |
| 203 | // GCs really were induced at around the same time. |
| 204 | #ifdef FEATURE_REDHAWK |
| 205 | static volatile LONGLONG s_l64LastClientSequenceNumber; |
| 206 | #else // FEATURE_REDHAWK |
| 207 | static Volatile<LONGLONG> s_l64LastClientSequenceNumber; |
| 208 | #endif // FEATURE_REDHAWK |
| 209 | |
| 210 | public: |
| 211 | typedef union st_GCEventInfo { |
| 212 | typedef struct _GenerationInfo { |
| 213 | ULONGLONG GenerationSize; |
| 214 | ULONGLONG TotalPromotedSize; |
| 215 | } GenerationInfo; |
| 216 | |
| 217 | struct { |
| 218 | GenerationInfo GenInfo[4]; // the heap info on gen0, gen1, gen2 and the large object heap. |
| 219 | ULONGLONG FinalizationPromotedSize; //not available per generation |
| 220 | ULONGLONG FinalizationPromotedCount; //not available per generation |
| 221 | ULONG PinnedObjectCount; |
| 222 | ULONG SinkBlockCount; |
| 223 | ULONG GCHandleCount; |
| 224 | } HeapStats; |
| 225 | |
| 226 | typedef enum _HeapType { |
| 227 | SMALL_OBJECT_HEAP, LARGE_OBJECT_HEAP, READ_ONLY_HEAP |
| 228 | } HeapType; |
| 229 | struct { |
| 230 | ULONGLONG Address; |
| 231 | ULONGLONG Size; |
| 232 | HeapType Type; |
| 233 | } GCCreateSegment; |
| 234 | |
| 235 | struct { |
| 236 | ULONGLONG Address; |
| 237 | } GCFreeSegment; |
| 238 | struct { |
| 239 | ULONG Count; |
| 240 | ULONG Depth; |
| 241 | } GCEnd; |
| 242 | |
| 243 | typedef enum _AllocationKind { |
| 244 | AllocationSmall = 0, |
| 245 | AllocationLarge |
| 246 | }AllocationKind; |
| 247 | struct { |
| 248 | ULONG Allocation; |
| 249 | AllocationKind Kind; |
| 250 | } AllocationTick; |
| 251 | |
| 252 | // These values are gotten from the gc_reason |
| 253 | // in gcimpl.h |
| 254 | typedef enum _GC_REASON { |
| 255 | GC_ALLOC_SOH = 0, |
| 256 | GC_INDUCED = 1, |
| 257 | GC_LOWMEMORY = 2, |
| 258 | GC_EMPTY = 3, |
| 259 | GC_ALLOC_LOH = 4, |
| 260 | GC_OOS_SOH = 5, |
| 261 | GC_OOS_LOH = 6, |
| 262 | GC_INDUCED_NOFORCE = 7, |
| 263 | GC_GCSTRESS = 8, |
| 264 | GC_LOWMEMORY_BLOCKING = 9, |
| 265 | GC_INDUCED_COMPACTING = 10, |
| 266 | GC_LOWMEMORY_HOST = 11 |
| 267 | } GC_REASON; |
| 268 | typedef enum _GC_TYPE { |
| 269 | GC_NGC = 0, |
| 270 | GC_BGC = 1, |
| 271 | GC_FGC = 2 |
| 272 | } GC_TYPE; |
| 273 | typedef enum _GC_ROOT_KIND { |
| 274 | GC_ROOT_STACK = 0, |
| 275 | GC_ROOT_FQ = 1, |
| 276 | GC_ROOT_HANDLES = 2, |
| 277 | GC_ROOT_OLDER = 3, |
| 278 | GC_ROOT_SIZEDREF = 4, |
| 279 | GC_ROOT_OVERFLOW = 5 |
| 280 | } GC_ROOT_KIND; |
| 281 | struct { |
| 282 | ULONG Count; |
| 283 | ULONG Depth; |
| 284 | GC_REASON Reason; |
| 285 | GC_TYPE Type; |
| 286 | } GCStart; |
| 287 | |
| 288 | struct { |
| 289 | ULONG Count; // how many finalizers we called. |
| 290 | } GCFinalizers; |
| 291 | |
| 292 | struct { |
| 293 | ULONG Reason; |
| 294 | // This is only valid when SuspendEE is called by GC (ie, Reason is either |
| 295 | // SUSPEND_FOR_GC or SUSPEND_FOR_GC_PREP. |
| 296 | ULONG GcCount; |
| 297 | } SuspendEE; |
| 298 | |
| 299 | struct { |
| 300 | ULONG HeapNum; |
| 301 | } GCMark; |
| 302 | |
| 303 | struct { |
| 304 | ULONGLONG SegmentSize; |
| 305 | ULONGLONG LargeObjectSegmentSize; |
| 306 | BOOL ServerGC; // TRUE means it's server GC; FALSE means it's workstation. |
| 307 | } GCSettings; |
| 308 | |
| 309 | struct { |
| 310 | // The generation that triggered this notification. |
| 311 | ULONG Count; |
| 312 | // 1 means the notification was due to allocation; 0 means it was due to other factors. |
| 313 | ULONG Alloc; |
| 314 | } GCFullNotify; |
| 315 | } ETW_GC_INFO, *PETW_GC_INFO; |
| 316 | |
| 317 | #ifdef FEATURE_EVENT_TRACE |
| 318 | static VOID GCSettingsEvent(); |
| 319 | #else |
| 320 | static VOID GCSettingsEvent() {}; |
| 321 | #endif // FEATURE_EVENT_TRACE |
| 322 | |
| 323 | static BOOL ShouldWalkHeapObjectsForEtw(); |
| 324 | static BOOL ShouldWalkHeapRootsForEtw(); |
| 325 | static BOOL ShouldTrackMovementForEtw(); |
| 326 | static HRESULT ForceGCForDiagnostics(); |
| 327 | static VOID ForceGC(LONGLONG l64ClientSequenceNumber); |
| 328 | static VOID FireGcStart(ETW_GC_INFO * pGcInfo); |
| 329 | static VOID RootReference( |
| 330 | LPVOID pvHandle, |
| 331 | Object * pRootedNode, |
| 332 | Object * pSecondaryNodeForDependentHandle, |
| 333 | BOOL fDependentHandle, |
| 334 | ProfilingScanContext * profilingScanContext, |
| 335 | DWORD dwGCFlags, |
| 336 | DWORD rootFlags); |
| 337 | static VOID ObjectReference( |
| 338 | ProfilerWalkHeapContext * profilerWalkHeapContext, |
| 339 | Object * pObjReferenceSource, |
| 340 | ULONGLONG typeID, |
| 341 | ULONGLONG cRefs, |
| 342 | Object ** rgObjReferenceTargets); |
| 343 | static BOOL ShouldWalkStaticsAndCOMForEtw(); |
| 344 | static VOID WalkStaticsAndCOMForETW(); |
| 345 | static VOID EndHeapDump(ProfilerWalkHeapContext * profilerWalkHeapContext); |
| 346 | #ifdef FEATURE_EVENT_TRACE |
| 347 | static VOID BeginMovedReferences(size_t * pProfilingContext); |
| 348 | static VOID MovedReference(BYTE * pbMemBlockStart, BYTE * pbMemBlockEnd, ptrdiff_t cbRelocDistance, size_t profilingContext, BOOL fCompacting, BOOL fAllowProfApiNotification = TRUE); |
| 349 | static VOID EndMovedReferences(size_t profilingContext, BOOL fAllowProfApiNotification = TRUE); |
| 350 | #else |
| 351 | // TODO: Need to be implemented for PROFILING_SUPPORTED. |
| 352 | static VOID BeginMovedReferences(size_t * pProfilingContext) {}; |
| 353 | static VOID MovedReference(BYTE * pbMemBlockStart, BYTE * pbMemBlockEnd, ptrdiff_t cbRelocDistance, size_t profilingContext, BOOL fCompacting, BOOL fAllowProfApiNotification = TRUE) {}; |
| 354 | static VOID EndMovedReferences(size_t profilingContext, BOOL fAllowProfApiNotification = TRUE) {}; |
| 355 | #endif // FEATURE_EVENT_TRACE |
| 356 | static VOID SendFinalizeObjectEvent(MethodTable * pMT, Object * pObj); |
| 357 | }; |
| 358 | }; |
| 359 | |
| 360 | |
| 361 | #endif //_VMEVENTTRACE_H_ |
| 362 | |