Scripts com o PowerShell e o histórico de desempenho dos Espaços de Armazenamento Diretos
No Windows Server 2019, os Espaços de Armazenamento Diretos registram e armazenam um extenso histórico de desempenho para máquinas virtuais, servidores, unidades, volumes, adaptadores de rede e muito mais. O histórico de desempenho é fácil de consultar e processar no PowerShell para que você possa ir rapidamente de dados brutos para respostas reais para perguntas como:
- Houve algum pico de uso de CPU na semana passada?
- Algum disco físico está apresentando latência anormal?
- Quais VMs estão consumindo mais IOPS de armazenamento no momento?
- Minha largura de banda de rede está saturada?
- Quando esse volume ficará sem espaço livre?
- No mês passado, quais VMs usaram mais memória?
O cmdlet Get-ClusterPerf
é criado para scripts. Ele aceita entrada de cmdlets como Get-VM
ou Get-PhysicalDisk
pelo pipeline para lidar com a associação, e você pode redirecionar sua saída para cmdlets utilitários como Sort-Object
, Where-Object
e Measure-Object
para compor rapidamente consultas avançadas.
Este tópico fornece e explica seis scripts de exemplo que respondem às seis perguntas acima. Eles apresentam padrões que você pode aplicar para localizar picos, encontrar médias, plotar linhas de tendência, detectar exceções e muito mais, em uma variedade de dados e períodos. Eles são fornecidos como código inicial gratuito para você copiar, estender e reutilizar.
Observação
Para serem breves, os scripts de exemplo omitem coisas como tratamento de erros, que você pode esperar de códigos do PowerShell de alta qualidade. Eles se destinam principalmente à inspiração e à educação, e não ao uso em produção.
Exemplo 1: CPU, vejo você!
Este exemplo usa a série ClusterNode.Cpu.Usage
do período LastWeek
para mostrar o uso máximo ("marca d'água alta"), o uso mínimo e o uso médio da CPU para cada servidor no cluster. Ele também faz uma análise de quartil simples para mostrar quantas horas o uso da CPU foi superior a 25%, 50% e 75% nos últimos 8 dias.
Captura de tela
Na captura de tela abaixo, vemos que Server-02 teve um pico inexplicável na semana passada:
Como ele funciona
A saída de Get-ClusterPerf
é direcionada para o cmdlet interno Measure-Object
, nós apenas especificamos a propriedade Value
. Com os sinalizadores -Maximum
, -Minimum
e -Average
, Measure-Object
nos dá as três primeiras colunas quase de graça. Para fazer a análise de quartil, podemos direcionar para Where-Object
e contar quantos valores foram -Gt
(maiores que) 25, 50 ou 75. A última etapa é embelezar com as funções auxiliares Format-Hours
e Format-Percent
, certamente opcionais.
Script
Este é o 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
Exemplo 2: Fogo, fogo, exceção de latência
Este exemplo usa a série PhysicalDisk.Latency.Average
do período LastHour
para procurar exceções estatísticas, definidas como unidades com latência média por hora superior a +3σ (três desvios padrão) acima da média populacional.
Importante
Para resumir, esse script não implementa proteções contra baixa variação, não manipula dados parciais ausentes, não distingue por modelo ou firmware etc. Exerça um bom julgamento e não dependa apenas do script para determinar se um disco rígido deve ser substituído. Ele é apresentado aqui apenas para fins educacionais.
Captura de tela
Na captura de tela abaixo, vemos que não há exceções:
Como ele funciona
Primeiro, excluímos unidades ociosas ou quase ociosas verificando se PhysicalDisk.Iops.Total
é consistentemente -Gt 1
. Para cada HDD ativo, direcionamos o período LastHour
, composto por 360 medidas em intervalos de 10 segundos, para Measure-Object -Average
para obter a latência média na última hora. Isso configura nossa população.
Implementamos a fórmula amplamente conhecida para encontrar a média μ
e o desvio padrão σ
da população. Para cada HDD ativo, comparamos a latência média com a média populacional e dividimos pelo desvio padrão. Mantemos os valores brutos, para que possamos Sort-Object
os resultados, mas usamos as funções auxiliares Format-Latency
e Format-StandardDeviation
para embelezar o que mostraremos – certamente opcional.
Se qualquer unidade for maior que +3σ, nós Write-Host
vermelho; caso contrário, em verde.
Script
Este é o 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."
}
Exemplo 3: Vizinho barulhento? É uma gravação!
O histórico de desempenho também pode responder a perguntas sobre agora. Novas medidas ficam disponíveis em tempo real, a cada 10 segundos. Este exemplo usa a série VHD.Iops.Total
do período MostRecent
para identificar as máquinas virtuais mais movimentadas (alguns podem dizer "mais barulhentas") que consomem mais IOPS de armazenamento, em todos os hosts do cluster, e mostrar o detalhamento de leitura/gravação de suas atividades.
Captura de tela
Na captura de tela abaixo, vemos as 10 principais máquinas virtuais por atividade de armazenamento:
Como ele funciona
Diferente de Get-PhysicalDisk
, o cmdlet Get-VM
não tem reconhecimento de cluster – ele retorna apenas VMs no servidor local. Para consultar de todos os servidores em paralelo, encapsulamos a chamada em Invoke-Command (Get-ClusterNode).Name { ... }
. Para cada VM, obtemos as medidas VHD.Iops.Total
, VHD.Iops.Read
e VHD.Iops.Write
. Ao não especificar o parâmetro -TimeFrame
, obtemos o ponto de dados único MostRecent
para cada um.
Dica
Essas séries refletem a soma da atividade dessa VM a todos os seus arquivos VHD/VHDX. Este é um exemplo em que o histórico de desempenho está sendo agregado automaticamente para nós. Para obter o detalhamento por VHD/VHDX, você pode direcionar um Get-VHD
individual a Get-ClusterPerf
em vez da VM.
Os resultados de cada servidor se reúnem como $Output
, o que podemos Sort-Object
e, em seguida, Select-Object -First 10
. Observe que Invoke-Command
decora resultados com uma propriedade PsComputerName
que indica de onde eles vieram, que podemos imprimir para saber onde a VM está em execução.
Script
Este é o 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
Exemplo 4: Como dizem, "25 gig é o novo 10 gig"
Este exemplo usa a série NetAdapter.Bandwidth.Total
do período LastDay
para procurar sinais de saturação de rede, definidos como >90% da largura de banda máxima teórica. Para cada adaptador de rede no cluster, ele compara o maior uso de largura de banda observado no último dia com sua velocidade de link declarada.
Captura de tela
Na captura de tela abaixo, vemos que Fabrikam NX-4 Pro #2 atingiu o pico no último dia:
Como ele funciona
Repetimos nosso truque Invoke-Command
acima para Get-NetAdapter
em cada servidor e direcionamos para Get-ClusterPerf
. Ao longo do caminho, pegamos duas propriedades relevantes: sua cadeia de caracteres LinkSpeed
como "10 Gbps" e seu inteiro bruto Speed
como 10000000000. Usamos Measure-Object
para obter a média e o pico do último dia (lembrete: cada medida no período LastDay
representa 5 minutos) e multiplicar por 8 bits por byte para obter uma comparação entre maçãs e maçãs.
Observação
Alguns fornecedores, como a Chelsio, incluem atividade RDMA (acesso à memória direta remota) em seus contadores de desempenho do Adaptador de Rede, portanto, isso está incluído na série NetAdapter.Bandwidth.Total
. Outros, como a Mellanox, podem não incluir. Se o seu fornecedor não fizer incluir, basta adicionar a série NetAdapter.Bandwidth.RDMA.Total
à sua versão desse script.
Script
Este é o 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
Exemplo 5: Fazer do armazenamento uma tendência novamente!
Para examinar tendências de macro, o histórico de desempenho é mantido por até 1 ano. Este exemplo usa a série Volume.Size.Available
do período LastYear
para determinar a taxa em que o armazenamento está sendo preenchido e estimar quando ele estará cheio.
Captura de tela
Na captura de tela abaixo, vemos que o volume de Backup está adicionando cerca de 15 GB por dia:
Nesse ritmo, ele atingirá sua capacidade em mais 42 dias.
Como ele funciona
O período LastYear
tem um ponto de dados por dia. Embora você só precise estritamente de dois pontos para traçar uma linha de tendência, na prática é melhor exigir mais, como 14 dias. Usamos Select-Object -Last 14
para configurar uma matriz de pontos (x, y), para x no intervalo [1, 14]. Com esses pontos, implementamos o algoritmo de quadrados mínimos lineares simples para localizar $A
e $B
, que parametrizam a linha de melhor ajuste y = ax + b. Bem-vindo ao ensino médio de novo.
Dividir a propriedade SizeRemaining
do volume pela tendência (a inclinação $A
) nos permite estimar grosseiramente quantos dias, na taxa atual de crescimento do armazenamento, até que o volume esteja cheio. As funções auxiliares Format-Bytes
, Format-Trend
e Format-Days
embelezam a saída.
Importante
Essa estimativa é linear e baseada apenas nas 14 medidas diárias mais recentes. Existem técnicas mais sofisticadas e precisas. Exerça bom julgamento e não dependa apenas desse script para determinar se deseja investir na expansão do armazenamento. Ele é apresentado aqui apenas para fins educacionais.
Script
Este é o 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
Exemplo 6: Hog de memória, você pode correr, mas não pode se esconder
Como o histórico de desempenho é coletado e armazenado centralmente para todo o cluster, você nunca precisa unir dados de computadores diferentes, independentemente de quantas vezes as VMs se movem entre hosts. Este exemplo usa a série VM.Memory.Assigned
do período LastMonth
para identificar as máquinas virtuais que consumiram mais memória nos últimos 35 dias.
Captura de tela
Na captura de tela abaixo, vemos as 10 principais máquinas virtuais por uso de memória no último mês:
Como ele funciona
Repetimos nosso truque Invoke-Command
, introduzido acima, em Get-VM
em todos os servidores. Usamos Measure-Object -Average
para obter a média mensal para cada VM, em seguida Sort-Object
, seguido por Select-Object -First 10
para obter nosso placar de líderes. (Ou talvez seja nossa lista dos Mais procurados?)
Script
Este é o 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
É isso! Esperamos que esses exemplos o inspirem e o ajudem a começar. Com o histórico de desempenho dos Espaços de Armazenamento Diretos e o poderoso cmdlet amigável para scripts Get-ClusterPerf
, você tem o poder de fazer – e responder! – perguntas complexas à medida que gerencia e monitora sua infraestrutura do Windows Server 2019.