使用 PowerShell 和儲存空間直接存取效能歷程記錄編寫腳本

適用於︰Windows Server 2022、Windows Server 2019

在 Windows Server 2019 中, 儲存空間直接存取 記錄並儲存虛擬機器、伺服器、磁片磁碟機、磁片區、網路介面卡等的廣泛 效能歷程記錄 。 效能歷程記錄很容易在 PowerShell 中查詢和處理,因此您可以快速地從 原始資料 移至 實際解答 ,例如:

  1. 上周是否有任何 CPU 尖峰?
  2. 是否有任何實體磁片表現出異常延遲?
  3. 哪些 VM 目前會耗用最多的儲存體 IOPS?
  4. 我的網路頻寬是否飽和?
  5. 此磁片區何時會用盡可用空間?
  6. 在過去一個月中,哪些 VM 使用最多的記憶體?

Cmdlet Get-ClusterPerf 是針對腳本所建置。 它接受類似 Get-VMGet-PhysicalDisk 管線的 Cmdlet 輸入來處理關聯,而且您可以將輸出管線傳送至 、 Where-ObjectSort-Object 公用程式 Cmdlet,並 Measure-Object 快速撰寫功能強大的查詢。

本主題提供並說明 6 個回答上述 6 個問題的範例腳本。 這些模式可讓您套用到尋找尖峰、尋找平均值、繪製趨勢線、執行極端值偵測等各種資料和時間範圍。 它們會以免費入門程式碼的形式提供,讓您複製、擴充和重複使用。

注意

為了簡潔起見,範例腳本會省略您可能預期高品質 PowerShell 程式碼的錯誤處理等事項。 它們主要是用於靈感和教育,而不是生產用途。

範例 1:CPU,我看見你!

此範例會 ClusterNode.Cpu.Usage 使用時間範圍中的 LastWeek 數列來顯示叢集中每部伺服器的最大(「高水位線」)、最小和平均 CPU 使用量。 它也會進行簡單的四分位數分析,以顯示過去 8 天內 CPU 使用量超過 25%、50% 和 75% 的時數。

螢幕擷取畫面

在下列螢幕擷取畫面中,我們看到 Server-02 上周出現無法解釋的尖峰:

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

運作方式

Get-ClusterPerf 管道順利輸出到內 Measure-Object 建 Cmdlet 中,我們只指定 Value 屬性。 透過它的 -Maximum-Minimum-Average 旗標, Measure-Object 我們幾乎免費提供前三個數據行。 若要進行四分位數分析,我們可以使用管線傳送至 Where-Object 和計算有多少值 -Gt (大於) 25、50 或 75。 最後一個步驟是美化 和 Format-HoursFormat-Percent 協助程式函式 – 當然是選擇性的。

指令碼

以下是腳本:

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

範例 2:引發、引發、延遲極端值

此範例會 PhysicalDisk.Latency.Average 使用時間範圍中的 LastHour 數列來尋找統計極端值,定義為每小時平均延遲超過 +3σ(三個標準差)高於母體平均值的磁片磁碟機。

重要

為了簡潔起見,此腳本不會針對低變異數實作保護,不會處理部分遺漏的資料,也不會區分模型或韌體等。請練習良好的判斷,不要只依賴此腳本來判斷是否要更換硬碟。 這裡僅供教育之用。

螢幕擷取畫面

在下列螢幕擷取畫面中,我們看到沒有極端值:

Screenshot that shows there are no outliers.

運作方式

首先,我們會檢查是否 PhysicalDisk.Iops.Total 一致 -Gt 1 地排除閒置或幾乎閒置的磁片磁碟機。 針對每個作用中的 HDD,我們會使用管線傳送時間 LastHour 範圍,其時間範圍由 10 秒間隔的 360 個測量組成,以 Measure-Object -Average 取得其過去一小時內的平均延遲。 這會設定我們的人口。

我們會實作 廣為人知的公式 ,以找出母體的平均 μ 和標準差 σ 。 對於每個作用中的 HDD,我們會比較其平均延遲與母體平均,並除以標準差。 我們會保留原始值,因此我們可以 Sort-Object 取得結果,但使用 Format-LatencyFormat-StandardDeviation 協助程式函式來美化我們將顯示的內容 – 當然是選擇性的。

如果有任何磁片磁碟機超過 +3σ,我們會 Write-Host 以紅色表示;如果不是,則以綠色表示。

指令碼

以下是腳本:

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

範例 3:嘈雜的鄰居? 這就是寫的!

效能歷程記錄也可以回答有關目前 的問題 。 每 10 秒即可即時取得新的度量。 此範例會 VHD.Iops.TotalMostRecent 時間範圍使用數列來識別最忙碌的虛擬機器,其中有些虛擬機器會耗用叢集中每個主機的最大儲存體 IOPS,並顯示其活動的讀取/寫入明細。

螢幕擷取畫面

在下列螢幕擷取畫面中,我們依儲存體活動看到前 10 部虛擬機器:

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

運作方式

與 不同的 Get-PhysicalDiskGet-VM ,Cmdlet 不是叢集感知,它只會傳回本機伺服器上的 VM。 若要平行查詢每個伺服器,我們會在 中 Invoke-Command (Get-ClusterNode).Name { ... } 包裝呼叫。 針對每個 VM,我們會取得 VHD.Iops.TotalVHD.Iops.ReadVHD.Iops.Write 度量。 如果未指定 -TimeFrame 參數,我們會取得 MostRecent 每個資料點的單一資料點。

提示

這些系列會反映此 VM 活動的所有 VHD/VHDX 檔案的總和。 這是自動匯總效能歷程記錄的範例。 若要取得每個 VHD/VHDX 明細,您可以將個人 Get-VHD 管線傳送到 Get-ClusterPerf ,而不是 VM。

每部伺服器的結果會以 的形式結合在一起 $OutputSort-Object 我們可以和之後 Select-Object -First 10 。 請注意, Invoke-Command 使用屬性來裝飾結果 PsComputerName ,指出其來自何處,我們可以列印以知道 VM 的執行位置。

指令碼

以下是腳本:

$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

範例 4:正如他們所說,「25 gig 是新的 10 gig」

此範例會 NetAdapter.Bandwidth.Total 使用時間範圍中的 LastDay 數列來尋找網路飽和的跡象,定義為 > 理論最大頻寬的 90%。 對於叢集中的每個網路介面卡,它會比較過去一天觀察到的最高頻寬使用量與其所陳述的連結速度。

螢幕擷取畫面

在下列螢幕擷取畫面中,我們看到前一天有一個 Fabrikam NX-4 Pro #2 達到尖峰:

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

運作方式

我們會在每個伺服器上重複上述 Invoke-Command 的技巧, Get-NetAdapter 並將管線傳送至 Get-ClusterPerf 。 一路上,我們會擷取兩個相關屬性:其 LinkSpeed 字串,例如 「10 Gbps」,以及其原始 Speed 整數,例如 10000000000。 我們用來 Measure-Object 從最後一天取得平均值和尖峰(提醒:時間範圍中的每個 LastDay 測量都代表 5 分鐘),並將每個位元組乘以 8 位,以取得蘋果對蘋果的比較。

注意

某些廠商,如 Chelsio,在其網路介面卡 效能計數器中包含 遠端直接記憶體存取 (RDMA) 活動,因此包含在系列中 NetAdapter.Bandwidth.Total 。 其他人,如梅蘭諾克斯,可能不會。 如果您的廠商不這樣做,只要在此腳本版本中新增 NetAdapter.Bandwidth.RDMA.Total 數列即可。

指令碼

以下是腳本:

$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

範例 5:再次讓儲存體變得時尚!

若要查看宏趨勢,效能歷程記錄會保留最多 1 年。 此範例會 Volume.Size.Available 使用時間範圍中的 LastYear 數列來判斷儲存體填滿的速率,並估計何時會滿。

螢幕擷取畫面

在下列螢幕擷取畫面中,我們看到 備份 磁片區每天新增約 15 GB:

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

以這個速度,它將在另一個42天內達到其容量。

運作方式

時間 LastYear 範圍每天有一個資料點。 雖然您只需要兩個點才能符合趨勢線,但實際上最好需要更多點,例如 14 天。 我們用來 Select-Object -Last 14 為範圍 [1, 14] 中的 x 設定 x 的陣列 (x, y) 。 透過這些點,我們實作簡單的 線性最小平方演算法 來尋找 $A ,並將 $B 最符合 y = ax + b 的線條參數化。 歡迎再次上高中。

將磁片 SizeRemaining 區的屬性除以趨勢(斜率 $A )可讓我們粗略估計儲存成長率的天數,直到磁片區滿為止。 Format-BytesFormat-TrendFormat-Days 協助程式函式會美化輸出。

重要

此估計值是線性的,且僅以最近的 14 天測量為基礎。 更複雜且精確的技術存在。 請執行良好的判斷,不要只依賴此腳本來判斷是否要投資擴充您的儲存體。 這裡僅供教育之用。

指令碼

以下是腳本:


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

範例 6:記憶體小豬,您可以執行,但無法隱藏

因為效能歷程記錄會針對整個叢集集中收集並儲存,所以不論 VM 在主機之間移動多少次,您都不需要將不同機器的資料合併在一起。 此範例會 VM.Memory.Assigned 使用時間範圍中的 LastMonth 數列來識別過去 35 天內耗用最多記憶體的虛擬機器。

螢幕擷取畫面

在下列螢幕擷取畫面中,我們看到上個月記憶體使用量的前 10 部虛擬機器:

Screenshot of PowerShell

運作方式

我們會在每個伺服器上重複上述 Invoke-Command 介紹的 Get-VM 技巧。 我們會使用 Measure-Object -Average 來取得每個 VM 的每月平均值,然後 Sort-Object 接著 Select-Object -First 10 取得排行榜。 (或者可能是我們的 最想要 的清單?

指令碼

以下是腳本:

$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

介紹完畢 希望這些範例能激勵您,並協助您開始使用。 透過儲存空間直接存取效能歷程記錄和功能強大的腳本易 Get-ClusterPerf 記 Cmdlet,您可以要求及回答! – 管理及監視 Windows Server 2019 基礎結構時的複雜問題。

其他參考