Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion dag/gunbc/ci/ci_fleet.dag
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import gunbc.fleet_intent {
fleet_host_memory_nominal_vs_usable_note,
}
import gunbc.fleet_intent { IsolationBoundary, PerJobFilesystem }
import extdeps.cpu.ampere { altra_q6430_catalog }
import extdeps.cpu.ampere { altra_q6430_catalog, altra_q8030_catalog }
import gunbc.fleet_host_identity { operator_host_mtcollins1 }
import extdeps.cpu.types { CpuFacts, CpuDeploymentFacts }
import extdeps.memory.types { MemoryKind, Dram }
import product.host_identity { HostIdentity }
Expand Down Expand Up @@ -113,6 +114,61 @@ data srv4_offer: ComputeOffer = ComputeOffer {
cost_quote: none,
}

// MT. COLLINS UNIT 1 AS A DISKLESS EPHEMERAL CI RUNNER HOST, AT ITS MEASURED ONE-SOCKET SHAPE.
// Operator ruling 2026-10-03: the unit is admissible with socket 1 out (bent pins), so the offer
// carries ONE processor. The board is a two-socket Mt. Collins, and offering its spec would admit
// twice the CPU width the machine can run. Every one-socket boot in
// gunbc.machine_intake_mtcollins1_socket1_investigation_observation read sockets 1, cpus 80,
// numa_nodes 1, and the witness checks this offer's thread count against those readings.
//
// THE PART IS A BET, TYPED AS ONE IN THIS NOTE. The operator reports an Ampere Altra engineering
// sample in socket 0 (mtcollins1_socket_cpus), and no model number has been read off it. Q80-30 is
// the corpus's only 80-core Altra row, chosen because its thread count equals the measured 80. What
// the width depends on is that count, and the count is measured; the model number is not.
//
// NO MEMORY DEVICES ARE LISTED. The DIMM population is being changed (2026-10-04), and nominal
// capacity is not what placement charges anyway. Usable RAM arrives as this host's
// HostUsableRamMeasured row, read by gunbc.host_memory_observation host_usable_ram_from_capture from
// the census boot's meminfo receipt.
//
// NOT ENROLLED in fleet_intent_offers, and that is a declared frontier, not an omission. Enrolment
// waits on TWO receipts, in this order:
// 1. The one-socket census boot's meminfo receipt. From it,
// gunbc.host_memory_observation host_usable_ram_from_capture and
// host_fixed_overhead_from_capture produce this host's usable-RAM and fixed-overhead rows.
// 2. The wet qualification receipt. Its workspace-inclusive memory.peak, read through
// gunbc.runner_throughput_qualification aggregate_memory, is the slot-envelope term for the
// RAM-backed tmpfs workspace (gunbc.runner_slot_allocation host_workspace_standing). Until it
// lands, committed_width_of refuses this host with WidthWorkspacePeakUnestablished.
// Placement accepts this offer over supplied facts of exactly that shape
// (test.claim.mtcollins1_runner_offer_witness).
data mtcollins1_socket0_cpu: CpuFacts = CpuFacts {
catalog: altra_q8030_catalog,
deployment: CpuDeploymentFacts { sustained_per_thread_hz: altra_q8030_catalog.nominal_sustained_per_thread_hz },
}

data mtcollins1_runner_host: ComputeHost = ComputeHost {
identity: operator_host_mtcollins1,
baseboard: none,
bmc_intent: none,
processors: [CpuProcessor { cpu: mtcollins1_socket0_cpu }],
memory: [],
storage: [],
network_interfaces: [],
}

data mtcollins1_offer: ComputeOffer = ComputeOffer {
provider: "gunbc-operator-mtcollins1",
supply: ComputeSupplyFacts {
physical: mtcollins1_runner_host,
execution: fleet_intent_execution_surface(),
cost: Present { value: operator_owned_cost() },
observed_performance: [],
},
available_window: AvailabilityWindow { open: "always-open", close: none },
cost_quote: none,
}

data fleet_intent_offers: List<ComputeOffer> = [srv1_offer, srv2_offer, srv3_offer, srv4_offer]

fn fleet_compute_offer_for(host: HostIdentity) -> ComputeOffer? {
Expand Down
96 changes: 96 additions & 0 deletions dag/gunbc/host/host_memory_observation.dag
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import std.disposition { Disposition, Scaffold, SingleAuthority, Terminal }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import product.host_identity { HostIdentity }
import gunbc.fleet_host_identity { operator_host_srv1, operator_host_srv2, operator_host_srv3, operator_host_srv4 }
import std.measure { kibibyte_count, kibibyte_to_byte_size }
import std.goal_assessment { ObservationAttempt, ObservationEstablished, ObservationRefused }
import gunbc.machine_intake_host_capture_envelope {
BoundHostCaptureEnvelope, HostCaptureSectionReading, SectionAbsent, SectionPending, SectionComplete, SectionMalformed,
host_capture_section_reading, host_capture_run_identity_value,
}
import gunbc.machine_intake_host_resource_observation { ResourceUnread, HostMemoryReading, observe_memory }

// WHAT A HOST HAS, READ FROM THE HOST -- SEPARATED FROM WHAT CI DOES WITH IT. Every declaration
// here came from gunbc.ci_floor_measurement and the extraction is the whole
Expand Down Expand Up @@ -302,3 +309,92 @@ data gunbc_fleet_oomd_floor_basis: String = "oomd_actuation_floor is a DERIVED l
fn oomd_actuation_floor(host: ByteSize) -> ByteSize {
measure_scale_fraction_floor(m: host, num: 1, den: 16)
}

// ── the census route: a host capture's meminfo section becomes this host's measured usable RAM ──

// THE READ IS A CAPTURED RECEIPT, NOT A NUMBER COPIED OUT OF ONE. A boot that runs the capture
// envelope (gunbc.machine_intake_host_capture_envelope, whose stage list carries a meminfo section)
// prints MemTotal and MemAvailable between that section's ARGV and EXIT lines. This reader is the
// only route from those bytes to HostUsableRamMeasured, so a host enrolled from a census boot
// (mtcollins1 first) gets its row by naming its receipt, with no figure hand-copied beside it, and
// the provenance names the machine and run the receipt came from.
//
// WHAT IT REFUSES, EACH FOR ITS OWN REASON. A section that is absent, still pending or malformed
// is not a read; a section that exited nonzero printed something other than the kernel's answer;
// a complete, zero-exit section that does not parse as MemTotal/MemAvailable is the meminfo parse's
// own refusal (gunbc.machine_intake_host_resource_observation observe_memory), carried unchanged.
// A firmware-trained DIMM total is not an input this function accepts at all: it is nominal
// capacity, which fleet_host_memory_nominal_vs_usable_note keeps out of HostUsableRam.
type CensusMeminfoUnread
= CensusMeminfoSectionAbsent
| CensusMeminfoSectionPending
| CensusMeminfoSectionMalformed { reason: String }
| CensusMeminfoExitNonzero { exit_code: String }
| CensusMeminfoUnparsed { cause: ResourceUnread }

// ONE PARSE OF THE RECEIPT, TWO ROWS FROM IT. The usable-RAM row and the fixed-overhead row
// below both project this one reading, so a census boot is a single enrolment trigger for both.
fn census_meminfo_reading(envelope: BoundHostCaptureEnvelope) -> ObservationAttempt<HostMemoryReading, CensusMeminfoUnread> {
match host_capture_section_reading(envelope: envelope, name: "meminfo") {
SectionAbsent => ObservationRefused { cause: CensusMeminfoSectionAbsent }
SectionPending { output: _ } => ObservationRefused { cause: CensusMeminfoSectionPending }
SectionMalformed { reason: r } => ObservationRefused { cause: CensusMeminfoSectionMalformed { reason: r } }
SectionComplete { exit_code: code, output: out } =>
if code != "0" {
ObservationRefused { cause: CensusMeminfoExitNonzero { exit_code: code } }
} else {
match observe_memory(meminfo: Present { value: out }) {
ObservationRefused { cause: c } => ObservationRefused { cause: CensusMeminfoUnparsed { cause: c } }
ObservationEstablished { observed: m } => ObservationEstablished { observed: m }
}
}
}
}

fn census_receipt_provenance(envelope: BoundHostCaptureEnvelope, what: String) -> NonEmptyStr {
join([
envelope.machine as String, " ", what, ", read from the meminfo section of host capture run ",
host_capture_run_identity_value(run: envelope.run), " (gunbc.host_memory_observation census_meminfo_reading)",
], "") as NonEmptyStr
}

fn host_usable_ram_from_capture(envelope: BoundHostCaptureEnvelope) -> ObservationAttempt<HostUsableRam, CensusMeminfoUnread> {
match census_meminfo_reading(envelope: envelope) {
ObservationRefused { cause: c } => ObservationRefused { cause: c }
ObservationEstablished { observed: m } =>
ObservationEstablished {
observed: HostUsableRamMeasured {
bytes: kibibyte_to_byte_size(k: m.total),
provenance: census_receipt_provenance(envelope: envelope, what: concat("/proc/meminfo MemTotal = ", to_string(kibibyte_count(k: m.total)), " kB")),
},
}
}
}

// THE OVERHEAD ROW FROM THE SAME RECEIPT, THROUGH THE SAME UPPER BOUND srv3's row uses.
// host_overhead_upper_bound is MemTotal minus MemAvailable. gunbc_ci_host_memavailable_observation_note
// says its weakness is the instant it was read: a busy host's MemAvailable excludes resident job
// memory, which pushes the bound up, so its dissolution trigger is a quiescent read. A census boot
// is that read. The capture runs on a freshly netbooted image with no runner or session resident,
// so the bound is as tight as a single reading gets, and it stays an upper bound.
fn host_fixed_overhead_from_capture(envelope: BoundHostCaptureEnvelope) -> ObservationAttempt<HostFixedOverhead, CensusMeminfoUnread> {
match census_meminfo_reading(envelope: envelope) {
ObservationRefused { cause: c } => ObservationRefused { cause: c }
ObservationEstablished { observed: m } =>
ObservationEstablished {
observed: HostFixedOverheadMeasured {
bytes: host_overhead_upper_bound(obs: HostMemAvailableObservation {
host: envelope.machine as HostIdentity,
run_id: host_capture_run_identity_value(run: envelope.run) as NonEmptyStr,
runner_unit: "none: census boot, no runner resident",
host_total: kibibyte_to_byte_size(k: m.total),
mem_available: kibibyte_to_byte_size(k: m.available),
}),
provenance: census_receipt_provenance(envelope: envelope, what: join([
"fixed overhead upper bound = MemTotal ", to_string(kibibyte_count(k: m.total)),
" kB - MemAvailable ", to_string(kibibyte_count(k: m.available)), " kB at a quiescent census boot",
], "")),
},
}
}
}
8 changes: 4 additions & 4 deletions dag/gunbc/machine_intake/host_resource_observation.dag
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import product.fabric.supply { UnmetShapeAxis, unmet_shape_axes }
// on two nodes, more available memory than total). None of them is resolved by picking the first
// or last line -- a pick is an answer the host never gave.
type ResourceUnread
= SectionAbsent { section: NonEmptyStr }
= ResourceSectionAbsent { section: NonEmptyStr }
| SectionUnparsed { section: NonEmptyStr, detail: String }
| SectionInconsistent { section: NonEmptyStr, detail: String }

Expand Down Expand Up @@ -90,7 +90,7 @@ fn nat_token(t: String) -> Nat? {
// not a count each refuse; none of them is read as a count.
fn observe_threads(nproc: List<String>?) -> ObservationAttempt<HardwareThreadCount, ResourceUnread> {
match nproc {
Absent => ObservationRefused { cause: SectionAbsent { section: "nproc" } }
Absent => ObservationRefused { cause: ResourceSectionAbsent { section: "nproc" } }
Present { value: out } => {
let lines = non_blank(out: out)
if count(lines) != 1 {
Expand Down Expand Up @@ -152,7 +152,7 @@ fn has_duplicate(xs: List<Nat>) -> Bool {
// refusal naming the line verbatim, so the diagnostic names the offending record.
fn observe_numa(numa: List<String>?) -> ObservationAttempt<List<NumaNode>, ResourceUnread> {
match numa {
Absent => ObservationRefused { cause: SectionAbsent { section: "numa" } }
Absent => ObservationRefused { cause: ResourceSectionAbsent { section: "numa" } }
Present { value: out } => {
let lines = filter(non_blank(out: out), l => is_node_cpus_line(line: l))
let attempts = map(lines, l => numa_record_attempt(line: l))
Expand Down Expand Up @@ -218,7 +218,7 @@ fn memory_field(metrics: List<MemoryMetric>, field: NonEmptyStr) -> ObservationA
// only these two names), each name exactly once, and available may not exceed total.
fn observe_memory(meminfo: List<String>?) -> ObservationAttempt<HostMemoryReading, ResourceUnread> {
match meminfo {
Absent => ObservationRefused { cause: SectionAbsent { section: "meminfo" } }
Absent => ObservationRefused { cause: ResourceSectionAbsent { section: "meminfo" } }
Present { value: out } => {
let lines = non_blank(out: out)
let metrics = flat_map(lines, l => match meminfo_line_metric(line: l) { Present { value: m } => [m] Absent => [] })
Expand Down
Loading