TraceLoggingDynamic for .NET (CSharp) (#31)
* TraceLoggingDynamic for .NET (CSharp) * Comments * EOL Co-authored-by: Doug Cook (WINDOWS) <dcook@windows.microsoft.com>
This commit is contained in:
Родитель
cb544f186f
Коммит
b2372d9c1d
|
@ -25,5 +25,7 @@ This project provides additional support for TraceLogging developers:
|
|||
|
||||
- [TraceLoggingDynamic for C++](TraceLoggingDynamic_CPP/README.md) provides support for generating
|
||||
runtime-dynamic events from C++ code.
|
||||
- [TraceLoggingDynamic for C#](TraceLoggingDynamic_CS/README.md) provides support for generating
|
||||
runtime-dynamic events from C# code.
|
||||
- [TraceLoggingDynamic for Python](TraceLoggingDynamic_Python/README.md) provides support for generating
|
||||
runtime-dynamic events from Python3 code.
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
# Trace Logging Dynamic
|
||||
|
||||
[This implementation](TraceLoggingDynamic.cs) of manifest-free ETW supports
|
||||
more functionality than the implementation in
|
||||
[System.Diagnostics.Tracing.EventSource](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventsource),
|
||||
but it also has higher runtime costs. This implementation is intended for use only when
|
||||
the set of events is not known at compile-time. For example,
|
||||
TraceLoggingDynamic.cs might be used to implement a library providing
|
||||
manifest-free ETW to a higher-level API that does not enforce compile-time
|
||||
event layout.
|
||||
|
||||
```cs
|
||||
// At start of program:
|
||||
static readonly EventProvider p = new EventProvider("MyProviderName");
|
||||
|
||||
// When you want to write an event:
|
||||
if (p.IsEnabled(EventLevel.Verbose, 0x1234)) // Anybody listening?
|
||||
{
|
||||
var eb = new EventBuilder(); // Or reuse an existing EventBuilder
|
||||
eb.Reset("MyEventName", EventLevel.Verbose, 0x1234);
|
||||
eb.AddInt32("MyInt32FieldName", intValue);
|
||||
eb.AddUnicodeString("MyStringFieldName", stringValue);
|
||||
p.Write(eb);
|
||||
}
|
||||
```
|
||||
|
||||
The EventProvider class encapsulates an ETW REGHANDLE, which is a handle
|
||||
through which events for a particular provider can be written. The
|
||||
EventProvider instance should generally be created at component
|
||||
initialization and one instance should be shared by all code within the
|
||||
component that needs to write events for the provider. In some cases, it
|
||||
might be used in a smaller scope, in which case it should be closed via
|
||||
Dispose().
|
||||
|
||||
The EventBuilder class is used to build an event. It stores the event name,
|
||||
field names, field types, and field values. When all of the fields have
|
||||
been added to the builder, call eventProvider.Write(eventBuilder, ...) to
|
||||
send the eventBuilder's event to ETW.
|
||||
|
||||
To reduce performance impact, you might want to skip building the event if
|
||||
there are no ETW listeners. To do this, use eventProvider.IsEnabled(...).
|
||||
It returns true if there are one or more ETW listeners that would be
|
||||
interested in an event with the specified level and keywords. You only
|
||||
need to build and write the event if IsEnabled returned true for the level
|
||||
and keyword that you will use in the event.
|
||||
|
||||
The EventBuilder object is a class, and it stores two byte[] buffers.
|
||||
This means each new EventBuilder object generates garbage. You can
|
||||
minimize garbage by reusing the same EventBuilder object for multiple
|
||||
events instead of creating a new EventBuilder for each event.
|
||||
|
||||
## Notes
|
||||
|
||||
Collect the events using Windows SDK tools like traceview or tracelog.
|
||||
Decode the events using Windows SDK tools like traceview or tracefmt.
|
||||
For example, for `EventProvider("MyCompany.MyComponent")`:
|
||||
|
||||
```powershell
|
||||
tracelog -start MyTrace -f MyTraceFile.etl -guid *MyCompany.MyComponent -level 5 -matchanykw 0xf
|
||||
<run your program>
|
||||
tracelog -stop MyTrace
|
||||
tracefmt -o MyTraceData.txt MyTraceFile.etl
|
||||
```
|
||||
|
||||
ETW events are limited in size (event size = headers + metadata + data).
|
||||
Windows will drop any event that is larger than 64KB and will drop any event
|
||||
that is larger than the buffer size of the recording session.
|
||||
|
||||
Most ETW decoding tools are unable to decode an event with more than 128
|
||||
fields.
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||
</startup>
|
||||
</configuration>
|
|
@ -0,0 +1,146 @@
|
|||
using Microsoft.TraceLoggingDynamic;
|
||||
using System.Security.Principal;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace TraceLoggingDynamicTest
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
var utf8 = Encoding.UTF8;
|
||||
|
||||
if (EventProvider.GetGuidForName("MyProvider") != new Guid("b3864c38-4273-58c5-545b-8b3608343471") ||
|
||||
EventProvider.GetGuidForName("MYPROVIDER") != new Guid("b3864c38-4273-58c5-545b-8b3608343471") ||
|
||||
EventProvider.GetGuidForName("myprovider") != new Guid("b3864c38-4273-58c5-545b-8b3608343471"))
|
||||
{
|
||||
Console.WriteLine("ERROR: Bad GetGuidForName.");
|
||||
}
|
||||
|
||||
var eb = new EventBuilder();
|
||||
using (var p = new EventProvider("TraceLoggingDynamicTest"))
|
||||
{
|
||||
Console.WriteLine("{0} enabled: {1}", p, p.IsEnabled());
|
||||
|
||||
eb.Reset("Default");
|
||||
p.Write(eb);
|
||||
|
||||
eb.Reset("4v2o6t123c0l3k11",
|
||||
new EventDescriptor { Id = 4, Version = 2, Opcode = EventOpcode.Reply, Task = 123, Channel = 0, Level = EventLevel.Warning, Keyword = 0x11 });
|
||||
p.Write(eb);
|
||||
|
||||
eb.Reset("tag0xFE00000", EventLevel.Verbose, 1, 0xFE00000);
|
||||
p.Write(eb);
|
||||
|
||||
eb.Reset("tag0xFEDC000", EventLevel.Verbose, 1, 0xFEDC000);
|
||||
p.Write(eb);
|
||||
|
||||
eb.Reset("tag0xFEDCBAF", EventLevel.Verbose, 1, 0xFEDCBAF);
|
||||
p.Write(eb);
|
||||
|
||||
eb.Reset("fieldtag");
|
||||
eb.AddUInt8("0xFE00000", 0, EventOutType.Default, 0xFE00000);
|
||||
eb.AddUInt8("0xFEDC000", 0, EventOutType.Default, 0xFEDC000);
|
||||
eb.AddUInt8("0xFEDCBAF", 0, EventOutType.Default, 0xFEDCBAF);
|
||||
p.Write(eb);
|
||||
|
||||
eb.Reset("outtypes");
|
||||
eb.AddUInt8("default100", 100);
|
||||
eb.AddUInt8("string65", 65, EventOutType.String);
|
||||
eb.AddUInt8("bool1", 1, EventOutType.Boolean);
|
||||
eb.AddUInt8("hex100", 100, EventOutType.Hex);
|
||||
p.Write(eb);
|
||||
|
||||
eb.Reset("structs");
|
||||
eb.AddUInt8("start", 0);
|
||||
eb.AddStruct("struct1", 2, 0xFE00000);
|
||||
eb.AddUInt8("nested1", 1);
|
||||
eb.AddStruct("struct2", 1);
|
||||
eb.AddUInt8("nested2", 2);
|
||||
p.Write(eb);
|
||||
|
||||
eb.Reset("zstrs-L4-kFF", EventLevel.Info, 0xff);
|
||||
eb.AddUInt8("A", 65, EventOutType.String);
|
||||
eb.AddUnicodeString("zstr16-", "");
|
||||
eb.AddUnicodeString("zstr16-a", "a");
|
||||
eb.AddUnicodeString("zstr16-0", "\0");
|
||||
eb.AddUnicodeString("zstr16-a0", "a\0");
|
||||
eb.AddUnicodeString("zstr16-0a", "\0a");
|
||||
eb.AddUnicodeString("zstr16-a0a", "a\0a");
|
||||
eb.AddAnsiString("zstr8-", utf8.GetBytes(""));
|
||||
eb.AddAnsiString("zstr8-a", utf8.GetBytes("a"));
|
||||
eb.AddAnsiString("zstr8-0", utf8.GetBytes("\0"));
|
||||
eb.AddAnsiString("zstr8-a0", utf8.GetBytes("a\0"));
|
||||
eb.AddAnsiString("zstr8-0a", utf8.GetBytes("\0a"));
|
||||
eb.AddAnsiString("zstr8-a0a", utf8.GetBytes("a\0a"));
|
||||
eb.AddUInt8("A", 65, EventOutType.String);
|
||||
p.Write(eb);
|
||||
|
||||
Validate(p, eb, "UnicodeString", eb.AddUnicodeString, eb.AddUnicodeStringArray, "zutf16");
|
||||
Validate(p, eb, "AnsiString", eb.AddAnsiString, eb.AddAnsiStringArray, utf8.GetBytes("zutf8"));
|
||||
Validate<sbyte>(p, eb, "Int8", eb.AddInt8, eb.AddInt8Array, -8);
|
||||
Validate<byte>(p, eb, "UInt8", eb.AddUInt8, eb.AddUInt8Array, 8);
|
||||
Validate<short>(p, eb, "Int16", eb.AddInt16, eb.AddInt16Array, -16);
|
||||
Validate<ushort>(p, eb, "UInt16", eb.AddUInt16, eb.AddUInt16Array, 16);
|
||||
Validate<int>(p, eb, "Int32", eb.AddInt32, eb.AddInt32Array, -32);
|
||||
Validate<uint>(p, eb, "UInt32", eb.AddUInt32, eb.AddUInt32Array, 32u);
|
||||
Validate<long>(p, eb, "Int64", eb.AddInt64, eb.AddInt64Array, -64);
|
||||
Validate<ulong>(p, eb, "UInt64", eb.AddUInt64, eb.AddUInt64Array, 64);
|
||||
Validate(p, eb, "IntPtr", eb.AddIntPtr, eb.AddIntPtrArray, (IntPtr)(-3264));
|
||||
Validate(p, eb, "UIntPtr", eb.AddUIntPtr, eb.AddUIntPtrArray, (UIntPtr)3264);
|
||||
Validate(p, eb, "Float32", eb.AddFloat32, eb.AddFloat32Array, 3.2f);
|
||||
Validate(p, eb, "Float64", eb.AddFloat64, eb.AddFloat64Array, 6.4);
|
||||
Validate(p, eb, "Bool32", eb.AddBool32, eb.AddBool32Array, 0);
|
||||
Validate(p, eb, "Bool32", eb.AddBool32, eb.AddBool32Array, 1);
|
||||
Validate(p, eb, "Binary", eb.AddBinary, eb.AddCountedBinaryArray, utf8.GetBytes("0123"));
|
||||
Validate(p, eb, "Guid", eb.AddGuid, eb.AddGuidArray, EventProvider.GetGuidForName("sample"));
|
||||
Validate(p, eb, "FileTime", eb.AddFileTime, eb.AddFileTimeArray, 0x01d7ace794497cb5);
|
||||
Validate(p, eb, "FileTime", eb.AddFileTime, eb.AddFileTimeArray, new DateTime(2022, 12, 25, 1, 2, 3, DateTimeKind.Utc));
|
||||
Validate(p, eb, "SystemTime", eb.AddSystemTime, eb.AddSystemTimeArray, new short[] { 2022, 1, 1, 2, 3, 4, 5, 6 });
|
||||
Validate(p, eb, "Sid", eb.AddSid, eb.AddSidArray, new byte[] { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 });
|
||||
Validate(p, eb, "HexInt32", eb.AddHexInt32, eb.AddHexInt32Array, unchecked((int)0xdeadbeef));
|
||||
Validate(p, eb, "HexInt32", eb.AddHexInt32, eb.AddHexInt32Array, 0xdeadbeef);
|
||||
Validate(p, eb, "HexInt64", eb.AddHexInt64, eb.AddHexInt64Array, unchecked((long)0xdeadbeeffeeef000));
|
||||
Validate(p, eb, "HexInt64", eb.AddHexInt64, eb.AddHexInt64Array, 0xdeadbeeffeeef000);
|
||||
Validate(p, eb, "HexIntPtr", eb.AddHexIntPtr, eb.AddHexIntPtrArray, (IntPtr)0x1234);
|
||||
Validate(p, eb, "HexIntPtr", eb.AddHexIntPtr, eb.AddHexIntPtrArray, (UIntPtr)0x1234);
|
||||
Validate(p, eb, "CountedString", eb.AddCountedString, eb.AddCountedStringArray, "utf16");
|
||||
Validate(p, eb, "CountedAnsiString", eb.AddCountedAnsiString, eb.AddCountedAnsiStringArray, utf8.GetBytes("utf8"));
|
||||
Validate(p, eb, "CountedBinary", eb.AddCountedBinary, eb.AddCountedBinaryArray, utf8.GetBytes("0123"));
|
||||
|
||||
var val = utf8.GetBytes("val");
|
||||
var N = p.IsEnabled() ? 10000 : 1000000;
|
||||
var timer = Stopwatch.StartNew();
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
eb.Reset("Benchmark");
|
||||
eb.AddCountedAnsiString("f1", val);
|
||||
eb.AddInt32("f2", i);
|
||||
p.Write(eb);
|
||||
}
|
||||
|
||||
timer.Stop();
|
||||
|
||||
Console.WriteLine("Benchmark event: N = {0}; events/s = {1}",
|
||||
N, N / timer.Elapsed.TotalSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Validate<T>(EventProvider p, EventBuilder eb, string name,
|
||||
Action<string, T, EventOutType, int> scalar,
|
||||
Action<string, T[], EventOutType, int> array,
|
||||
T value)
|
||||
{
|
||||
eb.Reset(name);
|
||||
eb.AddUInt8("A", 65, EventOutType.String);
|
||||
scalar("scalar", value, EventOutType.Default, 0);
|
||||
array("a0", new T[0], EventOutType.Default, 0);
|
||||
array("a1", new T[1] { value }, EventOutType.Default, 0);
|
||||
array("a2", new T[2] { value, value }, EventOutType.Default, 0);
|
||||
eb.AddUInt8("A", 65, EventOutType.String);
|
||||
p.Write(eb);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{CF15FB7A-4CA0-4E63-AA04-EB14C32C22C6}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>TraceLoggingDynamicTest</RootNamespace>
|
||||
<AssemblyName>TraceLoggingDynamicTest</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\TraceLoggingDynamic.cs">
|
||||
<Link>TraceLoggingDynamic.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\README.md">
|
||||
<Link>README.md</Link>
|
||||
</None>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
Загрузка…
Ссылка в новой задаче