Modules/businessdev.ALbuild.RuntimePackages/Private/Get-BcRuntimeWorkerCount.ps1
|
function Get-BcRuntimeWorkerCount { <# .SYNOPSIS Decides how many BC containers may be built concurrently, and says why. .DESCRIPTION The factory's throughput comes from running several containers at once inside ONE agent job. That is deliberate: the organisation has four parallel self-hosted jobs against a build host with 36 cores and 192 GB of RAM, so the licence - not the hardware - is what caps a per-job design. Running the containers as workers inside a job sidesteps that cap entirely. What must not happen is trading one bottleneck for a worse one. Each BC container carries its own SQL instance and service tier; over-subscribing memory pushes the host into paging, where every container gets slower and some fail to become ready at all - which reads as a flaky build, not as a capacity problem. So the requested count is capped by what the host can actually carry: * memory - (free - reserve) / per-container limit. The reserve keeps the agent, the compiler and the host itself out of the paging file. * disk - (free - reserve) / per-container working set. A BC container copies the service tier and restores its database into its writable layer; running the drive to zero mid-start surfaces as a cryptic "failed to start service" minutes later. Both the chosen number AND the reason are returned so the log can state "requested 6, memory allows 9, disk allows 4 -> running 4". A silently reduced worker count is indistinguishable from a slow host when someone reads the log a week later. .PARAMETER Requested The configured worker count (the ceiling; the caps can only lower it). .PARAMETER MemoryLimitGb Per-container memory limit in GB. .PARAMETER FreeMemoryGb Free host memory in GB. .PARAMETER ReserveMemoryGb Memory to leave for the agent, compiler and OS. .PARAMETER FreeDiskGb Free disk in GB on the drive Docker stores containers on. 0 or less skips the disk cap (the caller could not measure it). .PARAMETER ReserveDiskGb Disk to leave free. .PARAMETER DiskPerContainerGb Working set of one container on disk. .OUTPUTS PSCustomObject: Count, Requested, MemoryAllows, DiskAllows, Reason. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [ValidateRange(1, 64)] [int] $Requested, [ValidateRange(1, 512)] [int] $MemoryLimitGb = 8, [Parameter(Mandatory)] [double] $FreeMemoryGb, [ValidateRange(0, 512)] [int] $ReserveMemoryGb = 16, [double] $FreeDiskGb = 0, [ValidateRange(0, 4096)] [int] $ReserveDiskGb = 40, [ValidateRange(1, 512)] [int] $DiskPerContainerGb = 25 ) # At least 1: refusing to do any work at all is never the better answer. If the host really is that # tight, one container failing with a clear disk/memory error beats a run that silently does nothing. $memoryAllows = [Math]::Max(1, [Math]::Floor(($FreeMemoryGb - $ReserveMemoryGb) / $MemoryLimitGb)) $diskAllows = if ($FreeDiskGb -gt 0) { [Math]::Max(1, [Math]::Floor(($FreeDiskGb - $ReserveDiskGb) / $DiskPerContainerGb)) } else { $Requested } $count = [int][Math]::Min($Requested, [Math]::Min($memoryAllows, $diskAllows)) $reason = "requested $Requested, memory allows $memoryAllows ($([Math]::Round($FreeMemoryGb, 1)) GB free, $ReserveMemoryGb GB reserved, $MemoryLimitGb GB per container)" $reason += if ($FreeDiskGb -gt 0) { ", disk allows $diskAllows ($([Math]::Round($FreeDiskGb, 1)) GB free, $ReserveDiskGb GB reserved, $DiskPerContainerGb GB per container)" } else { ', disk not measured' } $reason += " -> running $count" return [PSCustomObject]@{ Count = $count Requested = $Requested MemoryAllows = [int]$memoryAllows DiskAllows = [int]$diskAllows Reason = $reason } } |