Skip to content

Commit 43d89be

Browse files
authored
Implement Nettrace Support for Traces with Universal Providers (#2182)
* Implement nettrace conversion to TraceLog using universal providers. * Add CPU stacks. * Uncomment assert. * Multi-process Support * Clean-up nettrace file views based on what data is present. Update Nettrace Universal converter with v6 fields. * React to changes in the Universal providers. Integral types --> VarInt Null-terminated UTF16 strings --> Counted UTF8 strings * Clean-up parsers. * Clean-up * Add documentation for the universal providers. * Updates to universal provider documentation. * Fix typo
1 parent 7fff15f commit 43d89be

File tree

8 files changed

+849
-15
lines changed

8 files changed

+849
-15
lines changed

src/MemoryGraph/graph.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,7 @@ public class Module : IFastSerializable
10701070
/// <summary>
10711071
/// The size of the image when loaded in memory
10721072
/// </summary>
1073-
public int Size;
1073+
public long Size;
10741074
/// <summary>
10751075
/// The time when this image was built (There is a field in the PE header). May be MinimumValue if unknonwn.
10761076
/// </summary>

src/PerfView/PerfViewData.cs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
using Utilities;
4343
using Address = System.UInt64;
4444
using EventSource = EventSources.EventSource;
45+
using Microsoft.Diagnostics.Tracing.Parsers.Universal.Events;
4546

4647
namespace PerfView
4748
{
@@ -6017,7 +6018,7 @@ protected internal override StackSource OpenStackSourceImpl(string streamName, T
60176018
{
60186019
const int numBuckets = 20;
60196020
int bucket = (int)(normalizeDistance * numBuckets);
6020-
int bucketSizeInPages = module.ModuleFile.ImageSize / (numBuckets * 4096);
6021+
int bucketSizeInPages = (int)(module.ModuleFile.ImageSize / (numBuckets * 4096));
60216022
string bucketName = "Image Bucket " + bucket + " Size " + bucketSizeInPages + " Pages";
60226023
stackIndex = stackSource.Interner.CallStackIntern(stackSource.Interner.FrameIntern(bucketName), stackIndex);
60236024
}
@@ -9403,6 +9404,29 @@ public partial class EventPipePerfViewData : PerfViewFile
94039404

94049405
private string m_extraTopStats;
94059406

9407+
public override bool SupportsProcesses => m_supportsProcesses;
9408+
9409+
private bool m_supportsProcesses;
9410+
9411+
public override List<IProcess> GetProcesses(TextWriter log)
9412+
{
9413+
var eventLog = GetTraceLog(log);
9414+
var processes = new List<IProcess>(eventLog.Processes.Count);
9415+
foreach (var process in eventLog.Processes)
9416+
{
9417+
var iprocess = new IProcessForStackSource(process.Name);
9418+
iprocess.StartTime = process.StartTime;
9419+
iprocess.EndTime = process.EndTime;
9420+
iprocess.CPUTimeMSec = process.CPUMSec;
9421+
iprocess.ParentID = process.ParentID;
9422+
iprocess.CommandLine = process.CommandLine;
9423+
iprocess.ProcessID = process.ProcessID;
9424+
processes.Add(iprocess);
9425+
}
9426+
processes.Sort();
9427+
return processes;
9428+
}
9429+
94069430
protected internal override EventSource OpenEventSourceImpl(TextWriter log)
94079431
{
94089432
var traceLog = GetTraceLog(log);
@@ -9433,6 +9457,8 @@ protected override Action<Action> OpenImpl(Window parentWindow, StatusBar worker
94339457
bool hasAspNetCoreHosting = false;
94349458
bool hasContention = false;
94359459
bool hasWaitHandle = false;
9460+
bool hasUniversalSystem = false;
9461+
bool hasUniversalCPU = false;
94369462
if (m_traceLog != null)
94379463
{
94389464
foreach (TraceEventCounts eventStats in m_traceLog.Stats)
@@ -9486,6 +9512,16 @@ protected override Action<Action> OpenImpl(Window parentWindow, StatusBar worker
94869512
{
94879513
hasWaitHandle = true;
94889514
}
9515+
else if (eventStats.ProviderGuid == UniversalSystemTraceEventParser.ProviderGuid)
9516+
{
9517+
hasUniversalSystem = true;
9518+
m_supportsProcesses = true;
9519+
}
9520+
else if (eventStats.ProviderGuid == UniversalEventsTraceEventParser.ProviderGuid && eventStats.EventName.StartsWith("cpu"))
9521+
{
9522+
hasUniversalCPU = true;
9523+
m_supportsProcesses = true;
9524+
}
94899525
}
94909526
}
94919527

@@ -9495,13 +9531,30 @@ protected override Action<Action> OpenImpl(Window parentWindow, StatusBar worker
94959531

94969532
if (m_traceLog != null)
94979533
{
9534+
if (hasUniversalSystem) // universal
9535+
{
9536+
m_Children.Add(new PerfViewProcesses(this));
9537+
9538+
if (hasUniversalCPU)
9539+
{
9540+
m_Children.Add(new PerfViewStackSource(this, "CPU"));
9541+
}
9542+
}
9543+
else // dotnet-trace
9544+
{
9545+
if (hasAnyStacks)
9546+
{
9547+
m_Children.Add(new PerfViewStackSource(this, "Thread Time (with StartStop Activities)"));
9548+
}
9549+
}
9550+
9551+
// Source agnostic
94989552
m_Children.Add(new PerfViewEventSource(this));
9499-
m_Children.Add(new PerfViewEventStats(this));
9553+
advanced.AddChild(new PerfViewEventStats(this));
95009554

95019555
if (hasAnyStacks)
95029556
{
9503-
m_Children.Add(new PerfViewStackSource(this, "Thread Time (with StartStop Activities)"));
9504-
m_Children.Add(new PerfViewStackSource(this, "Any"));
9557+
advanced.AddChild(new PerfViewStackSource(this, "Any"));
95059558
}
95069559

95079560
if (hasGC)
@@ -9750,6 +9803,26 @@ protected internal override StackSource OpenStackSourceImpl(string streamName, T
97509803
eventSource.Process();
97519804
stackSource.DoneAddingSamples();
97529805

9806+
return stackSource;
9807+
}
9808+
case "CPU":
9809+
{
9810+
var eventLog = GetTraceLog(log);
9811+
var eventSource = eventLog.Events.GetSource();
9812+
var stackSource = new MutableTraceEventStackSource(eventLog);
9813+
var sample = new StackSourceSample(stackSource);
9814+
9815+
var universalEventsParser = new UniversalEventsTraceEventParser(eventSource);
9816+
universalEventsParser.cpu += delegate (SampleTraceData data)
9817+
{
9818+
sample.TimeRelativeMSec = data.TimeStampRelativeMSec;
9819+
sample.Metric = data.Value;
9820+
sample.StackIndex = stackSource.GetCallStack(data.CallStackIndex(), data);
9821+
stackSource.AddSample(sample);
9822+
9823+
};
9824+
eventSource.Process();
9825+
97539826
return stackSource;
97549827
}
97559828
default:
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Universal Providers Tracing Events
2+
3+
## Overview
4+
This document describes the definitions of a set of universal machine-wide tracing events that are consumed by TraceEvent and exposed in PerfView.
5+
6+
## Format Restrictions
7+
- Any format that can be converted to a TraceLog (ETLX) by TraceEvent can use these event definitions. At this time, PerfView will only consume them from nettrace files.
8+
- Formats must provide a common set of fields with each event. These fields are used by in addition to the payloads listed in this document to interpret the events. The fields do not explicitly require data types. If they don't match what TraceEvent uses for storage, then TraceEvent will need to convert them. It is recommended to use the types specified in the field descriptions below (if specified).
9+
- Formats must provide the QPC frequency to ensure that times that are stored in the Value fields of the Universal Events Provider can be interpreted.
10+
11+
### Required Fields
12+
- TimeStamp: The time that the event occurred.
13+
- Process ID: The integer identifier of the process that caused the event to be written.
14+
- Thread ID: The integer identifier of the thread that caused the event to be written.
15+
- Processor ID: The integer identifier of the processor associated with the event.
16+
17+
## Common Format Information
18+
- All integers are of type varint encoded in ULEB64/7-bit encoded format.
19+
- All strings are length-prefixed (16-bit unsigned integer) followed by UTF8 bytes. Not null-terminated.
20+
21+
## Universal System Provider
22+
The universal system provider is named "Universal.System". It's purpose is to provide system-level events for reconstructing the state of a machine and its processes during a period of time.
23+
24+
### Event Definitions
25+
- **Event Name**: ProcessCreate
26+
- **Description**: Identifies a newly created processes. The process ID is inferred from the format's process ID field.
27+
- **Field: NamespaceId**: varint - The actual process ID on the system within the running namespace.
28+
- **Field: Name**: string - The name of the process.
29+
- **Field: NamespaceName**: string - Namespace of the process. Can be used to convey a set of related processes.
30+
31+
- **Event Name**: ProcessExit
32+
- **Description**: Identifies a process that has exited. The process ID is inferred from the format's process ID field.
33+
34+
- **Event Name**: ProcessMapping
35+
- **Description**: Identifies a mapping (file, memory, etc.) associated with a specific process.
36+
- **Field: Id**: varint - A trace-wide unique id for the mapping. Used to identify this mapping in other events.
37+
- **Field: ProcessId**: varint - The process ID on the system that the mapping is for.
38+
- **Field: StartAddress**: varint - Starting virtual address of the mapping within the process.
39+
- **Field: EndAddress**: varint - Ending virtual address of the mapping within the process.
40+
- **Field: FileOffset**: varint - Offset of the actual file on disk that StartAddress correlates to. For PE files this is always zero.
41+
- **Field: FileName**: string - Name of the file the mapping represents. Empty if none.
42+
- **Field: MetadataId**: varint - Identifier for a matcing ProcessMappingMetadataEvent. 0 means no metadata exists for this mapping.
43+
44+
- **Event Name**: ProcessMappingMetadata
45+
- **Description**: Contains symbol and version information for the mapping if available.
46+
- **Field: Id**: varint - Trace-wide unique identifier for the mapping metadata. Often seen in multiple ProcessMapping events when the same executable file is loaded into multiple processes.
47+
- **Field: SymbolMetadata**: string - Symbol metadata in JSON. Describes the type (ELF/PE) and details that enable looking up symbols locally or remotely.
48+
- **Field: VersionMetadata**: string - Version metadata in JSON. Describes the build and version details.
49+
50+
- **Event Name**: ProcessSymbol
51+
- **Description**: Maps a range of address space to a readable name (symbol).
52+
- **Field: Id**: varint - Trace-wide unique identifier for the symbol.
53+
- **Field: MappingId**: varint - Id of the ProcessMapping that contains the symbol.
54+
- **Field: StartAddress**: varint - Starting virtual address of the symbol within the mapping.
55+
- **Field: EndAddress**: varint - Ending virtual address of the symbol within the mapping.
56+
- **Field: Name**: string - Name of the symbol.
57+
58+
## Universal Events Provider
59+
The universal events provider is named "Universal.Events". It's purpose is to provide events that contain a value and have a callstack. There are no stable event IDs, but there will be a set of stable names. Required fields as listed above in this document are used to assist tools in interpreting the events.
60+
61+
### Current Stable Event Names
62+
- cpu - Represents a CPU sample.
63+
- cswitch - Represents a machine-level context switch.
64+
65+
### Event Definition
66+
- **Field: Value**: varint - Value of the event.
67+
68+
### Example: cpu
69+
- Each cpu event represents a CPU sample.
70+
- The value represents the weight of the sample.
71+
- As an example if emitting one CPU sample per core per millisecond, emit an event with Value=1 per core every millisecond.
72+
73+
### Example: cswitch
74+
- Each cswitch event represents an OS-level context switch.
75+
- The thread ID provided in the set of required fields is the thread being switched in.
76+
- The CPU number provided in the set of required fields is the CPU involved.
77+
- The value represents the amount of time that that has elapsed since the thread last executed (switched out time). Times are based on the QPC frequency from the source containing the events.
78+
- The callstack associated with the event is the stack where the thread switched out (blocked).
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Text;
4+
using Address = System.UInt64;
5+
6+
#pragma warning disable 1591 // disable warnings on XML comments not being present
7+
8+
namespace Microsoft.Diagnostics.Tracing.Parsers
9+
{
10+
using Microsoft.Diagnostics.Tracing.Parsers.Universal.Events;
11+
12+
public sealed class UniversalEventsTraceEventParser: TraceEventParser
13+
{
14+
public static string ProviderName = "Universal.Events";
15+
public static Guid ProviderGuid = new Guid("bc5e5d63-9799-5873-33d9-fba8316cef71");
16+
public enum Keywords : long
17+
{
18+
None = 0x0,
19+
};
20+
21+
public UniversalEventsTraceEventParser(TraceEventSource source) : base(source) { }
22+
23+
public event Action<SampleTraceData> cpu
24+
{
25+
add
26+
{
27+
source.RegisterEventTemplate(new SampleTraceData(value, 1, (int)TraceEventTask.Default, "cpu", Guid.Empty, 0, "Default", ProviderGuid, ProviderName));
28+
}
29+
remove
30+
{
31+
source.UnregisterEventTemplate(value, 1, Guid.Empty);
32+
}
33+
}
34+
35+
public event Action<SampleTraceData> cswitch
36+
{
37+
add
38+
{
39+
source.RegisterEventTemplate(new SampleTraceData(value, 2, (int)TraceEventTask.Default, "cswitch", Guid.Empty, 0, "Default", ProviderGuid, ProviderName));
40+
}
41+
remove
42+
{
43+
source.UnregisterEventTemplate(value, 2, Guid.Empty);
44+
}
45+
}
46+
47+
#region private
48+
protected override string GetProviderName() { return ProviderName; }
49+
50+
static private volatile TraceEvent[] s_templates;
51+
52+
protected internal override void EnumerateTemplates(Func<string, string, EventFilterResponse> eventsToObserve, Action<TraceEvent> callback)
53+
{
54+
if (s_templates == null)
55+
{
56+
var templates = new TraceEvent[2];
57+
templates[0] = new SampleTraceData(null, 1, (int)TraceEventTask.Default, "cpu", Guid.Empty, 0, "Default", ProviderGuid, ProviderName);
58+
templates[1] = new SampleTraceData(null, 2, (int)TraceEventTask.Default, "cswitch", Guid.Empty, 0, "Default", ProviderGuid, ProviderName);
59+
s_templates = templates;
60+
}
61+
foreach (var template in s_templates)
62+
if (eventsToObserve == null || eventsToObserve(template.ProviderName, template.EventName) == EventFilterResponse.AcceptEvent)
63+
callback(template);
64+
}
65+
66+
#endregion
67+
}
68+
}
69+
70+
namespace Microsoft.Diagnostics.Tracing.Parsers.Universal.Events
71+
{
72+
public sealed class SampleTraceData : TraceEvent
73+
{
74+
public Address Value { get { return GetVarUIntAt(0); } }
75+
76+
#region Private
77+
internal SampleTraceData(Action<SampleTraceData> action, int eventID, int task, string taskName, Guid taskGuid, int opcode, string opcodeName, Guid providerGuid, string providerName)
78+
: base(eventID, task, taskName, taskGuid, opcode, opcodeName, providerGuid, providerName)
79+
{
80+
Action = action;
81+
}
82+
protected internal override void Dispatch()
83+
{
84+
Action(this);
85+
}
86+
87+
protected internal override Delegate Target
88+
{
89+
get { return Action; }
90+
set { Action = (Action<SampleTraceData>)value; }
91+
}
92+
public override StringBuilder ToXml(StringBuilder sb)
93+
{
94+
Prefix(sb);
95+
XmlAttrib(sb, "Value", Value);
96+
sb.Append("/>");
97+
return sb;
98+
}
99+
100+
public override string[] PayloadNames
101+
{
102+
get
103+
{
104+
if (payloadNames == null)
105+
payloadNames = new string[] { "Value" };
106+
return payloadNames;
107+
}
108+
}
109+
110+
public override object PayloadValue(int index)
111+
{
112+
switch (index)
113+
{
114+
case 0:
115+
return Value;
116+
default:
117+
Debug.Assert(false, "Bad field index");
118+
return null;
119+
}
120+
}
121+
122+
public static ulong GetKeywords() { return 0; }
123+
public static string GetProviderName() { return UniversalEventsTraceEventParser.ProviderName; }
124+
public static Guid GetProviderGuid() { return UniversalEventsTraceEventParser.ProviderGuid; }
125+
private event Action<SampleTraceData> Action;
126+
#endregion
127+
}
128+
}

0 commit comments

Comments
 (0)