Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reminded me that I added a modified copy of Microsoft.Diagnostics.NETCore.Client in the runtime to get the EventPipe tests at src/tests/tracing/eventpipe working for Android #64358. Were there any changes on the diagnostics side that are missing in runtime's local copy that EventPipeListener needs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't run into anything but I also didn't run the tests on Android during my local testing. If loopback DiagnosticsClient doesn't work there I'd probably just turn the tests off on that platform. The goal of the new testing was to validate the serialization works and all the serialization code should be identical regardless of platform/OS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it's reasonable to still reference the runtime's copy src/tests/tracing/eventpipe/common/Microsoft.Diagnostics.NETCore.Client in System.Diagnostics.Tracing.Tests.csproj. I wasn't particularly thinking about the test running on Android, more that #64358 removed this property so all tests would use the runtime copy. Reintroducing this property means there's some places that uses the runtimes copy and other places that uses the diagnostics copy.

I'm fine if we want to do that, I just recall that one of the previous debts was that the runtime copy would be bumped if there was new logic in the diagnostics copy, so I was just curious if there was something in 0.2.621003 that the local copy was missing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't need to run the serialization tests on all platforms and we mainly need the runtime version of the NETCore.Client on mobile platforms using TCP/IP, then maybe we should keep using runtime version for mobile platforms and turn of the serialization tests on those platforms. Alternative is to bring over whats missing into the runtime version of NETCore.Client (and probably turn off the serialization tests on mobile if it depends on spawning additional processes).

<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,160 @@
// 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;
_disposed = true;
_session?.Stop();
_session?.Dispose();

if (_processingTask != null && !_processingTask.Wait(TimeSpan.FromSeconds(5)))
{
Debug.WriteLine("EventPipeEventListener processing task failed to stop in a timely manner.");
}
}

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