Skip to content

Dotnet-trace (.netrace) support #95

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

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 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
63 changes: 63 additions & 0 deletions DotNetEventPipe/DataOutputTypes/GenericEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Performance.SDK;
using System;
using Utilities;

namespace DotNetEventPipe.DataOutputTypes
{
/// <summary>
/// A GenericEvent
/// </summary>
public class GenericEvent
{
public string EventName { get; }
public TraceEventID ID { get; }
public TraceEventKeyword Keywords { get; }
public TraceEventLevel Level { get; }
public TraceEventOpcode Opcode { get; }
public string OpcodeName { get; }
public string[] PayloadNames { get; }
public object[] PayloadValues { get; }
public Guid ProviderGuid { get; }
public string ProviderName { get; }
public int ProcessID { get; }
public string ProcessName { get; }
public int ProcessorNumber { get; }
public int ThreadID { get; }
public Timestamp Timestamp { get; }
public string[] CallStack { get; }
/// <summary>
/// filename of the binary / library for the instruction pointer
/// </summary>
public TraceModuleFile Module { get; }
/// <summary>
/// Functionname of the instruction pointer
/// </summary>
public string FullMethodName { get; }

public GenericEvent(string eventName, TraceEventID id, TraceEventKeyword keywords, TraceEventLevel level, TraceEventOpcode opcode, string opcodeName, string[] payloadNames, object[] payloadValues, Guid providerGuid, string providerName, int processID, string processName, int processorNumber, int threadID, Timestamp timestamp, string[] callStack, TraceModuleFile module, string fullMethodName)
{
EventName = Common.StringIntern(eventName);
ID = id;
Keywords = keywords;
Level = level;
Opcode = opcode;
OpcodeName = Common.StringIntern(opcodeName);
PayloadNames = payloadNames;
PayloadValues = payloadValues;
ProviderGuid = providerGuid;
ProviderName = providerName;
ProcessID = processID;
ProcessName = Common.StringIntern(processName);
ProcessorNumber = processorNumber;
ThreadID = threadID;
Timestamp = timestamp;
CallStack = callStack;
Module = module;
FullMethodName = Common.StringIntern(fullMethodName);
}
}
}
51 changes: 51 additions & 0 deletions DotNetEventPipe/DataOutputTypes/ThreadSamplingEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Performance.SDK;
using Utilities;

namespace DotNetEventPipe.DataOutputTypes
{
/// <summary>
/// A CPU sampling event that samples at some interval
/// Shows process and threads, and stacks that were running on which CPUs at specific times.
/// </summary>
public readonly struct ThreadSamplingEvent
{
public int ProcessID { get; }
public string ProcessName { get; }
public int ProcessorNumber { get; }
public int ThreadID { get; }
public Timestamp Timestamp { get; }
public string[] CallStack { get; }
/// <summary>
/// filename of the binary / library for the instruction pointer
/// </summary>
public TraceModuleFile Module { get; }
/// <summary>
/// Functionname of the instruction pointer
/// </summary>
public string FullMethodName { get; }

public ThreadSamplingEvent(
int processID,
string processName,
int processorNumber,
int threadID,
Timestamp timestamp,
string[] callStack,
TraceModuleFile module,
string fullMethodName
)
{
ProcessID = processID;
ProcessName = Common.StringIntern(processName);
ProcessorNumber = processorNumber;
ThreadID = threadID;
Timestamp = timestamp;
CallStack = callStack; // Cache whole common stack??
Module = module;
FullMethodName = Common.StringIntern(fullMethodName);
}
}
}
14 changes: 14 additions & 0 deletions DotNetEventPipe/DataOutputTypes/TraceCallStackProcessed.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.Diagnostics.Tracing.Etlx;
using System;
using System.Collections.Generic;
using System.Text;

namespace DotnetEventpipe.DataOutputTypes
{
public class TraceCallStackProcessed
{
public string[] CallStack { get; set; }
public TraceModuleFile Module { get; set; }
public string FullMethodName { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe make readonly?

}
}
52 changes: 52 additions & 0 deletions DotNetEventPipe/DotnetEventpipe.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Version>1.0.0</Version>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Authors>Microsoft</Authors>
<Company>Microsoft Corp.</Company>
<Product>Performance ToolKit</Product>
<Description>Contains the .NET Trace datasource plugin.</Description>
<PackageId>Microsoft.Performance.Toolkit.Plugins.DotNetEvent</PackageId>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<RepositoryUrl>https://github.com/microsoft/Microsoft-Performance-Tools-Linux-Android</RepositoryUrl>
<PackageProjectUrl>https://github.com/microsoft/Microsoft-Performance-Tools-Linux-Android</PackageProjectUrl>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<None Include="..\LICENSE.txt">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.0.0">
<GeneratePathProperty>True</GeneratePathProperty>
</PackageReference>
<PackageReference Include="Microsoft.Performance.SDK" Version="1.0.16" />
</ItemGroup>

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

<ItemGroup>
<TraceEventWorkaround Include="$(PkgMicrosoft_Diagnostics_Tracing_Traceevent)\lib\netstandard2.0\Microsoft.Diagnostics.Tracing.TraceEvent.dll;$(PkgMicrosoft_Diagnostics_Tracing_Traceevent)\lib\netstandard2.0\Microsoft.Diagnostics.FastSerialization.dll" />
</ItemGroup>
<Target Name="CopyRulesToTarget" AfterTargets="Build">
<Copy SourceFiles="@(TraceEventWorkaround)" DestinationFolder="$(TargetDir)" />
</Target>

</Project>
111 changes: 111 additions & 0 deletions DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using DotNetEventPipe.Tables;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Diagnostics.Tracing.EventPipe;
using Microsoft.Diagnostics.Tracing.Etlx;

namespace DotNetEventPipe
{
public sealed class DotnetTraceDataProcessor
: CustomDataProcessor
{
private readonly string[] filePaths;
private IReadOnlyDictionary<string, TraceEventProcessor> fileContent;
private DataSourceInfo dataSourceInfo;

public DotnetTraceDataProcessor(
string[] filePaths,
ProcessorOptions options,
IApplicationEnvironment applicationEnvironment,
IProcessorEnvironment processorEnvironment)
: base(options, applicationEnvironment, processorEnvironment)
{
//
// Assign the files array to a readonly backing field.
//

this.filePaths = filePaths;
}

public override DataSourceInfo GetDataSourceInfo()
{
// The DataSourceInfo is used to tell analzyer the time range of the data(if applicable) and any other relevant data for rendering / synchronizing.

return this.dataSourceInfo;

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: extra line

}

protected override Task ProcessAsyncCore(
IProgress<int> progress,
CancellationToken cancellationToken)
{
var contentDictionary = new Dictionary<string, TraceEventProcessor>();

foreach (var path in this.filePaths)
{
var traceStartTime = DateTime.UtcNow.Date;
Copy link
Contributor

Choose a reason for hiding this comment

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

not needed?


// EventPipeEventSource doesn't expose the callstacks - https://github.com/Microsoft/perfview/blob/main/src/TraceEvent/EventPipe/EventPipeFormat.md
// But currently it's SessionDuration, SessionStartTime are correct
// Can remove when when this is released - https://github.com/microsoft/perfview/pull/1635
var dotnetFileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
Copy link
Contributor

Choose a reason for hiding this comment

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

add using

using (var traceSource = new EventPipeEventSource(dotnetFileStream))
{
traceSource.Process();
this.dataSourceInfo = new DataSourceInfo(0, traceSource.SessionDuration.Ticks * 100, traceSource.SessionStartTime.ToUniversalTime());
}

var tmpEtlx = Path.Combine(Path.GetTempPath(), Path.GetFileName(path) + ".etlx");

string traceLogPath = TraceLog.CreateFromEventPipeDataFile(path, tmpEtlx);
using (TraceLog traceLog = new TraceLog(traceLogPath))
{
TraceLogEventSource source = traceLog.Events.GetSource();

var traceEventProcessor = new TraceEventProcessor();
contentDictionary[path] = traceEventProcessor;
source.AllEvents += traceEventProcessor.ProcessTraceEvent;
source.Process();
// Below will work when this is released - https://github.com/microsoft/perfview/pull/1635
//this.dataSourceInfo = new DataSourceInfo(0, source.SessionDuration.Ticks * 100, source.SessionStartTime.ToUniversalTime());
}
File.Delete(tmpEtlx);
}

this.fileContent = new ReadOnlyDictionary<string, TraceEventProcessor>(contentDictionary);

return Task.CompletedTask;
}

protected override void BuildTableCore(
TableDescriptor tableDescriptor,
ITableBuilder tableBuilder)
{
//
// Instantiate the table, and pass the tableBuilder to it.
//

var table = this.InstantiateTable(tableDescriptor.Type);
table.Build(tableBuilder);
}

private TraceEventTableBase InstantiateTable(Type tableType)
{
//
// This private method is added to activate the given table type and pass in the file content.
//

var instance = Activator.CreateInstance(tableType, new[] { this.fileContent, });
return (TraceEventTableBase)instance;
}
}
}
73 changes: 73 additions & 0 deletions DotNetEventPipe/SourceDataCookers/DotnetTraceProcessingSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Performance.SDK.Processing;

namespace DotNetEventPipe
{
[ProcessingSource(
"{890C0A11-011E-43E1-AE28-7E1A903A6633}", // The GUID must be unique for your Custom Data Source. You can use Visual Studio's Tools -> Create Guid… tool to create a new GUID
".NET (dotnet-trace)", // The Custom Data Source MUST have a name
@".net trace EventPipe")] // The Custom Data Source MUST have a description
[FileDataSource(
".nettrace", // A file extension is REQUIRED
"dotnet-trace")] // A description is OPTIONAL. The description is what appears in the file open menu to help users understand what the file type actually is.

//
// There are two methods to creating a Custom Data Source that is recognized by UI:
// 1. Using the helper abstract base classes
// 2. Implementing the raw interfaces
// This sample demonstrates method 1 where the ProcessingSource abstract class
// helps provide a public parameterless constructor and implement the IProcessingSource interface
//

public class DotnetTraceProcessingSource
: ProcessingSource
{
private IApplicationEnvironment applicationEnvironment;

public override ProcessingSourceInfo GetAboutInfo()
{
return new ProcessingSourceInfo()
{
ProjectInfo = new ProjectInfo() { Uri = "https://aka.ms/linuxperftools" },
};
}

protected override void SetApplicationEnvironmentCore(IApplicationEnvironment applicationEnvironment)
{
//
// Saves the given application environment into this instance
//

this.applicationEnvironment = applicationEnvironment;
}

protected override bool IsDataSourceSupportedCore(IDataSource dataSource)
{
return dataSource.IsFile() && Path.GetExtension(dataSource.Uri.LocalPath) == ".nettrace";
}

protected override ICustomDataProcessor CreateProcessorCore(
IEnumerable<IDataSource> dataSources,
IProcessorEnvironment processorEnvironment,
ProcessorOptions options)
{
//
// Create a new instance implementing ICustomDataProcessor here to process the specified data sources.
// Note that you can have more advanced logic here to create different processors if you would like based on the file, or any other criteria.
// You are not restricted to always returning the same type from this method.
//

return new DotnetTraceDataProcessor(
dataSources.Select(x => x.Uri.LocalPath).ToArray(),
options,
this.applicationEnvironment,
processorEnvironment);
}
}
}
Loading