PowerShell/src/System.Management.Automation/utils/tracing/EtwActivity.cs
xtqqczze 883ca98dd7
Seal private classes (#15725)
* Seal private classes

* Fix CS0509

* Fix CS0628
2021-07-19 14:09:12 +05:00

546 lines
18 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#if !UNIX
using System.Collections.Generic;
using System.Diagnostics.Eventing;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
namespace System.Management.Automation.Tracing
{
/// <summary>
/// Attribute to represent an EtwEvent.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
public sealed class EtwEvent : Attribute
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="eventId"></param>
public EtwEvent(long eventId)
{
this.EventId = eventId;
}
/// <summary>
/// EventId.
/// </summary>
public long EventId { get; }
}
/// <summary>
/// Delegates that defines a call back with no parameter.
/// </summary>
public delegate void CallbackNoParameter();
/// <summary>
/// Delegates that defines a call back with one parameter (state)
/// </summary>
public delegate void CallbackWithState(object state);
/// <summary>
/// Delegates that defines a call back with two parameters; state and ElapsedEventArgs.
/// It will be used in System.Timers.Timer scenarios.
/// </summary>
public delegate void CallbackWithStateAndArgs(object state, System.Timers.ElapsedEventArgs args);
/// <summary>
/// ETW events argument class.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
public class EtwEventArgs : EventArgs
{
/// <summary> Gets Event descriptor </summary>
public EventDescriptor Descriptor
{
get;
private set;
}
/// <summary> Gets whether the event is successfully written </summary>
public bool Success { get; }
/// <summary> Gets payload in the event </summary>
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public object[] Payload { get; }
/// <summary>
/// Creates a new instance of EtwEventArgs class.
/// </summary>
/// <param name="descriptor">Event descriptor.</param>
/// <param name="success">Indicate whether the event is successfully written.</param>
/// <param name="payload">Event payload.</param>
public EtwEventArgs(EventDescriptor descriptor, bool success, object[] payload)
{
this.Descriptor = descriptor;
this.Payload = payload;
this.Success = success;
}
}
/// <summary>
/// This the abstract base class of all activity classes that represent an end-to-end scenario.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
public abstract class EtwActivity
{
/// <summary>
/// This is a helper class that is used to wrap many multi-threading scenarios
/// and makes correlation event to be logged easily.
/// </summary>
private sealed class CorrelatedCallback
{
private readonly CallbackNoParameter callbackNoParam;
private readonly CallbackWithState callbackWithState;
private readonly AsyncCallback asyncCallback;
/// <summary>
/// ParentActivityId.
/// </summary>
private readonly Guid parentActivityId;
private readonly EtwActivity tracer;
/// <summary>
/// EtwCorrelator Constructor.
/// </summary>
/// <param name="tracer"></param>
/// <param name="callback"></param>
public CorrelatedCallback(EtwActivity tracer, CallbackNoParameter callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
if (tracer == null)
{
throw new ArgumentNullException(nameof(tracer));
}
this.tracer = tracer;
this.parentActivityId = EtwActivity.GetActivityId();
this.callbackNoParam = callback;
}
/// <summary>
/// EtwCorrelator Constructor.
/// </summary>
/// <param name="tracer"></param>
/// <param name="callback"></param>
public CorrelatedCallback(EtwActivity tracer, CallbackWithState callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
if (tracer == null)
{
throw new ArgumentNullException(nameof(tracer));
}
this.tracer = tracer;
this.parentActivityId = EtwActivity.GetActivityId();
this.callbackWithState = callback;
}
/// <summary>
/// EtwCorrelator Constructor.
/// </summary>
/// <param name="tracer"></param>
/// <param name="callback"></param>
public CorrelatedCallback(EtwActivity tracer, AsyncCallback callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
if (tracer == null)
{
throw new ArgumentNullException(nameof(tracer));
}
this.tracer = tracer;
this.parentActivityId = EtwActivity.GetActivityId();
this.asyncCallback = callback;
}
/// <summary>
/// It is to be used in System.Timers.Timer scenarios.
/// </summary>
private readonly CallbackWithStateAndArgs callbackWithStateAndArgs;
/// <summary>
/// EtwCorrelator Constructor.
/// </summary>
/// <param name="tracer"></param>
/// <param name="callback"></param>
public CorrelatedCallback(EtwActivity tracer, CallbackWithStateAndArgs callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
if (tracer == null)
{
throw new ArgumentNullException(nameof(tracer));
}
this.tracer = tracer;
this.parentActivityId = EtwActivity.GetActivityId();
this.callbackWithStateAndArgs = callback;
}
/// <summary>
/// This is the wrapper on the actual callback.
/// </summary>
public void Callback(object state, System.Timers.ElapsedEventArgs args)
{
Debug.Assert(callbackWithStateAndArgs != null, "callback is NULL. There MUST always ba a valid callback!");
Correlate();
this.callbackWithStateAndArgs(state, args);
}
/// <summary>
/// Correlate.
/// </summary>
private void Correlate()
{
tracer.CorrelateWithActivity(this.parentActivityId);
}
/// <summary>
/// This is the wrapper on the actual callback.
/// </summary>
public void Callback()
{
Debug.Assert(callbackNoParam != null, "callback is NULL. There MUST always ba a valid callback");
Correlate();
this.callbackNoParam();
}
/// <summary>
/// This is the wrapper on the actual callback.
/// </summary>
public void Callback(object state)
{
Debug.Assert(callbackWithState != null, "callback is NULL. There MUST always ba a valid callback!");
Correlate();
this.callbackWithState(state);
}
/// <summary>
/// This is the wrapper on the actual callback.
/// </summary>
public void Callback(IAsyncResult asyncResult)
{
Debug.Assert(asyncCallback != null, "callback is NULL. There MUST always ba a valid callback!");
Correlate();
this.asyncCallback(asyncResult);
}
}
private static readonly Dictionary<Guid, EventProvider> providers = new Dictionary<Guid, EventProvider>();
private static readonly object syncLock = new object();
private static readonly EventDescriptor _WriteTransferEvent = new EventDescriptor(0x1f05, 0x1, 0x11, 0x5, 0x14, 0x0, (long)0x4000000000000000);
private EventProvider currentProvider;
/// <summary>
/// Event handler for the class.
/// </summary>
public static event EventHandler<EtwEventArgs> EventWritten;
/// <summary>
/// Sets the activityId provided in the current thread.
/// If current thread already has the same activityId it does
/// nothing.
/// </summary>
/// <param name="activityId"></param>
/// <returns>True when provided activity was set, false if current activity
/// was found to be same and set was not needed.</returns>
public static bool SetActivityId(Guid activityId)
{
if (GetActivityId() != activityId)
{
EventProvider.SetActivityId(ref activityId);
return true;
}
return false;
}
/// <summary>
/// Creates a new ActivityId that can be used to set in the thread's context.
/// </summary>
/// <returns></returns>
public static Guid CreateActivityId()
{
return EventProvider.CreateActivityId();
}
/// <summary>
/// Returns the ActivityId set in current thread.
/// </summary>
/// <returns></returns>
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults")]
public static Guid GetActivityId()
{
Guid activityId = Guid.Empty;
uint hresult = UnsafeNativeMethods.EventActivityIdControl(UnsafeNativeMethods.ActivityControlCode.Get, ref activityId);
return activityId;
}
/// <summary>
/// Constructor.
/// </summary>
protected EtwActivity()
{
}
/// <summary>
/// CorrelateWithActivity (EventId: 0x1f05/7941)
/// This method also sets a new activity id in current thread.
/// And then correlates the new id with parentActivityId.
/// </summary>
public void CorrelateWithActivity(Guid parentActivityId)
{
EventProvider provider = GetProvider();
if (!provider.IsEnabled())
return;
Guid activityId = CreateActivityId();
SetActivityId(activityId);
if (parentActivityId != Guid.Empty)
{
EventDescriptor transferEvent = TransferEvent;
provider.WriteTransferEvent(in transferEvent, parentActivityId, activityId, parentActivityId);
}
}
/// <summary>
/// IsEnabled.
/// </summary>
public bool IsEnabled
{
get
{
return GetProvider().IsEnabled();
}
}
/// <summary>
/// Checks whether a provider matching certain levels and keyword is enabled.
/// </summary>
/// <param name="levels">Levels to check.</param>
/// <param name="keywords">Keywords to check.</param>
/// <returns>True, if any ETW listener is enabled else false.</returns>
public bool IsProviderEnabled(byte levels, long keywords)
{
return GetProvider().IsEnabled(levels, keywords);
}
/// <summary>
/// Correlates parent activity id set in the thread with a new activity id
/// If parent activity id is not, it just sets a new activity in the current thread. And does not write the Transfer event.
/// </summary>
public void Correlate()
{
Guid parentActivity = GetActivityId();
CorrelateWithActivity(parentActivity);
}
/// <summary>
/// Wraps a callback with no params.
/// </summary>
/// <param name="callback"></param>
/// <returns></returns>
public CallbackNoParameter Correlate(CallbackNoParameter callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
return new CorrelatedCallback(this, callback).Callback;
}
/// <summary>
/// Wraps a callback with one object param.
/// </summary>
/// <param name="callback"></param>
/// <returns></returns>
public CallbackWithState Correlate(CallbackWithState callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
return new CorrelatedCallback(this, callback).Callback;
}
/// <summary>
/// Wraps a AsyncCallback with IAsyncResult param.
/// </summary>
/// <param name="callback"></param>
/// <returns></returns>
public AsyncCallback Correlate(AsyncCallback callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
return new CorrelatedCallback(this, callback).Callback;
}
/// <summary>
/// Wraps a callback with one object param and one ElapsedEventArgs object
/// This is menat to be used in System.Timers.Timer scenarios.
/// </summary>
/// <param name="callback"></param>
/// <returns></returns>
public CallbackWithStateAndArgs Correlate(CallbackWithStateAndArgs callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
return new CorrelatedCallback(this, callback).Callback;
}
/// <summary>
/// The provider where the tracing messages will be written to.
/// </summary>
protected virtual Guid ProviderId
{
get
{
return PSEtwLogProvider.ProviderGuid;
}
}
/// <summary>
/// The event that is defined to be used to log transfer event.
/// The derived class must override this property if they don't
/// want to use the PowerShell's transfer event.
/// </summary>
protected virtual EventDescriptor TransferEvent
{
get
{
return _WriteTransferEvent;
}
}
/// <summary>
/// This is the main method that write the messages to the trace.
/// All derived classes must use this method to write to the provider log.
/// </summary>
/// <param name="ed">EventDescriptor.</param>
/// <param name="payload">Payload.</param>
protected void WriteEvent(EventDescriptor ed, params object[] payload)
{
EventProvider provider = GetProvider();
if (!provider.IsEnabled())
return;
if (payload != null)
{
for (int i = 0; i < payload.Length; i++)
{
if (payload[i] == null)
{
payload[i] = string.Empty;
}
}
}
bool success = provider.WriteEvent(in ed, payload);
if (EventWritten != null)
{
EventWritten.Invoke(this, new EtwEventArgs(ed, success, payload));
}
}
private EventProvider GetProvider()
{
if (currentProvider != null)
return currentProvider;
lock (syncLock)
{
if (currentProvider != null)
return currentProvider;
if (!providers.TryGetValue(ProviderId, out currentProvider))
{
currentProvider = new EventProvider(ProviderId);
providers[ProviderId] = currentProvider;
}
}
return currentProvider;
}
private static class UnsafeNativeMethods
{
internal enum ActivityControlCode : uint
{
/// <summary>
/// Gets the ActivityId from thread local storage.
/// </summary>
Get = 1,
/// <summary>
/// Sets the ActivityId in the thread local storage.
/// </summary>
Set = 2,
/// <summary>
/// Creates a new activity id.
/// </summary>
Create = 3,
/// <summary>
/// Sets the activity id in thread local storage and returns the previous value.
/// </summary>
GetSet = 4,
/// <summary>
/// Creates a new activity id, sets thread local storage, and returns the previous value.
/// </summary>
CreateSet = 5
}
/// <summary>
/// Provides interop access to creating, querying and setting the current activity identifier.
/// </summary>
/// <param name="controlCode">The <see cref="ActivityControlCode"/> indicating the type of operation to perform.</param>
/// <param name="activityId">The activity id to set or retrieve.</param>
/// <returns>Zero on success.</returns>
[DllImport(PinvokeDllNames.EventActivityIdControlDllName, ExactSpelling = true, EntryPoint = "EventActivityIdControl", CharSet = CharSet.Unicode)]
internal static extern unsafe uint EventActivityIdControl([In] ActivityControlCode controlCode, [In][Out] ref Guid activityId);
}
}
}
#endif