47ceee67bd5751795e55e6503da3441f082d94b4
[mono.git] / mcs / class / referencesource / System.Web / AspNetEventSource.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="AspNetEventSource.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 namespace System.Web {
8     using System;
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;
17
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 {
24
25         // singleton
26         public static readonly AspNetEventSource Instance = new AspNetEventSource();
27
28         private unsafe delegate void WriteEventWithRelatedActivityIdCoreDelegate(int eventId, Guid* childActivityID, int eventDataCount, EventData* data);
29         private readonly WriteEventWithRelatedActivityIdCoreDelegate _writeEventWithRelatedActivityIdCoreDel;
30
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).
36
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);
41
42                 if (writeEventWithRelatedActivityIdCoreMethod != null) {
43                     _writeEventWithRelatedActivityIdCoreDel = (WriteEventWithRelatedActivityIdCoreDelegate)Delegate.CreateDelegate(
44                         typeof(WriteEventWithRelatedActivityIdCoreDelegate), this, writeEventWithRelatedActivityIdCoreMethod, throwOnBindFailure: false);
45                 }
46             }
47         }
48
49         [NonEvent] // use the private member signature for deducing ETW parameters
50         [MethodImpl(MethodImplOptions.AggressiveInlining)]
51         public void RequestEnteredAspNetPipeline(IIS7WorkerRequest wr, Guid childActivityId) {
52             if (!IsEnabled()) {
53                 return;
54             }
55
56             Guid parentActivityId = wr.RequestTraceIdentifier;
57             RequestEnteredAspNetPipelineImpl(parentActivityId, childActivityId);
58         }
59
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) {
63                 return;
64             }
65
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.
71
72             Guid originalThreadActivityId = ActivityIdHelper.Instance.CurrentThreadActivityId;
73             bool needToSetThreadActivityId = (originalThreadActivityId != iisActivityId);
74
75             // Step 1: Set the ID (if necessary)
76             if (needToSetThreadActivityId) {
77                 ActivityIdHelper.Instance.SetCurrentThreadActivityId(iisActivityId, out originalThreadActivityId);
78             }
79
80             // Step 2: Write to ETW, providing the recipient activity ID.
81             _writeEventWithRelatedActivityIdCoreDel((int)Events.RequestEnteredAspNetPipeline, &aspNetActivityId, 0, null);
82
83             // Step 3: Reset the ID (if necessary)
84             if (needToSetThreadActivityId) {
85                 Guid unused;
86                 ActivityIdHelper.Instance.SetCurrentThreadActivityId(originalThreadActivityId, out unused);
87             }
88         }
89
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.
92         //
93         // !! WARNING !!
94         // The logic in RequestEnteredAspNetPipelineImpl must be kept in [....] 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();
100         }
101
102         [NonEvent] // use the private member signature for deducing ETW parameters
103         [MethodImpl(MethodImplOptions.AggressiveInlining)]
104         public unsafe void RequestStarted(IIS7WorkerRequest wr) {
105             if (!IsEnabled()) {
106                 return;
107             }
108
109             RequestStartedImpl(wr);
110         }
111
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();
118
119             fixed (char* pHttpVerb = httpVerb) {
120                 // !! WARNING !!
121                 // This logic must be kept in [....] 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];
125
126                 FillInEventData(&pEventData[0], httpVerb, pHttpVerb);
127
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));
134
135                 FillInEventData(&pEventData[2], &requestCorrelationId);
136                 WriteEventCore((int)Events.RequestStarted, EVENTDATA_COUNT, pEventData);
137             }
138         }
139
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.
142         //
143         // !! WARNING !!
144         // The logic in RequestStartedImpl must be kept in [....] with these parameters, otherwise
145         // type safety violations could occur.
146         [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "ETW looks at this method using reflection.")]
147         [Event((int)Events.RequestStarted, Level = EventLevel.Informational, Task = (EventTask)Tasks.Request, Opcode = EventOpcode.Start, Version = 1)]
148         private unsafe void RequestStarted(string HttpVerb, string FullUrl, Guid RequestCorrelationId) {
149             throw new NotImplementedException();
150         }
151
152         // Event signals that ASP.NET has completed processing a request.
153         [Event((int)Events.RequestCompleted, Level = EventLevel.Informational, Task = (EventTask)Tasks.Request, Opcode = EventOpcode.Stop, Version = 1)]
154         [MethodImpl(MethodImplOptions.AggressiveInlining)]
155         public void RequestCompleted() {
156             if (!IsEnabled()) {
157                 return;
158             }
159
160             WriteEvent((int)Events.RequestCompleted);
161         }
162
163         /*
164          * Helpers to populate the EventData structure
165          */
166
167         // prerequisite: str must be pinned and provided as pStr; may be null.
168         // we'll convert null strings to empty strings if necessary.
169         [MethodImpl(MethodImplOptions.AggressiveInlining)]
170         private unsafe static void FillInEventData(EventData* pEventData, string str, char* pStr) {
171 #if DBG
172             fixed (char* pStr2 = str) { Debug.Assert(pStr == pStr2); }
173 #endif
174
175             if (pStr != null) {
176                 pEventData->DataPointer = (IntPtr)pStr;
177                 pEventData->Size = checked((str.Length + 1) * sizeof(char)); // size is specified in bytes, including null wide char
178             }
179             else {
180                 pEventData->DataPointer = NullHelper.Instance.PtrToNullChar; // empty string
181                 pEventData->Size = sizeof(char);
182             }
183         }
184
185         [MethodImpl(MethodImplOptions.AggressiveInlining)]
186         private unsafe static void FillInEventData(EventData* pEventData, Guid* pGuid) {
187             Debug.Assert(pGuid != null);
188             pEventData->DataPointer = (IntPtr)pGuid;
189             pEventData->Size = sizeof(Guid);
190         }
191
192         // Each ETW event should have its own entry here.
193         private enum Events {
194             RequestEnteredAspNetPipeline = 1,
195             RequestStarted,
196             RequestCompleted
197         }
198
199         // Tasks are used for correlating events; we're free to define our own.
200         // For example, Tasks.Request with Opcode = Start matches Tasks.Request with Opcode = Stop,
201         // and Tasks.Application with Opcode = Start matches Tasks.Application with Opcode = Stop.
202         //
203         // EventSource requires that this be a public static class with public const fields,
204         // otherwise manifest generation could fail at runtime.
205         public static class Tasks {
206             public const EventTask Request = (EventTask)1;
207         }
208
209         private sealed class NullHelper : CriticalFinalizerObject {
210             public static readonly NullHelper Instance = new NullHelper();
211
212             [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = @"Containing type is a CriticalFinalizerObject.")]
213             public readonly IntPtr PtrToNullChar;
214
215             [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
216             private unsafe NullHelper() {
217                 // allocate a single null character
218                 PtrToNullChar = Marshal.AllocHGlobal(sizeof(char));
219                 *((char*)PtrToNullChar) = '\0';
220             }
221
222             [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
223             ~NullHelper() {
224                 if (PtrToNullChar != IntPtr.Zero) {
225                     Marshal.FreeHGlobal(PtrToNullChar);
226                 }
227             }
228         }
229     }
230 }