Skip to content

Commit

Permalink
Add dotnet-trace examples on home page
Browse files Browse the repository at this point in the history
  • Loading branch information
verdie-g committed Sep 20, 2024
1 parent 06b80c8 commit ec66143
Show file tree
Hide file tree
Showing 5 changed files with 369 additions and 0 deletions.
38 changes: 38 additions & 0 deletions DotnetEventsViewer/Components/AggregatedEventsDialogContent.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@using System.Text.Json
@using EventPipe

@implements IDialogContentComponent<List<Event>>

<FluentDialogBody>
</FluentDialogBody>

@code
{
[Parameter]
public List<Event> Content { get; set; } = null!;

protected override Task OnInitializedAsync()

Check failure on line 14 in DotnetEventsViewer/Components/AggregatedEventsDialogContent.razor

View workflow job for this annotation

GitHub Actions / build

'AggregatedEventsDialogContent.OnInitializedAsync()': not all code paths return a value

Check failure on line 14 in DotnetEventsViewer/Components/AggregatedEventsDialogContent.razor

View workflow job for this annotation

GitHub Actions / build

'AggregatedEventsDialogContent.OnInitializedAsync()': not all code paths return a value
{
Dictionary<string, List<object>> mergedPayloads = [];
foreach (var evt in Content)
{
foreach (var field in evt.Payload)
{
if (!mergedPayloads.TryGetValue(field.Key, out var values))
{
mergedPayloads[field.Key] = [field.Value];
}
else
{
values.Add(field.Value);
}
}
}

Dictionary<string, List<object>> aggregatedPayloads = [];
foreach (var field in mergedPayloads)
{
Convert.ToD

Check failure on line 35 in DotnetEventsViewer/Components/AggregatedEventsDialogContent.razor

View workflow job for this annotation

GitHub Actions / build

; expected

Check failure on line 35 in DotnetEventsViewer/Components/AggregatedEventsDialogContent.razor

View workflow job for this annotation

GitHub Actions / build

'Convert' does not contain a definition for 'ToD'

Check failure on line 35 in DotnetEventsViewer/Components/AggregatedEventsDialogContent.razor

View workflow job for this annotation

GitHub Actions / build

; expected

Check failure on line 35 in DotnetEventsViewer/Components/AggregatedEventsDialogContent.razor

View workflow job for this annotation

GitHub Actions / build

'Convert' does not contain a definition for 'ToD'
}
}
}
24 changes: 24 additions & 0 deletions DotnetEventsViewer/Pages/Home.razor
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@
</ChildContent>
</FluentInputFile>

<h2 style="margin-top: 8px">Collect events with dotnet-trace</h2>

<p>
Here are a few examples about how to collect the most common EventPipe events. These commands will generate a
nettrace file that you can drop up there.
</p>

<h3>Collect allocation events</h3>

<pre><code>dotnet-trace collect --clrevents gc --clreventlevel verbose --process-id $PID</code></pre>

<h3>Collect CPU samples</h3>

<pre><code>dotnet-trace collect --profile cpu-sampling --process-id $PID</code></pre>

<h3>Collect contention events</h3>

<pre><code>dotnet-trace collect --clrevents contention --process-id $PID</code></pre>

<h3>Collect wait events</h3>

<pre><code>dotnet-trace collect --clrevents waithandle --clreventlevel verbose --process-id $PID</code></pre>


@code {
private int _progressPercent;

Expand Down
18 changes: 18 additions & 0 deletions EventPipe.Sample/EventPipe.Sample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\EventPipe\EventPipe.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.510501" />
</ItemGroup>

</Project>
54 changes: 54 additions & 0 deletions EventPipe.Sample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using EventPipe;

Console.WriteLine(Environment.ProcessId);

await using var nettraceStream = File.OpenRead(@"C:\Users\grego\Downloads\nettraces\arbitrage-allocation-5m.nettrace");
EventPipeReader reader = new(nettraceStream);
Trace trace = await reader.ReadFullTraceAsync();

Dictionary<string, List<Event>> eventsByType = new();
foreach (var evt in trace.Events)
{
if (evt.Metadata is not { EventId: 10, ProviderName: "Microsoft-Windows-DotNETRuntime" })
{
continue;
}

ulong allocationAmount = (ulong)evt.Payload["ObjectSize"];
if (allocationAmount < 85_000)
{
continue;
}

string typeName = (string)evt.Payload["TypeName"];
if (!eventsByType.TryGetValue(typeName, out var events))
{
events = [evt];
eventsByType[typeName] = events;
}
else
{
events.Add(evt);
}
}

//using var g = new StreamWriter(@"C:\Users\grego\Documents\dotnet-events-viewer\EventPipe.Sample\result.txt");
foreach (var x in eventsByType
.Select(kvp => (kvp.Key, kvp.Value, kvp.Value.Sum(e => (long)(ulong)e.Payload["ObjectSize"])))
.OrderByDescending(x => x.Item3)
.Take(6))
{
Console.WriteLine($"{x.Key} -> {x.Item3 / 1_000_000f} MB");

#if false
g.WriteLine($"------------------------- {x.Key} {x.Item3} bytes -------------------------");
foreach (var e in x.Value.Take(20))
{
g.WriteLine($"_______ {x.Key} {e.Payload["AllocationAmount"]} _______");
foreach (var f in e.StackTrace.Frames)
{
g.WriteLine($"{f.Namespace}.{f.Name}");
}
}
#endif
}
235 changes: 235 additions & 0 deletions EventPipe.Test/Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
using System.Diagnostics;
using System.Diagnostics.Tracing;

namespace EventPipe.Test;

public class Tests
{
[TestCaseSource(nameof(EnumerateTestCases))]
public async Task Test1(string path)
{
List<long> durations = new();
for (int i = 0; i < 3; i += 1)
{
Trace trace;
long fileLength;
long allocatedBytes1;
Stopwatch s = Stopwatch.StartNew();

{
await using var f = File.OpenRead(path);
EventPipeReader reader = new(f);
fileLength = f.Length;
allocatedBytes1 = GC.GetTotalAllocatedBytes(precise: true);
trace = await reader.ReadFullTraceAsync();
}

long duration = s.ElapsedMilliseconds;
durations.Add(duration);
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
var allocatedBytes2 = GC.GetTotalAllocatedBytes(precise: true);
var allocatedBytes3 = (allocatedBytes2 - allocatedBytes1) / 1_000_000f;
float fileSize = fileLength / 1_000_000f;
float ratio = 100 * allocatedBytes3 / fileSize;
TestContext.Progress.WriteLine($"{duration} ms, {trace.Events.Count} events, file size {fileSize:0.0} MB, {allocatedBytes3:0.0} MB allocated (+{ratio:0}%)");
}

TestContext.Progress.WriteLine("Average: " + durations.Skip(1).Average());
}

[Test]
public async Task Test2()
{
string path = @"C:\Users\grego\Downloads\nettraces\Rider.Backend.exe_20231209_205913.nettrace";
MemoryStream s = new(File.ReadAllBytes(path));
while (true)
{
s.Position = 0;
EventPipeReader reader = new(s);
await reader.ReadFullTraceAsync();
}
}

private static IEnumerable<string> EnumerateTestCases()
{
return Directory.EnumerateFiles(@"C:\Users\grego\Downloads\nettraces", "*.nettrace");
}

private static void WriteClass(EventMetadata m)
{
string className = m.EventName + "Payload";
//WriteLine($"[new Key({m.ProviderName}, {m.EventId}, {m.Version})] = new(\"{m.EventName}\", EventOpcode.{m.Opcode}, {className}.FieldDefinitions, {className}.Parse),");
//return;

WriteLine($"private class {className} : IReadOnlyDictionary<string, object>");
WriteLine("{");
WriteLine($$"""
public static EventFieldDefinition[] FieldDefinitions { get; } =
[
""");
foreach (var f in m.FieldDefinitions)
{
string typeCode = f.TypeCode switch
{
TypeCodeExtensions.Guid => "Guid",
var x => x.ToString(),
};
WriteLine($" new(\"{f.Name}\", TypeCode.{typeCode}),");
}
WriteLine(" ];");
WriteLine("");
WriteLine(" public static IReadOnlyDictionary<string, object> Parse(ref FastSerializerSequenceReader reader)");
WriteLine(" {");
WriteLine($" return new {className}(");
foreach (var f in m.FieldDefinitions)
{
string readCall = f.TypeCode switch
{
TypeCode.Boolean => "reader.ReadInt32() != 0",
TypeCode.String => "reader.ReadNullTerminatedString()",
TypeCodeExtensions.Guid => "reader.ReadGuid()",
var x => $"reader.Read{x}()",
};
Write($" {readCall}");
WriteLine(f == m.FieldDefinitions[^1] ? ");" : ",");
}
WriteLine(" }");
WriteLine("");

foreach (var f in m.FieldDefinitions)
{
string name = ToCamelCase(f.Name);
string type = TypeCodeToCSharp(f.TypeCode);
WriteLine($" private readonly {type} _{name};");
}
WriteLine("");

Write($" private {className}(");
foreach (var f in m.FieldDefinitions)
{
string name = ToCamelCase(f.Name);
string type = TypeCodeToCSharp(f.TypeCode);
Write($"{type} {name}");
if (f == m.FieldDefinitions[^1])
{
WriteLine(")");
}
else
{
Write(", ");
}
}

WriteLine(" {");
foreach (var f in m.FieldDefinitions)
{
string name = ToCamelCase(f.Name);
WriteLine($" _{name} = {name};");
}
WriteLine("""
}

public int Count => FieldDefinitions.Length;

public object this[string key] => TryGetValue(key, out object? val)
? val
: throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");

public IEnumerable<string> Keys => FieldDefinitions.Select(d => d.Name);

public bool ContainsKey(string key)
{
return TryGetValue(key, out _);
}

public bool TryGetValue(string key, [MaybeNullWhen(false)] out object value)
{
switch (key)
{
""");
foreach (var f in m.FieldDefinitions)
{
string xmlName = f.Name;
string csharpName = ToCamelCase(f.Name);
WriteLine($" case \"{xmlName}\":");
WriteLine($" value = _{csharpName};");
WriteLine($" return true;");
}

WriteLine("""
default:
value = null;
return false;
}
}

public IEnumerable<object> Values => GetKeyValues().Select(kvp => kvp.Value);

public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return GetKeyValues().GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

private IEnumerable<KeyValuePair<string, object>> GetKeyValues()
{
""");
foreach (var f in m.FieldDefinitions)
{
string xmlName = f.Name;
string csharpName = ToCamelCase(f.Name);
WriteLine($" yield return new KeyValuePair<string, object>(\"{xmlName}\", _{csharpName});");
}

WriteLine("""
}
}

""");
}

private static string ToCamelCase(string s)
{
if (s.Length < 2)
{
return s;
}
return char.ToLower(s[0]) + s[1..];
}

private static string TypeCodeToCSharp(TypeCode c)
{
return c switch
{
TypeCode.Boolean => "bool",
TypeCode.SByte => "sbyte",
TypeCode.Byte => "byte",
TypeCode.Int16 => "short",
TypeCode.UInt16 => "ushort",
TypeCode.Int32 => "int",
TypeCode.UInt32 => "uint",
TypeCode.Int64 => "long",
TypeCode.UInt64 => "ulong",
TypeCode.Single => "float",
TypeCode.Double => "double",
TypeCode.String => "string",
TypeCodeExtensions.Guid => "Guid",
_ => throw new ArgumentOutOfRangeException()
};
}

private static void Write(string s)
{
File.AppendAllText("C:\\Users\\grego\\Documents\\dotnet-events-viewer\\EventPipe.Test\\XX.cs", s);
}

private static void WriteLine(string s)
{
File.AppendAllLines("C:\\Users\\grego\\Documents\\dotnet-events-viewer\\EventPipe.Test\\XX.cs", new[] { s });
}
}

0 comments on commit ec66143

Please sign in to comment.