Skip to content

Commit

Permalink
Add support for last branch recording (#1754)
Browse files Browse the repository at this point in the history
* Add support for last branch recording

Add /LbrSources and /LbrFilters command line options. When the former is
specified last branch recording is enabled from the specified sources
(comma-separated string of numeric source IDs or "PmcInterrupt").
/LbrFilters can optionally be specified to filter which branches are
recorded (comma-separated list of filters using same names as xperf).

No special parsing of these events yet, but this at least allows us to
record them.

* Fix filter flag

* Fix a potential NullReferenceException in TraceEvent:

* Add parser support

* Add some docs, minor changes

* Add Windows 10 OS check
  • Loading branch information
jakobbotsch authored Jan 17, 2023
1 parent 75d67fc commit 2c67a31
Show file tree
Hide file tree
Showing 5 changed files with 487 additions and 13 deletions.
99 changes: 98 additions & 1 deletion src/PerfView/CommandLineArgs.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Session;
using System;
using System.Collections.Generic;
using System.Globalization;
using Utilities;

namespace PerfView
Expand Down Expand Up @@ -108,6 +110,8 @@ public static string GetHelpString(int maxLineWidth)

// Start options.
public bool StackCompression = true; // Use compresses stacks when collecting traces.
public CommandLineLbrSources LastBranchRecordingSources;
public CommandLineLbrFilters LastBranchRecordingFilters;
public int BufferSizeMB = 256;
public int CircularMB;
public bool InMemoryCircularBuffer; // Uses EVENT_TRACE_BUFFERING_MODE for an in-memory circular buffer
Expand Down Expand Up @@ -274,6 +278,13 @@ private void SetupCommandLine(CommandLineParser parser)
parser.DefineOptionalQualifier("CircularMB", ref CircularMB, "Do Circular logging with a file size in MB. Zero means non-circular.");
parser.DefineOptionalQualifier("InMemoryCircularBuffer", ref InMemoryCircularBuffer, "Keeps the circular buffer in memory until the session is stopped.");
parser.DefineOptionalQualifier("StackCompression", ref StackCompression, "Use stack compression (only on Win 8+) to make collected file smaller.");
parser.DefineOptionalQualifier("LbrSources", ref LastBranchRecordingSources,
$"Turn on LBR sampling from these sources (comma-separated numeric hex values with 0x prefix or 'PmcInterrupt'). At most {TraceEventSession.GetMaxLastBranchRecordingSources()} sources are supported.");
parser.DefineOptionalQualifier(
"LbrFilters",
ref LastBranchRecordingFilters,
"Filters to use with LBR sampling (comma-separated). Unspecified means no filtering. " +
"Valid filters are: StackMode, ConditionalBranches, NearRelativeCalls, NearIndirectCalls, NearReturns, NearIndirectJumps, NearRelativeJumps, FarBranches, Kernel, User");
parser.DefineOptionalQualifier("MaxCollectSec", ref MaxCollectSec,
"Turn off collection (and kill the program if perfView started it) after this many seconds. Zero means no timeout.");
parser.DefineOptionalQualifier("StopOnPerfCounter", ref StopOnPerfCounter,
Expand Down Expand Up @@ -679,5 +690,91 @@ private void SetupCommandLine(CommandLineParser parser)
parser.DefineOptionalParameter("DataFile", ref DataFile, "ETL or ETLX file containing profile data.");
}
#endregion
};
}

public class CommandLineLbrSources
{
public string Text { get; private set; }
public uint[] Parsed { get; private set; }

public static CommandLineLbrSources Parse(string text)
{
var sources = new List<uint>();
foreach (string entry in text.Split(','))
{
bool success;
uint source = 0;
string trimmed = entry.Trim();
if (trimmed.StartsWith("0x"))
{
success = uint.TryParse(trimmed.Substring(2), NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out source);
}
else
{
success = Enum.TryParse(trimmed, true, out LbrSource enumSource);
source = (uint)enumSource;
}

if (!success)
throw new CommandLineParserException($"/LbrSources: Could not parse '{entry}'");

sources.Add(source);
}

int maxSources = TraceEventSession.GetMaxLastBranchRecordingSources();
if (sources.Count > maxSources)
throw new CommandLineParserException($"/LbrSources: At most {maxSources} sources can be specified");

return new CommandLineLbrSources { Text = text, Parsed = sources.ToArray() };
}

public override string ToString() => Text;
}

public class CommandLineLbrFilters
{
public string Text { get; private set; }
public LbrFilterFlags Parsed { get; private set; }

public static CommandLineLbrFilters Parse(string text)
{
LbrFilterFlags flags = 0;
foreach (string entry in text.Split(','))
{
// Parse using same names as xperf.
switch (entry.ToLowerInvariant())
{
case "stackmode": flags |= LbrFilterFlags.CallstackEnable; break;
case "conditionalbranches": flags |= LbrFilterFlags.FilterJcc; break;
case "nearrelativecalls": flags |= LbrFilterFlags.FilterNearRelCall; break;
case "nearindirectcalls": flags |= LbrFilterFlags.FilterNearIndCall; break;
case "nearreturns": flags |= LbrFilterFlags.FilterNearRet; break;
case "nearindirectjumps": flags |= LbrFilterFlags.FilterNearIndJmp; break;
case "nearrelativejumps": flags |= LbrFilterFlags.FilterNearRelJmp; break;
case "farbranches": flags |= LbrFilterFlags.FilterFarBranch; break;
case "kernel": flags |= LbrFilterFlags.FilterKernel; break;
case "user": flags |= LbrFilterFlags.FilterUser; break;
default: throw new CommandLineParserException($"Could not parse '{entry}' as an LBR filter");
}
}

const LbrFilterFlags callstackAllowedFlags =
LbrFilterFlags.CallstackEnable | LbrFilterFlags.FilterKernel | LbrFilterFlags.FilterUser;
if ((flags & LbrFilterFlags.CallstackEnable) != 0 &&
(flags & ~callstackAllowedFlags) != 0)
{
throw new CommandLineParserException("/LbrFilters: Only 'Kernel' or 'User' can be specified alongside 'StackMode'");
}

const LbrFilterFlags kernelUser = LbrFilterFlags.FilterKernel | LbrFilterFlags.FilterUser;
if ((flags & kernelUser) == kernelUser)
{
throw new CommandLineParserException("/LbrFilters: Only one of 'Kernel' or 'User' can be specified");
}

return new CommandLineLbrFilters { Text = text, Parsed = flags };
}

public override string ToString() => Text;
}
}
12 changes: 12 additions & 0 deletions src/PerfView/CommandProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ public void Start(CommandLineArgs parsedArgs)

kernelModeSession.BufferSizeMB = parsedArgs.BufferSizeMB;
kernelModeSession.StackCompression = parsedArgs.StackCompression;
kernelModeSession.LastBranchRecordingProfileSources = parsedArgs.LastBranchRecordingSources?.Parsed;
kernelModeSession.LastBranchRecordingFilters = parsedArgs.LastBranchRecordingFilters?.Parsed ?? LbrFilterFlags.None;
kernelModeSession.CpuSampleIntervalMSec = parsedArgs.CpuSampleMSec;
if (parsedArgs.CircularMB != 0)
{
Expand Down Expand Up @@ -2797,6 +2799,16 @@ public static string ParsedArgsAsString(string command, CommandLineArgs parsedAr
cmdLineArgs += " /StackCompression";
}

if (parsedArgs.LastBranchRecordingSources != null)
{
cmdLineArgs += " " + Command.Quote("/LbrSources:" + parsedArgs.LastBranchRecordingSources.Text);
}

if (parsedArgs.LastBranchRecordingFilters != null)
{
cmdLineArgs += " " + Command.Quote("/LbrFilters:" + parsedArgs.LastBranchRecordingFilters.Text);
}

if (parsedArgs.CircularMB != 0)
{
cmdLineArgs += " /CircularMB:" + parsedArgs.CircularMB;
Expand Down
Loading

0 comments on commit 2c67a31

Please sign in to comment.