//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web { using System; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Web.Hosting; using System.Web.Util; // Name and Guid are part of the public contract (for identification by ETW listeners) so cannot // be changed. We're statically specifying a GUID using the same logic as EventSource.GetGuid, // as otherwise EventSource invokes crypto to generate the GUID and this results in an // unacceptable performance degradation (DevDiv #652801). [EventSource(Name = "Microsoft-Windows-ASPNET", Guid = "ee799f41-cfa5-550b-bf2c-344747c1c668")] internal sealed class AspNetEventSource : EventSource { // singleton public static readonly AspNetEventSource Instance = new AspNetEventSource(); private unsafe delegate void WriteEventWithRelatedActivityIdCoreDelegate(int eventId, Guid* childActivityID, int eventDataCount, EventData* data); private readonly WriteEventWithRelatedActivityIdCoreDelegate _writeEventWithRelatedActivityIdCoreDel; private AspNetEventSource() { // We need to light up when running on .NET 4.5.1 since we can't compile directly // against the protected methods we might need to consume. Only ever try creating // this delegate if we're in full trust, otherwise exceptions could happen at // inopportune times (such as during invocation). if (AppDomain.CurrentDomain.IsHomogenous && AppDomain.CurrentDomain.IsFullyTrusted) { MethodInfo writeEventWithRelatedActivityIdCoreMethod = typeof(EventSource).GetMethod( "WriteEventWithRelatedActivityIdCore", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(int), typeof(Guid*), typeof(int), typeof(EventData*) }, null); if (writeEventWithRelatedActivityIdCoreMethod != null) { _writeEventWithRelatedActivityIdCoreDel = (WriteEventWithRelatedActivityIdCoreDelegate)Delegate.CreateDelegate( typeof(WriteEventWithRelatedActivityIdCoreDelegate), this, writeEventWithRelatedActivityIdCoreMethod, throwOnBindFailure: false); } } } [NonEvent] // use the private member signature for deducing ETW parameters [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RequestEnteredAspNetPipeline(IIS7WorkerRequest wr, Guid childActivityId) { if (!IsEnabled()) { return; } Guid parentActivityId = wr.RequestTraceIdentifier; RequestEnteredAspNetPipelineImpl(parentActivityId, childActivityId); } [NonEvent] // use the private member signature for deducing ETW parameters private unsafe void RequestEnteredAspNetPipelineImpl(Guid iisActivityId, Guid aspNetActivityId) { if (ActivityIdHelper.Instance == null || _writeEventWithRelatedActivityIdCoreDel == null || iisActivityId == Guid.Empty) { return; } // IIS doesn't always set the current thread's activity ID before invoking user code. Instead, // its tracing APIs (IHttpTraceContext::RaiseTraceEvent) set the ID, write to ETW, then reset // the ID. If we want to write a transfer event but the current thread's activity ID is // incorrect, then we need to mimic this behavior. We don't use a try / finally since // exceptions here are fatal to the process. Guid originalThreadActivityId = ActivityIdHelper.Instance.CurrentThreadActivityId; bool needToSetThreadActivityId = (originalThreadActivityId != iisActivityId); // Step 1: Set the ID (if necessary) if (needToSetThreadActivityId) { ActivityIdHelper.Instance.SetCurrentThreadActivityId(iisActivityId, out originalThreadActivityId); } // Step 2: Write to ETW, providing the recipient activity ID. _writeEventWithRelatedActivityIdCoreDel((int)Events.RequestEnteredAspNetPipeline, &aspNetActivityId, 0, null); // Step 3: Reset the ID (if necessary) if (needToSetThreadActivityId) { Guid unused; ActivityIdHelper.Instance.SetCurrentThreadActivityId(originalThreadActivityId, out unused); } } // Transfer event signals that control has transitioned from IIS -> ASP.NET. // Overload used only for deducing ETW parameters; use the public entry point instead. // // !! WARNING !! // The logic in RequestEnteredAspNetPipelineImpl must be kept in sync with these parameters, otherwise // type safety violations could occur. [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "ETW looks at this method using reflection.")] [Event((int)Events.RequestEnteredAspNetPipeline, Level = EventLevel.Informational, Task = (EventTask)Tasks.Request, Opcode = EventOpcode.Send, Version = 1)] private void RequestEnteredAspNetPipeline() { throw new NotImplementedException(); } [NonEvent] // use the private member signature for deducing ETW parameters [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void RequestStarted(IIS7WorkerRequest wr) { if (!IsEnabled()) { return; } RequestStartedImpl(wr); } [NonEvent] // use the private member signature for deducing ETW parameters private unsafe void RequestStartedImpl(IIS7WorkerRequest wr) { string httpVerb = wr.GetHttpVerbName(); HTTP_COOKED_URL* pCookedUrl = wr.GetCookedUrl(); Guid iisEtwActivityId = wr.RequestTraceIdentifier; Guid requestCorrelationId = wr.GetRequestCorrelationId(); fixed (char* pHttpVerb = httpVerb) { // !! WARNING !! // This logic must be kept in sync with the ETW-deduced parameters in RequestStarted, // otherwise type safety violations could occur. const int EVENTDATA_COUNT = 3; EventData* pEventData = stackalloc EventData[EVENTDATA_COUNT]; FillInEventData(&pEventData[0], httpVerb, pHttpVerb); // We have knowledge that pFullUrl is null-terminated so we can optimize away // the copy we'd otherwise have to perform. Still need to adjust the length // to account for the null terminator, though. Debug.Assert(pCookedUrl->pFullUrl != null); pEventData[1].DataPointer = (IntPtr)pCookedUrl->pFullUrl; pEventData[1].Size = checked(pCookedUrl->FullUrlLength + sizeof(char)); FillInEventData(&pEventData[2], &requestCorrelationId); WriteEventCore((int)Events.RequestStarted, EVENTDATA_COUNT, pEventData); } } // Event signals that ASP.NET has started processing a request. // Overload used only for deducing ETW parameters; use the public entry point instead. // // Visual Studio Online #222067 - This event is hardcoded to opt-out of EventSource activityID tracking. // This would normally be done by setting ActivityOptions = EventActivityOptions.Disable in the // Event attribute, but this causes a dependency between System.Web and mscorlib that breaks servicing. // // !! WARNING !! // The logic in RequestStartedImpl must be kept in sync with these parameters, otherwise // type safety violations could occur. [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "ETW looks at this method using reflection.")] [Event((int)Events.RequestStarted, Level = EventLevel.Informational, Task = (EventTask)Tasks.Request, Opcode = EventOpcode.Start, Version = 1)] private unsafe void RequestStarted(string HttpVerb, string FullUrl, Guid RequestCorrelationId) { throw new NotImplementedException(); } // Event signals that ASP.NET has completed processing a request. // // Visual Studio Online #222067 - This event is hardcoded to opt-out of EventSource activityID tracking. // This would normally be done by setting ActivityOptions = EventActivityOptions.Disable in the // Event attribute, but this causes a dependency between System.Web and mscorlib that breaks servicing. [Event((int)Events.RequestCompleted, Level = EventLevel.Informational, Task = (EventTask)Tasks.Request, Opcode = EventOpcode.Stop, Version = 1)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RequestCompleted() { if (!IsEnabled()) { return; } WriteEvent((int)Events.RequestCompleted); } /* * Helpers to populate the EventData structure */ // prerequisite: str must be pinned and provided as pStr; may be null. // we'll convert null strings to empty strings if necessary. [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe static void FillInEventData(EventData* pEventData, string str, char* pStr) { #if DBG fixed (char* pStr2 = str) { Debug.Assert(pStr == pStr2); } #endif if (pStr != null) { pEventData->DataPointer = (IntPtr)pStr; pEventData->Size = checked((str.Length + 1) * sizeof(char)); // size is specified in bytes, including null wide char } else { pEventData->DataPointer = NullHelper.Instance.PtrToNullChar; // empty string pEventData->Size = sizeof(char); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe static void FillInEventData(EventData* pEventData, Guid* pGuid) { Debug.Assert(pGuid != null); pEventData->DataPointer = (IntPtr)pGuid; pEventData->Size = sizeof(Guid); } // Each ETW event should have its own entry here. private enum Events { RequestEnteredAspNetPipeline = 1, RequestStarted, RequestCompleted } // Tasks are used for correlating events; we're free to define our own. // For example, Tasks.Request with Opcode = Start matches Tasks.Request with Opcode = Stop, // and Tasks.Application with Opcode = Start matches Tasks.Application with Opcode = Stop. // // EventSource requires that this be a public static class with public const fields, // otherwise manifest generation could fail at runtime. public static class Tasks { public const EventTask Request = (EventTask)1; } private sealed class NullHelper : CriticalFinalizerObject { public static readonly NullHelper Instance = new NullHelper(); [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = @"Containing type is a CriticalFinalizerObject.")] public readonly IntPtr PtrToNullChar; [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] private unsafe NullHelper() { // allocate a single null character PtrToNullChar = Marshal.AllocHGlobal(sizeof(char)); *((char*)PtrToNullChar) = '\0'; } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] ~NullHelper() { if (PtrToNullChar != IntPtr.Zero) { Marshal.FreeHGlobal(PtrToNullChar); } } } } }