PowerShell script for Resource Utilization Logging on XenApp and more
The Goal:
In our daily tasks we have to deal with many application performance issues and with no granular reporting on resource utilization it is difficult to catch potential causes. This has set the goal for this script, which is a simple but yet effective way to monitor server resource utilization per user (something that I was not able to find across Internet)
What it can do:
Script will monitor the following parameters :
- Memory per user session
- CPU utilization per user session
- Total memory utilization
- Total CPU utilization
All the above is recorded in a CSV file including the date and time of the sampling plus username and session ID. File is named after the server host name, and later is renamed with date and time. After the log file reaches a predefined size - the script will compress it and will move it in to folder Archives.
Data collection:
Well I have taken a bit different approach in to implementing this part. Most common way would be having script on one server and pulling the data from all servers remotely. This is not the case here - I have decided to have the script running on each server as standalone - here is why :
- It is fairly simple to achieve
- If a server hangs - Script will continue to work for all the rest of the servers.
- Speed - instead of running multiple remote requests for pulling data (synchronously) each server is running its own request at same time and sends back data to the log files which are kept in central shared location
How to run it:
- Open the script and configure to your needs
- Put script in a shared location accessible from all servers.
- Create a scheduled task on one server to run the script with the required frequency
- Export the task to an XML file
- Run this below command in PowerShell to spread the task to all servers in XenApp farm
Once set script will run and push data to csv files. You will end up with a bunch of CSV files in the same folder with the script.
How to read logs:
You can use a very neat feature of MS Excel called Slicer. Here are the steps :
- Open the desired log file in Excel
- Go to Insert > Pivot table > OK
- Then on the toolbar you will see a button Insert Slicer
- Once you click it - you will have to select the desired elements that you want displayed and hit OK
Then you will end up with something similar to this :
What is the cool about that - you can click on any element and it will high lite for you the rest of the corresponding elements e.g. in the above picture I have clicked a 14GB session memory and I can see right away who the user was and time frame and what was the CPU utilization at that time etc.
I hope script will save you a lot of time in the search for performance issues.
Script is provided as is. I am not taking any responsibility for any data loss or damage that can occur due to the use of the script. Please use only at your own risk and only if you are familiar with PowerShell programing.
##
##
## Simple Resource Utilization report
##
## Plamen Georgiev (c) 2015
## http://valnet3.com
##
## v 2.5
##
## Script is used for collecting CPU and Memory utilization data per session and in total.
##
## It needs to be run by a scheduled task.
##
## The output file is in CSV format and is named with the hostname of the server on which
## script is running. Log file location is in the same folder as the script.
##
## Here is a quick way of deploying the script on multiple Citrix servers.
##
## Add-PSSnapin Citrix.*
## get-xaserver | where-object {$_.servername -like "Filter*"}| % {Write-Host $_.servername;schtasks /S $_.servername /create /XML '\\path_to\ExportedTask.xml'/tn NameOfTask /f}
##
##
$computerName = [system.environment]::MachineName
$Invocation = (Get-Variable MyInvocation).Value
$PSScriptRoot = Split-Path $Invocation.MyCommand.Path
$logfile = "$PSScriptRoot\$computerName.csv"
$archLogFileName = "$PSScriptRoot\$computerName-{0:yyyy-MM-dd-HHmmss}.csv" -f (Get-Date)
$archFolderName= "$PSScriptRoot\archived"
$zipFileName = "$archFolderName\$computerName-{0:yyyy-MM-dd-HHmmss}.zip" -f (Get-Date)
$maxLogSizeKB = 1024KB;
$date = Get-Date -format d
$time = Get-Date -format t
if ((Get-Item $logfile -ErrorAction SilentlyContinue).length -gt $maxLogSizeKB)
{
if(-not (test-path($archFolderName)))
{
md $archFolderName
}
ren $logfile $archLogFileName
if(-not (test-path($zipFileName)))
{
set-content $zipFileName ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
(dir $zipFileName).IsReadOnly = $false
}
$shellApplication = new-object -com shell.application
$zipPackage = $shellApplication.NameSpace($zipFileName)
$zipPackage.CopyHere($archLogFileName)
do
{
Start-sleep -milliseconds 1200
}
while ($zipPackage.Items().count -eq 0)
del $archLogFileName
}
$csvHeader = "Date,Time,SessionID,UserName,SessionMemoryMB,TotalFreeMemoryMB,TotalFreeMemory%,SessionCPU%,TotalCPU%"
$tcpu = ((Get-Counter "\processor(_total)\% processor time").countersamples) | select -expand cookedvalue
$avg = [math]::round([int]($tcpu),2)
$memMB = [math]::round((Get-WmiObject win32_operatingsystem).freephysicalmemory/1024,0)
$memPrc = (100/[math]::round((Get-WmiObject win32_operatingsystem).TotalVisibleMemorySize/1024,0))* $memMB
### Uncomment for debugging output
### Write-Host "TotCPU: $avg FMMB: $memMB FM%: $memPrc"
if (!(Test-Path $logfile))
{
$csvHeader | Out-File -Encoding "UTF8" $logfile
}
$CpuCores = (Get-WMIObject Win32_ComputerSystem).NumberOfLogicalProcessors
$a=@()
$procs = get-process | where-object {$_.ProcessName -ne "Idle"}
$counters = get-counter -Counter "\Process(*)\ID Process","\process(*)\% Processor Time" -ErrorAction SilentlyContinue
$sessions = $procs | Select-Object SessionID -Unique | Sort -Property SessionId | Select -Expand SessionID
foreach ($sessid in $sessions)
{
$usrprocs = $procs | where-object {$_.SessionId -eq $sessid}
foreach($proc in $usrprocs)
{
$usrMem = $usrMem + [int64]($proc.WorkingSet64/(1024*1024))
}
$ucount = $usrprocs | %{ $up = $_ ; $counters.CounterSamples | where-object {$_.cookedvalue -eq $up.ID } | select -ExpandProperty Path } | %{$pn = ($_|out-string).split("(")[1].split(')')[0]; $cnt ="\\$computerName\process($pn)\% Processor Time"; $cnt}
$pcpu = $ucount | %{ $uc = $_ ; $counters.CounterSamples | where-object {$_.path -eq "$uc" }} | select -ExpandProperty cookedvalue
$utcpu = [math]::round((($pcpu | Measure-Object -Sum).Sum)/$CpuCores,2)
$procActive = get-process | Where-Object {$_.processname -eq "dwm"-and $_.sessionID -eq $sessid}| select -expand ProcessName -ErrorAction SilentlyContinue
if($procActive)
{
$u = (gwmi win32_process -filter "sessionId like $sessid and name like 'dwm%'").getowner().user;
}
Else
{
$u = "----"
}
$usrCpu = [int]($usrCpu / $CpuCores)
$objResult = New-Object System.Object
$objResult | Add-Member -type NoteProperty -name 'Date' -value $date
$objResult | Add-Member -type NoteProperty -name 'Time' -value $time
$objResult | Add-Member -type NoteProperty -name 'SessionID' -value $sessid
$objResult | Add-Member -type NoteProperty -name 'UserName' -value $u
$objResult | Add-Member -type NoteProperty -name 'SessionMemoryMB' -value $usrMem
$objResult | Add-Member -type NoteProperty -name 'TotalFreeMemoryMB' -value $memMB
$objResult | Add-Member -type NoteProperty -name 'TotalFreeMemory%' -value $memPrc
$objResult | Add-Member -type NoteProperty -name 'SessionCPU%' -value $utcpu
$objResult | Add-Member -type NoteProperty -name 'TotalCPU%' -value $avg
$newdata = $objResult | ConvertTo-Csv -NoTypeInformation
$null, $justData = $newdata
Add-Content -Path $logfile -Value $justData -Encoding UTF8
$usrCpu = $null
$usrMem = $null
}
Hi Plamen Georgiev, is updated script available? With this script insted of usernames I am getting usernames as DWM-1, DWM-2........
Hi, I'm very interested in this script, but the download site is down. Can you share it we by other means?
I have uploaded by mistake an early version of the script. I have now corrected this and the latest version is on-line.