@@ -11,6 +11,9 @@ const LAYER_DIR: &str = "/l";
1111const COW_DIR : & str = "/cow" ;
1212const NEWROOT : & str = "/newroot" ;
1313const POLL_INTERVAL : Duration = Duration :: from_millis ( 2 ) ;
14+ // NICs probe in single-digit ms; a missing one must degrade to the DHCP
15+ // fallback, not stall rootfs handoff for the full disk budget (10s).
16+ const NIC_TIMEOUT : Duration = Duration :: from_millis ( 200 ) ;
1417
1518pub fn run ( ) -> ! {
1619 // Best-effort: if devtmpfs fails there is no console either; later
@@ -35,10 +38,16 @@ pub fn run() -> ! {
3538 Ok ( cfg) => cfg,
3639 Err ( err) => sys:: fatal ( & err, cfg:: debug_requested ( & cmdline) ) ,
3740 } ;
38- if let Err ( err) = assemble ( & cfg) {
41+ let mut marks = Marks :: new ( ) ;
42+ if let Err ( err) = assemble ( & cfg, & mut marks) {
3943 sys:: fatal ( & err, cfg. debug ) ;
4044 }
4145
46+ // One deferred trace line (µs, cumulative since sandbox-init start):
47+ // per-phase console writes would perturb exactly what they measure.
48+ if cfg. trace {
49+ println ! ( "sandbox-init: trace{}" , marks. render( ) ) ;
50+ }
4251 // Single marker line; boot-bench.sh keys on it. Uptime is kernel-relative,
4352 // directly comparable with printk timestamps on the serial log.
4453 println ! (
@@ -50,21 +59,54 @@ pub fn run() -> ! {
5059 sys:: fatal ( & format ! ( "exec {}: {err}" , cfg. init) , cfg. debug )
5160}
5261
53- fn assemble ( cfg : & BootCfg ) -> Result < ( ) , String > {
54- let mut lower = Vec :: with_capacity ( cfg. layers . len ( ) ) ;
55- for ( i, id) in cfg. layers . iter ( ) . enumerate ( ) {
56- let dev = resolve_disk ( id, cfg. timeout ) ?;
62+ /// Cumulative µs checkpoints since sandbox-init start.
63+ struct Marks {
64+ start : Instant ,
65+ points : Vec < ( & ' static str , u128 ) > ,
66+ }
67+
68+ impl Marks {
69+ fn new ( ) -> Self {
70+ Marks {
71+ start : Instant :: now ( ) ,
72+ points : Vec :: new ( ) ,
73+ }
74+ }
75+
76+ fn mark ( & mut self , label : & ' static str ) {
77+ self . points . push ( ( label, self . start . elapsed ( ) . as_micros ( ) ) ) ;
78+ }
79+
80+ fn render ( & self ) -> String {
81+ let mut out = String :: new ( ) ;
82+ for ( label, us) in & self . points {
83+ out. push_str ( & format ! ( " {label}@{us}us" ) ) ;
84+ }
85+ out
86+ }
87+ }
88+
89+ fn assemble ( cfg : & BootCfg , marks : & mut Marks ) -> Result < ( ) , String > {
90+ let mut ids: Vec < String > = cfg. layers . clone ( ) ;
91+ ids. push ( cfg. cow . clone ( ) ) ;
92+ let mut devs = resolve_disks ( & ids, cfg. timeout ) ?;
93+ let cow_dev = devs. pop ( ) . expect ( "cow id was pushed above" ) ;
94+ marks. mark ( "resolve" ) ;
95+
96+ let mut lower = Vec :: with_capacity ( devs. len ( ) ) ;
97+ for ( i, dev) in devs. iter ( ) . enumerate ( ) {
5798 let mnt = format ! ( "{LAYER_DIR}/{i}" ) ;
5899 mkdir_all ( & mnt) ?;
59- sys:: mount ( & dev, & mnt, Some ( "erofs" ) , libc:: MS_RDONLY , None ) ?;
100+ sys:: mount ( dev, & mnt, Some ( "erofs" ) , libc:: MS_RDONLY , None ) ?;
60101 lower. push ( mnt) ;
61102 }
103+ marks. mark ( "erofs" ) ;
62104
63- let cow_dev = resolve_disk ( & cfg. cow , cfg. timeout ) ?;
64105 mkdir_all ( COW_DIR ) ?;
65106 sys:: mount ( & cow_dev, COW_DIR , Some ( "ext4" ) , libc:: MS_NOATIME , None ) ?;
66107 mkdir_all ( & format ! ( "{COW_DIR}/upper" ) ) ?;
67108 mkdir_all ( & format ! ( "{COW_DIR}/work" ) ) ?;
109+ marks. mark ( "cow" ) ;
68110
69111 mkdir_all ( NEWROOT ) ?;
70112 sys:: mount (
@@ -74,6 +116,7 @@ fn assemble(cfg: &BootCfg) -> Result<(), String> {
74116 0 ,
75117 Some ( & cfg:: overlay_data ( & lower, COW_DIR ) ) ,
76118 ) ?;
119+ marks. mark ( "overlay" ) ;
77120
78121 if let Some ( hostname) = & cfg. hostname {
79122 sys:: sethostname ( hostname) ?;
@@ -82,14 +125,17 @@ fn assemble(cfg: &BootCfg) -> Result<(), String> {
82125 let _ = fs:: write ( format ! ( "{NEWROOT}/etc/machine-id" ) , "" ) ;
83126
84127 persist_network ( cfg) ;
128+ marks. mark ( "net" ) ;
85129
86130 for dir in [ "dev" , "proc" , "sys" , "run" ] {
87131 let _ = fs:: create_dir_all ( format ! ( "{NEWROOT}/{dir}" ) ) ;
88132 }
89133 for mnt in [ "/dev" , "/proc" , "/sys" ] {
90134 sys:: move_mount ( mnt, & format ! ( "{NEWROOT}{mnt}" ) ) ?;
91135 }
92- sys:: switch_root ( NEWROOT )
136+ sys:: switch_root ( NEWROOT ) ?;
137+ marks. mark ( "switch" ) ;
138+ Ok ( ( ) )
93139}
94140
95141/// Materializes kernel ip= params (cocoon CNI static flow) as MAC-matched
@@ -107,7 +153,7 @@ fn persist_network(cfg: &BootCfg) {
107153 return ;
108154 }
109155 for ip in & cfg. ips {
110- let Some ( mac) = wait_nic_mac ( & ip. device , cfg . timeout ) else {
156+ let Some ( mac) = wait_nic_mac ( & ip. device , NIC_TIMEOUT ) else {
111157 eprintln ! (
112158 "sandbox-init: WARN: NIC {} not found, static config skipped" ,
113159 ip. device
@@ -142,28 +188,43 @@ fn mkdir_all(path: &str) -> Result<(), String> {
142188 fs:: create_dir_all ( path) . map_err ( |err| format ! ( "mkdir {path}: {err}" ) )
143189}
144190
145- /// CH disks carry a virtio-blk serial; FC has no serials, so cocoon passes
146- /// /dev/vdX paths there. 2ms polling — virtio probe completes a few ms into
147- /// boot, so the first or second try normally hits; the old initramfs hook's
148- /// 1s sleep granularity was a visible cost .
149- fn resolve_disk ( id : & str , timeout : Duration ) -> Result < String , String > {
191+ /// Resolves every disk in one sysfs sweep per poll iteration instead of a
192+ /// scan per disk. CH disks carry a virtio-blk serial; FC has no serials, so
193+ /// cocoon passes /dev/vdX paths there. 2ms polling — virtio probe completes
194+ /// a few ms into boot, so the first or second try normally hits .
195+ fn resolve_disks ( ids : & [ String ] , timeout : Duration ) -> Result < Vec < String > , String > {
150196 let deadline = Instant :: now ( ) + timeout;
197+ let mut found: Vec < Option < String > > = vec ! [ None ; ids. len( ) ] ;
151198 loop {
152- if let Some ( dev) = try_resolve ( id) {
153- return Ok ( dev) ;
199+ for ( i, id) in ids. iter ( ) . enumerate ( ) {
200+ if found[ i] . is_none ( ) && id. starts_with ( "/dev/" ) && sys:: is_block_dev ( id) {
201+ found[ i] = Some ( id. clone ( ) ) ;
202+ }
203+ }
204+ if found. iter ( ) . any ( Option :: is_none) {
205+ scan_serials ( ids, & mut found) ;
206+ }
207+ if found. iter ( ) . all ( Option :: is_some) {
208+ return Ok ( found. into_iter ( ) . flatten ( ) . collect ( ) ) ;
154209 }
155210 if Instant :: now ( ) >= deadline {
156- return Err ( format ! ( "disk {id} not found within {timeout:?}" ) ) ;
211+ let missing: Vec < & str > = ids
212+ . iter ( )
213+ . zip ( & found)
214+ . filter ( |( _, f) | f. is_none ( ) )
215+ . map ( |( id, _) | id. as_str ( ) )
216+ . collect ( ) ;
217+ return Err ( format ! ( "disks not found within {timeout:?}: {missing:?}" ) ) ;
157218 }
158219 std:: thread:: sleep ( POLL_INTERVAL ) ;
159220 }
160221}
161222
162- fn try_resolve ( id : & str ) -> Option < String > {
163- if id . starts_with ( "/dev/" ) {
164- return sys :: is_block_dev ( id ) . then ( || id . to_string ( ) ) ;
165- }
166- for entry in fs :: read_dir ( "/sys/block" ) . ok ( ) ? . flatten ( ) {
223+ fn scan_serials ( ids : & [ String ] , found : & mut [ Option < String > ] ) {
224+ let Ok ( entries ) = fs :: read_dir ( "/sys/block" ) else {
225+ return ;
226+ } ;
227+ for entry in entries . flatten ( ) {
167228 let Ok ( name) = entry. file_name ( ) . into_string ( ) else {
168229 continue ;
169230 } ;
@@ -175,14 +236,18 @@ fn try_resolve(id: &str) -> Option<String> {
175236 format ! ( "/sys/block/{name}/serial" ) ,
176237 format ! ( "/sys/block/{name}/device/serial" ) ,
177238 ] ;
178- if paths
179- . iter ( )
180- . any ( |p| fs:: read_to_string ( p) . is_ok_and ( |s| s. trim_end ( ) == id) )
181- {
182- return Some ( format ! ( "/dev/{name}" ) ) ;
239+ for path in paths {
240+ let Ok ( serial) = fs:: read_to_string ( & path) else {
241+ continue ;
242+ } ;
243+ let serial = serial. trim_end ( ) ;
244+ for ( i, id) in ids. iter ( ) . enumerate ( ) {
245+ if found[ i] . is_none ( ) && id == serial {
246+ found[ i] = Some ( format ! ( "/dev/{name}" ) ) ;
247+ }
248+ }
183249 }
184250 }
185- None
186251}
187252
188253fn uptime ( ) -> String {
0 commit comments