Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
211 lines (202 sloc)
10.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Query a cluster for cache daily write stats | |
.DESCRIPTION | |
Makes use of the 'Get-StorageHistory' command and the Cluster Performance | |
History database to display stats for each cache drive in a cluster, such as | |
estimated Drive Writes per Day (DWPD), average daily writes, and average | |
write throughput. | |
.EXAMPLE | |
PS C:\> Get-S2DCacheChurn -Cluster S2D-Cluster -anonymize | Format-Table -AutoSize | |
Returns a table containing each of the disks in each node in the Cluster 'S2D-Cluster' | |
but the Cluster name and the Node names have been swapped out with generic values | |
Cluster ComputerName Disk Size EstDwpd AvgDailyWrite AvgWriteThroughput AvgCacheUsage | |
------- ------------ ---- ---- ------- ------------- ------------------ ------------- | |
Cluster1 Node1 Slot 0 745.21 GB 1.6x 1.18 TB 19.71 MB/s 3.35 GB | |
Cluster1 Node1 Slot 1 745.21 GB 1.0x 756.15 GB 12.30 MB/s 21.51 GB | |
Cluster1 Node1 Slot 2 745.21 GB 1.8x 1.28 TB 21.25 MB/s 4.45 GB | |
Cluster1 Node1 Slot 3 745.21 GB 1.4x 1.02 TB 16.92 MB/s 2.44 GB | |
Cluster1 Node2 Slot 0 745.21 GB 1.3x 1,000.90 GB 16.17 MB/s 2.23 GB | |
Cluster1 Node2 Slot 1 745.21 GB 1.3x 932.73 GB 15.08 MB/s 2.05 GB | |
Cluster1 Node2 Slot 2 745.21 GB 1.5x 1.11 TB 18.45 MB/s 2.86 GB | |
Cluster1 Node2 Slot 3 745.21 GB 1.5x 1.09 TB 18.07 MB/s 2.49 GB | |
.INPUTS | |
None | |
.OUTPUTS | |
PSCustomObject | |
.NOTES | |
Written by: Ben Thomas (@NZ_BenThomas) | |
Created: 2020/02/23 | |
URL: https://bcthomas.com | |
#> | |
[cmdletbinding()] | |
param( | |
# List of clusters to query | |
[parameter(Mandatory)] | |
[string[]]$cluster, | |
# (Optional) Only query the last 24 hours | |
[switch]$lastDay, | |
# (Optional) Anonymize resource names | |
[switch]$anonymize | |
) | |
begin { | |
# Helper Functions | |
function New-S2DCacheDiskObj { | |
param ( | |
[string]$FriendlyName = $null, | |
[string]$PhysicalLocation = $Null, | |
[string]$SerialNumber = $null | |
) | |
$Object = New-Module -AsCustomObject -ScriptBlock { | |
[string]$FriendlyName = $null | |
[string]$PhysicalLocation = $Null | |
[string]$SerialNumber = $null | |
Function ToString { | |
return ("{0}" -f $PhysicalLocation) | |
} | |
function ToLongString { | |
return ("{0}|{1}|{2}" -f $PhysicalLocation, $FriendlyName, $SerialNumber) | |
} | |
Export-ModuleMember -Variable * -Function * | |
} | |
$Object.FriendlyName = $FriendlyName | |
$Object.PhysicalLocation = $PhysicalLocation | |
$Object.SerialNumber = $SerialNumber | |
Return $Object | |
} | |
function Format-Bytes { | |
param( | |
$Bytes | |
) | |
switch ([math]::Truncate([Math]::log($Bytes, 1024))) { | |
0 { "{0} B" -f $Bytes } | |
1 { "{0:N2} KB" -f ($Bytes / 1KB) } | |
2 { "{0:N2} MB" -f ($Bytes / 1MB) } | |
3 { "{0:N2} GB" -f ($Bytes / 1GB) } | |
4 { "{0:N2} TB" -f ($Bytes / 1TB) } | |
5 { "{0:N2} PB" -f ($Bytes / 1PB) } | |
} | |
} | |
# Establish Variables | |
$ClusterCounter = 0 | |
} | |
Process { | |
Foreach ($PSItem in $Cluster) { | |
Try { | |
# Check Cluster Version is Supported | |
Write-Verbose "[$((Get-Date).ToShortTimeString())]$PSItem - Checking the storage pool is compatible" | |
$SPVersion = Invoke-Command -ComputerName $PSItem -ScriptBlock { | |
Get-StoragePool S2D* -IsPrimordial:$false -Verbose:$false | Select-Object FriendlyName, Version | |
} | |
if ($SPVersion.Version -inotlike "*2019*") { | |
Throw "[$((Get-Date).ToShortTimeString())]Skipping $PSItem because it is incompatible.`nStorage Pool: $($SPVersion.FriendlyName)`nCurrent level: $($SPVersion.Version)`nRequired Level: Windows Server 2019" | |
} | |
} | |
Catch { | |
Write-Warning $_.Exception.Message | |
Continue | |
} | |
$ClusterCounter++ | |
$ClusterName = $PSItem | |
# Find Nodes in Cluster | |
Write-Verbose "[$((Get-Date).ToShortTimeString())]$ClusterName - Querying the cluster for nodes" | |
$Nodes = Get-StorageSubSystem CLU* -CimSession $ClusterName -Verbose:$false | ` | |
Get-StorageNode -CimSession $ClusterName -Verbose:$false | |
Write-Verbose "[$((Get-Date).ToShortTimeString())]$ClusterName - Found $($Nodes.Count) Nodes ($($Nodes.Name -join ','))" | |
$NodeCounter = 0 | |
# Look though Nodes | |
($Nodes | Sort-Object Name).Foreach{ | |
$NodeCounter++ | |
$ComputerName = $PSItem.Name.Split(".")[0] | |
# Find node Cache Disks | |
Write-Verbose "[$((Get-Date).ToShortTimeString())]$ClusterName - $ComputerName - Querying the node for Cache Disks" | |
$cacheDisks = $PSItem | ` | |
Get-PhysicalDisk -Usage Journal -PhysicallyConnected -CimSession $ComputerName | |
if ($cacheDisks.Count -gt 0) { | |
Write-Verbose "[$((Get-Date).ToShortTimeString())]$ClusterName - $ComputerName - Discovered $($cacheDisks.Count) Cache Disks" | |
# Loop through Cache Disks | |
($cacheDisks | Sort-Object PhysicalLocation).Foreach{ | |
<# | |
Check if we only want the last day of data. | |
Query the storage history for the device and timeframe. | |
Use the timeframe to work out the average daily write. | |
#> | |
if ($lastDay) { | |
Write-Verbose "[$((Get-Date).ToShortTimeString())]$ClusterName - $ComputerName - $($PSitem.PhysicalLocation) - Querying last 24 hours of storage history" | |
$StorageHistory = Invoke-Command $Computername { | |
$Using:PSItem | Get-StorageHistory -NumberOfHours 24 | |
} | |
} | |
else { | |
Write-Verbose "[$((Get-Date).ToShortTimeString())]$ClusterName - $ComputerName - $($PSitem.PhysicalLocation) - Querying available storage history" | |
$StorageHistory = Invoke-Command $Computername { | |
$Using:PSItem | Get-StorageHistory | |
} | |
} | |
$TimePeriod = New-TimeSpan -Start $StorageHistory.StartTime -End $StorageHistory.EndTime | |
$DailyWrite = ( | |
$StorageHistory.TotalWriteBytes / $TimePeriod.TotalDays | |
) | |
<# | |
Find the current timeframe returned by storage history. | |
Query the Cluster Performance History database to get the related | |
throughput statistics and find the average write throughput | |
#> | |
$timeframe = switch ($TimePeriod.TotalDays) { | |
{ $_ -le 1 } { "LastDay"; break } | |
{ $_ -le 8 } { "LastWeek"; break } | |
{ $_ -le 35 } { "LastMonth"; break } | |
default { "LastYear"; break } | |
} | |
Write-Verbose "[$((Get-Date).ToShortTimeString())]$ClusterName - $ComputerName - $($PSitem.PhysicalLocation) - Querying Cluster Performance History" | |
$clusterPerf = Invoke-Command $Computername { | |
$Using:PSItem | Get-ClusterPerformanceHistory -PhysicalDiskSeriesName PhysicalDisk.Cache.Size.Dirty, PhysicalDisk.Throughput.Write -TimeFrame $Using:timeframe | |
} | |
$avgThroughput = $clusterPerf.Where{ $_.MetricID -ilike "*Throughput*" } | ` | |
Measure-Object -Property Value -Average | ` | |
Select-Object -ExpandProperty Average | |
$avgUsage = $clusterPerf.Where{ $_.MetricID -ilike "*Dirty*" } | ` | |
Measure-Object -Property Value -Average | ` | |
Select-Object -ExpandProperty Average | |
<# | |
Format and return the data, anonymizing if required. | |
#> | |
Write-Verbose "[$((Get-Date).ToShortTimeString())]$ClusterName - $ComputerName - $($PSitem.PhysicalLocation) - Formatting results" | |
# Format Cache Disk Object | |
$cacheDiskObj = New-S2DCacheDiskObj -FriendlyName $PSItem.FriendlyName -PhysicalLocation $PSItem.PhysicalLocation -SerialNumber $PSItem.SerialNumber | |
# Return final object | |
if (!$anonymize) { | |
[pscustomobject][ordered]@{ | |
Cluster = $ClusterName | |
ComputerName = $ComputerName | |
Disk = $cacheDiskObj | |
Size = Format-Bytes -Bytes $PSItem.Size | |
EstDwpd = "{0:N1}x" -f ( $DailyWrite / $PSItem.Size ) | |
AvgDailyWrite = Format-Bytes -Bytes $DailyWrite | |
AvgWriteThroughput = "$(Format-Bytes -Bytes $avgThroughput)/s" | |
AvgCacheUsage = Format-Bytes -Bytes $avgUsage | |
} | |
} | |
else { | |
# Remove identifiable information | |
$cacheDiskObj.SerialNumber = Get-Random | |
[pscustomobject][ordered]@{ | |
Cluster = "Cluster{0}" -f $ClusterCounter | |
ComputerName = "Node{0}" -f $NodeCounter | |
Disk = $cacheDiskObj | |
Size = Format-Bytes -Bytes $PSItem.Size | |
EstDwpd = "{0:N1}x" -f ( $DailyWrite / $PSItem.Size ) | |
AvgDailyWrite = Format-Bytes -Bytes $DailyWrite | |
AvgWriteThroughput = "$(Format-Bytes -Bytes $avgThroughput)/s" | |
AvgCacheUsage = Format-Bytes -Bytes $avgUsage | |
} | |
} | |
} | |
} | |
else { | |
Write-Warning "$ClusterName did return any Cache disks for $ComputerName" | |
Continue | |
} | |
} | |
} | |
} |