Поделиться через


Создание скриптов с помощью PowerShell и журнала производительности Локальные дисковые пространства

В Windows Server 2019 Хранилище Дисковых Пространств Direct записывает и хранит историю производительности виртуальных машин, серверов, дисков, томов, сетевых адаптеров и многого другого. Performance history is easy to query and process in PowerShell so you can quickly go from raw data to actual answers to questions like:

  1. Были ли какие-либо пики ЦП на прошлой неделе?
  2. Является ли любой физический диск ненормальным задержкой?
  3. Какие виртуальные машины используют больше всего операций ввода-вывода в секунду?
  4. Перегружена ли пропускная способность сети?
  5. Когда этот том не будет свободного места?
  6. В прошлом месяце, какие виртуальные машины использовали большую память?

Командлет Get-ClusterPerf создан для создания скриптов. Он принимает входные данные из командлетов, таких как Get-VM или Get-PhysicalDisk конвейер для обработки связи, и вы можете передать выходные данные в командлеты служебной программы, например Sort-Object, Where-Objectи Measure-Object быстро создавать мощные запросы.

В этом разделе содержатся и описаны 6 примеров скриптов, которые отвечают на 6 приведенных выше вопросов. Они представляют собой шаблоны, которые можно применять для поиска пиков, поиска средних значений, линий тренда графиков, обнаружения перебежки и многое другое в различных данных и временных интервалах. Они предоставляются как бесплатный начальный код для копирования, расширения и повторного использования.

Note

Для краткости примеры скриптов опустят такие вещи, как обработка ошибок, которые могут ожидать высококачественного кода PowerShell. Они предназначены в первую очередь для вдохновения и образования, а не для производства.

Пример 1. ЦП, я вижу вас!

В этом примере используется ClusterNode.Cpu.Usage ряд из LastWeek временных интервалов для отображения максимального ("высокой водяной отметки"), минимального и среднего использования ЦП для каждого сервера в кластере. Он также выполняет простой анализ квартили, чтобы показать, сколько часов использования ЦП было более 25%, 50%, и 75% за последние 8 дней.

Screenshot

In the screenshot below, we see that Server-02 had an unexplained spike last week:

Снимок экрана, показывающий, что на сервере-02 на прошлой неделе наблюдался необъяснимый всплеск.

Принцип работы

Выходные данные из Get-ClusterPerf каналов хорошо в встроенный Measure-Object командлет мы просто указываем Value это свойство. С его -Maximum, -Minimumи -Average флаги, Measure-Object дает нам первые три столбца почти бесплатно. Для анализа квартиля можно передать Where-Object и подсчитать, сколько значений было -Gt (больше) 25, 50 или 75. Последний шаг заключается в том, чтобы перенастраивать функции и Format-HoursFormat-Percent вспомогательные функции , безусловно, необязательным.

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

Пример 2. Пожар, пожар, задержка

В этом примере используется PhysicalDisk.Latency.Average ряд из LastHour временных интервалов для поиска статистических вылитий, определенных как диски с почасовой средней задержкой, превышающей +3σ (три стандартных отклонения), превышающих среднее число населения.

Important

Для краткости этот сценарий не реализует меры защиты от низкой дисперсии, не обрабатывает частичные отсутствующие данные, не отличается от модели или встроенного ПО и т. д. Пожалуйста, выполните хорошее решение и не полагаться только на этот сценарий, чтобы определить, следует ли заменить жесткий диск. Здесь представлено только для образовательных целей.

Screenshot

На снимке экрана ниже мы видим, что нет выскользов:

Снимок экрана, на котором показано отсутствие выбивающихся значений.

Принцип работы

Во-первых, мы исключим бездействующие или почти бездействующие диски, проверяя их PhysicalDisk.Iops.Total согласованно -Gt 1. Для каждого активного HDD мы передаем его LastHour интервал времени, состоящий из 360 измерений в 10 секунд, чтобы Measure-Object -Average получить среднюю задержку за последний час. Это настраивает наше население.

We implement the widely-known formula to find the mean μ and standard deviation σ of the population. Для каждого активного HDD мы сравниваем среднюю задержку с средней задержкой населения и разделим на стандартное отклонение. Мы сохраняем необработанные значения, чтобы мы могли Sort-Object получать результаты, но использовать Format-Latency и Format-StandardDeviation вспомогательные функции для того, чтобы продемонстрировать то, что мы показываем, безусловно, необязательным.

Если любой диск больше 3σ, мы Write-Host в красном цвете, если нет, в зеленом.

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

Пример 3. Шумный сосед? That's write!

Performance history can answer questions about right now, too. Новые измерения доступны в режиме реального времени каждые 10 секунд. В этом примере используется VHD.Iops.Total ряд из MostRecent временных интервалов, чтобы определить наиболее загруженные виртуальные машины (некоторые могут сказать"шумно") виртуальные машины, потребляющие большую часть операций ввода-вывода в секунду на каждом узле кластера, и показать разбивку операций чтения и записи.

Screenshot

На снимке экрана ниже мы видим первые 10 виртуальных машин по активности хранилища:

Снимок экрана: первые 10 виртуальных машин по активности хранилища.

Принцип работы

Get-PhysicalDisk В отличие от Get-VMэтого командлет не учитывает кластер, он возвращает только виртуальные машины на локальном сервере. Для параллельного запроса с каждого сервера мы заключим вызов Invoke-Command (Get-ClusterNode).Name { ... }. Для каждой виртуальной VHD.Iops.Totalмашины мы получаем и VHD.Iops.ReadVHD.Iops.Write измерения. Не указывая -TimeFrame параметр, мы получаем одну точку данных для каждой MostRecent .

Tip

Эти ряды отражают сумму действия этой виртуальной машины ко всем файлам VHD/VHDX. Это пример, в котором журнал производительности автоматически агрегируется для нас. Чтобы получить разбивку на виртуальный жесткий диск или VHDX, можно передать отдельный объект Get-VHDGet-ClusterPerf вместо виртуальной машины.

Результаты каждого сервера объединяются как $Output, что мы можем Sort-Object , а затем Select-Object -First 10. Обратите внимание, что Invoke-Command украшает результаты свойством PsComputerName , указывающим, откуда они были получены, чтобы узнать, откуда выполняется виртуальная машина.

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

Пример 4. Как говорится, "25-gig является новым 10-gig"

В этом примере используется NetAdapter.Bandwidth.Total ряд из LastDay временных интервалов для поиска признаков насыщенности сети, определенных как >90 % от теоретических максимальных пропускной способности. Для каждого сетевого адаптера в кластере он сравнивает наибольшее наблюдаемое использование пропускной способности за последний день с указанной скоростью канала.

Screenshot

На снимке экрана ниже мы видим, что один Fabrikam NX-4 Pro #2 достиг пика в последний день:

Снимок экрана, на котором показано, что Fabrikam NX-4 Pro #2 достиг пика за последний день.

Принцип работы

Мы повторяем наш Invoke-Command трюк выше на Get-NetAdapter каждом сервере и канале в Get-ClusterPerf. По пути мы захватим два важных свойства: его LinkSpeed строку, как "10 Гбит/с", и его необработанное Speed целое число, как 1000000000000. Мы используем Measure-Object для получения среднего и пикового показателя с последнего дня (напоминание: каждое измерение в LastDay интервале времени представляет 5 минут) и умножаем на 8 бит на байт, чтобы получить сравнение яблок с яблоками в яблоки.

Note

Some vendors, like Chelsio, include remote-direct memory access (RDMA) activity in their Network Adapter performance counters, so it's included in the NetAdapter.Bandwidth.Total series. Другие, как Мелланокс, могут не. Если поставщик не используется, просто добавьте ряд в NetAdapter.Bandwidth.RDMA.Total версию этого скрипта.

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

Пример 5. Сделать хранилище модным снова!

Для просмотра тенденций макросов журнал производительности сохраняется до 1 года. В этом примере используется Volume.Size.Available ряд из интервала LastYear времени, чтобы определить частоту заполнения хранилища и оценить, когда она будет заполнена.

Screenshot

In the screenshot below, we see the Backup volume is adding about 15 GB per day:

Снимок экрана показывает, что том резервного копирования добавляет около 15 ГБ в день.

На этом уровне он достигнет своей емкости в еще 42 дня.

Принцип работы

Интервал LastYear времени содержит одну точку данных в день. Хотя вам нужно только два пункта, чтобы соответствовать линии тренда, на практике лучше требовать больше, как 14 дней. We use Select-Object -Last 14 to set up an array of (x, y) points, for x in the range [1, 14]. С этими точками мы реализуем простой алгоритм линейных наименьших квадратов, чтобы найти $A и $B, чтобы параметризовать линию наилучшего соответствия y = ax + b. Добро пожаловать в среднюю школу снова.

Деление свойства тома SizeRemaining на тенденцию (наклон $A) позволяет нам грубо оценить, сколько дней, по текущей скорости роста хранилища, пока объем не будет заполнен. Функции Format-Bytes, Format-Trendа Format-Days также вспомогательные функции окупают выходные данные.

Important

Эта оценка является линейной и основана только на последних 14 ежедневных измерениях. Существуют более сложные и точные методы. Пожалуйста, выполните хорошее решение и не полагаться только на этот сценарий, чтобы определить, следует ли инвестировать в расширение хранилища. Здесь представлено только для образовательных целей.

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

Пример 6. Перехват памяти можно запустить, но не удается скрыть

Так как журнал производительности собирается и хранится централизованно для всего кластера, вам никогда не нужно объединять данные из разных компьютеров, независимо от того, сколько раз виртуальные машины перемещаются между узлами. В этом примере используется VM.Memory.Assigned ряд из LastMonth интервала времени для идентификации виртуальных машин, потребляющих большую память за последние 35 дней.

Screenshot

На снимке экрана ниже мы видим первые 10 виртуальных машин по использованию памяти в прошлом месяце:

Снимок экрана: PowerShell

Принцип работы

Мы повторяем наш Invoke-Command трюк, представленный выше, на Get-VM каждом сервере. Мы используем Measure-Object -Average для получения ежемесячного среднего показателя для каждой виртуальной машины, а затем Sort-ObjectSelect-Object -First 10 для получения нашей таблицы лидеров. (Or maybe it's our Most Wanted list?)

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

That's it! Надеюсь, эти примеры вдохновляют вас и помогут вам приступить к работе. С помощью Локальные дисковые пространства журнала производительности и мощного, понятного Get-ClusterPerf для сценариев командлета, вы можете попросить и ответить! — сложные вопросы, связанные с управлением и мониторингом инфраструктуры Windows Server 2019.

Additional References