mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 18:51:13 +08:00
feat: 新增 --profile 旗标聚合现有计数器统一出口
This commit is contained in:
@@ -13,6 +13,8 @@ pub mod leak_detect;
|
||||
pub mod metrics;
|
||||
/// 结果格式化输出。
|
||||
pub mod output;
|
||||
/// `--profile` 旗标的聚合计数器快照。
|
||||
pub mod profile;
|
||||
/// Rust 原生 benchmark 委托。
|
||||
pub mod rust_bench;
|
||||
|
||||
|
||||
@@ -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()),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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}")));
|
||||
|
||||
Reference in New Issue
Block a user