-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Update and improve EventSource documentation #7093
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
Merged
gewarren
merged 9 commits into
dotnet:main
from
josalem:dev/josalem/improve-eventsource
Sep 10, 2021
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
f94b69e
Update and improve EventSource documentation
7e97cfe
complete "..." in sample so it can compile
cbf84de
fix a couple xrefs
528c95c
Apply suggestions from code review
4e2a074
Add proj files for samples
f81b06c
Fix typo in sample
0ade23d
Fix compiler errors for snippets
77b00ce
Update sample to use new on stackalloc'd structs to ensure unset valu…
304e7e9
Use H3 instead of H2
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
samples/snippets/csharp/VS_Snippets_CLR/etwtrace/cs/etwtrace.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>netcoreapp5.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
</Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
199 changes: 199 additions & 0 deletions
199
samples/snippets/csharp/VS_Snippets_CLR/etwtracelarge/cs/program.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
//<root> | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.Tracing; | ||
|
||
namespace Demo | ||
{ | ||
//<InterfaceSource> | ||
public interface IMyLogging | ||
{ | ||
void Error(int errorCode, string message); | ||
void Warning(string message); | ||
} | ||
|
||
public sealed class MySource : EventSource, IMyLogging | ||
{ | ||
public static MySource Log = new(); | ||
|
||
[Event(1)] | ||
public void Error(int errorCode, string message) => WriteEvent(1, errorCode, message); | ||
|
||
[Event(2)] | ||
public void Warning(string message) => WriteEvent(2, message); | ||
} | ||
//</InterfaceSource> | ||
|
||
//<UtilitySource> | ||
public abstract class UtilBaseEventSource : EventSource | ||
{ | ||
protected UtilBaseEventSource() | ||
: base() | ||
{ } | ||
|
||
protected UtilBaseEventSource(bool throwOnEventWriteErrors) | ||
: base(throwOnEventWriteErrors) | ||
{ } | ||
|
||
// helper overload of WriteEvent for optimizing writing an event containing | ||
// payload properties that don't align with a provided overload. This prevents | ||
// EventSource from using the object[] overload which is expensive. | ||
protected unsafe void WriteEvent(int eventId, int arg1, short arg2, long arg3) | ||
{ | ||
if (IsEnabled()) | ||
{ | ||
EventSource.EventData* descrs = stackalloc EventSource.EventData[3]; | ||
descrs[0] = new EventData { DataPointer = (IntPtr)(&arg1), Size = 4 }; | ||
descrs[1] = new EventData { DataPointer = (IntPtr)(&arg2), Size = 2 }; | ||
descrs[2] = new EventData { DataPointer = (IntPtr)(&arg3), Size = 8 }; | ||
WriteEventCore(eventId, 3, descrs); | ||
} | ||
} | ||
} | ||
|
||
public sealed class OptimizedEventSource : UtilBaseEventSource | ||
{ | ||
public static OptimizedEventSource Log = new(); | ||
|
||
public static class Keywords | ||
{ | ||
public const EventKeywords Kwd1 = (EventKeywords)1; | ||
} | ||
|
||
[Event(1, Keywords = Keywords.Kwd1, Level = EventLevel.Informational, Message = "LogElements called {0}/{1}/{2}.")] | ||
public void LogElements(int n, short sh, long l) => WriteEvent(1, n, sh, l); // uses the overload we added! | ||
} | ||
//</UtilitySource> | ||
|
||
//<ComplexSource> | ||
public class ComplexComponent : IDisposable | ||
{ | ||
internal static Dictionary<string,string> _internalState = new(); | ||
|
||
private string _name; | ||
|
||
public ComplexComponent(string name) | ||
{ | ||
_name = name ?? throw new ArgumentNullException(nameof(name)); | ||
ComplexSource.Log.NewComponent(_name); | ||
} | ||
|
||
public void SetState(string key, string value) | ||
{ | ||
lock (_internalState) | ||
{ | ||
_internalState[key] = value; | ||
ComplexSource.Log.SetState(_name, key, value); | ||
} | ||
} | ||
|
||
private void ExpensiveWork1() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250)); | ||
private void ExpensiveWork2() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250)); | ||
private void ExpensiveWork3() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250)); | ||
private void ExpensiveWork4() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250)); | ||
|
||
public void DoWork() | ||
{ | ||
ComplexSource.Log.ExpensiveWorkStart(_name); | ||
|
||
ExpensiveWork1(); | ||
ExpensiveWork2(); | ||
ExpensiveWork3(); | ||
ExpensiveWork4(); | ||
|
||
ComplexSource.Log.ExpensiveWorkStop(_name); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
ComplexSource.Log.ComponentDisposed(_name); | ||
} | ||
} | ||
|
||
internal sealed class ComplexSource : EventSource | ||
{ | ||
public static ComplexSource Log = new(); | ||
|
||
public static class Keywords | ||
{ | ||
public const EventKeywords ComponentLifespan = (EventKeywords)1; | ||
public const EventKeywords StateChanges = (EventKeywords)(1 << 1); | ||
public const EventKeywords Performance = (EventKeywords)(1 << 2); | ||
public const EventKeywords DumpState = (EventKeywords)(1 << 3); | ||
// a utility keyword for a common combination of keywords users might enable | ||
public const EventKeywords StateTracking = ComponentLifespan & StateChanges & DumpState; | ||
} | ||
|
||
protected override void OnEventCommand(EventCommandEventArgs args) | ||
{ | ||
base.OnEventCommand(args); | ||
|
||
if (args.Command == EventCommand.Enable) | ||
{ | ||
DumpComponentState(); | ||
} | ||
} | ||
|
||
[Event(1, Keywords = Keywords.ComponentLifespan, Message = "New component with name '{0}'.")] | ||
public void NewComponent(string name) => WriteEvent(1, name); | ||
|
||
[Event(2, Keywords = Keywords.ComponentLifespan, Message = "Component with name '{0}' disposed.")] | ||
public void ComponentDisposed(string name) => WriteEvent(2, name); | ||
|
||
[Event(3, Keywords = Keywords.StateChanges)] | ||
public void SetState(string name, string key, string value) => WriteEvent(3, name, key, value); | ||
|
||
[Event(4, Keywords = Keywords.Performance)] | ||
public void ExpensiveWorkStart(string name) => WriteEvent(4, name); | ||
|
||
[Event(5, Keywords = Keywords.Performance)] | ||
public void ExpensiveWorkStop(string name) => WriteEvent(5, name); | ||
|
||
[Event(6, Keywords = Keywords.DumpState)] | ||
public void ComponentState(string key, string value) => WriteEvent(6, key, value); | ||
|
||
[NonEvent] | ||
public void DumpComponentState() | ||
{ | ||
if (IsEnabled(EventLevel.Informational, Keywords.DumpState)) | ||
{ | ||
lock (ComplexComponent._internalState) | ||
{ | ||
foreach (var (key, value) in ComplexComponent._internalState) | ||
ComponentState(key, value); | ||
} | ||
} | ||
} | ||
} | ||
//</ComplexSource> | ||
|
||
//<Main> | ||
class Program | ||
{ | ||
static void Main(string[] args) | ||
{ | ||
Console.WriteLine($"PID: {System.Diagnostics.Process.GetCurrentProcess().Id}"); | ||
|
||
long i = 0; | ||
while (true) | ||
{ | ||
using ComplexComponent c1 = new($"COMPONENT_{i++}"); | ||
using ComplexComponent c2 = new($"COMPONENT_{i++}"); | ||
using ComplexComponent c3 = new($"COMPONENT_{i++}"); | ||
using ComplexComponent c4 = new($"COMPONENT_{i++}"); | ||
|
||
c1.SetState("key1", "value1"); | ||
c2.SetState("key2", "value2"); | ||
c3.SetState("key3", "value3"); | ||
c4.SetState("key4", "value4"); | ||
|
||
c1.DoWork(); | ||
c2.DoWork(); | ||
c3.DoWork(); | ||
c4.DoWork(); | ||
} | ||
} | ||
} | ||
//</Main> | ||
} | ||
//</root> |
9 changes: 9 additions & 0 deletions
9
samples/snippets/csharp/VS_Snippets_CLR/etwtracelarge/etwtracelarge.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>netcoreapp5.0</TargetFramework> | ||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||
</PropertyGroup> | ||
|
||
</Project> |
8 changes: 8 additions & 0 deletions
8
samples/snippets/csharp/VS_Snippets_CLR/etwtracesmall/cs/etwtracesmall.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>netcoreapp5.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
</Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a safe thing to do in a sample? Here you are getting them from args only on primitives, which makes it OK. Do we have good guidance around fields of classes for things like strings and other ref types?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Event methods on
EventSource
must callWriteEvent
and their parameters must match. An exception will be thrown if the event method's parameter list doesn't match the parameter list of theWriteEvent
overload used. This code is adding an overload ofWriteEvent
that knows how to quickly write a payload ofint
,short
,long
which isn't a pre-written overload.The
EventData
struct can only contain types that are capable of being represented withEventSource
metadata:EventSource
derives its metadata from the parameter list of the event method which must match the call toWriteEvent
.In other words, you can only do this optimization with strings and primitive types. You could theoretically write something like this though:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly what I meant: should the sample show to save pointers for things that are not safe to assume pinned (strings and other things adhering to the third point of "opted in", including value types that live in the heap like boxed and embedded types). Exactly like the example you have or something like
CounterPayload
since this is an advanced scenario type of thing.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't really do this optimization with anything reference typed besides strings and you need to have the
fixed
scope surround your call toWriteEventCore
or you will run into issues.The "opted in" option is a reference to types decorated with
EventDataAttribute
which only applies to theWrite<T>
API which can't be optimized like this.