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:
Doug Cook 2022-04-23 12:30:17 -07:00 коммит произвёл GitHub
Родитель cb544f186f
Коммит b2372d9c1d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 2522 добавлений и 0 удалений

Просмотреть файл

@ -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>