From ab48b31b2ad8d3e4117ec9cef15ce0205219ed5d Mon Sep 17 00:00:00 2001 From: Ivan Berg Date: Wed, 18 May 2022 15:55:24 -0700 Subject: [PATCH 1/9] Check in working prototype of .NET trace EventPipe parsing support (still work / refactoring to be done) --- .../DataOutputTypes/ThreadSamplingEvent.cs | 51 +++ DotNetEventPipe/DotnetEventpipe.csproj | 52 +++ .../DotnetTraceDataProcessor.cs | 106 +++++ .../PerfDataProcessingSource.cs | 73 ++++ .../SourceDataCookers/TraceEventProcessor.cs | 65 +++ DotNetEventPipe/Tables/CpuSamplingTable.cs | 223 +++++++++++ DotNetEventPipe/Tables/TraceEventTableBase.cs | 30 ++ .../DotnetEventpipeTest.csproj | 22 ++ DotnetEventpipeTest/DotnetTraceTests.cs | 59 +++ Microsoft-Perf-Tools-Linux-Android.sln | 373 +++++++++--------- PerfDataExtensions/PerfDataExtensions.csproj | 2 +- .../PerfDataProcessingSource.cs | 25 -- PerfDataExtensions/Tables/CpuSamplingTable.cs | 4 +- .../Tables/PerfTxtCpuSamplingTable.cs | 4 +- PerfUnitTest/PerfUnitTest.csproj | 2 +- UnitTestCommon/UnitTest.cs | 5 +- .../Tables => Utilities}/TimeHelper.cs | 2 +- 17 files changed, 885 insertions(+), 213 deletions(-) create mode 100644 DotNetEventPipe/DataOutputTypes/ThreadSamplingEvent.cs create mode 100644 DotNetEventPipe/DotnetEventpipe.csproj create mode 100644 DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs create mode 100644 DotNetEventPipe/SourceDataCookers/PerfDataProcessingSource.cs create mode 100644 DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs create mode 100644 DotNetEventPipe/Tables/CpuSamplingTable.cs create mode 100644 DotNetEventPipe/Tables/TraceEventTableBase.cs create mode 100644 DotnetEventpipeTest/DotnetEventpipeTest.csproj create mode 100644 DotnetEventpipeTest/DotnetTraceTests.cs rename {PerfDataExtensions/Tables => Utilities}/TimeHelper.cs (95%) diff --git a/DotNetEventPipe/DataOutputTypes/ThreadSamplingEvent.cs b/DotNetEventPipe/DataOutputTypes/ThreadSamplingEvent.cs new file mode 100644 index 0000000..dcd1531 --- /dev/null +++ b/DotNetEventPipe/DataOutputTypes/ThreadSamplingEvent.cs @@ -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 +{ + /// + /// A CPU sampling event that samples at some interval + /// Shows process and threads, and stacks that were running on which CPUs at specific times. + /// + 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; } + /// + /// filename of the binary / library for the instruction pointer + /// + public TraceModuleFile Module { get; } + /// + /// Functionname of the instruction pointer + /// + 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); + } + } +} diff --git a/DotNetEventPipe/DotnetEventpipe.csproj b/DotNetEventPipe/DotnetEventpipe.csproj new file mode 100644 index 0000000..aac2040 --- /dev/null +++ b/DotNetEventPipe/DotnetEventpipe.csproj @@ -0,0 +1,52 @@ + + + + netstandard2.1 + 1.0.0 + true + Microsoft + Microsoft Corp. + Performance ToolKit + Contains the .NET Trace datasource plugin. + Microsoft.Performance.Toolkit.Plugins.DotNetEvent + © Microsoft Corporation. All rights reserved. + https://github.com/microsoft/Microsoft-Performance-Tools-Linux-Android + https://github.com/microsoft/Microsoft-Performance-Tools-Linux-Android + LICENSE.txt + + + + TRACE + false + + + + false + + + + + True + + + + + + + True + + + + + + + + + + + + + + + + diff --git a/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs b/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs new file mode 100644 index 0000000..b5844ef --- /dev/null +++ b/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs @@ -0,0 +1,106 @@ +// 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 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; + + } + + protected override Task ProcessAsyncCore( + IProgress progress, + CancellationToken cancellationToken) + { + var contentDictionary = new Dictionary(); + + foreach (var path in this.filePaths) + { + var traceStartTime = DateTime.UtcNow.Date; + + // 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 + var dotnetFileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + using (var traceSource = new EventPipeEventSource(dotnetFileStream)) + { + traceSource.Process(); + this.dataSourceInfo = new DataSourceInfo(0, traceSource.SessionDuration.Ticks * 100, traceSource.SessionStartTime.ToUniversalTime()); + } + + string traceLogPath = TraceLog.CreateFromEventPipeDataFile(path); + using (TraceLog traceLog = new TraceLog(traceLogPath)) + { + TraceLogEventSource source = traceLog.Events.GetSource(); + + var traceEventProcessor = new TraceEventProcessor(); + contentDictionary[path] = traceEventProcessor; + source.AllEvents += traceEventProcessor.ProcessTraceEvent; + source.Process(); + //this.dataSourceInfo = new DataSourceInfo(0, source.SessionDuration.Ticks * 100, source.SessionStartTime.ToUniversalTime()); + } + } + + this.fileContent = new ReadOnlyDictionary(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; + } + } +} diff --git a/DotNetEventPipe/SourceDataCookers/PerfDataProcessingSource.cs b/DotNetEventPipe/SourceDataCookers/PerfDataProcessingSource.cs new file mode 100644 index 0000000..e36da60 --- /dev/null +++ b/DotNetEventPipe/SourceDataCookers/PerfDataProcessingSource.cs @@ -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 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); + } + } +} diff --git a/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs b/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs new file mode 100644 index 0000000..71c30e3 --- /dev/null +++ b/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs @@ -0,0 +1,65 @@ +using DotNetEventPipe.DataOutputTypes; +using Microsoft.Diagnostics.Tracing; +using Microsoft.Diagnostics.Tracing.Etlx; +using Microsoft.Diagnostics.Tracing.EventPipe; +using System; +using System.Collections.Generic; +using System.Text; +using Utilities; + +namespace DotNetEventPipe +{ + public class TraceEventProcessor + { + const uint MSEC_TO_NS = 1000000; + public List ThreadSamplingEvents = new List(); + + // TODO - Move this to a DataCooker + + public void ProcessTraceEvent(TraceEvent data) + { + string eventName = data.ProviderName + "/" + data.EventName; + + TraceCallStack stack = data.CallStack(); + + switch (data.EventName) + { + case "Thread/Sample": + var clrTS = (ClrThreadSampleTraceData) data; + + string[] callStack = null; + TraceModuleFile module = null; + string fullMethodName = null; + if (stack != null) + { + module = stack.CodeAddress.ModuleFile; + fullMethodName = stack.CodeAddress.FullMethodName; + callStack = new string[stack.Depth]; + + TraceCallStack current = stack; + while (current != null) + { + callStack[current.Depth-1] = Common.StringIntern($"{current.CodeAddress.ModuleName}!{current.CodeAddress.FullMethodName}"); + current = current.Caller; + } + } + + var threadSamplingEvent = new ThreadSamplingEvent( + clrTS.ProcessID, + clrTS.ProcessName, + clrTS.ProcessorNumber, + clrTS.ThreadID, + new Microsoft.Performance.SDK.Timestamp((long)(clrTS.TimeStampRelativeMSec * MSEC_TO_NS)), + callStack, + module, + fullMethodName + ); + ThreadSamplingEvents.Add(threadSamplingEvent); + break; + default: + // TODO all other events go to GenericEvents + break; + } + } + } +} diff --git a/DotNetEventPipe/Tables/CpuSamplingTable.cs b/DotNetEventPipe/Tables/CpuSamplingTable.cs new file mode 100644 index 0000000..1d81792 --- /dev/null +++ b/DotNetEventPipe/Tables/CpuSamplingTable.cs @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Performance.SDK.Processing; +using Utilities.AccessProviders; +using static Utilities.TimeHelper; + +namespace DotNetEventPipe.Tables +{ + // + // Have the MetadataTable inherit the TableBase class + // + [Table] // A category is optional. It useful for grouping different types of tables + public sealed class CpuSamplingTable + : TraceEventTableBase + { + public static readonly TableDescriptor TableDescriptor = new TableDescriptor( + Guid.Parse("{29C3ECF1-0857-4A3D-B6E3-2197CE1E9C81}"), + ".NET (dotnet)", + ".nettrace (dotnet-trace)", + category: ".NET"); + + public CpuSamplingTable(IReadOnlyDictionary traceEventProcessor) + : base(traceEventProcessor) + { + } + + private static readonly ColumnConfiguration timestampColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{8FFAE86A-5608-42CB-80E4-01D982AFBCA2}"), "Timestamp", "The timestamp of the sample"), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration functionColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{4E6F6543-925A-479D-947C-CC3E932DA139}"), "Function", "The function of the Instruction Pointer(IP)"), + new UIHints { Width = 130 }); + + private static readonly ColumnConfiguration moduleColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{A3EE00E4-71A9-47F9-B0B8-448CAEE92BB0}"), "Module", "The module of the Instruction Pointer(IP)"), + new UIHints { Width = 130 }); + + private static readonly ColumnConfiguration countColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{711ECA36-9B10-4E88-829D-F5E6749E22A6}"), "Count", "The count of samples"), + new UIHints { + Width = 130, + AggregationMode = AggregationMode.Sum, // Sum needed instead of Count for flame + SortOrder = SortOrder.Descending, + SortPriority = 0, + }); + + private static readonly ColumnConfiguration callStackColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{5072587D-B69C-4B4B-AEB9-AD51C3FFEBFB}"), "Callstack"), + new UIHints { Width = 800, }); + + private static readonly ColumnConfiguration threadIdColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{11C8D2B4-3E39-46A4-B9AF-33D2F4E79148}"), "ThreadId"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration processColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{DA2F0BA6-5F54-449E-A3B8-586DB3D38DCC}"), "Process"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration processIdColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{357841CB-AE3B-4BFF-A778-C0B6AD3E4CD0}"), "Process Id"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration cpuColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{DB744571-72CE-4C56-8277-58A402682016}"), "CPU"), + new UIHints { Width = 80, }); + + + public override void Build(ITableBuilder tableBuilder) + { + if (TraceEventProcessor == null || TraceEventProcessor.Count == 0) + { + return; + } + + var firstTraceProcessorEventsParsed = TraceEventProcessor.First().Value; // First Log + var threadSamplingEvents = firstTraceProcessorEventsParsed.ThreadSamplingEvents; + + var tableGenerator = tableBuilder.SetRowCount(threadSamplingEvents.Count); + var baseProjection = Projection.Index(threadSamplingEvents); + + + tableGenerator.AddColumn(countColumn, baseProjection.Compose(x => 1)); // 1 sample + tableGenerator.AddColumn(processIdColumn, baseProjection.Compose(x => x.ProcessID)); + tableGenerator.AddColumn(processColumn, baseProjection.Compose(x => x.ProcessName)); + tableGenerator.AddColumn(cpuColumn, baseProjection.Compose(x => x.ProcessorNumber)); + tableGenerator.AddColumn(threadIdColumn, baseProjection.Compose(x => x.ThreadID)); + tableGenerator.AddColumn(timestampColumn, baseProjection.Compose(x => x.Timestamp)); + tableGenerator.AddColumn(moduleColumn, baseProjection.Compose(x => x.Module?.Name)); + tableGenerator.AddColumn(functionColumn, baseProjection.Compose(x => x.FullMethodName)); + tableGenerator.AddHierarchicalColumn(callStackColumn, baseProjection.Compose(x => x.CallStack), new ArrayAccessProvider()); + + var utilByCpuStackConfig = new TableConfiguration("Utilization by CPU, Stack") + { + Columns = new[] + { + cpuColumn, + callStackColumn, + TableConfiguration.PivotColumn, + processColumn, + threadIdColumn, + functionColumn, + moduleColumn, + timestampColumn, + //weightColumn, + TableConfiguration.GraphColumn, + countColumn + //weightPctColumn + + }, + }; + utilByCpuStackConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); + utilByCpuStackConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + + var utilByCpuConfig = new TableConfiguration("Utilization by CPU") + { + Columns = new[] + { + cpuColumn, + TableConfiguration.PivotColumn, + callStackColumn, + processColumn, + threadIdColumn, + functionColumn, + moduleColumn, + timestampColumn, + //weightColumn, + TableConfiguration.GraphColumn, + countColumn + //weightPctColumn + + }, + }; + utilByCpuConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); + utilByCpuConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + + var utilByProcessConfig = new TableConfiguration("Utilization by Process") + { + Columns = new[] + { + processColumn, + TableConfiguration.PivotColumn, + callStackColumn, + cpuColumn, + threadIdColumn, + functionColumn, + moduleColumn, + timestampColumn, + //weightColumn, + TableConfiguration.GraphColumn, + countColumn, + //weightPctColumn + + }, + }; + utilByProcessConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); + utilByProcessConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + + var utilByProcessStackConfig = new TableConfiguration("Utilization by Process, Stack") + { + Columns = new[] + { + processColumn, + callStackColumn, + TableConfiguration.PivotColumn, + cpuColumn, + threadIdColumn, + functionColumn, + moduleColumn, + timestampColumn, + //weightColumn, + TableConfiguration.GraphColumn, + countColumn + //weightPctColumn + + }, + }; + utilByProcessStackConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); + utilByProcessStackConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + + var flameByProcessStackConfig = new TableConfiguration("Flame by Process, Stack") + { + Columns = new[] + { + processColumn, + callStackColumn, + TableConfiguration.PivotColumn, + cpuColumn, + threadIdColumn, + functionColumn, + moduleColumn, + timestampColumn, + //weightColumn, + TableConfiguration.GraphColumn, + countColumn + //weightPctColumn + + }, + ChartType = ChartType.Flame, + + }; + flameByProcessStackConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); + flameByProcessStackConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + + var table = tableBuilder + .AddTableConfiguration(utilByCpuStackConfig) + .SetDefaultTableConfiguration(utilByProcessStackConfig) + .AddTableConfiguration(utilByCpuConfig) + .AddTableConfiguration(utilByProcessConfig) + .AddTableConfiguration(utilByProcessStackConfig) + .AddTableConfiguration(flameByProcessStackConfig); + } + } +} diff --git a/DotNetEventPipe/Tables/TraceEventTableBase.cs b/DotNetEventPipe/Tables/TraceEventTableBase.cs new file mode 100644 index 0000000..2f7523d --- /dev/null +++ b/DotNetEventPipe/Tables/TraceEventTableBase.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.Diagnostics.Tracing.StackSources; +using Microsoft.Performance.SDK.Processing; + +namespace DotNetEventPipe.Tables +{ + public abstract class TraceEventTableBase + { + protected TraceEventTableBase(IReadOnlyDictionary traceEventProcessor) + { + this.TraceEventProcessor = traceEventProcessor; + } + + // + // In this sample we are going to assume the files will fit in memory, + // and so we will make sure all tables have access to the collection of lines in the file. + // + + public IReadOnlyDictionary TraceEventProcessor { get; } + + // + // All tables will need some way to build themselves via the ITableBuilder interface. + // + + public abstract void Build(ITableBuilder tableBuilder); + } +} diff --git a/DotnetEventpipeTest/DotnetEventpipeTest.csproj b/DotnetEventpipeTest/DotnetEventpipeTest.csproj new file mode 100644 index 0000000..c0ad326 --- /dev/null +++ b/DotnetEventpipeTest/DotnetEventpipeTest.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + + diff --git a/DotnetEventpipeTest/DotnetTraceTests.cs b/DotnetEventpipeTest/DotnetTraceTests.cs new file mode 100644 index 0000000..bb735fb --- /dev/null +++ b/DotnetEventpipeTest/DotnetTraceTests.cs @@ -0,0 +1,59 @@ +using DotNetEventPipe.Tables; +using Microsoft.Performance.SDK.Processing; +using Microsoft.Performance.Toolkit.Engine; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using UnitTestCommon; + +namespace DotnetEventpipeTest +{ + [TestClass] + public class DotnetTraceTests + { + public static bool IsTraceProcessed = false; + public static object IsTraceProcessedLock = new object(); + + private static RuntimeExecutionResults RuntimeExecutionResults; + + + public static void ProcessTrace() + { + lock (IsTraceProcessedLock) + { + if (!IsTraceProcessed) + { + // Input data + string[] dotnetTraceData = { @"..\..\..\..\TestData\Dotnet-Trace\dotnet_20220517_102703.nettrace" }; + var dotnetTraceDataPath = new FileInfo(dotnetTraceData[0]); + Assert.IsTrue(dotnetTraceDataPath.Exists); + + // Approach #1 - Engine - Doesn't test tables UI but tests processing + var runtime = Engine.Create(new FileDataSource(dotnetTraceDataPath.FullName)); + + // Enable our various types of data + // Need to use cookers for this to work + + // Enable tables used by UI + runtime.EnableTable(CpuSamplingTable.TableDescriptor); + + // + // Process our data. + // + + RuntimeExecutionResults = runtime.Process(); + + UnitTest.TestTableBuild(RuntimeExecutionResults, CpuSamplingTable.TableDescriptor, 103800); + + IsTraceProcessed = true; + } + } + } + + [TestMethod] + public void DotnetTraceTest() + { + ProcessTrace(); + } + } +} diff --git a/Microsoft-Perf-Tools-Linux-Android.sln b/Microsoft-Perf-Tools-Linux-Android.sln index 27815d4..4d0b0da 100644 --- a/Microsoft-Perf-Tools-Linux-Android.sln +++ b/Microsoft-Perf-Tools-Linux-Android.sln @@ -1,180 +1,193 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CtfPlayback", "CtfPlayback\CtfPlayback.csproj", "{85A74448-3940-481D-B449-EF7FCAC02290}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LTTngCds", "LTTngCds\LTTngCds.csproj", "{E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LTTngDataExtensions", "LTTngDataExtensions\LTTngDataExtensions.csproj", "{8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CtfUnitTest", "CtfUnitTest\CtfUnitTest.csproj", "{FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfCds", "PerfCds\PerfCds.csproj", "{DF90DB4D-93B8-498F-8DA9-557495BBFCB6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfDataExtensions", "PerfDataExtensions\PerfDataExtensions.csproj", "{B381F72E-335C-4842-9DB2-615D5C18A943}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LinuxLogParsers", "LinuxLogParsers", "{9B2D4167-1CC7-4D8B-A01C-E9919E809A0A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxLogParserCore", "LinuxLogParsers\LinuxLogParserCore\LinuxLogParserCore.csproj", "{5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxLogParser", "LinuxLogParsers\LinuxLogParser\LinuxLogParser.csproj", "{03DBB240-C4DD-4FF4-9405-F50CBF0A960D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloud-Init", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\Cloud-init\Cloud-Init.csproj", "{7CBFAE62-0D0D-4075-A426-99945DD4E9A8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dmesg", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\DmesgIsoLog\Dmesg.csproj", "{04455C36-F127-4D40-BCB3-78E7DE21E70E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WaLinuxAgent", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\WaLinuxAgent\WaLinuxAgent.csproj", "{75AF82A4-AF0E-425F-8CF5-62F4F54380B9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{E48222FC-D167-4281-BC94-961C22908B25}" - ProjectSection(SolutionItems) = preProject - CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md - LinuxTraceLogCapture.md = LinuxTraceLogCapture.md - NOTICE.md = NOTICE.md - README.md = README.md - SECURITY.md = SECURITY.md - SUPPORT.md = SUPPORT.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxLogParsersUnitTest", "LinuxLogParsers\LinuxLogParsersUnitTest\LinuxLogParsersUnitTest.csproj", "{F910A8EA-9B0F-4BDA-87D4-0765EE973421}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LTTngDriver", "LTTngDriver\LTTngDriver.csproj", "{355F2F1D-8500-4DEA-994E-D456771247CB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestCommon", "UnitTestCommon\UnitTestCommon.csproj", "{20968FBC-74B7-4A65-9936-9F9C2EF8771B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LTTngDataExtUnitTest", "LTTngDataExtUnitTest\LTTngDataExtUnitTest.csproj", "{5750B61D-C1FD-46E7-A175-DB515C6185B0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfUnitTest", "PerfUnitTest\PerfUnitTest.csproj", "{159F637D-6AB1-4E7F-878E-E2C46CF2D920}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfettoCds", "PerfettoCds\PerfettoCds.csproj", "{83418D84-CACE-40E8-A0C3-8EEAF7E71969}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfettoProcessor", "PerfettoProcessor\PerfettoProcessor.csproj", "{90FE5422-631F-4B5A-8EF7-88FE542739DF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfettoUnitTest", "PerfettoUnitTest\PerfettoUnitTest.csproj", "{CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utilities", "Utilities\Utilities.csproj", "{0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Launcher", "Launcher", "{6A9515FC-D4B2-4221-8E74-78E7AFF0E421}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windows", "Windows", "{DB7FCF02-C949-4545-B58C-F514E85AB1FA}" - ProjectSection(SolutionItems) = preProject - Launcher\Windows\LaunchWpaPerfToolsLinuxAndroid.bat = Launcher\Windows\LaunchWpaPerfToolsLinuxAndroid.bat - Launcher\Windows\LaunchWpaPerfToolsLinuxAndroid.ps1 = Launcher\Windows\LaunchWpaPerfToolsLinuxAndroid.ps1 - Launcher\Windows\MicrosoftPerfToolsLinuxAndroid.url = Launcher\Windows\MicrosoftPerfToolsLinuxAndroid.url - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{38D9C39A-AB30-4DED-BA93-2638388FC57C}" - ProjectSection(SolutionItems) = preProject - azure-pipelines.yml = azure-pipelines.yml - EndProjectSection -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AndroidLogcat", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\AndroidLogCat\AndroidLogcat.csproj", "{9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {85A74448-3940-481D-B449-EF7FCAC02290}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85A74448-3940-481D-B449-EF7FCAC02290}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85A74448-3940-481D-B449-EF7FCAC02290}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85A74448-3940-481D-B449-EF7FCAC02290}.Release|Any CPU.Build.0 = Release|Any CPU - {E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}.Release|Any CPU.Build.0 = Release|Any CPU - {8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}.Release|Any CPU.Build.0 = Release|Any CPU - {FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}.Release|Any CPU.Build.0 = Release|Any CPU - {DF90DB4D-93B8-498F-8DA9-557495BBFCB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF90DB4D-93B8-498F-8DA9-557495BBFCB6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF90DB4D-93B8-498F-8DA9-557495BBFCB6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF90DB4D-93B8-498F-8DA9-557495BBFCB6}.Release|Any CPU.Build.0 = Release|Any CPU - {B381F72E-335C-4842-9DB2-615D5C18A943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B381F72E-335C-4842-9DB2-615D5C18A943}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B381F72E-335C-4842-9DB2-615D5C18A943}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B381F72E-335C-4842-9DB2-615D5C18A943}.Release|Any CPU.Build.0 = Release|Any CPU - {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Release|Any CPU.Build.0 = Release|Any CPU - {03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Release|Any CPU.Build.0 = Release|Any CPU - {7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Release|Any CPU.Build.0 = Release|Any CPU - {04455C36-F127-4D40-BCB3-78E7DE21E70E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {04455C36-F127-4D40-BCB3-78E7DE21E70E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {04455C36-F127-4D40-BCB3-78E7DE21E70E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {04455C36-F127-4D40-BCB3-78E7DE21E70E}.Release|Any CPU.Build.0 = Release|Any CPU - {75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Release|Any CPU.Build.0 = Release|Any CPU - {F910A8EA-9B0F-4BDA-87D4-0765EE973421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F910A8EA-9B0F-4BDA-87D4-0765EE973421}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F910A8EA-9B0F-4BDA-87D4-0765EE973421}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F910A8EA-9B0F-4BDA-87D4-0765EE973421}.Release|Any CPU.Build.0 = Release|Any CPU - {355F2F1D-8500-4DEA-994E-D456771247CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {355F2F1D-8500-4DEA-994E-D456771247CB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {355F2F1D-8500-4DEA-994E-D456771247CB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {355F2F1D-8500-4DEA-994E-D456771247CB}.Release|Any CPU.Build.0 = Release|Any CPU - {20968FBC-74B7-4A65-9936-9F9C2EF8771B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {20968FBC-74B7-4A65-9936-9F9C2EF8771B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {20968FBC-74B7-4A65-9936-9F9C2EF8771B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {20968FBC-74B7-4A65-9936-9F9C2EF8771B}.Release|Any CPU.Build.0 = Release|Any CPU - {5750B61D-C1FD-46E7-A175-DB515C6185B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5750B61D-C1FD-46E7-A175-DB515C6185B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5750B61D-C1FD-46E7-A175-DB515C6185B0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5750B61D-C1FD-46E7-A175-DB515C6185B0}.Release|Any CPU.Build.0 = Release|Any CPU - {159F637D-6AB1-4E7F-878E-E2C46CF2D920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {159F637D-6AB1-4E7F-878E-E2C46CF2D920}.Debug|Any CPU.Build.0 = Debug|Any CPU - {159F637D-6AB1-4E7F-878E-E2C46CF2D920}.Release|Any CPU.ActiveCfg = Release|Any CPU - {159F637D-6AB1-4E7F-878E-E2C46CF2D920}.Release|Any CPU.Build.0 = Release|Any CPU - {83418D84-CACE-40E8-A0C3-8EEAF7E71969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {83418D84-CACE-40E8-A0C3-8EEAF7E71969}.Debug|Any CPU.Build.0 = Debug|Any CPU - {83418D84-CACE-40E8-A0C3-8EEAF7E71969}.Release|Any CPU.ActiveCfg = Release|Any CPU - {83418D84-CACE-40E8-A0C3-8EEAF7E71969}.Release|Any CPU.Build.0 = Release|Any CPU - {90FE5422-631F-4B5A-8EF7-88FE542739DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90FE5422-631F-4B5A-8EF7-88FE542739DF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90FE5422-631F-4B5A-8EF7-88FE542739DF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90FE5422-631F-4B5A-8EF7-88FE542739DF}.Release|Any CPU.Build.0 = Release|Any CPU - {CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}.Release|Any CPU.Build.0 = Release|Any CPU - {0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}.Release|Any CPU.Build.0 = Release|Any CPU - {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} - {03DBB240-C4DD-4FF4-9405-F50CBF0A960D} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} - {7CBFAE62-0D0D-4075-A426-99945DD4E9A8} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} - {04455C36-F127-4D40-BCB3-78E7DE21E70E} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} - {75AF82A4-AF0E-425F-8CF5-62F4F54380B9} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} - {F910A8EA-9B0F-4BDA-87D4-0765EE973421} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} - {DB7FCF02-C949-4545-B58C-F514E85AB1FA} = {6A9515FC-D4B2-4221-8E74-78E7AFF0E421} - {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E96603EA-8E1D-4AA9-A474-D267A116C316} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CtfPlayback", "CtfPlayback\CtfPlayback.csproj", "{85A74448-3940-481D-B449-EF7FCAC02290}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LTTngCds", "LTTngCds\LTTngCds.csproj", "{E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LTTngDataExtensions", "LTTngDataExtensions\LTTngDataExtensions.csproj", "{8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CtfUnitTest", "CtfUnitTest\CtfUnitTest.csproj", "{FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfCds", "PerfCds\PerfCds.csproj", "{DF90DB4D-93B8-498F-8DA9-557495BBFCB6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfDataExtensions", "PerfDataExtensions\PerfDataExtensions.csproj", "{B381F72E-335C-4842-9DB2-615D5C18A943}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LinuxLogParsers", "LinuxLogParsers", "{9B2D4167-1CC7-4D8B-A01C-E9919E809A0A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxLogParserCore", "LinuxLogParsers\LinuxLogParserCore\LinuxLogParserCore.csproj", "{5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxLogParser", "LinuxLogParsers\LinuxLogParser\LinuxLogParser.csproj", "{03DBB240-C4DD-4FF4-9405-F50CBF0A960D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloud-Init", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\Cloud-init\Cloud-Init.csproj", "{7CBFAE62-0D0D-4075-A426-99945DD4E9A8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dmesg", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\DmesgIsoLog\Dmesg.csproj", "{04455C36-F127-4D40-BCB3-78E7DE21E70E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WaLinuxAgent", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\WaLinuxAgent\WaLinuxAgent.csproj", "{75AF82A4-AF0E-425F-8CF5-62F4F54380B9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{E48222FC-D167-4281-BC94-961C22908B25}" + ProjectSection(SolutionItems) = preProject + CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md + LinuxTraceLogCapture.md = LinuxTraceLogCapture.md + NOTICE.md = NOTICE.md + README.md = README.md + SECURITY.md = SECURITY.md + SUPPORT.md = SUPPORT.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxLogParsersUnitTest", "LinuxLogParsers\LinuxLogParsersUnitTest\LinuxLogParsersUnitTest.csproj", "{F910A8EA-9B0F-4BDA-87D4-0765EE973421}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LTTngDriver", "LTTngDriver\LTTngDriver.csproj", "{355F2F1D-8500-4DEA-994E-D456771247CB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestCommon", "UnitTestCommon\UnitTestCommon.csproj", "{20968FBC-74B7-4A65-9936-9F9C2EF8771B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LTTngDataExtUnitTest", "LTTngDataExtUnitTest\LTTngDataExtUnitTest.csproj", "{5750B61D-C1FD-46E7-A175-DB515C6185B0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfUnitTest", "PerfUnitTest\PerfUnitTest.csproj", "{159F637D-6AB1-4E7F-878E-E2C46CF2D920}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfettoCds", "PerfettoCds\PerfettoCds.csproj", "{83418D84-CACE-40E8-A0C3-8EEAF7E71969}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfettoProcessor", "PerfettoProcessor\PerfettoProcessor.csproj", "{90FE5422-631F-4B5A-8EF7-88FE542739DF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfettoUnitTest", "PerfettoUnitTest\PerfettoUnitTest.csproj", "{CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utilities", "Utilities\Utilities.csproj", "{0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Launcher", "Launcher", "{6A9515FC-D4B2-4221-8E74-78E7AFF0E421}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windows", "Windows", "{DB7FCF02-C949-4545-B58C-F514E85AB1FA}" + ProjectSection(SolutionItems) = preProject + Launcher\Windows\LaunchWpaPerfToolsLinuxAndroid.bat = Launcher\Windows\LaunchWpaPerfToolsLinuxAndroid.bat + Launcher\Windows\LaunchWpaPerfToolsLinuxAndroid.ps1 = Launcher\Windows\LaunchWpaPerfToolsLinuxAndroid.ps1 + Launcher\Windows\MicrosoftPerfToolsLinuxAndroid.url = Launcher\Windows\MicrosoftPerfToolsLinuxAndroid.url + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{38D9C39A-AB30-4DED-BA93-2638388FC57C}" + ProjectSection(SolutionItems) = preProject + azure-pipelines.yml = azure-pipelines.yml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AndroidLogcat", "LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\AndroidLogCat\AndroidLogcat.csproj", "{9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetEventpipe", "DotNetEventPipe\DotnetEventpipe.csproj", "{863E041F-0715-4B08-A1B8-B5CFC027ED6B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotnetEventpipeTest", "DotnetEventpipeTest\DotnetEventpipeTest.csproj", "{46861BDC-350C-45ED-A72E-9661C3BBE2D8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85A74448-3940-481D-B449-EF7FCAC02290}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85A74448-3940-481D-B449-EF7FCAC02290}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85A74448-3940-481D-B449-EF7FCAC02290}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85A74448-3940-481D-B449-EF7FCAC02290}.Release|Any CPU.Build.0 = Release|Any CPU + {E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4EB869C-DBF9-4C7F-95BA-6D28DBC69FD0}.Release|Any CPU.Build.0 = Release|Any CPU + {8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8EE62907-1246-48FA-9AD5-D1DFEC7F26F8}.Release|Any CPU.Build.0 = Release|Any CPU + {FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC2236A8-FBE7-43BC-86A1-CBC364DFDF91}.Release|Any CPU.Build.0 = Release|Any CPU + {DF90DB4D-93B8-498F-8DA9-557495BBFCB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF90DB4D-93B8-498F-8DA9-557495BBFCB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF90DB4D-93B8-498F-8DA9-557495BBFCB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF90DB4D-93B8-498F-8DA9-557495BBFCB6}.Release|Any CPU.Build.0 = Release|Any CPU + {B381F72E-335C-4842-9DB2-615D5C18A943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B381F72E-335C-4842-9DB2-615D5C18A943}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B381F72E-335C-4842-9DB2-615D5C18A943}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B381F72E-335C-4842-9DB2-615D5C18A943}.Release|Any CPU.Build.0 = Release|Any CPU + {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4}.Release|Any CPU.Build.0 = Release|Any CPU + {03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03DBB240-C4DD-4FF4-9405-F50CBF0A960D}.Release|Any CPU.Build.0 = Release|Any CPU + {7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CBFAE62-0D0D-4075-A426-99945DD4E9A8}.Release|Any CPU.Build.0 = Release|Any CPU + {04455C36-F127-4D40-BCB3-78E7DE21E70E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04455C36-F127-4D40-BCB3-78E7DE21E70E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04455C36-F127-4D40-BCB3-78E7DE21E70E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04455C36-F127-4D40-BCB3-78E7DE21E70E}.Release|Any CPU.Build.0 = Release|Any CPU + {75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75AF82A4-AF0E-425F-8CF5-62F4F54380B9}.Release|Any CPU.Build.0 = Release|Any CPU + {F910A8EA-9B0F-4BDA-87D4-0765EE973421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F910A8EA-9B0F-4BDA-87D4-0765EE973421}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F910A8EA-9B0F-4BDA-87D4-0765EE973421}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F910A8EA-9B0F-4BDA-87D4-0765EE973421}.Release|Any CPU.Build.0 = Release|Any CPU + {355F2F1D-8500-4DEA-994E-D456771247CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {355F2F1D-8500-4DEA-994E-D456771247CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {355F2F1D-8500-4DEA-994E-D456771247CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {355F2F1D-8500-4DEA-994E-D456771247CB}.Release|Any CPU.Build.0 = Release|Any CPU + {20968FBC-74B7-4A65-9936-9F9C2EF8771B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20968FBC-74B7-4A65-9936-9F9C2EF8771B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20968FBC-74B7-4A65-9936-9F9C2EF8771B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20968FBC-74B7-4A65-9936-9F9C2EF8771B}.Release|Any CPU.Build.0 = Release|Any CPU + {5750B61D-C1FD-46E7-A175-DB515C6185B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5750B61D-C1FD-46E7-A175-DB515C6185B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5750B61D-C1FD-46E7-A175-DB515C6185B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5750B61D-C1FD-46E7-A175-DB515C6185B0}.Release|Any CPU.Build.0 = Release|Any CPU + {159F637D-6AB1-4E7F-878E-E2C46CF2D920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {159F637D-6AB1-4E7F-878E-E2C46CF2D920}.Debug|Any CPU.Build.0 = Debug|Any CPU + {159F637D-6AB1-4E7F-878E-E2C46CF2D920}.Release|Any CPU.ActiveCfg = Release|Any CPU + {159F637D-6AB1-4E7F-878E-E2C46CF2D920}.Release|Any CPU.Build.0 = Release|Any CPU + {83418D84-CACE-40E8-A0C3-8EEAF7E71969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83418D84-CACE-40E8-A0C3-8EEAF7E71969}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83418D84-CACE-40E8-A0C3-8EEAF7E71969}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83418D84-CACE-40E8-A0C3-8EEAF7E71969}.Release|Any CPU.Build.0 = Release|Any CPU + {90FE5422-631F-4B5A-8EF7-88FE542739DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90FE5422-631F-4B5A-8EF7-88FE542739DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90FE5422-631F-4B5A-8EF7-88FE542739DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90FE5422-631F-4B5A-8EF7-88FE542739DF}.Release|Any CPU.Build.0 = Release|Any CPU + {CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF89184C-8CE8-4656-9C5C-3F6C547EF2A3}.Release|Any CPU.Build.0 = Release|Any CPU + {0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BA62F1B-1456-48FD-B2DE-C9FE1CB94B8B}.Release|Any CPU.Build.0 = Release|Any CPU + {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE}.Release|Any CPU.Build.0 = Release|Any CPU + {863E041F-0715-4B08-A1B8-B5CFC027ED6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {863E041F-0715-4B08-A1B8-B5CFC027ED6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {863E041F-0715-4B08-A1B8-B5CFC027ED6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {863E041F-0715-4B08-A1B8-B5CFC027ED6B}.Release|Any CPU.Build.0 = Release|Any CPU + {46861BDC-350C-45ED-A72E-9661C3BBE2D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46861BDC-350C-45ED-A72E-9661C3BBE2D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46861BDC-350C-45ED-A72E-9661C3BBE2D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46861BDC-350C-45ED-A72E-9661C3BBE2D8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5B8667E9-0CB6-4562-B2BD-B5E7768BC1A4} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} + {03DBB240-C4DD-4FF4-9405-F50CBF0A960D} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} + {7CBFAE62-0D0D-4075-A426-99945DD4E9A8} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} + {04455C36-F127-4D40-BCB3-78E7DE21E70E} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} + {75AF82A4-AF0E-425F-8CF5-62F4F54380B9} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} + {F910A8EA-9B0F-4BDA-87D4-0765EE973421} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} + {DB7FCF02-C949-4545-B58C-F514E85AB1FA} = {6A9515FC-D4B2-4221-8E74-78E7AFF0E421} + {9FD0BECC-AC5E-46A8-9749-46CD82BA89DE} = {9B2D4167-1CC7-4D8B-A01C-E9919E809A0A} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E96603EA-8E1D-4AA9-A474-D267A116C316} + EndGlobalSection +EndGlobal diff --git a/PerfDataExtensions/PerfDataExtensions.csproj b/PerfDataExtensions/PerfDataExtensions.csproj index 5837844..d0ac281 100644 --- a/PerfDataExtensions/PerfDataExtensions.csproj +++ b/PerfDataExtensions/PerfDataExtensions.csproj @@ -61,7 +61,7 @@ - + compile diff --git a/PerfDataExtensions/SourceDataCookers/PerfDataProcessingSource.cs b/PerfDataExtensions/SourceDataCookers/PerfDataProcessingSource.cs index da166b3..c1818b7 100644 --- a/PerfDataExtensions/SourceDataCookers/PerfDataProcessingSource.cs +++ b/PerfDataExtensions/SourceDataCookers/PerfDataProcessingSource.cs @@ -9,18 +9,6 @@ namespace PerfDataProcessingSource { - // - // This is a sample Custom Data Source (CDS) that understands files with the .txt extension - // - - // In order for a CDS to be recognized, it MUST satisfy the following: - // a) Be a public type - // b) Have a public parameterless constructor - // c) Implement the IProcessingSource interface - // d) Be decorated with the ProcessingSourceAttribute attribute - // e) Be decorated with at least one of the derivatives of the DataSourceAttribute attribute - // - [ProcessingSource( "{EA48A279-2B4E-43A0-AC86-030113A23064}", // 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 "Linux Perf Txt Data", // The Custom Data Source MUST have a name @@ -29,13 +17,6 @@ namespace PerfDataProcessingSource ".txt", // A file extension is REQUIRED "Linux perf.data.txt parser")] // 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 PerfDataProcessingSource : ProcessingSource @@ -69,12 +50,6 @@ protected override ICustomDataProcessor CreateProcessorCore( 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 PerfDataCustomDataProcessor( dataSources.Select(x => x.Uri.LocalPath).ToArray(), options, diff --git a/PerfDataExtensions/Tables/CpuSamplingTable.cs b/PerfDataExtensions/Tables/CpuSamplingTable.cs index ab6bd76..ccdce64 100644 --- a/PerfDataExtensions/Tables/CpuSamplingTable.cs +++ b/PerfDataExtensions/Tables/CpuSamplingTable.cs @@ -11,8 +11,8 @@ using PerfDataExtensions.SourceDataCookers.Cpu; using PerfDataExtensions.Tables.Generators; using Utilities.AccessProviders; -using static PerfDataExtensions.Tables.TimeHelper; - +using static Utilities.TimeHelper; + namespace PerfDataExtensions.Tables { [Table] diff --git a/PerfDataExtensions/Tables/PerfTxtCpuSamplingTable.cs b/PerfDataExtensions/Tables/PerfTxtCpuSamplingTable.cs index a46888d..bec5eaa 100644 --- a/PerfDataExtensions/Tables/PerfTxtCpuSamplingTable.cs +++ b/PerfDataExtensions/Tables/PerfTxtCpuSamplingTable.cs @@ -12,8 +12,8 @@ using Microsoft.Performance.SDK.Processing; using PerfDataExtensions.Tables.Generators; using Utilities.AccessProviders; -using static PerfDataExtensions.Tables.TimeHelper; - +using static Utilities.TimeHelper; + namespace PerfDataExtensions.Tables { // diff --git a/PerfUnitTest/PerfUnitTest.csproj b/PerfUnitTest/PerfUnitTest.csproj index b68c7b2..039d502 100644 --- a/PerfUnitTest/PerfUnitTest.csproj +++ b/PerfUnitTest/PerfUnitTest.csproj @@ -7,7 +7,7 @@ - + diff --git a/UnitTestCommon/UnitTest.cs b/UnitTestCommon/UnitTest.cs index 9e83816..8af9d2f 100644 --- a/UnitTestCommon/UnitTest.cs +++ b/UnitTestCommon/UnitTest.cs @@ -19,7 +19,10 @@ public static void TestTableBuild(RuntimeExecutionResults runtimeExecutionResult if (skipDataAvailableCheck || isTableDataAvailable) { var tableResult = runtimeExecutionResults.BuildTable(tableDescriptor); - Assert.IsTrue(tableResult.RowCount == expectedCount); + if (tableResult.RowCount != expectedCount) + { + throw new Exception($"We have {tableResult.RowCount} rows, but we expected {expectedCount} rows"); + } var tableData = tableResult.GetDataForAllRows(); Assert.IsTrue(tableData.Length == expectedCount); } diff --git a/PerfDataExtensions/Tables/TimeHelper.cs b/Utilities/TimeHelper.cs similarity index 95% rename from PerfDataExtensions/Tables/TimeHelper.cs rename to Utilities/TimeHelper.cs index 174dd1f..9edc207 100644 --- a/PerfDataExtensions/Tables/TimeHelper.cs +++ b/Utilities/TimeHelper.cs @@ -3,7 +3,7 @@ using Microsoft.Performance.SDK; -namespace PerfDataExtensions.Tables +namespace Utilities { public static class TimeHelper { From a36fa9af76a6ab6d8072c6f53d2b28c48f329351 Mon Sep 17 00:00:00 2001 From: Ivan Berg Date: Fri, 20 May 2022 13:04:30 -0700 Subject: [PATCH 2/9] Add support for Generic Events --- .../DataOutputTypes/GenericEvent.cs | 63 ++++++ .../TraceCallStackProcessed.cs | 14 ++ .../DotnetTraceDataProcessor.cs | 5 +- ...urce.cs => DotnetTraceProcessingSource.cs} | 0 .../SourceDataCookers/TraceEventProcessor.cs | 85 ++++++-- DotNetEventPipe/Tables/CpuSamplingTable.cs | 6 +- DotNetEventPipe/Tables/GenericEventTable.cs | 198 ++++++++++++++++++ DotnetEventpipeTest/DotnetTraceTests.cs | 2 + 8 files changed, 346 insertions(+), 27 deletions(-) create mode 100644 DotNetEventPipe/DataOutputTypes/GenericEvent.cs create mode 100644 DotNetEventPipe/DataOutputTypes/TraceCallStackProcessed.cs rename DotNetEventPipe/SourceDataCookers/{PerfDataProcessingSource.cs => DotnetTraceProcessingSource.cs} (100%) create mode 100644 DotNetEventPipe/Tables/GenericEventTable.cs diff --git a/DotNetEventPipe/DataOutputTypes/GenericEvent.cs b/DotNetEventPipe/DataOutputTypes/GenericEvent.cs new file mode 100644 index 0000000..3440e9e --- /dev/null +++ b/DotNetEventPipe/DataOutputTypes/GenericEvent.cs @@ -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 +{ + /// + /// A GenericEvent + /// + public readonly struct 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; } + /// + /// filename of the binary / library for the instruction pointer + /// + public TraceModuleFile Module { get; } + /// + /// Functionname of the instruction pointer + /// + 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); + } + } +} diff --git a/DotNetEventPipe/DataOutputTypes/TraceCallStackProcessed.cs b/DotNetEventPipe/DataOutputTypes/TraceCallStackProcessed.cs new file mode 100644 index 0000000..745c317 --- /dev/null +++ b/DotNetEventPipe/DataOutputTypes/TraceCallStackProcessed.cs @@ -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; } + } +} diff --git a/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs b/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs index b5844ef..597d481 100644 --- a/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs +++ b/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs @@ -63,7 +63,9 @@ protected override Task ProcessAsyncCore( this.dataSourceInfo = new DataSourceInfo(0, traceSource.SessionDuration.Ticks * 100, traceSource.SessionStartTime.ToUniversalTime()); } - string traceLogPath = TraceLog.CreateFromEventPipeDataFile(path); + 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(); @@ -74,6 +76,7 @@ protected override Task ProcessAsyncCore( source.Process(); //this.dataSourceInfo = new DataSourceInfo(0, source.SessionDuration.Ticks * 100, source.SessionStartTime.ToUniversalTime()); } + File.Delete(tmpEtlx); } this.fileContent = new ReadOnlyDictionary(contentDictionary); diff --git a/DotNetEventPipe/SourceDataCookers/PerfDataProcessingSource.cs b/DotNetEventPipe/SourceDataCookers/DotnetTraceProcessingSource.cs similarity index 100% rename from DotNetEventPipe/SourceDataCookers/PerfDataProcessingSource.cs rename to DotNetEventPipe/SourceDataCookers/DotnetTraceProcessingSource.cs diff --git a/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs b/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs index 71c30e3..cbd8955 100644 --- a/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs +++ b/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs @@ -1,9 +1,11 @@ -using DotNetEventPipe.DataOutputTypes; +using DotnetEventpipe.DataOutputTypes; +using DotNetEventPipe.DataOutputTypes; using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Etlx; using Microsoft.Diagnostics.Tracing.EventPipe; using System; using System.Collections.Generic; +using System.Linq; using System.Text; using Utilities; @@ -12,7 +14,11 @@ namespace DotNetEventPipe public class TraceEventProcessor { const uint MSEC_TO_NS = 1000000; - public List ThreadSamplingEvents = new List(); + public IReadOnlyList ThreadSamplingEvents => threadSamplingEvents.AsReadOnly(); + List threadSamplingEvents = new List(); + + public IReadOnlyList GenericEvents => genericEvents.AsReadOnly(); + List genericEvents = new List(); // TODO - Move this to a DataCooker @@ -21,28 +27,13 @@ public void ProcessTraceEvent(TraceEvent data) string eventName = data.ProviderName + "/" + data.EventName; TraceCallStack stack = data.CallStack(); + var stackProcessed = ProcessCallStack(stack); switch (data.EventName) { case "Thread/Sample": var clrTS = (ClrThreadSampleTraceData) data; - string[] callStack = null; - TraceModuleFile module = null; - string fullMethodName = null; - if (stack != null) - { - module = stack.CodeAddress.ModuleFile; - fullMethodName = stack.CodeAddress.FullMethodName; - callStack = new string[stack.Depth]; - - TraceCallStack current = stack; - while (current != null) - { - callStack[current.Depth-1] = Common.StringIntern($"{current.CodeAddress.ModuleName}!{current.CodeAddress.FullMethodName}"); - current = current.Caller; - } - } var threadSamplingEvent = new ThreadSamplingEvent( clrTS.ProcessID, @@ -50,16 +41,64 @@ public void ProcessTraceEvent(TraceEvent data) clrTS.ProcessorNumber, clrTS.ThreadID, new Microsoft.Performance.SDK.Timestamp((long)(clrTS.TimeStampRelativeMSec * MSEC_TO_NS)), - callStack, - module, - fullMethodName + stackProcessed?.CallStack, + stackProcessed?.Module, + stackProcessed?.FullMethodName ); - ThreadSamplingEvents.Add(threadSamplingEvent); + threadSamplingEvents.Add(threadSamplingEvent); break; default: - // TODO all other events go to GenericEvents + var payLoadValues = new object[data.PayloadNames.Length]; + for (int i=0; i < data.PayloadNames.Length; i++) + { + payLoadValues[i] = data.PayloadValue(i); + } + + var genericEvent = new GenericEvent( + data.EventName, + data.ID, + data.Keywords, + data.Level, + data.Opcode, + data.OpcodeName, + data.PayloadNames, + payLoadValues, + data.ProviderGuid, + data.ProviderName, + data.ProcessID, + data.ProcessName, + data.ProcessorNumber, + data.ThreadID, + new Microsoft.Performance.SDK.Timestamp((long)(data.TimeStampRelativeMSec * MSEC_TO_NS)), + stackProcessed?.CallStack, + stackProcessed?.Module, + stackProcessed?.FullMethodName + ); + genericEvents.Add(genericEvent); break; } } + + public TraceCallStackProcessed ProcessCallStack(TraceCallStack stack) + { + if (stack == null) + { + return null; + } + + var traceCallStackProcessed = new TraceCallStackProcessed(); + traceCallStackProcessed.Module = stack.CodeAddress.ModuleFile; + traceCallStackProcessed.FullMethodName = stack.CodeAddress.FullMethodName; + traceCallStackProcessed.CallStack = new string[stack.Depth]; + + TraceCallStack current = stack; + while (current != null) + { + traceCallStackProcessed.CallStack[current.Depth - 1] = Common.StringIntern($"{current.CodeAddress.ModuleName}!{current.CodeAddress.FullMethodName}"); + current = current.Caller; + } + + return traceCallStackProcessed; + } } } diff --git a/DotNetEventPipe/Tables/CpuSamplingTable.cs b/DotNetEventPipe/Tables/CpuSamplingTable.cs index 1d81792..82ed4d7 100644 --- a/DotNetEventPipe/Tables/CpuSamplingTable.cs +++ b/DotNetEventPipe/Tables/CpuSamplingTable.cs @@ -19,9 +19,9 @@ public sealed class CpuSamplingTable { public static readonly TableDescriptor TableDescriptor = new TableDescriptor( Guid.Parse("{29C3ECF1-0857-4A3D-B6E3-2197CE1E9C81}"), - ".NET (dotnet)", - ".nettrace (dotnet-trace)", - category: ".NET"); + "CPU Sampling", + "Thread Profiler Samples", + category: ".NET trace (dotnet-trace)"); public CpuSamplingTable(IReadOnlyDictionary traceEventProcessor) : base(traceEventProcessor) diff --git a/DotNetEventPipe/Tables/GenericEventTable.cs b/DotNetEventPipe/Tables/GenericEventTable.cs new file mode 100644 index 0000000..cdef6e7 --- /dev/null +++ b/DotNetEventPipe/Tables/GenericEventTable.cs @@ -0,0 +1,198 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Performance.SDK.Processing; +using Utilities; +using Utilities.AccessProviders; +using static Utilities.TimeHelper; + +namespace DotNetEventPipe.Tables +{ + // + // Have the MetadataTable inherit the TableBase class + // + [Table] // A category is optional. It useful for grouping different types of tables + public sealed class GenericEventTable + : TraceEventTableBase + { + public static readonly TableDescriptor TableDescriptor = new TableDescriptor( + Guid.Parse("{50315806-167F-46FF-946A-EA8B21F33F36}"), + "Generic Events", + "Generic Events", + category: ".NET trace (dotnet-trace)"); + + public GenericEventTable(IReadOnlyDictionary traceEventProcessor) + : base(traceEventProcessor) + { + } + + private static readonly ColumnConfiguration eventNameColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{7AE1590E-20D3-4F39-90D1-4AEE5CD41394}"), "EventName", "Event Name"), + new UIHints { Width = 305 }); + + private static readonly ColumnConfiguration idColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{75FE88C4-CB8E-46E5-B8F4-CE760B8A6F51}"), "ID", "Event ID"), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration keywordsColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{54145FD1-2EF3-4A6D-9914-44ECCBBF8532}"), "Keywords", "Keywords"), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration levelColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{A2DCBDE1-4899-49E2-8096-BE34BB10674F}"), "Level", "Level"), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration opcodeColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{520E92D2-F4DE-432E-80EE-8A557D5E6BD1}"), "Opcode", "Opcode"), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration opcodeNameColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{912561AE-BDE2-4745-8504-D4E696C6F800}"), "OpcodeName", "Opcode Name"), + new UIHints { Width = 170 }); + + private static readonly ColumnConfiguration providerGuidColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{5C13098C-579C-443E-8476-3187FF532966}"), "ProviderGuid", "Provider Guid"), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration providerNameColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{93C151D8-53D0-4AAF-AFDE-BBB2D516F59A}"), "Provider Name", "Provider Name"), + new UIHints { Width = 270 }); + + private static readonly ColumnConfiguration timestampColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{F5AE0530-DC46-41B3-A0E9-416955A9C7CA}"), "Timestamp", "The timestamp of the event"), + new UIHints { Width = 80 }); + + + private static readonly ColumnConfiguration countColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{06B669F7-A719-4B4C-9F1F-0B50B6D20EF7}"), "Count", "The count of samples"), + new UIHints { + Width = 130, + AggregationMode = AggregationMode.Sum, + SortOrder = SortOrder.Descending, + SortPriority = 0, + }); + + private static readonly ColumnConfiguration hascallStackColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{2BCEC64D-A3A2-4EC5-B376-4040560066FC}"), "Has Callstack"), + new UIHints { Width = 40, }); + + private static readonly ColumnConfiguration callStackColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{37EF26BF-8FAF-4700-AD66-01D90DA743BF}"), "Callstack"), + new UIHints { Width = 800, }); + + private static readonly ColumnConfiguration threadIdColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{4EEBBEF3-C5E5-422C-AF6D-B94CE17AB3DF}"), "ThreadId"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration processColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{E3FEEE4F-CECC-4BEA-A926-253869ADD223}"), "Process"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration processIdColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{E40AED70-CAF0-4B24-AE44-3EE02FD2A5BD}"), "Process Id"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration cpuColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{5AAB0226-0599-427C-B84F-0E1A86CE6B73}"), "CPU"), + new UIHints { Width = 80, }); + + + public override void Build(ITableBuilder tableBuilder) + { + if (TraceEventProcessor == null || TraceEventProcessor.Count == 0) + { + return; + } + + var firstTraceProcessorEventsParsed = TraceEventProcessor.First().Value; // First Log + var genericEvents = firstTraceProcessorEventsParsed.GenericEvents; + + var tableGenerator = tableBuilder.SetRowCount(genericEvents.Count); + var baseProjection = Projection.Index(genericEvents); + + var maximumFieldCount = 0; + foreach (var genericEvent in genericEvents) + { + maximumFieldCount = Math.Max(maximumFieldCount, genericEvent.PayloadValues.Length); + } + + tableGenerator.AddColumn(countColumn, baseProjection.Compose(x => 1)); // 1 sample + tableGenerator.AddColumn(eventNameColumn, baseProjection.Compose(x => x.EventName)); + tableGenerator.AddColumn(idColumn, baseProjection.Compose(x => x.ID)); + tableGenerator.AddColumn(keywordsColumn, baseProjection.Compose(x => x.Keywords)); + tableGenerator.AddColumn(levelColumn, baseProjection.Compose(x => x.Level)); + tableGenerator.AddColumn(opcodeColumn, baseProjection.Compose(x => x.Opcode)); + tableGenerator.AddColumn(opcodeNameColumn, baseProjection.Compose(x => x.OpcodeName)); + tableGenerator.AddColumn(providerGuidColumn, baseProjection.Compose(x => x.ProviderGuid)); + tableGenerator.AddColumn(providerNameColumn, baseProjection.Compose(x => x.ProviderName)); + tableGenerator.AddColumn(processIdColumn, baseProjection.Compose(x => x.ProcessID)); + tableGenerator.AddColumn(processColumn, baseProjection.Compose(x => x.ProcessName)); + tableGenerator.AddColumn(cpuColumn, baseProjection.Compose(x => x.ProcessorNumber)); + tableGenerator.AddColumn(threadIdColumn, baseProjection.Compose(x => x.ThreadID)); + tableGenerator.AddColumn(timestampColumn, baseProjection.Compose(x => x.Timestamp)); + tableGenerator.AddColumn(hascallStackColumn, baseProjection.Compose(x => x.CallStack != null)); + tableGenerator.AddHierarchicalColumn(callStackColumn, baseProjection.Compose(x => x.CallStack), new ArrayAccessProvider()); + + // Add the field columns, with column names depending on the given event + List fieldColumns = new List(); + for (int index = 0; index < maximumFieldCount; index++) + { + var colIndex = index; // This seems unncessary but causes odd runtime behavior if not done this way. Compiler is confused perhaps because w/o this func will index=genericEvent.FieldNames.Count every time. index is passed as ref but colIndex as value into func + string fieldName = "Field " + (colIndex + 1); + + var genericEventFieldNameProjection = baseProjection.Compose((genericEvent) => colIndex < genericEvent.PayloadNames?.Length ? genericEvent.PayloadNames[colIndex] : string.Empty); + + // generate a column configuration + var fieldColumnConfiguration = new ColumnConfiguration( + new ColumnMetadata(Common.GenerateGuidFromName(fieldName), fieldName, genericEventFieldNameProjection, fieldName), + new UIHints + { + IsVisible = true, + Width = 80, + TextAlignment = TextAlignment.Left, + }); + fieldColumns.Add(fieldColumnConfiguration); + + var genericEventFieldAsStringProjection = baseProjection.Compose((genericEvent) => colIndex < genericEvent.PayloadValues?.Length ? genericEvent.PayloadValues[colIndex] : string.Empty); + + tableGenerator.AddColumn(fieldColumnConfiguration, genericEventFieldAsStringProjection); + } + + + List defaultColumns = new List() + { + providerNameColumn, + eventNameColumn, + opcodeNameColumn, + TableConfiguration.PivotColumn, + cpuColumn, + processColumn, + threadIdColumn, + }; + defaultColumns.AddRange(fieldColumns); + defaultColumns.Add(countColumn); + defaultColumns.Add(TableConfiguration.GraphColumn); // Columns after this get graphed + defaultColumns.Add(timestampColumn); + + var actProviderNameOpcode = new TableConfiguration("Activity by Provider, EventName, Opcode") + { + Columns = defaultColumns + }; + actProviderNameOpcode.AddColumnRole(ColumnRole.StartTime, timestampColumn); + actProviderNameOpcode.AddColumnRole(ColumnRole.EndTime, timestampColumn); + + var table = tableBuilder + .AddTableConfiguration(actProviderNameOpcode) + .SetDefaultTableConfiguration(actProviderNameOpcode); + } + } +} diff --git a/DotnetEventpipeTest/DotnetTraceTests.cs b/DotnetEventpipeTest/DotnetTraceTests.cs index bb735fb..45d0527 100644 --- a/DotnetEventpipeTest/DotnetTraceTests.cs +++ b/DotnetEventpipeTest/DotnetTraceTests.cs @@ -36,6 +36,7 @@ public static void ProcessTrace() // Enable tables used by UI runtime.EnableTable(CpuSamplingTable.TableDescriptor); + runtime.EnableTable(GenericEventTable.TableDescriptor); // // Process our data. @@ -44,6 +45,7 @@ public static void ProcessTrace() RuntimeExecutionResults = runtime.Process(); UnitTest.TestTableBuild(RuntimeExecutionResults, CpuSamplingTable.TableDescriptor, 103800); + UnitTest.TestTableBuild(RuntimeExecutionResults, GenericEventTable.TableDescriptor, 26104); // Wrong?? IsTraceProcessed = true; } From 2bc897b90ba95eb4e2b0c3c975d58d99eb5db8fd Mon Sep 17 00:00:00 2001 From: Ivan Berg Date: Sat, 21 May 2022 12:56:08 -0700 Subject: [PATCH 3/9] Add Exceptions and GC Table --- .../DataOutputTypes/GenericEvent.cs | 2 +- DotNetEventPipe/Tables/ExceptionTable.cs | 159 ++++++++++++++++ DotNetEventPipe/Tables/GCTable.cs | 172 ++++++++++++++++++ DotNetEventPipe/Tables/GenericEventTable.cs | 4 +- DotnetEventpipeTest/DotnetTraceTests.cs | 5 +- .../Properties/launchSettings.json | 16 +- UnitTestCommon/ITableResultExts.cs | 17 +- UnitTestCommon/UnitTest.cs | 2 +- 8 files changed, 361 insertions(+), 16 deletions(-) create mode 100644 DotNetEventPipe/Tables/ExceptionTable.cs create mode 100644 DotNetEventPipe/Tables/GCTable.cs diff --git a/DotNetEventPipe/DataOutputTypes/GenericEvent.cs b/DotNetEventPipe/DataOutputTypes/GenericEvent.cs index 3440e9e..386daaa 100644 --- a/DotNetEventPipe/DataOutputTypes/GenericEvent.cs +++ b/DotNetEventPipe/DataOutputTypes/GenericEvent.cs @@ -11,7 +11,7 @@ namespace DotNetEventPipe.DataOutputTypes /// /// A GenericEvent /// - public readonly struct GenericEvent + public class GenericEvent { public string EventName { get; } public TraceEventID ID { get; } diff --git a/DotNetEventPipe/Tables/ExceptionTable.cs b/DotNetEventPipe/Tables/ExceptionTable.cs new file mode 100644 index 0000000..73e3e0f --- /dev/null +++ b/DotNetEventPipe/Tables/ExceptionTable.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.Diagnostics.Tracing.Parsers.Clr; +using Microsoft.Performance.SDK.Processing; +using Utilities; +using Utilities.AccessProviders; +using static Utilities.TimeHelper; + +namespace DotNetEventPipe.Tables +{ + // + // Have the MetadataTable inherit the TableBase class + // + [Table] // A category is optional. It useful for grouping different types of tables + public sealed class ExceptionTable + : TraceEventTableBase + { + public static readonly TableDescriptor TableDescriptor = new TableDescriptor( + Guid.Parse("{4975D02A-9950-49B6-847F-C9C0D41DDCBE}"), + "Exceptions", + "Exceptions", + category: ".NET trace (dotnet-trace)"); + + public ExceptionTable(IReadOnlyDictionary traceEventProcessor) + : base(traceEventProcessor) + { + } + + private static readonly ColumnConfiguration exceptionTypeColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{E7CD518A-6644-4E17-9AD8-5A034C3F63A2}"), "ExceptionType", "Exception Type"), + new UIHints { Width = 305 }); + + private static readonly ColumnConfiguration exceptionMessageColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{6A2381EF-6C6F-427F-846F-84D21C7F7793}"), "ExceptionMessage", "Exception Message"), + new UIHints { Width = 160 }); + + private static readonly ColumnConfiguration exceptionEIP = new ColumnConfiguration( + new ColumnMetadata(new Guid("{9C107E94-1DB5-400F-B36B-0864BA1B9F94}"), "ExceptionEIP", "Exception EIP"), + new UIHints { Width = 125, CellFormat = ColumnFormats.HexFormat, IsVisible = false }); + + private static readonly ColumnConfiguration exceptionHRESULTColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{996C5372-9F87-46A5-9D0D-678DCF37D040}"), "ExceptionHRESULT", "Exception HRESULT"), + new UIHints { Width = 125, CellFormat = ColumnFormats.HexFormat }); + + private static readonly ColumnConfiguration exceptionFlagsColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{5A263CF7-2338-4BC4-9523-FD3EAB8B5AA0}"), "ExceptionFlags", "Exception Flags"), + new UIHints { Width = 120 }); + + private static readonly ColumnConfiguration clrInstanceIDColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{08C5C067-C27A-4542-A761-B1350B0E8DF4}"), "ClrInstanceID", "Clr Instance ID"), + new UIHints { Width = 90, IsVisible = false }); + + private static readonly ColumnConfiguration timestampColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{6056D148-77BC-430A-927D-CF7192C02E45}"), "Timestamp", "The timestamp of the exception"), + new UIHints { Width = 80 }); + + + private static readonly ColumnConfiguration countColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{09CCE4A5-A3B0-4C25-B05E-609620702FD7}"), "Count", "The count of samples"), + new UIHints { + Width = 130, + AggregationMode = AggregationMode.Sum, + SortOrder = SortOrder.Descending, + SortPriority = 0, + }); + + private static readonly ColumnConfiguration hascallStackColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{E2DFB281-9904-4595-AB00-26FD104CAB0D}"), "HasCallstack", "Has Callstack"), + new UIHints { Width = 40, }); + + private static readonly ColumnConfiguration callStackColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{1C9357D2-CA16-439F-A3BE-9E7C5B903CEB}"), "ExStack", "Callstack of the exception"), + new UIHints { Width = 430, }); + + private static readonly ColumnConfiguration threadIdColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{0D6A5367-BF52-49CD-859E-CAC7F7F5C912}"), "ThreadId"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration processColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{BE450391-3E06-45C9-ABCB-6786A6CBAB00}"), "Process"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration processIdColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{E3D33FD4-4120-4EFB-B2B9-16523332B2F5}"), "Process Id"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration cpuColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{23FA8CD4-C253-426A-BADB-1AAE246A7169}"), "CPU"), + new UIHints { Width = 80, }); + + + public override void Build(ITableBuilder tableBuilder) + { + if (TraceEventProcessor == null || TraceEventProcessor.Count == 0) + { + return; + } + + var firstTraceProcessorEventsParsed = TraceEventProcessor.First().Value; // First Log + var exceptions = firstTraceProcessorEventsParsed.GenericEvents.Where(f => f.ProviderName == "Microsoft-Windows-DotNETRuntime" && f.EventName == "Exception/Start").ToArray(); + + var tableGenerator = tableBuilder.SetRowCount(exceptions.Length); + var baseProjection = Projection.Index(exceptions); + + tableGenerator.AddColumn(countColumn, baseProjection.Compose(x => 1)); // 1 sample + tableGenerator.AddColumn(exceptionTypeColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 1 ? (string) x.PayloadValues[0] : String.Empty)); + tableGenerator.AddColumn(exceptionMessageColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 2 ? (string) x.PayloadValues[1] : String.Empty)); + tableGenerator.AddColumn(exceptionEIP, baseProjection.Compose(x => x.PayloadValues.Length >= 3 ? (ulong) x.PayloadValues[2] : 0)); + tableGenerator.AddColumn(exceptionHRESULTColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 4 ? (int) x.PayloadValues[3] : 0)); + tableGenerator.AddColumn(exceptionFlagsColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 5 ? (ExceptionThrownFlags) x.PayloadValues[4] : ExceptionThrownFlags.None)); + tableGenerator.AddColumn(clrInstanceIDColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 6 ? (int) x.PayloadValues[5] : 0)); + tableGenerator.AddColumn(processIdColumn, baseProjection.Compose(x => x.ProcessID)); + tableGenerator.AddColumn(processColumn, baseProjection.Compose(x => x.ProcessName)); + tableGenerator.AddColumn(cpuColumn, baseProjection.Compose(x => x.ProcessorNumber)); + tableGenerator.AddColumn(threadIdColumn, baseProjection.Compose(x => x.ThreadID)); + tableGenerator.AddColumn(timestampColumn, baseProjection.Compose(x => x.Timestamp)); + tableGenerator.AddColumn(hascallStackColumn, baseProjection.Compose(x => x.CallStack != null)); + tableGenerator.AddHierarchicalColumn(callStackColumn, baseProjection.Compose(x => x.CallStack), new ArrayAccessProvider()); + + var exceptionsTableConfig = new TableConfiguration("Exceptions by Type, Callstack") + { + Columns = new ColumnConfiguration[] + { + exceptionTypeColumn, + callStackColumn, + TableConfiguration.PivotColumn, + exceptionMessageColumn, + exceptionHRESULTColumn, + processColumn, + exceptionFlagsColumn, + cpuColumn, + threadIdColumn, + countColumn, + exceptionEIP, + clrInstanceIDColumn, + TableConfiguration.GraphColumn, // Columns after this get graphed + timestampColumn, + } + }; + exceptionsTableConfig.AddColumnRole(ColumnRole.StartTime, timestampColumn); + exceptionsTableConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); + + var table = tableBuilder + .AddTableConfiguration(exceptionsTableConfig) + .SetDefaultTableConfiguration(exceptionsTableConfig); + } + } +} diff --git a/DotNetEventPipe/Tables/GCTable.cs b/DotNetEventPipe/Tables/GCTable.cs new file mode 100644 index 0000000..0a2c9c7 --- /dev/null +++ b/DotNetEventPipe/Tables/GCTable.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using DotNetEventPipe.DataOutputTypes; +using Microsoft.Diagnostics.Tracing.Parsers.Clr; +using Microsoft.Performance.SDK; +using Microsoft.Performance.SDK.Processing; +using Utilities; +using Utilities.AccessProviders; +using static Utilities.TimeHelper; + +namespace DotNetEventPipe.Tables +{ + // + // Have the MetadataTable inherit the TableBase class + // + [Table] // A category is optional. It useful for grouping different types of tables + public sealed class GCTable + : TraceEventTableBase + { + public static readonly TableDescriptor TableDescriptor = new TableDescriptor( + Guid.Parse("{9D91E453-8ADD-4650-8C91-77E58B42BBBA}"), + "GC", + "Garbage Collector", + category: ".NET trace (dotnet-trace)"); + + public GCTable(IReadOnlyDictionary traceEventProcessor) + : base(traceEventProcessor) + { + } + + private static readonly ColumnConfiguration countColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{847D85A8-77CB-4E4F-B664-4FAEF6394707}"), "Count", "Count"), + new UIHints + { + Width = 130, + AggregationMode = AggregationMode.Sum, + SortOrder = SortOrder.Descending, + SortPriority = 0, + }); + + private static readonly ColumnConfiguration gcReasonColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{39BEA7E7-C543-46FD-9FCF-6A7C249D35EC}"), "Reason", "GC Reason"), + new UIHints { Width = 125 }); + + private static readonly ColumnConfiguration depthColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{CB84FD81-24EF-49AA-91B0-4BB2D00BBE0E}"), "Depth", "Depth"), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration gcTypeColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{096124B1-F46D-4204-8C0A-D4DA65ACEBDA}"), "Type", "GC Type"), + new UIHints { Width = 160 }); + + private static readonly ColumnConfiguration clrInstanceIDColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{FFE7E4B2-0636-403B-A2F6-0B6D696C18FC}"), "ClrInstanceID", "Clr Instance ID"), + new UIHints { Width = 115 }); + + private static readonly ColumnConfiguration clientSequenceNumberColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{105C1056-EC3B-4980-A36E-D0CBE1E4A479}"), "ClientSequenceNumber", "Client Sequence Number"), + new UIHints { Width = 145 }); + + private static readonly ColumnConfiguration timestampColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{00746811-C2AE-48B8-AF00-1213AFFB4D6F}"), "Timestamp", "The timestamp of the event"), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration durationColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{2EFD88CE-0559-46D7-8E1A-43F5B2EDC482}"), "Duration", "Duration of the GC"), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration threadIdColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{0CDD7D87-FDC3-457D-87CD-692EA4280664}"), "ThreadId"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration processColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{B3BA7352-86CC-4271-A136-04CA071D19C5}"), "Process"), + new UIHints { Width = 100, }); + + private static readonly ColumnConfiguration processIdColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{7F13B9C1-7FD5-417C-B768-637D7BF0CA55}"), "Process Id"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration cpuColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{5C9EAC74-23E2-4450-9CC0-65C516AC1422}"), "CPU"), + new UIHints { Width = 80, }); + + + public override void Build(ITableBuilder tableBuilder) + { + if (TraceEventProcessor == null || TraceEventProcessor.Count == 0) + { + return; + } + + var firstTraceProcessorEventsParsed = TraceEventProcessor.First().Value; // First Log + var gcStartEvents = firstTraceProcessorEventsParsed.GenericEvents.Where(f => f.ProviderName == "Microsoft-Windows-DotNETRuntime" && + f.EventName == "GC/Start").ToArray(); + var gcStopEvents = firstTraceProcessorEventsParsed.GenericEvents.Where(f => f.ProviderName == "Microsoft-Windows-DotNETRuntime" && + f.EventName == "GC/Stop").OrderBy(f => f.Timestamp).ToArray(); + + var tableGenerator = tableBuilder.SetRowCount(gcStartEvents.Length); + var baseProjection = Projection.Index(gcStartEvents); + + var maximumFieldCount = 0; + foreach (var genericEvent in gcStartEvents) + { + maximumFieldCount = Math.Max(maximumFieldCount, genericEvent.PayloadValues.Length); + } + + tableGenerator.AddColumn(countColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 1 ? (int)x.PayloadValues[0] : 0)); + tableGenerator.AddColumn(gcReasonColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 2 ? (GCReason)x.PayloadValues[1] : GCReason.AllocSmall)); + tableGenerator.AddColumn(depthColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 3 ? (int)x.PayloadValues[2] : 0)); + tableGenerator.AddColumn(gcTypeColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 4 ? (GCType)x.PayloadValues[3] : 0)); + tableGenerator.AddColumn(clrInstanceIDColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 5 ? (int)x.PayloadValues[4] : 0)); + tableGenerator.AddColumn(clientSequenceNumberColumn, baseProjection.Compose(x => x.PayloadValues.Length >= 6 ? (long)x.PayloadValues[5] : 0)); + + tableGenerator.AddColumn(durationColumn, baseProjection.Compose(x => FindGCDuration(x, gcStopEvents))); + tableGenerator.AddColumn(processIdColumn, baseProjection.Compose(x => x.ProcessID)); + tableGenerator.AddColumn(processColumn, baseProjection.Compose(x => x.ProcessName)); + tableGenerator.AddColumn(cpuColumn, baseProjection.Compose(x => x.ProcessorNumber)); + tableGenerator.AddColumn(threadIdColumn, baseProjection.Compose(x => x.ThreadID)); + tableGenerator.AddColumn(timestampColumn, baseProjection.Compose(x => x.Timestamp)); + + + var gcConfig = new TableConfiguration("GC") + { + Columns = new ColumnConfiguration[] + { + gcReasonColumn, + gcTypeColumn, + TableConfiguration.PivotColumn, + processColumn, + cpuColumn, + threadIdColumn, + countColumn, + depthColumn, + clientSequenceNumberColumn, + clrInstanceIDColumn, + TableConfiguration.GraphColumn, // Columns after this get graphed + timestampColumn, + durationColumn, + } + }; + gcConfig.AddColumnRole(ColumnRole.StartTime, timestampColumn); + gcConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); + gcConfig.AddColumnRole(ColumnRole.Duration, durationColumn); + + var table = tableBuilder + .AddTableConfiguration(gcConfig) + .SetDefaultTableConfiguration(gcConfig); + } + + TimestampDelta FindGCDuration(GenericEvent gcStart, IEnumerable gcStops) + { + var stop = gcStops.FirstOrDefault(f => f.ThreadID == gcStart.ThreadID && f.Timestamp > gcStart.Timestamp); + if (stop == null) + { + return TimestampDelta.Zero; + } + else + { + return stop.Timestamp - gcStart.Timestamp; + } + } + } +} diff --git a/DotNetEventPipe/Tables/GenericEventTable.cs b/DotNetEventPipe/Tables/GenericEventTable.cs index cdef6e7..ffa68f3 100644 --- a/DotNetEventPipe/Tables/GenericEventTable.cs +++ b/DotNetEventPipe/Tables/GenericEventTable.cs @@ -77,12 +77,12 @@ public GenericEventTable(IReadOnlyDictionary traceE private static readonly ColumnConfiguration hascallStackColumn = new ColumnConfiguration( - new ColumnMetadata(new Guid("{2BCEC64D-A3A2-4EC5-B376-4040560066FC}"), "Has Callstack"), + new ColumnMetadata(new Guid("{2BCEC64D-A3A2-4EC5-B376-4040560066FC}"), "HasCallstack", "Has Callstack"), new UIHints { Width = 40, }); private static readonly ColumnConfiguration callStackColumn = new ColumnConfiguration( - new ColumnMetadata(new Guid("{37EF26BF-8FAF-4700-AD66-01D90DA743BF}"), "Callstack"), + new ColumnMetadata(new Guid("{37EF26BF-8FAF-4700-AD66-01D90DA743BF}"), "Callstack", "Call stack"), new UIHints { Width = 800, }); private static readonly ColumnConfiguration threadIdColumn = diff --git a/DotnetEventpipeTest/DotnetTraceTests.cs b/DotnetEventpipeTest/DotnetTraceTests.cs index 45d0527..51b9511 100644 --- a/DotnetEventpipeTest/DotnetTraceTests.cs +++ b/DotnetEventpipeTest/DotnetTraceTests.cs @@ -37,15 +37,18 @@ public static void ProcessTrace() // Enable tables used by UI runtime.EnableTable(CpuSamplingTable.TableDescriptor); runtime.EnableTable(GenericEventTable.TableDescriptor); + runtime.EnableTable(ExceptionTable.TableDescriptor); + runtime.EnableTable(GCTable.TableDescriptor); // // Process our data. // RuntimeExecutionResults = runtime.Process(); - UnitTest.TestTableBuild(RuntimeExecutionResults, CpuSamplingTable.TableDescriptor, 103800); UnitTest.TestTableBuild(RuntimeExecutionResults, GenericEventTable.TableDescriptor, 26104); // Wrong?? + UnitTest.TestTableBuild(RuntimeExecutionResults, ExceptionTable.TableDescriptor, 2); + UnitTest.TestTableBuild(RuntimeExecutionResults, GCTable.TableDescriptor, 12); IsTraceProcessed = true; } diff --git a/PerfDataExtensions/Properties/launchSettings.json b/PerfDataExtensions/Properties/launchSettings.json index a29d98f..552a517 100644 --- a/PerfDataExtensions/Properties/launchSettings.json +++ b/PerfDataExtensions/Properties/launchSettings.json @@ -1,9 +1,9 @@ -{ - "profiles": { - "PerfDataExtensions": { - "commandName": "Executable", - "executablePath": "C:\\Tools\\WPT\\latest\\wpa.exe", - "commandLineArgs": "-addsearchdir C:\\src\\Microsoft-Performance-Tools-Linux\\PerfDataExtensions\\bin\\Debug\\netstandard2.1" - } - } +{ + "profiles": { + "PerfDataExtensions": { + "commandName": "Executable", + "executablePath": "C:\\Tools\\WPT\\latest\\wpa.exe", + "commandLineArgs": "-addsearchdir E:\\src\\Microsoft-Performance-Tools-Linux-Android\\PerfDataExtensions\\bin\\Debug" + } + } } \ No newline at end of file diff --git a/UnitTestCommon/ITableResultExts.cs b/UnitTestCommon/ITableResultExts.cs index 2680dd3..6bce3c9 100644 --- a/UnitTestCommon/ITableResultExts.cs +++ b/UnitTestCommon/ITableResultExts.cs @@ -1,18 +1,29 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Microsoft.Performance.SDK.Processing; using Microsoft.Performance.Toolkit.Engine; using System; +using System.Collections.Generic; using System.Linq; namespace UnitTestCommon { + public class ColumnsWithData + { + public IEnumerable ColumnNames{ get; set; } + public object[][] Data { get; set; } + } + public static class ITableResultExts { - public static object[][] GetDataForAllRows(this ITableResult tableResult) + public static ColumnsWithData GetDataForAllRows(this ITableResult tableResult) { - var allColumns = tableResult.Columns.Select(f => f.Configuration.Metadata.Guid).ToArray(); - return GetDataForAllRows(tableResult, allColumns); + var allColumns = tableResult.Columns.Select(f => f.Configuration.Metadata); + var columnsWithData = new ColumnsWithData(); + columnsWithData.ColumnNames = allColumns.Select(f => f.Name).ToArray(); + columnsWithData.Data = GetDataForAllRows(tableResult, allColumns.Select(f => f.Guid).ToArray()); + return columnsWithData; } public static object[][] GetDataForAllRows(this ITableResult tableResult, Guid[] columns) diff --git a/UnitTestCommon/UnitTest.cs b/UnitTestCommon/UnitTest.cs index 8af9d2f..342a80d 100644 --- a/UnitTestCommon/UnitTest.cs +++ b/UnitTestCommon/UnitTest.cs @@ -24,7 +24,7 @@ public static void TestTableBuild(RuntimeExecutionResults runtimeExecutionResult throw new Exception($"We have {tableResult.RowCount} rows, but we expected {expectedCount} rows"); } var tableData = tableResult.GetDataForAllRows(); - Assert.IsTrue(tableData.Length == expectedCount); + Assert.IsTrue(tableData.Data.Length == expectedCount); } else if (!isTableDataAvailable && expectedCount > 0) { From 61883974fb3a1083b0890d8f2ce766f4c5bf9ba9 Mon Sep 17 00:00:00 2001 From: Ivan Berg Date: Sat, 21 May 2022 14:47:16 -0700 Subject: [PATCH 4/9] Add support for calculating cpu % and add small Hello World dotnet-trace /w Console output, GC, thread, and Exception --- DotNetEventPipe/Tables/CpuSamplingTable.cs | 115 +++++++++++++----- DotnetEventpipeTest/DotnetTraceTests.cs | 10 +- PerfDataExtensions/Tables/CpuSamplingTable.cs | 2 +- .../Tables/PerfTxtCpuSamplingTable.cs | 2 +- .../HelloWorld_GC_Threads_Exception.nettrace | Bin 0 -> 262258 bytes UnitTestCommon/UnitTest.cs | 2 +- .../Generators/IMapListToStream.cs | 2 +- .../Generators/SequentialGenerator.cs | 2 +- Utilities/Utilities.csproj | 2 +- 9 files changed, 98 insertions(+), 39 deletions(-) create mode 100644 TestData/Dotnet-Trace/HelloWorld_GC_Threads_Exception.nettrace rename {PerfDataExtensions/Tables => Utilities}/Generators/IMapListToStream.cs (90%) rename {PerfDataExtensions/Tables => Utilities}/Generators/SequentialGenerator.cs (99%) diff --git a/DotNetEventPipe/Tables/CpuSamplingTable.cs b/DotNetEventPipe/Tables/CpuSamplingTable.cs index 82ed4d7..b5f2700 100644 --- a/DotNetEventPipe/Tables/CpuSamplingTable.cs +++ b/DotNetEventPipe/Tables/CpuSamplingTable.cs @@ -4,8 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using DotNetEventPipe.DataOutputTypes; +using Microsoft.Performance.SDK; using Microsoft.Performance.SDK.Processing; using Utilities.AccessProviders; +using Utilities.Generators; using static Utilities.TimeHelper; namespace DotNetEventPipe.Tables @@ -45,8 +48,6 @@ public CpuSamplingTable(IReadOnlyDictionary traceEv new UIHints { Width = 130, AggregationMode = AggregationMode.Sum, // Sum needed instead of Count for flame - SortOrder = SortOrder.Descending, - SortPriority = 0, }); private static readonly ColumnConfiguration callStackColumn = @@ -74,6 +75,38 @@ public CpuSamplingTable(IReadOnlyDictionary traceEv new ColumnMetadata(new Guid("{DB744571-72CE-4C56-8277-58A402682016}"), "CPU"), new UIHints { Width = 80, }); + private static readonly ColumnConfiguration weightColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{46478B6A-C6FB-4552-AF78-E129E660189B}"), "Sample Weight"), + new UIHints { Width = 80, CellFormat = TimestampFormatter.FormatMillisecondsGrouped, }); + + private static readonly ColumnConfiguration weightPctColumn = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{EED346D1-98C4-4702-838F-FF3EFF822EC1}"), "Weight %"), + new UIHints + { + Width = 80, + AggregationMode = AggregationMode.Sum, + SortPriority = 0, + SortOrder = SortOrder.Descending, + }); + + private static readonly ColumnConfiguration viewportClippedStartTimeCol = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{D0F5DE08-E4FF-45D2-8644-E6D2AACFB2CD}"), "Clipped Start Time"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration viewportClippedEndTimeCol = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{8AC0B8A4-CE20-4B54-A031-1558B2A9238C}"), "Clipped End Time"), + new UIHints { Width = 80, }); + + private static readonly ColumnConfiguration clippedWeightCol = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{E5A38163-3FB1-47DC-9E0E-E296041A563D}"), "Clipped Weight"), + new UIHints { Width = 80, }); + + private IReadOnlyList ThreadSamplingEvents; public override void Build(ITableBuilder tableBuilder) { @@ -83,10 +116,10 @@ public override void Build(ITableBuilder tableBuilder) } var firstTraceProcessorEventsParsed = TraceEventProcessor.First().Value; // First Log - var threadSamplingEvents = firstTraceProcessorEventsParsed.ThreadSamplingEvents; + ThreadSamplingEvents = firstTraceProcessorEventsParsed.ThreadSamplingEvents; - var tableGenerator = tableBuilder.SetRowCount(threadSamplingEvents.Count); - var baseProjection = Projection.Index(threadSamplingEvents); + var tableGenerator = tableBuilder.SetRowCount(ThreadSamplingEvents.Count); + var baseProjection = Projection.Index(ThreadSamplingEvents); tableGenerator.AddColumn(countColumn, baseProjection.Compose(x => 1)); // 1 sample @@ -94,10 +127,38 @@ public override void Build(ITableBuilder tableBuilder) tableGenerator.AddColumn(processColumn, baseProjection.Compose(x => x.ProcessName)); tableGenerator.AddColumn(cpuColumn, baseProjection.Compose(x => x.ProcessorNumber)); tableGenerator.AddColumn(threadIdColumn, baseProjection.Compose(x => x.ThreadID)); - tableGenerator.AddColumn(timestampColumn, baseProjection.Compose(x => x.Timestamp)); tableGenerator.AddColumn(moduleColumn, baseProjection.Compose(x => x.Module?.Name)); tableGenerator.AddColumn(functionColumn, baseProjection.Compose(x => x.FullMethodName)); tableGenerator.AddHierarchicalColumn(callStackColumn, baseProjection.Compose(x => x.CallStack), new ArrayAccessProvider()); + var timeStampProjection = Projection.CreateUsingFuncAdaptor((i) => ThreadSamplingEvents[i].Timestamp); + + // Calculating sample weights + var tenMsSample = new TimestampDelta(10000000); // 10ms - https://docs.microsoft.com/en-us/dotnet/core/diagnostics/well-known-event-providers + var weightProj = Projection.Constant(tenMsSample); + var oneNs = new TimestampDelta(1); + + var timeStampStartProjection = baseProjection.Compose(x => x.Timestamp - oneNs); // We will say sample lasted 1ns + IProjection viewportClippedStartTimeProj = Projection.ClipTimeToVisibleDomain.Create(timeStampStartProjection); + IProjection viewportClippedEndTimeProj = Projection.ClipTimeToVisibleDomain.Create(timeStampProjection); + + IProjection clippedWeightProj = Projection.Select( + viewportClippedEndTimeProj, + viewportClippedStartTimeProj, + new ReduceTimeSinceLastDiff()); + + IProjection weightPercentProj = Projection.VisibleDomainRelativePercent.Create(clippedWeightProj); + + IProjection countProj = SequentialGenerator.Create( + ThreadSamplingEvents.Count, + Projection.Constant(1), + Projection.Constant(0)); + + tableGenerator.AddColumn(weightColumn, weightProj); + tableGenerator.AddColumn(weightPctColumn, weightPercentProj); + tableGenerator.AddColumn(timestampColumn, timeStampStartProjection); + tableGenerator.AddColumn(viewportClippedStartTimeCol, viewportClippedStartTimeProj); + tableGenerator.AddColumn(viewportClippedEndTimeCol, viewportClippedEndTimeProj); + tableGenerator.AddColumn(clippedWeightCol, clippedWeightProj); var utilByCpuStackConfig = new TableConfiguration("Utilization by CPU, Stack") { @@ -111,15 +172,15 @@ public override void Build(ITableBuilder tableBuilder) functionColumn, moduleColumn, timestampColumn, - //weightColumn, + countColumn, TableConfiguration.GraphColumn, - countColumn - //weightPctColumn + weightPctColumn }, }; utilByCpuStackConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); - utilByCpuStackConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + utilByCpuStackConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + utilByCpuStackConfig.AddColumnRole(ColumnRole.Duration, weightColumn); var utilByCpuConfig = new TableConfiguration("Utilization by CPU") { @@ -127,21 +188,20 @@ public override void Build(ITableBuilder tableBuilder) { cpuColumn, TableConfiguration.PivotColumn, - callStackColumn, processColumn, threadIdColumn, functionColumn, moduleColumn, timestampColumn, - //weightColumn, + countColumn, TableConfiguration.GraphColumn, - countColumn - //weightPctColumn + weightPctColumn }, }; utilByCpuConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); - utilByCpuConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + utilByCpuConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + utilByCpuConfig.AddColumnRole(ColumnRole.Duration, weightColumn); var utilByProcessConfig = new TableConfiguration("Utilization by Process") { @@ -149,21 +209,20 @@ public override void Build(ITableBuilder tableBuilder) { processColumn, TableConfiguration.PivotColumn, - callStackColumn, cpuColumn, threadIdColumn, functionColumn, moduleColumn, timestampColumn, - //weightColumn, - TableConfiguration.GraphColumn, countColumn, - //weightPctColumn + TableConfiguration.GraphColumn, + weightPctColumn }, }; utilByProcessConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); - utilByProcessConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + utilByProcessConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + utilByProcessConfig.AddColumnRole(ColumnRole.Duration, weightColumn); var utilByProcessStackConfig = new TableConfiguration("Utilization by Process, Stack") { @@ -177,15 +236,15 @@ public override void Build(ITableBuilder tableBuilder) functionColumn, moduleColumn, timestampColumn, - //weightColumn, + countColumn, TableConfiguration.GraphColumn, - countColumn - //weightPctColumn + weightPctColumn }, }; utilByProcessStackConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); - utilByProcessStackConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + utilByProcessStackConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + utilByProcessStackConfig.AddColumnRole(ColumnRole.Duration, weightColumn); var flameByProcessStackConfig = new TableConfiguration("Flame by Process, Stack") { @@ -199,17 +258,17 @@ public override void Build(ITableBuilder tableBuilder) functionColumn, moduleColumn, timestampColumn, - //weightColumn, + countColumn, TableConfiguration.GraphColumn, - countColumn - //weightPctColumn + weightPctColumn }, ChartType = ChartType.Flame, }; flameByProcessStackConfig.AddColumnRole(ColumnRole.EndTime, timestampColumn); - flameByProcessStackConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + flameByProcessStackConfig.AddColumnRole(ColumnRole.ResourceId, cpuColumn); + flameByProcessStackConfig.AddColumnRole(ColumnRole.Duration, weightColumn); var table = tableBuilder .AddTableConfiguration(utilByCpuStackConfig) diff --git a/DotnetEventpipeTest/DotnetTraceTests.cs b/DotnetEventpipeTest/DotnetTraceTests.cs index 51b9511..82e78df 100644 --- a/DotnetEventpipeTest/DotnetTraceTests.cs +++ b/DotnetEventpipeTest/DotnetTraceTests.cs @@ -24,7 +24,7 @@ public static void ProcessTrace() if (!IsTraceProcessed) { // Input data - string[] dotnetTraceData = { @"..\..\..\..\TestData\Dotnet-Trace\dotnet_20220517_102703.nettrace" }; + string[] dotnetTraceData = { @"..\..\..\..\TestData\Dotnet-Trace\HelloWorld_GC_Threads_Exception.nettrace" }; var dotnetTraceDataPath = new FileInfo(dotnetTraceData[0]); Assert.IsTrue(dotnetTraceDataPath.Exists); @@ -45,10 +45,10 @@ public static void ProcessTrace() // RuntimeExecutionResults = runtime.Process(); - UnitTest.TestTableBuild(RuntimeExecutionResults, CpuSamplingTable.TableDescriptor, 103800); - UnitTest.TestTableBuild(RuntimeExecutionResults, GenericEventTable.TableDescriptor, 26104); // Wrong?? - UnitTest.TestTableBuild(RuntimeExecutionResults, ExceptionTable.TableDescriptor, 2); - UnitTest.TestTableBuild(RuntimeExecutionResults, GCTable.TableDescriptor, 12); + UnitTest.TestTableBuild(RuntimeExecutionResults, CpuSamplingTable.TableDescriptor, 39); + UnitTest.TestTableBuild(RuntimeExecutionResults, GenericEventTable.TableDescriptor, 421); + UnitTest.TestTableBuild(RuntimeExecutionResults, ExceptionTable.TableDescriptor, 1); + UnitTest.TestTableBuild(RuntimeExecutionResults, GCTable.TableDescriptor, 1); IsTraceProcessed = true; } diff --git a/PerfDataExtensions/Tables/CpuSamplingTable.cs b/PerfDataExtensions/Tables/CpuSamplingTable.cs index ccdce64..9c02b92 100644 --- a/PerfDataExtensions/Tables/CpuSamplingTable.cs +++ b/PerfDataExtensions/Tables/CpuSamplingTable.cs @@ -9,8 +9,8 @@ using PerfCds.CookerData; using PerfDataExtensions.DataOutputTypes; using PerfDataExtensions.SourceDataCookers.Cpu; -using PerfDataExtensions.Tables.Generators; using Utilities.AccessProviders; +using Utilities.Generators; using static Utilities.TimeHelper; namespace PerfDataExtensions.Tables diff --git a/PerfDataExtensions/Tables/PerfTxtCpuSamplingTable.cs b/PerfDataExtensions/Tables/PerfTxtCpuSamplingTable.cs index bec5eaa..a78beb4 100644 --- a/PerfDataExtensions/Tables/PerfTxtCpuSamplingTable.cs +++ b/PerfDataExtensions/Tables/PerfTxtCpuSamplingTable.cs @@ -10,8 +10,8 @@ using Microsoft.Diagnostics.Tracing.StackSources; using Microsoft.Performance.SDK; using Microsoft.Performance.SDK.Processing; -using PerfDataExtensions.Tables.Generators; using Utilities.AccessProviders; +using Utilities.Generators; using static Utilities.TimeHelper; namespace PerfDataExtensions.Tables diff --git a/TestData/Dotnet-Trace/HelloWorld_GC_Threads_Exception.nettrace b/TestData/Dotnet-Trace/HelloWorld_GC_Threads_Exception.nettrace new file mode 100644 index 0000000000000000000000000000000000000000..776cfa833bf6a59b86dc0a70e25c36d2cff4df87 GIT binary patch literal 262258 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 up^rOX netstandard2.1 - 1.2.2 + 1.2.3 true Microsoft Microsoft Corp. From 3401409a064e4ad2fae8e74be205453a4f5ea5b7 Mon Sep 17 00:00:00 2001 From: Ivan Berg Date: Mon, 23 May 2022 12:17:24 -0700 Subject: [PATCH 5/9] Add event stats table --- DotNetEventPipe/Tables/EventStat.cs | 17 ++++ DotNetEventPipe/Tables/EventStats.cs | 127 +++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 DotNetEventPipe/Tables/EventStat.cs create mode 100644 DotNetEventPipe/Tables/EventStats.cs diff --git a/DotNetEventPipe/Tables/EventStat.cs b/DotNetEventPipe/Tables/EventStat.cs new file mode 100644 index 0000000..fe2d5ac --- /dev/null +++ b/DotNetEventPipe/Tables/EventStat.cs @@ -0,0 +1,17 @@ +using Microsoft.Performance.SDK; +using System; +using System.Collections.Generic; +using System.Text; + +namespace DotNetEventPipe.Tables +{ + internal class EventStat + { + public string ProviderName { get; set; } + public string EventName { get; set; } + public int Count { get; set; } + public int StackCount { get; set; } + public Timestamp StartTime { get; set; } + public Timestamp EndTime { get; set; } + } +} diff --git a/DotNetEventPipe/Tables/EventStats.cs b/DotNetEventPipe/Tables/EventStats.cs new file mode 100644 index 0000000..9abf06c --- /dev/null +++ b/DotNetEventPipe/Tables/EventStats.cs @@ -0,0 +1,127 @@ +using Microsoft.Performance.SDK; +using Microsoft.Performance.SDK.Extensibility; +using Microsoft.Performance.SDK.Processing; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace DotNetEventPipe.Tables +{ + [Table] + public sealed class EventStats : TraceEventTableBase + { + public static TableDescriptor TableDescriptor = new TableDescriptor( + Guid.Parse("{3B038894-8539-47CE-8FB7-8E01226E9DDD}"), + "Event Stats", + "Event Stats", + category: ".NET trace (dotnet-trace)", + defaultLayout: TableLayoutStyle.GraphAndTable); + + private static readonly ColumnConfiguration providerNameColumnConfig = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{380F9262-5F13-486D-96B5-3ADB9779E54D}"), "Provider Name"), + new UIHints { Width = 300 }); + + private static readonly ColumnConfiguration eventNameColumnConfig = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{67BB853A-94FC-49EA-ACB1-E6236D3125F2}"), "Event Name"), + new UIHints { Width = 600 }); + + private static readonly ColumnConfiguration eventCountColumnConfig = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{C657FBD3-C424-4896-A8DF-B84199499A02}"), "Event Count"), + new UIHints { Width = 150, SortOrder = SortOrder.Descending, SortPriority = 0, AggregationMode = AggregationMode.Sum }); + + private static readonly ColumnConfiguration stackCountColumnConfig = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{1F98EDA4-A80A-49E6-AB0A-D3323A6E7DD0}"), "Stack Count"), + new UIHints { Width = 150, SortOrder = SortOrder.Descending, SortPriority = 1 }); + + private static readonly ColumnConfiguration startTimeConfig = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{F4E2F334-AE8E-4B15-88DC-3304D0E3BA91}"), "StartTime"), + new UIHints { Width = 150, IsVisible = false }); + + private static readonly ColumnConfiguration endTimeConfig = + new ColumnConfiguration( + new ColumnMetadata(new Guid("{191CCB3E-F0A1-40C7-96BA-015E9F99BA17}"), "EndTime"), + new UIHints { Width = 150, IsVisible = false }); + + public EventStats(IReadOnlyDictionary traceEventProcessor) + : base(traceEventProcessor) + { + } + + public override void Build(ITableBuilder tableBuilder) + { + if (TraceEventProcessor == null || TraceEventProcessor.Count == 0) + { + return; + } + + var firstTraceProcessorEventsParsed = TraceEventProcessor.First().Value; // First Log + var threadSamplingEvents = firstTraceProcessorEventsParsed.ThreadSamplingEvents; + EventStat[] threadSamplingStats = { new EventStat + { + ProviderName = "Microsoft-DotNETCore-SampleProfiler", + EventName = "Thread/Sample", + Count = threadSamplingEvents.Count(), + StackCount = threadSamplingEvents.Count(f => f.CallStack != null), + StartTime = threadSamplingEvents.Min(f => f.Timestamp), + EndTime = threadSamplingEvents.Max(f => f.Timestamp), + } }; + + var genericEvents = firstTraceProcessorEventsParsed.GenericEvents; + var genericEventStats = genericEvents.GroupBy( + ge => new { ge.ProviderName, ge.EventName }, + ge => ge, + (geGroup, geElems) => new EventStat + { + ProviderName = geGroup.ProviderName, + EventName = geGroup.EventName, + Count = geElems.Count(), + StackCount = geElems.Count(f => f.CallStack != null), + StartTime = geElems.Min(f => f.Timestamp), + EndTime = geElems.Max(f => f.Timestamp), + }); + + var allEventStats = threadSamplingStats.Union(genericEventStats).ToArray(); + + var table = tableBuilder.SetRowCount(allEventStats.Length); + var baseProj = Projection.Index(allEventStats); + + var providerNameProj = baseProj.Compose(x => x.ProviderName); + var eventNameProj = baseProj.Compose(x => x.EventName); + var countProj = baseProj.Compose(x => x.Count); + var stackCountProj = baseProj.Compose(x => x.StackCount); + var startTimeProj = baseProj.Compose(x => x.StartTime); + var endTimeProj = baseProj.Compose(x => x.EndTime); + + table.AddColumn(providerNameColumnConfig, providerNameProj); + table.AddColumn(eventNameColumnConfig, eventNameProj); + table.AddColumn(eventCountColumnConfig, countProj); + table.AddColumn(stackCountColumnConfig, stackCountProj); + table.AddColumn(startTimeConfig, startTimeProj); + table.AddColumn(endTimeConfig, endTimeProj); + + var tableConfig = new TableConfiguration("Stats") + { + Columns = new[] + { + providerNameColumnConfig, + eventNameColumnConfig, + TableConfiguration.PivotColumn, + stackCountColumnConfig, + TableConfiguration.GraphColumn, + eventCountColumnConfig, + } + }; + tableConfig.AddColumnRole(ColumnRole.StartTime, startTimeConfig); + tableConfig.AddColumnRole(ColumnRole.EndTime, endTimeConfig); + tableConfig.ChartType = ChartType.StackedBars; + + tableBuilder.SetDefaultTableConfiguration(tableConfig); + } + + } +} From 6063c0b75d47f7e61903f6d0a5ab56d89ef0b467 Mon Sep 17 00:00:00 2001 From: Ivan Berg Date: Mon, 23 May 2022 12:25:01 -0700 Subject: [PATCH 6/9] Small doc --- DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs b/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs index 597d481..ba1b5d9 100644 --- a/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs +++ b/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs @@ -56,6 +56,7 @@ protected override Task ProcessAsyncCore( // 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); using (var traceSource = new EventPipeEventSource(dotnetFileStream)) { @@ -74,6 +75,7 @@ protected override Task ProcessAsyncCore( 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); From 63ad24d6e3d4e894ad46d37617801910828480d4 Mon Sep 17 00:00:00 2001 From: Ivan Berg Date: Mon, 23 May 2022 12:33:59 -0700 Subject: [PATCH 7/9] Yaml build file --- azure-pipelines.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5d73c91..f9d7048 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -52,6 +52,13 @@ jobs: projects: | CtfPlayback\CtfPlayback.csproj CtfUnitTest\CtfUnitTest.csproj + DotNetEventPipe\DotnetEventpipe.csproj + DotnetEventpipeTest\DotnetEventpipeTest.csproj + LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\Cloud-init\Cloud-Init.csproj + LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\DmesgIsoLog\Dmesg.csproj + LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\WaLinuxAgent\WaLinuxAgent.csproj + LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\AndroidLogCat\AndroidLogcat.csproj + LinuxLogParsers\LinuxLogParsersUnitTest\LinuxLogParsersUnitTest.csproj LTTngCds\LTTngCds.csproj LTTngDataExtensions\LTTngDataExtensions.csproj LTTngDataExtUnitTest\LTTngDataExtUnitTest.csproj @@ -60,11 +67,6 @@ jobs: PerfDataExtensions\PerfDataExtensions.csproj PerfettoCds\PerfettoCds.csproj PerfUnitTest\PerfUnitTest.csproj - LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\Cloud-init\Cloud-Init.csproj - LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\DmesgIsoLog\Dmesg.csproj - LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\WaLinuxAgent\WaLinuxAgent.csproj - LinuxLogParsers\LinuxPlugins-MicrosoftPerformanceToolkSDK\AndroidLogCat\AndroidLogcat.csproj - LinuxLogParsers\LinuxLogParsersUnitTest\LinuxLogParsersUnitTest.csproj includesymbols: true versioningScheme: 'byBuildNumber' @@ -131,6 +133,13 @@ jobs: Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/Microsoft-Performance-Tools-Linux/MicrosoftPerfToolkitAddins/AndroidLogCat' + - task: CopyFiles@2 + displayName: Copy DotNetEventPipe Build to Output Artifacts + inputs: + SourceFolder: 'DotNetEventPipe/bin/$(BuildConfiguration)/netstandard2.1' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/Microsoft-Performance-Tools-Linux/MicrosoftPerfToolkitAddins/DotNetEventPipe' + - task: CopyFiles@2 displayName: Copy Launcher inputs: From 51b9b736cff5241ecd982d942cfa2eb8760e1683 Mon Sep 17 00:00:00 2001 From: Ivan Berg Date: Mon, 23 May 2022 13:22:59 -0700 Subject: [PATCH 8/9] Update docs --- LinuxTraceLogCapture.md | 1 + README.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/LinuxTraceLogCapture.md b/LinuxTraceLogCapture.md index a7533d2..02e9564 100644 --- a/LinuxTraceLogCapture.md +++ b/LinuxTraceLogCapture.md @@ -16,6 +16,7 @@ Logs: - LogLevel can be turned more verbose in custom image - /etc/waagent.conf - Logs.Verbose=y +- [dotnet-trace (.nettrace)](https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace) - [AndroidLogcat](https://developer.android.com/studio/command-line/logcat) - Default log format should be supported - Basic durations are supported/parsed on production builds / logs. diff --git a/README.md b/README.md index 2ee856c..5f11767 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ - [LTTng](https://lttng.org) (Kernel CPU scheduling, Processes, Threads, Block IO/Disk, Syscalls, File events, etc) - [perf](https://perf.wiki.kernel.org/) CPU Sampling(cpu-clock) - [Perfetto](https://perfetto.dev/) Android & Chromium (CPU Scheduling, CPU Sampling, CPU Frequency, FTrace, Android Logs, Generic Events / Default Tracks, GPU Counters, Jank Detection, Processes, Android Packages) +- [dotnet-trace (.nettrace)](https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace) - Cross-platform .NET trace. CPU Sampling, Generic Events, Exceptions, Garbage Collector (GC) > Logs supported: - [Dmesg](https://en.wikipedia.org/wiki/Dmesg) @@ -96,6 +97,7 @@ The tools can be run in several modes: # How to capture a trace or logs - Linux - Please see [Linux Trace Log Capture](LinuxTraceLogCapture.md) +- Cross Platform [dotnet-trace (.nettrace)](https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace) - Perfetto - Android - Please see [Record traces on Android](https://perfetto.dev/docs/quickstart/android-tracing) - Linux - Please see [Record traces on Linux](https://perfetto.dev/docs/quickstart/linux-tracing) From 0cd5b7c66a1312bd80e8ae565273200714b32a75 Mon Sep 17 00:00:00 2001 From: Ivan Berg Date: Tue, 7 Jun 2022 15:44:58 -0700 Subject: [PATCH 9/9] Upgrade to newer TraceEvent which fixes TraceLogEventSource not having trace session info. Include some updated docs and developer info --- CtfUnitTest/CtfUnitTest.csproj | 6 +-- DeveloperInfo.md | 51 +++++++++++++++++++ DotNetEventPipe/DotnetEventpipe.csproj | 2 +- .../DotnetTraceDataProcessor.cs | 50 ++++++++++-------- .../SourceDataCookers/TraceEventProcessor.cs | 6 ++- .../DotnetEventpipeTest.csproj | 6 +-- .../LTTngDataExtUnitTest.csproj | 8 +-- .../LinuxLogParsersUnitTest.csproj | 8 +-- Microsoft-Perf-Tools-Linux-Android.sln | 3 +- PerfDataExtensions/PerfDataExtensions.csproj | 2 +- PerfUnitTest/PerfUnitTest.csproj | 10 ++-- PerfettoUnitTest/PerfettoUnitTest.csproj | 8 +-- README.md | 11 ++-- UnitTestCommon/UnitTestCommon.csproj | 2 +- 14 files changed, 120 insertions(+), 53 deletions(-) create mode 100644 DeveloperInfo.md diff --git a/CtfUnitTest/CtfUnitTest.csproj b/CtfUnitTest/CtfUnitTest.csproj index 34d2fb3..794a926 100644 --- a/CtfUnitTest/CtfUnitTest.csproj +++ b/CtfUnitTest/CtfUnitTest.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/DeveloperInfo.md b/DeveloperInfo.md new file mode 100644 index 0000000..ab36c68 --- /dev/null +++ b/DeveloperInfo.md @@ -0,0 +1,51 @@ +# Microsoft Performance Tools Linux / Android - Developer Information + +# Prerequisites + +See [Readme Dev PreReqs](Readme.md#Dev%20prereqs) + +# Code Editing + +## Entire Project +If working on the entire project, or editing .sln or .csproj files +[Visual Studio](https://visualstudio.microsoft.com/) is recommended + +Open [Microsoft-Perf-Tools-Linux-Android.sln](Microsoft-Perf-Tools-Linux-Android.sln) in VS + +## Single Files +Use your favorite editor + +## Build & Test + +### Cross Platform Cmd-Line +- ```dotnet build``` +- ```dotnet test``` + +### IDE +- VS Build Solution or Build Project + +# Debugging & Testing + +## Dev inner loop +It's often fastest to debug the Unit Test examples since they wrap the plugins. This method keeps runtime overhead to a minimum. See the various *UnitTest projects + +- VS Test Explorer is a great way to visualize / run / debug tests. Test -> Test Explorer + +## Plugin visualization and trace testing +- After getting some stabilization in a plugin, it's often fastest to test or investigate multiple traces using a GUI. + +- The plugins are not tied to any specific GUI. However the GUI does need to support the [Microsoft Performance Toolkit SDK](https://github.com/microsoft/microsoft-performance-toolkit-sdk) + +### WPA GUI + +- Using VS2022 Launch Profiles + - To Start WPA with your plugin (doesn't auto-open file) + - Executable + - "C:\PATH\TO\wpa.exe" + - Command line arguments - + - -addsearchdir "C:\src\Microsoft-Performance-Tools-Linux-Android\ThePlugin\bin\Debug" + - To Start WPA with your plugin AND auto-open file + - Executable + - "C:\PATH\TO\wpa.exe" + - Command line arguments - + - -addsearchdir "C:\src\Microsoft-Performance-Tools-Linux-Android\ThePlugin\bin\Debug" -i "C:\PATH\TO\tracefile.ext" \ No newline at end of file diff --git a/DotNetEventPipe/DotnetEventpipe.csproj b/DotNetEventPipe/DotnetEventpipe.csproj index aac2040..2110378 100644 --- a/DotNetEventPipe/DotnetEventpipe.csproj +++ b/DotNetEventPipe/DotnetEventpipe.csproj @@ -32,7 +32,7 @@ - + True diff --git a/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs b/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs index ba1b5d9..0589538 100644 --- a/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs +++ b/DotNetEventPipe/SourceDataCookers/DotnetTraceDataProcessor.cs @@ -48,37 +48,45 @@ protected override Task ProcessAsyncCore( IProgress progress, CancellationToken cancellationToken) { + const string ReadPastEndOfStreamExceptionMessage = "Read past end of stream."; // Trace can be partially written but still have data - https://github.com/microsoft/perfview/issues/1637 var contentDictionary = new Dictionary(); foreach (var path in this.filePaths) { var traceStartTime = DateTime.UtcNow.Date; - // 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); - 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)) + var traceEventProcessor = new TraceEventProcessor(); + try { - TraceLogEventSource source = traceLog.Events.GetSource(); + 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()); + contentDictionary[path] = traceEventProcessor; + source.AllEvents += traceEventProcessor.ProcessTraceEvent; + source.Process(); + this.dataSourceInfo = new DataSourceInfo(0, source.SessionDuration.Ticks * 100, source.SessionStartTime.ToUniversalTime()); + } + } + catch (Exception e) + { + if (e.Message != ReadPastEndOfStreamExceptionMessage || !traceEventProcessor.HasTraceData()) + { + throw; + } + } + finally + { + try + { + File.Delete(tmpEtlx); + } + catch (Exception) + { + } } - File.Delete(tmpEtlx); } this.fileContent = new ReadOnlyDictionary(contentDictionary); diff --git a/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs b/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs index cbd8955..eb9ecbb 100644 --- a/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs +++ b/DotNetEventPipe/SourceDataCookers/TraceEventProcessor.cs @@ -20,8 +20,12 @@ public class TraceEventProcessor public IReadOnlyList GenericEvents => genericEvents.AsReadOnly(); List genericEvents = new List(); - // TODO - Move this to a DataCooker + public bool HasTraceData() + { + return threadSamplingEvents.Any() || genericEvents.Any(); + } + // TODO - Move this to a DataCooker public void ProcessTraceEvent(TraceEvent data) { string eventName = data.ProviderName + "/" + data.EventName; diff --git a/DotnetEventpipeTest/DotnetEventpipeTest.csproj b/DotnetEventpipeTest/DotnetEventpipeTest.csproj index c0ad326..99d6059 100644 --- a/DotnetEventpipeTest/DotnetEventpipeTest.csproj +++ b/DotnetEventpipeTest/DotnetEventpipeTest.csproj @@ -7,10 +7,10 @@ - + - - + + diff --git a/LTTngDataExtUnitTest/LTTngDataExtUnitTest.csproj b/LTTngDataExtUnitTest/LTTngDataExtUnitTest.csproj index af3ce5c..db0d96c 100644 --- a/LTTngDataExtUnitTest/LTTngDataExtUnitTest.csproj +++ b/LTTngDataExtUnitTest/LTTngDataExtUnitTest.csproj @@ -7,13 +7,13 @@ - + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/LinuxLogParsers/LinuxLogParsersUnitTest/LinuxLogParsersUnitTest.csproj b/LinuxLogParsers/LinuxLogParsersUnitTest/LinuxLogParsersUnitTest.csproj index f254dd6..f4329f8 100644 --- a/LinuxLogParsers/LinuxLogParsersUnitTest/LinuxLogParsersUnitTest.csproj +++ b/LinuxLogParsers/LinuxLogParsersUnitTest/LinuxLogParsersUnitTest.csproj @@ -7,12 +7,12 @@ - + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Microsoft-Perf-Tools-Linux-Android.sln b/Microsoft-Perf-Tools-Linux-Android.sln index 4d0b0da..4ac94f8 100644 --- a/Microsoft-Perf-Tools-Linux-Android.sln +++ b/Microsoft-Perf-Tools-Linux-Android.sln @@ -30,6 +30,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{E48222FC-D167-4281-BC94-961C22908B25}" ProjectSection(SolutionItems) = preProject CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md + DeveloperInfo.md = DeveloperInfo.md LinuxTraceLogCapture.md = LinuxTraceLogCapture.md NOTICE.md = NOTICE.md README.md = README.md @@ -73,7 +74,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AndroidLogcat", "LinuxLogPa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetEventpipe", "DotNetEventPipe\DotnetEventpipe.csproj", "{863E041F-0715-4B08-A1B8-B5CFC027ED6B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotnetEventpipeTest", "DotnetEventpipeTest\DotnetEventpipeTest.csproj", "{46861BDC-350C-45ED-A72E-9661C3BBE2D8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetEventpipeTest", "DotnetEventpipeTest\DotnetEventpipeTest.csproj", "{46861BDC-350C-45ED-A72E-9661C3BBE2D8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/PerfDataExtensions/PerfDataExtensions.csproj b/PerfDataExtensions/PerfDataExtensions.csproj index d0ac281..3d4a743 100644 --- a/PerfDataExtensions/PerfDataExtensions.csproj +++ b/PerfDataExtensions/PerfDataExtensions.csproj @@ -61,7 +61,7 @@ - + compile diff --git a/PerfUnitTest/PerfUnitTest.csproj b/PerfUnitTest/PerfUnitTest.csproj index 039d502..c58e4af 100644 --- a/PerfUnitTest/PerfUnitTest.csproj +++ b/PerfUnitTest/PerfUnitTest.csproj @@ -7,13 +7,13 @@ - - + + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PerfettoUnitTest/PerfettoUnitTest.csproj b/PerfettoUnitTest/PerfettoUnitTest.csproj index d128e01..b457e66 100644 --- a/PerfettoUnitTest/PerfettoUnitTest.csproj +++ b/PerfettoUnitTest/PerfettoUnitTest.csproj @@ -7,13 +7,13 @@ - + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/README.md b/README.md index 5f11767..a7377c9 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ Featuring some use-cases and some walkthroughs - [.NET Core SDK 3.1.x](https://dotnet.microsoft.com/download/dotnet-core/3.1) - [Visual Studio](https://visualstudio.microsoft.com/), [VSCode](https://visualstudio.microsoft.com/), or your favorite editor! +See [full developer instructions](DeveloperInfo.md) for more information + # Download - **For plugins Download** see [Releases](https://github.com/microsoft/Microsoft-Performance-Tools-Linux/releases) @@ -86,6 +88,8 @@ The tools can be run in several modes: wpa.exe /? "C:\Program Files\WindowsApps\Microsoft.WindowsPerformanceAnalyzerPreview_10.0.22504.0_x64__8wekyb3d8bbwe\10\Windows Performance Toolkit\wpa.exe" /? ``` + PowerShell: + ```Get-AppxPackage Microsoft.WindowsPerformanceAnalyzer*``` - Verify that these 2 command line WPA options are supported: - OPTIONS: **-addsearchdir PATH**. Adds a directory path to the plugin search path. .... - ENVIRONMENT VARIABLES: **WPA_ADDITIONAL_SEARCH_DIRECTORIES** - A semicolon (;) delimited list of additional directories to search for plugins. Equivalent to the -addsearchdir option. @@ -111,13 +115,12 @@ The tools can be run in several modes: - Note: Requires >= 1.2 release AND WPA >= 10.6.20.1 (via WPA Help -> About) - Perfetto - WPA -> Open -> (Select Perfetto trace file) - - Note: The Perfetto plugin explicitly supports the _.perfetto-trace_ and _.pftrace_ file types, but it does support more (e.g. Protobuf, Chrome JSON). You just need to rename to one of the stated supported types -- Unified (LTTng, Perfetto, or other multiple different logs files together) - - Once you gather the data, there is a tiny bit of prep needed to open them in a single unified timeline (like the screenshot above) +- Single session (LTTng, Perfetto, or other multiple different logs files together) + - Once you gather the data, there is a tiny bit of prep needed to open them in a single session (unified timeline) (like the screenshot above) - If you want to open multiple logs together in single timeline - Copy all trace files and logs you want to open to single folder - Example: You want to open in the same timeline: LTTng, Perf CPU Sampling, Dmesg - Ensure that the Linux CTF folder/trace is zipped and renamed to .ctf in the same folder (hack so open Unified works) - - WPA -> File -> Open -> Multi-select all files and choose "Open Unified" + - WPA -> File -> Open -> Multi-select all files and choose "Single Session" # How do I use WPA in general? If you want to learn how to use the GUI UI in general see [WPA MSDN Docs](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-analyzer) diff --git a/UnitTestCommon/UnitTestCommon.csproj b/UnitTestCommon/UnitTestCommon.csproj index 7233b27..d0462dd 100644 --- a/UnitTestCommon/UnitTestCommon.csproj +++ b/UnitTestCommon/UnitTestCommon.csproj @@ -7,7 +7,7 @@ - +