diff --git a/eng/Versions.props b/eng/Versions.props
index f3d3db85315ec1..837341dde90148 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -110,6 +110,7 @@
2.0.0
17.10.0-beta1.24272.1
3.1.16
+ 0.2.621003
2.1.0
2.0.3
1.0.4-preview6.19326.1
diff --git a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/Harness/EtwListener.cs b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/Harness/EtwListener.cs
index 13750cc99b7826..179be31081f980 100644
--- a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/Harness/EtwListener.cs
+++ b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/Harness/EtwListener.cs
@@ -32,20 +32,25 @@ internal static void EnsureStopped()
public EtwListener(string dataFileName = "EventSourceTestData.etl", string sessionName = "EventSourceTestSession")
{
_dataFileName = dataFileName;
+ _sessionName = sessionName;
+ _pendingCommands = new List<(string eventSourceName, EventCommand command, FilteringOptions options)>();
// Today you have to be Admin to turn on ETW events (anyone can write ETW events).
if (TraceEventSession.IsElevated() != true)
{
throw new SkipTestException("Need to be elevated to run. ");
}
+ }
- if (dataFileName == null)
+ public override void Start()
+ {
+ if (_dataFileName == null)
{
- Debug.WriteLine("Creating a real time session " + sessionName);
+ Debug.WriteLine("Creating a real time session " + _sessionName);
Task.Factory.StartNew(delegate ()
{
- var session = new TraceEventSession(sessionName, dataFileName);
+ var session = new TraceEventSession(_sessionName, _dataFileName);
session.Source.AllEvents += OnEventHelper;
Debug.WriteLine("Listening for real time events");
_session = session; // Indicate that we are alive.
@@ -58,13 +63,31 @@ public EtwListener(string dataFileName = "EventSourceTestData.etl", string sessi
else
{
// Normalize to a full path name.
- dataFileName = Path.GetFullPath(dataFileName);
- Debug.WriteLine("Creating ETW data file " + Path.GetFullPath(dataFileName));
- _session = new TraceEventSession(sessionName, dataFileName);
+ _dataFileName = Path.GetFullPath(_dataFileName);
+ Debug.WriteLine("Creating ETW data file " + Path.GetFullPath(_dataFileName));
+ _session = new TraceEventSession(_sessionName, _dataFileName);
+ }
+ foreach(var cmd in _pendingCommands)
+ {
+ ApplyEventSourceCommand(cmd.eventSourceName, cmd.command, cmd.options);
}
}
+ public override bool IsDynamicConfigChangeSupported => true;
+
public override void EventSourceCommand(string eventSourceName, EventCommand command, FilteringOptions options = null)
+ {
+ if (_session == null)
+ {
+ _pendingCommands.Add((eventSourceName, command, options));
+ }
+ else
+ {
+ ApplyEventSourceCommand(eventSourceName, command, options);
+ }
+ }
+
+ private void ApplyEventSourceCommand(string eventSourceName, EventCommand command, FilteringOptions options = null)
{
if (command == EventCommand.Enable)
{
@@ -111,6 +134,8 @@ public override void Dispose()
}
}
+ public override string ToString() => "EtwListener";
+
#region private
private void OnEventHelper(TraceEvent data)
{
@@ -136,7 +161,8 @@ private void OnEventHelper(TraceEvent data)
///
internal class EtwEvent : Event
{
- public override bool IsEtw { get { return true; } }
+ public override bool IsEnumValueStronglyTyped(bool selfDescribing, bool isWriteEvent) => !selfDescribing;
+ public override bool IsSizeAndPointerCoallescedIntoSingleArg => true;
public override string ProviderName { get { return _data.ProviderName; } }
public override string EventName { get { return _data.EventName; } }
public override object PayloadValue(int propertyIndex, string propertyName)
@@ -162,7 +188,9 @@ public override string PayloadString(int propertyIndex, string propertyName)
private bool _disposed;
private string _dataFileName;
+ private string _sessionName;
private volatile TraceEventSession _session;
+ private List<(string eventSourceName, EventCommand command, FilteringOptions options)> _pendingCommands;
#endregion
}
diff --git a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/Harness/EventPipeListener.cs b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/Harness/EventPipeListener.cs
new file mode 100644
index 00000000000000..b1156bf116297d
--- /dev/null
+++ b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/Harness/EventPipeListener.cs
@@ -0,0 +1,179 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Diagnostics.NETCore.Client;
+using Microsoft.Diagnostics.Tracing;
+using Xunit;
+
+namespace BasicEventSourceTests
+{
+ ///
+ /// Implementation of Listener for EventPipe (in-process collection using DiagnosticsClient + EventPipeEventSource).
+ ///
+ internal sealed class EventPipeListener : Listener
+ {
+ private readonly List<(string eventSourceName, EventCommand command, FilteringOptions options)> _pendingCommands = new();
+ private readonly Dictionary _enabled = new(StringComparer.Ordinal);
+ private EventPipeSession _session;
+ private Task _processingTask;
+ private bool _disposed;
+
+ public override bool IsDynamicConfigChangeSupported => false;
+
+ ///
+ /// EventPipe NetTrace V5 format can't emit the metadata for a Boolean8 HasValue field that self-describing events use.
+ ///
+ public override bool IsSelfDescribingNullableSupported => false;
+
+ public override bool IsEventPipe => true;
+
+ public EventPipeListener() { }
+
+ public override void EventSourceCommand(string eventSourceName, EventCommand command, FilteringOptions options = null)
+ {
+ if (eventSourceName is null)
+ {
+ throw new ArgumentNullException(nameof(eventSourceName));
+ }
+
+ if (_session != null)
+ {
+ throw new InvalidOperationException("EventPipeEventListener does not support dynamic configuration changes after Start().");
+ }
+ _pendingCommands.Add((eventSourceName, command, options));
+ }
+
+ public override void Start()
+ {
+ if (_session != null)
+ {
+ return; // already started
+ }
+
+ // Build provider enable list from pending commands
+ foreach (var (eventSourceName, command, options) in _pendingCommands)
+ {
+ if (command == EventCommand.Enable)
+ {
+ var effective = options ?? new FilteringOptions();
+ _enabled[eventSourceName] = effective;
+ }
+ else if (command == EventCommand.Disable)
+ {
+ _enabled.Remove(eventSourceName);
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ var providers = new List();
+ foreach (var kvp in _enabled)
+ {
+ var opt = kvp.Value;
+ providers.Add(new EventPipeProvider(kvp.Key, (EventLevel)opt.Level, (long)opt.Keywords, opt.Args));
+ }
+
+ var client = new DiagnosticsClient(Environment.ProcessId);
+ _session = client.StartEventPipeSession(providers, false);
+
+ _processingTask = Task.Factory.StartNew(() => ProcessEvents(_session), TaskCreationOptions.LongRunning);
+ }
+
+ private void ProcessEvents(EventPipeSession session)
+ {
+ using var source = new EventPipeEventSource(session.EventStream);
+ source.Dynamic.All += traceEvent =>
+ {
+ // EventPipe adds extra events we didn't ask for, ignore them.
+ if (traceEvent.ProviderName == "Microsoft-DotNETCore-EventPipe")
+ {
+ return;
+ }
+
+ OnEvent?.Invoke(new EventPipeEvent(traceEvent));
+ };
+ source.Process();
+ }
+
+ public override void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ try
+ {
+ _disposed = true;
+ _session?.Stop();
+
+ if (_processingTask != null && !_processingTask.Wait(TimeSpan.FromSeconds(5)))
+ {
+ // If the session is still streaming data then session.Dispose() below will disconnect the stream
+ // and likely cause the thread running source.Process() to throw.
+ Assert.Fail("EventPipeEventListener processing task failed to complete in 5 seconds.");
+ }
+ }
+ finally
+ {
+ _session?.Dispose();
+ }
+ }
+
+ public override string ToString() => "EventPipeListener";
+
+ ///
+ /// Wrapper mapping TraceEvent (EventPipe) to harness Event abstraction.
+ ///
+ private sealed class EventPipeEvent : Event
+ {
+ private readonly TraceEvent _data;
+ private readonly IList _payloadNames;
+ private readonly IList