Dela via


Skript med PowerShell och Storage Spaces Direct-prestandahistorik

I Windows Server 2019 registrerar Storage Spaces Direct och lagrar omfattande prestandahistorik för virtuella datorer, servrar, enheter, volymer, nätverkskort med mera. Prestandahistorik är lätt att fråga och bearbeta i PowerShell så att du snabbt kan gå från rådata till faktiska svar på frågor som:

  1. Fanns det några CPU-toppar förra veckan?
  2. Har någon fysisk disk onormal svarstid?
  3. Vilka virtuella datorer förbrukar mest lagrings-IOPS just nu?
  4. Är min nätverksbandbredd mättad?
  5. När tar volymen slut på ledigt utrymme?
  6. Vilka virtuella datorer har använt mest minne den senaste månaden?

Cmdleten Get-ClusterPerf är skapad för skript. Den accepterar indata från cmdletar som Get-VM eller Get-PhysicalDisk av pipelinen för att hantera associationen, och du kan skicka dess utdata till verktygs-cmdletar som Sort-Object, Where-Objectoch Measure-Object för att snabbt skapa kraftfulla frågor.

Det här avsnittet innehåller och förklarar 6 exempelskript som besvarar de 6 frågorna ovan. De innehåller mönster som du kan använda för att hitta toppar, hitta medelvärden, rita trendlinjer, köra avvikande identifiering med mera över en mängd olika data och tidsramar. De tillhandahålls som kostnadsfri startkod där du kan kopiera, utöka och återanvända dem.

Note

I korthet utelämnar exempelskripten saker som felhantering som du kan förvänta dig av PowerShell-kod av hög kvalitet. De är främst avsedda för inspiration och utbildning snarare än produktionsanvändning.

Exempel 1: CPU, jag ser dig!

Det här exemplet använder ClusterNode.Cpu.Usage serien från tidsramen LastWeek för att visa maximal ("högvattenmärke"), lägsta och genomsnittlig CPU-användning för varje server i klustret. Det gör också enkel kvartilanalys för att visa hur många timmar processoranvändningen var över 25%, 50%och 75% under de senaste 8 dagarna.

Screenshot

I skärmbilden nedan ser vi att Server-02 hade en oförklarlig topp förra veckan:

Skärmbild som visar att Server-02 hade en oförklarlig topp förra veckan.

Så här fungerar det

Utdata från Get-ClusterPerf kan enkelt kopplas till den inbyggda Measure-Object-cmdleten, där vi bara anger egenskapen Value. Med dess -Maximum, -Minimum, och -Average flaggor, Measure-Object ger oss de första tre kolumnerna nästan gratis. För att utföra kvartilanalysen kan vi skicka pipe till Where-Object och räkna hur många värden som var -Gt (större än) 25, 50 eller 75. Det sista steget är att försköna med Format-Hours och Format-Percent hjälpfunktioner – helt valfritt.

Script

Här är skriptet:

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

Exempel 2: Brand, brand, avvikande svarstid

Det här exemplet använder PhysicalDisk.Latency.Average serien från tidsramen LastHour för att leta efter statistiska extremvärden, definierade som enheter med en genomsnittlig svarstid per timme som överstiger +3σ (tre standardavvikelser) över populationsmedelvärdet.

Important

För korthet implementerar det här skriptet inte skydd mot låg varians, hanterar inte partiella saknade data, skiljer inte efter modell eller inbyggd programvara osv. Var noga med att bedöma och förlita dig inte enbart på det här skriptet för att avgöra om du vill ersätta en hårddisk. Det presenteras här endast i utbildningssyfte.

Screenshot

I skärmbilden nedan ser vi att det inte finns några extremvärden:

Skärmbild som visar att det inte finns några extremvärden.

Så här fungerar det

Först utesluter vi inaktiva eller nästan inaktiva enheter genom att kontrollera att PhysicalDisk.Iops.Total är konsekvent -Gt 1. För varje aktiv hårddisk överför vi dess LastHour tidsram, som består av 360 mätningar med 10 sekunders intervaller, till Measure-Object -Average för att erhålla dess genomsnittliga svarstid under den senaste timmen. Detta etablerar vår population.

Vi implementerar den allmänt kända formeln för att hitta populationens medelvärde μ och standardavvikelse σ . För varje aktiv HDD jämför vi dess genomsnittliga svarstid med populationsgenomsnittet och dividerar med standardavvikelsen. Vi behåller de råa värdena, så att vi kan Sort-Object våra resultat, och använd Format-Latency och Format-StandardDeviation hjälpfunktioner för att försköna vad vi ska visa – definitivt valfritt.

Om någon enhet är mer än +3σ, vi Write-Host i rött; om inte, i grönt.

Script

Här är skriptet:

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."
}

Exempel 3: Bullrig granne? Det är skrivet!

Prestandahistorik kan också svara på frågor om just nu. Nya mått är tillgängliga i realtid, var 10:e sekund. Det här exemplet använder VHD.Iops.Total serien från tidsramen MostRecent för att identifiera de mest trafikerade (vissa kanske säger "högljudda") virtuella datorer som förbrukar mest lagrings-IOPS, på varje värd i klustret och visar läs-/skrivuppdelningen av deras aktivitet.

Screenshot

I skärmbilden nedan ser vi de 10 främsta virtuella datorerna efter lagringsaktivitet:

Skärmbild som visar de 10 främsta virtuella datorerna efter lagringsaktivitet.

Så här fungerar det

Till skillnad från Get-PhysicalDiskär cmdleten Get-VM inte klustermedveten – den returnerar bara virtuella datorer på den lokala servern. Om du vill fråga från varje server parallellt omsluter vi anropet i Invoke-Command (Get-ClusterNode).Name { ... }. För varje virtuell dator får vi måtten VHD.Iops.Total, VHD.Iops.Readoch VHD.Iops.Write . Genom att inte ange parametern -TimeFrame får vi den MostRecent enskilda datapunkten för var och en.

Tip

Dessa serier återspeglar summan av den här virtuella maskinens aktivitet för alla dess VHD/VHDX-filer. Det här är ett exempel där prestandahistoriken aggregeras automatiskt åt oss. För att få uppdelningen per VHD/VHDX kan du skicka en enskild person Get-VHD till Get-ClusterPerf i stället för den virtuella datorn.

Resultaten från varje server sammanställs som $Output, vilket vi kan Sort-Object och sedan Select-Object -First 10. Observera att Invoke-Command dekorerar resultat med en PsComputerName egenskap som anger var de kom ifrån, som vi kan skriva ut för att veta var den virtuella datorn körs.

Script

Här är skriptet:

$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

Exempel 4: Som man säger, "25-gig är det nya 10-gig"

Det här exemplet använder NetAdapter.Bandwidth.Total serien från tidsramen LastDay för att leta efter tecken på nätverksmättnad, definierad som >90% teoretisk maximal bandbredd. För varje nätverkskort i klustret jämförs den högsta observerade bandbreddsanvändningen under den senaste dagen med den angivna länkhastigheten.

Screenshot

I skärmbilden nedan ser vi att en Fabrikam NX-4 Pro #2 nådde sin topp den senaste dagen:

Skärmbild som visar att Fabrikam NX-4 Pro #2 nådde sin topp den senaste dagen.

Så här fungerar det

Vi upprepar vårt Invoke-Command trick från ovan till Get-NetAdapter på varje server och rör till Get-ClusterPerf. Längs vägen tar vi två relevanta egenskaper: dess LinkSpeed sträng som "10 Gbit/s" och dess råa Speed heltal som 10000000000. Vi använder Measure-Object för att hämta medelvärdet och toppen från den senaste dagen (påminnelse: varje mätning inom tidsramen LastDay representerar 5 minuter) och multiplicerar med 8 bitar per byte för att få en jämförelse mellan äpplen och äpplen.

Note

Vissa leverantörer, till exempel Chelsio, inkluderar fjärrdirigering av minnesåtkomst (RDMA) i sina prestandaräknare för nätverkskort , så det ingår i NetAdapter.Bandwidth.Total serien. Andra, som Mellanox, kanske inte. Om leverantören inte gör det lägger du helt enkelt till NetAdapter.Bandwidth.RDMA.Total serien i din version av det här skriptet.

Script

Här är skriptet:

$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

Exempel 5: Gör lagringen trendig igen!

Om du vill titta på makrotrender behålls prestandahistoriken i upp till 1 år. Det här exemplet använder Volume.Size.Available serien från tidsramen LastYear för att fastställa den hastighet som lagringen fyller på och beräkna när den är full.

Screenshot

I skärmbilden nedan ser vi att säkerhetskopieringsvolymen lägger till cirka 15 GB per dag:

Skärmbild som visar att säkerhetskopieringsvolymen lägger till cirka 15 GB per dag.

I den här takten når den sin kapacitet om ytterligare 42 dagar.

Så här fungerar det

Tidsramen LastYear har en datapunkt per dag. Även om du bara behöver två punkter för att passa en trendlinje, är det i praktiken bättre att kräva mer, till exempel 14 dagar. Vi använder Select-Object -Last 14 för att konfigurera en matris med (x, y) punkter, för x i intervallet [1, 14]. Med dessa punkter implementerar vi den enkla linjära algoritmen för minsta kvadrater för att hitta $A och $B som parameteriserar raden med bästa passform y = ax + b. Välkommen till gymnasiet igen.

Genom att dividera volymens SizeRemaining egenskap med trenden (lutningen $A) kan vi grovt uppskatta hur många dagar, med den aktuella lagringstillväxten, tills volymen är full. Hjälpfunktionerna Format-Bytes, Format-Trendoch Format-Days förskönar utdata.

Important

Den här uppskattningen är linjär och baseras endast på de senaste 14 dagliga mätningarna. Det finns mer avancerade och exakta tekniker. Var god bedömning och förlita dig inte enbart på det här skriptet för att avgöra om du ska investera i att utöka lagringen. Det presenteras här endast i utbildningssyfte.

Script

Här är skriptet:


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

Exempel 6: Minneskrävande, du kan springa men du kan inte gömma dig

Eftersom prestandahistorik samlas in och lagras centralt för hela klustret behöver du aldrig sammanfoga data från olika datorer, oavsett hur många gånger virtuella datorer flyttas mellan värdar. Det här exemplet använder VM.Memory.Assigned serien från tidsramen LastMonth för att identifiera de virtuella datorer som förbrukar mest minne under de senaste 35 dagarna.

Screenshot

I skärmbilden nedan ser vi de 10 främsta virtuella datorerna efter minnesanvändning förra månaden:

Skärmbild av PowerShell

Så här fungerar det

Vi upprepar vårt Invoke-Command trick, som introducerades ovan, till Get-VM på varje server. Vi använder Measure-Object -Average för att få månadsgenomsnittet för varje virtuell dator, följt Sort-Object av Select-Object -First 10 för att få vår rankningslista. (Eller kanske är det vår mest önskade lista?)

Script

Här är skriptet:

$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

Det var allt! Förhoppningsvis inspirerar dessa exempel dig och hjälper dig att komma igång. Med lagringsdirigeringsprestandahistorik och den kraftfulla, skriptvänliga Get-ClusterPerf cmdleten har du möjlighet att fråga – och svara! – komplexa frågor när du hanterar och övervakar din Windows Server 2019-infrastruktur.

Ytterligare referenser