diff --git a/.spelling b/.spelling
index 53c82108302..449aa4d9dc3 100644
--- a/.spelling
+++ b/.spelling
@@ -1090,3 +1090,20 @@ interactivetesting
allowprerelease
prereleases
url
+ - tools/performance/README.md
+analyze
+wpa
+wpaProfile
+PerfView
+etl
+perfview
+wpr
+wprui.exe
+perfview.exe
+GC.Regions.xml
+JIT.Regions.xml
+PowerShell.Regions.xml
+PowerShell.stacktags
+PowerShell.wpaProfile
+PowerShell.wprp
+Invoke-PerfviewPS
diff --git a/tools/performance/GC.Regions.xml b/tools/performance/GC.Regions.xml
new file mode 100644
index 00000000000..b89be6ffd7c
--- /dev/null
+++ b/tools/performance/GC.Regions.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/performance/Invoke-PerfviewPS.ps1 b/tools/performance/Invoke-PerfviewPS.ps1
new file mode 100644
index 00000000000..77a33f3a754
--- /dev/null
+++ b/tools/performance/Invoke-PerfviewPS.ps1
@@ -0,0 +1,55 @@
+#requires -RunAsAdministrator
+
+param(
+ $ETLFileName = '.\PerfViewData.etl',
+
+ [Parameter(Mandatory)]
+ [scriptblock]
+ $ScriptBlock,
+
+ $LogFileName = '.\perfview.log',
+
+ $PowerShellPath = $(Get-Command pwsh.exe).Source)
+
+$EncodedScriptBlock = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($ScriptBlock.ToString()))
+$perfViewArgs = @(
+ '/AcceptEula'
+ '/ThreadTime'
+ "/LogFile=$LogFileName"
+ "/DataFile:$ETLFileName"
+ '/noRundown'
+ '/Merge'
+ '/Zip:False'
+# GCSampledObjectAllocationHigh is sometimes useful, but quite expensive so not included by default
+# '/ClrEvents=default+GCSampledObjectAllocationHigh'
+ '/Providers:*Microsoft-PowerShell-Runspaces,*Microsoft-PowerShell-CommandDiscovery,*Microsoft-PowerShell-Parser,*Microsoft.Windows.PowerShell'
+ 'run'
+ """$PowerShellPath"""
+ '-NoProfile'
+ '-EncodedCommand'
+ $EncodedScriptBlock
+)
+
+$process = Start-Process -FilePath (Get-Command PerfView.exe).Source -ArgumentList $perfViewArgs -PassThru
+
+$rs = [runspacefactory]::CreateRunspace($host)
+$rs.Open()
+$ps = [powershell]::Create()
+$ps.Runspace = $rs
+
+$null = $ps.AddCommand("Get-Content").
+ AddArgument($LogFileName).
+ AddParameter("Wait").
+ AddParameter("Tail", 0)
+$null = $ps.AddCommand("Out-Host")
+
+# If log file doesn't exist yet, wait a little bit so Get-Content doesn't fail
+while (!(Test-Path $LogFileName))
+{
+ Start-Sleep -Seconds 1
+}
+
+$null = $ps.BeginInvoke()
+$process.WaitForExit()
+$ps.Stop()
+
diff --git a/tools/performance/JIT.Regions.xml b/tools/performance/JIT.Regions.xml
new file mode 100644
index 00000000000..a6b4f072da4
--- /dev/null
+++ b/tools/performance/JIT.Regions.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/performance/PowerShell.Regions.xml b/tools/performance/PowerShell.Regions.xml
new file mode 100644
index 00000000000..0a070242487
--- /dev/null
+++ b/tools/performance/PowerShell.Regions.xml
@@ -0,0 +1,318 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/performance/PowerShell.stacktags b/tools/performance/PowerShell.stacktags
new file mode 100644
index 00000000000..a4bbecfc949
--- /dev/null
+++ b/tools/performance/PowerShell.stacktags
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/performance/PowerShell.wpaProfile b/tools/performance/PowerShell.wpaProfile
new file mode 100644
index 00000000000..46af43c0e0e
--- /dev/null
+++ b/tools/performance/PowerShell.wpaProfile
@@ -0,0 +1,272 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/performance/PowerShell.wprp b/tools/performance/PowerShell.wprp
new file mode 100644
index 00000000000..3c36c55ce14
--- /dev/null
+++ b/tools/performance/PowerShell.wprp
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/performance/README.md b/tools/performance/README.md
new file mode 100644
index 00000000000..c598fca5f28
--- /dev/null
+++ b/tools/performance/README.md
@@ -0,0 +1,49 @@
+
+# PowerShell Performance Analysis
+
+This directory contains useful scripts and related files for analyzing PowerShell performance.
+
+If you use the [Windows Performance Toolkit](https://developer.microsoft.com/en-US/windows/downloads/windows-10-sdk), you can use the following to collect data and analyze a trace.
+
+```PowerShell
+$PowerShellGitRepo = "D:\PowerShell"
+wpr -start $PowerShellGitRepo\tools\performance\PowerShell.wprp -filemode
+pwsh.exe -NoProfile -Command "echo 1"
+wpr -stop PowerShellTrace.etl
+wpa -i wpa://.\PowerShellTrace.etl?profile=$PowerShellGitRepo\tools\performance\PowerShell.wpaProfile
+```
+
+When wpa opens, under System Activity, you'll find a section "Regions of Interest".
+With the above wpaProfile, you should see a bunch of PowerShell related regions as well as GC and JIT activity.
+
+If you use [PerfView](https://github.com/microsoft/perfview), you can collect a trace by running
+
+```PowerShell
+Invoke-PerfviewPS.ps1 -scenario { echo 1 }
+perfview .\perfviewdata.etl
+```
+
+The etl files collected with perfview or wpr should contain roughly the same events.
+
+Also note that you can collect the trace with one tool and analyze with the other.
+
+## Symbols
+
+PDB files are not published for PowerShell Core,
+so the current recommendation is to build PowerShell yourself passing `-CrossGen` to `Start-Build`.
+
+If profiling Windows PowerShell, symbols are generated from GAC.
+wprui.exe and perfview.exe will both generate the PDB files needed.
+
+## Files
+
+| File | Description |
+| ---- | ----------- |
+| GC.Regions.xml | WPA regions of interest for GC |
+| JIT.Regions.xml | WPA regions of interest for JIT |
+| PowerShell.Regions.xml | WPA regions of interest for PowerShell |
+| PowerShell.stacktags | PowerShell stack tags |
+| PowerShell.wpaProfile | WPA profile to load regions of interest and stack tags |
+| PowerShell.wprp | WPR profile to enable CLR and PowerShell ETW events |
+| Invoke-PerfviewPS.ps1 | Script to run perfview and with PowerShell ETW events enabled |
+