Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
<MicrosoftDiaSymReaderVersion>2.0.0</MicrosoftDiaSymReaderVersion>
<MicrosoftDiaSymReaderNativeVersion>17.10.0-beta1.24272.1</MicrosoftDiaSymReaderNativeVersion>
<TraceEventVersion>3.1.16</TraceEventVersion>
<MicrosoftDiagnosticsNetCoreClientVersion>0.2.621003</MicrosoftDiagnosticsNetCoreClientVersion>
<NETStandardLibraryRefVersion>2.1.0</NETStandardLibraryRefVersion>
<NetStandardLibraryVersion>2.0.3</NetStandardLibraryVersion>
<MicrosoftDiagnosticsToolsRuntimeClientVersion>1.0.4-preview6.19326.1</MicrosoftDiagnosticsToolsRuntimeClientVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
{
Expand Down Expand Up @@ -111,6 +134,8 @@ public override void Dispose()
}
}

public override string ToString() => "EtwListener";

#region private
private void OnEventHelper(TraceEvent data)
{
Expand All @@ -136,7 +161,8 @@ private void OnEventHelper(TraceEvent data)
/// </summary>
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)
Expand All @@ -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

}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Implementation of Listener for EventPipe (in-process collection using DiagnosticsClient + EventPipeEventSource).
/// </summary>
internal sealed class EventPipeListener : Listener
{
private readonly List<(string eventSourceName, EventCommand command, FilteringOptions options)> _pendingCommands = new();
private readonly Dictionary<string, FilteringOptions> _enabled = new(StringComparer.Ordinal);
private EventPipeSession _session;
private Task _processingTask;
private bool _disposed;

public override bool IsDynamicConfigChangeSupported => false;

/// <summary>
/// EventPipe NetTrace V5 format can't emit the metadata for a Boolean8 HasValue field that self-describing events use.
/// </summary>
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<EventPipeProvider>();
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";

/// <summary>
/// Wrapper mapping TraceEvent (EventPipe) to harness Event abstraction.
/// </summary>
private sealed class EventPipeEvent : Event
{
private readonly TraceEvent _data;
private readonly IList<string> _payloadNames;
private readonly IList<object> _payloadValues;

public EventPipeEvent(TraceEvent data)
{
_data = data;
// EventPipe has a discrepancy with ETW for self-describing events - it exposes a single top-level object whereas ETW considers each of the fields within
// that object as top-level named fields. To workaround that we unwrap any top-level object at payload index 0.
if(data.PayloadNames.Length > 0 && data.PayloadValue(0) is IDictionary<string,object> d)
{
_payloadNames = d.Select(kv => kv.Key).ToList();
_payloadValues = d.Select(kv => kv.Value).ToList();
}
else
{
_payloadNames = data.PayloadNames;
_payloadValues = new List<object>();
for(int i = 0; i < _payloadNames.Count; i++)
{
_payloadValues.Add(data.PayloadValue(i));
}
}
}

public override string ProviderName => _data.ProviderName;
public override string EventName => _data.EventName;
public override int PayloadCount => _payloadNames.Count;
public override IList<string> PayloadNames => _payloadNames;

public override object PayloadValue(int propertyIndex, string propertyName)
{
if (propertyName != null)
{
Assert.Equal(propertyName, _payloadNames[propertyIndex]);
}
return _payloadValues[propertyIndex];
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,14 @@ public static void RunTests(List<SubTest> tests, Listener listener, EventSource
using (TestHarnessEventSource testHarnessEventSource = new TestHarnessEventSource())
{
// Turn on the test EventSource.
listener.EventSourceSynchronousEnable(source, options);
listener.EventSourceCommand(source.Name, EventCommand.Enable, options);
// And the harnesses's EventSource.
listener.EventSourceSynchronousEnable(testHarnessEventSource);
listener.EventSourceCommand(testHarnessEventSource.Name, EventCommand.Enable);

// Start the session and wait for the sources to be enabled.
listener.Start();
listener.WaitForEventSourceStateChange(source, true);
listener.WaitForEventSourceStateChange(testHarnessEventSource, true);

// Generate events for all the tests, surrounded by events that tell us we are starting a test.
int testNumber = 0;
Expand All @@ -126,12 +131,16 @@ public static void RunTests(List<SubTest> tests, Listener listener, EventSource
}
testHarnessEventSource.StartTest("", testNumber); // Empty test marks the end of testing.

// Disable the listeners.
listener.EventSourceCommand(source.Name, EventCommand.Disable);
listener.EventSourceCommand(testHarnessEventSource.Name, EventCommand.Disable);

if (listener.IsDynamicConfigChangeSupported)
{
// Disable the listeners.
listener.EventSourceSynchronousDisable(source);
listener.EventSourceSynchronousDisable(testHarnessEventSource);

// Send something that should be ignored.
testHarnessEventSource.IgnoreEvent();
// Send something that should be ignored.
testHarnessEventSource.IgnoreEvent();
}
}
}
catch (Exception e)
Expand Down Expand Up @@ -164,8 +173,8 @@ public static void RunTests(List<SubTest> tests, Listener listener, EventSource

listener.Dispose(); // Indicate we are done listening. For the ETW file based cases, we do all the processing here

// expectedTetst number are the number of tests we successfully ran.
Assert.Equal(expectedTestNumber, tests.Count);
int actualTestsRun = expectedTestNumber;
Assert.Equal(tests.Count, actualTestsRun);
}

public class EventTestHarnessException : Exception
Expand Down
Loading
Loading