Skip to content
Merged
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
110 changes: 72 additions & 38 deletions dsl/gunbc/runner_deploy_emit.dag
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,24 @@ import extdeps.github.actions { RunnerSpec, SelfHosted, HostedRunner, RunsOnExpr
import std.measure { byte_size_count, hardware_thread_count_value, percent_count }
import std.types { String, List, Bool }
import std.layout {
Doc, DocConcat,
Doc, DocConcat, DocLine,
LayoutProtocol,
doc_concat, doc_line_of,
doc_text, doc_concat, doc_line_of,
render
}
import std.languages { SerializationKnobs, SerialField, SerialValue, ScalarValue, json_record_knobs }
import std.serialize { serial_scalar, serial_field, serialize_fields_doc, serialize_record_doc }

data runner_deploy_protocol: LayoutProtocol = LayoutProtocol { indent_unit: "" }

data manifest_record_knobs: SerializationKnobs = {
assign: "=",
entry_separator: " ",
open: "",
close: "",
quote_keys: false,
value_quote: null
}

fn runner_labels_text(spec: RunnerSpec) -> String {
match spec {
Expand All @@ -51,23 +61,20 @@ fn runner_labels_text(spec: RunnerSpec) -> String {
}
}

fn manifest_host_text(h: RunnerHostDeployment) -> String {
concat(
concat(
concat(
concat(
concat(
concat(concat("host=", h.identity), concat(" runner_count=", to_string(h.runner_count))),
concat(" runner_slice_cap_bytes=", to_string(byte_size_count(b: h.runner_slice_cap)))
),
concat(" per_slot_memory_max_bytes=", to_string(byte_size_count(b: h.per_runner_memory_cap)))
),
concat(" build_tokens=", to_string(hardware_thread_count_value(t: h.build_tokens)))
),
concat(" labels=", runner_labels_text(spec: h.runner))
),
concat(" cpu_weight=", to_string(workload_class_cpu_weight(wc: h.workload_class)))
)
fn manifest_host_fields(h: RunnerHostDeployment) -> List<SerialField> {
[
serial_field(key: "host", value: serial_scalar(text: h.identity)),
serial_field(key: "runner_count", value: serial_scalar(text: to_string(h.runner_count))),
serial_field(key: "runner_slice_cap_bytes", value: serial_scalar(text: to_string(byte_size_count(b: h.runner_slice_cap)))),
serial_field(key: "per_slot_memory_max_bytes", value: serial_scalar(text: to_string(byte_size_count(b: h.per_runner_memory_cap)))),
serial_field(key: "build_tokens", value: serial_scalar(text: to_string(hardware_thread_count_value(t: h.build_tokens)))),
serial_field(key: "labels", value: serial_scalar(text: runner_labels_text(spec: h.runner))),
serial_field(key: "cpu_weight", value: serial_scalar(text: to_string(workload_class_cpu_weight(wc: h.workload_class))))
]
}

fn manifest_host_doc(h: RunnerHostDeployment) -> Doc {
serialize_fields_doc(knobs: manifest_record_knobs, fields: manifest_host_fields(h: h))
}

fn runner_plan_doc() -> Doc {
Expand All @@ -76,17 +83,22 @@ fn runner_plan_doc() -> Doc {
doc_line_of(text: concat("runner-deploy UNSOUND: ", reason))
PlanDerived { hosts: hosts, fleet_concurrent_runs: total } =>
doc_concat(parts: append(
map(hosts, h => doc_line_of(text: concat("runner-deploy ", manifest_host_text(h: h)))),
map(hosts, h => doc_concat(parts: [doc_text(text: "runner-deploy "), manifest_host_doc(h: h), DocLine])),
items: [doc_line_of(text: concat("runner-deploy fleet_concurrent_runs=", to_string(total)))]
))
}
}

fn session_host_text(h: SessionHostReservation) -> String {
concat(
concat(concat("host=", h.identity), concat(" sessions=", to_string(h.session_count))),
concat(" per_session_cap_bytes=", to_string(byte_size_count(b: h.per_session_reservation)))
)
fn session_host_fields(h: SessionHostReservation) -> List<SerialField> {
[
serial_field(key: "host", value: serial_scalar(text: h.identity)),
serial_field(key: "sessions", value: serial_scalar(text: to_string(h.session_count))),
serial_field(key: "per_session_cap_bytes", value: serial_scalar(text: to_string(byte_size_count(b: h.per_session_reservation))))
]
}

fn session_host_doc(h: SessionHostReservation) -> Doc {
serialize_fields_doc(knobs: manifest_record_knobs, fields: session_host_fields(h: h))
}

fn session_plan_doc() -> Doc {
Expand All @@ -101,7 +113,7 @@ fn session_plan_doc() -> Doc {
sample_count: samples
} =>
doc_concat(parts: append(
map(hosts, h => doc_line_of(text: concat("session-reservation ", session_host_text(h: h)))),
map(hosts, h => doc_concat(parts: [doc_text(text: "session-reservation "), session_host_doc(h: h), DocLine])),
items: [doc_line_of(text: concat(
concat("session-reservation fleet_concurrent_sessions=", to_string(total)),
concat(" measured_peak_coverage=", concat(to_string(covered), concat("/", to_string(samples))))
Expand All @@ -110,23 +122,27 @@ fn session_plan_doc() -> Doc {
}
}

fn operating_row_text(r: OperatingPointRow) -> String {
concat(
concat(
concat(" per_session_cap_bytes=", to_string(byte_size_count(b: r.per_session_cap))),
concat(" concurrency=", to_string(r.session_concurrency))
),
concat(
concat(" sessions_slice_bytes=", to_string(byte_size_count(b: r.sessions_slice))),
concat(" peak_coverage=", concat(to_string(r.coverage_count), concat("/", to_string(r.sample_count))))
)
)
fn operating_row_fields(r: OperatingPointRow) -> List<SerialField> {
[
serial_field(key: "per_session_cap_bytes", value: serial_scalar(text: to_string(byte_size_count(b: r.per_session_cap)))),
serial_field(key: "concurrency", value: serial_scalar(text: to_string(r.session_concurrency))),
serial_field(key: "sessions_slice_bytes", value: serial_scalar(text: to_string(byte_size_count(b: r.sessions_slice)))),
serial_field(key: "peak_coverage", value: serial_scalar(text: concat(concat(to_string(r.coverage_count), "/"), to_string(r.sample_count))))
]
}

fn operating_row_doc(r: OperatingPointRow) -> Doc {
doc_concat(parts: [
doc_text(text: " "),
serialize_fields_doc(knobs: manifest_record_knobs, fields: operating_row_fields(r: r)),
DocLine
])
}

fn operating_curve_doc() -> Doc {
doc_concat(parts: concat(
[doc_line_of(text: "operating-curve (SESSION-side coverage only: operator selects one per-session operating point grounded in measured per-session peaks min 0.28 / median ~2.06 / max 8.27 GiB; sessions_slice = per_session_cap*concurrency. The per-host runner_count is budget-derived from runner_slice_cap and reported on the runner-deploy lines above, independent of this curve):")],
map(operating_curve(), r => doc_line_of(text: operating_row_text(r: r)))
map(operating_curve(), r => operating_row_doc(r: r))
))
}

Expand Down Expand Up @@ -177,3 +193,21 @@ fn expected_runner_deploy_manifest() -> String {
fn runner_deploy_drifted(committed: String) -> Bool {
!(committed == expected_runner_deploy_manifest())
}

fn expected_runner_deploy_manifest_json() -> String {
match runner_deployment_plan() {
PlanUnsound { reason: reason } =>
render(doc: doc_line_of(text: concat("runner-deploy UNSOUND: ", reason)), proto: runner_deploy_protocol)
PlanDerived { hosts: hosts, fleet_concurrent_runs: _ } =>
render(
doc: doc_concat(parts: map(hosts, h =>
doc_concat(parts: [
doc_text(text: "runner-deploy "),
serialize_record_doc(knobs: json_record_knobs, fields: manifest_host_fields(h: h)),
DocLine
])
)),
proto: runner_deploy_protocol
)
}
}
52 changes: 40 additions & 12 deletions dsl/std/languages.dag
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ type CommentSyntax {
doc_prefix: String?
}

type SerializationKnobs {
assign: String
entry_separator: String
open: String
close: String
quote_keys: Bool
value_quote: String?
}

type SerialField {
key: String
value: SerialValue
}

type SerialValue
= ScalarValue { text: String }
| RecordValue { fields: List<SerialField> }

type NamingConvention {
type_case: NamingCase
function_case: NamingCase
Expand All @@ -33,13 +51,6 @@ type ImportGrouping
= StdExternalLocal
| NoGrouping

type FormatModel {
indent: IndentStyle
max_line_width: Int
import_grouping: ImportGrouping
trailing_newline: Bool
}

type ImportTrigger
= TypeUsageTrigger { type_name: String }
| TraitImplTrigger { trait_name: String }
Expand Down Expand Up @@ -348,6 +359,7 @@ type ConfigFormat {
name: String
extensions: List<String>
comment: CommentSyntax
record: SerializationKnobs?
}

data rust_language: Language = {
Expand Down Expand Up @@ -1122,7 +1134,8 @@ data yaml_format: ConfigFormat = {
block_start: null,
block_end: null,
doc_prefix: null
}
},
record: { assign: ": ", entry_separator: "\n", open: "", close: "", quote_keys: false, value_quote: null }
}

data toml_format: ConfigFormat = {
Expand All @@ -1134,7 +1147,8 @@ data toml_format: ConfigFormat = {
block_start: null,
block_end: null,
doc_prefix: null
}
},
record: { assign: " = ", entry_separator: "\n", open: "", close: "", quote_keys: false, value_quote: "\"" }
}

data makefile_format: ConfigFormat = {
Expand All @@ -1146,7 +1160,8 @@ data makefile_format: ConfigFormat = {
block_start: null,
block_end: null,
doc_prefix: null
}
},
record: { assign: " = ", entry_separator: "\n", open: "", close: "", quote_keys: false, value_quote: null }
}

data gitignore_format: ConfigFormat = {
Expand All @@ -1158,7 +1173,8 @@ data gitignore_format: ConfigFormat = {
block_start: null,
block_end: null,
doc_prefix: null
}
},
record: null
}

data css_format: ConfigFormat = {
Expand All @@ -1170,5 +1186,17 @@ data css_format: ConfigFormat = {
block_start: "/*",
block_end: "*/",
doc_prefix: null
}
},
record: null
}

data json_record_knobs: SerializationKnobs = {
assign: ": ",
entry_separator: ", ",
open: "{",
close: "}",
quote_keys: true,
value_quote: "\""
}

data value_quote_escape_dissolution_trigger: String = "dissolve-on (two gaps): (1) SerializationKnobs.value_quote is a wrap-char (concat(q,t,q)), NOT RFC-8259 JSON or TOML string escaping — a value containing a double-quote or newline produces silently invalid output; dissolution = real string-escape authority citing RFC 8259 §7 (JSON) and TOML v1.0 §String (TOML) in std.serialize. (2) ScalarValue { text: String } lacks typed scalars — Int/Bool values serialize as quoted strings (e.g. runner_count '4' not 4), making json_record_knobs non-conforming JSON the moment a real typed consumer arrives; dissolution = split SerialValue into TypedScalar { kind: ScalarKind, text: String } where ScalarKind = StringKind | NumberKind | BoolKind drives value_quote elision; no live bug today (manifest values are all stringified by to_string() at the call site)"
2 changes: 2 additions & 0 deletions dsl/std/render.dag
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,5 @@ type GeneratorHeader {
generator: String
regen_command: String
}

data output_format_dissolution_trigger: String = "dissolve-on: gitignore consumer in extdeps.git.gitignore migrates to std.serialize.SerializationKnobs + std.layout.LayoutProtocol + std.languages.ConfigFormat; OutputFormat.name -> ConfigFormat.name, OutputFormat.comment_prefix -> CommentSyntax.line_prefix, OutputFormat.indent_unit -> LayoutProtocol.indent_unit; residue = SerializationKnobs (kv_separator, list_prefix)"
55 changes: 55 additions & 0 deletions dsl/std/serialize.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module std.serialize

import std.types { String, Bool, List }
import std.layout { Doc, DocConcat, doc_text, doc_concat }
import std.languages { SerializationKnobs, SerialField, SerialValue, ScalarValue, RecordValue }

fn serial_scalar(text: String) -> SerialValue {
ScalarValue { text: text }
}

fn serial_field(key: String, value: SerialValue) -> SerialField {
SerialField { key: key, value: value }
}

fn serialize_value_doc(knobs: SerializationKnobs, v: SerialValue) -> Doc {
match v {
ScalarValue { text: t } =>
match knobs.value_quote {
null => doc_text(text: t)
q => doc_text(text: concat(concat(q, t), q))
}
RecordValue { fields: fields } =>
serialize_record_doc(knobs: knobs, fields: fields)
}
}

fn serialize_field_doc(knobs: SerializationKnobs, f: SerialField) -> Doc {
let key_text = if knobs.quote_keys {
concat(concat("\"", f.key), "\"")
} else {
f.key
}
doc_concat(parts: [
doc_text(text: concat(key_text, knobs.assign)),
serialize_value_doc(knobs: knobs, v: f.value)
])
}

fn serialize_fields_doc(knobs: SerializationKnobs, fields: List<SerialField>) -> Doc {
doc_concat(parts: fold(fields, init: [], f: (acc, fld) =>
if count(acc) == 0 {
[serialize_field_doc(knobs: knobs, f: fld)]
} else {
concat(acc, [doc_text(text: knobs.entry_separator), serialize_field_doc(knobs: knobs, f: fld)])
}
))
}

fn serialize_record_doc(knobs: SerializationKnobs, fields: List<SerialField>) -> Doc {
doc_concat(parts: [
doc_text(text: knobs.open),
serialize_fields_doc(knobs: knobs, fields: fields),
doc_text(text: knobs.close)
])
}
Loading