Skip to content
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