feat: 新增 --profile 旗标聚合现有计数器统一出口

This commit is contained in:
2026-10-04 20:18:38 +00:00
parent 3d85af635b
commit ae5297671d
6 changed files with 154 additions and 24 deletions
Generated
+18 -18
View File
@@ -1095,7 +1095,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"chrono",
"fast-float",
@@ -1120,7 +1120,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1128,7 +1128,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1139,7 +1139,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1159,7 +1159,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1168,7 +1168,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1187,7 +1187,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1201,7 +1201,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1216,7 +1216,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"bumpalo",
"dashmap",
@@ -1240,7 +1240,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1252,7 +1252,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"tracing",
"tracing-appender",
@@ -1261,7 +1261,7 @@ dependencies = [
[[package]]
name = "oxide_parser"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1276,7 +1276,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1292,7 +1292,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"num-bigint",
"num-traits",
@@ -1304,7 +1304,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"bumpalo",
"hashbrown 0.14.5",
@@ -1333,7 +1333,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.6.4"
version = "0.6.5"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
]
[workspace.package]
version = "0.6.4"
version = "0.6.5"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+2
View File
@@ -13,6 +13,8 @@ pub mod leak_detect;
pub mod metrics;
/// 结果格式化输出。
pub mod output;
/// `--profile` 旗标的聚合计数器快照。
pub mod profile;
/// Rust 原生 benchmark 委托。
pub mod rust_bench;
+104
View File
@@ -0,0 +1,104 @@
//! `--profile` 旗标的聚合计数器快照:只读聚合现有计数器(GC 统计、内联缓存
//! 命中/未命中、session 堆账目、指令数),按每指标一行输出。
//!
//! 零热路径写:`collect` 只从 VM 内省面读 getter,不写任何计数器;旗标默认
//! 关闭,关闭时不进入采集路径。
use oxide_vm::vm::Vm;
/// 单次 run 的聚合计数器快照(现有计数器的只读聚合)。
#[derive(Debug, Clone)]
pub struct ProfileOutput {
// GC 统计(session 累计口径,12 字段)
pub gc_total_collections: u64,
pub gc_total_bytes_freed: u64,
pub gc_total_objects_scanned: u64,
pub gc_total_objects_live: u64,
pub gc_total_objects_dead: u64,
pub gc_last_objects_scanned: u64,
pub gc_last_objects_live: u64,
pub gc_last_objects_dead: u64,
pub gc_last_bytes_freed: u64,
pub gc_last_duration_us: u64,
pub gc_max_duration_us: u64,
pub gc_min_duration_us: u64,
// 内联缓存命中/未命中
pub ic_hits: u64,
pub ic_misses: u64,
pub ic_hit_rate: f64,
// session 堆账目
pub session_objects: usize,
pub session_bytes: usize,
pub session_bytes_peak: usize,
pub run_alloc_peak: usize,
pub run_alloc_bytes: usize,
// 指令数与执行时长
pub instruction_count: u64,
pub exec_time_us: u64,
}
impl ProfileOutput {
/// 从 VM 内省面只读聚合(零热路径写,不写任何计数器)。
///
/// `exec_time_us` 由调用方在 `run` 前后计时后传入。
pub fn collect(vm: &Vm, exec_time_us: u64) -> Self {
let gc = vm.session_gc_stats();
Self {
gc_total_collections: gc.total_collections,
gc_total_bytes_freed: gc.total_bytes_freed,
gc_total_objects_scanned: gc.total_objects_scanned,
gc_total_objects_live: gc.total_objects_live,
gc_total_objects_dead: gc.total_objects_dead,
gc_last_objects_scanned: gc.last_collection_objects_scanned,
gc_last_objects_live: gc.last_collection_objects_live,
gc_last_objects_dead: gc.last_collection_objects_dead,
gc_last_bytes_freed: gc.last_collection_bytes_freed,
gc_last_duration_us: gc.last_collection_duration_us,
gc_max_duration_us: gc.max_collection_duration_us,
// 无收集时 min 字段为 u64::MAX 哨兵,映射为 0 避免输出哨兵值。
gc_min_duration_us: if gc.min_collection_duration_us == u64::MAX {
0
} else {
gc.min_collection_duration_us
},
ic_hits: vm.ic_hit_count(),
ic_misses: vm.ic_miss_count(),
ic_hit_rate: vm.ic_hit_rate(),
session_objects: vm.session_object_count(),
session_bytes: vm.session_bytes_allocated(),
session_bytes_peak: vm.session_bytes_peak(),
run_alloc_peak: vm.run_alloc_peak(),
run_alloc_bytes: vm.run_alloc_bytes_total(),
instruction_count: vm.instruction_count(),
exec_time_us,
}
}
/// 导出为 `(指标名, 值)` 列表,每指标一行,与 bench 输出对齐。
pub fn iter_metrics(&self) -> Vec<(&'static str, String)> {
vec![
("instruction_count", self.instruction_count.to_string()),
("session_objects", self.session_objects.to_string()),
("session_bytes", self.session_bytes.to_string()),
("session_bytes_peak", self.session_bytes_peak.to_string()),
("run_alloc_peak", self.run_alloc_peak.to_string()),
("run_alloc_bytes", self.run_alloc_bytes.to_string()),
("ic_hit_rate", format!("{:.4}", self.ic_hit_rate)),
("ic_hits", self.ic_hits.to_string()),
("ic_misses", self.ic_misses.to_string()),
("gc_total_collections", self.gc_total_collections.to_string()),
("gc_total_bytes_freed", self.gc_total_bytes_freed.to_string()),
("gc_total_objects_scanned", self.gc_total_objects_scanned.to_string()),
("gc_total_objects_live", self.gc_total_objects_live.to_string()),
("gc_total_objects_dead", self.gc_total_objects_dead.to_string()),
("gc_last_objects_scanned", self.gc_last_objects_scanned.to_string()),
("gc_last_objects_live", self.gc_last_objects_live.to_string()),
("gc_last_objects_dead", self.gc_last_objects_dead.to_string()),
("gc_last_bytes_freed", self.gc_last_bytes_freed.to_string()),
("gc_last_duration_us", self.gc_last_duration_us.to_string()),
("gc_max_duration_us", self.gc_max_duration_us.to_string()),
("gc_min_duration_us", self.gc_min_duration_us.to_string()),
("exec_time_us", self.exec_time_us.to_string()),
]
}
}
+21 -5
View File
@@ -4,6 +4,7 @@
use std::fs;
use std::process::ExitCode;
use std::sync::Arc;
use std::time::Instant;
use ansi_term::Colour::Red;
use clap::{Parser, Subcommand};
@@ -40,6 +41,11 @@ struct Cli {
#[arg(short, long, global = true)]
quiet: bool,
/// 聚合并输出现有计数器(GC 统计、内联缓存、session 堆账目、指令数)。
/// 默认关闭,关闭时零开销;输出每指标一行,与 bench 输出对齐。
#[arg(long, global = true)]
profile: bool,
}
#[derive(Subcommand)]
@@ -94,13 +100,13 @@ fn main() -> ExitCode {
Some(Commands::Eval { code, trace }) => {
let kernel = make_kernel(cli.verbose, cli.quiet);
let pool = make_pool(&kernel);
eval(&code, &kernel, &pool, trace, true)
eval(&code, &kernel, &pool, trace, cli.profile, true)
}
Some(Commands::Run { file, repeat, trace }) => {
let kernel = make_kernel(cli.verbose, cli.quiet);
let pool = make_pool(&kernel);
for n in 0..repeat {
let code = run(&file, &kernel, &pool, trace);
let code = run(&file, &kernel, &pool, trace, cli.profile);
// 失败即终止并传播退出码,供脚本与 CI 区分成败。
if code != ExitCode::SUCCESS {
return code;
@@ -158,7 +164,9 @@ fn make_pool(kernel: &Arc<KernelCore>) -> Arc<VmPool> {
VmPool::new(Arc::clone(kernel), kernel.config.min_pool_size, kernel.config.max_pool_size)
}
fn eval(code: &str, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>, trace: bool, print_result: bool) -> ExitCode {
fn eval(
code: &str, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>, trace: bool, profile: bool, print_result: bool,
) -> ExitCode {
let allocator = Allocator::default();
let program = match oxide_parser::parse(&allocator, code) {
Ok(p) => p,
@@ -184,8 +192,16 @@ fn eval(code: &str, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>, trace: bool, p
let mut guard = pool.spawn();
guard.vm_mut().set_instruction_trace(trace);
let exec_start = Instant::now();
match guard.vm_mut().run(&module) {
Ok(result) => {
// --profile 开启时聚合现有计数器,每指标一行输出到 stderr(stdout 保持纯结果)。
if profile {
let p = bench::profile::ProfileOutput::collect(guard.vm(), exec_start.elapsed().as_micros() as u64);
for (name, value) in p.iter_metrics() {
eprintln!("{name} = {value}");
}
}
// eval 臂按 REPL 语义打印完成值;run 臂脚本只输出自身产生内容。
if print_result {
format_result(guard.vm(), kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result);
@@ -292,9 +308,9 @@ fn format_array(
format!("[{}]", items.join(", "))
}
fn run(file: &str, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>, trace: bool) -> ExitCode {
fn run(file: &str, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>, trace: bool, profile: bool) -> ExitCode {
match fs::read_to_string(file) {
Ok(source) => eval(&source, kernel, pool, trace, false),
Ok(source) => eval(&source, kernel, pool, trace, profile, false),
Err(err) => {
kernel_error!("cannot read {}: {}", file, err);
eprintln!("{}", Red.paint(format!("Cannot read {file}: {err}")));
+8
View File
@@ -185,6 +185,14 @@ impl Vm {
+ self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::<Cell>()
}
/// 本 run 累计分配字节(O(1) 读)。
///
/// 公开包装,供 profiling 统一出口跨 crate 读取:与 `run_alloc_bytes` 同一读
/// 口径,零行为变化。
pub fn run_alloc_bytes_total(&self) -> usize {
self.run_alloc_bytes()
}
/// 本 run 累计分配字节的全量重算版:各分量按当前值各计一次、互不相交——
/// 存活对象头加堆数据(属性/元素/meta 向量容量、upvalue 列表容量、native
/// 状态盒)+ 存活串(头加 payload)+ BigInt 表长 × 尺寸 + cell 表长 ×