Comparing Performance Counters

A common TSG step is to look at some perf counter, wait n minutes, then look at it again. When we’re talking about pools, it gets a little more involved.  PerfMon.exe isn’t the most easy to automate (you can save the counterset to an .msc, then copy that file across machines, but you still have to log into the machine to get the local data.)

Here’s another way:

 function Test-PerfCounter
{
    <#
    .synopsis
    Capture change over time for a perf counter from specified computer(s).

    .description
    Takes a snapshot of perf counters upon invocation, wait specified minutes, take second snapshot, compare values.

    .parameter ComputerName
    Specified computer(s) from which to collect perf counter data.  Defaults to $env:Computername

    .parameter Counter
    Name of perf counter from which to collect data. Required.

    .parameter Minutes
    Wait interval between first and second samples. Defaults to 5.

    .parameter Delta
    Threshold for difference between first and second runs beyond which test is marked as failure. Defaults to 0.01.

    .outputs
    PSObject with properties
    - [string]           ComputerName
    - ([int] or [float]) Value1
    - [Datetime]         Time1
    - ([int] or [float]) Value1
    - [Datetime]         Time1
    - [bool]             Pass

    .example
    Test-PerfCounter -Counter '\Processor Information(_Total)\% Processor Time'

    #>

    param (
        [parameter(ValueFromPipeline=$true)][string[]]$ComputerName = @($env:ComputerName.ToLower()),
        [string]$Counter = $null,
        [int]$Minutes = 5,
        [float]$Delta = 0.01
    );

    begin
    {
        if (!$Counter)
        {
            $msg = "$($MyInvocation.Line) -Counter not specified, required.";
            Write-Error   -Message $msg -ErrorAction SilentlyContinue;
            Write-Warning -Message $msg;
            return;
            break __outOfScript;

        } # if (!$Counter)

        if ($Minutes -le 0)
        {
            $msg = "$($MyInvocation.MyCommand.Name) -Minutes must be positive, required.";
            Write-Error   -Message $msg -ErrorAction SilentlyContinue;
            Write-Warning -Message $msg;
            return;
            break __outOfScript;

        } # if ($Minutes -le 0)

        $now = Get-Date;

    } # begin

    process {

        #region get first pass counters

        Write-Progress -Activity "($now) $($MyInvocation.Line)" -Status "Pass 1: $Counter";

        [Object[]]$run1 = Get-Counter -ComputerName $ComputerName -Counter $Counter |
        ForEach-Object {
            $Timestamp = $_.Timestamp;

            $_.CounterSamples |
            Select-Object -Property @{
                n = 'ComputerName';
                e = { $_[0].Path -replace '^\\\\' -replace '\\.*'; }
            }, @{
                n = 'Value';
                e = { $_[0].CookedValue; }
            }, TimeStamp
        } |
        Sort-Object ComputerName;

        if (!$run1 -and !$run1.Count)
        {
            $msg = "$($MyInvocation.Line) did not return any perf counters, stopping.";
            Write-Error   -Message $msg -ErrorAction SilentlyContinue;
            Write-Warning -Message $msg;
            return;
            break __outOfScript;

        } # if (!$run1...

        if ($DebugPreference -eq 'Continue') { $Global:__run1 = $run1; }

        #endregion
        #region Sleep for specified time

        $now = Get-Date;
        $stop = $now + (New-TimeSpan -Minutes $Minutes);

        while ($now -lt $stop)
        {
            $now = Get-Date;
            Write-Progress -SecondsRemaining ($stop - $now).TotalSeconds -Activity "($now) $($MyInvocation.Line)" -Status "Sleeping for $Minutes minute(s).";
            Start-Sleep -Seconds 10;
        } # while ($now -lt $stop)

        #endregion
        #region get second pass counters

        Write-Progress -Activity "($now) $($MyInvocation.Line)" -Status "Pass 2: $Counter";

        [Object[]]$run2 = Get-Counter -ComputerName $ComputerName -Counter $Counter |
        ForEach-Object {
            $Timestamp = $_.Timestamp;

            $_.CounterSamples |
            Select-Object -Property @{
                n = 'ComputerName';
                e = { $_[0].Path -replace '^\\\\' -replace '\\.*'; }
            }, @{
                n = 'Value';
                e = { $_[0].CookedValue; }
            }, TimeStamp
        } |
        Sort-Object ComputerName;

        if (!$run2 -and !$run2.Count)
        {
            $msg = "$($MyInvocation.Line) did not return any perf counters, stopping.";
            Write-Error   -Message $msg -ErrorAction SilentlyContinue;
            Write-Warning -Message $msg;
            return;
            break __outOfScript;

        } # if (!$run2 ...

        if ($DebugPreference -eq 'Continue') { $Global:__run2 = $run2; }

        #endregion
        #region output data

        for ($i = 0; $i -lt $run1.Count; $i++)
        {
            $counter1 = $run1[$i];
            $counter2 = $run2 | ? { $_.ComputerName -eq $counter1.Computername; }
            New-Object -TypeName PsObject -Property @{
                ComputerName = $counter1.Computername;
                Value1       = $counter1.Value;
                Time1        = $counter1.Timestamp;
                Value2       = $counter2.Value;
                Time2        = $counter2.TimeStamp;
                Pass         = [Math]::Abs($counter1.Value - $counter2.Value) -le $Delta;
            } |
            Select-Object -Property ComputerName, Value1, Time1, Value2, Time2, Pass;

        } # for ($i = 0...

        #endregion

    } # process

} # function Test-PerfCounter