Skip to content
Draft
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
6 changes: 3 additions & 3 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<!-- Other libs -->
<MicrosoftBclAsyncInterfacesVersion>9.0.8</MicrosoftBclAsyncInterfacesVersion>
<MicrosoftDiaSymReaderNativeVersion>17.10.0-beta1.24272.1</MicrosoftDiaSymReaderNativeVersion>
<MicrosoftDiagnosticsTracingTraceEventVersion>3.1.23</MicrosoftDiagnosticsTracingTraceEventVersion>
<MicrosoftDiagnosticsTracingTraceEventVersion>3.1.28</MicrosoftDiagnosticsTracingTraceEventVersion>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For symbol resolution, here is the current state in PerfView/TraceEvent:

  • Native code: Symbols must be present on the collection machine at collection time so that one-collect can pick them up. They show up in the trace as ProcessSymbol events. From what I can see you're already getting these resolved properly.
  • Jitted Code: Symbols are emitted into the trace as ProcessSymbol events.
  • R2R Code: PerfView/TraceEvent downloads these from the symbol server at analysis time via an instance of SymbolReader.

<MicrosoftDotNetXUnitExtensionsVersion>11.0.0-beta.25528.108</MicrosoftDotNetXUnitExtensionsVersion>
<MicrosoftExtensionsLoggingVersion>8.0.1</MicrosoftExtensionsLoggingVersion>
<MicrosoftExtensionsLoggingAbstractionsVersion>8.0.3</MicrosoftExtensionsLoggingAbstractionsVersion>
Expand All @@ -46,8 +46,8 @@
<SystemMemoryVersion>4.5.5</SystemMemoryVersion>
<SystemRuntimeLoaderVersion>4.3.0</SystemRuntimeLoaderVersion>
<SystemThreadingTasksExtensionsVersion>4.5.4</SystemThreadingTasksExtensionsVersion>
<SystemTextEncodingsWebVersion>8.0.0</SystemTextEncodingsWebVersion>
<SystemTextJsonVersion>8.0.5</SystemTextJsonVersion>
<SystemTextEncodingsWebVersion>9.0.8</SystemTextEncodingsWebVersion>
<SystemTextJsonVersion>9.0.8</SystemTextJsonVersion>
<XUnitAbstractionsVersion>2.0.3</XUnitAbstractionsVersion>
<StyleCopAnalyzersVersion>1.2.0-beta.556</StyleCopAnalyzersVersion>
<cdbsosversion>10.0.26100.1</cdbsosversion>
Expand Down
66 changes: 27 additions & 39 deletions src/Tools/dotnet-trace/CommandLine/Commands/ReportCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,55 +41,43 @@ private static int TopNReport(string traceFile, int number, bool inclusive, bool
{
try
{
string tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(traceFile);
int count = 0;
int index = 0;
List<CallTreeNodeBase> nodesToReport = new();
using (SymbolReader symbolReader = new(System.IO.TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath })
using (TraceLog eventLog = new(tempEtlxFilename))
{
MutableTraceEventStackSource stackSource = new(eventLog)
{
OnlyManagedCodeStacks = true
};

SampleProfilerThreadTimeComputer computer = new(eventLog, symbolReader);
MutableTraceEventStackSource stackSource = ThreadTimeStackSourceHelper.GenerateStackSourceFromTrace(traceFile);

computer.GenerateThreadTimeStacks(stackSource);

FilterParams filterParams = new()
{
FoldRegExs = "CPU_TIME;UNMANAGED_CODE_TIME;{Thread (}",
};
FilterStackSource filterStack = new(filterParams, stackSource, ScalingPolicyKind.ScaleToData);
CallTree callTree = new(ScalingPolicyKind.ScaleToData);
callTree.StackSource = filterStack;
FilterParams filterParams = new()
{
FoldRegExs = "CPU_TIME;UNMANAGED_CODE_TIME;{Thread (}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you will want to separate a bit of the configuration based on whether or not the trace comes from the runtime (EventPipe) vs. one-collect. Traces from one-collect are going to have multiple processes and then either CPU time or Thread time.

One of the great things about this view is that when the data is focused on the right target (usually just a single process), it can be very useful. In the current state, we're looking at all processes on the machine, and blocked time. Blocked time almost always dominates CPU time because it's not a consumable resource - it's the time that was spent by every thread when they were blocked.

I'd recommend the following:

  • Filter to just CPU time - don't show blocked time in these tools.
  • Provide a way for users to select the process they want to filter to.

};
FilterStackSource filterStack = new(filterParams, stackSource, ScalingPolicyKind.ScaleToData);
CallTree callTree = new(ScalingPolicyKind.ScaleToData);
callTree.StackSource = filterStack;

List<CallTreeNodeBase> callTreeNodes = null;
List<CallTreeNodeBase> callTreeNodes = null;

if (!inclusive)
{
callTreeNodes = callTree.ByIDSortedExclusiveMetric();
}
else
{
callTreeNodes = callTree.ByIDSortedInclusiveMetric();
}
if (!inclusive)
{
callTreeNodes = callTree.ByIDSortedExclusiveMetric();
}
else
{
callTreeNodes = callTree.ByIDSortedInclusiveMetric();
}

int totalElements = callTreeNodes.Count;
while (count < number && index < totalElements)
int totalElements = callTreeNodes.Count;
while (count < number && index < totalElements)
{
CallTreeNodeBase node = callTreeNodes[index];
index++;
if (!unwantedMethodNames.Any(node.Name.Contains))
{
CallTreeNodeBase node = callTreeNodes[index];
index++;
if (!unwantedMethodNames.Any(node.Name.Contains))
{
nodesToReport.Add(node);
count++;
}
nodesToReport.Add(node);
count++;
}

PrintReportHelper.TopNWriteToStdOut(nodesToReport, inclusive, verbose);
}

PrintReportHelper.TopNWriteToStdOut(nodesToReport, inclusive, verbose);
return 0;
}
catch (Exception ex)
Expand Down
84 changes: 84 additions & 0 deletions src/Tools/dotnet-trace/ThreadTimeProviderType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using Microsoft.Diagnostics.Symbols;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Diagnostics.Tracing.Stacks;

namespace Microsoft.Diagnostics.Tools.Trace
{
internal enum ThreadTimeProviderType
{
SampleProfiler,
UniversalEvents,
Unknown
}

internal static class ThreadTimeStackSourceHelper
{
public static MutableTraceEventStackSource GenerateStackSourceFromTrace(string traceFile, bool includeEventSourceEvents = false, bool continueOnError = false)
{
string etlxFilePath = TraceLog.CreateFromEventPipeDataFile(traceFile, null, new TraceLogOptions() { ContinueOnError = continueOnError });
using SymbolReader symbolReader = new(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath };
using TraceLog eventLog = new(etlxFilePath);
MutableTraceEventStackSource stackSource = new(eventLog);

ThreadTimeProviderType providerType = DetectProviderType(eventLog);
switch (providerType)
{
case ThreadTimeProviderType.SampleProfiler:
{
stackSource.OnlyManagedCodeStacks = true;
SampleProfilerThreadTimeComputer computer = new(eventLog, symbolReader)
{
IncludeEventSourceEvents = includeEventSourceEvents,
};
computer.GenerateThreadTimeStacks(stackSource);
break;
}
case ThreadTimeProviderType.UniversalEvents:
{
stackSource.OnlyManagedCodeStacks = false;
#pragma warning disable 618
ThreadTimeStackComputer computer = new(eventLog, symbolReader)
{
IncludeEventSourceEvents = false,
};
computer.GenerateThreadTimeStacks(stackSource);
#pragma warning restore 618
break;
}
case ThreadTimeProviderType.Unknown:
default:
throw new DiagnosticToolException("The trace does not contain SampleProfiler or Universal.Events data required for thread-time analysis.");
}

if (File.Exists(etlxFilePath))
{
File.Delete(etlxFilePath);
}

return stackSource;
}

private static ThreadTimeProviderType DetectProviderType(TraceLog eventLog)
{
foreach (TraceEvent evt in eventLog.Events)
{
if (string.Equals(evt.ProviderName, "Microsoft-DotNETCore-SampleProfiler", StringComparison.Ordinal))
{
return ThreadTimeProviderType.SampleProfiler;
}
if (string.Equals(evt.ProviderName, "Universal.Events", StringComparison.Ordinal))
{
return ThreadTimeProviderType.UniversalEvents;
}
}

return ThreadTimeProviderType.Unknown;
}
}
}
42 changes: 12 additions & 30 deletions src/Tools/dotnet-trace/TraceFileFormatConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,38 +69,20 @@ internal static void ConvertToFormat(TextWriter stdOut, TextWriter stdError, Tra

private static void Convert(TraceFileFormat format, string fileToConvert, string outputFilename, bool continueOnError = false)
{
string etlxFilePath = TraceLog.CreateFromEventPipeDataFile(fileToConvert, null, new TraceLogOptions() { ContinueOnError = continueOnError });
using (SymbolReader symbolReader = new(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath })
using (TraceLog eventLog = new(etlxFilePath))
{
MutableTraceEventStackSource stackSource = new(eventLog)
{
OnlyManagedCodeStacks = true // EventPipe currently only has managed code stacks.
};

SampleProfilerThreadTimeComputer computer = new(eventLog, symbolReader)
{
IncludeEventSourceEvents = false // SpeedScope handles only CPU samples, events are not supported
};
computer.GenerateThreadTimeStacks(stackSource);

switch (format)
{
case TraceFileFormat.Speedscope:
SpeedScopeStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename);
break;
case TraceFileFormat.Chromium:
ChromiumStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename, compress: false);
break;
default:
// we should never get here
throw new DiagnosticToolException($"Invalid TraceFileFormat \"{format}\"");
}
}
// Speedscope/Chromium outputs only understand CPU samples; exclude EventSource events.
MutableTraceEventStackSource stackSource = ThreadTimeStackSourceHelper.GenerateStackSourceFromTrace(fileToConvert, includeEventSourceEvents: false, continueOnError);

if (File.Exists(etlxFilePath))
switch (format)
{
File.Delete(etlxFilePath);
case TraceFileFormat.Speedscope:
SpeedScopeStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename);
break;
case TraceFileFormat.Chromium:
ChromiumStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename, compress: false);
break;
default:
// we should never get here
throw new DiagnosticToolException($"Invalid TraceFileFormat \"{format}\"");
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Tools/dotnet-trace/dotnet-trace.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<ItemGroup>
<PackageReference Include="System.CommandLine" Version="$(SystemCommandLineVersion)" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="$(MicrosoftDiagnosticsTracingTraceEventVersion)" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="$(MicrosoftDiagnosticsTracingTraceEventVersion)" />
<PackageReference Include="Microsoft.OneCollect.RecordTrace" Version="$(MicrosoftOneCollectRecordTraceVersions)" PrivateAssets="All" GeneratePathProperty="true" />
</ItemGroup>

Expand Down