Skip to content

Commit 8bc6232

Browse files
committed
perf(sandbox): streaming SHA256, spawn_blocking for identity resolution
Key changes: - Replace full file read + SHA256 with streaming 64KB-buffered hash (saves 124MB allocation for node binary) - Wrap evaluate_opa_tcp in spawn_blocking to prevent blocking tokio runtime during heavy /proc I/O and SHA256 computation - Add file-based perf logging for profiling proxy latency phases Profiling data (node binary, 124MB): - Cold TOFU: ~890ms (read+hash), warm: 0ms (cache hit) - evaluate_opa_tcp: cold=1002ms, warm=11ms - OPA evaluation: 1ms - DNS+TCP connect: 166-437ms Made-with: Cursor
1 parent b4e20c1 commit 8bc6232

File tree

3 files changed

+243
-31
lines changed

3 files changed

+243
-31
lines changed

crates/openshell-sandbox/src/identity.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ use std::os::unix::fs::MetadataExt;
1717
use std::path::{Path, PathBuf};
1818
use std::sync::Mutex;
1919

20+
fn perf_log(msg: &str) {
21+
use std::io::Write;
22+
for path in &["/var/log/openshell-perf.log", "/tmp/openshell-perf.log"] {
23+
if let Ok(mut f) = std::fs::OpenOptions::new()
24+
.create(true)
25+
.append(true)
26+
.open(path)
27+
{
28+
let now = std::time::SystemTime::now()
29+
.duration_since(std::time::UNIX_EPOCH)
30+
.unwrap_or_default();
31+
let _ = writeln!(f, "[{:.3}] {}", now.as_secs_f64(), msg);
32+
return;
33+
}
34+
}
35+
eprintln!("PERF_LOG_FALLBACK: {msg}");
36+
}
37+
2038
#[derive(Clone)]
2139
struct FileFingerprint {
2240
len: u64,
@@ -100,6 +118,7 @@ impl BinaryIdentityCache {
100118
where
101119
F: FnMut(&Path) -> Result<String>,
102120
{
121+
let start = std::time::Instant::now();
103122
let metadata = std::fs::metadata(path)
104123
.map_err(|error| miette::miette!("Failed to stat {}: {error}", path.display()))?;
105124
let fingerprint = FileFingerprint::from_metadata(&metadata);
@@ -114,10 +133,24 @@ impl BinaryIdentityCache {
114133
if let Some(cached_binary) = &cached
115134
&& cached_binary.fingerprint == fingerprint
116135
{
136+
perf_log(&format!(
137+
" verify_or_cache: {}ms CACHE HIT path={}",
138+
start.elapsed().as_millis(), path.display()
139+
));
117140
return Ok(cached_binary.hash.clone());
118141
}
119142

143+
perf_log(&format!(
144+
" verify_or_cache: CACHE MISS size={} path={}",
145+
metadata.len(), path.display()
146+
));
147+
148+
let hash_start = std::time::Instant::now();
120149
let current_hash = hash_file(path)?;
150+
perf_log(&format!(
151+
" verify_or_cache SHA256: {}ms path={}",
152+
hash_start.elapsed().as_millis(), path.display()
153+
));
121154

122155
let mut hashes = self
123156
.hashes
@@ -143,6 +176,11 @@ impl BinaryIdentityCache {
143176
},
144177
);
145178

179+
perf_log(&format!(
180+
" verify_or_cache TOTAL (cold): {}ms path={}",
181+
start.elapsed().as_millis(), path.display()
182+
));
183+
146184
Ok(current_hash)
147185
}
148186
}

crates/openshell-sandbox/src/procfs.rs

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ use std::path::Path;
1111
#[cfg(target_os = "linux")]
1212
use std::path::PathBuf;
1313

14+
fn perf_log(msg: &str) {
15+
use std::io::Write;
16+
for path in &["/var/log/openshell-perf.log", "/tmp/openshell-perf.log"] {
17+
if let Ok(mut f) = std::fs::OpenOptions::new()
18+
.create(true)
19+
.append(true)
20+
.open(path)
21+
{
22+
let now = std::time::SystemTime::now()
23+
.duration_since(std::time::UNIX_EPOCH)
24+
.unwrap_or_default();
25+
let _ = writeln!(f, "[{:.3}] {}", now.as_secs_f64(), msg);
26+
return;
27+
}
28+
}
29+
eprintln!("PERF_LOG_FALLBACK: {msg}");
30+
}
31+
1432
/// Read the binary path of a process via `/proc/{pid}/exe` symlink.
1533
///
1634
/// Returns the canonical path to the executable that the process is running.
@@ -48,9 +66,33 @@ pub fn resolve_tcp_peer_binary(entrypoint_pid: u32, peer_port: u16) -> Result<Pa
4866
/// Needed for the ancestor walk: we must know the PID to walk `/proc/<pid>/status` PPid chain.
4967
#[cfg(target_os = "linux")]
5068
pub fn resolve_tcp_peer_identity(entrypoint_pid: u32, peer_port: u16) -> Result<(PathBuf, u32)> {
69+
let start = std::time::Instant::now();
70+
71+
let phase = std::time::Instant::now();
5172
let inode = parse_proc_net_tcp(entrypoint_pid, peer_port)?;
73+
perf_log(&format!(
74+
" parse_proc_net_tcp: {}ms inode={}",
75+
phase.elapsed().as_millis(), inode
76+
));
77+
78+
let phase = std::time::Instant::now();
5279
let pid = find_pid_by_socket_inode(inode, entrypoint_pid)?;
80+
perf_log(&format!(
81+
" find_pid_by_socket_inode: {}ms pid={}",
82+
phase.elapsed().as_millis(), pid
83+
));
84+
85+
let phase = std::time::Instant::now();
5386
let path = binary_path(pid.cast_signed())?;
87+
perf_log(&format!(
88+
" binary_path: {}ms path={}",
89+
phase.elapsed().as_millis(), path.display()
90+
));
91+
92+
perf_log(&format!(
93+
" resolve_tcp_peer_identity TOTAL: {}ms",
94+
start.elapsed().as_millis()
95+
));
5496
Ok((path, pid))
5597
}
5698

@@ -227,34 +269,55 @@ fn parse_proc_net_tcp(pid: u32, peer_port: u16) -> Result<u64> {
227269
/// `/proc/<pid>/fd/` for processes running as a different user.
228270
#[cfg(target_os = "linux")]
229271
fn find_pid_by_socket_inode(inode: u64, entrypoint_pid: u32) -> Result<u32> {
272+
let start = std::time::Instant::now();
230273
let target = format!("socket:[{inode}]");
231274

232-
// First: scan descendants of the entrypoint process (targeted, most likely to succeed)
275+
let phase = std::time::Instant::now();
233276
let descendants = collect_descendant_pids(entrypoint_pid);
277+
perf_log(&format!(
278+
" collect_descendant_pids: {}ms count={}",
279+
phase.elapsed().as_millis(), descendants.len()
280+
));
281+
282+
let phase = std::time::Instant::now();
234283
for &pid in &descendants {
235284
if let Some(found) = check_pid_fds(pid, &target) {
285+
perf_log(&format!(
286+
" find_pid_by_socket_inode: {}ms found_pid={} scan=descendants",
287+
start.elapsed().as_millis(), found
288+
));
236289
return Ok(found);
237290
}
238291
}
292+
perf_log(&format!(
293+
" descendant_fd_scan (not found): {}ms",
294+
phase.elapsed().as_millis()
295+
));
239296

240-
// Fallback: scan all of /proc in case the process isn't in the tree
241-
// (e.g., if /proc/<pid>/task/<tid>/children wasn't available)
297+
let phase = std::time::Instant::now();
242298
if let Ok(proc_dir) = std::fs::read_dir("/proc") {
243299
for entry in proc_dir.flatten() {
244300
let name = entry.file_name();
245301
let pid: u32 = match name.to_string_lossy().parse() {
246302
Ok(p) => p,
247303
Err(_) => continue,
248304
};
249-
// Skip PIDs we already checked
250305
if descendants.contains(&pid) {
251306
continue;
252307
}
253308
if let Some(found) = check_pid_fds(pid, &target) {
309+
perf_log(&format!(
310+
" find_pid_by_socket_inode: {}ms found_pid={} scan=full_proc",
311+
start.elapsed().as_millis(), found
312+
));
254313
return Ok(found);
255314
}
256315
}
257316
}
317+
perf_log(&format!(
318+
" full_proc_scan (not found): {}ms",
319+
phase.elapsed().as_millis()
320+
));
258321

259322
Err(miette::miette!(
260323
"No process found owning socket inode {} \
@@ -318,9 +381,28 @@ fn collect_descendant_pids(root_pid: u32) -> Vec<u32> {
318381
/// same hash, or the request is denied.
319382
pub fn file_sha256(path: &Path) -> Result<String> {
320383
use sha2::{Digest, Sha256};
384+
use std::io::Read;
385+
386+
let start = std::time::Instant::now();
387+
let mut file = std::fs::File::open(path)
388+
.map_err(|e| miette::miette!("Failed to open {}: {e}", path.display()))?;
389+
let mut hasher = Sha256::new();
390+
let mut buf = [0u8; 65536];
391+
let mut total_read = 0u64;
392+
loop {
393+
let n = file.read(&mut buf).into_diagnostic()?;
394+
if n == 0 {
395+
break;
396+
}
397+
total_read += n as u64;
398+
hasher.update(&buf[..n]);
399+
}
321400

322-
let bytes = std::fs::read(path).into_diagnostic()?;
323-
let hash = Sha256::digest(&bytes);
401+
let hash = hasher.finalize();
402+
perf_log(&format!(
403+
" file_sha256: {}ms size={} path={}",
404+
start.elapsed().as_millis(), total_read, path.display()
405+
));
324406
Ok(hex::encode(hash))
325407
}
326408

0 commit comments

Comments
 (0)