feat: 池状态接口加高水位跟踪与 Status 峰值字段

This commit is contained in:
2026-10-06 15:38:09 +00:00
parent 6422c142ed
commit a08652252b
5 changed files with 125 additions and 21 deletions
Generated
+18 -18
View File
@@ -1095,7 +1095,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1120,7 +1120,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_log", "oxide_log",
@@ -1128,7 +1128,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cfg" name = "oxide_cfg"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_emit", "oxide_emit",
@@ -1139,7 +1139,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cli" name = "oxide_cli"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1159,7 +1159,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1168,7 +1168,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1187,7 +1187,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1201,7 +1201,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1216,7 +1216,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1240,7 +1240,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1252,7 +1252,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1261,7 +1261,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1276,7 +1276,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1292,7 +1292,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1304,7 +1304,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"hashbrown 0.14.5", "hashbrown 0.14.5",
@@ -1333,7 +1333,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.8.28" version = "0.8.30"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.8.29" version = "0.8.30"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+3 -1
View File
@@ -90,10 +90,11 @@ pub enum ServerResponse {
}, },
/// 版本响应。 /// 版本响应。
Version { version: String }, Version { version: String },
/// 状态响应:池可用/总数与运行时长。 /// 状态响应:池可用/总数、峰值借出数与运行时长。
Status { Status {
pool_available: usize, pool_available: usize,
pool_total: usize, pool_total: usize,
pool_peak: usize,
uptime_ms: u64, uptime_ms: u64,
}, },
/// 健康检查响应。 /// 健康检查响应。
@@ -334,6 +335,7 @@ mod tests {
ServerResponse::Status { ServerResponse::Status {
pool_available: 2, pool_available: 2,
pool_total: 4, pool_total: 4,
pool_peak: 3,
uptime_ms: 1500, uptime_ms: 1500,
}, },
ServerResponse::Health { healthy: true }, ServerResponse::Health { healthy: true },
+3
View File
@@ -346,6 +346,7 @@ fn dispatch_control(request: &ServerRequest, ctx: &ServerContext) -> ServerRespo
ServerRequest::Status => ServerResponse::Status { ServerRequest::Status => ServerResponse::Status {
pool_available: ctx.pool_counters.available(), pool_available: ctx.pool_counters.available(),
pool_total: ctx.pool_counters.total(), pool_total: ctx.pool_counters.total(),
pool_peak: ctx.pool_counters.peak(),
uptime_ms: ctx.started_at.elapsed().as_millis() as u64, uptime_ms: ctx.started_at.elapsed().as_millis() as u64,
}, },
ServerRequest::Health => ServerResponse::Health { healthy: true }, ServerRequest::Health => ServerResponse::Health { healthy: true },
@@ -467,10 +468,12 @@ mod tests {
ServerResponse::Status { ServerResponse::Status {
pool_available, pool_available,
pool_total, pool_total,
pool_peak,
uptime_ms, uptime_ms,
} => { } => {
assert!(pool_available <= pool_total, "空闲数应不超过总数:{pool_available}/{pool_total}"); assert!(pool_available <= pool_total, "空闲数应不超过总数:{pool_available}/{pool_total}");
assert!(pool_total >= 1, "总数应不小于 1(标准配置预热 1 个):{pool_total}"); assert!(pool_total >= 1, "总数应不小于 1(标准配置预热 1 个):{pool_total}");
assert!(pool_peak <= pool_total, "峰值应不超过总数:{pool_peak}/{pool_total}");
assert!(uptime_ms < 10_000, "刚启动的 server 运行时长应小于 10 秒:{uptime_ms}"); assert!(uptime_ms < 10_000, "刚启动的 server 运行时长应小于 10 秒:{uptime_ms}");
} }
other => panic!("应得 Status 帧,实得 {other:?}"), other => panic!("应得 Status 帧,实得 {other:?}"),
+100 -1
View File
@@ -13,13 +13,17 @@ struct VmPoolInner {
total_count: usize, total_count: usize,
} }
/// 池状态计数器:空闲数与已创建总数的跨线程可读快照。 /// 池状态计数器:空闲数、在借数、峰值借出数与已创建总数的跨线程可读快照。
/// ///
/// 池本体不是 Send/Sync(Vm 不跨线程),状态接口经独立原子暴露, /// 池本体不是 Send/Sync(Vm 不跨线程),状态接口经独立原子暴露,
/// 与队列变更同步更新;空闲数恒不大于总数(总数只增不减)。 /// 与队列变更同步更新;空闲数恒不大于总数(总数只增不减)。
/// 安静态(无并发借出归还)下在借数恒等于总数减空闲数;峰值是每次
/// 借出后精确在借数的最大值,单调不降。
pub struct PoolCounters { pub struct PoolCounters {
available: AtomicUsize, available: AtomicUsize,
total: AtomicUsize, total: AtomicUsize,
in_use: AtomicUsize,
peak: AtomicUsize,
} }
impl PoolCounters { impl PoolCounters {
@@ -30,6 +34,8 @@ impl PoolCounters {
Arc::new(PoolCounters { Arc::new(PoolCounters {
available: AtomicUsize::new(0), available: AtomicUsize::new(0),
total: AtomicUsize::new(0), total: AtomicUsize::new(0),
in_use: AtomicUsize::new(0),
peak: AtomicUsize::new(0),
}) })
} }
@@ -42,6 +48,25 @@ impl PoolCounters {
pub fn total(&self) -> usize { pub fn total(&self) -> usize {
self.total.load(Ordering::Relaxed) self.total.load(Ordering::Relaxed)
} }
/// 读取历史峰值借出数(高水位,单调不降)。
pub fn peak(&self) -> usize {
self.peak.load(Ordering::Relaxed)
}
/// 记录一次借出:在借数加一,并抬高历史峰值。
///
/// 峰值取借出后精确在借数的最大值,单调不降;归还路径不触碰峰值。
/// 诊断计数器,`Relaxed` 序,不参与同步语义。
fn record_borrow(&self) {
let in_use = self.in_use.fetch_add(1, Ordering::Relaxed) + 1;
self.peak.fetch_max(in_use, Ordering::Relaxed);
}
/// 记录一次归还:在借数减一。
fn record_return(&self) {
self.in_use.fetch_sub(1, Ordering::Relaxed);
}
} }
/// 共享 `Vm` 实例池:按需创建并复用 VM,避免每次执行重建 kernel 共享状态。 /// 共享 `Vm` 实例池:按需创建并复用 VM,避免每次执行重建 kernel 共享状态。
@@ -132,6 +157,7 @@ impl VmPool {
if let Some(vm) = inner.available.pop() { if let Some(vm) = inner.available.pop() {
vm_trace!("pool: reused vm, {} available", inner.available.len()); vm_trace!("pool: reused vm, {} available", inner.available.len());
self.counters.available.fetch_sub(1, Ordering::Relaxed); self.counters.available.fetch_sub(1, Ordering::Relaxed);
self.counters.record_borrow();
return VmGuard { return VmGuard {
vm: Some(vm), vm: Some(vm),
pool: Arc::clone(self), pool: Arc::clone(self),
@@ -149,6 +175,7 @@ impl VmPool {
vm_debug!("pool: growing to {} vms", inner.total_count); vm_debug!("pool: growing to {} vms", inner.total_count);
drop(inner); drop(inner);
self.counters.total.fetch_add(1, Ordering::Relaxed); self.counters.total.fetch_add(1, Ordering::Relaxed);
self.counters.record_borrow();
let vm = Self::new_vm(&self.kernel_core); let vm = Self::new_vm(&self.kernel_core);
return VmGuard { return VmGuard {
vm: Some(vm), vm: Some(vm),
@@ -164,6 +191,7 @@ impl VmPool {
inner.total_count += 1; inner.total_count += 1;
drop(inner); drop(inner);
self.counters.total.fetch_add(1, Ordering::Relaxed); self.counters.total.fetch_add(1, Ordering::Relaxed);
self.counters.record_borrow();
let vm = Self::new_vm(&self.kernel_core); let vm = Self::new_vm(&self.kernel_core);
return VmGuard { return VmGuard {
vm: Some(vm), vm: Some(vm),
@@ -189,6 +217,13 @@ impl VmPool {
self.counters.total() self.counters.total()
} }
/// 读取历史峰值借出数(高水位)。
///
/// 读原子快照,不锁池内互斥锁;峰值单调不降。
pub fn peak_count(&self) -> usize {
self.counters.peak()
}
/// 跨线程可读的池状态句柄(原子快照,无锁)。 /// 跨线程可读的池状态句柄(原子快照,无锁)。
pub fn counters(&self) -> Arc<PoolCounters> { pub fn counters(&self) -> Arc<PoolCounters> {
Arc::clone(&self.counters) Arc::clone(&self.counters)
@@ -234,6 +269,7 @@ impl Drop for VmGuard {
} }
self.pool.counters.available.fetch_add(1, Ordering::Relaxed); self.pool.counters.available.fetch_add(1, Ordering::Relaxed);
self.pool.counters.record_return();
self.pool.condvar.notify_one(); self.pool.condvar.notify_one();
} }
} }
@@ -329,4 +365,67 @@ mod tests {
assert_eq!(pool_a.available_count(), counters.available()); assert_eq!(pool_a.available_count(), counters.available());
assert_eq!(pool_b.available_count(), counters.available()); assert_eq!(pool_b.available_count(), counters.available());
} }
#[test]
fn test_peak_count_single_thread_exact() {
let kernel = test_kernel();
let pool = VmPool::new(kernel, 2, None);
assert_eq!(pool.peak_count(), 0, "从未借出的池峰值应为零");
let g1 = pool.spawn();
let g2 = pool.spawn();
assert_eq!(pool.peak_count(), 2, "连续借出两个后峰值应为 2");
drop(g1);
drop(g2);
let g3 = pool.spawn();
assert_eq!(pool.peak_count(), 2, "归还后再借一个峰值不回落");
drop(g3);
}
#[test]
fn test_peak_count_shared_aggregate() {
let kernel = test_kernel();
let counters = PoolCounters::shared();
let pool_a = VmPool::with_counters(Arc::clone(&kernel), 1, Some(4), Arc::clone(&counters));
let pool_b = VmPool::with_counters(Arc::clone(&kernel), 1, Some(4), Arc::clone(&counters));
let g1 = pool_a.spawn();
let g2 = pool_b.spawn();
assert_eq!(counters.peak(), 2, "两池各借出一个后聚合峰值应为 2");
drop(g1);
drop(g2);
let g3 = pool_a.spawn();
assert_eq!(counters.peak(), 2, "归还后再借一个聚合峰值不回落");
drop(g3);
}
#[test]
fn test_peak_count_concurrent_bounded() {
let kernel = test_kernel();
let counters = PoolCounters::shared();
let handles: Vec<_> = (0..4)
.map(|_| {
let kernel = Arc::clone(&kernel);
let counters = Arc::clone(&counters);
std::thread::spawn(move || {
// 池本体不跨线程(Vm 不 Send),每线程自建池、共享同一聚合计数器。
let pool = VmPool::with_counters(kernel, 0, Some(8), counters);
for _ in 0..50 {
let guard = pool.spawn();
drop(guard);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
// 并发下精确峰值是竞争结果,只断言界:4 线程各至多持 1 个在借。
let peak = counters.peak();
assert!((1..=4).contains(&peak), "并发峰值应落在 1 到 4 之间:{peak}");
}
} }