536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
239 lines
12 KiB
C#
239 lines
12 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|