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
Expand Up @@ -87,12 +87,6 @@ from threadProcess in pd.DefaultIfEmpty()
Timestamp startTimestamp = new Timestamp(result.schedSlice.RelativeTimestamp);
Timestamp endTimestamp = new Timestamp(result.schedSlice.RelativeTimestamp + result.schedSlice.Duration);

PerfettoCpuWakeEvent? wakeEvent = null;
if (wokenTidToWakeEventsMap.TryGetValue(tid, out List<PerfettoCpuWakeEvent> wakeEvents))
{
wakeEvent = GetWakeEvent(wakeEvents, startTimestamp);
}

PerfettoCpuSchedEvent ev = new PerfettoCpuSchedEvent
(
processName,
Expand All @@ -103,14 +97,45 @@ from threadProcess in pd.DefaultIfEmpty()
endTimestamp,
result.schedSlice.Cpu,
result.schedSlice.EndStateStr,
result.schedSlice.Priority,
wakeEvent
result.schedSlice.Priority
);

this.CpuSchedEvents.AddEvent(ev);
}

this.CpuSchedEvents.FinalizeData();

// Add previous scheduling event info.
// This needs to be done after FinalizeData call to make sure enumeration and indexing is available.
var tidToSwitchEventsMap = this.CpuSchedEvents
.Where(s => s != null)
.GroupBy(s => s.Tid)
.ToDictionary(sg => sg.Key, sg => sg.OrderBy(s => s.StartTimestamp).ToList());

foreach (var tid in tidToSwitchEventsMap.Keys)
{
var cpuSchedEventsForCurrentThread = tidToSwitchEventsMap[tid];

for (int i = 1; i < cpuSchedEventsForCurrentThread.Count; i++)
{
cpuSchedEventsForCurrentThread[i].AddPreviousCpuSchedulingEvent(cpuSchedEventsForCurrentThread[i-1]);
}
}

// Add wake event info if required.
foreach (var schedEvent in this.CpuSchedEvents)
{
// If the thread state was already runnable then there will be no corresponding wake event.
if (schedEvent.PreviousSchedulingEvent?.EndState == "Runnable")
{
continue;
}

if (wokenTidToWakeEventsMap.TryGetValue(schedEvent.Tid, out List<PerfettoCpuWakeEvent> wakeEvents))
{
schedEvent.AddWakeEvent(GetWakeEvent(wakeEvents, schedEvent.StartTimestamp));
}
}
}

void PopulateCpuWakeEvents(IDataExtensionRetrieval requiredData, ProcessedEventData<PerfettoThreadEvent> threadData, ProcessedEventData<PerfettoProcessEvent> processData)
Expand Down Expand Up @@ -165,7 +190,7 @@ void PopulateCpuWakeEvents(IDataExtensionRetrieval requiredData, ProcessedEventD
/// <param name="cpuWakeEvents">Timestamp sorted wake events for the woken thread.</param>
/// <param name="time">Scheduling timestamp of the thread</param>
/// <returns>CPU wake event if exists else null</returns>
PerfettoCpuWakeEvent? GetWakeEvent(IList<PerfettoCpuWakeEvent> cpuWakeEvents, Timestamp time)
PerfettoCpuWakeEvent GetWakeEvent(IList<PerfettoCpuWakeEvent> cpuWakeEvents, Timestamp time)
{
int min = 0;
int max = cpuWakeEvents.Count;
Expand Down
66 changes: 62 additions & 4 deletions PerfettoCds/Pipeline/DataOutput/PerfettoCpuSchedEvent.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.SDK;
using System;
using Utilities;

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 class PerfettoCpuSchedEvent
{
TimestampDelta? waitDuration;
TimestampDelta? schedulingLatency;

public string ProcessName { get; }
public string ThreadName { get; }
public long Tid { get; }
Expand All @@ -19,7 +23,53 @@ public readonly struct PerfettoCpuSchedEvent
public uint Cpu { get; }
public string EndState { get; }
public int Priority { get; }
public PerfettoCpuWakeEvent? WakeEvent { get; }
public PerfettoCpuWakeEvent WakeEvent { get; private set; }
public PerfettoCpuSchedEvent PreviousSchedulingEvent { get; private set; }

public TimestampDelta WaitDuration
{
get
{
if (this.waitDuration == null)
{
if (this.WakeEvent != null && this.PreviousSchedulingEvent != null)
{
this.waitDuration = this.WakeEvent.Timestamp - this.PreviousSchedulingEvent.EndTimestamp;
}
else
{
this.waitDuration = TimestampDelta.Zero;
}
}

return waitDuration.Value;
}
}

public TimestampDelta SchedulingLatency
{
get
{
if (this.schedulingLatency == null)
{
if (this.WakeEvent != null)
{
this.schedulingLatency = this.StartTimestamp - this.WakeEvent.Timestamp;
}
else if (this.PreviousSchedulingEvent?.EndState == "Runnable")
{
// Thread was already 'Runnable' and hence was ready to run. Scheduling latency because of non-availability of CPU.
this.schedulingLatency = this.StartTimestamp - this.PreviousSchedulingEvent.EndTimestamp;
}
else
{
this.schedulingLatency = TimestampDelta.Zero;
}
}

return schedulingLatency.Value;
}
}

public PerfettoCpuSchedEvent(string processName,
string threadName,
Expand All @@ -29,8 +79,7 @@ public PerfettoCpuSchedEvent(string processName,
Timestamp endTimestamp,
uint cpu,
string endState,
int priority,
PerfettoCpuWakeEvent? wakeEvent)
int priority)
{
this.ProcessName = Common.StringIntern(processName);
this.ThreadName = Common.StringIntern(threadName);
Expand All @@ -41,6 +90,15 @@ public PerfettoCpuSchedEvent(string processName,
this.Cpu = cpu;
this.EndState = endState;
this.Priority = priority;
}

public void AddPreviousCpuSchedulingEvent(PerfettoCpuSchedEvent previousCpuSchedEvent)
{
this.PreviousSchedulingEvent = previousCpuSchedEvent;
}

public void AddWakeEvent(PerfettoCpuWakeEvent wakeEvent)
{
this.WakeEvent = wakeEvent;
}
}
Expand Down
2 changes: 1 addition & 1 deletion PerfettoCds/Pipeline/DataOutput/PerfettoCpuWakeEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace PerfettoCds.Pipeline.DataOutput
/// <summary>
/// A CPU wake event that displays which process and threads were woken on which CPUs at specific times.
/// </summary>
public readonly struct PerfettoCpuWakeEvent
public class PerfettoCpuWakeEvent
{
public string WokenProcessName { get; }
public long? WokenPid { get; }
Expand Down
34 changes: 30 additions & 4 deletions PerfettoCds/Pipeline/Tables/PerfettoCpuSchedTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,19 @@ public class PerfettoCpuSchedTable
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 ColumnMetadata(new Guid("{73984a25-99b1-43a9-8412-c57b55de5518}"), "Priority", "Priority of the thread"),
new UIHints { Width = 70 });

private static readonly ColumnConfiguration PreviousCpuColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{6F23E91A-299E-48E5-9F48-485144D9A50B}"), "PreviousCpu", "CPU where the thread ran at previous scheduling event"),
new UIHints { Width = 70, SortOrder = SortOrder.Ascending });

private static readonly ColumnConfiguration PreviousEndStateColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{FB21B315-8AF9-475E-8792-7835B21AB193}"), "PreviousEndState", "Ending state of the previous scheduling event of this thread"),
new UIHints { Width = 70 });

private static readonly ColumnConfiguration PreviousPriorityColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{0B2806ED-14EE-4753-A31E-D408CEA95E1A}"), "PreviousPriority", "Priority of the thread at previous scheduling event of this thread"),
new UIHints { Width = 70 });

private static readonly ColumnConfiguration WakeEventFoundColumn = new ColumnConfiguration(
Expand All @@ -78,6 +90,10 @@ public class PerfettoCpuSchedTable
new ColumnMetadata(new Guid("{51D27617-7734-42A8-921C-E83A585F77E0}"), "WakeTimestamp", "Timestamp when thread was woken"),
new UIHints { Width = 70 });

private static readonly ColumnConfiguration WaitDurationColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{F0B3CD76-0FB7-45C6-9791-DEDB351CFF11}"), "WaitDuration", "Duration between previous slice end timestamp and woken timestamp"),
new UIHints { Width = 70 });

private static readonly ColumnConfiguration SchedulingLatencyColumn = new ColumnConfiguration(
new ColumnMetadata(new Guid("{8315395D-8738-4E1B-9F89-9E4EE239FD72}"), "SchedulingLatency", "Duration between woken timestamp and schedule timestamp"),
new UIHints { Width = 70 });
Expand Down Expand Up @@ -124,9 +140,13 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
tableGenerator.AddColumn(WakerThreadNameColumn, baseProjection.Compose(x => x.WakeEvent?.WakerThreadName ?? String.Empty));
tableGenerator.AddColumn(WakerTidColumn, baseProjection.Compose(x => x.WakeEvent?.WakerTid ?? -1));
tableGenerator.AddColumn(WakerPriorityColumn, baseProjection.Compose(x => x.WakeEvent?.Priority ?? -1));
tableGenerator.AddColumn(WakerCpuColumn, baseProjection.Compose(x => x.WakeEvent != null ? (int)x.WakeEvent.Value.Cpu : -1));
tableGenerator.AddColumn(WakerCpuColumn, baseProjection.Compose(x => x.WakeEvent != null ? (int)x.WakeEvent.Cpu : -1));
tableGenerator.AddColumn(WakeTimestampColumn, baseProjection.Compose(x => x.WakeEvent?.Timestamp ?? Timestamp.MinValue));
tableGenerator.AddColumn(SchedulingLatencyColumn, baseProjection.Compose(x => x.StartTimestamp - (x.WakeEvent?.Timestamp ?? x.StartTimestamp)));
tableGenerator.AddColumn(SchedulingLatencyColumn, baseProjection.Compose(x => x.SchedulingLatency));
tableGenerator.AddColumn(WaitDurationColumn, baseProjection.Compose(x => x.WaitDuration));
tableGenerator.AddColumn(PreviousEndStateColumn, baseProjection.Compose(x => x.PreviousSchedulingEvent?.EndState ?? String.Empty));
tableGenerator.AddColumn(PreviousPriorityColumn, baseProjection.Compose(x => x.PreviousSchedulingEvent?.Priority ?? -1));
tableGenerator.AddColumn(PreviousCpuColumn, baseProjection.Compose(x => x.PreviousSchedulingEvent != null ? (int)x.PreviousSchedulingEvent.Cpu : -1));

// Create projections that are used for calculating CPU usage%
var startProjectionClippedToViewport = Projection.ClipTimeToViewport.Create(startProjection);
Expand Down Expand Up @@ -161,6 +181,7 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
WakerTidColumn,
WakerCpuColumn,
WakeTimestampColumn,
WaitDurationColumn,
SchedulingLatencyColumn,
TableConfiguration.GraphColumn, // Columns after this get graphed
StartTimestampColumn,
Expand All @@ -177,6 +198,7 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
{
Columns = new[]
{
WakeEventFoundColumn,
WakerProcessNameColumn,
WakerThreadNameColumn,
WakerTidColumn,
Expand All @@ -190,8 +212,11 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
EndTimestampColumn,
EndStateColumn,
PriorityColumn,
WakeEventFoundColumn,
PreviousEndStateColumn,
PreviousPriorityColumn,
PreviousCpuColumn,
WakerCpuColumn,
WaitDurationColumn,
SchedulingLatencyColumn,
TableConfiguration.GraphColumn, // Columns after this get graphed
WakeTimestampColumn
Expand Down Expand Up @@ -223,6 +248,7 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
WakerTidColumn,
WakerCpuColumn,
WakeTimestampColumn,
WaitDurationColumn,
SchedulingLatencyColumn,
TableConfiguration.GraphColumn, // Columns after this get graphed
PercentCpuUsageColumn
Expand Down
13 changes: 12 additions & 1 deletion PerfettoUnitTest/PerfettoUnitTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,22 @@ public void TestAndroidTrace()
Assert.IsTrue(cpuSchedEventData[0].ThreadName == "kworker/u17:9 (834)");
Assert.IsTrue(cpuSchedEventData[1].EndState == "Task Dead");
Assert.IsTrue(cpuSchedEventData[0].ProcessName == string.Empty);

Assert.IsTrue(cpuSchedEventData[5801].EndState == "Runnable");
Assert.IsTrue(cpuSchedEventData[5801].ThreadName == "TraceLogApiTest (20855)");
Assert.IsTrue(cpuSchedEventData[5801].ProcessName == "TraceLogApiTest (20855)");

// Wake event validation
Assert.IsTrue(cpuSchedEventData[0].WakeEvent.WokenTid == cpuSchedEventData[0].Tid);
Assert.IsTrue(cpuSchedEventData[1].WakeEvent.WakerTid == 834);
Assert.IsTrue(cpuSchedEventData[1].WakeEvent.WakerThreadName == "kworker/u17:9");
Assert.IsTrue(cpuSchedEventData[9581].WakeEvent.WokenTid == cpuSchedEventData[9581].Tid);
Assert.IsTrue(cpuSchedEventData[9581].WakeEvent.WakerTid == 19701);
Assert.IsTrue(cpuSchedEventData[9581].WakeEvent.WakerThreadName == "kworker/u16:13");

// Previous scheduling event validation
Assert.IsTrue(cpuSchedEventData[9581].PreviousSchedulingEvent.EndState == "Task Dead");
Assert.IsTrue(cpuSchedEventData[9581].PreviousSchedulingEvent.Tid == cpuSchedEventData[9581].Tid);

var ftraceEventData = RuntimeExecutionResults.QueryOutput<ProcessedEventData<PerfettoFtraceEvent>>(
new DataOutputPath(
PerfettoPluginConstants.FtraceEventCookerPath,
Expand Down