Delen via


Scripting met PowerShell en Storage Spaces Direct-prestatiegeschiedenis

In Windows Server 2019 registreert Opslagruimten Direct uitgebreide prestatiegeschiedenis voor virtuele machines, servers, stations, volumes, netwerkadapters en meer. Prestatiegeschiedenis is eenvoudig om query's uit te voeren en te verwerken in PowerShell, zodat u snel van onbewerkte gegevens kunt gaan naar werkelijke antwoorden op vragen zoals:

  1. Waren er vorige week enige processorspikes?
  2. Vertoont een fysieke schijf abnormale latentie?
  3. Welke VM's gebruiken momenteel de meeste IOPS voor opslag?
  4. Is mijn netwerkbandbreedte overbelast?
  5. Wanneer is er geen vrije ruimte meer beschikbaar voor dit volume?
  6. Welke VM's hebben in de afgelopen maand het meeste geheugen gebruikt?

De Get-ClusterPerf cmdlet is gebouwd voor scripting. Het accepteert invoer van cmdlets zoals Get-VM of Get-PhysicalDisk door de pijplijn voor het afhandelen van koppeling, en u kunt de uitvoer doorsluisen naar hulpprogramma-cmdlets zoals Sort-Object, Where-Objecten Measure-Object om snel krachtige query's op te stellen.

In dit onderwerp vindt u 6 voorbeeldscripts die de 6 bovenstaande vragen beantwoorden. Ze bevatten patronen die u kunt toepassen om pieken te vinden, gemiddelden te vinden, trendlijnen te tekenen, uitbijterdetectie uit te voeren en meer, in verschillende gegevens en tijdsbestekken. Ze worden geleverd als gratis starterscode voor u om te kopiëren, uit te breiden en opnieuw te gebruiken.

Note

Ter beknoptheid laten de voorbeeldscripts dingen weg zoals foutafhandeling die u kunt verwachten van PowerShell-code van hoge kwaliteit. Ze zijn voornamelijk bedoeld voor inspiratie en onderwijs in plaats van productiegebruik.

Voorbeeld 1: CPU, ik zie je!

In dit voorbeeld wordt de ClusterNode.Cpu.Usage reeks uit het LastWeek tijdsbestek gebruikt om het maximum ('hoog waterteken'), het minimum en het gemiddelde CPU-gebruik voor elke server in het cluster weer te geven. Het voert ook eenvoudige kwartielanalyse uit om te laten zien hoeveel uur het CPU-gebruik meer dan 25%, 50%en 75% in de afgelopen 8 dagen was.

Screenshot

In de onderstaande schermopname zien we dat Server-02 vorige week een onverklaarbare piek had:

Schermopname die laat zien dat Server-02 vorige week een onverklaarbare piek had.

Hoe het werkt

De uitvoer van Get-ClusterPerf wordt mooi doorgegeven aan de ingebouwde Measure-Object cmdlet, we specificeren simpelweg de Value eigenschap. Met zijn -Maximum, -Minimumen -Average vlaggen, Measure-Object krijgen we de eerste drie kolommen vrijwel gratis. Om de kwartielanalyse uit te voeren, kunnen we doorsluisen naar Where-Object en tellen hoeveel waarden (groter dan) 25, 50 of 75 zijn -Gt . De laatste stap is het verfraaien met Format-Hours en Format-Percent helperfuncties , zeker optioneel.

Script

Dit is het script:

Function Format-Hours {
    Param (
        $RawValue
    )
    # Weekly timeframe has frequency 15 minutes = 4 points per hour
    [Math]::Round($RawValue/4)
}

Function Format-Percent {
    Param (
        $RawValue
    )
    [String][Math]::Round($RawValue) + " " + "%"
}

$Output = Get-ClusterNode | ForEach-Object {
    $Data = $_ | Get-ClusterPerf -ClusterNodeSeriesName "ClusterNode.Cpu.Usage" -TimeFrame "LastWeek"

    $Measure = $Data | Measure-Object -Property Value -Minimum -Maximum -Average
    $Min = $Measure.Minimum
    $Max = $Measure.Maximum
    $Avg = $Measure.Average

    [PsCustomObject]@{
        "ClusterNode"    = $_.Name
        "MinCpuObserved" = Format-Percent $Min
        "MaxCpuObserved" = Format-Percent $Max
        "AvgCpuObserved" = Format-Percent $Avg
        "HrsOver25%"     = Format-Hours ($Data | Where-Object Value -Gt 25).Length
        "HrsOver50%"     = Format-Hours ($Data | Where-Object Value -Gt 50).Length
        "HrsOver75%"     = Format-Hours ($Data | Where-Object Value -Gt 75).Length
    }
}

$Output | Sort-Object ClusterNode | Format-Table

Voorbeeld 2: Brand, brand, latentie-uitbijter

In dit voorbeeld wordt de PhysicalDisk.Latency.Average reeks uit het LastHour tijdsbestek gebruikt om te zoeken naar statistische uitbijters, gedefinieerd als stations met een gemiddelde latentie per uur die groter is dan +3σ (drie standaarddeviaties) boven het populatiegemiddelde.

Important

Dit script implementeert geen beveiliging tegen lage variantie, verwerkt geen gedeeltelijke ontbrekende gegevens, maakt geen onderscheid tussen model of firmware, enzovoort. Wees goed oordeel en vertrouw niet alleen op dit script om te bepalen of een harde schijf moet worden vervangen. Het wordt hier alleen voor educatieve doeleinden gepresenteerd.

Screenshot

In de onderstaande schermopname zien we dat er geen uitschieters zijn:

Schermopname die laat zien dat er geen uitschieters zijn.

Hoe het werkt

Eerst sluiten we niet-actieve of bijna niet-actieve stations uit door te controleren of dat PhysicalDisk.Iops.Total consistent -Gt 1is. Voor elke actieve HDD sturen we het LastHour tijdframe, bestaande uit 360 metingen met intervallen van 10 seconden, door naar Measure-Object -Average om de gemiddelde latentie in het afgelopen uur te verkrijgen. Dit bereidt onze bevolking voor.

We implementeren de algemeen bekende formule om het gemiddelde μ en de standaarddeviatie σ van de populatie te vinden. Voor elke actieve HDD vergelijken we de gemiddelde latentie met het gemiddelde van de populatie en delen we door de standaarddeviatie. We houden de onbewerkte waarden, zodat we onze resultaten kunnen Sort-Object, maar gebruiken Format-Latency en Format-StandardDeviation helperfuncties om te verfraaien wat we zullen weergeven – zeker optioneel.

Als een drive meer dan +3σ is, markeren we Write-Host in rood; is dat niet het geval, dan in groen.

Script

Dit is het script:

Function Format-Latency {
    Param (
        $RawValue
    )
    $i = 0 ; $Labels = ("s", "ms", "μs", "ns") # Petabits, just in case!
    Do { $RawValue *= 1000 ; $i++ } While ( $RawValue -Lt 1 )
    # Return
    [String][Math]::Round($RawValue, 2) + " " + $Labels[$i]
}

Function Format-StandardDeviation {
    Param (
        $RawValue
    )
    If ($RawValue -Gt 0) {
        $Sign = "+"
    }
    Else {
        $Sign = "-"
    }
    # Return
    $Sign + [String][Math]::Round([Math]::Abs($RawValue), 2) + "σ"
}

$HDD = Get-StorageSubSystem Cluster* | Get-PhysicalDisk | Where-Object MediaType -Eq HDD

$Output = $HDD | ForEach-Object {

    $Iops = $_ | Get-ClusterPerf -PhysicalDiskSeriesName "PhysicalDisk.Iops.Total" -TimeFrame "LastHour"
    $AvgIops = ($Iops | Measure-Object -Property Value -Average).Average

    If ($AvgIops -Gt 1) { # Exclude idle or nearly idle drives

        $Latency = $_ | Get-ClusterPerf -PhysicalDiskSeriesName "PhysicalDisk.Latency.Average" -TimeFrame "LastHour"
        $AvgLatency = ($Latency | Measure-Object -Property Value -Average).Average

        [PsCustomObject]@{
            "FriendlyName"  = $_.FriendlyName
            "SerialNumber"  = $_.SerialNumber
            "MediaType"     = $_.MediaType
            "AvgLatencyPopulation" = $null # Set below
            "AvgLatencyThisHDD"    = Format-Latency $AvgLatency
            "RawAvgLatencyThisHDD" = $AvgLatency
            "Deviation"            = $null # Set below
            "RawDeviation"         = $null # Set below
        }
    }
}

If ($Output.Length -Ge 3) { # Minimum population requirement

    # Find mean μ and standard deviation σ
    $μ = ($Output | Measure-Object -Property RawAvgLatencyThisHDD -Average).Average
    $d = $Output | ForEach-Object { ($_.RawAvgLatencyThisHDD - $μ) * ($_.RawAvgLatencyThisHDD - $μ) }
    $σ = [Math]::Sqrt(($d | Measure-Object -Sum).Sum / $Output.Length)

    $FoundOutlier = $False

    $Output | ForEach-Object {
        $Deviation = ($_.RawAvgLatencyThisHDD - $μ) / $σ
        $_.AvgLatencyPopulation = Format-Latency $μ
        $_.Deviation = Format-StandardDeviation $Deviation
        $_.RawDeviation = $Deviation
        # If distribution is Normal, expect >99% within 3σ
        If ($Deviation -Gt 3) {
            $FoundOutlier = $True
        }
    }

    If ($FoundOutlier) {
        Write-Host -BackgroundColor Black -ForegroundColor Red "Oh no! There's an HDD significantly slower than the others."
    }
    Else {
        Write-Host -BackgroundColor Black -ForegroundColor Green "Good news! No outlier found."
    }

    $Output | Sort-Object RawDeviation -Descending | Format-Table FriendlyName, SerialNumber, MediaType, AvgLatencyPopulation, AvgLatencyThisHDD, Deviation

}
Else {
    Write-Warning "There aren't enough active drives to look for outliers right now."
}

Voorbeeld 3: Lawaaierige buur? Dat is schrijven!

Prestatiegeschiedenis kan ook vragen over nu beantwoorden. Nieuwe metingen zijn in realtime beschikbaar, elke 10 seconden. In dit voorbeeld wordt de VHD.Iops.Total reeks uit het MostRecent tijdsbestek gebruikt om de drukste (sommigen zeggen 'luidruchtigste') virtuele machines te identificeren die de meeste opslag-IOPS gebruiken, op elke host in het cluster en de uitsplitsing van lezen/schrijven van hun activiteit weergeven.

Screenshot

In de onderstaande schermopname ziet u de top 10 virtuele machines op opslagactiviteit:

Schermopname van de top 10 virtuele machines op opslagactiviteit.

Hoe het werkt

In tegenstelling tot Get-PhysicalDisk, is de Get-VM cmdlet niet clusterbewust. De cmdlet retourneert alleen VM's op de lokale server. Om een query uit te voeren vanaf elke server parallel, verpakken we onze aanroep.Invoke-Command (Get-ClusterNode).Name { ... } Voor elke virtuele machine krijgen we de VHD.Iops.Total, VHD.Iops.Readen VHD.Iops.Write metingen. Door de -TimeFrame parameter niet op te geven, krijgen we het MostRecent enkele gegevenspunt voor elk punt.

Tip

Deze reeks geeft de som van de activiteit van deze VM weer voor alle VHD-/VHDX-bestanden. Dit is een voorbeeld waarbij de prestatiegeschiedenis automatisch voor ons wordt samengevoegd. Als u de uitsplitsing per VHD/VHDX wilt ophalen, kunt u een individueel Get-VHD doorsturen naar Get-ClusterPerf in plaats van de virtuele machine.

De resultaten van elke server komen samen als $Output, wat we kunnen Sort-Object en dan Select-Object -First 10. Let op dat Invoke-Command resultaten worden voorzien van een PsComputerName eigenschap die aangeeft waar ze vandaan komen, die we kunnen afdrukken om te weten waar de virtuele machine draait.

Script

Dit is het script:

$Output = Invoke-Command (Get-ClusterNode).Name {
    Function Format-Iops {
        Param (
            $RawValue
        )
        $i = 0 ; $Labels = (" ", "K", "M", "B", "T") # Thousands, millions, billions, trillions...
        Do { if($RawValue -Gt 1000){$RawValue /= 1000 ; $i++ } } While ( $RawValue -Gt 1000 )
        # Return
        [String][Math]::Round($RawValue) + " " + $Labels[$i]
    }

    Get-VM | ForEach-Object {
        $IopsTotal = $_ | Get-ClusterPerf -VMSeriesName "VHD.Iops.Total"
        $IopsRead  = $_ | Get-ClusterPerf -VMSeriesName "VHD.Iops.Read"
        $IopsWrite = $_ | Get-ClusterPerf -VMSeriesName "VHD.Iops.Write"
        [PsCustomObject]@{
            "VM" = $_.Name
            "IopsTotal" = Format-Iops $IopsTotal.Value
            "IopsRead"  = Format-Iops $IopsRead.Value
            "IopsWrite" = Format-Iops $IopsWrite.Value
            "RawIopsTotal" = $IopsTotal.Value # For sorting...
        }
    }
}

$Output | Sort-Object RawIopsTotal -Descending | Select-Object -First 10 | Format-Table PsComputerName, VM, IopsTotal, IopsRead, IopsWrite

Voorbeeld 4: Zoals ze zeggen, "25-gig is de nieuwe 10-gig"

In dit voorbeeld wordt de NetAdapter.Bandwidth.Total reeks uit het LastDay tijdsbestek gebruikt om te zoeken naar tekenen van netwerkverzadiging, gedefinieerd als >90% van theoretische maximale bandbreedte. Voor elke netwerkadapter in het cluster vergelijkt deze het hoogst waargenomen bandbreedtegebruik in de afgelopen dag met de vermelde koppelingssnelheid.

Screenshot

In de onderstaande schermopname zien we dat één Fabrikam NX-4 Pro #2 piekte in de laatste dag:

Schermopname die laat zien dat Fabrikam NX-4 Pro #2 de laatste dag piekte.

Hoe het werkt

We herhalen onze Invoke-Command truc zoals hierboven naar Get-NetAdapter op elke server en routeren naar Get-ClusterPerf. Onderweg pakken we twee relevante eigenschappen: de LinkSpeed tekenreeks zoals 10 Gbps en het onbewerkte Speed gehele getal, zoals 1000000000000. We gebruiken Measure-Object om het gemiddelde en de piek van de laatste dag te verkrijgen (herinnering: elke meting in het LastDay tijdsbestek vertegenwoordigt 5 minuten) en vermenigvuldigt met 8 bits per byte om een vergelijking tussen appels en appels te krijgen.

Note

Sommige leveranciers, zoals Chelsio, bevatten rdMA-activiteit (Remote-Direct Memory Access) in hun prestatiemeteritems voor netwerkadapters , dus deze is opgenomen in de NetAdapter.Bandwidth.Total reeks. Anderen, zoals Mellanox, mogen dat niet. Als uw leverancier dit niet doet, voegt u gewoon de NetAdapter.Bandwidth.RDMA.Total reeks toe aan uw versie van dit script.

Script

Dit is het script:

$Output = Invoke-Command (Get-ClusterNode).Name {

    Function Format-BitsPerSec {
        Param (
            $RawValue
        )
        $i = 0 ; $Labels = ("bps", "kbps", "Mbps", "Gbps", "Tbps", "Pbps") # Petabits, just in case!
        Do { $RawValue /= 1000 ; $i++ } While ( $RawValue -Gt 1000 )
        # Return
        [String][Math]::Round($RawValue) + " " + $Labels[$i]
    }

    Get-NetAdapter | ForEach-Object {

        $Inbound = $_ | Get-ClusterPerf -NetAdapterSeriesName "NetAdapter.Bandwidth.Inbound" -TimeFrame "LastDay"
        $Outbound = $_ | Get-ClusterPerf -NetAdapterSeriesName "NetAdapter.Bandwidth.Outbound" -TimeFrame "LastDay"

        If ($Inbound -Or $Outbound) {

            $InterfaceDescription = $_.InterfaceDescription
            $LinkSpeed = $_.LinkSpeed

            $MeasureInbound = $Inbound | Measure-Object -Property Value -Maximum
            $MaxInbound = $MeasureInbound.Maximum * 8 # Multiply to bits/sec

            $MeasureOutbound = $Outbound | Measure-Object -Property Value -Maximum
            $MaxOutbound = $MeasureOutbound.Maximum * 8 # Multiply to bits/sec

            $Saturated = $False

            # Speed property is Int, e.g. 10000000000
            If (($MaxInbound -Gt (0.90 * $_.Speed)) -Or ($MaxOutbound -Gt (0.90 * $_.Speed))) {
                $Saturated = $True
                Write-Warning "In the last day, adapter '$InterfaceDescription' on server '$Env:ComputerName' exceeded 90% of its '$LinkSpeed' theoretical maximum bandwidth. In general, network saturation leads to higher latency and diminished reliability. Not good!"
            }

            [PsCustomObject]@{
                "NetAdapter"  = $InterfaceDescription
                "LinkSpeed"   = $LinkSpeed
                "MaxInbound"  = Format-BitsPerSec $MaxInbound
                "MaxOutbound" = Format-BitsPerSec $MaxOutbound
                "Saturated"   = $Saturated
            }
        }
    }
}

$Output | Sort-Object PsComputerName, InterfaceDescription | Format-Table PsComputerName, NetAdapter, LinkSpeed, MaxInbound, MaxOutbound, Saturated

Voorbeeld 5: Maak opslag weer trendy!

Als u macrotrends wilt bekijken, wordt de prestatiegeschiedenis maximaal 1 jaar bewaard. In dit voorbeeld wordt de Volume.Size.Available reeks uit het LastYear tijdsbestek gebruikt om de snelheid te bepalen die de opslag vult en schat wanneer deze vol is.

Screenshot

In de onderstaande schermopname zien we dat het back-upvolume ongeveer 15 GB per dag toevoegt:

Schermopname van het back-upvolume dat ongeveer 15 GB per dag toevoegt.

Tegen dit tarief bereikt het de capaciteit in nog eens 42 dagen.

Hoe het werkt

Het LastYear tijdsbestek heeft één gegevenspunt per dag. Hoewel u slechts twee punten nodig hebt om aan een trendlijn te passen, is het in de praktijk beter om meer te vereisen, zoals 14 dagen. We gebruiken Select-Object -Last 14 om een matrix van (x, y) punten in te stellen voor x in het bereik [1, 14]. Met deze punten implementeren we het eenvoudige algoritme voor lineaire kleinste kwadraten om te zoeken $A en $B die de lijn van de beste fit y = ax + b parameteriseren. Welkom op de middelbare school helemaal opnieuw.

Door de eigenschap van SizeRemaining van het volume te delen door de trend (de helling $A) kunnen we grof schatten hoeveel dagen er nog zijn voordat het volume vol is bij de huidige snelheid van opslaggroei. De Format-Bytes, Format-Trend en Format-Days helperfuncties verfraaien de uitvoer.

Important

Deze schatting is lineair en is alleen gebaseerd op de meest recente 14 dagelijkse metingen. Er bestaan geavanceerdere en nauwkeurige technieken. Wees goed oordeel en vertrouw niet alleen op dit script om te bepalen of u wilt investeren in het uitbreiden van uw opslag. Het wordt hier alleen voor educatieve doeleinden gepresenteerd.

Script

Dit is het script:


Function Format-Bytes {
    Param (
        $RawValue
    )
    $i = 0 ; $Labels = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
    Do { $RawValue /= 1024 ; $i++ } While ( $RawValue -Gt 1024 )
    # Return
    [String][Math]::Round($RawValue) + " " + $Labels[$i]
}

Function Format-Trend {
    Param (
        $RawValue
    )
    If ($RawValue -Eq 0) {
        "0"
    }
    Else {
        If ($RawValue -Gt 0) {
            $Sign = "+"
        }
        Else {
            $Sign = "-"
        }
        # Return
        $Sign + $(Format-Bytes ([Math]::Abs($RawValue))) + "/day"
    }
}

Function Format-Days {
    Param (
        $RawValue
    )
    [Math]::Round($RawValue)
}

$CSV = Get-Volume | Where-Object FileSystem -Like "*CSV*"

$Output = $CSV | ForEach-Object {

    $N = 14 # Require 14 days of history

    $Data = $_ | Get-ClusterPerf -VolumeSeriesName "Volume.Size.Available" -TimeFrame "LastYear" | Sort-Object Time | Select-Object -Last $N

    If ($Data.Length -Ge $N) {

        # Last N days as (x, y) points
        $PointsXY = @()
        1..$N | ForEach-Object {
            $PointsXY += [PsCustomObject]@{ "X" = $_ ; "Y" = $Data[$_-1].Value }
        }

        # Linear (y = ax + b) least squares algorithm
        $MeanX = ($PointsXY | Measure-Object -Property X -Average).Average
        $MeanY = ($PointsXY | Measure-Object -Property Y -Average).Average
        $XX = $PointsXY | ForEach-Object { $_.X * $_.X }
        $XY = $PointsXY | ForEach-Object { $_.X * $_.Y }
        $SSXX = ($XX | Measure-Object -Sum).Sum - $N * $MeanX * $MeanX
        $SSXY = ($XY | Measure-Object -Sum).Sum - $N * $MeanX * $MeanY
        $A = ($SSXY / $SSXX)
        $B = ($MeanY - $A * $MeanX)
        $RawTrend = -$A # Flip to get daily increase in Used (vs decrease in Remaining)
        $Trend = Format-Trend $RawTrend

        If ($RawTrend -Gt 0) {
            $DaysToFull = Format-Days ($_.SizeRemaining / $RawTrend)
        }
        Else {
            $DaysToFull = "-"
        }
    }
    Else {
        $Trend = "InsufficientHistory"
        $DaysToFull = "-"
    }

    [PsCustomObject]@{
        "Volume"     = $_.FileSystemLabel
        "Size"       = Format-Bytes ($_.Size)
        "Used"       = Format-Bytes ($_.Size - $_.SizeRemaining)
        "Trend"      = $Trend
        "DaysToFull" = $DaysToFull
    }
}

$Output | Format-Table

Voorbeeld 6: Geheugenvraat, je kunt wel rennen, maar je kunt je niet verstoppen

Omdat de prestatiegeschiedenis centraal wordt verzameld en opgeslagen voor het hele cluster, hoeft u nooit gegevens van verschillende machines samen te voegen, ongeacht hoe vaak VM's tussen hosts worden verplaatst. In dit voorbeeld wordt de VM.Memory.Assigned reeks uit het LastMonth tijdsbestek gebruikt om de virtuele machines te identificeren die het meeste geheugen in de afgelopen 35 dagen gebruiken.

Screenshot

In de onderstaande schermopname zien we de tien belangrijkste virtuele machines op basis van geheugengebruik vorige maand:

Schermopname van PowerShell

Hoe het werkt

We herhalen de truc van Invoke-Command, die hierboven is geïntroduceerd, op Get-VM elke server. We gebruiken Measure-Object -Average om het maandelijkse gemiddelde voor elke virtuele machine te verkrijgen, gevolgd Sort-Object door Select-Object -First 10 het verkrijgen van ons leaderboard. (Of misschien is het onze meest gewilde lijst?)

Script

Dit is het script:

$Output = Invoke-Command (Get-ClusterNode).Name {
    Function Format-Bytes {
        Param (
            $RawValue
        )
        $i = 0 ; $Labels = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
        Do { if( $RawValue -Gt 1024 ){ $RawValue /= 1024 ; $i++ } } While ( $RawValue -Gt 1024 )
        # Return
        [String][Math]::Round($RawValue) + " " + $Labels[$i]
    }

    Get-VM | ForEach-Object {
        $Data = $_ | Get-ClusterPerf -VMSeriesName "VM.Memory.Assigned" -TimeFrame "LastMonth"
        If ($Data) {
            $AvgMemoryUsage = ($Data | Measure-Object -Property Value -Average).Average
            [PsCustomObject]@{
                "VM" = $_.Name
                "AvgMemoryUsage" = Format-Bytes $AvgMemoryUsage.Value
                "RawAvgMemoryUsage" = $AvgMemoryUsage.Value # For sorting...
            }
        }
    }
}

$Output | Sort-Object RawAvgMemoryUsage -Descending | Select-Object -First 10 | Format-Table PsComputerName, VM, AvgMemoryUsage

Dat is het! Hopelijk inspireren deze voorbeelden u en helpen u om aan de slag te gaan. Met de prestatiegeschiedenis van Opslagruimten Direct en de krachtige, scriptvriendelijke Get-ClusterPerf cmdlet kunt u vragen en antwoorden krijgen. – complexe vragen tijdens het beheren en bewaken van uw Windows Server 2019-infrastructuur.

Aanvullende verwijzingen