Skip to content

Commit a04f1e7

Browse files
committed
wip: tracelog support for streaming an eventpipe source
1 parent 5643098 commit a04f1e7

File tree

4 files changed

+116
-30
lines changed

4 files changed

+116
-30
lines changed

src/TraceEvent/EventPipe/EventPipeEventSource.cs

+7
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ public DateTime QPCTimeToTimeStamp(long QPCTime)
125125
/// </summary>
126126
public int MinimumVersionCanRead => 0;
127127

128+
/// <summary>
129+
/// Called after headers are deserialized. This is especially useful in a streaming scenario
130+
/// because the headers are only read after Process() is called.
131+
/// </summary>
132+
internal Action HeadersDeserialized;
133+
128134
protected override void Dispose(bool disposing)
129135
{
130136
if (_deserializer != null)
@@ -144,6 +150,7 @@ public override bool Process()
144150
Debug.Assert(_deserializer != null);
145151
}
146152

153+
HeadersDeserialized?.Invoke();
147154

148155
if (FileFormatVersionNumber >= 3)
149156
{

src/TraceEvent/TraceEvent.Tests/Parsing/EventPipeParsing.cs

+52-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
using FastSerialization;
22
using Microsoft.Diagnostics.Tracing;
3+
using Microsoft.Diagnostics.Tracing.AutomatedAnalysis;
34
using Microsoft.Diagnostics.Tracing.Etlx;
45
using Microsoft.Diagnostics.Tracing.EventPipe;
56
using Microsoft.Diagnostics.Tracing.Parsers;
67
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
8+
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
79
using System;
810
using System.Collections.Generic;
911
using System.Diagnostics;
12+
using System.Diagnostics.Tracing;
1013
using System.IO;
1114
using System.Linq;
15+
using System.Runtime.InteropServices;
1216
using System.Text;
1317
using Xunit;
1418
using Xunit.Abstractions;
19+
using static Microsoft.Diagnostics.Tracing.Etlx.TraceLog;
1520

1621
namespace TraceEventTests
1722
{
@@ -94,8 +99,7 @@ public void Streaming(string eventPipeFileName)
9499
// block to read the events
95100
Assert.InRange(newStreamPosition, curStreamPosition, curStreamPosition + 103_000);
96101
curStreamPosition = newStreamPosition;
97-
98-
102+
99103
string eventName = data.ProviderName + "/" + data.EventName;
100104

101105
// For whatever reason the parse filtering below produces a couple extra events
@@ -148,6 +152,52 @@ public void Streaming(string eventPipeFileName)
148152
ValidateEventStatistics(eventStatistics, eventPipeFileName);
149153
}
150154

155+
[Theory()]
156+
[MemberData(nameof(StreamableTestEventPipeFiles))]
157+
public void TraceLogStreaming(string eventPipeFileName)
158+
{
159+
// Initialize
160+
PrepareTestData();
161+
162+
string eventPipeFilePath = Path.Combine(UnZippedDataDir, eventPipeFileName);
163+
Output.WriteLine(string.Format("Processing the file {0}", Path.GetFullPath(eventPipeFilePath)));
164+
var eventStatistics = new SortedDictionary<string, EventRecord>(StringComparer.Ordinal);
165+
166+
using (MockStreamingOnlyStream s = new MockStreamingOnlyStream(new FileStream(eventPipeFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)))
167+
{
168+
using (var eventPipeSource = new EventPipeEventSource(s))
169+
{
170+
using (var traceSource = TraceLog.CreateFromEventPipeEventSource(eventPipeSource))
171+
{
172+
Action<TraceEvent> handler = delegate (TraceEvent data)
173+
{
174+
string eventName = data.ProviderName + "/" + data.EventName;
175+
176+
if (eventStatistics.ContainsKey(eventName))
177+
{
178+
eventStatistics[eventName].TotalCount++;
179+
}
180+
else
181+
{
182+
eventStatistics[eventName] = new EventRecord()
183+
{
184+
TotalCount = 1,
185+
FirstSeriazliedSample = new String(data.ToString().Replace("\n", "\\n").Replace("\r", "\\r").Take(1000).ToArray())
186+
};
187+
}
188+
};
189+
190+
traceSource.AllEvents += handler;
191+
192+
// Process
193+
traceSource.Process();
194+
}
195+
}
196+
}
197+
// Validate
198+
ValidateEventStatistics(eventStatistics, eventPipeFileName);
199+
}
200+
151201
[Fact]
152202
public void CanParseHeaderOfV1EventPipeFile()
153203
{

src/TraceEvent/TraceEvent.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
</PropertyGroup>
4242

4343
<ItemGroup>
44+
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.421201" />
4445
<PackageReference Include="Microsoft.NETCore.Portable.Compatibility" Version="$(MicrosoftNETCorePortableCompatibilityVersion)" />
4546
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent.SupportFiles" Version="$(MicrosoftDiagnosticsTracingTraceEventSupportFilesVersion)" />
4647
<PackageReference Include="Microsoft.Win32.Registry" Version="$(MicrosoftWin32RegistryVersion)" />

src/TraceEvent/TraceLog.cs

+56-28
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// #define DEBUG_SERIALIZE
66

77
using FastSerialization;
8+
using Microsoft.Diagnostics.NETCore.Client;
89
using Microsoft.Diagnostics.Symbols;
910
using Microsoft.Diagnostics.Tracing.Compatibility;
1011
using Microsoft.Diagnostics.Tracing.EventPipe;
@@ -153,7 +154,38 @@ public static TraceLog OpenOrConvert(string etlOrEtlxFilePath, TraceLogOptions o
153154
/// </summary>
154155
public static TraceLogEventSource CreateFromTraceEventSession(TraceEventSession session)
155156
{
156-
var traceLog = new TraceLog(session);
157+
var traceLog = new TraceLog(session.Source);
158+
159+
// See if we are on Win7 and have a separate kernel session associated with 'session'
160+
if (session.m_kernelSession != null)
161+
{
162+
// Make sure both sources only dispatch one at a time by taking a lock during dispatch.
163+
session.m_kernelSession.Source.lockObj = traceLog.realTimeQueue;
164+
session.m_associatedWithTraceLog = true; // Indicate that it is OK to have the m_kernelSession.
165+
session.Source.lockObj = traceLog.realTimeQueue;
166+
167+
// Set up the callbacks to the kernel session.
168+
traceLog.rawKernelEventSource = session.m_kernelSession.Source;
169+
traceLog.SetupCallbacks(traceLog.rawKernelEventSource);
170+
traceLog.rawKernelEventSource.unhandledEventTemplate.traceEventSource = traceLog; // Make everything point to the log as its source.
171+
// TODO fixme - onAllEvents is local to the constructor
172+
// traceLog.rawKernelEventSource.AllEvents += traceLog.onAllEvents;
173+
}
174+
175+
return traceLog.realTimeSource;
176+
}
177+
178+
public static TraceLogEventSource CreateFromEventPipeSession(EventPipeSession session)
179+
{
180+
return CreateFromEventPipeEventSource(new EventPipeEventSource(session.EventStream));
181+
}
182+
183+
public static TraceLogEventSource CreateFromEventPipeEventSource(EventPipeEventSource source)
184+
{
185+
var traceLog = new TraceLog(source);
186+
var dynamicParser = source.Dynamic;
187+
var clrParser = source.Clr;
188+
var kernelParser = source.Kernel;
157189
return traceLog.realTimeSource;
158190
}
159191

@@ -543,7 +575,7 @@ private TraceLog()
543575
/// Functionality of TraceLog that does not depend on either remembering past EVENTS or require future
544576
/// knowledge (e.g. stacks of kernel events), will 'just work'.
545577
/// </summary>
546-
private unsafe TraceLog(TraceEventSession session)
578+
private unsafe TraceLog(TraceEventDispatcher source)
547579
: this()
548580
{
549581
IsRealTime = true;
@@ -605,23 +637,8 @@ private unsafe TraceLog(TraceEventSession session)
605637
}
606638
};
607639

608-
// See if we are on Win7 and have a separate kernel session associated with 'session'
609-
if (session.m_kernelSession != null)
610-
{
611-
// Make sure both sources only dispatch one at a time by taking a lock during dispatch.
612-
session.m_kernelSession.Source.lockObj = realTimeQueue;
613-
session.m_associatedWithTraceLog = true; // Indicate that it is OK to have the m_kernelSession.
614-
session.Source.lockObj = realTimeQueue;
615-
616-
// Set up the callbacks to the kernel session.
617-
rawKernelEventSource = session.m_kernelSession.Source;
618-
SetupCallbacks(rawKernelEventSource);
619-
rawKernelEventSource.unhandledEventTemplate.traceEventSource = this; // Make everything point to the log as its source.
620-
rawKernelEventSource.AllEvents += onAllEvents;
621-
}
622-
623640
// We use the session's source for our input.
624-
rawEventSourceToConvert = session.Source;
641+
rawEventSourceToConvert = source;
625642
SetupCallbacks(rawEventSourceToConvert);
626643
rawEventSourceToConvert.unhandledEventTemplate.traceEventSource = this; // Make everything point to the log as its source.
627644
rawEventSourceToConvert.AllEvents += onAllEvents;
@@ -1081,16 +1098,27 @@ private unsafe void SetupCallbacks(TraceEventDispatcher rawEvents)
10811098
sourceFilesByID = new Dictionary<JavaScriptSourceKey, string>();
10821099

10831100
// If this is a ETL file, we also need to compute all the normal TraceLog stuff the raw stream
1084-
pointerSize = rawEvents.PointerSize;
1085-
_syncTimeUTC = rawEvents._syncTimeUTC;
1086-
_syncTimeQPC = rawEvents._syncTimeQPC;
1087-
_QPCFreq = rawEvents._QPCFreq;
1088-
sessionStartTimeQPC = rawEvents.sessionStartTimeQPC;
1089-
sessionEndTimeQPC = rawEvents.sessionEndTimeQPC;
1090-
cpuSpeedMHz = rawEvents.CpuSpeedMHz;
1091-
numberOfProcessors = rawEvents.NumberOfProcessors;
1092-
eventsLost = rawEvents.EventsLost;
1093-
osVersion = rawEvents.OSVersion;
1101+
Action copyHeaders = delegate ()
1102+
{
1103+
pointerSize = rawEvents.PointerSize;
1104+
_syncTimeUTC = rawEvents._syncTimeUTC;
1105+
_syncTimeQPC = rawEvents._syncTimeQPC;
1106+
_QPCFreq = rawEvents._QPCFreq;
1107+
sessionStartTimeQPC = rawEvents.sessionStartTimeQPC;
1108+
sessionEndTimeQPC = rawEvents.sessionEndTimeQPC;
1109+
cpuSpeedMHz = rawEvents.CpuSpeedMHz;
1110+
numberOfProcessors = rawEvents.NumberOfProcessors;
1111+
eventsLost = rawEvents.EventsLost;
1112+
osVersion = rawEvents.OSVersion;
1113+
};
1114+
1115+
if (rawEvents is EventPipeEventSource eventPipeEventSource)
1116+
{
1117+
eventPipeEventSource.HeadersDeserialized += copyHeaders;
1118+
} else
1119+
{
1120+
copyHeaders();
1121+
}
10941122

10951123
// These parsers create state and we want to collect that so we put it on our 'parsers' list that we serialize.
10961124
var kernelParser = rawEvents.Kernel;

0 commit comments

Comments
 (0)