use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::ported::zsh_h::OPT_ISSET;
#[derive(Debug, Clone, Default)]
pub struct Pfunc { pub name: String, pub calls: i64, pub time: f64, pub self_time: f64, pub num: i64, }
#[derive(Debug, Clone, Copy)]
pub struct Sfunc { pub p: usize, pub beg: f64, }
#[derive(Debug, Clone, Default)]
pub struct Parc { pub from: usize, pub to: usize, pub calls: i64, pub time: f64, pub self_time: f64, }
pub fn freepfuncs(f: &mut Vec<Pfunc>) { f.clear(); }
pub fn freeparcs(a: &mut Vec<Parc>) { a.clear(); }
pub fn findpfunc(name: &str) -> Option<usize> { let calls = CALLS.lock().unwrap();
calls.iter().position(|f| f.name == name)
}
pub fn findparc(f: usize, t: usize) -> Option<usize> { let arcs = ARCS.lock().unwrap();
arcs.iter().position(|a| a.from == f && a.to == t)
}
pub fn cmpsfuncs(a: &Pfunc, b: &Pfunc) -> std::cmp::Ordering { b.self_time.partial_cmp(&a.self_time).unwrap_or(std::cmp::Ordering::Equal)
}
pub fn cmptfuncs(a: &Pfunc, b: &Pfunc) -> std::cmp::Ordering { b.time.partial_cmp(&a.time).unwrap_or(std::cmp::Ordering::Equal)
}
pub fn cmpparcs(a: &Parc, b: &Parc) -> std::cmp::Ordering { b.time.partial_cmp(&a.time).unwrap_or(std::cmp::Ordering::Equal)
}
pub fn bin_zprof(_nam: &str, _args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let opt_c = OPT_ISSET(ops, b'c');
if opt_c {
let mut calls = CALLS.lock().unwrap();
freepfuncs(&mut calls); NCALLS.store(0, Ordering::SeqCst); let mut arcs = ARCS.lock().unwrap();
freeparcs(&mut arcs); NARCS.store(0, Ordering::SeqCst); return 0; }
let calls = CALLS.lock().unwrap();
let arcs = ARCS.lock().unwrap();
let mut fs: Vec<usize> = (0..calls.len()).collect(); let as_arcs: Vec<usize> = (0..arcs.len()).collect(); let mut total: f64 = 0.0; for &i in &fs {
total += calls[i].self_time; }
fs.sort_by(|&a, &b| cmpsfuncs(&calls[a], &calls[b]));
println!("num calls time self name");
println!("-----------------------------------------------------------------------------------");
drop(calls);
{
let mut calls_w = CALLS.lock().unwrap();
for (i, &idx) in fs.iter().enumerate() { calls_w[idx].num = (i + 1) as i64; }
}
let calls = CALLS.lock().unwrap();
for &idx in &fs { let f = &calls[idx];
let avg_t = if f.calls > 0 { f.time / f.calls as f64 } else { 0.0 };
let avg_s = if f.calls > 0 { f.self_time / f.calls as f64 } else { 0.0 };
let pct_t = if total != 0.0 { (f.time / total) * 100.0 } else { 0.0 };
let pct_s = if total != 0.0 { (f.self_time / total) * 100.0 } else { 0.0 };
println!(
"{:2}) {:4} {:8.2} {:8.2} {:6.2}% {:8.2} {:8.2} {:6.2}% {}",
f.num, f.calls, f.time, avg_t, pct_t,
f.self_time, avg_s, pct_s,
f.name
);
}
let mut fs_t: Vec<usize> = fs.clone();
fs_t.sort_by(|&a, &b| cmptfuncs(&calls[a], &calls[b]));
for &fp_idx in &fs_t { println!();
println!("-----------------------------------------------------------------------------------");
println!();
let f = &calls[fp_idx];
for &ap in &as_arcs { let a = &arcs[ap];
if a.to == fp_idx { let avg_t = if a.calls > 0 { a.time / a.calls as f64 } else { 0.0 };
let avg_s = if a.calls > 0 { a.self_time / a.calls as f64 } else { 0.0 };
let pct_t = if total != 0.0 { (a.time / total) * 100.0 } else { 0.0 };
let from_name = &calls[a.from].name;
let from_num = calls[a.from].num;
println!(
" {:4}/{:<4} {:8.2} {:8.2} {:6.2}% {:8.2} {:8.2} {} [{}]",
a.calls, f.calls, a.time, avg_t, pct_t,
a.self_time, avg_s,
from_name, from_num
);
}
}
let avg_t = if f.calls > 0 { f.time / f.calls as f64 } else { 0.0 };
let avg_s = if f.calls > 0 { f.self_time / f.calls as f64 } else { 0.0 };
let pct_t = if total != 0.0 { (f.time / total) * 100.0 } else { 0.0 };
let pct_s = if total != 0.0 { (f.self_time / total) * 100.0 } else { 0.0 };
println!(
"{:2}) {:4} {:8.2} {:8.2} {:6.2}% {:8.2} {:8.2} {:6.2}% {}",
f.num, f.calls, f.time, avg_t, pct_t,
f.self_time, avg_s, pct_s,
f.name
);
for &ap in as_arcs.iter().rev() { let a = &arcs[ap];
if a.from == fp_idx { let avg_t = if a.calls > 0 { a.time / a.calls as f64 } else { 0.0 };
let avg_s = if a.calls > 0 { a.self_time / a.calls as f64 } else { 0.0 };
let pct_t = if total != 0.0 { (a.time / total) * 100.0 } else { 0.0 };
let to_name = &calls[a.to].name;
let to_num = calls[a.to].num;
let to_calls = calls[a.to].calls;
println!(
" {:4}/{:<4} {:8.2} {:8.2} {:6.2}% {:8.2} {:8.2} {} [{}]",
a.calls, to_calls, a.time, avg_t, pct_t,
a.self_time, avg_s,
to_name, to_num
);
}
}
}
0 }
pub fn name_for_anonymous_function(name: &str, filename: &str, lineno: i32) -> String { format!("{} [{}:{}]", name, filename, lineno)
}
#[allow(non_snake_case)]
pub fn zprof_wrapper(prog: *const crate::ported::zsh_h::eprog, w: *const crate::ported::zsh_h::funcwrap,
name: &str) -> i32 {
let mut active: i32 = 0; let mut sf = Sfunc { p: 0, beg: 0.0 }; let mut f: Option<usize> = None; let mut a: Option<usize> = None; let mut prev: f64 = 0.0;
let name_for_lookups: String = if name == "(anon)" { name_for_anonymous_function(name, "", 0) } else { name.to_string() };
if ZPROF_MODULE.load(Ordering::SeqCst) { active = 1; f = findpfunc(&name_for_lookups); if f.is_none() { let new_pfunc = Pfunc { name: crate::ported::mem::ztrdup(&name_for_lookups), calls: 0, time: 0.0, self_time: 0.0, num: 0,
};
let mut calls = CALLS.lock().unwrap();
f = Some(calls.len()); calls.push(new_pfunc); NCALLS.fetch_add(1, Ordering::SeqCst); }
let stack_top: Option<Sfunc> = { let st = STACK.lock().unwrap();
st.last().copied()
};
if let Some(top) = stack_top { a = findparc(top.p, f.unwrap()); if a.is_none() { let new_parc = Parc { from: top.p, to: f.unwrap(), calls: 0, self_time: 0.0, time: 0.0, };
let mut arcs = ARCS.lock().unwrap();
a = Some(arcs.len()); arcs.push(new_parc); NARCS.fetch_add(1, Ordering::SeqCst); }
}
sf.p = f.unwrap(); STACK.lock().unwrap().push(sf);
{
let mut calls = CALLS.lock().unwrap();
calls[f.unwrap()].calls += 1; }
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 }; crate::ported::compat::zgettime_monotonic_if_available(&mut ts); sf.beg = (ts.tv_sec as f64) * 1000.0 + (ts.tv_nsec as f64) / 1_000_000.0; prev = sf.beg; let mut st = STACK.lock().unwrap();
if let Some(top) = st.last_mut() { top.beg = sf.beg; }
}
let _ = (prog, w);
if active != 0 { if ZPROF_MODULE.load(Ordering::SeqCst) { let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 }; crate::ported::compat::zgettime_monotonic_if_available(&mut ts); let now = (ts.tv_sec as f64) * 1000.0 + (ts.tv_nsec as f64) / 1_000_000.0;
{
let mut calls = CALLS.lock().unwrap();
if let Some(idx) = f {
calls[idx].self_time += now - sf.beg; }
}
let recursion: bool = { let st = STACK.lock().unwrap();
let cur_f = f.unwrap();
st.iter().rev().skip(1).any(|fr| fr.p == cur_f)
};
if !recursion { let mut calls = CALLS.lock().unwrap();
if let Some(idx) = f {
calls[idx].time += now - prev; }
}
if let Some(arc_idx) = a { let mut arcs = ARCS.lock().unwrap();
arcs[arc_idx].calls += 1; arcs[arc_idx].self_time += now - sf.beg; }
{
let mut st = STACK.lock().unwrap();
st.pop(); }
let mut st = STACK.lock().unwrap();
if let Some(top) = st.last_mut() { top.beg += now - prev; if let Some(arc_idx) = a { drop(st);
let mut arcs = ARCS.lock().unwrap();
arcs[arc_idx].time += now - prev; }
}
} else { let mut st = STACK.lock().unwrap();
st.pop();
}
}
0 }
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 { ZPROF_MODULE.store(true, Ordering::SeqCst); 0 }
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 { *features = featuresarray(m, module_features());
0 }
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 { handlefeatures(m, module_features(), enables) }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 { let mut calls = CALLS.lock().unwrap();
calls.clear(); NCALLS.store(0, Ordering::SeqCst); let mut arcs = ARCS.lock().unwrap();
arcs.clear(); NARCS.store(0, Ordering::SeqCst); STACK.lock().unwrap().clear(); 0 }
pub fn cleanup_(m: *const module) -> i32 { let mut calls = CALLS.lock().unwrap();
freepfuncs(&mut calls); let mut arcs = ARCS.lock().unwrap();
freeparcs(&mut arcs); ZPROF_MODULE.store(false, Ordering::SeqCst);
setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 { 0
}
use crate::ported::zsh_h::module;
pub static CALLS: Mutex<Vec<Pfunc>> = Mutex::new(Vec::new());
pub static NCALLS: AtomicI32 = AtomicI32::new(0);
pub static ARCS: Mutex<Vec<Parc>> = Mutex::new(Vec::new());
pub static NARCS: AtomicI32 = AtomicI32::new(0);
pub static STACK: Mutex<Vec<Sfunc>> = Mutex::new(Vec::new());
pub static ZPROF_MODULE: AtomicBool = AtomicBool::new(false);
use crate::ported::zsh_h::features as features_t;
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["b:zprof".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
bn_list: None,
bn_size: 1,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 0,
n_abstract: 0,
}))
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn reset_state() {
let mut c = CALLS.lock().unwrap();
c.clear();
let mut a = ARCS.lock().unwrap();
a.clear();
STACK.lock().unwrap().clear();
NCALLS.store(0, Ordering::SeqCst);
NARCS.store(0, Ordering::SeqCst);
}
#[test]
fn pfunc_default_zeros() {
let p = Pfunc::default();
assert_eq!(p.name, "");
assert_eq!(p.calls, 0);
assert_eq!(p.time, 0.0);
assert_eq!(p.self_time, 0.0);
assert_eq!(p.num, 0);
}
#[test]
fn freepfuncs_clears() {
let mut v = vec![Pfunc { name: "a".into(), ..Default::default() }];
freepfuncs(&mut v);
assert!(v.is_empty());
}
#[test]
fn findpfunc_matches_by_name() {
let _g = TEST_LOCK.lock().unwrap();
reset_state();
CALLS.lock().unwrap().push(Pfunc { name: "alpha".into(), ..Default::default() });
CALLS.lock().unwrap().push(Pfunc { name: "beta".into(), ..Default::default() });
assert_eq!(findpfunc("alpha"), Some(0));
assert_eq!(findpfunc("beta"), Some(1));
assert_eq!(findpfunc("none"), None);
reset_state();
}
#[test]
fn findparc_matches_pair() {
let _g = TEST_LOCK.lock().unwrap();
reset_state();
ARCS.lock().unwrap().push(Parc { from: 0, to: 1, ..Default::default() });
ARCS.lock().unwrap().push(Parc { from: 0, to: 2, ..Default::default() });
assert_eq!(findparc(0, 1), Some(0));
assert_eq!(findparc(0, 2), Some(1));
assert_eq!(findparc(1, 0), None);
reset_state();
}
#[test]
fn cmpsfuncs_descending() {
let a = Pfunc { self_time: 5.0, ..Default::default() };
let b = Pfunc { self_time: 10.0, ..Default::default() };
assert_eq!(cmpsfuncs(&a, &b), std::cmp::Ordering::Greater);
assert_eq!(cmpsfuncs(&b, &a), std::cmp::Ordering::Less);
}
#[test]
fn bin_zprof_clear_resets_tables() {
let _g = TEST_LOCK.lock().unwrap();
reset_state();
CALLS.lock().unwrap().push(Pfunc { name: "x".into(), ..Default::default() });
ARCS.lock().unwrap().push(Parc { from: 0, to: 0, ..Default::default() });
NCALLS.store(1, Ordering::SeqCst);
NARCS.store(1, Ordering::SeqCst);
use crate::ported::zsh_h::{options, MAX_OPS};
let mut ops = options { ind: [0u8; MAX_OPS], args: Vec::new(),
argscount: 0, argsalloc: 0 };
ops.ind[b'c' as usize] = 1;
let r = bin_zprof("zprof", &["-c".to_string()], &ops, 0);
assert_eq!(r, 0);
assert!(CALLS.lock().unwrap().is_empty());
assert!(ARCS.lock().unwrap().is_empty());
assert_eq!(NCALLS.load(Ordering::SeqCst), 0);
assert_eq!(NARCS.load(Ordering::SeqCst), 0);
}
#[test]
fn zprof_wrapper_returns_zero() {
assert_eq!(
zprof_wrapper(std::ptr::null(), std::ptr::null(), "foo"),
0,
);
}
#[test]
fn name_for_anonymous_function_format() {
let s = name_for_anonymous_function("anon", "/tmp/foo.zsh", 42);
assert_eq!(s, "anon [/tmp/foo.zsh:42]");
}
#[test]
fn findpfunc_empty_table_returns_none() {
let _g = TEST_LOCK.lock().unwrap();
reset_state();
assert!(findpfunc("never-called").is_none());
}
#[test]
fn findpfunc_returns_correct_index_after_insert() {
let _g = TEST_LOCK.lock().unwrap();
reset_state();
CALLS.lock().unwrap().push(Pfunc { name: "alpha".into(), ..Default::default() });
CALLS.lock().unwrap().push(Pfunc { name: "beta".into(), ..Default::default() });
assert_eq!(findpfunc("alpha"), Some(0));
assert_eq!(findpfunc("beta"), Some(1));
assert!(findpfunc("gamma").is_none());
}
#[test]
fn findparc_empty_table_returns_none() {
let _g = TEST_LOCK.lock().unwrap();
reset_state();
assert!(findparc(0, 1).is_none());
}
#[test]
fn findparc_distinguishes_to_field() {
let _g = TEST_LOCK.lock().unwrap();
reset_state();
ARCS.lock().unwrap().push(Parc { from: 0, to: 1, ..Default::default() });
ARCS.lock().unwrap().push(Parc { from: 0, to: 2, ..Default::default() });
assert_eq!(findparc(0, 1), Some(0));
assert_eq!(findparc(0, 2), Some(1));
assert!(findparc(0, 99).is_none());
assert!(findparc(99, 1).is_none());
}
#[test]
fn cmpsfuncs_compares_by_self_time_descending() {
let high = Pfunc { name: "_".into(), self_time: 100.0, ..Default::default() };
let low = Pfunc { name: "_".into(), self_time: 1.0, ..Default::default() };
assert_eq!(cmpsfuncs(&high, &low), std::cmp::Ordering::Less);
assert_eq!(cmpsfuncs(&low, &high), std::cmp::Ordering::Greater);
assert_eq!(cmpsfuncs(&high, &high), std::cmp::Ordering::Equal);
}
#[test]
fn freepfuncs_empties_input_vec() {
let mut v = vec![
Pfunc { name: "x".into(), ..Default::default() },
Pfunc { name: "y".into(), ..Default::default() },
];
freepfuncs(&mut v);
assert!(v.is_empty(), "freepfuncs must clear the input vec");
}
#[test]
fn freeparcs_empties_input_vec() {
let mut v = vec![
Parc { from: 0, to: 1, ..Default::default() },
Parc { from: 2, to: 3, ..Default::default() },
];
freeparcs(&mut v);
assert!(v.is_empty(), "freeparcs must clear the input vec");
}
#[test]
fn cmpsfuncs_and_cmptfuncs_differ_when_fields_disagree() {
let alpha = Pfunc { name: "_".into(), self_time: 100.0, time: 1.0, ..Default::default() };
let beta = Pfunc { name: "_".into(), self_time: 1.0, time: 100.0, ..Default::default() };
let by_self = cmpsfuncs(&alpha, &beta);
let by_time = cmptfuncs(&alpha, &beta);
assert_ne!(by_self, by_time,
"cmpsfuncs (self_time) and cmptfuncs (time) must differ when fields disagree");
}
}