Retrieving Windows performance data in PowerShell

Taking Precautions

Reading Performance Data

Performance testing only really becomes exciting if you can evaluate the performance data. Windows provides performance logging in the Performance Monitor. The subsystem lets you create individual snippets or longer historical records of the computer's performance. The system uses a variety of indicators that measure and log certain performance components. The known performance counters include CPU usage, free available memory, and the physical hard disk queue and its percent free space (Figure 3).

Figure 3: The Performance Monitor collects performance data using a variety of configurable performance indicators.

Anyone who is familiar with the details of performance logging in Windows can familiarize themselves with the Performance Monitor. To access it via the console, type perfmon.msc. You can enable more performance indicators as needed; they are divided into several categories such as Processor, Memory, or PhysicalDisk. Indicators are freely selectable. After the indicators have been selected, the Performance Monitor displays the collected data as plots on a chart.

All the performance counters found in the Performance Monitor can be queried and reused with PowerShell. If you know the names of the counters, you can query them using the Get-Counter cmdlet (Listing 8).

Listing 8

Getting a Single Get-Counter Value

# Get-Counter -Counter "\Processor(_Total)\% Processor Time"
Timestamp                   CounterSamples
---------                   --------------
16.03.2014 10:58:22         \\helix\processor(_total)\% \
  processor time: 24.4764593867485

Querying an indicator once will output the current real-time value for the system. However, because it is only a single value that might be taken when the system is briefly peaking or in a trough, it might have very little validity in terms of the true state of the system. To collect multiple values then, the cmdlet supports two parameters: MaxSamples and SampleInterval.

The cmdlet in Listing 9 does not just retrieve the current value, it picks up a total of 10 samples at five-second intervals, or a period of 50 seconds, which you can then reprocess using, for example, Measure-Object to determine the mean value:

Listing 9

Collecting Multiple System Values

# Get-Counter -Counter "\Processor(_Total)\% Processor Time" \
  -SampleInterval 5 -MaxSamples 10
Timestamp                   CounterSamples
---------                   --------------
16.03.2014 11:17:13 AM      \\helix\processor(_total)\% \
  processor time: 18.6118692812167
                            16.03.2014 11:17:18 AM
                            \\helix\processor(_total)\% \
                              processor time: 24.2151756511614
                            16.03.2014 11:17:23 AM
                            \\helix\processor(_total)\% \
                              processor time: 23.8306694146364
[...]
# $samples = Get-Counter -Counter "\Processor(_Total)\% Processor Time" \
   -SampleInterval 5 -MaxSamples 10
# $samples.CounterSamples.CookedValue | Measure-Object -Average
Count:   10
Average: 9.04691386288023
Sum:
Maximum:
Minimum:
Property:

The collected raw data is embedded in CookedValue and sent through the pipe to Measure-Object, where it emerges as an average. This basic method can be compiled as a small script that collects performance counters, calculates the means, and then outputs the values.

You can expand the collect-counters.ps1 script (Listing 10) as you like. It collects the data for the selected performance counters and outputs them at the command line after retrieval. A neat progress indicator also appears. The performance counters are stored in an array that can be expanded arbitrarily. The three counters used are only examples. The script is created so that the array can be filled with a few more indicators, which are then automatically scanned one after another.

Listing 10

collect-counters.ps1

##Parameter
$NUMBER_OF_SAMPLES = 5;
$SAMPLE_INTERVAL_TIME = 1;
$counters = @(
"\Memory\Available MBytes",
"\Processor(_Total)\% Processor Time",
"\LogicalDisk(_Total)\% Free Space",
"\Network Adapter(NIC TEAM 1)\Bytes Total/sec",
"\Objects\Processes"
) ##enter other counter here.
##the values are saved here.
$val = @{}
$i = 0;
foreach($counter in $counters)
{
    Write-Progress -activity "Collecting performance data"
    -status "Counter data $($i+1) von $($counters.Count):
    $($counter)" -PercentComplete
    $(($i+1)/($counters.Count+1)*100)
    try
    {
      $v = Get-Counter -Counter $counter -MaxSamples
      $NUMBER_OF_SAMPLES -SampleInterval $SAMPLE_
      INTERVAL_TIME
      $val.Add($counter, ($v.CounterSamples.CookedValue
      | Measure-Object -Average).Average )
    }
    catch
    {
      Write-Host "Error: the counter could not be collected. Reason: $($_)"
    }
    $i++;
}
    Write-Progress -activity "Collecting performance data" status \
     "Releasing results" -PercentComplete 100
Write-Host "Results: "
$val

Reviewing Threshold Values

The script only outputs values at the command line, so there is no automatic warning if one of the performance counters reaches a critical value. To do so, the script in Listing 11 breaks the task down into three functions: one each to test for a shortfall or surfeit of the threshold value, and one to collect the data from remote servers.

Listing 11

warn-counters.ps1

##Parameter
$NUMBER_OF_SAMPLES = 5;
$SAMPLE_INTERVAL_TIME = 1;
###Load server list
$serverList = Get-Content -Path C:\temp\server.txt;
##Defining the counter and threshold values
##Each line: Counter, alarm triggers if over/under, threshold value
$counters = (
('\Memory\Available MBytes', 'under', 500), #Alert if under 500
('\Processor(_Total)\% Processor Time', 'over', 60), #Alert if over 60
('\LogicalDisk(_Total)\% Free Space', 'under', 10 ) #Alert if under 10
) #Array end
function checkLowerLimit([int]$threshold, [int]$average)
{
    if($average -lt $threshold)
   {
      Write-Host "threshold value not reached!" return $false;
    }
    return $true;
}
function checkUpperLimit([int]$threshold, [int]$average)
{
    if($average -gt $threshold)
    {
      Write-Host "threshold value exceeded!" return $false;
    }
    return $true;
}
function getPerformanceData([array]$counter, [string]$machine)
{
    try
    {
      $v = Get-Counter -Counter $counter -ComputerName $machine \
        -MaxSamples $NUMBER_OF_SAMPLES -SampleInterval
      $SAMPLE_INTERVAL_TIME -ErrorAction SilentlyContinue
      if($v -eq $null -or $v -eq "")
      {
        Write-Host "Counter $($counter) delivered no value."
        Continue;
      }
      $average = ($v.CounterSamples.CookedValue | \
        Measure-Object -Average).Average
    }
    catch
    {
      Write-Host "Error: the counter could not be collected. Reason: $($_)"
    }
    ##Bigger or smaller?
    if($counter[1] -eq "under") { $limit = checkLowerLimit \
                                  $counter[2] $average }
    else { $limit = checkUpperLimit $counter[2] $average }
    ##If the test retruns a "$false", the threshold value has been damaged:
    if(-not $limit) { Write-Host "[$($machine)] Counter \
      $($counter[0]) has, with the value $($average),
    exceed $($counter[2]) not reached $($counter[1]) \
      the threshold value." }
}
###The script starts here:
$c = $serverlist.Count * $counters.Count
$i=0;
foreach($server in $serverList)
{
    foreach($counter in $counters)
    {
      Write-Progress -activity "Collecting performance data" \
        -status "Counter data $($i+1) from $($c):
      $($counter) on $($server)" -PercentComplete $(($i+1)/($c)*100)
      getPerformanceData $counter $server
      $i++;
    }
} #End foreach server
    Write-Host "End of the collection. Threshold limit damages \
      are issued on the command line."

The collection script on remote computers is the well-known server.txt from the first example. The performance counters to be retrieved are in a table (array); each row represents a counter, and the columns represent the name, the check for a shortfall (under) or excess (over), and the threshold value itself. For example,

('\Memory\Available MBytes', 'under', 500)

checks to see whether the available memory drops below 500MB.

The script then runs through the freely extensible performance counters by server and checks the results. If a threshold is over- or undershot, it outputs a corresponding message on the command line. The server name is transmitted once again with the ComputerName parameter.

An extension of the script could dump the results to a text file or even a database, allowing you to evaluate a trend over time. PowerShell professionals could also parallelize the counter queries from several servers to save processing time.

Conclusions

PowerShell quickly and simply lets you keep an eye on the performance parameters of multiple computers from the command line. More complex scripts can be compiled to monitor threshold values.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus
Subscribe to our ADMIN Newsletters
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs



Support Our Work

ADMIN content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.

Learn More”>
	</a>

<hr>		    
			</div>
		    		</div>

		<div class=