|
//------------------------------------------------------------------------------
// <copyright file="AspNetEventSource.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
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);
}
}
}
}
}
|