| 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 | /*****************************************************************************/ |
| 6 | // <OWNER>clrjit</OWNER> |
| 7 | // |
| 8 | // This class abstracts the telemetry information collected for the JIT. |
| 9 | // |
| 10 | // Goals: |
| 11 | // 1. Telemetry information should be a NO-op when JIT level telemetry is disabled. |
| 12 | // 2. Data collection should be actionable. |
| 13 | // 3. Data collection should comply to privacy rules. |
| 14 | // 4. Data collection cannot impact JIT/OS performance. |
| 15 | // 5. Data collection volume should be manageable by our remote services. |
| 16 | // |
| 17 | // DESIGN CONCERNS: |
| 18 | // |
| 19 | // > To collect data, we use the TraceLogging API provided by Windows. |
| 20 | // |
| 21 | // The brief workflow suggested is: |
| 22 | // #include <TraceLoggingProvider.h> |
| 23 | // TRACELOGGING_DEFINE_PROVIDER( // defines g_hProvider |
| 24 | // g_hProvider, // Name of the provider variable |
| 25 | // "MyProvider", // Human-readable name of the provider |
| 26 | // (0xb3864c38, 0x4273, 0x58c5, 0x54, 0x5b, 0x8b, 0x36, 0x08, 0x34, 0x34, 0x71)); // Provider GUID |
| 27 | // int main(int argc, char* argv[]) // or DriverEntry for kernel-mode. |
| 28 | // { |
| 29 | // TraceLoggingRegister(g_hProvider, NULL, NULL, NULL); // NULLs only needed for C. Please do not include the |
| 30 | // // NULLs in C++ code. |
| 31 | // TraceLoggingWrite(g_hProvider, |
| 32 | // "MyEvent1", |
| 33 | // TraceLoggingString(argv[0], "arg0"), |
| 34 | // TraceLoggingInt32(argc)); |
| 35 | // TraceLoggingUnregister(g_hProvider); |
| 36 | // return 0; |
| 37 | // } |
| 38 | // |
| 39 | // In summary, this involves: |
| 40 | // 1. Creating a binary/DLL local provider using: |
| 41 | // TRACELOGGING_DEFINE_PROVIDER(g_hProvider, "ProviderName", providerId, [option]) |
| 42 | // 2. Registering the provider instance |
| 43 | // TraceLoggingRegister(g_hProvider) |
| 44 | // 3. Perform TraceLoggingWrite operations to write out data. |
| 45 | // 4. Unregister the provider instance. |
| 46 | // TraceLoggingUnregister(g_hProvider) |
| 47 | // |
| 48 | // A. Determining where to create the provider instance? |
| 49 | // 1) We use the same provider name/GUID as the CLR and the CLR creates its own DLL local provider handle. |
| 50 | // For CLRJIT.dll, the question is, can the same provider name/GUIDs be shared across binaries? |
| 51 | // |
| 52 | // Answer: |
| 53 | // "For TraceLogging providers, it is okay to use the same provider GUID / name |
| 54 | // in different binaries. Do not share the same provider handle across DLLs. |
| 55 | // As long as you do not pass an hProvider from one DLL to another, TraceLogging |
| 56 | // will properly keep track of the events." |
| 57 | // |
| 58 | // 2) CoreCLR is linked into the CLR. CLR already creates an instance, so where do we create the JIT's instance? |
| 59 | // Answer: |
| 60 | // "Ideally you would have one provider per DLL, but if you're folding distinct sets |
| 61 | // of functionality into one DLL (like shell32.dll or similar sort of catch-all things) |
| 62 | // you can have perhaps a few more providers per binary." |
| 63 | // |
| 64 | // B. Determining where to register and unregister the provider instance? |
| 65 | // 1) For CLRJIT.dll we can register the provider instance during jitDllOnProcessAttach. |
| 66 | // Since one of our goals is to turn telemetry off, we need to be careful about |
| 67 | // referencing environment variables during the DLL load and unload path. |
| 68 | // Referencing environment variables through ConfigDWORD uses UtilCode. |
| 69 | // This roughly translates to InitUtilcode() being called before jitDllOnProcessAttach. |
| 70 | // |
| 71 | // For CLRJIT.dll, compStartup is called on jitOnDllProcessAttach(). |
| 72 | // This can be called twice through sxsJitStartup -- so prevent double initialization. |
| 73 | // UtilCode is init-ed by this time. The same is true for CoreCLR. |
| 74 | // |
| 75 | // 2) For CLRJIT.dll and CoreCLR, compShutdown will be called during jitOnDllProcessDetach(). |
| 76 | // |
| 77 | // C. Determining the data to collect: |
| 78 | // |
| 79 | // IMPORTANT: Since telemetry data can be collected at any time after DLL load, |
| 80 | // make sure you initialize the compiler state variables you access in telemetry |
| 81 | // data collection. For example, if you are transmitting method names, then |
| 82 | // make sure info.compMethodHnd is initialized at that point. |
| 83 | // |
| 84 | // 1) Tracking noway assert count: |
| 85 | // After a noway assert is hit, in both min-opts and non-min-opts, we collect |
| 86 | // info such as the JIT version, method hash being compiled, filename and |
| 87 | // line number etc. |
| 88 | // |
| 89 | // 2) Tracking baseline for the noway asserts: |
| 90 | // During DLL unload, we report the number of methods that were compiled by |
| 91 | // the JIT per process both under normal mode and during min-opts. NOTE that |
| 92 | // this is ON for all processes. |
| 93 | // |
| 94 | // 3) For the future, be aware of privacy, performance and actionability of the data. |
| 95 | // |
| 96 | |
| 97 | #include "jitpch.h" |
| 98 | #include "compiler.h" |
| 99 | |
| 100 | #ifdef FEATURE_TRACELOGGING |
| 101 | #include "TraceLoggingProvider.h" |
| 102 | #include "MicrosoftTelemetry.h" |
| 103 | #include "clrtraceloggingcommon.h" |
| 104 | #include "fxver.h" |
| 105 | |
| 106 | // Since telemetry code could be called under a noway_assert, make sure, |
| 107 | // we don't call noway_assert again. |
| 108 | #undef noway_assert |
| 109 | |
| 110 | #define BUILD_STR1(x) #x |
| 111 | #define BUILD_STR2(x) BUILD_STR1(x) |
| 112 | #define BUILD_MACHINE BUILD_STR2(__BUILDMACHINE__) |
| 113 | |
| 114 | // A DLL local instance of the DotNet provider |
| 115 | TRACELOGGING_DEFINE_PROVIDER(g_hClrJitProvider, |
| 116 | CLRJIT_PROVIDER_NAME, |
| 117 | CLRJIT_PROVIDER_ID, |
| 118 | TraceLoggingOptionMicrosoftTelemetry()); |
| 119 | |
| 120 | // Threshold to detect if we are hitting too many bad (noway) methods |
| 121 | // over good methods per process to prevent logging too much data. |
| 122 | static const double NOWAY_NOISE_RATIO = 0.6; // Threshold of (bad / total) beyond which we'd stop |
| 123 | // logging. We'd restart if the pass rate improves. |
| 124 | static const unsigned NOWAY_SUFFICIENCY_THRESHOLD = 25; // Count of methods beyond which we'd apply percent |
| 125 | // threshold |
| 126 | |
| 127 | // Initialize Telemetry State |
| 128 | volatile bool JitTelemetry::s_fProviderRegistered = false; |
| 129 | volatile UINT32 JitTelemetry::s_uMethodsCompiled = 0; |
| 130 | volatile UINT32 JitTelemetry::s_uMethodsHitNowayAssert = 0; |
| 131 | |
| 132 | // Constructor for telemetry state per compiler instance |
| 133 | JitTelemetry::JitTelemetry() |
| 134 | { |
| 135 | Initialize(nullptr); |
| 136 | } |
| 137 | |
| 138 | //------------------------------------------------------------------------ |
| 139 | // Initialize: Initialize the object with the compiler instance |
| 140 | // |
| 141 | // Description: |
| 142 | // Compiler instance may not be fully initialized. If you are |
| 143 | // tracking object data for telemetry, make sure they are initialized |
| 144 | // in the compiler is ready. |
| 145 | // |
| 146 | void JitTelemetry::Initialize(Compiler* c) |
| 147 | { |
| 148 | comp = c; |
| 149 | m_pszAssemblyName = "" ; |
| 150 | m_pszScopeName = "" ; |
| 151 | m_pszMethodName = "" ; |
| 152 | m_uMethodHash = 0; |
| 153 | m_fMethodInfoCached = false; |
| 154 | } |
| 155 | |
| 156 | //------------------------------------------------------------------------ |
| 157 | // IsTelemetryEnabled: Can we perform JIT telemetry |
| 158 | // |
| 159 | // Return Value: |
| 160 | // Returns "true" if COMPlus_JitTelemetry environment flag is |
| 161 | // non-zero. Else returns "false". |
| 162 | // |
| 163 | // |
| 164 | /* static */ |
| 165 | bool JitTelemetry::IsTelemetryEnabled() |
| 166 | { |
| 167 | return JitConfig.JitTelemetry() != 0; |
| 168 | } |
| 169 | |
| 170 | //------------------------------------------------------------------------ |
| 171 | // NotifyDllProcessAttach: Notification for DLL load and static initializations |
| 172 | // |
| 173 | // Description: |
| 174 | // Register telemetry provider with the OS. |
| 175 | // |
| 176 | // Note: |
| 177 | // This method can be called twice in NGEN scenario. |
| 178 | // |
| 179 | void JitTelemetry::NotifyDllProcessAttach() |
| 180 | { |
| 181 | if (!IsTelemetryEnabled()) |
| 182 | { |
| 183 | return; |
| 184 | } |
| 185 | |
| 186 | if (!s_fProviderRegistered) |
| 187 | { |
| 188 | // Register the provider. |
| 189 | TraceLoggingRegister(g_hClrJitProvider); |
| 190 | s_fProviderRegistered = true; |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | //------------------------------------------------------------------------ |
| 195 | // NotifyDllProcessDetach: Notification for DLL unload and teardown |
| 196 | // |
| 197 | // Description: |
| 198 | // Log the methods compiled data if telemetry is enabled and |
| 199 | // Unregister telemetry provider with the OS. |
| 200 | // |
| 201 | void JitTelemetry::NotifyDllProcessDetach() |
| 202 | { |
| 203 | if (!IsTelemetryEnabled()) |
| 204 | { |
| 205 | return; |
| 206 | } |
| 207 | |
| 208 | assert(s_fProviderRegistered); // volatile read |
| 209 | |
| 210 | // Unregister the provider. |
| 211 | TraceLoggingUnregister(g_hClrJitProvider); |
| 212 | } |
| 213 | |
| 214 | //------------------------------------------------------------------------ |
| 215 | // NotifyEndOfCompilation: Notification for end of current method |
| 216 | // compilation. |
| 217 | // |
| 218 | // Description: |
| 219 | // Increment static volatile counters for the current compiled method. |
| 220 | // This is slightly inaccurate due to lack of synchronization around |
| 221 | // the counters. Inaccuracy is the tradeoff for JITting cost. |
| 222 | // |
| 223 | // Note: |
| 224 | // 1. Must be called post fully successful compilation of the method. |
| 225 | // 2. This serves as an effective baseline as how many methods compiled |
| 226 | // successfully. |
| 227 | void JitTelemetry::NotifyEndOfCompilation() |
| 228 | { |
| 229 | if (!IsTelemetryEnabled()) |
| 230 | { |
| 231 | return; |
| 232 | } |
| 233 | |
| 234 | s_uMethodsCompiled++; // volatile increment |
| 235 | } |
| 236 | |
| 237 | //------------------------------------------------------------------------ |
| 238 | // NotifyNowayAssert: Notification that a noway handling is under-way. |
| 239 | // |
| 240 | // Arguments: |
| 241 | // filename - The JIT source file name's absolute path at the time of |
| 242 | // building the JIT. |
| 243 | // line - The line number where the noway assert was hit. |
| 244 | // |
| 245 | // Description: |
| 246 | // If telemetry is enabled, then obtain data to collect from the |
| 247 | // compiler or the VM and use the tracelogging APIs to write out. |
| 248 | // |
| 249 | void JitTelemetry::NotifyNowayAssert(const char* filename, unsigned line) |
| 250 | { |
| 251 | if (!IsTelemetryEnabled()) |
| 252 | { |
| 253 | return; |
| 254 | } |
| 255 | |
| 256 | s_uMethodsHitNowayAssert++; |
| 257 | |
| 258 | // Check if our assumption that noways are rare is invalid for this |
| 259 | // process. If so, return early than logging too much data. |
| 260 | unsigned noways = s_uMethodsHitNowayAssert; |
| 261 | unsigned attempts = max(1, s_uMethodsCompiled + noways); |
| 262 | double ratio = (noways / ((double)attempts)); |
| 263 | if (noways > NOWAY_SUFFICIENCY_THRESHOLD && ratio > NOWAY_NOISE_RATIO) |
| 264 | { |
| 265 | return; |
| 266 | } |
| 267 | |
| 268 | assert(comp); |
| 269 | |
| 270 | UINT32 nowayIndex = s_uMethodsHitNowayAssert; |
| 271 | UINT32 codeSize = 0; |
| 272 | INT32 minOpts = -1; |
| 273 | const char* lastPhase = "" ; |
| 274 | if (comp != nullptr) |
| 275 | { |
| 276 | codeSize = comp->info.compILCodeSize; |
| 277 | minOpts = comp->opts.IsMinOptsSet() ? comp->opts.MinOpts() : -1; |
| 278 | lastPhase = PhaseNames[comp->previousCompletedPhase]; |
| 279 | } |
| 280 | |
| 281 | CacheCurrentMethodInfo(); |
| 282 | |
| 283 | TraceLoggingWrite(g_hClrJitProvider, "CLRJIT.NowayAssert" , |
| 284 | |
| 285 | TraceLoggingUInt32(codeSize, "IL_CODE_SIZE" ), TraceLoggingInt32(minOpts, "MINOPTS_MODE" ), |
| 286 | TraceLoggingString(lastPhase, "PREVIOUS_COMPLETED_PHASE" ), |
| 287 | |
| 288 | TraceLoggingString(m_pszAssemblyName, "ASSEMBLY_NAME" ), |
| 289 | TraceLoggingString(m_pszMethodName, "METHOD_NAME" ), |
| 290 | TraceLoggingString(m_pszScopeName, "METHOD_SCOPE" ), |
| 291 | TraceLoggingUInt32(m_uMethodHash, "METHOD_HASH" ), |
| 292 | |
| 293 | TraceLoggingString(filename, "FILENAME" ), TraceLoggingUInt32(line, "LINE" ), |
| 294 | TraceLoggingUInt32(nowayIndex, "NOWAY_INDEX" ), |
| 295 | |
| 296 | TraceLoggingString(TARGET_READABLE_NAME, "ARCH" ), |
| 297 | TraceLoggingString(VER_FILEVERSION_STR, "VERSION" ), TraceLoggingString(BUILD_MACHINE, "BUILD" ), |
| 298 | TraceLoggingString(VER_COMMENTS_STR, "FLAVOR" ), |
| 299 | |
| 300 | TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); |
| 301 | } |
| 302 | |
| 303 | //------------------------------------------------------------------------ |
| 304 | // CacheCurrentMethodInfo: Cache the method/assembly/scope name info. |
| 305 | // |
| 306 | // Description: |
| 307 | // Obtain the method information if not already cached, for the |
| 308 | // method under compilation from the compiler. This includes: |
| 309 | // |
| 310 | // Method name, assembly name, scope name, method hash. |
| 311 | // |
| 312 | void JitTelemetry::CacheCurrentMethodInfo() |
| 313 | { |
| 314 | if (m_fMethodInfoCached) |
| 315 | { |
| 316 | return; |
| 317 | } |
| 318 | |
| 319 | assert(comp); |
| 320 | if (comp != nullptr) |
| 321 | { |
| 322 | comp->compGetTelemetryDefaults(&m_pszAssemblyName, &m_pszScopeName, &m_pszMethodName, &m_uMethodHash); |
| 323 | assert(m_pszAssemblyName); |
| 324 | assert(m_pszScopeName); |
| 325 | assert(m_pszMethodName); |
| 326 | } |
| 327 | |
| 328 | // Set cached to prevent getting this twice. |
| 329 | m_fMethodInfoCached = true; |
| 330 | } |
| 331 | |
| 332 | //------------------------------------------------------------------------ |
| 333 | // compGetTelemetryDefaults: Obtain information specific to telemetry |
| 334 | // from the JIT-interface. |
| 335 | // |
| 336 | // Arguments: |
| 337 | // assemblyName - Pointer to hold assembly name upon return |
| 338 | // scopeName - Pointer to hold scope name upon return |
| 339 | // methodName - Pointer to hold method name upon return |
| 340 | // methodHash - Pointer to hold method hash upon return |
| 341 | // |
| 342 | // Description: |
| 343 | // Obtains from the JIT EE interface the information for the |
| 344 | // current method under compilation. |
| 345 | // |
| 346 | // Warning: |
| 347 | // The eeGetMethodName call could be expensive for generic |
| 348 | // methods, so call this method only when there is less impact |
| 349 | // to throughput. |
| 350 | // |
| 351 | void Compiler::compGetTelemetryDefaults(const char** assemblyName, |
| 352 | const char** scopeName, |
| 353 | const char** methodName, |
| 354 | unsigned* methodHash) |
| 355 | { |
| 356 | if (info.compMethodHnd != nullptr) |
| 357 | { |
| 358 | __try |
| 359 | { |
| 360 | |
| 361 | // Expensive calls, call infrequently or in exceptional scenarios. |
| 362 | *methodHash = info.compCompHnd->getMethodHash(info.compMethodHnd); |
| 363 | *methodName = eeGetMethodName(info.compMethodHnd, scopeName); |
| 364 | |
| 365 | // SuperPMI needs to implement record/replay of these method calls. |
| 366 | *assemblyName = info.compCompHnd->getAssemblyName( |
| 367 | info.compCompHnd->getModuleAssembly(info.compCompHnd->getClassModule(info.compClassHnd))); |
| 368 | } |
| 369 | __except (EXCEPTION_EXECUTE_HANDLER) |
| 370 | { |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | // If the JIT interface methods init-ed these values to nullptr, |
| 375 | // make sure they are set to empty string. |
| 376 | if (*methodName == nullptr) |
| 377 | { |
| 378 | *methodName = "" ; |
| 379 | } |
| 380 | if (*scopeName == nullptr) |
| 381 | { |
| 382 | *scopeName = "" ; |
| 383 | } |
| 384 | if (*assemblyName == nullptr) |
| 385 | { |
| 386 | *assemblyName = "" ; |
| 387 | } |
| 388 | } |
| 389 | |
| 390 | #endif // FEATURE_TRACELOGGING |
| 391 | |