########################################################################################################
# Script: PowerDbgScriptHighCPU
#
# Parameters: [string] [$timeLimit]
# Minimum time limit for threads consuming high CPU.
# This argument is optional. If you don't provide it the script will use 10 seconds.
# The format must be "hh:mm:ss"
# The threads to be analyzed will be those consuming CPU time >= $timeLimit.
#
# Purpose: Show the threads consuming high CPU and details about them. Works for managed and native codes.
# It warns user if Kernel time is higher than User time.
# For managed code, make sure you loaded SOS.DLL extension before using this script.
#
# Attention! This is for 32 bits.
#
# Usage: Before running the script:
# a) Open your workspace to load the symbols.
# b) .wtitle PowerDbg
# c) .reload
#
# Attention! If you don't load the symbols before running the script you may face timing issues.
#
# Changes History:
#
# Mike McIntyre
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
param(
[string] $timeLimit = "00:00:10"
)
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
write-Host "Verifying User Mode time and Kernel time..." -foreground Green -background Black
# If you want you can create code to load SOS.DLL extension if necessary.
# Here I'm assuming this extension was already loaded.
Send-PowerDbgCommand("vertarget")
# Extracts output from previous command.
Parse-PowerDbgVERTARGET
# Converts CSV file to Hash Table.
$output = @{}
$output = Convert-PowerDbgCSVtoHashTable
# The flag will be true if Kernel time is greater than User time.
$isKernelTimeHigher = $([System.DateTime] $output["Kernel time:"]) -gt $([System.DateTime] $output["User time:"])
write-Host "Done!" -foreground Green -background Black
$hasASPXApplication = $false
$hasManagedCode = $false # If the flag above is true this flag must be true too. ;-)
write-Host "Verifying if this process has managed code and if it is an ASP.NET application..." -foreground Green -background Black
# Gets module names.
Send-PowerDbgCommand "lm1m"
# Parses output.
Parse-PowerDbgLM1M
# Converts CSV file to Hash Table.
$output = @{}
$output = Convert-PowerDbgCSVtoHashTable
# First thing, find aspnet_isapi.dll
# If we don't find it, we need to verify is mscorwks or mscorsvr is in the process address space.
if($output["aspnet_isapi"] -ne $null)
{
$hasASPXApplication = $hasManagedCode = $true
}
elseif(($output["mscorsvr"] -ne $null) -or ($output["mscorwks"] -ne $null))
{
$hasManagedCode = $true
$hasASPXApplication = $false
}
write-Host "Finding out what each thread is doing..." -foreground Green -background Black
# This is the kernel of this script.
$threadsWorking = Classify-PowerDbgThreads
write-Host "Done!" -foreground Green -background Black
write-Host "Extracting CPU time for each thread..." -foreground Green -background Black
# Gets User mode time.
Send-PowerDbgCommand "!runaway 1"
$hasCommandSucceeded = Has-PowerDbgCommandSucceeded
if($false -eq $hasCommandSucceeded)
{
throw "This dump has no threads information!"
}
# Parses the output. The results are in arrayFromRUNAWAY
$arrayFromRUNAWAY = Convert-PowerDbgRUNAWAYtoArray
# If all threads matches our time limit, it means the limit is the last element of the array.
# In other words, all threads should be considered.
[System.Int32] $indexLimit = (($arrayFromRUNAWAY.count / 2) - 1)
# This flag indicates if, at least, one thread is running for long time is actually consuming high CPU.
$isThreadDoingWork = $false
$currentThreadNumer = 0
# Gets all threads that have CPU time equal or higher than our threshold.
for([System.Int32] $i = 0; $i -lt ($arrayFromRUNAWAY.count / 2); $i++)
{
# Get the thread number.
[System.Int32] $currentThreadNumber = $arrayFromRUNAWAY[$i, 0]
# Checks if thread CPU time is less than our threshold and if the thread is actually doing work.
if($([System.DateTime] $arrayFromRUNAWAY[$i, 1]) -lt $([System.DateTime] $timeLimit))
{
# The limit is from the beginning of the array until the previous element.
# If $indexLimit is -1 it means there are no threads that match our threshold value.
# In other words, we are using a threshold value that is too high.
$indexLimit = $i - 1;
break
}
# We need to have at least one thread doing work.
# For "doing work" I mean:
# 1- Doing I/O.
# 2- Unknwon symbols.
if(($threadsWorking[$currentThreadNumber] -eq $global:g_unknownSymbol) -or ($threadsWorking[$currentThreadNumber] -eq $global:g_doingIO ))
{
$isThreadDoingWork = $true
}
}
# This flag is true if there are no threads that are running for $timeLimit time or more.
# Using a flag is easier to change the code later and avoid problems if we change $indeLimit by mistake.
$hasNoThreadsMatchingLimit = ($indexLimit -eq -1)
write-Host "Done!" -foreground Green -background Black
# Clear Windbg screen.
Send-PowerDbgCommand ".cls"
write-Host "`n`nSUMMARY`n" -foreground Green -background Black
# No threads that match our threshold or the threads that match our threshold are not doing work.
if($hasNoThreadsMatchingLimit -or ($isThreadDoingWork -eq $false) )
{
write-Host "There are no threads consuming CPU time for $timeLimit or more." -foreground Green -background Black
Send-PowerDbgComment "There are no threads consuming CPU time for $timeLimit or more."
}
else
{
write-Host "The following threads either are or may be consuming high CPU:`n" -foreground Green -background Black
Send-PowerDbgComment "The following threads either are or may be consuming high CPU:"
# Prepare header according to CPU time.
write-Host "Thread Number`tUser Time`t`tActivity" -foreground Green -background Black
# Prepare summary for user and detailed information in Windbg screen.
for([System.Int32] $i = 0; $i -le $indexLimit; $i++)
{
# Get the thread number.
[System.Int32] $currentThreadNumber = $arrayFromRUNAWAY[$i, 0]
# We need to have at least one thread doing work.
# For "doing work" I mean:
# 1- Doing I/O.
# 2- Unknwon symbols.
if($threadsWorking[$currentThreadNumber] -eq $global:g_unknownSymbol)
{
# Prepare DML using SOS commands if necessary.
if($hasManagedCode)
{
Send-PowerDbgDML "Thread $currentThreadNumber is doing unknown work and may be running managed code" "~$currentThreadNumber s;kpn 1000;!clrstack;!dso"
}
else
{
Send-PowerDbgDML "Thread $currentThreadNumber is doing unknown work running native code" "~$currentThreadNumber kM 1000"
}
write-Host " " $currentThreadNumber "`t`t" $arrayFromRUNAWAY[$i, 1] "`t`t" $global:g_whatThreadIsDoing[$threadsWorking[$currentThreadNumber]] -foreground Green -background Black
}
elseif($threadsWorking[$currentThreadNumber] -eq $global:g_doingIO)
{
# Prepare DML using SOS commands if necessary.
if($hasManagedCode)
{
Send-PowerDbgDML "Thread $currentThreadNumber is doing I/O and may be running managed code" "~$currentThreadNumber s;kpn 1000;!clrstack;!dso"
}
else
{
Send-PowerDbgDML "Thread $currentThreadNumber is doing I/O work running native code" "~$currentThreadNumber kM 1000"
}
write-Host " " $currentThreadNumber "`t`t" $arrayFromRUNAWAY[$i, 1] "`t`t" $global:g_whatThreadIsDoing[$threadsWorking[$currentThreadNumber]] -foreground Green -background Black
}
}
# If this is an ASP.NET application the user may want to see the ASP.NET pages to know if they are consuming
# high CPU.
if($hasASPXApplication)
{
Send-PowerDbgDML "ASP.NET pages" "!ASPXPages"
}
# Give the user to browse the modules. After locating a specific module based on a thread call stack, the
# user may want to know more about the module.
Send-PowerDbgDML "Modules" "lmD"
}
# Alert user if process is using more Kernel time than User time.
if($isKernelTimeHigher)
{
write-Host "`nAttention! The application is consuming more Kernel time than User time." -foreground Green -background Black
write-Host "This should not be considered normal for a user mode process!`n" -foreground Green -background Black
}
write-Host "`nFor more technical details see the WinDbg window." -foreground Green -background Black
# Notifies user the script finished the execution.
Send-PowerDbgComment "PowerDbgScriptHighCPU was executed. See the PowerShell window for more information."