1 //------------------------------------------------------------------------------
2 // <copyright file="AspNetEventSource.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
9 using System.Diagnostics.CodeAnalysis;
10 using System.Diagnostics.Tracing;
11 using System.Reflection;
12 using System.Runtime.CompilerServices;
13 using System.Runtime.ConstrainedExecution;
14 using System.Runtime.InteropServices;
15 using System.Web.Hosting;
16 using System.Web.Util;
18 // Name and Guid are part of the public contract (for identification by ETW listeners) so cannot
19 // be changed. We're statically specifying a GUID using the same logic as EventSource.GetGuid,
20 // as otherwise EventSource invokes crypto to generate the GUID and this results in an
21 // unacceptable performance degradation (DevDiv #652801).
22 [EventSource(Name = "Microsoft-Windows-ASPNET", Guid = "ee799f41-cfa5-550b-bf2c-344747c1c668")]
23 internal sealed class AspNetEventSource : EventSource {
26 public static readonly AspNetEventSource Instance = new AspNetEventSource();
28 private unsafe delegate void WriteEventWithRelatedActivityIdCoreDelegate(int eventId, Guid* childActivityID, int eventDataCount, EventData* data);
29 private readonly WriteEventWithRelatedActivityIdCoreDelegate _writeEventWithRelatedActivityIdCoreDel;
31 private AspNetEventSource() {
32 // We need to light up when running on .NET 4.5.1 since we can't compile directly
33 // against the protected methods we might need to consume. Only ever try creating
34 // this delegate if we're in full trust, otherwise exceptions could happen at
35 // inopportune times (such as during invocation).
37 if (AppDomain.CurrentDomain.IsHomogenous && AppDomain.CurrentDomain.IsFullyTrusted) {
38 MethodInfo writeEventWithRelatedActivityIdCoreMethod = typeof(EventSource).GetMethod(
39 "WriteEventWithRelatedActivityIdCore", BindingFlags.Instance | BindingFlags.NonPublic, null,
40 new Type[] { typeof(int), typeof(Guid*), typeof(int), typeof(EventData*) }, null);
42 if (writeEventWithRelatedActivityIdCoreMethod != null) {
43 _writeEventWithRelatedActivityIdCoreDel = (WriteEventWithRelatedActivityIdCoreDelegate)Delegate.CreateDelegate(
44 typeof(WriteEventWithRelatedActivityIdCoreDelegate), this, writeEventWithRelatedActivityIdCoreMethod, throwOnBindFailure: false);
49 [NonEvent] // use the private member signature for deducing ETW parameters
50 [MethodImpl(MethodImplOptions.AggressiveInlining)]
51 public void RequestEnteredAspNetPipeline(IIS7WorkerRequest wr, Guid childActivityId) {
56 Guid parentActivityId = wr.RequestTraceIdentifier;
57 RequestEnteredAspNetPipelineImpl(parentActivityId, childActivityId);
60 [NonEvent] // use the private member signature for deducing ETW parameters
61 private unsafe void RequestEnteredAspNetPipelineImpl(Guid iisActivityId, Guid aspNetActivityId) {
62 if (ActivityIdHelper.Instance == null || _writeEventWithRelatedActivityIdCoreDel == null || iisActivityId == Guid.Empty) {
66 // IIS doesn't always set the current thread's activity ID before invoking user code. Instead,
67 // its tracing APIs (IHttpTraceContext::RaiseTraceEvent) set the ID, write to ETW, then reset
68 // the ID. If we want to write a transfer event but the current thread's activity ID is
69 // incorrect, then we need to mimic this behavior. We don't use a try / finally since
70 // exceptions here are fatal to the process.
72 Guid originalThreadActivityId = ActivityIdHelper.Instance.CurrentThreadActivityId;
73 bool needToSetThreadActivityId = (originalThreadActivityId != iisActivityId);
75 // Step 1: Set the ID (if necessary)
76 if (needToSetThreadActivityId) {
77 ActivityIdHelper.Instance.SetCurrentThreadActivityId(iisActivityId, out originalThreadActivityId);
80 // Step 2: Write to ETW, providing the recipient activity ID.
81 _writeEventWithRelatedActivityIdCoreDel((int)Events.RequestEnteredAspNetPipeline, &aspNetActivityId, 0, null);
83 // Step 3: Reset the ID (if necessary)
84 if (needToSetThreadActivityId) {
86 ActivityIdHelper.Instance.SetCurrentThreadActivityId(originalThreadActivityId, out unused);
90 // Transfer event signals that control has transitioned from IIS -> ASP.NET.
91 // Overload used only for deducing ETW parameters; use the public entry point instead.
94 // The logic in RequestEnteredAspNetPipelineImpl must be kept in sync with these parameters, otherwise
95 // type safety violations could occur.
96 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "ETW looks at this method using reflection.")]
97 [Event((int)Events.RequestEnteredAspNetPipeline, Level = EventLevel.Informational, Task = (EventTask)Tasks.Request, Opcode = EventOpcode.Send, Version = 1)]
98 private void RequestEnteredAspNetPipeline() {
99 throw new NotImplementedException();
102 [NonEvent] // use the private member signature for deducing ETW parameters
103 [MethodImpl(MethodImplOptions.AggressiveInlining)]
104 public unsafe void RequestStarted(IIS7WorkerRequest wr) {
109 RequestStartedImpl(wr);
112 [NonEvent] // use the private member signature for deducing ETW parameters
113 private unsafe void RequestStartedImpl(IIS7WorkerRequest wr) {
114 string httpVerb = wr.GetHttpVerbName();
115 HTTP_COOKED_URL* pCookedUrl = wr.GetCookedUrl();
116 Guid iisEtwActivityId = wr.RequestTraceIdentifier;
117 Guid requestCorrelationId = wr.GetRequestCorrelationId();
119 fixed (char* pHttpVerb = httpVerb) {
121 // This logic must be kept in sync with the ETW-deduced parameters in RequestStarted,
122 // otherwise type safety violations could occur.
123 const int EVENTDATA_COUNT = 3;
124 EventData* pEventData = stackalloc EventData[EVENTDATA_COUNT];
126 FillInEventData(&pEventData[0], httpVerb, pHttpVerb);
128 // We have knowledge that pFullUrl is null-terminated so we can optimize away
129 // the copy we'd otherwise have to perform. Still need to adjust the length
130 // to account for the null terminator, though.
131 Debug.Assert(pCookedUrl->pFullUrl != null);
132 pEventData[1].DataPointer = (IntPtr)pCookedUrl->pFullUrl;
133 pEventData[1].Size = checked(pCookedUrl->FullUrlLength + sizeof(char));
135 FillInEventData(&pEventData[2], &requestCorrelationId);
136 WriteEventCore((int)Events.RequestStarted, EVENTDATA_COUNT, pEventData);
140 // Event signals that ASP.NET has started processing a request.
141 // Overload used only for deducing ETW parameters; use the public entry point instead.
143 // Visual Studio Online #222067 - This event is hardcoded to opt-out of EventSource activityID tracking.
144 // This would normally be done by setting ActivityOptions = EventActivityOptions.Disable in the
145 // Event attribute, but this causes a dependency between System.Web and mscorlib that breaks servicing.
148 // The logic in RequestStartedImpl must be kept in sync with these parameters, otherwise
149 // type safety violations could occur.
150 [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "ETW looks at this method using reflection.")]
151 [Event((int)Events.RequestStarted, Level = EventLevel.Informational, Task = (EventTask)Tasks.Request, Opcode = EventOpcode.Start, Version = 1)]
152 private unsafe void RequestStarted(string HttpVerb, string FullUrl, Guid RequestCorrelationId) {
153 throw new NotImplementedException();
156 // Event signals that ASP.NET has completed processing a request.
158 // Visual Studio Online #222067 - This event is hardcoded to opt-out of EventSource activityID tracking.
159 // This would normally be done by setting ActivityOptions = EventActivityOptions.Disable in the
160 // Event attribute, but this causes a dependency between System.Web and mscorlib that breaks servicing.
161 [Event((int)Events.RequestCompleted, Level = EventLevel.Informational, Task = (EventTask)Tasks.Request, Opcode = EventOpcode.Stop, Version = 1)]
162 [MethodImpl(MethodImplOptions.AggressiveInlining)]
163 public void RequestCompleted() {
168 WriteEvent((int)Events.RequestCompleted);
172 * Helpers to populate the EventData structure
175 // prerequisite: str must be pinned and provided as pStr; may be null.
176 // we'll convert null strings to empty strings if necessary.
177 [MethodImpl(MethodImplOptions.AggressiveInlining)]
178 private unsafe static void FillInEventData(EventData* pEventData, string str, char* pStr) {
180 fixed (char* pStr2 = str) { Debug.Assert(pStr == pStr2); }
184 pEventData->DataPointer = (IntPtr)pStr;
185 pEventData->Size = checked((str.Length + 1) * sizeof(char)); // size is specified in bytes, including null wide char
188 pEventData->DataPointer = NullHelper.Instance.PtrToNullChar; // empty string
189 pEventData->Size = sizeof(char);
193 [MethodImpl(MethodImplOptions.AggressiveInlining)]
194 private unsafe static void FillInEventData(EventData* pEventData, Guid* pGuid) {
195 Debug.Assert(pGuid != null);
196 pEventData->DataPointer = (IntPtr)pGuid;
197 pEventData->Size = sizeof(Guid);
200 // Each ETW event should have its own entry here.
201 private enum Events {
202 RequestEnteredAspNetPipeline = 1,
207 // Tasks are used for correlating events; we're free to define our own.
208 // For example, Tasks.Request with Opcode = Start matches Tasks.Request with Opcode = Stop,
209 // and Tasks.Application with Opcode = Start matches Tasks.Application with Opcode = Stop.
211 // EventSource requires that this be a public static class with public const fields,
212 // otherwise manifest generation could fail at runtime.
213 public static class Tasks {
214 public const EventTask Request = (EventTask)1;
217 private sealed class NullHelper : CriticalFinalizerObject {
218 public static readonly NullHelper Instance = new NullHelper();
220 [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = @"Containing type is a CriticalFinalizerObject.")]
221 public readonly IntPtr PtrToNullChar;
223 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
224 private unsafe NullHelper() {
225 // allocate a single null character
226 PtrToNullChar = Marshal.AllocHGlobal(sizeof(char));
227 *((char*)PtrToNullChar) = '\0';
230 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
232 if (PtrToNullChar != IntPtr.Zero) {
233 Marshal.FreeHGlobal(PtrToNullChar);