Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Processing;
using PerfettoCds.Pipeline.DataOutput;
using PerfettoProcessor;

namespace PerfettoCds.Pipeline.DataCookers
{
/// <summary>
/// Pulls data from multiple individual SQL tables and joins them to create a CPU scheduling event
/// </summary>
public sealed class PerfettoCpuSchedEventCooker : CookedDataReflector, ICompositeDataCookerDescriptor
{
public static readonly DataCookerPath DataCookerPath = PerfettoPluginConstants.CpuSchedEventCookerPath;

public string Description => "CPU scheduling event composite cooker";

public DataCookerPath Path => DataCookerPath;

// Declare all of the cookers that are used by this CompositeCooker.
public IReadOnlyCollection<DataCookerPath> RequiredDataCookers => new[]
{
PerfettoPluginConstants.ThreadCookerPath,
PerfettoPluginConstants.ProcessCookerPath,
PerfettoPluginConstants.SchedSliceCookerPath
};

[DataOutput]
public ProcessedEventData<PerfettoCpuSchedEvent> CpuSchedEvents { get; }

public PerfettoCpuSchedEventCooker() : base(PerfettoPluginConstants.CpuSchedEventCookerPath)
{
this.CpuSchedEvents =
new ProcessedEventData<PerfettoCpuSchedEvent>();
}

public void OnDataAvailable(IDataExtensionRetrieval requiredData)
{
// Gather the data from all the SQL tables
var threadData = requiredData.QueryOutput<ProcessedEventData<PerfettoThreadEvent>>(new DataOutputPath(PerfettoPluginConstants.ThreadCookerPath, nameof(PerfettoThreadCooker.ThreadEvents)));
var processData = requiredData.QueryOutput<ProcessedEventData<PerfettoProcessEvent>>(new DataOutputPath(PerfettoPluginConstants.ProcessCookerPath, nameof(PerfettoProcessCooker.ProcessEvents)));
var schedSliceData = requiredData.QueryOutput<ProcessedEventData<PerfettoSchedSliceEvent>>(new DataOutputPath(PerfettoPluginConstants.SchedSliceCookerPath, nameof(PerfettoSchedSliceCooker.SchedSliceEvents)));

// The sched slice data contains the timings, CPU, priority, and end state info. We get the process and thread from
// those respective tables
var joined = from schedSlice in schedSliceData
join thread in threadData on schedSlice.Utid equals thread.Utid
join process in processData on thread.Upid equals process.Upid
select new { schedSlice, thread, process };

// Create events out of the joined results
foreach (var result in joined)
{
PerfettoCpuSchedEvent ev = new PerfettoCpuSchedEvent
(
result.process.Name,
result.thread.Name,
new TimestampDelta(result.schedSlice.Duration),
new Timestamp(result.schedSlice.Timestamp),
new Timestamp(result.schedSlice.Timestamp + result.schedSlice.Duration),
result.schedSlice.Cpu,
result.schedSlice.EndStateStr,
result.schedSlice.Priority
);
this.CpuSchedEvents.AddEvent(ev);
}
this.CpuSchedEvents.FinalizeData();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
namespace PerfettoCds.Pipeline.DataCookers
{
/// <summary>
/// Pulls data from all the individual SQL tables and joins them to create a Generic Peretto event
/// Pulls data from multiple individual SQL tables and joins them to create a Generic Peretto event
/// </summary>
public sealed class PerfettoGenericEventCooker : CookedDataReflector, ICompositeDataCookerDescriptor
{
Expand Down
40 changes: 40 additions & 0 deletions PerfettoCds/Pipeline/DataOutput/PerfettoCpuSchedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK;

namespace PerfettoCds.Pipeline.DataOutput
{
/// <summary>
/// A CPU scheduled event that displays which process and threads were running on which CPUs at specific times.
/// </summary>
public readonly struct PerfettoCpuSchedEvent
{
public string ProcessName { get; }
public string ThreadName { get; }
public TimestampDelta Duration { get; }
public Timestamp StartTimestamp { get; }
public Timestamp EndTimestamp { get; }
public long Cpu { get; }
public string EndState { get; }
public long Priority { get; }

public PerfettoCpuSchedEvent(string processName,
string threadName,
TimestampDelta duration,
Timestamp startTimestamp,
Timestamp endTimestamp,
long cpu,
string endState,
long priority)
{
this.ProcessName = processName;
this.ThreadName = threadName;
this.Duration = duration;
this.StartTimestamp = startTimestamp;
this.EndTimestamp = endTimestamp;
this.Cpu = cpu;
this.EndState = endState;
this.Priority = priority;
}
}
}
8 changes: 8 additions & 0 deletions PerfettoCds/Pipeline/PerfettoPluginConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,23 @@ public static class PerfettoPluginConstants
public const string ThreadCookerId = "PerfettoThreadCooker";
public const string ThreadTrackCookerId = "PerfettoThreadCookerId";
public const string ProcessCookerId = "PerfettoProcessCooker";
public const string SchedSliceCookerId = "PerfettoSchedSliceCooker";

// ID for composite data cookers
public const string GenericEventCookerId = "PerfettoGenericEventCooker";
public const string CpuSchedEventCookerId = "PerfettoCpuSchedEventCooker";

// Events for source cookers
public const string SliceEvent = PerfettoSliceEvent.Key;
public const string ArgEvent = PerfettoArgEvent.Key;
public const string ThreadTrackEvent = PerfettoThreadTrackEvent.Key;
public const string ThreadEvent = PerfettoThreadEvent.Key;
public const string ProcessEvent = PerfettoProcessEvent.Key;
public const string SchedSliceEvent = PerfettoSchedSliceEvent.Key;

// Output events for composite cookers
public const string GenericEvent = "PerfettoGenericEvent";
public const string CpuSchedEvent = "PerfettoCpuSchedEvent";

// Path from source parser to example data cooker. This is the path
// that is used to programatically access the data cooker's data outputs,
Expand All @@ -46,8 +50,12 @@ public static class PerfettoPluginConstants
new DataCookerPath(PerfettoPluginConstants.ParserId, PerfettoPluginConstants.ThreadCookerId);
public static readonly DataCookerPath ProcessCookerPath =
new DataCookerPath(PerfettoPluginConstants.ParserId, PerfettoPluginConstants.ProcessCookerId);
public static readonly DataCookerPath SchedSliceCookerPath =
new DataCookerPath(PerfettoPluginConstants.ParserId, PerfettoPluginConstants.SchedSliceCookerId);

public static readonly DataCookerPath GenericEventCookerPath =
new DataCookerPath(PerfettoPluginConstants.GenericEventCookerId);
public static readonly DataCookerPath CpuSchedEventCookerPath =
new DataCookerPath(PerfettoPluginConstants.CpuSchedEventCookerId);
}
}
21 changes: 11 additions & 10 deletions PerfettoCds/Pipeline/PerfettoSourceParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,15 @@ void EventCallback(PerfettoSqlEvent ev)
var result = dataProcessor.ProcessDataElement(newEvent, this, cancellationToken);
}

// Perform queries for the events we need
List<PerfettoSqlEvent> eventsToQuery = new List<PerfettoSqlEvent>
{
new PerfettoSliceEvent(),
new PerfettoArgEvent(),
new PerfettoThreadTrackEvent(),
new PerfettoThreadEvent(),
new PerfettoProcessEvent()
// Perform the base queries for all the events we need
List<PerfettoSqlEvent> eventsToQuery = new List<PerfettoSqlEvent>
{
new PerfettoSliceEvent(),
new PerfettoArgEvent(),
new PerfettoThreadTrackEvent(),
new PerfettoThreadEvent(),
new PerfettoProcessEvent(),
new PerfettoSchedSliceEvent(),
};

// Increment progress for each table queried.
Expand All @@ -123,9 +124,9 @@ void EventCallback(PerfettoSqlEvent ev)
var dateTimeQueryStarted = DateTime.UtcNow;
traceProc.QueryTraceForEvents(query.GetSqlQuery(), query.GetEventKey(), EventCallback);
var dateTimeQueryFinished = DateTime.UtcNow;

logger.Verbose($"Query for {query.GetEventKey()} completed in {(dateTimeQueryFinished - dateTimeQueryStarted).TotalSeconds}s at {dateTimeQueryFinished} UTC");

IncreaseProgress(queryProgressIncrease);
}

Expand Down
52 changes: 52 additions & 0 deletions PerfettoCds/Pipeline/SourceDataCookers/PerfettoSchedSliceCooker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Extensibility.DataCooking;
using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking;
using Microsoft.Performance.SDK.Processing;
using System.Collections.Generic;
using System.Threading;
using PerfettoCds.Pipeline.Events;
using PerfettoProcessor;

namespace PerfettoCds
{
/// <summary>
/// Cooks the data from the SchedSlice table in Perfetto traces
/// </summary>
public sealed class PerfettoSchedSliceCooker : BaseSourceDataCooker<PerfettoSqlEventKeyed, PerfettoSourceParser, string>
{
public override string Description => "Processes events from the sched_slice Perfetto SQL table";

//
// The data this cooker outputs. Tables or other cookers can query for this data
// via the SDK runtime
//
[DataOutput]
public ProcessedEventData<PerfettoSchedSliceEvent> SchedSliceEvents { get; }

// Instructs runtime to only send events with the given keys this data cooker
public override ReadOnlyHashSet<string> DataKeys =>
new ReadOnlyHashSet<string>(new HashSet<string> { PerfettoPluginConstants.SchedSliceEvent });


public PerfettoSchedSliceCooker() : base(PerfettoPluginConstants.SchedSliceCookerPath)
{
this.SchedSliceEvents = new ProcessedEventData<PerfettoSchedSliceEvent>();
}

public override DataProcessingResult CookDataElement(PerfettoSqlEventKeyed perfettoEvent, PerfettoSourceParser context, CancellationToken cancellationToken)
{
this.SchedSliceEvents.AddEvent((PerfettoSchedSliceEvent)perfettoEvent.SqlEvent);

return DataProcessingResult.Processed;
}

public override void EndDataCooking(CancellationToken cancellationToken)
{
base.EndDataCooking(cancellationToken);
this.SchedSliceEvents.FinalizeData();
}
}
}
106 changes: 106 additions & 0 deletions PerfettoCds/Pipeline/Tables/PerfettoCpuSchedTable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK.Extensibility;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Diagnostics.CodeAnalysis;
using PerfettoCds.Pipeline.DataOutput;
using Microsoft.Performance.SDK;
using PerfettoCds.Pipeline.DataCookers;
using System.Linq;

namespace PerfettoCds.Pipeline.Tables
{
[Table]
public class PerfettoCpuSchedTable
{
public static TableDescriptor TableDescriptor => new TableDescriptor(
Guid.Parse("{db17169e-afe5-41f6-ba24-511af1d869f9}"),
"Perfetto CPU Scheduler Events",
"Displays CPU scheduling events for processes and threads",
"Perfetto",
requiredDataCookers: new List<DataCookerPath> { PerfettoPluginConstants.CpuSchedEventCookerPath }
);

private static readonly ColumnConfiguration ProcessNameColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{91a51bf2-85d6-4932-9df5-dc44445e8521}"), "Process", "Name of the process"),
new UIHints { Width = 210 });

private static readonly ColumnConfiguration ThreadNameColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{62c7c290-3803-4a1a-8bcb-a4f441dc35b6}"), "Thread", "Name of the thread"),
new UIHints { Width = 210 });

private static readonly ColumnConfiguration StartTimestampColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{9c242b6d-bc9a-440d-8eff-82b1b6571d38}"), "StartTimestamp", "Start timestamp for the event"),
new UIHints { Width = 120 });

private static readonly ColumnConfiguration EndTimestampColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{5d37669b-7ae3-471b-97b2-06b593565cd6}"), "EndTimestamp", "End timestamp for the event"),
new UIHints { Width = 120 });

private static readonly ColumnConfiguration DurationColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{1e1d2517-9bf9-4533-b00f-9744021dcf05}"), "Duration", "Duration of the event"),
new UIHints { Width = 70 });

private static readonly ColumnConfiguration CpuColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{704e6901-bc63-46b4-b426-c0642342c991}"), "Cpu", "The CPU this event happened on"),
new UIHints { Width = 70, SortOrder = SortOrder.Ascending });

private static readonly ColumnConfiguration EndStateColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{9f33703d-d2d6-49b1-8d0c-758f4a875d2b}"), "EndState", "Ending state of the event"),
new UIHints { Width = 70 });

private static readonly ColumnConfiguration PriorityColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{73984a25-99b1-43a9-8412-c57b55de5518}"), "Priority", "Priority of the event"),
new UIHints { Width = 70 });

public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval tableData)
{
// Get data from the cooker
var events = tableData.QueryOutput<ProcessedEventData<PerfettoCpuSchedEvent>>(
new DataOutputPath(PerfettoPluginConstants.CpuSchedEventCookerPath, nameof(PerfettoCpuSchedEventCooker.CpuSchedEvents)));

var test = events.OrderBy(x => x.Cpu);

// Start construction of the column order. Pivot on process and thread
List<ColumnConfiguration> allColumns = new List<ColumnConfiguration>()
{
CpuColumn,
ProcessNameColumn,
ThreadNameColumn,
TableConfiguration.PivotColumn, // Columns before this get pivotted on
DurationColumn,
EndStateColumn,
PriorityColumn,
TableConfiguration.GraphColumn, // Columns after this get graphed
StartTimestampColumn,
EndTimestampColumn
};

var tableGenerator = tableBuilder.SetRowCount((int)events.Count);
Comment thread
KyleStorck marked this conversation as resolved.
var baseProjection = Projection.Index(events);

tableGenerator.AddColumn(CpuColumn, baseProjection.Compose(x => x.Cpu));
tableGenerator.AddColumn(ProcessNameColumn, baseProjection.Compose(x => x.ProcessName));
tableGenerator.AddColumn(ThreadNameColumn, baseProjection.Compose(x => x.ThreadName));
tableGenerator.AddColumn(DurationColumn, baseProjection.Compose(x => x.Duration));
tableGenerator.AddColumn(EndStateColumn, baseProjection.Compose(x => x.EndState));
tableGenerator.AddColumn(PriorityColumn, baseProjection.Compose(x => x.Priority));
tableGenerator.AddColumn(StartTimestampColumn, baseProjection.Compose(x => x.StartTimestamp));
tableGenerator.AddColumn(EndTimestampColumn, baseProjection.Compose(x => x.EndTimestamp));

var tableConfig = new TableConfiguration("Perfetto CPU Scheduling")
{
Columns = allColumns,
Layout = TableLayoutStyle.GraphAndTable
};
tableConfig.AddColumnRole(ColumnRole.StartTime, StartTimestampColumn.Metadata.Guid);
tableConfig.AddColumnRole(ColumnRole.EndTime, EndTimestampColumn.Metadata.Guid);
tableConfig.AddColumnRole(ColumnRole.Duration, DurationColumn.Metadata.Guid);

tableBuilder.AddTableConfiguration(tableConfig).SetDefaultTableConfiguration(tableConfig);
}
}
}
2 changes: 1 addition & 1 deletion PerfettoCds/Pipeline/Tables/PerfettoGenericEventTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class PerfettoGenericEventTable
public const int AbsoluteMaxFields = 20;

public static TableDescriptor TableDescriptor => new TableDescriptor(
Guid.Parse("{37cedaaa-5679-4366-b627-9b638aaefeb3}"),
Guid.Parse("{506777b6-f1a3-437a-b976-bc48190450b6}"),
"Perfetto Generic Events",
"All app/component events in the Perfetto trace",
"Perfetto",
Expand Down
Loading