Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DLR support for TraceEvent dynamic properties #630

Merged
merged 1 commit into from
Jun 13, 2018
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
26 changes: 25 additions & 1 deletion documentation/TraceEvent/TraceEventProgrammersGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,31 @@ source.Dynamic.AddCallbackForEvent("MyFirstEvent", delegate(MyFirstEventTraceDat
});
```

Where we have an event-specific type that with a `MyName` and `MyId` property. Thus even though `DynamicTraceEventParser` is sufficient to parse events from an `EventSource` with full fidelity, if you are doing more than just printing the event, it is a good idea to create a static (compile time) parser that is tailored for your `EventSource` and thus can return compile time types tailored for your event payloads. This is what the **TraceParserGen** tool is designed to do, which we will cover later.
Where we have an event-specific type that with a `MyName` and `MyId` property. There are two options for doing that:

1. You can create a static (compile time) parser that is tailored for your `EventSource` and thus can return compile time types tailored for your event payloads. This is what the **TraceParserGen** tool is designed to do, which we will cover later.

1. You can take advantage of built-in DLR integration. In C#, you do this by casting to 'dynamic':

```csharp
source.Dynamic.AddCallbackForEvent("MyFirstEvent", delegate(TraceEvent data) {
var asDynamic = (dynamic) data;
Console.WriteLine("GOT MyFirstEvent MyName={0} MyId={1}", asDynamic.MyName, asDynamic.MyId);
});
```
Note that unlike the **TraceParserGen** approach, this still has the downside of not having compile-time validation (if you misspell a property name, you won't know at compile time). Also note that the perf implications of casting to dynamic have not been measured, but it cannot be better than the `PayloadByName` approach (because that's what happens internally).

Other languages may have built-in DLR integration, such as PowerShell:

```powershell
# Get the set of all MyName values
$myProc.EventsInProcess | `
where ProviderName -eq MyProvider | `
%{ $PSItem.MyName } | `
Select -Unique
```

While the performance of using the DLR integration might not be as good as the **TraceParserGen** approach, it can be very convenient for prototyping, or just poking around in an interactive shell like PowerShell.

## Lifetime constraints on `TraceEvent` objects

Expand Down
4 changes: 3 additions & 1 deletion src/TraceEvent/TraceEvent.Tests/EventPipeParsing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ public void GuidsInMetadataAreSupported()

Assert.False(activityIdHasBeenSet); // make sure the event comes only once

if (@event.PayloadByName("NewId").Equals(ExpectedActivityId))
// Sneak in a test of the DLR support here (casting to 'dynamic'
// instead of using PayloadByName("NewId")):
if (((dynamic) @event).NewId.Equals(ExpectedActivityId))
activityIdHasBeenSet = true;
};

Expand Down
4 changes: 4 additions & 0 deletions src/TraceEvent/TraceEvent.Tests/TraceEvent.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
</Content>
</ItemGroup>

<ItemGroup>
<Reference Include="Microsoft.CSharp" />
</ItemGroup>

<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
Expand Down
30 changes: 30 additions & 0 deletions src/TraceEvent/TraceEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
#if !NETSTANDARD1_6
using System.Dynamic;
#endif
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -523,6 +526,13 @@ internal unsafe virtual Guid GetRelatedActivityID(TraceEventNativeMethods.EVENT_
/// </para>
/// </summary>
public unsafe abstract class TraceEvent
#if !NETSTANDARD1_6
// To support DLR access of dynamic payload data ("((dynamic) myEvent).MyPayloadName"),
// we derive from DynamicObject and override a couple of methods. If for some reason in
// the future we wanted to derive from a different base class, we could also accomplish
// this by implementing the IDynamicMetaObjectProvider interface instead.
: DynamicObject
#endif
{
/// <summary>
/// The GUID that uniquely identifies the Provider for this event. This can return Guid.Empty for classic (Pre-VISTA) ETW providers.
Expand Down Expand Up @@ -858,6 +868,26 @@ public bool IsClassicProvider
get { return (eventRecord->EventHeader.Flags & TraceEventNativeMethods.EVENT_HEADER_FLAG_CLASSIC_HEADER) != 0; }
}

#if !NETSTANDARD1_6
// These overloads allow integration with the DLR (Dynamic Language Runtime). That
// enables getting at payload data in a more convenient fashion, directly by name.
// In PowerShell, it "just works" (e.g. "$myEvent.MyPayload" will just work); in
// C# you can activate it by casting to 'dynamic' (e.g. "var myEvent = (dynamic)
// GetEventSomehow(); Console.WriteLine(myEvent.MyPayload);").

public override IEnumerable<string> GetDynamicMemberNames()
{
return PayloadNames;
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = this.PayloadByName(binder.Name);
return result != null;
}
#endif


// Getting at payload values.
/// <summary>
/// Returns the names of all the manifest declared field names for the event. May be empty if the manifest is not available.
Expand Down