Skip to main content

presolve_compiler/
production_reports.rs

1//! K15 immutable optimization and static runtime-cost reports.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    OptimizationPolicyId, ProductionChunkGraph, ProductionChunkKind, ProductionRuntimeArtifactV1,
7    ResumeBuildId,
8};
9
10pub const OPTIMIZATION_REPORT_SCHEMA_VERSION: u32 = 1;
11pub const RUNTIME_COST_REPORT_SCHEMA_VERSION: u32 = 1;
12
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct ProductionReportInputs {
15    pub dead_products_removed: u32,
16    pub constants_pooled: u32,
17    pub programs_deduplicated: u32,
18    pub shared_candidates_rejected: u32,
19    pub binding_writes_coalesced: u32,
20    pub development_bytes: u64,
21    pub production_bytes: u64,
22    pub cold_init_operation_count: u32,
23    pub resume_restore_operation_count: u32,
24    pub max_action_batch_operation_count: u32,
25    pub max_scheduler_batch_width: u32,
26    pub max_dom_patch_count_per_action: u32,
27    pub retained_slot_count: u32,
28}
29
30#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase", deny_unknown_fields)]
32pub struct OptimizationReportV1 {
33    pub schema_version: u32,
34    pub build_id: ResumeBuildId,
35    pub optimization_policy: OptimizationPolicyId,
36    pub dead_products_removed: u32,
37    pub constants_pooled: u32,
38    pub programs_deduplicated: u32,
39    pub shared_chunks_extracted: u32,
40    pub shared_candidates_rejected: u32,
41    pub binding_writes_coalesced: u32,
42    pub runtime_table_count: u32,
43    pub development_bytes: u64,
44    pub production_bytes: u64,
45    pub retained_exclusions: Vec<String>,
46    pub validation_status: String,
47}
48
49#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase", deny_unknown_fields)]
51pub struct RuntimeCostReportV1 {
52    pub schema_version: u32,
53    pub build_id: ResumeBuildId,
54    pub bootstrap_module_bytes: u64,
55    pub production_artifact_bytes: u64,
56    pub eager_program_count: u32,
57    pub lazy_root_chunk_count: u32,
58    pub shared_chunk_count: u32,
59    pub max_lazy_dependency_depth: u32,
60    pub runtime_table_count: u32,
61    pub runtime_record_count: u32,
62    pub estimated_boot_decode_units: u32,
63    pub estimated_boot_validation_units: u32,
64    pub estimated_cold_init_operation_count: u32,
65    pub estimated_resume_restore_operation_count: u32,
66    pub max_action_batch_operation_count: u32,
67    pub max_scheduler_batch_width: u32,
68    pub max_dom_patch_count_per_action: u32,
69    pub retained_slot_count: u32,
70}
71
72#[derive(Clone, Debug, Eq, PartialEq)]
73pub struct OptimizationInspectionQuery {
74    pub report: OptimizationReportV1,
75}
76
77#[derive(Clone, Debug, Eq, PartialEq)]
78pub struct RuntimeCostInspectionQuery {
79    pub report: RuntimeCostReportV1,
80}
81
82#[must_use]
83pub fn build_production_reports(
84    artifact: &ProductionRuntimeArtifactV1,
85    graph: &ProductionChunkGraph,
86    inputs: &ProductionReportInputs,
87) -> (OptimizationReportV1, RuntimeCostReportV1) {
88    let table_count = count_u32(artifact.tables.tables.len());
89    let table_records = count_u32(
90        artifact
91            .tables
92            .tables
93            .iter()
94            .map(|table| table.mappings.len())
95            .sum::<usize>(),
96    );
97    let shared_count = count_u32(
98        graph
99            .chunks
100            .iter()
101            .filter(|chunk| chunk.kind == ProductionChunkKind::Shared)
102            .count(),
103    );
104    let root_count = count_u32(graph.activation_plans.len());
105    let eager_program_count = graph
106        .chunks
107        .iter()
108        .find(|chunk| chunk.kind == ProductionChunkKind::Eager)
109        .map_or(0, |chunk| count_u32(chunk.programs.len()));
110    (
111        OptimizationReportV1 {
112            schema_version: OPTIMIZATION_REPORT_SCHEMA_VERSION,
113            build_id: artifact.build_id.clone(),
114            optimization_policy: artifact.optimization_policy.clone(),
115            dead_products_removed: inputs.dead_products_removed,
116            constants_pooled: inputs.constants_pooled,
117            programs_deduplicated: inputs.programs_deduplicated,
118            shared_chunks_extracted: shared_count,
119            shared_candidates_rejected: inputs.shared_candidates_rejected,
120            binding_writes_coalesced: inputs.binding_writes_coalesced,
121            runtime_table_count: table_count,
122            development_bytes: inputs.development_bytes,
123            production_bytes: inputs.production_bytes,
124            retained_exclusions: vec![
125                "cryptographic-signing".to_string(),
126                "wall-clock-timing".to_string(),
127            ],
128            validation_status: "valid".to_string(),
129        },
130        RuntimeCostReportV1 {
131            schema_version: RUNTIME_COST_REPORT_SCHEMA_VERSION,
132            build_id: artifact.build_id.clone(),
133            bootstrap_module_bytes: graph
134                .chunks
135                .iter()
136                .find(|chunk| chunk.kind == ProductionChunkKind::Eager)
137                .map_or(0, |chunk| {
138                    count_u64(chunk.provisional_module_filename.len())
139                }),
140            production_artifact_bytes: inputs.production_bytes,
141            eager_program_count,
142            lazy_root_chunk_count: root_count,
143            shared_chunk_count: shared_count,
144            max_lazy_dependency_depth: u32::from(shared_count > 0),
145            runtime_table_count: table_count,
146            runtime_record_count: table_records + count_u32(graph.chunks.len()),
147            estimated_boot_decode_units: table_records,
148            estimated_boot_validation_units: table_records + count_u32(graph.dependencies.len()),
149            estimated_cold_init_operation_count: inputs.cold_init_operation_count,
150            estimated_resume_restore_operation_count: inputs.resume_restore_operation_count,
151            max_action_batch_operation_count: inputs.max_action_batch_operation_count,
152            max_scheduler_batch_width: inputs.max_scheduler_batch_width,
153            max_dom_patch_count_per_action: inputs.max_dom_patch_count_per_action,
154            retained_slot_count: inputs.retained_slot_count,
155        },
156    )
157}
158
159#[must_use]
160///
161/// # Panics
162///
163/// Panics only if an in-memory compiler report cannot serialize.
164pub fn optimization_report_json(report: &OptimizationReportV1) -> String {
165    serde_json::to_string(report).expect("optimization report should serialize") + "\n"
166}
167
168#[must_use]
169///
170/// # Panics
171///
172/// Panics only if an in-memory compiler report cannot serialize.
173pub fn runtime_cost_report_json(report: &RuntimeCostReportV1) -> String {
174    serde_json::to_string(report).expect("runtime cost report should serialize") + "\n"
175}
176
177fn count_u32(value: usize) -> u32 {
178    u32::try_from(value).expect("production report count exceeds u32")
179}
180
181fn count_u64(value: usize) -> u64 {
182    u64::try_from(value).expect("production report byte count exceeds u64")
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crate::{
189        build_production_runtime_artifact, extract_production_chunk_graph,
190        ExecutableProgramFingerprint, ProductionRootChunkInput, ResumeBoundaryId, ResumeManifest,
191        SharedChunkCandidatePlan,
192    };
193    use std::str::FromStr;
194
195    #[test]
196    fn k15_reports_recompute_exact_static_products_without_timing() {
197        let manifest = ResumeManifest {
198            schema_version: 6,
199            build_id: ResumeBuildId::zero_sentinel(),
200            snapshot_schema_version: 1,
201            runtime_protocol_version: 1,
202            application_root_boundary_id: ResumeBoundaryId::from_str("resume-boundary:root")
203                .expect("boundary"),
204            boundaries: Vec::new(),
205            slot_schemas: Vec::new(),
206            capture_programs: Vec::new(),
207            restore_programs: Vec::new(),
208            chunks: Vec::new(),
209            activations: Vec::new(),
210            anchors: Vec::new(),
211            events: Vec::new(),
212            phase_i_component_resume_records: Vec::new(),
213            phase_i_form_resume_records: Vec::new(),
214        };
215        let graph = extract_production_chunk_graph(
216            &SharedChunkCandidatePlan {
217                candidates: Vec::new(),
218                rejections: Vec::new(),
219            },
220            &[ProductionRootChunkInput {
221                activation_root_id: "root".to_string(),
222                root_kind: "interaction".to_string(),
223                programs: vec![ExecutableProgramFingerprint::for_canonical_opcode_stream(
224                    b"a",
225                )],
226            }],
227        )
228        .expect("graph")
229        .0;
230        let artifact = build_production_runtime_artifact(&manifest, &graph).expect("artifact");
231        let inputs = ProductionReportInputs {
232            dead_products_removed: 1,
233            constants_pooled: 2,
234            programs_deduplicated: 3,
235            shared_candidates_rejected: 4,
236            binding_writes_coalesced: 5,
237            development_bytes: 100,
238            production_bytes: 80,
239            cold_init_operation_count: 6,
240            resume_restore_operation_count: 7,
241            max_action_batch_operation_count: 8,
242            max_scheduler_batch_width: 9,
243            max_dom_patch_count_per_action: 10,
244            retained_slot_count: 11,
245        };
246        let (optimization, cost) = build_production_reports(&artifact, &graph, &inputs);
247        assert_eq!(optimization.production_bytes, 80);
248        assert_eq!(
249            cost.runtime_table_count,
250            count_u32(artifact.tables.tables.len())
251        );
252        let bytes = optimization_report_json(&optimization) + &runtime_cost_report_json(&cost);
253        assert!(!bytes.contains("millisecond"));
254        assert!(!bytes.contains("timestamp"));
255    }
256}