Skip to content

Profiler: Analysis

lordmilko edited this page Dec 22, 2023 · 32 revisions

Contents

DebugTools provides a number of cmdlets to analyze the data collected in a profiler trace. These cmdlets break down into the following categories:

  • Methods
  • Frames
  • Exceptions

By default, all analysis cmdlets will operate against the implicit profiler session if no session is specified. Sessions can be retrieved using the Get-DbgProfiler cmdlet

Get-DbgProfiler -Id 1001 | Get-DbgProfilerMethod

For more information on the implicit profiler session, please see Session Management.

Unfortunately, as some cmdlet names are long to describe what they do, it can be a bit confusing trying to remember and type out the exact name of a cmdlet. It is recommended to use PowerShell auto-completion when typing out some of the longer cmdlet names in DebugTools. i.e. you can simply do

Show-Db <tab>

to auto-complete Show-DbgProfilerStackTrace

Methods

Once a target process has been launched containing the DebugTools Profiler, regardless of whether or not you are actively recording frame transitions, all methods JITted will be reported back to the profiler controller process. All frames currently known to the profiler controller can be listed using the Get-DbgProfilerMethod cmdlet

C:\> Get-DbgProfilerMethod

FunctionID     ModuleName                       TypeName                                           MethodName
----------     ----------                       --------                                           ----------
0x7FFCBEFADF60 System.Management.Automation.dll <GetDefaultAvailableModuleFiles>d__2               <>m__Finally2
0x7FFCBEE10530 System.Management.Automation.dll System.Management.Automation.PowerShell            CoreInvoke
...

The Get-DbgProfilerMethod provides a number of parameters that can be used to search for target methods. The simplest way to search using Get-DbgProfilerMethod is to specify a wildcard expression specifying part of the fully qualified method name (including namespace, type and method name).

C:\> Get-DbgProfilerMethod *childitem*

Get-DbgProfilerMethod provides a number of parameters for performing more specific types of filtering: you can filter based on the -ModuleName, -TypeName, -MethodName and even -Exclude certain name patterns that you're not interested in.

By default, Get-DbgProfiler will search all methods that have been invoked since the target process was started. To search only for methods that were invoked in the last trace, specify the -LastTrace parameter.

C:\> Get-DbgProfilerMethod -TypeName *child* -LastTrace

Searching for methods from the last trace can sometimes be a better idea than trying to search for frames, or even drawing a visual stack trace. Once you've identified some potential methods you may be interested, you can jump to cmdlets like Show-DbgProfilerStackTrace which can sometimes take some time to run depending on the number of captured frames that need to be filtered.

To view the parameters available on Get-DbgProfilerMethod do Get-Help Get-DbgProfilerMethod

Frames

When a trace is stopped, the data collected in that trace is cached on the profiler session. The root frames of this trace can be retrieved using the Get-DbgProfilerLastStackTrace cmdlet

C:\> Get-DbgProfilerLastStackTrace

ThreadId ThreadName                Parent Children
-------- ----------                ------ --------
19516    Pipeline Execution Thread        {System.Management.Automation.Runspaces.Pipeline...
...

The data contained returned from this cmdlet can then potentially be cached and then passed to other cmdlets that analyze frames. If no frame is specified to a frame analysis cmdlet, an analysis will automatically be performed starting from the root frame of each thread in the last recorded trace. Due to the amount of memory a trace can potentially consume, if you wish to hold onto the output of a previous trace for potentially comparing two traces, you should keep an eye on your process/system memory, as this data will not be garbage collected as long as you are holding a reference to it.

Frames can be searched for using the Get-DbgProfilerStackFrame cmdlet. The Get-DbgProfilerStackFrame cmdlet supports specifying a wide array of parameters for narrowing down its search results.

# List all frames whose fully qualified name includes "childitem"
C:\> Get-DbgProfilerStackFrame *childitem*

Display                                               MethodInfo
-------                                               ----------
GetChildItemCommand.get_Force                         Microsoft.PowerShell.Commands.GetChildItemCommand.get_Force
GetChildItemCommand.get_Include                       Microsoft.PowerShell.Commands.GetChildItemCommand.get_Include
GetChildItemCommand.get_Exclude                       Microsoft.PowerShell.Commands.GetChildItemCommand.get_Exclude
...

To get a more visual feel for a stack trace, the Show-DbgProfilerStackTrace can be used. Show-DbgProfilerStackTrace supports all of the same filtering parameters that Get-DbgProfilerStackFrame supports.

Filter cmdlets provide many parameters which can be used to drill down to just the stacks containing frames you are interested in. Except where noted, all parameters listed allow specifying an array of potential values to match against. All frames supported by a given cmdlet can be shown within PowerShell itself by doing Get-Help <cmdlet>

Get-Help Show-DbgProfilerStackTrace

There are three types of parameters that can be specified

  • Inclusion parameters indicate whether a given frame should be included/excluded based on its full method name (namespace + type + method name)
  • Value parameters filter for parameters/return values that match a specific value/expression
  • Special parameters perform special functions for certain cmdlets

Inclusion

Inclusion parameters are treated as a logical AND. As such, the following invocation

C:\> Show-DbgProfilerStackTrace *process* -Unmanaged

shows the stack traces of all unmanaged frames whose full method name contains "process"

Parameter Description
-Include Include frames whose combined namespace, type and method name match the specified wildcard. Can be positionally specified at index 0
-Exclude Exclude frames whose combined namespace, type and method name match the specified wildcard.
-CalledFrom Include frames whose ancestor's combined namespace, type and method name match the specified wildcard.
-Unmanaged Include frames that were reported to be transitions to or from unmanaged code. This parameter is a switch (-Unmanaged)
-MethodModuleName Include frames whose full module path (i.e. DLL/EXE filename, excluding path) matches the specified wildcard
-MethodTypeName Include frames whose type name (including namespace) matches the specified wildcard. This parameter can be used in lieu of -Include to specifically target an expression against the type name
-MethodName Include frames whose method name matches the specified wildcard. This parameter can be used in lieu of -Include to specifically target an expression against the method name without looking at the type/namespace
-ParentMethodModuleName Similar to -MethodModuleName but includes frames whose parent frame's method's module matches the specified wildcard expression
-ParentMethodTypeName Similar to -MethodTypeName but includes frames whose parent frame's method's type (including namespace) matches the specified wildcard expression
-ParentMethodName Similar to -MethodName but includes frames whose parent frame's method's name matches the specified wildcard expression without looking at the type/namespace

Value

Value parameters are treated as a logical OR. As soon as DebugTools matches any specified value, the frame will be included. It is not currently possible to filter value parameters using logical AND. If you would like to see this feature, please open an issue!

Parameter Description
-VoidValue Includes frames containing a parameter/return value (or object member of said value) of type void. This parameter is a switch (-VoidValue)
-BoolValue Includes frames containing a parameter/return value (or object member of said value) with the specified bool value
-CharValue Includes frames containing a parameter/return value (or object member of said value) with the specified char value
-SByteValue Includes frames containing a parameter/return value (or object member of said value) with the specified sbyte value
-ByteValue Includes frames containing a parameter/return value (or object member of said value) with the specified byte value
-Int16Value Includes frames containing a parameter/return value (or object member of said value) with the specified short value
-UInt16Value Includes frames containing a parameter/return value (or object member of said value) with the specified ushort value
-Int32Value Includes frames containing a parameter/return value (or object member of said value) with the specified int value
-UInt32Value Includes frames containing a parameter/return value (or object member of said value) with the specified uint value
-Int64Value Includes frames containing a parameter/return value (or object member of said value) with the specified long value
-UInt64Value Includes frames containing a parameter/return value (or object member of said value) with the specified ulong value
-FloatValue Includes frames containing a parameter/return value (or object member of said value) with the specified float value
-DoubleValue Includes frames containing a parameter/return value (or object member of said value) with the specified double value
-IntPtrValue Includes frames containing a parameter/return value (or object member of said value) with the specified IntPtr value
-UIntPtrValue Includes frames containing a parameter/return value (or object member of said value) with the specified UIntPtr value
-StringValue Includes frames containing a parameter/return value (or object member of said value) that matches the specified wildcard expression
-ClassTypeName Includes frames containing a parameter/return value (or object member of said value) that is a class or struct whose namespace + type name matches the specified wildcard expression. This parameter matches both classes and structs, and is also aliased as StructTypeName.

Special

Common

Parameter Description
-Unique Limits the output to only include the first frame targeting a given method, as identified by its internal FunctionID. This parameter is evaluated after all other filters have executed. This parameter is a switch (-Unique)

Get-DbgProfilerStackFrame

Parameter Description
-Sequence Returns the frame with the specified sequence

Show-DbgProfilerStackTrace

Parameter Description
-ExcludeNamespace Omit the namespace on all methods/types emitted. This parameter is a switch (-ExcludeNamespace)
-IncludeSequence Include the unique method sequence on all methods emitted. This parameter is a switch.
-IncludeModule Include the module name on all methods emitted. This parameter is a switch.
-Simple Shorthand for specifying -Unique and -ExcludeNamespace
-Depth Specifies how many frames deep the cmdlet should display frames when invoked with no filters. When filters are specified, the entirety of the stack trace will always be shown
-Unlimited When no filters have been specified, specifies that -Depth should be ignored and all frames should displayed. Note that when displaying very very large stack traces, emitting extreme amounts of fancily formatted text to the PowerShell console can cause it to glitch out. This parameter is a switch

Watch-DbgProfilerStack

Parameter Description
-DisableProgress Disables the progress bar that informs you how to disable tracing (via Ctrl+C) to prevent it from overlapping the output that is being emitted from the cmdlet. This parameter is a switch.

Unique

Cmdlets capable of filtering frames (including Get-DbgProfilerStackFrame, Watch-DbgProfilerStack Show-DbgProfilerStackTrace) allow specifying that only -Unique frames should be displayed. However, "uniqueness" can mean different things depending on your perspective.

To illustrate this, consider the following stack trace, with sequence numbers listed in brackets after each frame

first (1)
├─second (2)
│ └─third (3)
│   ├─second (4)
│   │ └─fourth (5)
│   └─second (6)
│     └─fourth (7)
└─second (8)

In this trace, the method second occurs in four different frames

  • Twice, being called from first
  • Twice, being called from third which itself called from a separate invocation of second

There are several different strategies that can potentially be used for determining "uniqueness" of a frame

Strategy Pro Con Example
Include the first occurrence of a method executed globally Exactly one occurrence of second will occur in the trace Methods such as fourth which only occur on a subsequent, or inner invocation, will be lost first(1)
└─second(2)
Analyze the entire hierarchy a frame and all its children for determining uniqueness
Only compare frames at the same level under the same parent
Make child frames that would have been lost children of the first unique parent we are including All frames are included in the output Frames will be listed under a parent frame that isn't actually their parent

Currently, DebugTools uses Strategy 1, taking the first occurrence of a method executed globally. Depending on how DebugTools' filter cmdlets are invoked, different behavior can be exhibited.

There are two phases of analysis that occur when DebugTools attempts to filter a stack trace

  • Frame Analysis
  • Tree Reconstruction

When only the -Unique parameter is specified,

Show-DbgProfilerStackTrace -Unique

DebugTools skips the entire Frame Analysis stage entirely, and proceeds straight to the Tree Reconstruction Phase, where the first frame encountered that uses each method will be included in the new stack trace that is built.

If any additional parameters are specified, DebugTools will search for all frames that match the specified predicate, and then construct a new tree with all matched frames as leaf nodes.

Show-DbgProfilerStackTrace -Unique *childitem*

When determining uniqueness, the uniqueness engine will prefer the parents of any matches frames over standalone frames to ensure a given frame is not listed twice. For example, consider the following scenario filtering for unique methods that are like *movenext*

30600
└─System.Diagnostics.Tracing.EventListener.DisposeOnShutdown (1)
  ├─Enumerator.MoveNext (4)
  └─Enumerator.MoveNext (36)
    └─Enumerator.MoveNextRare (37)

Methods Enumerator.MoveNext and Enumerator.MoveNextRare are found to match the predicate. The first invocation of each is selected. But since a separate invocation of Enumerator.MoveNext calls Enumerator.MoveNextRare, we could inadvertently list Enumerator.MoveNext twice, which is not what we want! As such, DebugTools will analyze the parents of MoveNext (4) and MoveNextRare (37), see that MoveNext (36) will be included, and use that instead of MoveNext (4) giving us the final output of

30600
└─System.Diagnostics.Tracing.EventListener.DisposeOnShutdown (1)
  └─Enumerator.MoveNext (36)
    └─Enumerator.MoveNextRare (37)

If two unique methods B and C are each executed from a different invocation of function A however, function A will be listed twice in the output

# Even though -Unique methods were listed, A is listed twice due to B and C being called from
# separate invocation of A

1000
├─A
│ └─B
└─A
  └─C

Exceptions

A list of all exceptions captured in the last trace can be displayed via the Get-DbgProfilerException cmdlet

C:\> Get-DbgProfilerException

ThreadId Type                             Sequence Status ThrownFrame                    HandledFrame
-------- ----                             -------- ------ -----------                    ------------
3640     System.IO.FileNotFoundException  1        Caught ExecutionContext.LoadAssembly  ExecutionContext.LoadAssembly
...

Exceptions output can be limited to those that occurred on a specific -ThreadId or those that match a specific -Type

C:\> Get-DbgProfilerException *invalidoperation*

Import/Export

Traces can be saved to a file for future viewing using the Export-DbgProfilerStackTrace cmdlet

Note: DebugTools does not currently support exporting exception details, only frame/method details

C:\> Export-DbgProfilerStackTrace C:\

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        31/03/2023   1:49 PM         658302 StackTrace_42004_devenv_2023-03-31_1349h.gz

Export-DbgProfilerStackTrace will automatically attempt to guess whether the path you've specified refers to a file or a directory. As stack traces can very quickly get quite large, by default Export-DbgProfilerStackTrace will GZip the file up as it is written. If you wish to inspect or modify the output file, specify -Raw to emit the trace as an XML file instead.

C:\> Export-DbgProfilerStackTrace C:\export -Raw

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        31/03/2023   1:58 PM       10523613 export.xml

Note that files emitted as raw XML can be over 15x the size of an equivalent GZip file.

To export a specific stack trace within a larger trace that has been collected, the Get-DbgProfilerStackFrame cmdlet can be used. By default, Get-DbgProfilerStackFrame will emit all frames that match the specified filters. This is in contrast to Show-DbgProfilerStackTrace which performs the same filtering but then constructs a virtual tree containing just the frames that will need to be displayed. Get-DbgProfilerStackFrame can be used to gain access to these virtual trees via the use of the -Roots parameter

# Get all individual frames that contained the string value "1+1"
C:\> Get-DbgProfilerStackFrame -StringValue "1+1"

# Get the root frame(s) of a virtual tree that descends down to all nodes containing
# the string value "1+1". Multiple trees may be emitted when matches occurred across
# multiple threads
C:\> Get-DbgProfilerStackFrame -StringValue "1+1" -Roots
# Export the virtual tree(s) of all nodes containing the string value "1+1"
# to C:\plus.gz
C:\> Get-DbgProfilerStackFrame -StringValue "1+1" -Roots | Export-DbgProfilerStackTrace C:\plus

To import a previously exported stack trace, use the Import-DbgProfilerStackTrace cmdlet

C:\> Import-DbgProfilerStackTrace C:\export.xml

Import-DbgProfilerStackTrace will automatically detect whether the specified file is an XML or GZip file. Files must end in either *.xml or *.gz or Import-DbgProfilerStackTrace will not be able to read them.

Imported stack traces will generate a pseudo ProfilerSession that is stored in the current session state, similar to profiler sessions normally created via Start-DbgProfiler. You can then perform analytics by executing all normal cmdlets that operate on sessions/frames as described in this article.