Skip to content

Commit 5b32c5f

Browse files
Add previous CPU scheduling event info. (#59)
* Add previous CPU scheduling event info. * Update comments.
1 parent 58c2eb0 commit 5b32c5f

File tree

5 files changed

+139
-19
lines changed

5 files changed

+139
-19
lines changed

PerfettoCds/Pipeline/CompositeDataCookers/PerfettoCpuSchedEventCooker.cs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,6 @@ from threadProcess in pd.DefaultIfEmpty()
8787
Timestamp startTimestamp = new Timestamp(result.schedSlice.RelativeTimestamp);
8888
Timestamp endTimestamp = new Timestamp(result.schedSlice.RelativeTimestamp + result.schedSlice.Duration);
8989

90-
PerfettoCpuWakeEvent? wakeEvent = null;
91-
if (wokenTidToWakeEventsMap.TryGetValue(tid, out List<PerfettoCpuWakeEvent> wakeEvents))
92-
{
93-
wakeEvent = GetWakeEvent(wakeEvents, startTimestamp);
94-
}
95-
9690
PerfettoCpuSchedEvent ev = new PerfettoCpuSchedEvent
9791
(
9892
processName,
@@ -103,14 +97,45 @@ from threadProcess in pd.DefaultIfEmpty()
10397
endTimestamp,
10498
result.schedSlice.Cpu,
10599
result.schedSlice.EndStateStr,
106-
result.schedSlice.Priority,
107-
wakeEvent
100+
result.schedSlice.Priority
108101
);
109102

110103
this.CpuSchedEvents.AddEvent(ev);
111104
}
112105

113106
this.CpuSchedEvents.FinalizeData();
107+
108+
// Add previous scheduling event info.
109+
// This needs to be done after FinalizeData call to make sure enumeration and indexing is available.
110+
var tidToSwitchEventsMap = this.CpuSchedEvents
111+
.Where(s => s != null)
112+
.GroupBy(s => s.Tid)
113+
.ToDictionary(sg => sg.Key, sg => sg.OrderBy(s => s.StartTimestamp).ToList());
114+
115+
foreach (var tid in tidToSwitchEventsMap.Keys)
116+
{
117+
var cpuSchedEventsForCurrentThread = tidToSwitchEventsMap[tid];
118+
119+
for (int i = 1; i < cpuSchedEventsForCurrentThread.Count; i++)
120+
{
121+
cpuSchedEventsForCurrentThread[i].AddPreviousCpuSchedulingEvent(cpuSchedEventsForCurrentThread[i-1]);
122+
}
123+
}
124+
125+
// Add wake event info if required.
126+
foreach (var schedEvent in this.CpuSchedEvents)
127+
{
128+
// If the thread state was already runnable then there will be no corresponding wake event.
129+
if (schedEvent.PreviousSchedulingEvent?.EndState == "Runnable")
130+
{
131+
continue;
132+
}
133+
134+
if (wokenTidToWakeEventsMap.TryGetValue(schedEvent.Tid, out List<PerfettoCpuWakeEvent> wakeEvents))
135+
{
136+
schedEvent.AddWakeEvent(GetWakeEvent(wakeEvents, schedEvent.StartTimestamp));
137+
}
138+
}
114139
}
115140

116141
void PopulateCpuWakeEvents(IDataExtensionRetrieval requiredData, ProcessedEventData<PerfettoThreadEvent> threadData, ProcessedEventData<PerfettoProcessEvent> processData)
@@ -165,7 +190,7 @@ void PopulateCpuWakeEvents(IDataExtensionRetrieval requiredData, ProcessedEventD
165190
/// <param name="cpuWakeEvents">Timestamp sorted wake events for the woken thread.</param>
166191
/// <param name="time">Scheduling timestamp of the thread</param>
167192
/// <returns>CPU wake event if exists else null</returns>
168-
PerfettoCpuWakeEvent? GetWakeEvent(IList<PerfettoCpuWakeEvent> cpuWakeEvents, Timestamp time)
193+
PerfettoCpuWakeEvent GetWakeEvent(IList<PerfettoCpuWakeEvent> cpuWakeEvents, Timestamp time)
169194
{
170195
int min = 0;
171196
int max = cpuWakeEvents.Count;

PerfettoCds/Pipeline/DataOutput/PerfettoCpuSchedEvent.cs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33
using Microsoft.Performance.SDK;
4+
using System;
45
using Utilities;
56

67
namespace PerfettoCds.Pipeline.DataOutput
78
{
89
/// <summary>
910
/// A CPU scheduled event that displays which process and threads were running on which CPUs at specific times.
1011
/// </summary>
11-
public readonly struct PerfettoCpuSchedEvent
12+
public class PerfettoCpuSchedEvent
1213
{
14+
TimestampDelta? waitDuration;
15+
TimestampDelta? schedulingLatency;
16+
1317
public string ProcessName { get; }
1418
public string ThreadName { get; }
1519
public long Tid { get; }
@@ -19,7 +23,53 @@ public readonly struct PerfettoCpuSchedEvent
1923
public uint Cpu { get; }
2024
public string EndState { get; }
2125
public int Priority { get; }
22-
public PerfettoCpuWakeEvent? WakeEvent { get; }
26+
public PerfettoCpuWakeEvent WakeEvent { get; private set; }
27+
public PerfettoCpuSchedEvent PreviousSchedulingEvent { get; private set; }
28+
29+
public TimestampDelta WaitDuration
30+
{
31+
get
32+
{
33+
if (this.waitDuration == null)
34+
{
35+
if (this.WakeEvent != null && this.PreviousSchedulingEvent != null)
36+
{
37+
this.waitDuration = this.WakeEvent.Timestamp - this.PreviousSchedulingEvent.EndTimestamp;
38+
}
39+
else
40+
{
41+
this.waitDuration = TimestampDelta.Zero;
42+
}
43+
}
44+
45+
return waitDuration.Value;
46+
}
47+
}
48+
49+
public TimestampDelta SchedulingLatency
50+
{
51+
get
52+
{
53+
if (this.schedulingLatency == null)
54+
{
55+
if (this.WakeEvent != null)
56+
{
57+
this.schedulingLatency = this.StartTimestamp - this.WakeEvent.Timestamp;
58+
}
59+
else if (this.PreviousSchedulingEvent?.EndState == "Runnable")
60+
{
61+
// Thread was already 'Runnable' and hence was ready to run. Scheduling latency because of non-availability of CPU.
62+
this.schedulingLatency = this.StartTimestamp - this.PreviousSchedulingEvent.EndTimestamp;
63+
}
64+
else
65+
{
66+
this.schedulingLatency = TimestampDelta.Zero;
67+
}
68+
}
69+
70+
return schedulingLatency.Value;
71+
}
72+
}
2373

2474
public PerfettoCpuSchedEvent(string processName,
2575
string threadName,
@@ -29,8 +79,7 @@ public PerfettoCpuSchedEvent(string processName,
2979
Timestamp endTimestamp,
3080
uint cpu,
3181
string endState,
32-
int priority,
33-
PerfettoCpuWakeEvent? wakeEvent)
82+
int priority)
3483
{
3584
this.ProcessName = Common.StringIntern(processName);
3685
this.ThreadName = Common.StringIntern(threadName);
@@ -41,6 +90,15 @@ public PerfettoCpuSchedEvent(string processName,
4190
this.Cpu = cpu;
4291
this.EndState = endState;
4392
this.Priority = priority;
93+
}
94+
95+
public void AddPreviousCpuSchedulingEvent(PerfettoCpuSchedEvent previousCpuSchedEvent)
96+
{
97+
this.PreviousSchedulingEvent = previousCpuSchedEvent;
98+
}
99+
100+
public void AddWakeEvent(PerfettoCpuWakeEvent wakeEvent)
101+
{
44102
this.WakeEvent = wakeEvent;
45103
}
46104
}

PerfettoCds/Pipeline/DataOutput/PerfettoCpuWakeEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace PerfettoCds.Pipeline.DataOutput
88
/// <summary>
99
/// A CPU wake event that displays which process and threads were woken on which CPUs at specific times.
1010
/// </summary>
11-
public readonly struct PerfettoCpuWakeEvent
11+
public class PerfettoCpuWakeEvent
1212
{
1313
public string WokenProcessName { get; }
1414
public long? WokenPid { get; }

PerfettoCds/Pipeline/Tables/PerfettoCpuSchedTable.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,19 @@ public class PerfettoCpuSchedTable
5151
new UIHints { Width = 70 });
5252

5353
private static readonly ColumnConfiguration PriorityColumn = new ColumnConfiguration(
54-
new ColumnMetadata(new Guid("{73984a25-99b1-43a9-8412-c57b55de5518}"), "Priority", "Priority of the event"),
54+
new ColumnMetadata(new Guid("{73984a25-99b1-43a9-8412-c57b55de5518}"), "Priority", "Priority of the thread"),
55+
new UIHints { Width = 70 });
56+
57+
private static readonly ColumnConfiguration PreviousCpuColumn = new ColumnConfiguration(
58+
new ColumnMetadata(new Guid("{6F23E91A-299E-48E5-9F48-485144D9A50B}"), "PreviousCpu", "CPU where the thread ran at previous scheduling event"),
59+
new UIHints { Width = 70, SortOrder = SortOrder.Ascending });
60+
61+
private static readonly ColumnConfiguration PreviousEndStateColumn = new ColumnConfiguration(
62+
new ColumnMetadata(new Guid("{FB21B315-8AF9-475E-8792-7835B21AB193}"), "PreviousEndState", "Ending state of the previous scheduling event of this thread"),
63+
new UIHints { Width = 70 });
64+
65+
private static readonly ColumnConfiguration PreviousPriorityColumn = new ColumnConfiguration(
66+
new ColumnMetadata(new Guid("{0B2806ED-14EE-4753-A31E-D408CEA95E1A}"), "PreviousPriority", "Priority of the thread at previous scheduling event of this thread"),
5567
new UIHints { Width = 70 });
5668

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

93+
private static readonly ColumnConfiguration WaitDurationColumn = new ColumnConfiguration(
94+
new ColumnMetadata(new Guid("{F0B3CD76-0FB7-45C6-9791-DEDB351CFF11}"), "WaitDuration", "Duration between previous slice end timestamp and woken timestamp"),
95+
new UIHints { Width = 70 });
96+
8197
private static readonly ColumnConfiguration SchedulingLatencyColumn = new ColumnConfiguration(
8298
new ColumnMetadata(new Guid("{8315395D-8738-4E1B-9F89-9E4EE239FD72}"), "SchedulingLatency", "Duration between woken timestamp and schedule timestamp"),
8399
new UIHints { Width = 70 });
@@ -124,9 +140,13 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
124140
tableGenerator.AddColumn(WakerThreadNameColumn, baseProjection.Compose(x => x.WakeEvent?.WakerThreadName ?? String.Empty));
125141
tableGenerator.AddColumn(WakerTidColumn, baseProjection.Compose(x => x.WakeEvent?.WakerTid ?? -1));
126142
tableGenerator.AddColumn(WakerPriorityColumn, baseProjection.Compose(x => x.WakeEvent?.Priority ?? -1));
127-
tableGenerator.AddColumn(WakerCpuColumn, baseProjection.Compose(x => x.WakeEvent != null ? (int)x.WakeEvent.Value.Cpu : -1));
143+
tableGenerator.AddColumn(WakerCpuColumn, baseProjection.Compose(x => x.WakeEvent != null ? (int)x.WakeEvent.Cpu : -1));
128144
tableGenerator.AddColumn(WakeTimestampColumn, baseProjection.Compose(x => x.WakeEvent?.Timestamp ?? Timestamp.MinValue));
129-
tableGenerator.AddColumn(SchedulingLatencyColumn, baseProjection.Compose(x => x.StartTimestamp - (x.WakeEvent?.Timestamp ?? x.StartTimestamp)));
145+
tableGenerator.AddColumn(SchedulingLatencyColumn, baseProjection.Compose(x => x.SchedulingLatency));
146+
tableGenerator.AddColumn(WaitDurationColumn, baseProjection.Compose(x => x.WaitDuration));
147+
tableGenerator.AddColumn(PreviousEndStateColumn, baseProjection.Compose(x => x.PreviousSchedulingEvent?.EndState ?? String.Empty));
148+
tableGenerator.AddColumn(PreviousPriorityColumn, baseProjection.Compose(x => x.PreviousSchedulingEvent?.Priority ?? -1));
149+
tableGenerator.AddColumn(PreviousCpuColumn, baseProjection.Compose(x => x.PreviousSchedulingEvent != null ? (int)x.PreviousSchedulingEvent.Cpu : -1));
130150

131151
// Create projections that are used for calculating CPU usage%
132152
var startProjectionClippedToViewport = Projection.ClipTimeToViewport.Create(startProjection);
@@ -161,6 +181,7 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
161181
WakerTidColumn,
162182
WakerCpuColumn,
163183
WakeTimestampColumn,
184+
WaitDurationColumn,
164185
SchedulingLatencyColumn,
165186
TableConfiguration.GraphColumn, // Columns after this get graphed
166187
StartTimestampColumn,
@@ -177,6 +198,7 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
177198
{
178199
Columns = new[]
179200
{
201+
WakeEventFoundColumn,
180202
WakerProcessNameColumn,
181203
WakerThreadNameColumn,
182204
WakerTidColumn,
@@ -190,8 +212,11 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
190212
EndTimestampColumn,
191213
EndStateColumn,
192214
PriorityColumn,
193-
WakeEventFoundColumn,
215+
PreviousEndStateColumn,
216+
PreviousPriorityColumn,
217+
PreviousCpuColumn,
194218
WakerCpuColumn,
219+
WaitDurationColumn,
195220
SchedulingLatencyColumn,
196221
TableConfiguration.GraphColumn, // Columns after this get graphed
197222
WakeTimestampColumn
@@ -223,6 +248,7 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
223248
WakerTidColumn,
224249
WakerCpuColumn,
225250
WakeTimestampColumn,
251+
WaitDurationColumn,
226252
SchedulingLatencyColumn,
227253
TableConfiguration.GraphColumn, // Columns after this get graphed
228254
PercentCpuUsageColumn

PerfettoUnitTest/PerfettoUnitTest.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,22 @@ public void TestAndroidTrace()
9494
Assert.IsTrue(cpuSchedEventData[0].ThreadName == "kworker/u17:9 (834)");
9595
Assert.IsTrue(cpuSchedEventData[1].EndState == "Task Dead");
9696
Assert.IsTrue(cpuSchedEventData[0].ProcessName == string.Empty);
97-
9897
Assert.IsTrue(cpuSchedEventData[5801].EndState == "Runnable");
9998
Assert.IsTrue(cpuSchedEventData[5801].ThreadName == "TraceLogApiTest (20855)");
10099
Assert.IsTrue(cpuSchedEventData[5801].ProcessName == "TraceLogApiTest (20855)");
101100

101+
// Wake event validation
102+
Assert.IsTrue(cpuSchedEventData[0].WakeEvent.WokenTid == cpuSchedEventData[0].Tid);
103+
Assert.IsTrue(cpuSchedEventData[1].WakeEvent.WakerTid == 834);
104+
Assert.IsTrue(cpuSchedEventData[1].WakeEvent.WakerThreadName == "kworker/u17:9");
105+
Assert.IsTrue(cpuSchedEventData[9581].WakeEvent.WokenTid == cpuSchedEventData[9581].Tid);
106+
Assert.IsTrue(cpuSchedEventData[9581].WakeEvent.WakerTid == 19701);
107+
Assert.IsTrue(cpuSchedEventData[9581].WakeEvent.WakerThreadName == "kworker/u16:13");
108+
109+
// Previous scheduling event validation
110+
Assert.IsTrue(cpuSchedEventData[9581].PreviousSchedulingEvent.EndState == "Task Dead");
111+
Assert.IsTrue(cpuSchedEventData[9581].PreviousSchedulingEvent.Tid == cpuSchedEventData[9581].Tid);
112+
102113
var ftraceEventData = RuntimeExecutionResults.QueryOutput<ProcessedEventData<PerfettoFtraceEvent>>(
103114
new DataOutputPath(
104115
PerfettoPluginConstants.FtraceEventCookerPath,

0 commit comments

Comments
 (0)