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