[
  {
    "path": ".github/workflows/rust.yaml",
    "content": "name: Rust\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n  \njobs: \n  linux-android: \n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    - uses: actions-rs/toolchain@v1\n      with:\n        toolchain: nightly\n        default: true\n        components: rustfmt, clippy\n    - name: Check code format\n      run: cargo fmt -- --check\n    # - name: Check code style # enable clippy in the future\n    #   run: cargo clippy --all-targets -- -Dwarnings\n    - name: Build\n      run: cargo build --verbose\n    - name: Run tests\n      run: cargo test --lib --verbose\n    - uses: nttld/setup-ndk@v1\n      with:\n        ndk-version: r22b\n    - name: Build on android\n      run: rustup target add armv7-linux-androideabi && cargo build --target armv7-linux-androideabi\n    - name: Build on android 64\n      run: rustup target add aarch64-linux-android && cargo build --target aarch64-linux-android\n  macos-ios:\n    runs-on: macos-latest\n    steps:\n    - uses: actions/checkout@v2\n    - uses: actions-rs/toolchain@v1\n      with:\n        toolchain: stable\n        target: aarch64-apple-ios\n    - name: Build on MacOS\n      run: cargo build --verbose\n    - name: Build for iOS\n      run: cargo build --target aarch64-apple-ios\n  windows:\n    runs-on: windows-latest\n    steps:\n    - uses: actions/checkout@v2\n    - uses: actions-rs/toolchain@v1\n      with:\n        toolchain: stable\n    - name: Build on Windows\n      run: cargo build --verbose\n\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\nCargo.lock\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"perf_monitor\"\nversion = \"0.2.0\"\nauthors = [\"zhangli.pear <zhangli.pear@bytedance.com>\"]\nedition = \"2018\"\n\nlicense-file = \"LICENSE\"\ndescription = \"A toolkit designed to be a foundation for applications to monitor their performance.\"\nrepository = \"https://github.com/larksuite/perf-monitor-rs\"\ndocumentation = \"https://docs.rs/perf_monitor/\"\n\ncategories = [\"api-bindings\", \"accessibility\", \"development-tools\"]\nkeywords = [\"perf\", \"statistics\", \"monitor\", \"performance\"]\n\n\n[features]\nallocation_counter = []\ndarwin_private = []\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n[dependencies]\nlibc = \"0.2\"\nthiserror = \"1\"\n\n[target.'cfg(target_os = \"windows\")'.dependencies]\nwindows-sys = { version = \"0.48.0\", features = [\"Win32_Foundation\", \"Win32_System_Threading\", \"Win32_System_ProcessStatus\"] }\n\n[target.'cfg(any(target_os = \"macos\", target_os = \"ios\"))'.dependencies]\nmach =  \"0.3\"\n\n[build-dependencies]\nbindgen = \"0.59\"\ncc = \"1.0\"\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2021 Lark Technologies Pte. Ltd. \n\nPermission is hereby granted, free of charge, to any person \nobtaining a copy of this software and associated documentation \nfiles (the \"Software\"), to deal in the Software without \nrestriction, including without limitation the rights to use, \ncopy, modify, merge, publish, distribute, sublicense, and/or \nsell copies of the Software, and to permit persons to whom the \nSoftware is furnished to do so, subject to the following \nconditions:\n\nThe above copyright notice and this permission notice shall be \nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, \nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES \nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND \nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT \nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR \nOTHER DEALINGS IN THE SOFTWARE.\n\nThis project uses some sub-components listed below.  \nFor the components licensed under multiple licenses,\nwe choose to redistribute them under the MIT license.  \n\n******\nCc-rs licensed under MIT or Apache-2.0 license \nCopyright (c) 2014 Alex Crichton\n******\nrust-bindgen licensed under BSD-3-Clause license \nCopyright (c) 2013, Jyun-Yan You \nAll rights reserved.\n******\nmach licensed under BSD-2-Clause or Apache-2.0 or MIT license.   \nCopyright (c) 2019 Nick Fitzgerald\n"
  },
  {
    "path": "README.md",
    "content": "# perf-monitor-rs\n\n[![github](https://img.shields.io/badge/GitHub-perf_monitor_rs-9b88bb?logo=github)](https://github.com/larksuite/perf-monitor-rs)\n[![minimum rustc 1.31.0](https://img.shields.io/badge/Minimum%20rustc-1.31.0-c18170?logo=rust)](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html)\n[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n[![docs.rs](https://docs.rs/perf_monitor/badge.svg)](https://docs.rs/perf_monitor)\n[![crates.io](https://img.shields.io/crates/v/perf_monitor.svg)](https://crates.io/crates/perf_monitor)\n\n```toml\n# Cargo.toml\n[dependencies]\nperf_monitor = \"0.2\"\n```\n\nA toolkit designed to be a foundation for applications to monitor their performance. It is:\n- **Cross-Platform:** perf-monitor supports Windows, macOS, Linux, iOS, and Android.\n- **Safe Wrapper:** perf-monitor uses many system C interfaces internally but exposes safe wrapper API outside. \n- **Effective:** perf-monitor is a thin wrapper around underlying APIs, taking care of not introducing unnecessary overhead, choosing the most lightweight method among many homogeneous APIs.\n\n# Features\n- CPU\n    - Usage of current process\n    - Usage of other process (coming soon)\n    - Usage of any thread in current process\n    - Logic core number\n- Memory\n    - A global allocator that tracks rust allocations\n    - Process memory info of current process for Windows and MacOS(Linux is conming soon).\n- IO\n    - Disk IO\n    - Network IO(coming soon)\n- FD\n    - FD number\n\n# Example\nA simple activity monitor:\n\n```rust\n    use perf_monitor::cpu::{ThreadStat, ProcessStat, processor_numbers};\n    use perf_monitor::fd::fd_count_cur;\n    use perf_monitor::io::get_process_io_stats;\n    use perf_monitor::mem::get_process_memory_info;\n\n    // cpu\n    let core_num = processor_numbers().unwrap();\n    let mut stat_p = ProcessStat::cur().unwrap();\n    let mut stat_t = ThreadStat::cur().unwrap();\n\n    let _ = (0..1_000).into_iter().sum::<i128>();\n\n    let usage_p = stat_p.cpu().unwrap() * 100f64;\n    let usage_t = stat_t.cpu().unwrap() * 100f64;\n\n    println!(\"[CPU] core Number: {}, process usage: {:.2}%, current thread usage: {:.2}%\", core_num, usage_p, usage_t);\n\n    // mem\n    let mem_info = get_process_memory_info().unwrap();\n    println!(\"[Memory] memory used: {} bytes, virtural memory used: {} bytes \", mem_info.resident_set_size, mem_info.virtual_memory_size);\n\n    // fd\n    let fd_num = fd_count_cur().unwrap();\n    println!(\"[FD] fd number: {}\", fd_num);\n\n    // io\n    let io_stat = get_process_io_stats().unwrap();   \n    println!(\"[IO] io-in: {} bytes, io-out: {} bytes\", io_stat.read_bytes, io_stat.write_bytes);\n```\n\nThe above code should have the following output:\n```txt\n[CPU] core Number: 12, process usage: 502.16%, current thread usage: 2.91%\n[Memory] memory used: 1073152 bytes, virtural memory used: 4405747712 bytes \n[FD] fd number: 7\n[IO] io-in: 0 bytes, io-out: 32768 bytes\n```\n\nSee [examples](./examples/activity_monitor.rs) for details. \n\n# Perfomance\nWe are concerned about the overhead associated with obtaining performance information. We try to use the most efficient methods while ensuring the API usability.\n\nFor example, CPU usage and FD number cost on these devices has following result:\n- MacOS: MacBookPro15,1; 6-Core Intel Core i7; 2.6GHz; 16GB\n- Windows: Windows10; Intel Core i3-2310M; 2.10GHz; 64bit; 4GB\n- Andorid: Pixel 2; android 10\n\n| profiling | Windows | MacOS | Android |\n| :--- | :---: | :---: | :---: | \n| thread CPU usage (ms) | 3 | 0.45 | 16 |\n| FD number (ms) | 0.15 | 0.07 | 10 |\n\n# Supported Platform\n\n| profiling | Windows | MacOS | iOS | Android | Linux |\n| :--- | :---: | :---: | :---: | :---: | :---: |\n| [CPU](https://docs.rs/perf_monitor/cpu/index.html) | ✅ | ✅ |✅ |✅ |✅ |\n| [Memory](https://docs.rs/perf_monitor/mem/index.html) | ✅ |✅ |✅ |✅ |✅ |\n| [FD count](https://docs.rs/perf_monitor/fd/index.html) | ✅ |✅ |❌ |✅ |✅ |\n| [IO](https://docs.rs/perf_monitor/io/index.html) | ✅ |✅ |✅ |✅ |✅ \n\nSee [documents](https://docs.rs/perf_monitor/) of each module for usage and more details.\n\n# Rust Version\n\nTo compile document require the nightly version, others should work both in stable and nightly version.\n\n\n```shell\ncargo build\n\ncargo +nightly doc \n\ncargo +nightly test\ncargo test --lib\n```\n\n# Contribution\n\nContributions are welcome!\n\nOpen an issue or create a PR to report bugs, add new features or improve documents and tests.\nIf you are a new contributor, see [this page](https://github.com/firstcontributions/first-contributions) for help.\n\n\n# Why perf-monitor-rs?\n\nThere are some crates to do similar things, such as [spork](https://github.com/azuqua/spork.rs), [procfs](https://github.com/eminence/procfs), and [sysinfo](https://github.com/GuillaumeGomez/sysinfo). \n\nOur application needs to monitor itself at runtime to help us find out performance issues. For example, when the CPU usage rises abnormally, we want to figure out which threads cause this. \n\nHowever, none of the above crates meet our needs. \n\n* `spork` can't get other thread information other than the calling thread. Only memory and CPU information can be processed. And it stops updating for years.\n* `procfs` looks good enough now, but only support the Linux platform. In its early stages, when we developed perf_monitor_rs, there was no way to get thread information.\n* `sysinfo` support all platform we need, but we think its interface is not elegant, because an explicit refresh is required before each call, otherwise an old value will be retrieved and you are not able to tell from the returning value. More importantly, it lacks some features like fd, CPU usage. \n\nIf you are building a cross-platform application and facing the same problem, we hope perf_monitor_rs can be your first choice. \n\n# License\nperf-monitor is providing under the MIT license. See [LICENSE](./LICENSE).\n"
  },
  {
    "path": "build.rs",
    "content": "#![allow(clippy::all, clippy::restriction, clippy::style, clippy::perf)]\nuse std::{convert::AsRef, env, ffi::OsStr, path::Path, process::Command};\n\n/// Using `build.rs` to generate bindings to eliminate the difference\n/// among the different target.\n///\n/// Following command generate the same output,\n/// which makes it easy to glance the bindings when coding.\n///\n/// ```shell ignore\n/// echo \"\" > /tmp/bindings.h\n/// echo \"#include <mach/thread_info.h>\"  >> /tmp/bindings.h\n/// echo \"#include <mach/thread_act.h>\"  >> /tmp/bindings.h\n/// echo \"#include <mach/mach_init.h>\"  >> /tmp/bindings.h\n/// echo \"#include <mach/kern_return.h>\"  >> /tmp/bindings.h\n/// echo \"#include <mach/task.h>\"  >> /tmp/bindings.h\n/// echo \"#include <mach/vm_map.h>\"  >> /tmp/bindings.h\n/// echo \"#include <mach/host_info.h>\"  >> /tmp/bindings.h\n/// echo \"#include <mach/mach_host.h>\"  >> /tmp/bindings.h\n/// echo \"#include <pthread/pthread.h>\"  >> /tmp/bindings.h\n/// echo \"#include <mach/mach_traps.h>\"  >> /tmp/bindings.h\n///\n/// bindgen \\\n/// --with-derive-default \\\n/// --with-derive-eq \\\n/// --with-derive-ord \\\n/// --no-layout-tests \\\n/// --whitelist-var THREAD_BASIC_INFO \\\n/// --whitelist-var KERN_SUCCESS \\\n/// --whitelist-var HOST_BASIC_INFO \\\n/// --whitelist-var mach_task_self_ \\\n/// --whitelist-var TH_USAGE_SCALE \\\n/// --whitelist-type thread_basic_info \\\n/// --whitelist-type host_basic_info \\\n/// --whitelist-function thread_info \\\n/// --whitelist-function mach_thread_self \\\n/// --whitelist-function task_threads \\\n/// --whitelist-function vm_deallocate \\\n/// --whitelist-function host_info \\\n/// --whitelist-function mach_host_self \\\n/// --whitelist-function pthread_from_mach_thread_np \\\n/// --whitelist-function pthread_getname_np \\\n/// --whitelist-function task_for_pid \\\n/// /tmp/bindings.h > /tmp/bindings.rs\n/// ```\n\nfn main() {\n    let target_os = env::var(\"CARGO_CFG_TARGET_OS\");\n    if target_os != Ok(\"macos\".into()) && target_os != Ok(\"ios\".into()) {\n        return;\n    }\n\n    let target_arch = env::var(\"CARGO_CFG_TARGET_ARCH\");\n\n    fn build_include_path(sdk: impl AsRef<OsStr>) -> String {\n        let output = Command::new(\"xcrun\")\n            .arg(\"--sdk\")\n            .arg(sdk)\n            .arg(\"--show-sdk-path\")\n            .output()\n            .expect(\"failed to run xcrun\");\n        let sdk_path = String::from_utf8(output.stdout.clone()).expect(\"valid path\");\n        format!(\"{}/usr/include\", sdk_path.trim())\n    }\n\n    let mut include_path = String::new();\n\n    if target_os == Ok(\"ios\".into()) && target_arch == Ok(\"aarch64\".into()) {\n        env::set_var(\"TARGET\", \"arm64-apple-ios\");\n        include_path = build_include_path(\"iphoneos\");\n    }\n\n    if target_os == Ok(\"ios\".into()) && target_arch == Ok(\"x86_64\".into()) {\n        env::set_var(\"TARGET\", \"x86_64-apple-ios\");\n        include_path = build_include_path(\"iphonesimulator\");\n    }\n\n    if target_os == Ok(\"macos\".into()) {\n        if target_arch == Ok(\"x86_64\".into()) {\n            env::set_var(\"TARGET\", \"x86_64-apple-darwin\");\n            include_path = build_include_path(\"macosx\");\n        }\n        if target_arch == Ok(\"aarch64\".into()) {\n            env::set_var(\"TARGET\", \"aarch64-apple-darwin\");\n            include_path = build_include_path(\"macosx\");\n        }\n    }\n\n    let outdir = env::var(\"OUT_DIR\").expect(\"OUT_DIR not set\");\n    let outfile = Path::new(&outdir).join(\"monitor_rs_ios_macos_binding.rs\");\n\n    bindgen::Builder::default()\n        .derive_default(true)\n        .derive_eq(true)\n        .derive_ord(true)\n        .layout_tests(false)\n        // clang args\n        .clang_arg(\"-I\")\n        .clang_arg(include_path)\n        // headers\n        .header_contents(\n            \"ios_macos.h\",\n            [\n                \"#include <mach/thread_info.h>\",\n                \"#include <mach/thread_act.h>\",\n                \"#include <mach/mach_init.h>\",\n                \"#include <mach/kern_return.h>\",\n                \"#include <mach/task.h>\",\n                \"#include <mach/vm_map.h>\",\n                \"#include <mach/host_info.h>\",\n                \"#include <mach/mach_host.h>\",\n                \"#include <pthread/pthread.h>\",\n                \"#include <mach/mach_traps.h>\",\n                \"#include <mach/task_info.h>\",\n                \"#include <sys/resource.h>\",\n                \"#include <malloc/malloc.h>\",\n            ]\n            .join(\"\\n\")\n            .as_str(),\n        )\n        // var\n        .allowlist_var(\"THREAD_BASIC_INFO\")\n        .allowlist_var(\"KERN_SUCCESS\")\n        .allowlist_var(\"HOST_BASIC_INFO\")\n        .allowlist_var(\"mach_task_self_\")\n        .allowlist_var(\"TH_USAGE_SCALE\")\n        // type\n        .allowlist_type(\"thread_basic_info\")\n        .allowlist_type(\"host_basic_info\")\n        .allowlist_type(\"task_vm_info\")\n        .allowlist_type(\"rusage_info_v2\")\n        .allowlist_type(\"malloc_zone_t\")\n        // function\n        .allowlist_function(\"thread_info\")\n        .allowlist_function(\"mach_thread_self\")\n        .allowlist_function(\"task_threads\")\n        .allowlist_function(\"vm_deallocate\")\n        .allowlist_function(\"host_info\")\n        .allowlist_function(\"mach_host_self\")\n        .allowlist_function(\"pthread_from_mach_thread_np\")\n        .allowlist_function(\"pthread_getname_np\")\n        .allowlist_function(\"task_for_pid\")\n        .allowlist_function(\"malloc_get_all_zones\")\n        .allowlist_function(\"malloc_default_zone\")\n        // generate\n        .generate()\n        .expect(\"generate binding failed\")\n        .write_to_file(Path::new(&outfile))\n        .expect(\"write to file failed\")\n}\n"
  },
  {
    "path": "examples/activity_monitor.rs",
    "content": "use std::time::Duration;\nuse std::time::Instant;\n\nuse perf_monitor::cpu::processor_numbers;\nuse perf_monitor::cpu::ProcessStat;\nuse perf_monitor::cpu::ThreadStat;\nuse perf_monitor::fd::fd_count_cur;\nuse perf_monitor::io::get_process_io_stats;\nuse perf_monitor::mem::get_process_memory_info;\n\nfn main() {\n    build_some_threads();\n\n    // cpu\n    let core_num = processor_numbers().unwrap();\n    let mut stat_p = ProcessStat::cur().unwrap();\n    let mut stat_t = ThreadStat::cur().unwrap();\n\n    let mut last_loop = Instant::now();\n    loop {\n        if last_loop.elapsed() > Duration::from_secs(1) {\n            last_loop = Instant::now();\n        } else {\n            std::thread::sleep(Duration::from_micros(100));\n            continue;\n        }\n        println!(\"----------\");\n\n        // cpu\n        let _ = (0..1_000).into_iter().sum::<i128>();\n\n        let usage_p = stat_p.cpu().unwrap() * 100f64;\n        let usage_t = stat_t.cpu().unwrap() * 100f64;\n\n        println!(\n            \"[CPU] core Number: {}, process usage: {:.2}%, current thread usage: {:.2}%\",\n            core_num, usage_p, usage_t\n        );\n\n        // mem\n        let mem_info = get_process_memory_info().unwrap();\n\n        println!(\n            \"[Memory] memory used: {} bytes, virtural memory used: {} bytes \",\n            mem_info.resident_set_size, mem_info.virtual_memory_size\n        );\n\n        // fd\n        let fd_num = fd_count_cur().unwrap();\n\n        println!(\"[FD] fd number: {}\", fd_num);\n\n        // io\n        let io_stat = get_process_io_stats().unwrap();\n\n        println!(\n            \"[IO] io-in: {} bytes, io-out: {} bytes\",\n            io_stat.read_bytes, io_stat.write_bytes\n        );\n    }\n}\n\nfn build_some_threads() {\n    for _ in 0..5 {\n        std::thread::spawn(|| loop {\n            let _ = (0..9_000).into_iter().sum::<i128>();\n        });\n    }\n}\n"
  },
  {
    "path": "src/cpu/android_linux.rs",
    "content": "use libc::{pthread_t, timespec};\nuse std::{\n    convert::TryInto,\n    io::Error,\n    io::Result,\n    mem::MaybeUninit,\n    time::{Duration, Instant},\n};\n\n#[derive(Clone, Copy)]\npub struct ThreadId(pthread_t);\n\nimpl ThreadId {\n    #[inline]\n    pub fn current() -> Self {\n        ThreadId(unsafe { libc::pthread_self() })\n    }\n}\n\nfn timespec_to_duration(timespec { tv_sec, tv_nsec }: timespec) -> Duration {\n    let sec: u64 = tv_sec.try_into().unwrap_or_default();\n    let nsec: u64 = tv_nsec.try_into().unwrap_or_default();\n    let (sec, nanos) = (\n        sec.saturating_add(nsec / 1_000_000_000),\n        (nsec % 1_000_000_000) as u32,\n    );\n    Duration::new(sec, nanos)\n}\n\nfn get_thread_cputime(ThreadId(thread): ThreadId) -> Result<timespec> {\n    let mut clk_id = 0;\n    let ret = unsafe { libc::pthread_getcpuclockid(thread, &mut clk_id) };\n    if ret != 0 {\n        return Err(Error::from_raw_os_error(ret));\n    }\n\n    let mut timespec = MaybeUninit::<timespec>::uninit();\n    let ret = unsafe { libc::clock_gettime(clk_id, timespec.as_mut_ptr()) };\n    if ret != 0 {\n        return Err(Error::last_os_error());\n    }\n    Ok(unsafe { timespec.assume_init() })\n}\n\npub struct ThreadStat {\n    tid: ThreadId,\n    last_stat: (timespec, Instant),\n}\n\nimpl ThreadStat {\n    pub fn cur() -> Result<Self> {\n        Self::build(ThreadId::current())\n    }\n\n    pub fn build(tid: ThreadId) -> Result<Self> {\n        let cputime = get_thread_cputime(tid)?;\n        let total_time = Instant::now();\n        Ok(ThreadStat {\n            tid,\n            last_stat: (cputime, total_time),\n        })\n    }\n\n    /// un-normalized\n    pub fn cpu(&mut self) -> Result<f64> {\n        let cputime = get_thread_cputime(self.tid)?;\n        let total_time = Instant::now();\n        let (old_cputime, old_total_time) =\n            std::mem::replace(&mut self.last_stat, (cputime, total_time));\n        let cputime = cputime.tv_sec as f64 + cputime.tv_nsec as f64 / 1_000_000_000f64;\n        let old_cputime = old_cputime.tv_sec as f64 + old_cputime.tv_nsec as f64 / 1_000_000_000f64;\n        let dt_cputime = cputime - old_cputime;\n        let dt_total_time = total_time\n            .saturating_duration_since(old_total_time)\n            .as_secs_f64();\n        Ok(dt_cputime / dt_total_time)\n    }\n\n    pub fn cpu_time(&mut self) -> Result<Duration> {\n        let cputime = get_thread_cputime(self.tid)?;\n        let total_time = Instant::now();\n        let (old_cputime, _old_total_time) =\n            std::mem::replace(&mut self.last_stat, (cputime, total_time));\n        Ok(timespec_to_duration(cputime).saturating_sub(timespec_to_duration(old_cputime)))\n    }\n}\n\npub fn cpu_time() -> Result<Duration> {\n    let mut timespec = MaybeUninit::<timespec>::uninit();\n    let ret = unsafe { libc::clock_gettime(libc::CLOCK_PROCESS_CPUTIME_ID, timespec.as_mut_ptr()) };\n    if ret != 0 {\n        return Err(Error::last_os_error());\n    }\n    Ok(timespec_to_duration(unsafe { timespec.assume_init() }))\n}\n"
  },
  {
    "path": "src/cpu/ios_macos.rs",
    "content": "use libc::{\n    mach_thread_self, rusage, thread_basic_info, time_value_t, KERN_SUCCESS, RUSAGE_SELF,\n    THREAD_BASIC_INFO, THREAD_BASIC_INFO_COUNT,\n};\nuse std::convert::TryInto;\nuse std::mem::MaybeUninit;\nuse std::time::Instant;\nuse std::{\n    io::{Error, Result},\n    time::Duration,\n};\n\n#[derive(Clone, Copy)]\npub struct ThreadId(u32);\n\nimpl ThreadId {\n    #[inline]\n    pub fn current() -> Self {\n        ThreadId(unsafe { mach_thread_self() })\n    }\n}\n\nfn get_thread_basic_info(ThreadId(tid): ThreadId) -> Result<thread_basic_info> {\n    let mut thread_basic_info = MaybeUninit::<thread_basic_info>::uninit();\n    let mut thread_info_cnt = THREAD_BASIC_INFO_COUNT;\n\n    let ret = unsafe {\n        libc::thread_info(\n            tid,\n            THREAD_BASIC_INFO as u32,\n            thread_basic_info.as_mut_ptr() as *mut _,\n            &mut thread_info_cnt,\n        )\n    };\n    if ret != KERN_SUCCESS as i32 {\n        return Err(Error::from_raw_os_error(ret));\n    }\n    Ok(unsafe { thread_basic_info.assume_init() })\n}\n\npub struct ThreadStat {\n    tid: ThreadId,\n    stat: (thread_basic_info, Instant),\n}\n\nimpl ThreadStat {\n    pub fn cur() -> Result<Self> {\n        Self::build(ThreadId::current())\n    }\n\n    pub fn build(tid: ThreadId) -> Result<Self> {\n        Ok(ThreadStat {\n            tid,\n            stat: (get_thread_basic_info(tid)?, Instant::now()),\n        })\n    }\n\n    /// unnormalized\n    pub fn cpu(&mut self) -> Result<f64> {\n        let cur_stat = get_thread_basic_info(self.tid)?;\n        let cur_time = Instant::now();\n        let (last_stat, last_time) = std::mem::replace(&mut self.stat, (cur_stat, cur_time));\n\n        let cur_user_time = time_value_to_u64(cur_stat.user_time);\n        let cur_sys_time = time_value_to_u64(cur_stat.system_time);\n        let last_user_time = time_value_to_u64(last_stat.user_time);\n        let last_sys_time = time_value_to_u64(last_stat.system_time);\n\n        let cpu_time_us = cur_user_time\n            .saturating_sub(last_user_time)\n            .saturating_add(cur_sys_time.saturating_sub(last_sys_time));\n\n        let dt_duration = cur_time.saturating_duration_since(last_time);\n        Ok(cpu_time_us as f64 / dt_duration.as_micros() as f64)\n    }\n\n    pub fn cpu_time(&mut self) -> Result<Duration> {\n        let cur_stat = get_thread_basic_info(self.tid)?;\n        let cur_time = Instant::now();\n        let (last_stat, _last_time) = std::mem::replace(&mut self.stat, (cur_stat, cur_time));\n\n        let cur_user_time = time_value_to_u64(cur_stat.user_time);\n        let cur_sys_time = time_value_to_u64(cur_stat.system_time);\n        let last_user_time = time_value_to_u64(last_stat.user_time);\n        let last_sys_time = time_value_to_u64(last_stat.system_time);\n\n        let cpu_time_us = cur_user_time\n            .saturating_sub(last_user_time)\n            .saturating_add(cur_sys_time.saturating_sub(last_sys_time));\n\n        Ok(Duration::from_micros(cpu_time_us))\n    }\n}\n\n#[inline]\nfn time_value_to_u64(t: time_value_t) -> u64 {\n    (t.seconds.try_into().unwrap_or(0u64))\n        .saturating_mul(1_000_000)\n        .saturating_add(t.microseconds.try_into().unwrap_or(0u64))\n}\n\npub fn cpu_time() -> Result<Duration> {\n    let mut time = MaybeUninit::<rusage>::uninit();\n    let ret = unsafe { libc::getrusage(RUSAGE_SELF, time.as_mut_ptr()) };\n    if ret != 0 {\n        return Err(Error::last_os_error());\n    }\n    let time = unsafe { time.assume_init() };\n    let sec = (time.ru_utime.tv_sec as u64).saturating_add(time.ru_stime.tv_sec as u64);\n    let nsec = (time.ru_utime.tv_usec as u32)\n        .saturating_add(time.ru_stime.tv_usec as u32)\n        .saturating_mul(1000);\n    Ok(Duration::new(sec, nsec))\n}\n\n#[cfg(test)]\n#[allow(clippy::all, clippy::print_stdout)]\nmod tests {\n    use super::*;\n    use test::Bencher;\n\n    // There is a field named `cpu_usage` in `thread_basic_info` which represents the CPU usage of the thread.\n    // However, we have no idea about how long the interval is. And it will make the API being different from other platforms.\n    // We calculate the usage instead of using the field directory to make the API is the same on all platforms.\n    // The cost of the calculation is very very small according to the result of the following benchmark.\n    #[bench]\n    fn bench_cpu_usage_by_calculate(b: &mut Bencher) {\n        let tid = ThreadId::current();\n        let last_stat = get_thread_basic_info(tid).unwrap();\n        let last_time = Instant::now();\n\n        b.iter(|| {\n            let cur_stat = get_thread_basic_info(tid).unwrap();\n            let cur_time = Instant::now();\n\n            let cur_user_time = time_value_to_u64(cur_stat.user_time);\n            let cur_sys_time = time_value_to_u64(cur_stat.system_time);\n            let last_user_time = time_value_to_u64(last_stat.user_time);\n            let last_sys_time = time_value_to_u64(last_stat.system_time);\n\n            let dt_duration = cur_time - last_time;\n            let cpu_time_us = cur_user_time + cur_sys_time - last_user_time - last_sys_time;\n            let dt_wtime = Duration::from_micros(cpu_time_us);\n\n            let _ = (cur_stat, cur_time);\n            let _ = dt_wtime.as_micros() as f64 / dt_duration.as_micros() as f64;\n        });\n    }\n\n    #[bench]\n    fn bench_cpu_usage_by_field(b: &mut Bencher) {\n        let tid = ThreadId::current();\n        b.iter(|| {\n            let cur_stat = get_thread_basic_info(tid).unwrap();\n            let _ = cur_stat.cpu_usage / 1000;\n        });\n    }\n}\n"
  },
  {
    "path": "src/cpu/mod.rs",
    "content": "//! Get cpu usage for current process and specified thread.\n//!\n//! A method named `cpu` on `ThreadStat` and `ProcessStat`\n//! can retrieve cpu usage of thread and process respectively.\n//!\n//! The returning value is unnormalized, that is for multi-processor machine,\n//! the cpu usage will beyond 100%, for example returning 2.8 means 280% cpu usage.\n//! If normalized value is what you expected, divide the returning by processor_numbers.\n//!\n//! ## Example\n//!\n//! ```\n//! # use perf_monitor::cpu::ThreadStat;\n//! let mut stat = ThreadStat::cur().unwrap();\n//! let _ = (0..1_000_000).into_iter().sum::<u64>();\n//! let usage = stat.cpu().unwrap();\n//! println!(\"current thread cpu usage is {:.2}%\", usage * 100f64);\n//! ```\n//!\n//! ## Bottom Layer Interface\n//! | platform | thread | process |\n//! | -- | -- | -- |\n//! | windows |[GetThreadTimes] | [GetProcessTimes] |\n//! | linux & android | [/proc/{pid}/task/{tid}/stat][man5] | [clockgettime] |\n//! | macos & ios | [thread_info] | [getrusage] |\n//!\n//! [GetThreadTimes]: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes\n//! [GetProcessTimes]: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes\n//! [man5]: https://man7.org/linux/man-pages/man5/proc.5.html\n//! [thread_info]: http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/thread_info.html\n//! [clockgettime]: https://man7.org/linux/man-pages/man2/clock_gettime.2.html\n//! [getrusage]: https://www.man7.org/linux/man-pages/man2/getrusage.2.html\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nmod android_linux;\n#[cfg(any(target_os = \"ios\", target_os = \"macos\"))]\nmod ios_macos;\n#[cfg(target_os = \"windows\")]\nmod windows;\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nuse android_linux as platform;\n#[cfg(any(target_os = \"ios\", target_os = \"macos\"))]\nuse ios_macos as platform;\n#[cfg(target_os = \"windows\")]\nuse windows as platform;\n\npub use platform::{cpu_time, ThreadId};\npub use std::io::Result;\nuse std::{\n    io, mem,\n    time::{Duration, Instant},\n};\n\n/// logical processor number\npub fn processor_numbers() -> std::io::Result<usize> {\n    std::thread::available_parallelism().map(|x| x.get())\n}\n\n/// A struct to monitor process cpu usage\npub struct ProcessStat {\n    now: Instant,\n    cpu_time: Duration,\n}\n\nimpl ProcessStat {\n    /// return a monitor of current process\n    pub fn cur() -> io::Result<Self> {\n        Ok(ProcessStat {\n            now: Instant::now(),\n            cpu_time: platform::cpu_time()?,\n        })\n    }\n\n    /// return the cpu usage from last invoke,\n    /// or when this struct created if it is the first invoke.\n    pub fn cpu(&mut self) -> io::Result<f64> {\n        let old_time = mem::replace(&mut self.cpu_time, platform::cpu_time()?);\n        let old_now = mem::replace(&mut self.now, Instant::now());\n        let real_time = self.now.saturating_duration_since(old_now).as_secs_f64();\n        let cpu_time = self.cpu_time.saturating_sub(old_time).as_secs_f64();\n        Ok(cpu_time / real_time)\n    }\n}\n\n/// A struct to monitor thread cpu usage\npub struct ThreadStat {\n    stat: platform::ThreadStat,\n}\n\nimpl ThreadStat {\n    /// return a monitor of current thread.\n    pub fn cur() -> Result<Self> {\n        Ok(ThreadStat {\n            stat: platform::ThreadStat::cur()?,\n        })\n    }\n\n    /// return a monitor of specified thread.\n    ///\n    /// `tid` is **NOT** `std::thread::ThreadId`.\n    /// [`ThreadId::current`] can be used to retrieve a valid tid.\n    pub fn build(thread_id: ThreadId) -> Result<Self> {\n        Ok(ThreadStat {\n            stat: platform::ThreadStat::build(thread_id)?,\n        })\n    }\n\n    /// return the cpu usage from last invoke,\n    /// or when this struct created if it is the first invoke.\n    pub fn cpu(&mut self) -> Result<f64> {\n        self.stat.cpu()\n    }\n\n    /// return the cpu_time in user mode and system mode from last invoke,\n    /// or when this struct created if it is the first invoke.\n    pub fn cpu_time(&mut self) -> Result<Duration> {\n        self.stat.cpu_time()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    // this test should be executed alone.\n    #[test]\n    #[ignore]\n    fn test_process_usage() {\n        let mut stat = ProcessStat::cur().unwrap();\n\n        std::thread::sleep(std::time::Duration::from_secs(1));\n\n        let usage = stat.cpu().unwrap();\n\n        assert!(usage < 0.01);\n\n        let num = processor_numbers().unwrap();\n        for _ in 0..num * 10 {\n            std::thread::spawn(move || loop {\n                let _ = (0..10_000_000).into_iter().sum::<u128>();\n            });\n        }\n\n        let mut stat = ProcessStat::cur().unwrap();\n\n        std::thread::sleep(std::time::Duration::from_secs(1));\n\n        let usage = stat.cpu().unwrap();\n\n        assert!(usage > 0.9 * num as f64)\n    }\n\n    #[test]\n    fn test_thread_usage() {\n        let mut stat = ThreadStat::cur().unwrap();\n\n        std::thread::sleep(std::time::Duration::from_secs(1));\n        let usage = stat.cpu().unwrap();\n        assert!(usage < 0.01);\n\n        let mut x = 1_000_000u64;\n        std::hint::black_box(&mut x);\n        let mut times = 1000u64;\n        std::hint::black_box(&mut times);\n        for i in 0..times {\n            let x = (0..x + i).into_iter().sum::<u64>();\n            std::hint::black_box(x);\n        }\n        let usage = stat.cpu().unwrap();\n        assert!(usage > 0.5)\n    }\n}\n"
  },
  {
    "path": "src/cpu/windows/mod.rs",
    "content": "use super::processor_numbers;\nuse super::windows::process_times::ProcessTimes;\nuse super::windows::system_times::SystemTimes;\nuse super::windows::thread_times::ThreadTimes;\nuse std::io::Result;\nuse std::time::Duration;\nuse windows_sys::Win32::Foundation::FILETIME;\nuse windows_sys::Win32::System::Threading::GetCurrentThreadId;\n\npub mod process_times;\npub mod system_times;\npub mod thread_times;\n\n#[derive(Clone, Copy)]\npub struct ThreadId(u32);\n\nimpl ThreadId {\n    #[inline]\n    pub fn current() -> Self {\n        ThreadId(unsafe { GetCurrentThreadId() })\n    }\n}\n\n/// convert to u64, unit 100 ns\nfn filetime_to_ns100(ft: &FILETIME) -> u64 {\n    ((ft.dwHighDateTime as u64) << 32) + ft.dwLowDateTime as u64\n}\n\npub struct ThreadStat {\n    tid: ThreadId,\n    last_work_time: u64,\n    last_total_time: u64,\n}\n\nimpl ThreadStat {\n    fn get_times(thread_id: ThreadId) -> Result<(u64, u64)> {\n        let system_times = SystemTimes::capture()?;\n        let thread_times = ThreadTimes::capture_with_thread_id(thread_id)?;\n\n        let work_time =\n            filetime_to_ns100(&thread_times.kernel) + filetime_to_ns100(&thread_times.user);\n        let total_time =\n            filetime_to_ns100(&system_times.kernel) + filetime_to_ns100(&system_times.user);\n        Ok((work_time, total_time))\n    }\n\n    pub fn cur() -> Result<Self> {\n        let tid = ThreadId::current();\n        let (work_time, total_time) = Self::get_times(tid)?;\n        Ok(ThreadStat {\n            tid,\n            last_work_time: work_time,\n            last_total_time: total_time,\n        })\n    }\n\n    pub fn build(tid: ThreadId) -> Result<Self> {\n        let (work_time, total_time) = Self::get_times(tid)?;\n        Ok(ThreadStat {\n            tid,\n            last_work_time: work_time,\n            last_total_time: total_time,\n        })\n    }\n\n    pub fn cpu(&mut self) -> Result<f64> {\n        let (work_time, total_time) = Self::get_times(self.tid)?;\n\n        let dt_total_time = total_time - self.last_total_time;\n        if dt_total_time == 0 {\n            return Ok(0.0);\n        }\n        let dt_work_time = work_time - self.last_work_time;\n\n        self.last_work_time = work_time;\n        self.last_total_time = total_time;\n\n        Ok(dt_work_time as f64 / dt_total_time as f64 * processor_numbers()? as f64)\n    }\n\n    pub fn cpu_time(&mut self) -> Result<Duration> {\n        let (work_time, total_time) = Self::get_times(self.tid)?;\n\n        let cpu_time = work_time - self.last_work_time;\n\n        self.last_work_time = work_time;\n        self.last_total_time = total_time;\n\n        Ok(Duration::from_nanos(cpu_time))\n    }\n}\n\n#[inline]\npub fn cpu_time() -> Result<Duration> {\n    let process_times = ProcessTimes::capture_current()?;\n\n    let kt = filetime_to_ns100(&process_times.kernel);\n    let ut = filetime_to_ns100(&process_times.user);\n\n    // convert ns\n    //\n    // Note: make it ns unit may overflow in some cases.\n    // For example, a machine with 128 cores runs for one year.\n    let cpu = (kt + ut).saturating_mul(100);\n\n    // make it un-normalized\n    let cpu = cpu * processor_numbers()? as u64;\n\n    Ok(Duration::from_nanos(cpu))\n}\n"
  },
  {
    "path": "src/cpu/windows/process_times.rs",
    "content": "use std::io::Error;\nuse std::io::Result;\nuse std::mem::MaybeUninit;\nuse windows_sys::Win32::Foundation::FILETIME;\nuse windows_sys::Win32::Foundation::HANDLE;\nuse windows_sys::Win32::System::Threading::GetCurrentProcess;\nuse windows_sys::Win32::System::Threading::GetProcessTimes;\n\npub struct ProcessTimes {\n    pub create: FILETIME,\n    pub exit: FILETIME,\n    pub kernel: FILETIME,\n    pub user: FILETIME,\n}\n\nimpl ProcessTimes {\n    pub fn capture_current() -> Result<Self> {\n        unsafe { Self::capture_with_handle(GetCurrentProcess()) }\n    }\n\n    pub unsafe fn capture_with_handle(handle: HANDLE) -> Result<Self> {\n        let mut create = MaybeUninit::<FILETIME>::uninit();\n        let mut exit = MaybeUninit::<FILETIME>::uninit();\n        let mut kernel = MaybeUninit::<FILETIME>::uninit();\n        let mut user = MaybeUninit::<FILETIME>::uninit();\n        let ret = unsafe {\n            GetProcessTimes(\n                handle,\n                create.as_mut_ptr(),\n                exit.as_mut_ptr(),\n                kernel.as_mut_ptr(),\n                user.as_mut_ptr(),\n            )\n        };\n        if ret == 0 {\n            return Err(Error::last_os_error());\n        }\n        Ok(unsafe {\n            Self {\n                create: create.assume_init(),\n                exit: exit.assume_init(),\n                kernel: kernel.assume_init(),\n                user: user.assume_init(),\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "src/cpu/windows/system_times.rs",
    "content": "use std::io::Error;\nuse std::io::Result;\nuse std::mem::MaybeUninit;\nuse windows_sys::Win32::Foundation::FILETIME;\nuse windows_sys::Win32::System::Threading::GetSystemTimes;\n\npub struct SystemTimes {\n    pub idle: FILETIME,\n    pub kernel: FILETIME,\n    pub user: FILETIME,\n}\n\nimpl SystemTimes {\n    pub fn capture() -> Result<Self> {\n        let mut idle = MaybeUninit::<FILETIME>::uninit();\n        let mut kernel = MaybeUninit::<FILETIME>::uninit();\n        let mut user = MaybeUninit::<FILETIME>::uninit();\n        let ret =\n            unsafe { GetSystemTimes(idle.as_mut_ptr(), kernel.as_mut_ptr(), user.as_mut_ptr()) };\n        if ret == 0 {\n            return Err(Error::last_os_error());\n        }\n        Ok(unsafe {\n            Self {\n                idle: idle.assume_init(),\n                kernel: kernel.assume_init(),\n                user: user.assume_init(),\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "src/cpu/windows/thread_times.rs",
    "content": "use super::ThreadId;\nuse crate::utils::ptr_upgrade::HandleUpgrade;\nuse crate::utils::windows_handle::Handle;\nuse std::io::Error;\nuse std::io::Result;\nuse std::mem::MaybeUninit;\nuse windows_sys::Win32::Foundation::FALSE;\nuse windows_sys::Win32::Foundation::FILETIME;\nuse windows_sys::Win32::Foundation::HANDLE;\nuse windows_sys::Win32::System::Threading::GetCurrentThread;\nuse windows_sys::Win32::System::Threading::GetThreadTimes;\nuse windows_sys::Win32::System::Threading::OpenThread;\nuse windows_sys::Win32::System::Threading::THREAD_QUERY_LIMITED_INFORMATION;\n\npub struct ThreadTimes {\n    pub create: FILETIME,\n    pub exit: FILETIME,\n    pub kernel: FILETIME,\n    pub user: FILETIME,\n}\n\nimpl ThreadTimes {\n    #[allow(dead_code)]\n    pub fn capture_current() -> Result<Self> {\n        unsafe { Self::capture_with_handle(GetCurrentThread()) }\n    }\n\n    pub fn capture_with_thread_id(ThreadId(thread_id): ThreadId) -> Result<Self> {\n        // Use THREAD_QUERY_LIMITED_INFORMATION to acquire minimum access rights and\n        // support for Windows Server 2023 and Windows XP is dropped:\n        //\n        // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes\n        let handle =\n            unsafe { OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE as i32, thread_id) }\n                .upgrade()\n                .map(|x| unsafe { Handle::new(x) });\n        let Some(handle) = handle else {\n            return Err(Error::last_os_error());\n        };\n        unsafe { Self::capture_with_handle(handle.as_handle()) }\n    }\n\n    /// Get thread times for given thread handle, given handle needs specific access rights:\n    ///\n    /// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes\n    pub unsafe fn capture_with_handle(handle: HANDLE) -> Result<Self> {\n        let mut create = MaybeUninit::<FILETIME>::uninit();\n        let mut exit = MaybeUninit::<FILETIME>::uninit();\n        let mut kernel = MaybeUninit::<FILETIME>::uninit();\n        let mut user = MaybeUninit::<FILETIME>::uninit();\n        let ret = unsafe {\n            GetThreadTimes(\n                handle,\n                create.as_mut_ptr(),\n                exit.as_mut_ptr(),\n                kernel.as_mut_ptr(),\n                user.as_mut_ptr(),\n            )\n        };\n        if ret == 0 {\n            return Err(Error::last_os_error());\n        }\n        Ok(unsafe {\n            Self {\n                create: create.assume_init(),\n                exit: exit.assume_init(),\n                kernel: kernel.assume_init(),\n                user: user.assume_init(),\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "src/fd/android_linux.rs",
    "content": "pub fn fd_count_pid(pid: u32) -> std::io::Result<usize> {\n    // Subtract 2 to exclude `.`, `..` entries\n    std::fs::read_dir(format!(\"/proc/{}/fd\", pid)).map(|entries| entries.count().saturating_sub(2))\n}\n\npub fn fd_count_cur() -> std::io::Result<usize> {\n    // Subtract 3 to exclude `.`, `..` entries and fd created by `read_dir`\n    std::fs::read_dir(\"/proc/self/fd\").map(|entries| entries.count().saturating_sub(3))\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_fd_count() {\n        #[cfg(target_os = \"linux\")]\n        const TEMP_DIR: &str = \"/tmp\";\n        #[cfg(target_os = \"android\")]\n        const TEMP_DIR: &str = \"/data/local/tmp\";\n\n        const NUM: usize = 100;\n\n        // open some files and do not close them.\n        let fds: Vec<_> = (0..NUM)\n            .map(|i| {\n                let fname = format!(\"{}/tmpfile{}\", TEMP_DIR, i);\n                std::fs::File::create(fname).unwrap()\n            })\n            .collect();\n        let count = fd_count_cur().unwrap();\n\n        dbg!(count);\n        assert!(count >= NUM);\n        let old_count = count;\n\n        drop(fds);\n        let count = fd_count_cur().unwrap();\n        // Though tests are run in multi-thread mode without using nextest, we\n        // assume NUM is big enough to make fd count lower in a short period.\n        assert!(count < old_count);\n    }\n}\n"
  },
  {
    "path": "src/fd/darwin_private.rs",
    "content": "//! This is assumed to be private api due to App Store's rejection: https://github.com/dotnet/maui/issues/3290\n//!\n//! Inspired by lsof: <https://github.com/apple-opensource/lsof/blob/da09c8c6436286e5bd8c400b42e86b54404f12a7/lsof/dialects/darwin/libproc/dproc.c#L623>\n\nuse libc::proc_taskallinfo;\nuse std::mem::MaybeUninit;\nuse std::os::raw::c_int;\nuse std::os::raw::c_void;\n\nconst PROC_PIDLISTFDS: c_int = 1;\nconst PROC_PIDTASKALLINFO: c_int = 2;\n\nextern \"C\" {\n    fn proc_pidinfo(\n        pid: c_int,\n        flavor: c_int,\n        arg: u64,\n        buffer: *mut c_void,\n        buffersize: c_int,\n    ) -> c_int;\n}\n\n#[repr(C)]\npub struct proc_fdinfo {\n    pub proc_fd: i32,\n    pub proc_fdtype: u32,\n}\n\npub fn fd_count_cur() -> std::io::Result<usize> {\n    fd_count_pid(std::process::id())\n}\n\npub fn fd_count_pid(pid: u32) -> std::io::Result<usize> {\n    let pid = pid as i32;\n    let max_fds = unsafe {\n        let mut info = MaybeUninit::<proc_taskallinfo>::uninit();\n        let buffersize = std::mem::size_of::<proc_taskallinfo>() as c_int;\n        let ret = proc_pidinfo(\n            pid,\n            PROC_PIDTASKALLINFO,\n            0,\n            info.as_mut_ptr() as _,\n            buffersize,\n        );\n        if ret <= 0 {\n            return Err(std::io::Error::from_raw_os_error(ret));\n        }\n        if ret < buffersize {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::Other,\n                \"proc_pidinfo(PROC_PIDTASKALLINFO) too few bytes\",\n            ));\n        }\n        info.assume_init_ref().pbsd.pbi_nfiles as c_int\n    };\n    let buffersize = max_fds * std::mem::size_of::<proc_fdinfo>() as c_int;\n    let mut buffer = vec![0u8; buffersize as usize];\n    let ret = unsafe {\n        proc_pidinfo(\n            pid,\n            PROC_PIDLISTFDS,\n            0,\n            buffer.as_mut_ptr() as _,\n            buffersize,\n        )\n    };\n    if ret <= 0 {\n        Err(std::io::Error::from_raw_os_error(ret))\n    } else {\n        Ok(ret as usize / std::mem::size_of::<proc_fdinfo>())\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_fd_count_private() {\n        // case1: open some files and do not close them.\n        {\n            let mut buf = vec![];\n            const NUM: usize = 100;\n            let init_count = fd_count_cur().unwrap();\n\n            for i in 0..NUM {\n                let fname = format!(\"/tmp/fd_count_test_tmpfile{}\", i);\n                let file = std::fs::OpenOptions::new()\n                    .write(true)\n                    .create(true)\n                    .open(fname);\n                buf.push(file);\n            }\n            let count = fd_count_cur().unwrap();\n            assert_eq!(NUM + init_count, count);\n        }\n\n        // case2: compare the result with lsof.\n        {\n            let count_devfd = fd_count_cur().unwrap();\n            let count_lsof = fd_lsof() - 2; // minus pipe fd between parent process and child process.\n            assert_eq!(count_lsof, count_devfd);\n        }\n    }\n\n    fn fd_lsof() -> usize {\n        let pid = unsafe { libc::getpid() };\n        let output = std::process::Command::new(\"lsof\")\n            .arg(\"-p\")\n            .arg(pid.to_string())\n            .output()\n            .unwrap();\n        std::thread::sleep(std::time::Duration::from_secs(1));\n        let output_txt = String::from_utf8(output.stdout).unwrap();\n        let count_lsof = output_txt\n            .lines()\n            .filter(|s| s.find(\"cwd\").is_none() && s.find(\"txt\").is_none())\n            .map(|s| println!(\"{}\", s))\n            .count();\n\n        count_lsof - 1 // minus title line of lsof output.\n    }\n}\n"
  },
  {
    "path": "src/fd/ios.rs",
    "content": "// There is no api to retrieve the fd count of the process for ios.\n// following links contains a available way, but it's complicated and\n// inefficient. <https://stackoverflow.com/questions/4083608/on-ios-iphone-too-many-open-files-need-to-list-open-files-like-lsof>\n\npub fn fd_count_cur() -> std::io::Result<usize> {\n    unimplemented!()\n}\n\npub fn fd_count_pid(pid: u32) -> std::io::Result<usize> {\n    unimplemented!()\n}\n"
  },
  {
    "path": "src/fd/macos.rs",
    "content": "pub fn fd_count_cur() -> std::io::Result<usize> {\n    // Remove the opening fd created by `read_dir`\n    std::fs::read_dir(\"/dev/fd\").map(|entries| entries.count().saturating_sub(1))\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    // We put these test case in one test to make them get executed one by one.\n    // Parallel execution causes them to interact with each other and fail the test.\n    #[test]\n    fn test_fd_count() {\n        // case1: open some files and do not close them.\n        {\n            let mut buf = vec![];\n            const NUM: usize = 100;\n            let init_count = fd_count_cur().unwrap();\n\n            for i in 0..NUM {\n                let fname = format!(\"/tmp/fd_count_test_tmpfile{}\", i);\n                let file = std::fs::OpenOptions::new()\n                    .write(true)\n                    .create(true)\n                    .open(fname);\n                buf.push(file);\n            }\n            let count = fd_count_cur().unwrap();\n            assert_eq!(NUM + init_count, count);\n        }\n\n        // case2: compare the result with lsof.\n        {\n            let count_devfd = fd_count_cur().unwrap();\n            let count_lsof = fd_lsof() - 2; // minus pipe fd between parent process and child process.\n            assert_eq!(count_lsof, count_devfd);\n        }\n    }\n\n    fn fd_lsof() -> usize {\n        let pid = unsafe { libc::getpid() };\n        let output = std::process::Command::new(\"lsof\")\n            .arg(\"-p\")\n            .arg(pid.to_string())\n            .output()\n            .unwrap();\n        std::thread::sleep(std::time::Duration::from_secs(1));\n        let output_txt = String::from_utf8(output.stdout).unwrap();\n        let count_lsof = output_txt\n            .lines()\n            .filter(|s| s.find(\"cwd\").is_none() && s.find(\"txt\").is_none())\n            .map(|s| println!(\"{}\", s))\n            .count();\n\n        count_lsof - 1 // minus title line of lsof output.\n    }\n}\n"
  },
  {
    "path": "src/fd/mod.rs",
    "content": "//! Get file descriptor(say handle for windows) numbers for current process.\n//!\n//! ```\n//! use perf_monitor::fd::fd_count_cur;\n//!\n//! let count = fd_count_cur().unwrap();\n//! ```\n//!\n//! ## Bottom Layer Interface\n//!\n//! - Windows: [GetProcessHandleCount](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesshandlecount)\n//! - Linux & android: [/proc/{pid}/fd](https://man7.org/linux/man-pages/man5/proc.5.html)\n//! - MacOS: [/dev/fd](https://www.informit.com/articles/article.aspx?p=99706&seqNum=15)\n//! - iOS Unfortunately there is no api to retrieve the fd count of the process for iOS.\n//! Following links contains a available method, but it's complicated and\n//! inefficient. <https://stackoverflow.com/questions/4083608/on-ios-iphone-too-many-open-files-need-to-list-open-files-like-lsof>\n//!\n//! ## Other Process\n//!\n//! For windows, linux and android(maybe), it is possible to get fd number of other process.\n//! However we didn't re-export these function because macos and ios is not supported.\n//!\n\n#[cfg(target_os = \"windows\")]\nmod windows;\n#[cfg(target_os = \"windows\")]\nuse windows as platform;\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nmod android_linux;\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nuse android_linux as platform;\n\n#[cfg(all(target_os = \"macos\", not(feature = \"darwin_private\")))]\nmod macos;\n#[cfg(all(target_os = \"macos\", not(feature = \"darwin_private\")))]\nuse macos as platform;\n\n#[cfg(all(target_os = \"ios\", not(feature = \"darwin_private\")))]\nmod ios;\n#[cfg(all(target_os = \"ios\", not(feature = \"darwin_private\")))]\nuse ios as platform;\n\n#[cfg(all(\n    any(target_os = \"macos\", target_os = \"ios\"),\n    feature = \"darwin_private\"\n))]\nmod darwin_private;\n#[cfg(all(\n    any(target_os = \"macos\", target_os = \"ios\"),\n    feature = \"darwin_private\"\n))]\nuse darwin_private as platform;\n\n/// return the fd count of current process\n#[inline]\npub fn fd_count_cur() -> std::io::Result<usize> {\n    platform::fd_count_cur().map(|count| count as usize)\n}\n"
  },
  {
    "path": "src/fd/windows.rs",
    "content": "use windows_sys::Win32::Foundation::FALSE;\nuse windows_sys::Win32::Foundation::HANDLE;\nuse windows_sys::Win32::System::Threading::GetCurrentProcess;\nuse windows_sys::Win32::System::Threading::GetProcessHandleCount;\nuse windows_sys::Win32::System::Threading::OpenProcess;\nuse windows_sys::Win32::System::Threading::PROCESS_QUERY_LIMITED_INFORMATION;\n\nuse crate::utils::ptr_upgrade::HandleUpgrade;\nuse crate::utils::windows_handle::Handle;\n\n#[inline]\nfn process_fd_count(handler: HANDLE) -> std::io::Result<u32> {\n    let mut count = 0;\n    let ret = unsafe { GetProcessHandleCount(handler, &mut count) };\n    if ret == 0 {\n        return Err(std::io::Error::last_os_error());\n    }\n    Ok(count)\n}\n\npub fn fd_count_pid(pid: u32) -> std::io::Result<u32> {\n    // Use PROCESS_QUERY_LIMITED_INFORMATION to acquire less privilege and drop\n    // support for Windows Server 2023 and Windows XP:\n    // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesshandlecount\n    let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE as i32, pid) }\n        .upgrade()\n        .map(|x| unsafe { Handle::new(x) });\n    let Some(handle) = handle else {\n        return Err(std::io::Error::last_os_error());\n    };\n    process_fd_count(handle.as_handle())\n}\n\npub fn fd_count_cur() -> std::io::Result<u32> {\n    process_fd_count(unsafe { GetCurrentProcess() })\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use windows_sys::Win32::Foundation::CloseHandle;\n\n    #[test]\n    fn test_count_fd() {\n        const NUM: u32 = 100000;\n\n        // open then close handle\n        for _ in 0..NUM {\n            let pid = std::process::id();\n            let handler =\n                unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE as i32, pid) };\n            unsafe { CloseHandle(handler) };\n        }\n        let new_count = fd_count_cur().unwrap();\n\n        assert!(new_count < NUM);\n\n        // open some handle and do not close them\n        for _ in 0..NUM {\n            let pid = std::process::id();\n            unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE as i32, pid) };\n        }\n        let new_count = fd_count_cur().unwrap();\n\n        assert!(new_count >= NUM);\n    }\n}\n"
  },
  {
    "path": "src/io/mod.rs",
    "content": "//! Get io usage for current process.\nuse thiserror::Error;\n\n#[derive(Error, Debug)]\n#[error(\"IOStatsError({code}):{msg}\")]\npub struct IOStatsError {\n    pub code: i32,\n    pub msg: String,\n}\n\nimpl From<std::io::Error> for IOStatsError {\n    fn from(e: std::io::Error) -> Self {\n        Self {\n            code: e.kind() as i32,\n            msg: e.to_string(),\n        }\n    }\n}\n\nimpl From<std::num::ParseIntError> for IOStatsError {\n    fn from(e: std::num::ParseIntError) -> Self {\n        Self {\n            code: 0,\n            msg: e.to_string(),\n        }\n    }\n}\n/// A struct represents io status.\n#[derive(Debug, Clone, Default)]\npub struct IOStats {\n    /// (linux & windows)  the number of read operations performed (cumulative)\n    pub read_count: u64,\n\n    /// (linux & windows) the number of write operations performed (cumulative)\n    pub write_count: u64,\n\n    /// the number of bytes read (cumulative).\n    pub read_bytes: u64,\n\n    /// the number of bytes written (cumulative)\n    pub write_bytes: u64,\n}\n/// Get the io stats of current process. Most platforms are supported.\n#[cfg(any(\n    target_os = \"linux\",\n    target_os = \"android\",\n    target_os = \"macos\",\n    target_os = \"windows\"\n))]\npub fn get_process_io_stats() -> Result<IOStats, IOStatsError> {\n    get_process_io_stats_impl()\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nfn get_process_io_stats_impl() -> Result<IOStats, IOStatsError> {\n    use std::{\n        io::{BufRead, BufReader},\n        str::FromStr,\n    };\n    let mut io_stats = IOStats::default();\n    let reader = BufReader::new(std::fs::File::open(\"/proc/self/io\")?);\n\n    for line in reader.lines() {\n        let line = line?;\n        let mut s = line.split_whitespace();\n        if let (Some(field), Some(value)) = (s.next(), s.next()) {\n            match field {\n                \"syscr:\" => io_stats.read_count = u64::from_str(value)?,\n                \"syscw:\" => io_stats.write_count = u64::from_str(value)?,\n                \"read_bytes:\" => io_stats.read_bytes = u64::from_str(value)?,\n                \"write_bytes:\" => io_stats.write_bytes = u64::from_str(value)?,\n                _ => continue,\n            }\n        }\n    }\n\n    Ok(io_stats)\n}\n\n#[cfg(target_os = \"windows\")]\nfn get_process_io_stats_impl() -> Result<IOStats, IOStatsError> {\n    use std::mem::MaybeUninit;\n    use windows_sys::Win32::System::Threading::GetCurrentProcess;\n    use windows_sys::Win32::System::Threading::GetProcessIoCounters;\n    use windows_sys::Win32::System::Threading::IO_COUNTERS;\n    let mut io_counters = MaybeUninit::<IO_COUNTERS>::uninit();\n    let ret = unsafe {\n        // If the function succeeds, the return value is nonzero.\n        // If the function fails, the return value is zero.\n        // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprocessiocounters\n        GetProcessIoCounters(GetCurrentProcess(), io_counters.as_mut_ptr())\n    };\n    if ret == 0 {\n        return Err(std::io::Error::last_os_error().into());\n    }\n    let io_counters = unsafe { io_counters.assume_init() };\n    Ok(IOStats {\n        read_count: io_counters.ReadOperationCount,\n        write_count: io_counters.WriteOperationCount,\n        read_bytes: io_counters.ReadTransferCount,\n        write_bytes: io_counters.WriteTransferCount,\n    })\n}\n\n#[cfg(target_os = \"macos\")]\nfn get_process_io_stats_impl() -> Result<IOStats, IOStatsError> {\n    use libc::{rusage_info_v2, RUSAGE_INFO_V2};\n    use std::{mem::MaybeUninit, os::raw::c_int};\n\n    let mut rusage_info_v2 = MaybeUninit::<rusage_info_v2>::uninit();\n    let ret_code = unsafe {\n        libc::proc_pid_rusage(\n            std::process::id() as c_int,\n            RUSAGE_INFO_V2,\n            rusage_info_v2.as_mut_ptr() as *mut _,\n        )\n    };\n    if ret_code != 0 {\n        return Err(std::io::Error::last_os_error().into());\n    }\n    let rusage_info_v2 = unsafe { rusage_info_v2.assume_init() };\n    Ok(IOStats {\n        read_bytes: rusage_info_v2.ri_diskio_bytesread,\n        write_bytes: rusage_info_v2.ri_diskio_byteswritten,\n        ..Default::default()\n    })\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! This crate provide the ability to retrieve information for profiling.\n//!\n//!\n\n#![cfg_attr(test, allow(clippy::all, clippy::unwrap_used))]\n#![cfg_attr(doc, feature(doc_cfg))]\n#![cfg_attr(test, feature(test))]\n\n#[cfg(test)]\nextern crate test;\n\n#[allow(warnings)]\n#[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\npub(crate) mod bindings {\n    include!(concat!(env!(\"OUT_DIR\"), \"/monitor_rs_ios_macos_binding.rs\"));\n}\n\npub mod cpu;\n\npub mod mem;\n\npub mod io;\n\npub mod fd;\n\nmod utils;\n"
  },
  {
    "path": "src/mem/allocation_counter.rs",
    "content": "use std::{\n    alloc::{GlobalAlloc, Layout, System},\n    sync::atomic::{AtomicBool, AtomicIsize, Ordering},\n};\n\npub const MIN_ALIGN: usize = 16; // module `sys_common` is private. https://doc.rust-lang.org/src/std/sys_common/alloc.rs.html#28\n\nstatic ALLOCATED: AtomicIsize = AtomicIsize::new(0);\nstatic ENABLE: AtomicBool = AtomicBool::new(false);\n\n/// An allocator tracks inuse allocated bytes.\n///\n/// The counter is disable by default. Please enable it by `CountingAllocator::enable()` then call `CountingAllocator::get_allocated()` will return the bytes inused.\npub struct CountingAllocator;\n\nimpl CountingAllocator {\n    /// Get the inuse bytes allocated by rust.\n    pub fn get_allocated() -> isize {\n        ALLOCATED.load(Ordering::SeqCst)\n    }\n\n    /// Check whether the counter is enable.\n    pub fn is_enable() -> bool {\n        ENABLE.load(Ordering::SeqCst)\n    }\n\n    /// Reset the counter.\n    pub fn reset() {\n        ALLOCATED.store(0, Ordering::SeqCst)\n    }\n\n    /// Enable the counter.\n    pub fn enable() {\n        ENABLE.store(true, Ordering::SeqCst)\n    }\n\n    /// Disable the counter.\n    pub fn disable() {\n        ENABLE.store(false, Ordering::SeqCst)\n    }\n}\n\nunsafe impl GlobalAlloc for CountingAllocator {\n    #[inline]\n    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {\n        let ret = System.alloc(layout);\n        if !ret.is_null() && Self::is_enable() {\n            ALLOCATED.fetch_add(layout.size() as isize, Ordering::SeqCst);\n        }\n        ret\n    }\n\n    #[inline]\n    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {\n        System.dealloc(ptr, layout);\n        if Self::is_enable() {\n            ALLOCATED.fetch_sub(layout.size() as isize, Ordering::SeqCst);\n        }\n    }\n\n    #[inline]\n    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {\n        let ret: *mut u8 = System.realloc(ptr, layout, new_size);\n        if !ret.is_null()\n            && Self::is_enable()\n            && layout.align() <= MIN_ALIGN\n            && layout.align() <= new_size\n        {\n            ALLOCATED.fetch_add(new_size as isize - layout.size() as isize, Ordering::SeqCst);\n        }\n        ret\n    }\n\n    #[inline]\n    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {\n        let ret = System.alloc_zeroed(layout);\n        if !ret.is_null() && Self::is_enable() {\n            ALLOCATED.fetch_add(layout.size() as isize, Ordering::SeqCst);\n        }\n        ret\n    }\n}\n#[cfg(feature = \"allocation_counter\")]\n#[global_allocator]\nstatic _COUNTER: perf_monitor::mem::CountingAllocator = perf_monitor::mem::CountingAllocator;\n"
  },
  {
    "path": "src/mem/apple/heap.rs",
    "content": "//! A wrapper around libmalloc APIs.\n\nuse crate::bindings::{\n    mach_task_self_, malloc_default_zone, malloc_statistics_t, malloc_zone_t, vm_address_t,\n};\nuse std::{io, str};\n\n/// A Wrapper around `malloc_statistics_t`, originally defined at `libmalloc.h`.\npub type MallocStatistics = malloc_statistics_t;\n\n/// A Wrapper around `malloc_zone_t`, originally defined at `libmalloc.h`.\npub struct MallocZone(*mut malloc_zone_t);\n\nimpl MallocZone {\n    /// Get the name of this zone.\n    pub fn name(&self) -> Result<&str, str::Utf8Error> {\n        unsafe { std::ffi::CStr::from_ptr((*self.0).zone_name) }.to_str()\n    }\n    /// Get the statistics of this zone.\n    pub fn statistics(&mut self) -> Option<MallocStatistics> {\n        unsafe {\n            let mut stats = std::mem::MaybeUninit::<malloc_statistics_t>::zeroed();\n            if let Some(f) = (*((*self.0).introspect)).statistics {\n                f(self.0, stats.as_mut_ptr());\n                Some(stats.assume_init())\n            } else {\n                None\n            }\n        }\n    }\n}\n/// Get all malloc zones of current process.\n///\n/// # Safety\n/// CAUTION： `MallocZone`s(*malloc_zone_t) returned by `malloc_get_all_zones`\n/// may be destoryed by other threads.\npub unsafe fn malloc_get_all_zones() -> io::Result<Vec<MallocZone>> {\n    let mut count: u32 = 0;\n    let mut zones: *mut vm_address_t = std::ptr::null_mut();\n    let ret = crate::bindings::malloc_get_all_zones(mach_task_self_, None, &mut zones, &mut count);\n    if ret != 0 {\n        Err(io::Error::from_raw_os_error(ret))\n    } else {\n        let zones =\n            std::slice::from_raw_parts_mut(zones as *mut *mut malloc_zone_t, count as usize)\n                .iter()\n                .map(|&p| MallocZone(p))\n                .collect::<Vec<_>>();\n        Ok(zones)\n    }\n}\n\n/// Get the default malloc zone of current process.\npub fn malloc_get_default_zone() -> MallocZone {\n    MallocZone(unsafe { malloc_default_zone() })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_malloc_get_all_zones() {\n        let zones = unsafe { malloc_get_all_zones().unwrap() };\n        assert!(!zones.is_empty());\n        let zone_names = zones.iter().map(|z| z.name().unwrap()).collect::<Vec<_>>();\n        assert!(zone_names.contains(&\"DefaultMallocZone\"));\n    }\n\n    #[test]\n    fn test_malloc_get_default_zone() {\n        let zone = malloc_get_default_zone();\n        assert_eq!(zone.name().unwrap(), \"DefaultMallocZone\");\n    }\n\n    #[test]\n    fn test_malloc_zone_statistics() {\n        let zones = unsafe { malloc_get_all_zones() }.unwrap();\n        for mut zone in zones {\n            let stat = zone.statistics().unwrap();\n            assert!(stat.blocks_in_use > 0);\n            assert!(stat.size_in_use > 0);\n            assert!(stat.max_size_in_use > 0);\n            assert!(stat.size_allocated > 0);\n        }\n    }\n}\n"
  },
  {
    "path": "src/mem/apple/mod.rs",
    "content": "pub mod heap;\npub mod vm;\n"
  },
  {
    "path": "src/mem/apple/vm.rs",
    "content": "//! A wrapper around `mach_vm_region` API.\n\nuse mach::{\n    kern_return::KERN_SUCCESS,\n    mach_types::vm_task_entry_t,\n    message::mach_msg_type_number_t,\n    port::mach_port_t,\n    traps::mach_task_self,\n    vm_page_size::vm_page_size,\n    vm_region::{vm_region_extended_info_data_t, vm_region_info_t, VM_REGION_EXTENDED_INFO},\n    vm_types::{mach_vm_address_t, mach_vm_size_t},\n};\nuse std::{marker::PhantomData, mem};\n\n// `vm_region_info_t`'s `user_tag`, originally defined at osfmk/mach/vm_statistics.h\n#[derive(Debug)]\npub enum VMRegionKind {\n    Malloc,\n    MallocSmall,\n    MallocLarge,\n    MallocHuge,\n    Sbrk,\n    Realloc,\n    MallocTiny,\n    MallocLargeReusable,\n    MallocLargeReused,\n    Stack,\n    MallocNano,\n    Dylib,\n    Dyld,\n    DyldMalloc,\n    Tag(u32),\n}\n\nimpl From<u32> for VMRegionKind {\n    fn from(user_tag: u32) -> Self {\n        match user_tag {\n            1 => VMRegionKind::Malloc,\n            2 => VMRegionKind::MallocSmall,\n            3 => VMRegionKind::MallocLarge,\n            4 => VMRegionKind::MallocHuge,\n            5 => VMRegionKind::Sbrk,\n            6 => VMRegionKind::Realloc,\n            7 => VMRegionKind::MallocTiny,\n            8 => VMRegionKind::MallocLargeReusable,\n            9 => VMRegionKind::MallocLargeReused,\n            11 => VMRegionKind::MallocNano,\n            30 => VMRegionKind::Stack,\n            33 => VMRegionKind::Dylib,\n            60 => VMRegionKind::Dyld,\n            61 => VMRegionKind::DyldMalloc,\n            tag => VMRegionKind::Tag(tag),\n        }\n    }\n}\n// A wrapper around `vm_region_extended_info` with addr, size ... props.\n#[derive(Debug)]\npub struct VMRegion {\n    addr: mach_vm_address_t,\n    size: mach_vm_size_t,\n    info: mach::vm_region::vm_region_extended_info,\n}\n\nimpl VMRegion {\n    pub fn kind(&self) -> VMRegionKind {\n        VMRegionKind::from(self.info.user_tag)\n    }\n\n    pub fn dirty_bytes(&self) -> usize {\n        self.info.pages_dirtied as usize * unsafe { vm_page_size }\n    }\n\n    pub fn swapped_bytes(&self) -> usize {\n        self.info.pages_swapped_out as usize * unsafe { vm_page_size }\n    }\n\n    pub fn resident_bytes(&self) -> usize {\n        self.info.pages_dirtied as usize * unsafe { vm_page_size }\n    }\n\n    fn end_addr(&self) -> mach_vm_address_t {\n        self.addr + self.size as mach_vm_address_t\n    }\n}\n// An iter over VMRegions.\npub struct VMRegionIter {\n    task: vm_task_entry_t,\n    addr: mach_vm_address_t,\n    _mark: PhantomData<*const ()>, // make it !Sync & !Send\n}\n\nimpl Default for VMRegionIter {\n    fn default() -> Self {\n        Self {\n            task: unsafe { mach_task_self() } as vm_task_entry_t,\n            addr: 1,\n            _mark: PhantomData,\n        }\n    }\n}\n\nimpl Iterator for VMRegionIter {\n    type Item = VMRegion;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let mut count = mem::size_of::<vm_region_extended_info_data_t>() as mach_msg_type_number_t;\n        let mut object_name: mach_port_t = 0;\n        let mut size = unsafe { mem::zeroed::<mach_vm_size_t>() };\n        let mut info = unsafe { mem::zeroed::<vm_region_extended_info_data_t>() };\n        let result = unsafe {\n            mach::vm::mach_vm_region(\n                self.task,\n                &mut self.addr,\n                &mut size,\n                VM_REGION_EXTENDED_INFO,\n                &mut info as *mut vm_region_extended_info_data_t as vm_region_info_t,\n                &mut count,\n                &mut object_name,\n            )\n        };\n        if result != KERN_SUCCESS {\n            None\n        } else {\n            let region = VMRegion {\n                addr: self.addr,\n                size,\n                info,\n            };\n            self.addr = region.end_addr();\n            Some(region)\n        }\n    }\n}\n"
  },
  {
    "path": "src/mem/mod.rs",
    "content": "//! This sub-mod provides some facilities about memory performance profiling.\n//! # Memory usage of current process\n//! There's a platform-related function called `get_process_memory_info` available on MacOS and Windows.\n//! # Memory usage of ALL Rust allocations\n//! We provide a `CountingAllocator` that wraps the system allocator but tracks the bytes used by rust allocations.\n//! This crate DOES NOT replace the global allocator by default. You need to make it as a `global_allocator` or enable the `allocation_counter` feature.\n//! ```ignore\n//! #[global_allocator]\n//! static _COUNTER: perf_monitor::mem::CountingAllocator = perf_monitor:mem::CountingAllocator;\n//! ```\n\nmod allocation_counter;\n\npub use allocation_counter::CountingAllocator;\n\nmod process_memory_info;\npub use process_memory_info::{get_process_memory_info, ProcessMemoryInfo};\n\n#[cfg(target_os = \"macos\")]\n#[cfg_attr(doc, doc(cfg(macos)))]\npub mod apple;\n"
  },
  {
    "path": "src/mem/process_memory_info.rs",
    "content": "use std::io::{Error, Result};\n\n/// Process Memory Info returned by `get_process_memory_info`\n#[derive(Clone, Default)]\npub struct ProcessMemoryInfo {\n    /// this is the non-swapped physical memory a process has used.\n    /// On UNIX it matches `top`'s RES column.\n    ///\n    /// On Windows this is an alias for wset field and it matches \"Mem Usage\"\n    /// column of taskmgr.exe.\n    pub resident_set_size: u64,\n    #[cfg(not(any(target_os = \"android\", target_os = \"linux\")))]\n    #[cfg_attr(doc, doc(cfg(not(linux))))]\n    pub resident_set_size_peak: u64,\n\n    /// this is the total amount of virtual memory used by the process.\n    /// On UNIX it matches `top`'s VIRT column.\n    ///\n    /// On Windows this is an alias for pagefile field and it matches \"Mem\n    /// Usage\" \"VM Size\" column of taskmgr.exe.\n    pub virtual_memory_size: u64,\n\n    ///  This is the sum of:\n    ///\n    ///    + (internal - alternate_accounting)\n    ///\n    ///    + (internal_compressed - alternate_accounting_compressed)\n    ///\n    ///    + iokit_mapped\n    ///\n    ///    + purgeable_nonvolatile\n    ///\n    ///    + purgeable_nonvolatile_compressed\n    ///\n    ///    + page_table\n    ///\n    /// details: <https://github.com/apple/darwin-xnu/blob/master/osfmk/kern/task.c>\n    #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n    #[cfg_attr(doc, doc(macos))]\n    pub phys_footprint: u64,\n\n    #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n    #[cfg_attr(doc, doc(macos))]\n    pub compressed: u64,\n}\n\n#[cfg(target_os = \"windows\")]\nfn get_process_memory_info_impl() -> Result<ProcessMemoryInfo> {\n    use std::mem::MaybeUninit;\n    use windows_sys::Win32::System::ProcessStatus::GetProcessMemoryInfo;\n    use windows_sys::Win32::System::ProcessStatus::PROCESS_MEMORY_COUNTERS;\n    use windows_sys::Win32::System::Threading::GetCurrentProcess;\n    let mut process_memory_counters = MaybeUninit::<PROCESS_MEMORY_COUNTERS>::uninit();\n    let ret = unsafe {\n        // If the function succeeds, the return value is nonzero.\n        // If the function fails, the return value is zero.\n        // https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getprocessmemoryinfo\n        GetProcessMemoryInfo(\n            GetCurrentProcess(),\n            process_memory_counters.as_mut_ptr(),\n            std::mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32,\n        )\n    };\n    if ret == 0 {\n        return Err(Error::last_os_error());\n    }\n    let process_memory_counters = unsafe { process_memory_counters.assume_init() };\n    Ok(ProcessMemoryInfo {\n        resident_set_size: process_memory_counters.WorkingSetSize as u64,\n        resident_set_size_peak: process_memory_counters.PeakWorkingSetSize as u64,\n        virtual_memory_size: process_memory_counters.PagefileUsage as u64,\n    })\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nfn get_process_memory_info_impl() -> Result<ProcessMemoryInfo> {\n    // https://www.kernel.org/doc/Documentation/filesystems/proc.txt\n    let statm = std::fs::read_to_string(\"/proc/self/statm\")?;\n    let mut parts = statm.split(' ');\n    let Some(virtual_memory_size) = parts.next().and_then(|s| s.parse().ok()) else {\n        return Err(Error::new(std::io::ErrorKind::Other, \"Invalid VmSize in /proc/self/statm\"));\n    };\n    let Some(resident_set_size) = parts.next().and_then(|s| s.parse().ok()) else {\n        return Err(Error::new(std::io::ErrorKind::Other, \"Invalid VmRSS in /proc/self/statm\"));\n    };\n    Ok(ProcessMemoryInfo {\n        virtual_memory_size,\n        resident_set_size,\n    })\n}\n\n#[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\nfn get_process_memory_info_impl() -> Result<ProcessMemoryInfo> {\n    use crate::bindings::task_vm_info;\n    use mach::{\n        kern_return::KERN_SUCCESS, message::mach_msg_type_number_t, task::task_info,\n        task_info::TASK_VM_INFO, traps::mach_task_self, vm_types::natural_t,\n    };\n    use std::mem::MaybeUninit;\n\n    let mut task_vm_info = MaybeUninit::<task_vm_info>::uninit();\n\n    // https://github.com/apple/darwin-xnu/blob/master/osfmk/mach/task_info.h line 396\n    // #define TASK_VM_INFO_COUNT\t((mach_msg_type_number_t) \\\n    // (sizeof (task_vm_info_data_t) / sizeof (natural_t)))\n    let mut task_info_cnt: mach_msg_type_number_t = (std::mem::size_of::<task_vm_info>()\n        / std::mem::size_of::<natural_t>())\n        as mach_msg_type_number_t;\n\n    let kern_ret = unsafe {\n        task_info(\n            mach_task_self(),\n            TASK_VM_INFO,\n            task_vm_info.as_mut_ptr() as *mut _,\n            &mut task_info_cnt,\n        )\n    };\n    if kern_ret != KERN_SUCCESS {\n        // see https://docs.rs/mach/0.2.3/mach/kern_return/index.html for more details\n        return Err(Error::new(\n            std::io::ErrorKind::Other,\n            format!(\"DARWIN_KERN_RET_CODE:{}\", kern_ret),\n        ));\n    }\n    let task_vm_info = unsafe { task_vm_info.assume_init() };\n    Ok(ProcessMemoryInfo {\n        resident_set_size: task_vm_info.resident_size,\n        resident_set_size_peak: task_vm_info.resident_size_peak,\n        virtual_memory_size: task_vm_info.virtual_size,\n        phys_footprint: task_vm_info.phys_footprint,\n        compressed: task_vm_info.compressed,\n    })\n}\n\npub fn get_process_memory_info() -> Result<ProcessMemoryInfo> {\n    get_process_memory_info_impl()\n}\n"
  },
  {
    "path": "src/utils/mod.rs",
    "content": "pub mod ptr_upgrade;\n#[cfg(windows)]\npub mod windows_handle;\n"
  },
  {
    "path": "src/utils/ptr_upgrade.rs",
    "content": "use std::num::NonZeroIsize;\n\n/// Triage a return value of windows handle to `Some(handle)` or `None`\npub trait HandleUpgrade: Sized {\n    fn upgrade(self) -> Option<NonZeroIsize>;\n}\n\nimpl HandleUpgrade for isize {\n    #[inline]\n    fn upgrade(self) -> Option<NonZeroIsize> {\n        NonZeroIsize::new(self)\n    }\n}\n"
  },
  {
    "path": "src/utils/windows_handle.rs",
    "content": "use std::num::NonZeroIsize;\nuse windows_sys::Win32::Foundation::CloseHandle;\n\n/// Windows handle wrapper\npub struct Handle(NonZeroIsize);\n\nimpl Handle {\n    /// Invalid handle leads to UB\n    pub unsafe fn new(handle: NonZeroIsize) -> Self {\n        Handle(handle)\n    }\n\n    pub fn as_handle(&self) -> isize {\n        self.0.get()\n    }\n}\n\nimpl Drop for Handle {\n    fn drop(&mut self) {\n        unsafe { CloseHandle(self.0.get()) };\n    }\n}\n"
  }
]