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:
- Ci sono stati picchi di CPU la scorsa settimana?
- Un disco fisico presenta una latenza anomala?
- Quali macchine virtuali usano attualmente la maggior parte delle operazioni di I/O al secondo di archiviazione?
- La larghezza di banda di rete è satura?
- Quando questo volume esaurisce lo spazio libero?
- 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-Object
e 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:
Funzionamento
L'output delle Get-ClusterPerf
pipe nel cmdlet predefinito Measure-Object
è sufficiente specificare la Value
proprietà . Con i flag -Maximum
, -Minimum
e -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:
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:
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.Total
misurazioni , VHD.Iops.Read
e 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:
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:
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-Bytes
funzioni helper , Format-Trend
e 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:
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.