Skip to main content

scale_audit/
scale_audit.rs

1//! Repeatable registration scale audit for release qualification.
2//!
3//! Two things are checked, not one: that the registry still answers correctly at
4//! scale (the page counts and the incremental transaction below), and that it does
5//! so inside a per-node time budget. Without the budget a tenfold regression would
6//! only make the printed numbers larger, which nobody reads in a CI log.
7//!
8//! The ceilings are order-of-magnitude guards, not a grade for the machine: they
9//! carry roughly eight times the headroom measured when the baseline was recorded
10//! (`docs/performance-baseline.md`), and both can be raised through the
11//! environment on a slow runner without editing this file.
12//! 这里检查两件事而不是一件:注册机在规模上仍然给出正确答案(下面的页数与增量事务),以及
13//! 它在那份每节点时间预算之内。没有预算时,十倍的性能退化只会让打印出来的数字更大,而 CI
14//! 日志里没人会去读那些数字。
15//! 上限是数量级的守卫,而不是给机器打分:它们带着记录基线时实测值约八倍的余量
16//! (`docs/performance-baseline.md`),两者都可以在慢机器上经环境变量抬高,无需改本文件。
17
18use std::time::Instant;
19
20use nichlink_run_method::{
21    FrameworkId, NodeId, OwnedAdmission, OwnedFlowContract, OwnedLocalizedText,
22    OwnedObjectContract, OwnedRegistrationRule, OwnedSourceLocation, RegistrationSnapshot,
23    Registry, root_node_id,
24};
25
26/// One per-node ceiling in microseconds, or `default` when the environment
27/// overrides it.
28/// 每节点上限(微秒);环境变量覆盖时用覆盖值。
29fn ceiling(name: &str, default: u128) -> u128 {
30    std::env::var(name)
31        .ok()
32        .and_then(|value| value.parse().ok())
33        .unwrap_or(default)
34}
35
36fn snapshot(namespace: &str, index: usize, parent: NodeId) -> RegistrationSnapshot {
37    let kind = format!("Face{index}");
38    RegistrationSnapshot {
39        namespace: namespace.to_owned(),
40        id: NodeId::from_namespaced_path(namespace, &format!("scale/{index}.rs"), &kind),
41        parent,
42        kind: kind.clone(),
43        preset: "default".to_owned(),
44        parts: String::new(),
45        params: String::new(),
46        handle: kind.clone(),
47        stable_name: None,
48        name: OwnedLocalizedText {
49            zh: kind.clone(),
50            en: kind.clone(),
51        },
52        summary: OwnedLocalizedText {
53            zh: String::new(),
54            en: String::new(),
55        },
56        exports: Vec::new(),
57        needs_registry: false,
58        registry_name: format!("face-{index}"),
59        getting_from_other_registry: None,
60        registry_rule_path: "<scale-audit>".to_owned(),
61        registry_rule: OwnedRegistrationRule {
62            required_preset: None,
63            required_parts: Vec::new(),
64            required_exports: Vec::new(),
65            required_handle_traits: Vec::new(),
66            required_part_traits: Vec::new(),
67        },
68        admission: OwnedAdmission {
69            allowed_paths: Vec::new(),
70            denied_paths: Vec::new(),
71        },
72        requires: Vec::new(),
73        provides: Vec::new(),
74        contract: OwnedObjectContract {
75            required_parts: Vec::new(),
76            provided_parts: Vec::new(),
77        },
78        flow: OwnedFlowContract::none(),
79        flow_provider: None,
80        handle_traits: Vec::new(),
81        part_traits: Vec::new(),
82        runtime_checks: Vec::new(),
83        plugin: None,
84        source: OwnedSourceLocation {
85            file: format!("scale/{index}.rs"),
86            line: 1,
87            column: 1,
88            function: kind,
89        },
90    }
91}
92
93fn main() {
94    let sizes = std::env::args()
95        .skip(1)
96        .map(|value| value.parse::<usize>().expect("size must be an integer"))
97        .collect::<Vec<_>>();
98    let sizes = if sizes.is_empty() {
99        vec![10_000, 100_000]
100    } else {
101        sizes
102    };
103    println!(
104        "nodes\tregister_ms\tregister_budget_ms\tindex_ms\tindex_budget_ms\tentries\tpages\tstatic_face_bytes"
105    );
106    // 40 µs and 20 µs per node: roughly eight times the 5.2 µs and 2.6 µs measured
107    // for 100 000 nodes when this budget was added.
108    // 每节点 40 µs 与 20 µs:约等于加入本预算时 100 000 个节点实测 5.2 µs 与 2.6 µs 的八倍。
109    let register_ceiling = ceiling("NICHLINK_SCALE_REGISTER_US", 40);
110    let index_ceiling = ceiling("NICHLINK_SCALE_INDEX_US", 20);
111    for size in sizes {
112        let namespace = format!("scale-{size}");
113        let root = Registry::root_for_namespace(FrameworkId::new("nichlink.scale"), &namespace);
114        let parent = root_node_id(&namespace);
115        let submissions = (0..size)
116            .map(|index| snapshot(&namespace, index, parent))
117            .collect::<Vec<_>>();
118        let mut registry = root;
119        let register_start = Instant::now();
120        registry
121            .register_snapshot_batch(submissions)
122            .expect("generated scale batch must register");
123        let register_ms = register_start.elapsed().as_millis();
124        let index_start = Instant::now();
125        let index = registry.index();
126        let index_ms = index_start.elapsed().as_millis();
127        let stats = registry.storage_stats();
128        let register_budget_ms = register_ceiling * size as u128 / 1000;
129        let index_budget_ms = index_ceiling * size as u128 / 1000;
130        println!(
131            "{size}\t{register_ms}\t{register_budget_ms}\t{index_ms}\t{index_budget_ms}\t{}\t{}\t{}",
132            index.len(),
133            stats.pages,
134            size * std::mem::size_of::<nichlink::StaticFace>()
135        );
136        assert!(
137            register_ms <= register_budget_ms,
138            "registering {size} nodes took {register_ms} ms, over the {register_budget_ms} ms              budget ({register_ceiling} µs per node); raise NICHLINK_SCALE_REGISTER_US if this              machine is simply slower, and update docs/performance-baseline.md if the baseline moved"
139        );
140        assert!(
141            index_ms <= index_budget_ms,
142            "indexing {size} nodes took {index_ms} ms, over the {index_budget_ms} ms budget              ({index_ceiling} µs per node); raise NICHLINK_SCALE_INDEX_US if this machine is              simply slower, and update docs/performance-baseline.md if the baseline moved"
143        );
144        let extra = snapshot(&namespace, size, parent);
145        registry
146            .register_snapshot_batch([extra])
147            .expect("incremental transaction must register");
148        assert_eq!(registry.index().len(), size + 2);
149    }
150}