Share via


Scripting con PowerShell e cronologia delle prestazioni Spazi di archiviazione diretta

Si applica a: Windows Server 2022, Windows Server 2019

In Windows Server 2019, Spazi di archiviazione diretta registra e archivia una lunga cronologia delle prestazioni per macchine virtuali, server, unità, volumi, schede di rete e altro ancora. La cronologia delle prestazioni è facile da eseguire query ed elaborare in PowerShell, in modo da poter passare rapidamente dai dati non elaborati alle risposte effettive alle domande come:

  1. Ci sono stati picchi di CPU la scorsa settimana?
  2. Un disco fisico presenta una latenza anomala?
  3. Quali macchine virtuali usano attualmente la maggior parte delle operazioni di I/O al secondo di archiviazione?
  4. La larghezza di banda di rete è satura?
  5. Quando questo volume esaurisce lo spazio libero?
  6. Nell'ultimo mese, quali macchine virtuali hanno usato la maggior quantità di memoria?

Il Get-ClusterPerf cmdlet viene compilato per la creazione di script. Accetta input da cmdlet come Get-VM o Get-PhysicalDisk dalla pipeline per gestire l'associazione ed è possibile inviare tramite pipe l'output in cmdlet di utilità come Sort-Object, Where-Objecte Measure-Object per comporre rapidamente query avanzate.

Questo argomento fornisce e illustra 6 script di esempio che rispondono alle 6 domande precedenti. Presentano modelli che è possibile applicare per trovare picchi, trovare medie, linee di tendenza del tracciato, rilevare outlier e altro ancora, in un'ampia gamma di dati e intervalli di tempo. Vengono forniti come codice di avvio gratuito che consente di copiare, estendere e riutilizzare.

Nota

Per brevità, gli script di esempio omettono elementi come la gestione degli errori che si potrebbe aspettare di codice PowerShell di alta qualità. Sono destinati principalmente all'ispirazione e all'istruzione piuttosto che all'uso della produzione.

Esempio 1: CPU, ti vedo!

Questo esempio usa la ClusterNode.Cpu.Usage serie dell'intervallo LastWeek di tempo per visualizzare il massimo ("contrassegno di acqua elevato"), il minimo e l'utilizzo medio della CPU per ogni server del cluster. Esegue anche un'analisi quartile semplice per mostrare il numero di ore di utilizzo della CPU superiore al 25%, al 50% e al 75% negli ultimi 8 giorni.

Schermata acquisita

Nello screenshot seguente si noterà che Server-02 ha avuto un picco inspiegabile la settimana scorsa:

Screenshot that shows that Server-02 had an unexplained spike last week.

Funzionamento

L'output delle Get-ClusterPerf pipe nel cmdlet predefinito Measure-Object è sufficiente specificare la Value proprietà . Con i flag -Maximum, -Minimume -Average , Measure-Object ci fornisce le prime tre colonne quasi gratuitamente. Per eseguire l'analisi quartile, è possibile inviare tramite pipe e Where-Object contare il numero di valori -Gt maggiori di 25, 50 o 75. L'ultimo passaggio consiste nell'abbellire le Format-Hours funzioni helper e Format-Percent , certamente facoltativo.

Script

Ecco lo 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

Esempio 2: fuoco, incendio, latenza outlier

Questo esempio usa la PhysicalDisk.Latency.Average serie dell'intervallo LastHour di tempo per cercare outlier statistici, definiti come unità con una latenza media oraria superiore a +3σ (tre deviazioni standard) al di sopra della media della popolazione.

Importante

Per brevità, questo script non implementa misure di sicurezza contro la varianza bassa, non gestisce dati parziali mancanti, non distingue per modello o firmware e così via. Esercitare un buon giudizio e non fare affidamento solo su questo script per determinare se sostituire un disco rigido. Qui viene presentato solo a scopo educativo.

Schermata acquisita

Nello screenshot seguente non sono presenti outlier:

Screenshot that shows there are no outliers.

Funzionamento

Prima di tutto, si escludono unità inattive o quasi inattive controllando che PhysicalDisk.Iops.Total sia coerente.-Gt 1 Per ogni HDD attivo, si invia tramite pipe il relativo LastHour intervallo di tempo, costituito da 360 misurazioni a intervalli di 10 secondi, per Measure-Object -Average ottenere la latenza media nell'ultima ora. In questo modo la nostra popolazione viene impostata.

Implementiamo la formula ampiamente nota per trovare la deviazione σ media μ e standard della popolazione. Per ogni HDD attivo, la latenza media viene confrontata con la media della popolazione e divisa in base alla deviazione standard. Si mantengono i valori non elaborati, in modo da poter ottenere Sort-Object i risultati, ma usare Format-Latency e Format-StandardDeviation funzioni helper per abbellire ciò che verrà mostrato , certamente facoltativo.

Se un'unità è maggiore di +3σ, in Write-Host rosso; in caso contrario, in verde.

Script

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

Esempio 3: Vicino rumoroso? Questo è scrivere!

Anche la cronologia delle prestazioni può rispondere a domande sul momento. Le nuove misurazioni sono disponibili in tempo reale, ogni 10 secondi. In questo esempio viene usata la VHD.Iops.Total serie dall'intervallo MostRecent di tempo per identificare le macchine virtuali più trafficato (alcune potrebbero essere "più rumorose") che utilizzano la maggior parte delle operazioni di I/O al secondo di archiviazione, in ogni host del cluster e visualizzare la suddivisione in lettura/scrittura dell'attività.

Schermata acquisita

Nello screenshot seguente vengono visualizzate le prime 10 macchine virtuali per attività di archiviazione:

Screenshot that shows the Top 10 virtual machines by storage activity.

Funzionamento

A differenza di Get-PhysicalDisk, il Get-VM cmdlet non è compatibile con il cluster, ma restituisce solo le macchine virtuali nel server locale. Per eseguire query da ogni server in parallelo, eseguire il wrapping della chiamata in Invoke-Command (Get-ClusterNode).Name { ... }. Per ogni macchina virtuale si ottengono le VHD.Iops.Totalmisurazioni , VHD.Iops.Reade VHD.Iops.Write . Non specificando il -TimeFrame parametro, si ottiene il MostRecent singolo punto dati per ognuno di essi.

Suggerimento

Queste serie riflettono la somma dell'attività della macchina virtuale in tutti i relativi file VHD/VHDX. Questo è un esempio in cui la cronologia delle prestazioni viene aggregata automaticamente. Per ottenere la suddivisione per VHD/VHDX, è possibile inviare tramite pipe un singolo utente Get-VHD anziché Get-ClusterPerf la macchina virtuale.

I risultati di ogni server vengono combinati come $Output, che è possibile Sort-Object e quindi Select-Object -First 10. Si noti che Invoke-Command decora i risultati con una PsComputerName proprietà che indica da dove provengono, da cui è possibile stampare per sapere dove è in esecuzione la macchina virtuale.

Script

Ecco lo 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

Campione 4: Come dicono, "25-gig è il nuovo 10-gig"

Questo esempio usa la NetAdapter.Bandwidth.Total serie dell'intervallo LastDay di tempo per cercare segni di saturazione della rete, definiti come >il 90% della larghezza di banda massima teorica. Per ogni scheda di rete nel cluster, confronta l'utilizzo della larghezza di banda osservato più alto nell'ultimo giorno con la velocità di collegamento dichiarata.

Schermata acquisita

Nello screenshot seguente viene visualizzato un picco di Fabrikam NX-4 Pro #2 nell'ultimo giorno:

Screenshot that shows that Fabrikam NX-4 Pro #2 peaked in the last day.

Funzionamento

Ripetere il Invoke-Command trucco dall'alto a Get-NetAdapter in ogni server e inviare tramite pipe a Get-ClusterPerf. Lungo la strada si afferrano due proprietà rilevanti: la stringa LinkSpeed come "10 Gbps" e il relativo numero intero non elaborato Speed come 100000000000. Viene usato Measure-Object per ottenere la media e il picco dall'ultimo giorno (promemoria: ogni misura nell'intervallo LastDay di tempo rappresenta 5 minuti) e moltiplicare per 8 bit per byte per ottenere un confronto tra mele e mele.

Nota

Alcuni fornitori, ad esempio Chelsio, includono l'attività RDMA (Remote-Direct Memory Access) nei contatori delle prestazioni della scheda di rete, quindi è incluso nella NetAdapter.Bandwidth.Total serie. Altri, come Mellanox, potrebbero non essere. Se il fornitore non lo fa, è sufficiente aggiungere la NetAdapter.Bandwidth.RDMA.Total serie nella versione di questo script.

Script

Ecco lo 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

Esempio 5: Preparare di nuovo l'archiviazione alla moda!

Per esaminare le tendenze delle macro, la cronologia delle prestazioni viene mantenuta per un massimo di 1 anno. In questo esempio viene usata la Volume.Size.Available serie dall'intervallo LastYear di tempo per determinare la frequenza di riempimento dello spazio di archiviazione e la stima quando sarà piena.

Schermata acquisita

Nello screenshot seguente si noterà che il volume di backup aggiunge circa 15 GB al giorno:

Screenshot that shows that the Backup volume is adding about 15 GB per day.

A questa velocità, raggiungerà la sua capacità in altri 42 giorni.

Funzionamento

L'intervallo LastYear di tempo ha un punto dati al giorno. Anche se è necessario solo due punti per adattarsi a una linea di tendenza, in pratica è meglio richiedere di più, come 14 giorni. Viene usata Select-Object -Last 14 per impostare una matrice di punti (x, y), per x nell'intervallo [1, 14]. Con questi punti, implementiamo l'algoritmo semplice linear least squares per trovare $A e $B che parametrizza la linea di adattamento ottimale y = ax + b. Benvenuti di nuovo alla scuola superiore.

Dividendo la proprietà del SizeRemaining volume in base alla tendenza (pendenza $A) consente di stimare in modo crudele il numero di giorni, al tasso corrente di crescita dello spazio di archiviazione, fino a quando il volume non è pieno. Le Format-Bytesfunzioni helper , Format-Trende Format-Days semplificano l'output.

Importante

Questa stima è lineare e basata solo sulle misurazioni giornaliere più recenti di 14. Esistono tecniche più sofisticate e accurate. Esercitare un buon giudizio e non basarsi solo su questo script per determinare se investire nell'espansione dello spazio di archiviazione. Qui viene presentato solo a scopo educativo.

Script

Ecco lo 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

Esempio 6: Hog di memoria, è possibile eseguire ma non è possibile nascondere

Poiché la cronologia delle prestazioni viene raccolta e archiviata centralmente per l'intero cluster, non è mai necessario unire i dati di computer diversi, indipendentemente dal numero di volte in cui le macchine virtuali si spostano tra host. Questo esempio usa la VM.Memory.Assigned serie dall'intervallo LastMonth di tempo per identificare le macchine virtuali che consumano la maggior quantità di memoria negli ultimi 35 giorni.

Schermata acquisita

Nello screenshot seguente vengono visualizzate le prime 10 macchine virtuali in base all'utilizzo della memoria dell'ultimo mese:

Screenshot of PowerShell

Funzionamento

Ripetiamo il nostro Invoke-Command trucco, introdotto in precedenza, a Get-VM in ogni server. Si usa Measure-Object -Average per ottenere la media mensile per ogni macchina virtuale, quindi Sort-Object seguita da Select-Object -First 10 per ottenere il tabellone punteggi. (O forse è il nostro Elenco più desiderato ?)

Script

Ecco lo 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

Ecco fatto! Speriamo che questi esempi ti ispirano e ti aiutino a iniziare. Con Spazi di archiviazione diretta cronologia delle prestazioni e il potente cmdlet intuitivo Get-ClusterPerf per gli script, è possibile chiedere e rispondere. : domande complesse durante la gestione e il monitoraggio dell'infrastruttura di Windows Server 2019.

Riferimenti aggiuntivi