Modules/Private/Invoke-S2DWaterfallCalculation.ps1
|
# Invoke-S2DWaterfallCalculation — pure waterfall math, no session dependency. # Called by Get-S2DCapacityWaterfall (live cluster) and Invoke-S2DCapacityWhatIf (what-if modeling). function Invoke-S2DWaterfallCalculation { <# .SYNOPSIS Computes the 8-stage S2D capacity waterfall from explicit numeric inputs. .DESCRIPTION Pure function — no PowerShell session, no module state, no live CIM queries. All inputs are passed explicitly. Returns an S2DCapacityWaterfall object. Stage 1 Raw physical capacity (pool-member capacity disks) Stage 2 Vendor TB label note (informational — no bytes deducted) Stage 3 After storage pool overhead Stage 4 After reserve space (min(NodeCount,4) × largest drive) Stage 5 After infrastructure volume Stage 6 Available for workload volumes Stage 7 After resiliency overhead (theoretical) Stage 8 Final usable capacity (pipeline terminus = Stage 7) .PARAMETER RawDiskBytes Sum of all pool-member capacity-tier disk sizes in bytes (Stage 1). .PARAMETER NodeCount Number of nodes in the cluster. Used for reserve calculation. .PARAMETER LargestDiskSizeBytes Size in bytes of the largest capacity-tier disk. Used for reserve calculation. .PARAMETER PoolTotalBytes Storage pool total size in bytes (Stage 3). If 0, estimated as RawDiskBytes × (1 - PoolOverheadFraction). .PARAMETER PoolFreeBytes Current unallocated pool bytes. Used for reserve status only (Adequate/Warning/Critical). Does not affect stage values. .PARAMETER PoolOverheadFraction Pool overhead as a fraction (default 0.01 = 1%). Used only when PoolTotalBytes is 0. .PARAMETER InfraVolumeBytes Infrastructure volume pool footprint in bytes (Stage 5 deduction). .PARAMETER ResiliencyFactor Number of data copies for resiliency (default 3.0 for 3-way mirror). Stage 7 = Stage 6 / ResiliencyFactor. .PARAMETER ResiliencyName Human-readable label for the resiliency type (default '3-way mirror'). #> [CmdletBinding()] [OutputType([S2DCapacityWaterfall])] param( [Parameter(Mandatory)] [int64] $RawDiskBytes, [Parameter(Mandatory)] [int] $NodeCount, [Parameter(Mandatory)] [int64] $LargestDiskSizeBytes, [int64] $PoolTotalBytes = 0, [int64] $PoolFreeBytes = 0, [double] $PoolOverheadFraction = 0.01, [int64] $InfraVolumeBytes = 0, [double] $ResiliencyFactor = 3.0, [string] $ResiliencyName = '3-way mirror' ) # ── Stage 1: Raw physical ───────────────────────────────────────────────── $stage1Bytes = $RawDiskBytes # ── Stage 2: Vendor TB label note (no deduction) ───────────────────────── $vendorLabeledTB = [math]::Round($stage1Bytes / 1000000000000, 2) $stage2Bytes = $stage1Bytes # ── Stage 3: Pool overhead ──────────────────────────────────────────────── $stage3Bytes = if ($PoolTotalBytes -gt 0) { $PoolTotalBytes } else { [int64]($stage2Bytes * (1.0 - $PoolOverheadFraction)) } # ── Stage 4: Reserve space ──────────────────────────────────────────────── $reserveCalc = Get-S2DReserveCalculation ` -NodeCount $NodeCount ` -LargestCapacityDriveSizeBytes $LargestDiskSizeBytes ` -PoolFreeBytes $PoolFreeBytes $reserveBytes = $reserveCalc.ReserveRecommendedBytes $stage4Bytes = $stage3Bytes - $reserveBytes # ── Stage 5: Infrastructure volume ─────────────────────────────────────── $stage5Bytes = $stage4Bytes - $InfraVolumeBytes # ── Stage 6: Available ──────────────────────────────────────────────────── $stage6Bytes = $stage5Bytes # ── Stage 7: Theoretical resiliency ────────────────────────────────────── $stage7Bytes = [int64]($stage6Bytes / $ResiliencyFactor) $theoreticalEffPct = [math]::Round(100.0 / $ResiliencyFactor, 1) # ── Stage 8: Final usable (pipeline terminus) ───────────────────────────── $stage8Bytes = $stage7Bytes # ── Build stage objects ─────────────────────────────────────────────────── function local:New-Stage { param([int]$N, [string]$Name, [int64]$Bytes, [int64]$Prev, [string]$Desc, [string]$Status = 'OK') $s = [S2DWaterfallStage]::new() $s.Stage = $N $s.Name = $Name $s.Size = if ($Bytes -gt 0) { [S2DCapacity]::new($Bytes) } else { [S2DCapacity]::new([int64]0) } $s.Delta = if ($Prev -gt $Bytes -and $Prev -gt 0) { [S2DCapacity]::new($Prev - $Bytes) } else { $null } $s.Description = $Desc $s.Status = $Status $s } $stage4Status = switch ($reserveCalc.Status) { 'Adequate' { 'OK' } 'Warning' { 'Warning' } default { 'Critical' } } $driveCount = if ($LargestDiskSizeBytes -gt 0) { [math]::Round($RawDiskBytes / $LargestDiskSizeBytes) } else { 0 } $infraDisplay = if ($InfraVolumeBytes -gt 0) { "$([math]::Round($InfraVolumeBytes/1073741824,1)) GiB" } else { 'None detected' } $stages = @( (New-Stage 1 'Raw Physical' $stage1Bytes $stage1Bytes "Sum of pool-member capacity-tier disk sizes ($driveCount drives, $('{0:N2}' -f ($LargestDiskSizeBytes/1TB)) TB each)"), (New-Stage 2 'Vendor Label (TB)' $stage2Bytes $stage1Bytes "Informational — vendor labels drives in decimal TB; Windows reports binary TiB. Vendor label: $vendorLabeledTB TB. No bytes deducted."), (New-Stage 3 'Pool (after overhead)' $stage3Bytes $stage2Bytes "Storage pool overhead (~$([math]::Round($PoolOverheadFraction*100,0))%). Pool total: $('{0:N2}' -f ($stage3Bytes/1TB)) TB"), (New-Stage 4 'After Reserve' $stage4Bytes $stage3Bytes "Reserve: min($NodeCount,4) × $('{0:N2}' -f ($LargestDiskSizeBytes/1TB)) TB = $('{0:N2}' -f ($reserveBytes/1TB)) TB" $stage4Status), (New-Stage 5 'After Infra Volume' $stage5Bytes $stage4Bytes "Infrastructure volume footprint: $infraDisplay"), (New-Stage 6 'Available' $stage6Bytes $stage5Bytes "Pool space available for workload volumes"), (New-Stage 7 'After Resiliency' $stage7Bytes $stage6Bytes "Theoretical resiliency overhead ($ResiliencyName, $theoreticalEffPct% efficiency). Available ÷ $ResiliencyFactor."), (New-Stage 8 'Final Usable' $stage8Bytes $stage7Bytes "Pipeline terminus — no further theoretical deductions. Usable VM and workload capacity under $ResiliencyName resiliency.") ) $wf = [S2DCapacityWaterfall]::new() $wf.Stages = $stages $wf.RawCapacity = [S2DCapacity]::new($stage1Bytes) $wf.UsableCapacity = if ($stage8Bytes -gt 0) { [S2DCapacity]::new($stage8Bytes) } else { [S2DCapacity]::new([int64]0) } $wf.ReserveRecommended = $reserveCalc.ReserveRecommended $wf.ReserveActual = $reserveCalc.ReserveActual $wf.ReserveStatus = $reserveCalc.Status $wf.IsOvercommitted = $false $wf.OvercommitRatio = 0.0 $wf.NodeCount = $NodeCount $wf.BlendedEfficiencyPercent = $theoreticalEffPct $wf } |