Создание скриптов с помощью PowerShell и журнала производительности Локальные дисковые пространства
В Windows Server 2019 Локальные дисковые пространства записывают и хранятся обширные журналы производительности для виртуальных машин, серверов, дисков, томов, сетевых адаптеров и т. д. Журнал производительности легко запрашивать и обрабатывать в PowerShell, чтобы быстро перейти от необработанных данных к фактическим ответам на такие вопросы , как:
- Были ли какие-либо пики ЦП на прошлой неделе?
- Является ли любой физический диск ненормальным задержкой?
- Какие виртуальные машины используют больше всего операций ввода-вывода в секунду?
- Перегружена ли пропускная способность сети?
- Когда этот том не будет свободного места?
- В прошлом месяце, какие виртуальные машины использовали большую память?
Командлет Get-ClusterPerf
создан для создания скриптов. Он принимает входные данные из командлетов, таких как Get-VM
или Get-PhysicalDisk
конвейер для обработки связи, и вы можете передать выходные данные в командлеты служебной программы, например Sort-Object
, Where-Object
и Measure-Object
быстро создавать мощные запросы.
В этом разделе содержатся и описаны 6 примеров скриптов, которые отвечают на 6 приведенных выше вопросов. Они представляют собой шаблоны, которые можно применять для поиска пиков, поиска средних значений, линий тренда графиков, обнаружения перебежки и многое другое в различных данных и временных интервалах. Они предоставляются как бесплатный начальный код для копирования, расширения и повторного использования.
Примечание.
Для краткости примеры скриптов опустят такие вещи, как обработка ошибок, которые могут ожидать высококачественного кода PowerShell. Они предназначены в первую очередь для вдохновения и образования, а не для производства.
Пример 1. ЦП, я вижу вас!
В этом примере используется ClusterNode.Cpu.Usage
ряд из LastWeek
временных интервалов для отображения максимального ("высокой водяной отметки"), минимального и среднего использования ЦП для каждого сервера в кластере. Он также выполняет простой анализ квартили, чтобы показать, сколько часов использования ЦП было более 25%, 50%, и 75% за последние 8 дней.
Снимок экрана
На снимке экрана ниже мы видим, что сервер-02 имел необъяснимый всплеск на прошлой неделе:
Принцип работы
Выходные данные из Get-ClusterPerf
каналов хорошо в встроенный Measure-Object
командлет мы просто указываем Value
это свойство. С его -Maximum
, -Minimum
и -Average
флаги, Measure-Object
дает нам первые три столбца почти бесплатно. Для анализа квартиля можно передать Where-Object
и подсчитать, сколько значений было -Gt
(больше) 25, 50 или 75. Последний шаг заключается в том, чтобы перенастраивать функции и Format-Hours
Format-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σ (три стандартных отклонения), превышающих среднее число населения.
Внимание
Для краткости этот сценарий не реализует меры защиты от низкой дисперсии, не обрабатывает частичные отсутствующие данные, не отличается от модели или встроенного ПО и т. д. Пожалуйста, выполните хорошее решение и не полагаться только на этот сценарий, чтобы определить, следует ли заменить жесткий диск. Здесь представлено только для образовательных целей.
Снимок экрана
На снимке экрана ниже мы видим, что нет выскользов:
Принцип работы
Во-первых, мы исключим бездействующие или почти бездействующие диски, проверяя их PhysicalDisk.Iops.Total
согласованно -Gt 1
. Для каждого активного HDD мы передаем его LastHour
интервал времени, состоящий из 360 измерений в 10 секунд, чтобы Measure-Object -Average
получить среднюю задержку за последний час. Это настраивает наше население.
Мы реализуем широко известную формулу , чтобы найти среднее μ
и стандартное отклонение σ
населения. Для каждого активного HDD мы сравниваем среднюю задержку с средней задержкой населения и разделим на стандартное отклонение. Мы сохраняем необработанные значения, чтобы мы могли Sort-Object
получать результаты, но использовать Format-Latency
и Format-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.Total
ряд из MostRecent
временных интервалов, чтобы определить наиболее загруженные виртуальные машины (некоторые могут сказать"шумно") виртуальные машины, потребляющие большую часть операций ввода-вывода в секунду на каждом узле кластера, и показать разбивку операций чтения и записи.
Снимок экрана
На снимке экрана ниже мы видим первые 10 виртуальных машин по активности хранилища:
Принцип работы
Get-VM
В отличие от Get-PhysicalDisk
этого командлет не учитывает кластер, он возвращает только виртуальные машины на локальном сервере. Для параллельного запроса с каждого сервера мы заключим вызов Invoke-Command (Get-ClusterNode).Name { ... }
. Для каждой виртуальной VHD.Iops.Total
машины мы получаем и VHD.Iops.Read
VHD.Iops.Write
измерения. Не указывая -TimeFrame
параметр, мы получаем одну точку данных для каждой MostRecent
.
Совет
Эти ряды отражают сумму действия этой виртуальной машины ко всем файлам VHD/VHDX. Это пример, в котором журнал производительности автоматически агрегируется для нас. Чтобы получить разбивку на виртуальный жесткий диск или VHDX, можно передать отдельный объект Get-VHD
Get-ClusterPerf
вместо виртуальной машины.
Результаты каждого сервера объединяются как $Output
, что мы можем Sort-Object
, а затем Select-Object -First 10
. Обратите внимание, что Invoke-Command
украшает результаты свойством PsComputerName
, указывающим, откуда они были получены, чтобы узнать, откуда выполняется виртуальная машина.
Скрипт
Ниже приведен сценарий:
$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 достиг пика в последний день:
Принцип работы
Мы повторяем наш Invoke-Command
трюк выше на Get-NetAdapter
каждом сервере и канале в Get-ClusterPerf
. По пути мы захватим два важных свойства: его LinkSpeed
строку, как "10 Гбит/с", и его необработанное Speed
целое число, как 1000000000000. Мы используем Measure-Object
для получения среднего и пикового показателя с последнего дня (напоминание: каждое измерение в LastDay
интервале времени представляет 5 минут) и умножаем на 8 бит на байт, чтобы получить сравнение яблок с яблоками в яблоки.
Примечание.
Некоторые поставщики, такие как Челсио, включают в себя действие удаленного прямого доступа к памяти (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 ГБ в день:
На этом уровне он достигнет своей емкости в еще 42 дня.
Принцип работы
Интервал LastYear
времени содержит одну точку данных в день. Хотя вам нужно только два пункта, чтобы соответствовать линии тренда, на практике лучше требовать больше, как 14 дней. Мы используем Select-Object -Last 14
для настройки массива точек (x, y) для x в диапазоне [1, 14]. С этими точками мы реализуем простой алгоритм линейных наименьших квадратов, чтобы найти $A
и $B
что параметризовать линию лучшего соответствия y = ax + b. Добро пожаловать в среднюю школу снова.
Деление свойства тома SizeRemaining
на тенденцию (наклон $A
) позволяет нам грубо оценить, сколько дней, по текущей скорости роста хранилища, пока объем не будет заполнен. Функции Format-Bytes
, Format-Trend
а Format-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.Memory.Assigned
ряд из LastMonth
интервала времени для идентификации виртуальных машин, потребляющих большую память за последние 35 дней.
Снимок экрана
На снимке экрана ниже мы видим первые 10 виртуальных машин по использованию памяти в прошлом месяце:
Принцип работы
Мы повторяем наш Invoke-Command
трюк, представленный выше, на Get-VM
каждом сервере. Мы используем Measure-Object -Average
для получения ежемесячного среднего показателя для каждой виртуальной машины, а затем 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
для сценариев командлета, вы можете попросить и ответить! — сложные вопросы, связанные с управлением и мониторингом инфраструктуры Windows Server 2019.