Get a pretty view of your S2D Storage Pools
I’d like to start with a shout out to Philip Elder, for he came up with the initial idea and script that I’ve used here.
One thing that’s not always obvious when dealing with S2D Clusters is how much of your Storage Pool has been provisioned and how much capacity, if any, is left.
To help with this, we came up with be script you’ll see at the bottom of this article.
It’s designed to be run both locally on a cluster host and against multiple clusters remotely.
So what can you expect to get out of this script?
- Total Size - This is your RAW capacity in the pool
- Allocated Size - This is the RAW amount of storage already allocated to Virtual Disks
- RAW Free Space - This is your remaining capacity in the pool
- Mirror Available - This is how much space you can provision as Mirror Volumes, it assumes 2-way mirror for 2-Nodes and 3-way mirror for 3+ Nodes
- Parity Available - This is how much space you can provision as Parity Volumes
- Reserved Space, this is how much space needs to be retained for rebuilds after disk failure
So how do you run this?
Well it’s very simple, you can just run the command with nothing else directly on a cluster host and get an output
Get-PoolStats | ft -au
Or you can run it remotely against multiple clusters
Get-PoolStats -CimSession Cluster-Gen13,Cluster-Gen14 | ft -au
Get-StoragePool -FriendlyName S2D* -CimSession Cluster-Gen13,Cluster-Gen14 | Get-PoolStats | ft -au
As always, I hope this script helps someone else out there
function Get-PoolStats {
[CmdletBinding()]
param(
# Remote machines to query
[alias("ComputerName")]
[CimSession[]]$CimSession = "localhost",
# Storage Pools you want to query
[PSTypeName("Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_StoragePool")]
[parameter(
Position=0,
Mandatory=$false,
ValueFromPipeline=$true
)]
$StoragePool = (Get-StoragePool -CimSession $CimSession | Where-Object IsPrimordial -eq $false)
)
begin{
Write-Verbose "[$((get-date).TimeOfDay.ToString()) BEGIN ] Starting: $($MyInvocation.Mycommand)"
Write-Verbose "[$((get-date).TimeOfDay.ToString()) BEGIN ] Initialising array"
$Results = @()
#region: Helper Functions
Write-Verbose "[$((get-date).TimeOfDay.ToString()) BEGIN ] Initialising helper functions"
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]
}
#endregion
}
process{
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Starting process block"
Foreach($Pool in $StoragePool){
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Starting process $($Pool.FriendlyName)"
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Calculating Parity Efficiency"
$PhysicalDisks = $Pool | Get-PhysicalDisk -CimSession $Pool.CimSystemProperties.ServerName
$StorageNodes = ($Pool | Get-StorageSubSystem | Get-StorageNode).Count
$SlowTierMediaType = ( $PhysicalDisks | Where-Object{ $_.Usage -ieq "Auto-Select" } ).MediaType | Sort | Get-Unique
if($SlowTierMediaType -ieq "NVMe" -or $SlowTierMediaType -ieq "SSD"){
# All-flash!
if ($StorageNodes -lt 7) {
$ParityEfficiency = 2 / (2 + 2);
}
elseif ($StorageNodes -lt 9) {
$ParityEfficiency = 4 / (4 + 2);
}
elseif ($StorageNodes -lt 16) {
$ParityEfficiency = 6 / (6 + 2);
}
else {
$ParityEfficiency = 12 / (12 + 2 + 1); # LRC
}
}else{
# Hybrid
if ($StorageNodes -lt 7) {
$ParityEfficiency = 2 / (2 + 2);
}
elseif ($StorageNodes -lt 12) {
$ParityEfficiency = 4 / (4 + 2);
}
else {
$ParityEfficiency = 8 / (8 + 2 + 1); # LRC
}
}
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Parity Efficiency is $ParityEfficiency"
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Calculating Mirror Efficiency"
$MirrorEfficiency = 1 / ( ( $Pool | Get-ResiliencySetting -Name Mirror ).PhysicalDiskRedundancyDefault + 1 )
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Mirror Efficiency is $MirrorEfficiency"
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Calculating Rebuild Space"
$LargestDisk = $PhysicalDisks | Where-Object{ $_.Usage -ieq "Auto-Select" } | Sort-Object Size -Descending | Select-Object -First 1 -ExpandProperty Size
If($StorageNodes -gt 4){
$RebuildSpace = $LargestDisk * 4
}else{
$RebuildSpace = $LargestDisk * $StorageNodes
}
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Calculating Pool Stats"
$FriendlyName = $Pool.FriendlyName
$Size = Format-Bytes -RawValue $Pool.Size
$AllocatedSize = Format-Bytes -RawValue $Pool.AllocatedSize
$RawFreeSpace = Format-Bytes -RawValue ($Pool.Size - $Pool.AllocatedSize)
$RebuildSpaceNeeded = Format-Bytes -RawValue $RebuildSpace
$MirrorAvailable = Format-Bytes -RawValue ( ( $Pool.Size - $Pool.AllocatedSize - $RebuildSpace ) * $MirrorEfficiency)
$ParityAvailable = Format-Bytes -RawValue ( ( $Pool.Size - $Pool.AllocatedSize - $RebuildSpace ) * $ParityEfficiency)
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Adding output to results array"
$Results += [pscustomobject][ordered]@{
Name = $FriendlyName
TotalSize = $Size
AllocatedSize = $AllocatedSize
RawFreeSpace = $RawFreeSpace
MirrorAvailable = $MirrorAvailable
ParityAvailable = $ParityAvailable
ReservedSpace = $RebuildSpaceNeeded
}
Write-Verbose "[$((get-date).TimeOfDay.ToString()) PROCESS ] Finished processing $($Pool.FriendlyName)"
}
}
end{
Write-Verbose "[$((get-date).TimeOfDay.ToString()) END ] Returning results"
Write-Output $Results
}
}