1use 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]
160pub fn optimization_report_json(report: &OptimizationReportV1) -> String {
165 serde_json::to_string(report).expect("optimization report should serialize") + "\n"
166}
167
168#[must_use]
169pub 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}