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
    }
}