Modules/Private/Get-S2DExpansionHeadroom.ps1
|
# Get-S2DExpansionHeadroom — calculates remaining room to expand or create volumes # at 70 / 80 / 90 / 100% fill thresholds against AvailableForVolumes (footprint basis). # # CANONICAL MATH (docs/capacity-model.md): # A = CapacityWaterfall.AvailableForVolumes.Bytes (excludes pool meta, reserve, infra) # U = sum of FootprintOnPool.Bytes for non-infra volumes # copies = prevailing NumberOfDataCopies among non-infra volumes (assumed for new volumes) # currentUtilizationPct = U / A * 100 # For X in {0.70, 0.80, 0.90, 1.00}: # footprintBudgetBytes = X * A # remainingFootprintBytes = max(0, X*A - U) # remainingNewUsableBytes = remainingFootprintBytes / copies # isPastLine = (U > X*A) function Get-S2DExpansionHeadroom { <# .SYNOPSIS Calculates expansion headroom at 70/80/90/100% fill thresholds. .DESCRIPTION Given a completed capacity waterfall and the current volume map, computes how much footprint space and new usable data capacity remain before each fill threshold is crossed. Uses the prevailing (most-common) NumberOfDataCopies among non-infrastructure volumes to estimate new-volume usable data from remaining footprint. All capacity values are returned as [S2DCapacity] objects carrying both TiB (binary) and TB (decimal) representations. .PARAMETER Waterfall The S2DCapacityWaterfall object from Get-S2DCapacityWaterfall. AvailableForVolumes.Bytes is the denominator (A). .PARAMETER Volumes Array of S2DVolume objects from Get-S2DVolumeMap. Only non-infrastructure volumes contribute to the used footprint (U). .OUTPUTS PSCustomObject with CurrentUtilizationPct, PrevalentDataCopies, AvailableForVolumesBytes, UsedFootprintBytes, and a Thresholds array. #> [CmdletBinding()] param( [Parameter(Mandatory)] [object] $Waterfall, [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Volumes ) # A = available for volumes (footprint basis, excludes meta + reserve + infra) $availBytes = if ($Waterfall -and $Waterfall.AvailableForVolumes -and $Waterfall.AvailableForVolumes.Bytes -gt 0) { [int64]$Waterfall.AvailableForVolumes.Bytes } else { [int64]0 } # Non-infrastructure volumes only $workloadVols = @($Volumes | Where-Object { -not $_.IsInfrastructureVolume }) # U = sum of footprint bytes for non-infra volumes $usedBytes = [int64]( ($workloadVols | ForEach-Object { if ($_.FootprintOnPool -and $_.FootprintOnPool.Bytes -gt 0) { [int64]$_.FootprintOnPool.Bytes } else { [int64]0 } } | Measure-Object -Sum).Sum ) # Prevailing data copies — most common NumberOfDataCopies among non-infra volumes. # Defaults to 2 (two-way mirror) when volumes list is empty. $prevalentCopies = if ($workloadVols.Count -gt 0) { $grouped = $workloadVols | Group-Object -Property NumberOfDataCopies | Sort-Object -Property Count -Descending [int]$grouped[0].Name } else { 2 } $currentPct = if ($availBytes -gt 0) { [math]::Round($usedBytes / $availBytes * 100, 1) } else { 0.0 } $thresholds = foreach ($pct in @(70, 80, 90, 100)) { $fraction = $pct / 100.0 $budgetBytes = [int64][math]::Round($fraction * $availBytes) $remainingFootprint = [int64][math]::Max([int64]0, $budgetBytes - $usedBytes) $remainingUsable = if ($prevalentCopies -gt 0) { [int64][math]::Round($remainingFootprint / $prevalentCopies) } else { [int64]0 } $isPast = ($usedBytes -gt $budgetBytes) [PSCustomObject]@{ FillTargetPct = $pct FootprintBudget = [S2DCapacity]::new($budgetBytes) RemainingFootprint = [S2DCapacity]::new($remainingFootprint) NewUsableData = [S2DCapacity]::new($remainingUsable) IsPastLine = $isPast IsRecommendedPlanningLine = ($pct -eq 70) } } [PSCustomObject]@{ CurrentUtilizationPct = $currentPct PrevalentDataCopies = $prevalentCopies PrevalentCopiesNote = "Assumed for new-volume estimates. Actual resiliency can differ." AvailableForVolumesBytes = $availBytes UsedFootprintBytes = $usedBytes Thresholds = @($thresholds) } } |