diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..2c6fa9336 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +github: [GuillaumeGomez] +patreon: GuillaumeGomez +custom: ["https://paypal.me/imperioland"] diff --git a/.travis.yml b/.travis.yml index 851ba077c..1978a0d98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,7 +55,7 @@ script: - rustc --version - sysctl -a | grep mem - if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then - (rustup component add clippy-preview && cargo clippy) || touch clippy_install_failed; + (rustup component add clippy && cargo clippy) || touch clippy_install_failed; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cargo build --features debug; @@ -74,6 +74,7 @@ script: - if [[ -z "$EXTRA" ]]; then RUST_BACKTRACE=1 cargo test; fi + - cargo doc - cd examples - if [[ -z "$EXTRA" ]]; then RUST_BACKTRACE=1 cargo build; @@ -87,4 +88,4 @@ script: fi - if [[ -z "$EXTRA" ]]; then LD_LIBRARY_PATH=./target/debug ./simple; - fi + fi \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 4081c2a28..c4e1e1a99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "sysinfo" -version = "0.9.6" +version = "0.11.2" authors = ["Guillaume Gomez "] -description = "Library to handle processes" +description = "Library to get system information such as processes, processors, disks, components and networks" repository = "https://github.com/GuillaumeGomez/sysinfo" license = "MIT" readme = "README.md" @@ -16,11 +16,17 @@ build = "build.rs" cfg-if = "0.1" rayon = "^1.0" doc-comment = "0.3" +once_cell = "1.0" +walkdir = "2.2.9" +pnet_datalink = "0.23.0" +num_cpus = "1.11.1" +cache-size = "0.4.0" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["fileapi", "handleapi", "ioapiset", "minwindef", "pdh", "psapi", "synchapi", "sysinfoapi", "tlhelp32", "winbase", "winerror", "winioctl", "winnt"] } +winapi = { version = "0.3", features = ["fileapi", "handleapi", "ifdef", "ioapiset", "minwindef", "pdh", "psapi", "synchapi", "sysinfoapi", "winbase", "winerror", "winioctl", "winnt", "oleauto", "wbemcli", "rpcdce", "combaseapi", "objidl", "objbase", "powerbase", "netioapi"] } +ntapi = "0.3" -[target.'cfg(not(target_os = "unknown"))'.dependencies] +[target.'cfg(not(any(target_os = "unknown", target_arch = "wasm32")))'.dependencies] libc = "0.2" [lib] diff --git a/README.md b/README.md index a9a258d44..1a613f4ac 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,28 @@ Support the following platforms: * Linux * Raspberry - * Mac OSX + * Android + * macOS * Windows It also compiles for Android but never been tested on it. ### Running on Raspberry -It'll be difficult to build on Raspberry. A good way-around is to be build on Linux before sending it to your Raspberry: +It'll be difficult to build on Raspberry. A good way-around is to be build on Linux before sending it to your Raspberry. + +First install the arm toolchain, for example on Ubuntu: `sudo apt-get install gcc-multilib-arm-linux-gnueabihf`. + +Then configure cargo to use the corresponding toolchain: + +```bash +cat << EOF > ~/.cargo/config +[target.armv7-unknown-linux-gnueabihf] +linker = "arm-linux-gnueabihf-gcc" +EOF +``` + +Finally, cross compile: ```bash rustup target add armv7-unknown-linux-gnueabihf @@ -33,8 +47,6 @@ cargo build --target=armv7-unknown-linux-gnueabihf You have an example into the `examples` folder. Just run `cargo run` inside the `examples` folder to start it. Otherwise, here is a little code sample: ```rust -extern crate sysinfo; - use sysinfo::{NetworkExt, System, SystemExt}; let mut sys = System::new(); @@ -55,13 +67,13 @@ for component in sys.get_components_list() { } // Memory information: -println!("total memory: {} kB", sys.get_total_memory()); -println!("used memory : {} kB", sys.get_used_memory()); -println!("total swap : {} kB", sys.get_total_swap()); -println!("used swap : {} kB", sys.get_used_swap()); +println!("total memory: {} KiB", sys.get_total_memory()); +println!("used memory : {} KiB", sys.get_used_memory()); +println!("total swap : {} KiB", sys.get_total_swap()); +println!("used swap : {} KiB", sys.get_used_swap()); // Number of processors -println!("NB processors: {}", sys.get_processor_list().len()); +println!("NB processors: {}", sys.get_processors().len()); // To refresh all system information: sys.refresh_all(); @@ -80,6 +92,82 @@ To build the C example, just run: > LD_LIBRARY_PATH=target/release/ ./simple ``` +### Benchmarks + +You can run the benchmarks locally with rust **nightly** by doing: + +```bash +> cargo bench +``` + +Here are the current results: + +**Linux** + +
+ +```text +test bench_new ... bench: 3,741 ns/iter (+/- 252) +test bench_new_all ... bench: 10,491,084 ns/iter (+/- 450,925) +test bench_refresh_all ... bench: 2,787,974 ns/iter (+/- 235,649) +test bench_refresh_components ... bench: 24,270 ns/iter (+/- 1,127) +test bench_refresh_components_list ... bench: 370,693 ns/iter (+/- 51,925) +test bench_refresh_cpu ... bench: 13,367 ns/iter (+/- 1,858) +test bench_refresh_disks ... bench: 2,532 ns/iter (+/- 108) +test bench_refresh_disks_lists ... bench: 50,359 ns/iter (+/- 5,877) +test bench_refresh_memory ... bench: 11,713 ns/iter (+/- 1,006) +test bench_refresh_networks ... bench: 220,246 ns/iter (+/- 24,294) +test bench_refresh_networks_list ... bench: 229,648 ns/iter (+/- 82,050) +test bench_refresh_process ... bench: 77,375 ns/iter (+/- 10,657) +test bench_refresh_processes ... bench: 2,282,106 ns/iter (+/- 154,098) +test bench_refresh_system ... bench: 52,466 ns/iter (+/- 4,710) +``` +
+ +**Windows** + +
+ +```text +test bench_new ... bench: 7,688,460 ns/iter (+/- 1,230,010) +test bench_new_all ... bench: 24,098,860 ns/iter (+/- 5,260,950) +test bench_refresh_all ... bench: 3,096,107 ns/iter (+/- 94,257) +test bench_refresh_components ... bench: 1,205,378 ns/iter (+/- 40,071) +test bench_refresh_components_list ... bench: 3,181,602 ns/iter (+/- 102,533) +test bench_refresh_cpu ... bench: 395 ns/iter (+/- 18) +test bench_refresh_disks ... bench: 53,082 ns/iter (+/- 1,834) +test bench_refresh_disks_lists ... bench: 114,080 ns/iter (+/- 1,920) +test bench_refresh_memory ... bench: 596 ns/iter (+/- 48) +test bench_refresh_networks ... bench: 37,549 ns/iter (+/- 1,622) +test bench_refresh_networks_list ... bench: 667,180 ns/iter (+/- 59,859) +test bench_refresh_process ... bench: 755 ns/iter (+/- 47) +test bench_refresh_processes ... bench: 1,217,488 ns/iter (+/- 69,041) +test bench_refresh_system ... bench: 1,214,780 ns/iter (+/- 52,013) +``` +
+ +**macOS** + +
+ +```text +test bench_new ... bench: 56,861 ns/iter (+/- 5,653) +test bench_new_all ... bench: 4,634,509 ns/iter (+/- 1,604,369) +test bench_refresh_all ... bench: 1,962,343 ns/iter (+/- 129,726) +test bench_refresh_components ... bench: 294,752 ns/iter (+/- 45,107) +test bench_refresh_components_list ... bench: 895,672 ns/iter (+/- 112,586) +test bench_refresh_cpu ... bench: 11,187 ns/iter (+/- 2,483) +test bench_refresh_disks ... bench: 975 ns/iter (+/- 50) +test bench_refresh_disks_lists ... bench: 25,955 ns/iter (+/- 3,159) +test bench_refresh_memory ... bench: 3,440 ns/iter (+/- 198) +test bench_refresh_networks ... bench: 211,552 ns/iter (+/- 16,686) +test bench_refresh_networks_list ... bench: 211,138 ns/iter (+/- 22,644) +test bench_refresh_process ... bench: 4,174 ns/iter (+/- 1,249) +test bench_refresh_processes ... bench: 803,559 ns/iter (+/- 42,974) +test bench_refresh_system ... bench: 365,762 ns/iter (+/- 55,893) +``` +
+ ## Donations If you appreciate my work and want to support me, you can do it here: diff --git a/appveyor.yml b/appveyor.yml index de8d07e9a..be938e573 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,12 +11,15 @@ install: - curl -sSf -o rustup-init.exe https://win.rustup.rs - rustup-init.exe --default-host "%ARCH%-pc-windows-gnu" --default-toolchain %RUST% -y - SET PATH=C:\Users\appveyor\.cargo\bin;C:\msys64\mingw%BITS%\bin;%PATH%;C:\msys64\usr\bin + - rustup component add clippy - rustc -Vv - cargo -Vv build_script: + - cargo clippy - cargo build - cargo test + - cargo doc - cd examples - cargo build diff --git a/benches/basic.rs b/benches/basic.rs index 407c8774b..970510f19 100644 --- a/benches/basic.rs +++ b/benches/basic.rs @@ -3,6 +3,7 @@ extern crate sysinfo; extern crate test; +use sysinfo::get_current_pid; use sysinfo::SystemExt; #[bench] @@ -12,9 +13,16 @@ fn bench_new(b: &mut test::Bencher) { }); } +#[bench] +fn bench_new_all(b: &mut test::Bencher) { + b.iter(|| { + sysinfo::System::new_all(); + }); +} + #[bench] fn bench_refresh_all(b: &mut test::Bencher) { - let mut s = sysinfo::System::new(); + let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_all(); @@ -23,7 +31,7 @@ fn bench_refresh_all(b: &mut test::Bencher) { #[bench] fn bench_refresh_system(b: &mut test::Bencher) { - let mut s = sysinfo::System::new(); + let mut s = sysinfo::System::new_all(); s.refresh_system(); b.iter(move || { @@ -35,6 +43,7 @@ fn bench_refresh_system(b: &mut test::Bencher) { fn bench_refresh_processes(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); + s.refresh_processes(); // to load the whole processes list a first time. b.iter(move || { s.refresh_processes(); }); @@ -45,7 +54,8 @@ fn bench_refresh_process(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); s.refresh_all(); - let pid = *s.get_process_list().iter().take(1).next().unwrap().0; + // to be sure it'll exist for at least as long as we run + let pid = get_current_pid().expect("failed to get current pid"); b.iter(move || { s.refresh_process(pid); }); @@ -53,7 +63,7 @@ fn bench_refresh_process(b: &mut test::Bencher) { #[bench] fn bench_refresh_disks(b: &mut test::Bencher) { - let mut s = sysinfo::System::new(); + let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_disks(); @@ -61,19 +71,64 @@ fn bench_refresh_disks(b: &mut test::Bencher) { } #[bench] -fn bench_refresh_disk_lists(b: &mut test::Bencher) { +fn bench_refresh_disks_lists(b: &mut test::Bencher) { + let mut s = sysinfo::System::new(); + + b.iter(move || { + s.refresh_disks_list(); + }); +} + +#[bench] +fn bench_refresh_networks(b: &mut test::Bencher) { + let mut s = sysinfo::System::new_all(); + + b.iter(move || { + s.refresh_networks(); + }); +} + +#[bench] +fn bench_refresh_networks_list(b: &mut test::Bencher) { + let mut s = sysinfo::System::new(); + + b.iter(move || { + s.refresh_networks_list(); + }); +} + +#[bench] +fn bench_refresh_memory(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); b.iter(move || { - s.refresh_disk_list(); + s.refresh_memory(); }); } #[bench] -fn bench_refresh_network(b: &mut test::Bencher) { +fn bench_refresh_cpu(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); b.iter(move || { - s.refresh_network(); + s.refresh_cpu(); + }); +} + +#[bench] +fn bench_refresh_components(b: &mut test::Bencher) { + let mut s = sysinfo::System::new_all(); + + b.iter(move || { + s.refresh_components(); + }); +} + +#[bench] +fn bench_refresh_components_list(b: &mut test::Bencher) { + let mut s = sysinfo::System::new_all(); + + b.iter(move || { + s.refresh_components_list(); }); } diff --git a/examples/src/simple.c b/examples/src/simple.c index 534d74cbb..2e6125dc1 100644 --- a/examples/src/simple.c +++ b/examples/src/simple.c @@ -63,19 +63,19 @@ bool process_loop(pid_t pid, CProcess process, void *data) { int main() { CSystem system = sysinfo_init(); sysinfo_refresh_all(system); - printf("total memory: %ld\n", sysinfo_get_total_memory(system)); - printf("free memory: %ld\n", sysinfo_get_free_memory(system)); - printf("used memory: %ld\n", sysinfo_get_used_memory(system)); - printf("total swap: %ld\n", sysinfo_get_total_swap(system)); - printf("free swap: %ld\n", sysinfo_get_free_swap(system)); - printf("used swap: %ld\n", sysinfo_get_used_swap(system)); - printf("network income: %ld\n", sysinfo_get_network_income(system)); - printf("network outcome: %ld\n", sysinfo_get_network_outcome(system)); + printf("total memory: %ld\n", sysinfo_get_total_memory(system)); + printf("free memory: %ld\n", sysinfo_get_free_memory(system)); + printf("used memory: %ld\n", sysinfo_get_used_memory(system)); + printf("total swap: %ld\n", sysinfo_get_total_swap(system)); + printf("free swap: %ld\n", sysinfo_get_free_swap(system)); + printf("used swap: %ld\n", sysinfo_get_used_swap(system)); + printf("networks income: %ld\n", sysinfo_get_networks_income(system)); + printf("networks outcome: %ld\n", sysinfo_get_networks_outcome(system)); unsigned int len = 0, i = 0; float *procs = NULL; sysinfo_get_processors_usage(system, &len, &procs); while (i < len) { - printf("Processor #%d usage: %f\n", i, procs[i]); + printf("Processor #%d usage: %f%%\n", i, procs[i]); i += 1; } free(procs); diff --git a/examples/src/simple.rs b/examples/src/simple.rs index d16b488ae..a28ce212e 100644 --- a/examples/src/simple.rs +++ b/examples/src/simple.rs @@ -1,43 +1,126 @@ -// +// // Sysinfo -// +// // Copyright (c) 2017 Guillaume Gomez // #![crate_type = "bin"] - #![allow(unused_must_use, non_upper_case_globals)] extern crate sysinfo; -use sysinfo::{NetworkExt, Pid, ProcessExt, ProcessorExt, Signal, System, SystemExt}; -use sysinfo::Signal::*; use std::io::{self, BufRead, Write}; use std::str::FromStr; +use sysinfo::Signal::*; +use sysinfo::{NetworkExt, NetworksExt, Pid, ProcessExt, ProcessorExt, Signal, System, SystemExt}; -const signals: [Signal; 31] = [Hangup, Interrupt, Quit, Illegal, Trap, Abort, Bus, - FloatingPointException, Kill, User1, Segv, User2, Pipe, Alarm, - Term, Stklft, Child, Continue, Stop, TSTP, TTIN, TTOU, Urgent, - XCPU, XFSZ, VirtualAlarm, Profiling, Winch, IO, Power, Sys]; +const signals: [Signal; 31] = [ + Hangup, + Interrupt, + Quit, + Illegal, + Trap, + Abort, + Bus, + FloatingPointException, + Kill, + User1, + Segv, + User2, + Pipe, + Alarm, + Term, + Stklft, + Child, + Continue, + Stop, + TSTP, + TTIN, + TTOU, + Urgent, + XCPU, + XFSZ, + VirtualAlarm, + Profiling, + Winch, + IO, + Power, + Sys, +]; fn print_help() { writeln!(&mut io::stdout(), "== Help menu =="); writeln!(&mut io::stdout(), "help : show this menu"); - writeln!(&mut io::stdout(), "signals : show the available signals"); - writeln!(&mut io::stdout(), "refresh : reloads all processes' information"); - writeln!(&mut io::stdout(), "refresh [pid] : reloads corresponding process' information"); - writeln!(&mut io::stdout(), "refresh_disks : reloads only disks' information"); - writeln!(&mut io::stdout(), "show [pid | name] : show information of the given process \ - corresponding to [pid | name]"); - writeln!(&mut io::stdout(), "kill [pid] [signal]: send [signal] to the process with this \ - [pid]. 0 < [signal] < 32"); - writeln!(&mut io::stdout(), "proc : Displays proc state"); - writeln!(&mut io::stdout(), "memory : Displays memory state"); - writeln!(&mut io::stdout(), "temperature : Displays components' temperature"); - writeln!(&mut io::stdout(), "disks : Displays disks' information"); - writeln!(&mut io::stdout(), "network : Displays network' information"); - writeln!(&mut io::stdout(), "all : Displays all process name and pid"); - writeln!(&mut io::stdout(), "uptime : Displays system uptime"); + writeln!( + &mut io::stdout(), + "signals : show the available signals" + ); + writeln!( + &mut io::stdout(), + "refresh : reloads all processes' information" + ); + writeln!( + &mut io::stdout(), + "refresh [pid] : reloads corresponding process' information" + ); + writeln!( + &mut io::stdout(), + "refresh_disks : reloads only disks' information" + ); + writeln!( + &mut io::stdout(), + "show [pid | name] : show information of the given process \ + corresponding to [pid | name]" + ); + writeln!( + &mut io::stdout(), + "kill [pid] [signal]: send [signal] to the process with this \ + [pid]. 0 < [signal] < 32" + ); + writeln!( + &mut io::stdout(), + "processors : Displays processors state" + ); + writeln!( + &mut io::stdout(), + "memory : Displays memory state" + ); + writeln!( + &mut io::stdout(), + "temperature : Displays components' temperature" + ); + writeln!( + &mut io::stdout(), + "disks : Displays disks' information" + ); + writeln!( + &mut io::stdout(), + "network : Displays network' information" + ); + writeln!( + &mut io::stdout(), + "all : Displays all process name and pid" + ); + writeln!( + &mut io::stdout(), + "uptime : Displays system uptime" + ); + writeln!( + &mut io::stdout(), + "vendor_id : Displays processor vendor id" + ); + writeln!( + &mut io::stdout(), + "brand : Displays processor brand" + ); + writeln!( + &mut io::stdout(), + "load_avg : Displays system load average" + ); + writeln!( + &mut io::stdout(), + "frequency : Displays processor frequency" + ); writeln!(&mut io::stdout(), "quit : exit the program"); } @@ -46,7 +129,7 @@ fn interpret_input(input: &str, sys: &mut System) -> bool { "help" => print_help(), "refresh_disks" => { writeln!(&mut io::stdout(), "Refreshing disk list..."); - sys.refresh_disk_list(); + sys.refresh_disks_list(); writeln!(&mut io::stdout(), "Done."); } "signals" => { @@ -57,32 +140,87 @@ fn interpret_input(input: &str, sys: &mut System) -> bool { nb += 1; } } - "proc" => { - // Note: you should refresh a few times before using this, so that usage statistics can be ascertained - let procs = sys.get_processor_list(); - - writeln!(&mut io::stdout(), "total process usage: {}%", procs[0].get_cpu_usage()); - for proc_ in procs.iter().skip(1) { + "processors" => { + // Note: you should refresh a few times before using this, so that usage statistics + // can be ascertained + writeln!( + &mut io::stdout(), + "total process usage: {}%", + sys.get_global_processor_info().get_cpu_usage() + ); + for proc_ in sys.get_processors() { writeln!(&mut io::stdout(), "{:?}", proc_); } } "memory" => { - writeln!(&mut io::stdout(), "total memory: {} kB", sys.get_total_memory()); - writeln!(&mut io::stdout(), "used memory : {} kB", sys.get_used_memory()); - writeln!(&mut io::stdout(), "total swap : {} kB", sys.get_total_swap()); - writeln!(&mut io::stdout(), "used swap : {} kB", sys.get_used_swap()); + writeln!( + &mut io::stdout(), + "total memory: {} KiB", + sys.get_total_memory() + ); + writeln!( + &mut io::stdout(), + "used memory : {} KiB", + sys.get_used_memory() + ); + writeln!( + &mut io::stdout(), + "total swap : {} KiB", + sys.get_total_swap() + ); + writeln!( + &mut io::stdout(), + "used swap : {} KiB", + sys.get_used_swap() + ); } "quit" | "exit" => return true, "all" => { - for (pid, proc_) in sys.get_process_list() { - writeln!(&mut io::stdout(), "{}:{} status={:?}", pid, proc_.name(), proc_.status()); + for (pid, proc_) in sys.get_processes() { + writeln!( + &mut io::stdout(), + "{}:{} status={:?}", + pid, + proc_.name(), + proc_.status() + ); } } + "frequency" => { + writeln!( + &mut io::stdout(), + "{} MHz", + sys.get_processors()[0].get_frequency() + ); + } + "vendor_id" => { + writeln!( + &mut io::stdout(), + "vendor ID: {}", + sys.get_processors()[0].get_vendor_id() + ); + } + "brand" => { + writeln!( + &mut io::stdout(), + "brand: {}", + sys.get_processors()[0].get_brand() + ); + } + "load_avg" => { + let load_avg = sys.get_load_average(); + writeln!(&mut io::stdout(), "one minute : {}%", load_avg.one); + writeln!(&mut io::stdout(), "five minutes : {}%", load_avg.five); + writeln!(&mut io::stdout(), "fifteen minutes: {}%", load_avg.fifteen); + } e if e.starts_with("show ") => { - let tmp : Vec<&str> = e.split(' ').collect(); + let tmp: Vec<&str> = e.split(' ').collect(); if tmp.len() != 2 { - writeln!(&mut io::stdout(), "show command takes a pid or a name in parameter!"); + writeln!( + &mut io::stdout(), + "show command takes a pid or a name in parameter!" + ); writeln!(&mut io::stdout(), "example: show 1254"); } else if let Ok(pid) = Pid::from_str(tmp[1]) { match sys.get_process(pid) { @@ -98,38 +236,57 @@ fn interpret_input(input: &str, sys: &mut System) -> bool { } } "temperature" => { - for component in sys.get_components_list() { + for component in sys.get_components() { writeln!(&mut io::stdout(), "{:?}", component); } } "network" => { - writeln!(&mut io::stdout(), "input data : {} B", sys.get_network().get_income()); - writeln!(&mut io::stdout(), "output data: {} B", sys.get_network().get_outcome()); + for (interface_name, data) in sys.get_networks().iter() { + writeln!( + &mut io::stdout(), + "{}:\n input data (new / total): {} / {} B\n output data (new / total): {} / {} B", + interface_name, + data.get_income(), + data.get_total_income(), + data.get_outcome(), + data.get_total_outcome(), + ); + } } "show" => { - writeln!(&mut io::stdout(), "'show' command expects a pid number or a process name"); + writeln!( + &mut io::stdout(), + "'show' command expects a pid number or a process name" + ); } e if e.starts_with("kill ") => { - let tmp : Vec<&str> = e.split(' ').collect(); + let tmp: Vec<&str> = e.split(' ').collect(); if tmp.len() != 3 { - writeln!(&mut io::stdout(), - "kill command takes the pid and a signal number in parameter !"); + writeln!( + &mut io::stdout(), + "kill command takes the pid and a signal number in parameter !" + ); writeln!(&mut io::stdout(), "example: kill 1254 9"); } else { let pid = Pid::from_str(tmp[1]).unwrap(); let signal = i32::from_str(tmp[2]).unwrap(); if signal < 1 || signal > 31 { - writeln!(&mut io::stdout(), - "Signal must be between 0 and 32 ! See the signals list with the \ - signals command"); + writeln!( + &mut io::stdout(), + "Signal must be between 0 and 32 ! See the signals list with the \ + signals command" + ); } else { match sys.get_process(pid) { Some(p) => { - writeln!(&mut io::stdout(), "kill: {}", - p.kill(*signals.get(signal as usize - 1).unwrap())); - }, + writeln!( + &mut io::stdout(), + "kill: {}", + p.kill(*signals.get(signal as usize - 1).unwrap()) + ); + } None => { writeln!(&mut io::stdout(), "pid not found"); } @@ -149,11 +306,13 @@ fn interpret_input(input: &str, sys: &mut System) -> bool { let hours = uptime / 3600; uptime -= hours * 3600; let minutes = uptime / 60; - writeln!(&mut io::stdout(), - "{} days {} hours {} minutes", - days, - hours, - minutes); + writeln!( + &mut io::stdout(), + "{} days {} hours {} minutes", + days, + hours, + minutes + ); } x if x.starts_with("refresh") => { if x == "refresh" { @@ -162,25 +321,40 @@ fn interpret_input(input: &str, sys: &mut System) -> bool { writeln!(&mut io::stdout(), "Done."); } else if x.starts_with("refresh ") { writeln!(&mut io::stdout(), "Getting process' information..."); - if let Some(pid) = x.split(' ').filter_map(|pid| pid.parse().ok()).take(1).next() { + if let Some(pid) = x + .split(' ') + .filter_map(|pid| pid.parse().ok()) + .take(1) + .next() + { if sys.refresh_process(pid) { writeln!(&mut io::stdout(), "Process `{}` updated successfully", pid); } else { - writeln!(&mut io::stdout(), "Process `{}` couldn't be updated...", pid); + writeln!( + &mut io::stdout(), + "Process `{}` couldn't be updated...", + pid + ); } } else { writeln!(&mut io::stdout(), "Invalid [pid] received..."); } } else { - writeln!(&mut io::stdout(), - "\"{}\": Unknown command. Enter 'help' if you want to get the commands' \ - list.", x); + writeln!( + &mut io::stdout(), + "\"{}\": Unknown command. Enter 'help' if you want to get the commands' \ + list.", + x + ); } } e => { - writeln!(&mut io::stdout(), - "\"{}\": Unknown command. Enter 'help' if you want to get the commands' \ - list.", e); + writeln!( + &mut io::stdout(), + "\"{}\": Unknown command. Enter 'help' if you want to get the commands' \ + list.", + e + ); } } false @@ -188,7 +362,7 @@ fn interpret_input(input: &str, sys: &mut System) -> bool { fn main() { println!("Getting processes' information..."); - let mut t = System::new(); + let mut t = System::new_all(); println!("Done."); let t_stin = io::stdin(); let mut stin = t_stin.lock(); @@ -201,6 +375,12 @@ fn main() { io::stdout().flush(); stin.read_line(&mut input); + if input.is_empty() { + // The string is empty, meaning there is no '\n', meaning + // that the user used CTRL+D so we can just quit! + println!("\nLeaving, bye!"); + break; + } if (&input as &str).ends_with('\n') { input.pop(); } diff --git a/src/c_interface.rs b/src/c_interface.rs index 2592f3b4f..78ac3d612 100644 --- a/src/c_interface.rs +++ b/src/c_interface.rs @@ -7,18 +7,18 @@ use libc::{self, c_char, c_float, c_uint, c_void, pid_t, size_t}; use std::borrow::BorrowMut; use std::ffi::CString; -use {NetworkExt, Process, ProcessExt, ProcessorExt, System, SystemExt}; +use {NetworkExt, NetworksExt, Process, ProcessExt, ProcessorExt, System, SystemExt}; -/// Equivalent of `System` struct. +/// Equivalent of [`System`][crate::System] struct. pub type CSystem = *mut c_void; -/// Equivalent of `Process` struct. +/// Equivalent of [`Process`][crate::Process] struct. pub type CProcess = *const c_void; /// C string returned from `CString::into_raw`. pub type RString = *const c_char; -/// Callback used by `get_process_list`. +/// Callback used by [`get_processes`][crate::System#method.get_processes]. pub type ProcessLoop = extern "C" fn(pid: pid_t, process: CProcess, data: *mut c_void) -> bool; -/// Equivalent of `System::new()`. +/// Equivalent of [`System::new()`][crate::System#method.new]. #[no_mangle] pub extern "C" fn sysinfo_init() -> CSystem { let system = Box::new(System::new()); @@ -34,7 +34,7 @@ pub extern "C" fn sysinfo_destroy(system: CSystem) { } } -/// Equivalent of `System::refresh_system()`. +/// Equivalent of [`System::refresh_system()`][crate::System#method.refresh_system]. #[no_mangle] pub extern "C" fn sysinfo_refresh_system(system: CSystem) { assert!(!system.is_null()); @@ -46,7 +46,43 @@ pub extern "C" fn sysinfo_refresh_system(system: CSystem) { Box::into_raw(system); } -/// Equivalent of `System::refresh_all()`. +/// Equivalent of [`System::refresh_memory()`][crate::System#method.refresh_memory]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_memory(system: CSystem) { + assert!(!system.is_null()); + let mut system: Box = unsafe { Box::from_raw(system as *mut System) }; + { + let system: &mut System = system.borrow_mut(); + system.refresh_memory(); + } + Box::into_raw(system); +} + +/// Equivalent of [`System::refresh_cpu()`][crate::System#method.refresh_cpu]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_cpu(system: CSystem) { + assert!(!system.is_null()); + let mut system: Box = unsafe { Box::from_raw(system as *mut System) }; + { + let system: &mut System = system.borrow_mut(); + system.refresh_cpu(); + } + Box::into_raw(system); +} + +/// Equivalent of [`System::refresh_components()`][crate::System#method.refresh_temperatures]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_components(system: CSystem) { + assert!(!system.is_null()); + let mut system: Box = unsafe { Box::from_raw(system as *mut System) }; + { + let system: &mut System = system.borrow_mut(); + system.refresh_components(); + } + Box::into_raw(system); +} + +/// Equivalent of [`System::refresh_all()`][crate::System#method.refresh_all]. #[no_mangle] pub extern "C" fn sysinfo_refresh_all(system: CSystem) { assert!(!system.is_null()); @@ -58,7 +94,7 @@ pub extern "C" fn sysinfo_refresh_all(system: CSystem) { Box::into_raw(system); } -/// Equivalent of `System::refresh_processes()`. +/// Equivalent of [`System::refresh_processes()`][crate::System#method.refresh_processes]. #[no_mangle] pub extern "C" fn sysinfo_refresh_processes(system: CSystem) { assert!(!system.is_null()); @@ -70,7 +106,7 @@ pub extern "C" fn sysinfo_refresh_processes(system: CSystem) { Box::into_raw(system); } -/// Equivalent of `System::refresh_process()`. +/// Equivalent of [`System::refresh_process()`][crate::System#method.refresh_process]. #[cfg(target_os = "linux")] #[no_mangle] pub extern "C" fn sysinfo_refresh_process(system: CSystem, pid: pid_t) { @@ -83,7 +119,7 @@ pub extern "C" fn sysinfo_refresh_process(system: CSystem, pid: pid_t) { Box::into_raw(system); } -/// Equivalent of `System::refresh_disks()`. +/// Equivalent of [`System::refresh_disks()`][crate::System#method.refresh_disks]. #[no_mangle] pub extern "C" fn sysinfo_refresh_disks(system: CSystem) { assert!(!system.is_null()); @@ -95,19 +131,19 @@ pub extern "C" fn sysinfo_refresh_disks(system: CSystem) { Box::into_raw(system); } -/// Equivalent of `System::refresh_disk_list()`. +/// Equivalent of [`System::refresh_disks_list()`][crate::System#method.refresh_disks_list]. #[no_mangle] -pub extern "C" fn sysinfo_refresh_disk_list(system: CSystem) { +pub extern "C" fn sysinfo_refresh_disks_list(system: CSystem) { assert!(!system.is_null()); let mut system: Box = unsafe { Box::from_raw(system as *mut System) }; { let system: &mut System = system.borrow_mut(); - system.refresh_disk_list(); + system.refresh_disks_list(); } Box::into_raw(system); } -/// Equivalent of `System::get_total_memory()`. +/// Equivalent of [`System::get_total_memory()`][crate::System#method.get_total_memory]. #[no_mangle] pub extern "C" fn sysinfo_get_total_memory(system: CSystem) -> size_t { assert!(!system.is_null()); @@ -117,7 +153,7 @@ pub extern "C" fn sysinfo_get_total_memory(system: CSystem) -> size_t { ret } -/// Equivalent of `System::get_free_memory()`. +/// Equivalent of [`System::get_free_memory()`][crate::System#method.get_free_memory]. #[no_mangle] pub extern "C" fn sysinfo_get_free_memory(system: CSystem) -> size_t { assert!(!system.is_null()); @@ -127,7 +163,7 @@ pub extern "C" fn sysinfo_get_free_memory(system: CSystem) -> size_t { ret } -/// Equivalent of `System::get_used_memory()`. +/// Equivalent of [`System::get_used_memory()`][crate::System#method.get_used_memory]. #[no_mangle] pub extern "C" fn sysinfo_get_used_memory(system: CSystem) -> size_t { assert!(!system.is_null()); @@ -137,7 +173,7 @@ pub extern "C" fn sysinfo_get_used_memory(system: CSystem) -> size_t { ret } -/// Equivalent of `System::get_total_swap()`. +/// Equivalent of [`System::get_total_swap()`][crate::System#method.get_total_swap]. #[no_mangle] pub extern "C" fn sysinfo_get_total_swap(system: CSystem) -> size_t { assert!(!system.is_null()); @@ -147,7 +183,7 @@ pub extern "C" fn sysinfo_get_total_swap(system: CSystem) -> size_t { ret } -/// Equivalent of `System::get_free_swap()`. +/// Equivalent of [`System::get_free_swap()`][crate::System#method.get_free_swap]. #[no_mangle] pub extern "C" fn sysinfo_get_free_swap(system: CSystem) -> size_t { assert!(!system.is_null()); @@ -157,7 +193,7 @@ pub extern "C" fn sysinfo_get_free_swap(system: CSystem) -> size_t { ret } -/// Equivalent of `System::get_used_swap()`. +/// Equivalent of [`System::get_used_swap()`][crate::System#method.get_used_swap]. #[no_mangle] pub extern "C" fn sysinfo_get_used_swap(system: CSystem) -> size_t { assert!(!system.is_null()); @@ -167,27 +203,35 @@ pub extern "C" fn sysinfo_get_used_swap(system: CSystem) -> size_t { ret } -/// Equivalent of `system::get_network().get_income()`. +/// Equivalent of +/// `system::get_networks().iter().fold(0, |acc, (_, data)| acc + data.get_income() as size_t)`. #[no_mangle] -pub extern "C" fn sysinfo_get_network_income(system: CSystem) -> size_t { +pub extern "C" fn sysinfo_get_networks_income(system: CSystem) -> size_t { assert!(!system.is_null()); let system: Box = unsafe { Box::from_raw(system as *mut System) }; - let ret = system.get_network().get_income() as size_t; + let ret = system + .get_networks() + .iter() + .fold(0, |acc, (_, data)| acc + data.get_income() as size_t); Box::into_raw(system); ret } -/// Equivalent of `system::get_network().get_outcome()`. +/// Equivalent of +/// `system::get_networks().iter().fold(0, |acc, (_, data)| acc + data.get_outcome() as size_t)`. #[no_mangle] -pub extern "C" fn sysinfo_get_network_outcome(system: CSystem) -> size_t { +pub extern "C" fn sysinfo_get_networks_outcome(system: CSystem) -> size_t { assert!(!system.is_null()); let system: Box = unsafe { Box::from_raw(system as *mut System) }; - let ret = system.get_network().get_outcome() as size_t; + let ret = system + .get_networks() + .iter() + .fold(0, |acc, (_, data)| acc + data.get_outcome() as size_t); Box::into_raw(system); ret } -/// Equivalent of `System::get_processors_usage()`. +/// Equivalent of [`System::get_processors_usage()`][crate::System#method.get_processors_usage]. /// /// * `length` will contain the number of cpu usage added into `procs`. /// * `procs` will be allocated if it's null and will contain of cpu usage. @@ -203,7 +247,7 @@ pub extern "C" fn sysinfo_get_processors_usage( } let system: Box = unsafe { Box::from_raw(system as *mut System) }; { - let processors = system.get_processor_list(); + let processors = system.get_processors(); unsafe { if (*procs).is_null() { (*procs) = libc::malloc(::std::mem::size_of::() * processors.len()) @@ -218,8 +262,8 @@ pub extern "C" fn sysinfo_get_processors_usage( Box::into_raw(system); } -/// Equivalent of `System::get_process_list()`. Returns an array ended by a null pointer. Must -/// be freed. +/// Equivalent of [`System::get_processes()`][crate::System#method.get_processes]. Returns an +/// array ended by a null pointer. Must be freed. /// /// # /!\ WARNING /!\ /// @@ -234,7 +278,7 @@ pub extern "C" fn sysinfo_get_processes( if let Some(fn_pointer) = fn_pointer { let system: Box = unsafe { Box::from_raw(system as *mut System) }; let len = { - let entries = system.get_process_list(); + let entries = system.get_processes(); for (pid, process) in entries { if !fn_pointer(*pid, process as *const Process as CProcess, data) { break; @@ -249,7 +293,7 @@ pub extern "C" fn sysinfo_get_processes( } } -/// Equivalent of `System::get_process()`. +/// Equivalent of [`System::get_process()`][crate::System#method.get_process]. /// /// # /!\ WARNING /!\ /// @@ -268,7 +312,7 @@ pub extern "C" fn sysinfo_get_process_by_pid(system: CSystem, pid: pid_t) -> CPr ret } -/// Equivalent of iterating over `Process::tasks()`. +/// Equivalent of iterating over [`Process::tasks()`][crate::Process#method.tasks]. /// /// # /!\ WARNING /!\ /// @@ -294,7 +338,7 @@ pub extern "C" fn sysinfo_process_get_tasks( } } -/// Equivalent of `Process::pid()`. +/// Equivalent of [`Process::pid()`][crate::Process#method.pid]. #[no_mangle] pub extern "C" fn sysinfo_process_get_pid(process: CProcess) -> pid_t { assert!(!process.is_null()); @@ -302,7 +346,7 @@ pub extern "C" fn sysinfo_process_get_pid(process: CProcess) -> pid_t { unsafe { (*process).pid() } } -/// Equivalent of `Process::parent()`. +/// Equivalent of [`Process::parent()`][crate::Process#method.parent]. /// /// In case there is no known parent, it returns `0`. #[no_mangle] @@ -312,7 +356,7 @@ pub extern "C" fn sysinfo_process_get_parent_pid(process: CProcess) -> pid_t { unsafe { (*process).parent().unwrap_or_else(|| 0) } } -/// Equivalent of `Process::cpu_usage()`. +/// Equivalent of [`Process::cpu_usage()`][crate::Process#method.cpu_usage]. #[no_mangle] pub extern "C" fn sysinfo_process_get_cpu_usage(process: CProcess) -> c_float { assert!(!process.is_null()); @@ -320,7 +364,7 @@ pub extern "C" fn sysinfo_process_get_cpu_usage(process: CProcess) -> c_float { unsafe { (*process).cpu_usage() } } -/// Equivalent of `Process::memory()`. +/// Equivalent of [`Process::memory()`][crate::Process#method.memory]. #[no_mangle] pub extern "C" fn sysinfo_process_get_memory(process: CProcess) -> size_t { assert!(!process.is_null()); @@ -328,7 +372,7 @@ pub extern "C" fn sysinfo_process_get_memory(process: CProcess) -> size_t { unsafe { (*process).memory() as usize } } -/// Equivalent of `Process::virtual_memory()`. +/// Equivalent of [`Process::virtual_memory()`][crate::Process#method.virtual_memory]. #[no_mangle] pub extern "C" fn sysinfo_process_get_virtual_memory(process: CProcess) -> size_t { assert!(!process.is_null()); @@ -336,7 +380,7 @@ pub extern "C" fn sysinfo_process_get_virtual_memory(process: CProcess) -> size_ unsafe { (*process).virtual_memory() as usize } } -/// Equivalent of `Process::exe()`. +/// Equivalent of [`Process::exe()`][crate::Process#method.exe]. #[no_mangle] pub extern "C" fn sysinfo_process_get_executable_path(process: CProcess) -> RString { assert!(!process.is_null()); @@ -351,7 +395,7 @@ pub extern "C" fn sysinfo_process_get_executable_path(process: CProcess) -> RStr } } -/// Equivalent of `Process::root()`. +/// Equivalent of [`Process::root()`][crate::Process#method.root]. #[no_mangle] pub extern "C" fn sysinfo_process_get_root_directory(process: CProcess) -> RString { assert!(!process.is_null()); @@ -366,7 +410,7 @@ pub extern "C" fn sysinfo_process_get_root_directory(process: CProcess) -> RStri } } -/// Equivalent of `Process::cwd()`. +/// Equivalent of [`Process::cwd()`][crate::Process#method.cwd]. #[no_mangle] pub extern "C" fn sysinfo_process_get_current_directory(process: CProcess) -> RString { assert!(!process.is_null()); diff --git a/src/common.rs b/src/common.rs index 5224699f1..32479dafc 100644 --- a/src/common.rs +++ b/src/common.rs @@ -4,14 +4,18 @@ // Copyright (c) 2015 Guillaume Gomez // -/// Trait to have a common fallback for the `Pid` type. +use NetworkData; +use Networks; +use NetworksExt; + +/// Trait to have a common fallback for the [`Pid`][crate::Pid] type. pub trait AsU32 { - /// Allows to convert `Pid` into `u32`. + /// Allows to convert [`Pid`][crate::Pid] into [`u32`]. fn as_u32(&self) -> u32; } cfg_if! { - if #[cfg(any(windows, target_os = "unknown"))] { + if #[cfg(any(windows, target_os = "unknown", target_arch = "wasm32"))] { /// Process id. pub type Pid = usize; @@ -105,18 +109,24 @@ assert_eq!(r.", stringify!($name), "(), false); /// use sysinfo::{RefreshKind, System, SystemExt}; /// /// // We want everything except disks. -/// let mut system = System::new_with_specifics(RefreshKind::everything().without_disk_list()); +/// let mut system = System::new_with_specifics(RefreshKind::everything().without_disks_list()); /// /// assert_eq!(system.get_disks().len(), 0); -/// assert!(system.get_process_list().len() > 0); +/// assert!(system.get_processes().len() > 0); /// ``` +/// +/// [`System`]: crate::System #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct RefreshKind { - system: bool, - network: bool, + networks: bool, + networks_list: bool, processes: bool, - disk_list: bool, + disks_list: bool, disks: bool, + memory: bool, + cpu: bool, + components: bool, + components_list: bool, } impl RefreshKind { @@ -129,19 +139,27 @@ impl RefreshKind { /// /// let r = RefreshKind::new(); /// - /// assert_eq!(r.system(), false); - /// assert_eq!(r.network(), false); + /// assert_eq!(r.networks(), false); + /// assert_eq!(r.networks_list(), false); /// assert_eq!(r.processes(), false); - /// assert_eq!(r.disk_list(), false); + /// assert_eq!(r.disks_list(), false); /// assert_eq!(r.disks(), false); + /// assert_eq!(r.memory(), false); + /// assert_eq!(r.cpu(), false); + /// assert_eq!(r.components(), false); + /// assert_eq!(r.components_list(), false); /// ``` pub fn new() -> RefreshKind { RefreshKind { - system: false, - network: false, + networks: false, + networks_list: false, processes: false, disks: false, - disk_list: false, + disks_list: false, + memory: false, + cpu: false, + components: false, + components_list: false, } } @@ -154,25 +172,110 @@ impl RefreshKind { /// /// let r = RefreshKind::everything(); /// - /// assert_eq!(r.system(), true); - /// assert_eq!(r.network(), true); + /// assert_eq!(r.networks(), true); + /// assert_eq!(r.networks_list(), true); /// assert_eq!(r.processes(), true); - /// assert_eq!(r.disk_list(), true); + /// assert_eq!(r.disks_list(), true); /// assert_eq!(r.disks(), true); + /// assert_eq!(r.memory(), true); + /// assert_eq!(r.cpu(), true); + /// assert_eq!(r.components(), true); + /// assert_eq!(r.components_list(), true); /// ``` pub fn everything() -> RefreshKind { RefreshKind { - system: true, - network: true, + networks: true, + networks_list: true, processes: true, disks: true, - disk_list: true, + disks_list: true, + memory: true, + cpu: true, + components: true, + components_list: true, } } - impl_get_set!(system, with_system, without_system); - impl_get_set!(network, with_network, without_network); + impl_get_set!(networks, with_networks, without_networks); + impl_get_set!(networks_list, with_networks_list, without_networks_list); impl_get_set!(processes, with_processes, without_processes); impl_get_set!(disks, with_disks, without_disks); - impl_get_set!(disk_list, with_disk_list, without_disk_list); + impl_get_set!(disks_list, with_disks_list, without_disks_list); + impl_get_set!(memory, with_memory, without_memory); + impl_get_set!(cpu, with_cpu, without_cpu); + impl_get_set!(components, with_components, without_components); + impl_get_set!( + components_list, + with_components_list, + without_components_list + ); +} + +/// Iterator over network interfaces. +/// +/// It is returned by [`Networks::iter`][crate::Networks#method.iter]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt, NetworksExt}; +/// +/// let system = System::new_all(); +/// let networks_iter = system.get_networks().iter(); +/// ``` +pub struct NetworksIter<'a> { + inner: std::collections::hash_map::Iter<'a, String, NetworkData>, +} + +impl<'a> NetworksIter<'a> { + pub(crate) fn new(v: std::collections::hash_map::Iter<'a, String, NetworkData>) -> Self { + NetworksIter { inner: v } + } +} + +impl<'a> Iterator for NetworksIter<'a> { + type Item = (&'a String, &'a NetworkData); + + fn next(&mut self) -> Option { + self.inner.next() + } +} + +impl<'a> IntoIterator for &'a Networks { + type Item = (&'a String, &'a NetworkData); + type IntoIter = NetworksIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// Enum containing the different supported disks types. +/// +/// This type is returned by [`Disk::get_type`][crate::Disk#method.get_type]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt, DiskExt}; +/// +/// let system = System::new_all(); +/// for disk in system.get_disks() { +/// println!("{:?}: {:?}", disk.get_name(), disk.get_type()); +/// } +/// ``` +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum DiskType { + /// HDD type. + HDD, + /// SSD type. + SSD, + /// Unknown type. + Unknown(isize), +} + +impl From for DiskType { + fn from(t: isize) -> DiskType { + match t { + 0 => DiskType::HDD, + 1 => DiskType::SSD, + id => DiskType::Unknown(id), + } + } } diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 000000000..05bdc3356 --- /dev/null +++ b/src/io.rs @@ -0,0 +1,93 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; + +/// IOLoad represents current system block devices IO statistics +#[derive(Debug)] +pub struct IOLoad { + /// number of read I/Os processed + /// units: requests + pub read_io: f64, + /// number of read I/Os merged with in-queue I/O + /// units: requests + pub read_merges: f64, + /// number of sectors read + /// units: sectors + pub read_sectors: f64, + /// total wait time for read requests + /// units: milliseconds + pub read_ticks: f64, + /// number of write I/Os processed + /// units: requests + pub write_io: f64, + /// number of write I/Os merged with in-queue I/O + /// units: requests + pub write_merges: f64, + /// number of sectors written + /// units: sectors + pub write_sectors: f64, + /// total wait time for write requests + /// units: milliseconds + pub write_ticks: f64, + /// number of I/Os currently in flight + /// units: requests + pub in_flight: f64, + /// total time this block device has been active + /// units: milliseconds + pub io_ticks: f64, + /// total wait time for all requests + /// units: milliseconds + pub time_in_queue: f64, +} + +impl IOLoad { + /// Returns the current IO statistics + /// + /// # Notes + /// + /// Current don't support non-unix operating system + #[cfg(not(unix))] + pub fn snapshot() -> HashMap { + HashMap::new() + } + + /// Returns the current IO statistics + #[cfg(unix)] + pub fn snapshot() -> HashMap { + let mut result = HashMap::new(); + // https://www.kernel.org/doc/Documentation/block/stat.txt + if let Ok(dir) = std::fs::read_dir("/sys/block/") { + for entry in dir { + if let Ok(entry) = entry { + let stat = entry.path().join("stat"); + let mut s = String::new(); + if let Err(_) = File::open(stat).and_then(|mut f| f.read_to_string(&mut s)) { + continue; + }; + let parts = s + .split_whitespace() + .map(|w| w.parse().unwrap_or_default()) + .collect::>(); + if parts.len() != 11 { + continue; + } + let load = IOLoad { + read_io: parts[0], + read_merges: parts[1], + read_sectors: parts[2], + read_ticks: parts[3], + write_io: parts[4], + write_merges: parts[5], + write_sectors: parts[6], + write_ticks: parts[7], + in_flight: parts[8], + io_ticks: parts[9], + time_in_queue: parts[10], + }; + result.insert(format!("{:?}", entry.file_name()), load); + } + } + } + result + } +} diff --git a/src/linux/component.rs b/src/linux/component.rs index 456891062..d203d59a4 100644 --- a/src/linux/component.rs +++ b/src/linux/component.rs @@ -8,13 +8,13 @@ use ComponentExt; use std::collections::HashMap; use std::ffi::OsStr; -use std::fs::{read_dir, File}; +use std::fs::{metadata, read_dir, File}; use std::io::Read; use std::path::{Path, PathBuf}; /// More information can be found at [kernel.org][k]. /// -/// k: https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface +/// [k]: https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface pub struct Component { temperature: f32, max: f32, @@ -36,8 +36,16 @@ fn get_file_line(file: &Path, capacity: usize) -> Option { } } +fn is_file>(path: T) -> bool { + metadata(path) + .ok() + .map(|m| m.is_file()) + .unwrap_or_else(|| false) +} + fn append_files(components: &mut Vec, folder: &Path) { - let mut matchings = HashMap::new(); + let mut matchings: HashMap> = HashMap::with_capacity(10); + if let Ok(dir) = read_dir(folder) { for entry in dir { if let Ok(entry) = entry { @@ -54,11 +62,17 @@ fn append_files(components: &mut Vec, folder: &Path) { } if let Some(entry) = entry.file_name() { if let Some(entry) = entry.to_str() { - if let Ok(id) = entry[4..5].parse::() { + let mut parts = entry.split('_'); + if let Some(Some(id)) = parts.next().map(|s| s[4..].parse::().ok()) { matchings .entry(id) - .or_insert_with(|| Vec::with_capacity(1)) - .push(entry[6..].to_owned()); + .or_insert_with(|| Vec::with_capacity(5)) + .push( + parts + .next() + .map(|s| format!("_{}", s)) + .unwrap_or_else(String::new), + ); } } } @@ -69,45 +83,41 @@ fn append_files(components: &mut Vec, folder: &Path) { let mut found_label = None; for (pos, v) in val.iter().enumerate() { match v.as_str() { - "input" => { + // raspberry has empty string for temperature input + "_input" | "" => { found_input = Some(pos); } - "label" => { + "_label" => { found_label = Some(pos); } _ => {} } - if found_label.is_some() && found_input.is_some() { - let mut p_label = folder.to_path_buf(); - let mut p_input = folder.to_path_buf(); - let mut p_crit = folder.to_path_buf(); - let mut p_max = folder.to_path_buf(); - - p_label.push(&format!("temp{}_label", key)); - p_input.push(&format!("temp{}_input", key)); - p_max.push(&format!("temp{}_max", key)); - p_crit.push(&format!("temp{}_crit", key)); - if let Some(content) = get_file_line(p_label.as_path(), 10) { - let label = content.replace("\n", ""); - let max = if let Some(max) = get_file_line(p_max.as_path(), 10) { - Some( - max.replace("\n", "").parse::().unwrap_or(100_000f32) - / 1000f32, - ) - } else { - None - }; - let crit = if let Some(crit) = get_file_line(p_crit.as_path(), 10) { - Some( - crit.replace("\n", "").parse::().unwrap_or(100_000f32) - / 1000f32, - ) - } else { - None - }; - components.push(Component::new(label, p_input.as_path(), max, crit)); - break; - } + } + if let (Some(_), Some(found_input)) = (found_label, found_input) { + let mut p_label = folder.to_path_buf(); + let mut p_input = folder.to_path_buf(); + let mut p_crit = folder.to_path_buf(); + let mut p_max = folder.to_path_buf(); + + p_label.push(&format!("temp{}_label", key)); + p_input.push(&format!("temp{}{}", key, val[found_input])); + p_max.push(&format!("temp{}_max", key)); + p_crit.push(&format!("temp{}_crit", key)); + if is_file(&p_input) { + let label = get_file_line(p_label.as_path(), 10) + .unwrap_or_else(|| format!("Component {}", key)) // needed for raspberry pi + .replace("\n", ""); + let max = if let Some(max) = get_file_line(p_max.as_path(), 10) { + Some(max.replace("\n", "").parse::().unwrap_or(100_000f32) / 1000f32) + } else { + None + }; + let crit = if let Some(crit) = get_file_line(p_crit.as_path(), 10) { + Some(crit.replace("\n", "").parse::().unwrap_or(100_000f32) / 1000f32) + } else { + None + }; + components.push(Component::new(label, p_input.as_path(), max, crit)); } } } @@ -116,7 +126,7 @@ fn append_files(components: &mut Vec, folder: &Path) { impl Component { /// Creates a new component with the given information. - pub fn new( + pub(crate) fn new( label: String, input_path: &Path, max: Option, @@ -129,23 +139,9 @@ impl Component { max: max.unwrap_or(0.0), critical, }; - c.update(); + c.refresh(); c } - - /// Updates the component. - pub fn update(&mut self) { - if let Some(content) = get_file_line(self.input_file.as_path(), 10) { - self.temperature = content - .replace("\n", "") - .parse::() - .unwrap_or(100_000f32) - / 1000f32; - if self.temperature > self.max { - self.max = self.temperature; - } - } - } } impl ComponentExt for Component { @@ -164,11 +160,25 @@ impl ComponentExt for Component { fn get_label(&self) -> &str { &self.label } + + fn refresh(&mut self) { + if let Some(content) = get_file_line(self.input_file.as_path(), 10) { + self.temperature = content + .replace("\n", "") + .parse::() + .unwrap_or(100_000f32) + / 1000f32; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } } pub fn get_components() -> Vec { - let mut ret = Vec::new(); if let Ok(dir) = read_dir(&Path::new("/sys/class/hwmon/")) { + let mut components = Vec::with_capacity(10); + for entry in dir { if let Ok(entry) = entry { let entry = entry.path(); @@ -182,10 +192,20 @@ pub fn get_components() -> Vec { { continue; } - append_files(&mut ret, &entry); + append_files(&mut components, &entry); } } + components.sort_by(|c1, c2| c1.label.to_lowercase().cmp(&c2.label.to_lowercase())); + components + } else if is_file("/sys/class/thermal/thermal_zone0/temp") { + // Specfic to raspberry pi. + vec![Component::new( + "CPU".to_owned(), + Path::new("/sys/class/thermal/thermal_zone0/temp"), + None, + None, + )] + } else { + Vec::new() } - ret.sort_by(|c1, c2| c1.label.to_lowercase().cmp(&c2.label.to_lowercase())); - ret } diff --git a/src/linux/disk.rs b/src/linux/disk.rs index e3795dd67..334b88329 100644 --- a/src/linux/disk.rs +++ b/src/linux/disk.rs @@ -7,6 +7,7 @@ use super::system::get_all_data; use utils; use DiskExt; +use DiskType; use libc::statvfs; use std::ffi::{OsStr, OsString}; @@ -15,27 +16,6 @@ use std::mem; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; -/// Enum containing the different handled disks types. -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum DiskType { - /// HDD type. - HDD, - /// SSD type. - SSD, - /// Unknown type. - Unknown(isize), -} - -impl From for DiskType { - fn from(t: isize) -> DiskType { - match t { - 0 => DiskType::SSD, - 1 => DiskType::HDD, - id => DiskType::Unknown(id), - } - } -} - fn find_type_for_name(name: &OsStr) -> DiskType { /* turn "sda1" into "sda": */ let mut trimmed: &[u8] = name.as_bytes(); @@ -51,7 +31,8 @@ fn find_type_for_name(name: &OsStr) -> DiskType { .to_owned() .join(trimmed) .join("queue/rotational"); - let rotational_int = get_all_data(path).unwrap_or_default().trim().parse(); + // Normally, this file only contains '0' or '1' but just in case, we get 8 bytes... + let rotational_int = get_all_data(path, 8).unwrap_or_default().trim().parse(); DiskType::from(rotational_int.unwrap_or(-1)) } @@ -133,7 +114,7 @@ impl DiskExt for Disk { self.available_space } - fn update(&mut self) -> bool { + fn refresh(&mut self) -> bool { unsafe { let mut stat: statvfs = mem::zeroed(); let mount_point_cpath = utils::to_cpath(&self.mount_point); diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 9238248b9..9a228c465 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -12,8 +12,8 @@ pub mod processor; pub mod system; pub use self::component::Component; -pub use self::disk::{Disk, DiskType}; -pub use self::network::NetworkData; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; pub use self::process::{Process, ProcessStatus}; pub use self::processor::Processor; pub use self::system::System; diff --git a/src/linux/network.rs b/src/linux/network.rs index 12316ea53..52328a32a 100644 --- a/src/linux/network.rs +++ b/src/linux/network.rs @@ -5,79 +5,252 @@ // use std::fs::File; -use std::io::{Error, ErrorKind, Read}; +use std::io::Read; +use std::path::Path; +use std::collections::HashMap; use NetworkExt; +use NetworksExt; +use NetworksIter; + +/// Network interfaces. +/// +/// ```no_run +/// use sysinfo::{NetworksExt, System, SystemExt}; +/// +/// let s = System::new_all(); +/// let networks = s.get_networks(); +/// ``` +#[derive(Debug)] +pub struct Networks { + interfaces: HashMap, +} + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $name; + }}; + ($ty_:expr, $name:ident, $old:ident, $path:expr) => {{ + let _tmp = $path; + $ty_.$old = $ty_.$name; + $ty_.$name = _tmp; + }}; +} + +fn read>(parent: P, path: &str, data: &mut Vec) -> usize { + if let Ok(mut f) = File::open(parent.as_ref().join(path)) { + if let Ok(size) = f.read(data) { + let mut i = 0; + let mut ret = 0; + + while i < size && i < data.len() && data[i] >= b'0' && data[i] <= b'9' { + ret *= 10; + ret += (data[i] - b'0') as usize; + i += 1; + } + return ret; + } + } + 0 +} + +impl Networks { + pub(crate) fn new() -> Self { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh(&mut self) { + let mut v = vec![0; 30]; + + for (interface_name, data) in self.interfaces.iter_mut() { + data.update(interface_name, &mut v); + } + } + + fn refresh_networks_list(&mut self) { + if let Ok(dir) = std::fs::read_dir("/sys/class/net/") { + let mut data = vec![0; 30]; + for entry in dir { + if let Ok(entry) = entry { + let parent = &entry.path().join("statistics"); + let entry = match entry.file_name().into_string() { + Ok(entry) => entry, + Err(_) => continue, + }; + let rx_bytes = read(parent, "rx_bytes", &mut data); + let tx_bytes = read(parent, "tx_bytes", &mut data); + let rx_packets = read(parent, "rx_packets", &mut data); + let tx_packets = read(parent, "tx_packets", &mut data); + let rx_errors = read(parent, "rx_errors", &mut data); + let tx_errors = read(parent, "tx_errors", &mut data); + // let rx_compressed = read(parent, "rx_compressed", &mut data); + // let tx_compressed = read(parent, "tx_compressed", &mut data); + let interface = self.interfaces.entry(entry).or_insert_with(|| NetworkData { + rx_bytes, + old_rx_bytes: rx_bytes, + tx_bytes, + old_tx_bytes: tx_bytes, + rx_packets, + old_rx_packets: rx_packets, + tx_packets, + old_tx_packets: tx_packets, + rx_errors, + old_rx_errors: rx_errors, + tx_errors, + old_tx_errors: tx_errors, + // rx_compressed, + // old_rx_compressed: rx_compressed, + // tx_compressed, + // old_tx_compressed: tx_compressed, + }); + old_and_new!(interface, rx_bytes, old_rx_bytes); + old_and_new!(interface, tx_bytes, old_tx_bytes); + old_and_new!(interface, rx_packets, old_rx_packets); + old_and_new!(interface, tx_packets, old_tx_packets); + old_and_new!(interface, rx_errors, old_rx_errors); + old_and_new!(interface, tx_errors, old_tx_errors); + // old_and_new!(interface, rx_compressed, old_rx_compressed); + // old_and_new!(interface, tx_compressed, old_tx_compressed); + } + } + } + } +} /// Contains network information. #[derive(Debug)] pub struct NetworkData { - old_in: u64, - old_out: u64, - current_in: u64, - current_out: u64, + /// Total number of bytes received over interface. + rx_bytes: usize, + old_rx_bytes: usize, + /// Total number of bytes transmitted over interface. + tx_bytes: usize, + old_tx_bytes: usize, + /// Total number of packets received. + rx_packets: usize, + old_rx_packets: usize, + /// Total number of packets transmitted. + tx_packets: usize, + old_tx_packets: usize, + /// Shows the total number of packets received with error. This includes + /// too-long-frames errors, ring-buffer overflow errors, CRC errors, + /// frame alignment errors, fifo overruns, and missed packets. + rx_errors: usize, + old_rx_errors: usize, + /// similar to `rx_errors` + tx_errors: usize, + old_tx_errors: usize, + // /// Indicates the number of compressed packets received by this + // /// network device. This value might only be relevant for interfaces + // /// that support packet compression (e.g: PPP). + // rx_compressed: usize, + // old_rx_compressed: usize, + // /// Indicates the number of transmitted compressed packets. Note + // /// this might only be relevant for devices that support + // /// compression (e.g: PPP). + // tx_compressed: usize, + // old_tx_compressed: usize, +} + +impl NetworkData { + fn update(&mut self, path: &str, data: &mut Vec) { + let path = &Path::new("/sys/class/net/").join(path).join("statistics"); + old_and_new!(self, rx_bytes, old_rx_bytes, read(path, "rx_bytes", data)); + old_and_new!(self, tx_bytes, old_tx_bytes, read(path, "tx_bytes", data)); + old_and_new!( + self, + rx_packets, + old_rx_packets, + read(path, "rx_packets", data) + ); + old_and_new!( + self, + tx_packets, + old_tx_packets, + read(path, "tx_packets", data) + ); + old_and_new!( + self, + rx_errors, + old_rx_errors, + read(path, "rx_errors", data) + ); + old_and_new!( + self, + tx_errors, + old_tx_errors, + read(path, "tx_errors", data) + ); + // old_and_new!( + // self, + // rx_compressed, + // old_rx_compressed, + // read(path, "rx_compressed", data) + // ); + // old_and_new!( + // self, + // tx_compressed, + // old_tx_compressed, + // read(path, "tx_compressed", data) + // ); + } } impl NetworkExt for NetworkData { fn get_income(&self) -> u64 { - self.current_in - self.old_in + self.rx_bytes as u64 - self.old_rx_bytes as u64 + } + + fn get_total_income(&self) -> u64 { + self.rx_bytes as u64 } fn get_outcome(&self) -> u64 { - self.current_out - self.old_out + self.tx_bytes as u64 - self.old_tx_bytes as u64 } -} -pub fn new() -> NetworkData { - NetworkData { - old_in: 0, - old_out: 0, - current_in: 0, - current_out: 0, + fn get_total_outcome(&self) -> u64 { + self.tx_bytes as u64 } -} -fn read_things() -> Result<(u64, u64), Error> { - fn read_interface_stat(iface: &str, typ: &str) -> Result { - let mut file = File::open(format!("/sys/class/net/{}/statistics/{}_bytes", iface, typ))?; - let mut content = String::with_capacity(20); - file.read_to_string(&mut content)?; - content - .trim() - .parse() - .map_err(|_| Error::new(ErrorKind::Other, "Failed to parse network stat")) - } - - let default_interface = { - let mut file = File::open("/proc/net/route")?; - let mut content = String::with_capacity(800); - file.read_to_string(&mut content)?; - content - .lines() - .filter(|l| { - l.split_whitespace() - .nth(2) - .map(|l| l != "00000000") - .unwrap_or(false) - }) - .last() - .and_then(|l| l.split_whitespace().nth(0)) - .ok_or_else(|| Error::new(ErrorKind::Other, "Default device not found"))? - .to_owned() - }; - - Ok(( - read_interface_stat(&default_interface, "rx")?, - read_interface_stat(&default_interface, "tx")?, - )) -} + fn get_packets_income(&self) -> u64 { + self.rx_packets as u64 - self.old_rx_packets as u64 + } + + fn get_total_packets_income(&self) -> u64 { + self.rx_packets as u64 + } + + fn get_packets_outcome(&self) -> u64 { + self.tx_packets as u64 - self.old_tx_packets as u64 + } + + fn get_total_packets_outcome(&self) -> u64 { + self.tx_packets as u64 + } + + fn get_errors_income(&self) -> u64 { + self.rx_errors as u64 - self.old_rx_errors as u64 + } + + fn get_total_errors_income(&self) -> u64 { + self.rx_errors as u64 + } + + fn get_errors_outcome(&self) -> u64 { + self.tx_errors as u64 - self.old_tx_errors as u64 + } -pub fn update_network(n: &mut NetworkData) { - if let Ok((new_in, new_out)) = read_things() { - n.old_in = n.current_in; - n.old_out = n.current_out; - n.current_in = new_in; - n.current_out = new_out; + fn get_total_errors_outcome(&self) -> u64 { + self.tx_errors as u64 } - // TODO: maybe handle error here? } diff --git a/src/linux/process.rs b/src/linux/process.rs index d57354c0c..c15543e41 100644 --- a/src/linux/process.rs +++ b/src/linux/process.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; +use std::fs::File; use std::path::{Path, PathBuf}; use libc::{c_int, gid_t, kill, uid_t}; @@ -97,7 +98,6 @@ impl fmt::Display for ProcessStatus { } /// Struct containing a process' information. -#[derive(Clone)] pub struct Process { pub(crate) name: String, pub(crate) cmd: Vec, @@ -123,16 +123,17 @@ pub struct Process { pub(crate) status: ProcessStatus, /// Tasks run by this process. pub tasks: HashMap, + pub(crate) stat_file: Option, } impl ProcessExt for Process { fn new(pid: Pid, parent: Option, start_time: u64) -> Process { Process { - name: String::new(), + name: String::with_capacity(20), pid, parent, - cmd: Vec::new(), - environ: Vec::new(), + cmd: Vec::with_capacity(2), + environ: Vec::with_capacity(10), exe: PathBuf::new(), cwd: PathBuf::new(), root: PathBuf::new(), @@ -148,7 +149,12 @@ impl ProcessExt for Process { uid: 0, gid: 0, status: ProcessStatus::Unknown(0), - tasks: HashMap::new(), + tasks: if pid == 0 { + HashMap::with_capacity(1000) + } else { + HashMap::new() + }, + stat_file: None, } } @@ -211,6 +217,16 @@ impl ProcessExt for Process { } } +impl Drop for Process { + fn drop(&mut self) { + if self.stat_file.is_some() { + if let Ok(ref mut x) = unsafe { ::linux::system::REMAINING_FILES.lock() } { + **x += 1; + } + } + } +} + #[allow(unused_must_use)] impl Debug for Process { fn fmt(&self, f: &mut Formatter) -> fmt::Result { @@ -230,8 +246,8 @@ impl Debug for Process { writeln!(f, "executable path: {:?}", self.exe); writeln!(f, "current working directory: {:?}", self.cwd); writeln!(f, "owner/group: {}:{}", self.uid, self.gid); - writeln!(f, "memory usage: {} kB", self.memory); - writeln!(f, "virtual memory usage: {} kB", self.virtual_memory); + writeln!(f, "memory usage: {} KiB", self.memory); + writeln!(f, "virtual memory usage: {} KiB", self.virtual_memory); writeln!(f, "cpu usage: {}%", self.cpu_usage); writeln!(f, "status: {}", self.status); write!(f, "root path: {:?}", self.root) diff --git a/src/linux/processor.rs b/src/linux/processor.rs index e7d988465..df2027dc5 100644 --- a/src/linux/processor.rs +++ b/src/linux/processor.rs @@ -6,6 +6,9 @@ #![allow(clippy::too_many_arguments)] +use std::fs::File; +use std::io::Read; + use ProcessorExt; /// Struct containing values to compute a CPU usage. @@ -121,26 +124,17 @@ impl CpuValues { pub struct Processor { old_values: CpuValues, new_values: CpuValues, - name: String, + pub(crate) name: String, cpu_usage: f32, total_time: u64, old_total_time: u64, + frequency: u64, + pub(crate) vendor_id: String, + pub(crate) brand: String, } impl Processor { - #[allow(dead_code)] - fn new() -> Processor { - Processor { - name: String::new(), - old_values: CpuValues::new(), - new_values: CpuValues::new(), - cpu_usage: 0f32, - total_time: 0, - old_total_time: 0, - } - } - - fn new_with_values( + pub(crate) fn new_with_values( name: &str, user: u64, nice: u64, @@ -152,6 +146,9 @@ impl Processor { steal: u64, guest: u64, guest_nice: u64, + frequency: u64, + vendor_id: String, + brand: String, ) -> Processor { Processor { name: name.to_owned(), @@ -162,10 +159,13 @@ impl Processor { cpu_usage: 0f32, total_time: 0, old_total_time: 0, + frequency, + vendor_id, + brand, } } - fn set( + pub(crate) fn set( &mut self, user: u64, nice: u64, @@ -194,7 +194,8 @@ impl Processor { user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, ); self.cpu_usage = min(self.new_values.work_time(), self.old_values.work_time()) - / min(self.new_values.total_time(), self.old_values.total_time()); + / min(self.new_values.total_time(), self.old_values.total_time()) + * 100.; self.old_total_time = self.old_values.total_time(); self.total_time = self.new_values.total_time(); } @@ -208,44 +209,80 @@ impl ProcessorExt for Processor { fn get_name(&self) -> &str { &self.name } -} -pub fn new_processor( - name: &str, - user: u64, - nice: u64, - system: u64, - idle: u64, - iowait: u64, - irq: u64, - softirq: u64, - steal: u64, - guest: u64, - guest_nice: u64, -) -> Processor { - Processor::new_with_values( - name, user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, - ) -} + /// Returns the CPU frequency in MHz. + fn get_frequency(&self) -> u64 { + self.frequency + } -pub fn set_processor( - p: &mut Processor, - user: u64, - nice: u64, - system: u64, - idle: u64, - iowait: u64, - irq: u64, - softirq: u64, - steal: u64, - guest: u64, - guest_nice: u64, -) { - p.set( - user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, - ) + fn get_vendor_id(&self) -> &str { + &self.vendor_id + } + + fn get_brand(&self) -> &str { + &self.brand + } } pub fn get_raw_times(p: &Processor) -> (u64, u64) { (p.new_values.total_time(), p.old_values.total_time()) } + +pub fn get_cpu_frequency() -> u64 { + // /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq + let mut s = String::new(); + if File::open("/proc/cpuinfo") + .and_then(|mut f| f.read_to_string(&mut s)) + .is_err() + { + return 0; + } + + let find_cpu_mhz = s.split('\n').find(|line| { + line.starts_with("cpu MHz\t") + || line.starts_with("BogoMIPS") + || line.starts_with("clock\t") + || line.starts_with("bogomips per cpu") + }); + + find_cpu_mhz + .and_then(|line| line.split(':').last()) + .and_then(|val| val.replace("MHz", "").trim().parse::().ok()) + .map(|speed| speed as u64) + .unwrap_or_default() +} + +/// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs). +pub fn get_vendor_id_and_brand() -> (String, String) { + let mut s = String::new(); + if File::open("/proc/cpuinfo") + .and_then(|mut f| f.read_to_string(&mut s)) + .is_err() + { + return (String::new(), String::new()); + } + + fn get_value(s: &str) -> String { + s.split(':') + .last() + .map(|x| x.trim().to_owned()) + .unwrap_or_default() + } + + let mut vendor_id = None; + let mut brand = None; + + for it in s.split('\n') { + if it.starts_with("vendor_id\t") { + vendor_id = Some(get_value(it)); + } else if it.starts_with("model name\t") { + brand = Some(get_value(it)); + } else { + continue; + } + if brand.is_some() && vendor_id.is_some() { + break; + } + } + (vendor_id.unwrap_or_default(), brand.unwrap_or_default()) +} diff --git a/src/linux/system.rs b/src/linux/system.rs index b8c6934b3..4be3e7938 100644 --- a/src/linux/system.rs +++ b/src/linux/system.rs @@ -6,27 +6,88 @@ use sys::component::{self, Component}; use sys::disk; -use sys::network; use sys::process::*; use sys::processor::*; -use sys::Disk; -use sys::NetworkData; + +use Disk; +use LoadAvg; +use Networks; use Pid; -use {DiskExt, ProcessExt, RefreshKind, SystemExt}; +use {ProcessExt, RefreshKind, SystemExt}; -use libc::{sysconf, uid_t, _SC_CLK_TCK, _SC_PAGESIZE}; +use libc::{self, gid_t, sysconf, uid_t, _SC_CLK_TCK, _SC_PAGESIZE}; use std::cell::UnsafeCell; use std::collections::HashMap; use std::fs::{self, read_link, File}; use std::io::{self, BufRead, BufReader, Read}; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::sync::{Arc, Mutex}; use std::time::SystemTime; use utils::realpath; use rayon::prelude::*; +// This whole thing is to prevent having too much files open at once. It could be problematic +// for processes using a lot of files and using sysinfo at the same time. +pub(crate) static mut REMAINING_FILES: once_cell::sync::Lazy>> = + once_cell::sync::Lazy::new(|| { + #[cfg(target_os = "android")] + { + // The constant "RLIMIT_NOFILE" doesn't exist on Android so we have to return a value. + // The default value seems to be 1024 so let's return 50% of it... + Arc::new(Mutex::new(1024 / 2)) + } + #[cfg(not(target_os = "android"))] + unsafe { + let mut limits = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 { + // Most linux system now defaults to 1024. + return Arc::new(Mutex::new(1024 / 2)); + } + // We save the value in case the update fails. + let current = limits.rlim_cur; + + // The set the soft limit to the hard one. + limits.rlim_cur = limits.rlim_max; + // In this part, we leave minimum 50% of the available file descriptors to the process + // using sysinfo. + Arc::new(Mutex::new( + if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 { + limits.rlim_cur / 2 + } else { + current / 2 + } as _, + )) + } + }); + +pub(crate) fn get_max_nb_fds() -> isize { + #[cfg(target_os = "android")] + { + // The constant "RLIMIT_NOFILE" doesn't exist on Android so we have to return a value. + // The default value seems to be 1024... + 1024 / 2 + } + #[cfg(not(target_os = "android"))] + unsafe { + let mut limits = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 { + // Most linux system now defaults to 1024. + 1024 / 2 + } else { + limits.rlim_max as isize / 2 + } + } +} + macro_rules! to_str { ($e:expr) => { unsafe { ::std::str::from_utf8_unchecked($e) } @@ -41,27 +102,27 @@ pub struct System { mem_free: u64, swap_total: u64, swap_free: u64, + global_processor: Processor, processors: Vec, page_size_kb: u64, - temperatures: Vec, + components: Vec, disks: Vec, - network: NetworkData, + networks: Networks, uptime: u64, } impl System { fn clear_procs(&mut self) { if !self.processors.is_empty() { - let (new, old) = get_raw_times(&self.processors[0]); + let (new, old) = get_raw_times(&self.global_processor); let total_time = (if old > new { 1 } else { new - old }) as f32; - let mut to_delete = Vec::new(); - let nb_processors = self.processors.len() as u64 - 1; + let mut to_delete = Vec::with_capacity(20); for (pid, proc_) in &mut self.process_list.tasks { if !has_been_updated(proc_) { to_delete.push(*pid); } else { - compute_cpu_usage(proc_, nb_processors, total_time); + compute_cpu_usage(proc_, self.processors.len() as u64, total_time); } } for pid in to_delete { @@ -77,7 +138,40 @@ impl System { let first = self.processors.is_empty(); let mut it = buf.split(b'\n'); let mut count = 0; + let frequency = if first { get_cpu_frequency() } else { 0 }; + let (vendor_id, brand) = if first { + get_vendor_id_and_brand() + } else { + (String::new(), String::new()) + }; + if let Some(Ok(line)) = it.next() { + if &line[..3] != b"cpu" { + return; + } + count += 1; + let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); + if first { + self.global_processor.name = to_str!(parts.next().unwrap_or(&[])).to_owned(); + } + self.global_processor.set( + parts.next().map(|v| to_u64(v)).unwrap_or(0), + parts.next().map(|v| to_u64(v)).unwrap_or(0), + parts.next().map(|v| to_u64(v)).unwrap_or(0), + parts.next().map(|v| to_u64(v)).unwrap_or(0), + parts.next().map(|v| to_u64(v)).unwrap_or(0), + parts.next().map(|v| to_u64(v)).unwrap_or(0), + parts.next().map(|v| to_u64(v)).unwrap_or(0), + parts.next().map(|v| to_u64(v)).unwrap_or(0), + parts.next().map(|v| to_u64(v)).unwrap_or(0), + parts.next().map(|v| to_u64(v)).unwrap_or(0), + ); + if let Some(limit) = limit { + if count >= limit { + return; + } + } + } while let Some(Ok(line)) = it.next() { if &line[..3] != b"cpu" { break; @@ -86,7 +180,7 @@ impl System { count += 1; let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); if first { - self.processors.push(new_processor( + self.processors.push(Processor::new_with_values( to_str!(parts.next().unwrap_or(&[])), parts.next().map(|v| to_u64(v)).unwrap_or(0), parts.next().map(|v| to_u64(v)).unwrap_or(0), @@ -98,11 +192,13 @@ impl System { parts.next().map(|v| to_u64(v)).unwrap_or(0), parts.next().map(|v| to_u64(v)).unwrap_or(0), parts.next().map(|v| to_u64(v)).unwrap_or(0), + frequency, + vendor_id.clone(), + brand.clone(), )); } else { parts.next(); // we don't want the name again - set_processor( - &mut self.processors[i], + self.processors[i].set( parts.next().map(|v| to_u64(v)).unwrap_or(0), parts.next().map(|v| to_u64(v)).unwrap_or(0), parts.next().map(|v| to_u64(v)).unwrap_or(0), @@ -122,6 +218,10 @@ impl System { } } } + if first { + self.global_processor.vendor_id = vendor_id; + self.global_processor.brand = brand; + } } } } @@ -134,23 +234,43 @@ impl SystemExt for System { mem_free: 0, swap_total: 0, swap_free: 0, - processors: Vec::new(), + global_processor: Processor::new_with_values( + "", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + String::new(), + String::new(), + ), + processors: Vec::with_capacity(4), page_size_kb: unsafe { sysconf(_SC_PAGESIZE) as u64 / 1024 }, - temperatures: component::get_components(), - disks: Vec::new(), - network: network::new(), + components: Vec::new(), + disks: Vec::with_capacity(2), + networks: Networks::new(), uptime: get_uptime(), }; + if !refreshes.cpu() { + s.refresh_processors(None); // We need the processors to be filled. + } s.refresh_specifics(refreshes); s } - fn refresh_system(&mut self) { + fn refresh_components_list(&mut self) { + self.components = component::get_components(); + } + + fn refresh_memory(&mut self) { self.uptime = get_uptime(); - for component in &mut self.temperatures { - component.update(); - } - if let Ok(data) = get_all_data("/proc/meminfo") { + if let Ok(data) = get_all_data("/proc/meminfo", 16_385) { for line in data.split('\n') { let field = match line.split(':').next() { Some("MemTotal") => &mut self.mem_total, @@ -166,6 +286,10 @@ impl SystemExt for System { } } } + } + + fn refresh_cpu(&mut self) { + self.uptime = get_uptime(); self.refresh_processors(None); } @@ -202,36 +326,25 @@ impl SystemExt for System { }; if found && !self.processors.is_empty() { self.refresh_processors(Some(1)); - let (new, old) = get_raw_times(&self.processors[0]); + let (new, old) = get_raw_times(&self.global_processor); let total_time = (if old > new { 1 } else { new - old }) as f32; - let nb_processors = self.processors.len() as u64 - 1; if let Some(p) = self.process_list.tasks.get_mut(&pid) { - compute_cpu_usage(p, nb_processors, total_time); + compute_cpu_usage(p, self.processors.len() as u64, total_time); } } found } - fn refresh_disks(&mut self) { - for disk in &mut self.disks { - disk.update(); - } - } - - fn refresh_disk_list(&mut self) { + fn refresh_disks_list(&mut self) { self.disks = get_all_disks(); } - fn refresh_network(&mut self) { - network::update_network(&mut self.network); - } - // COMMON PART // // Need to be moved into a "common" file to avoid duplication. - fn get_process_list(&self) -> &HashMap { + fn get_processes(&self) -> &HashMap { &self.process_list.tasks } @@ -239,12 +352,20 @@ impl SystemExt for System { self.process_list.tasks.get(&pid) } - fn get_network(&self) -> &NetworkData { - &self.network + fn get_networks(&self) -> &Networks { + &self.networks + } + + fn get_networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn get_global_processor_info(&self) -> &Processor { + &self.global_processor } - fn get_processor_list(&self) -> &[Processor] { - &self.processors[..] + fn get_processors(&self) -> &[Processor] { + &self.processors } fn get_total_memory(&self) -> u64 { @@ -272,17 +393,46 @@ impl SystemExt for System { self.swap_total - self.swap_free } - fn get_components_list(&self) -> &[Component] { - &self.temperatures[..] + fn get_components(&self) -> &[Component] { + &self.components + } + + fn get_components_mut(&mut self) -> &mut [Component] { + &mut self.components } fn get_disks(&self) -> &[Disk] { - &self.disks[..] + &self.disks + } + + fn get_disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks } fn get_uptime(&self) -> u64 { self.uptime } + + fn get_load_average(&self) -> LoadAvg { + let mut s = String::new(); + if File::open("/proc/loadavg") + .and_then(|mut f| f.read_to_string(&mut s)) + .is_err() + { + return LoadAvg::default(); + } + let loads = s + .trim() + .split(' ') + .take(3) + .map(|val| val.parse::().unwrap()) + .collect::>(); + LoadAvg { + one: loads[0], + five: loads[1], + fifteen: loads[2], + } + } } impl Default for System { @@ -291,18 +441,6 @@ impl Default for System { } } -pub fn get_all_data>(file_path: P) -> io::Result { - use std::error::Error; - let mut file = File::open(file_path.as_ref())?; - let mut data = vec![0; 16_385]; - - let size = file.read(&mut data)?; - data.truncate(size); - let data = String::from_utf8(data) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.description()))?; - Ok(data) -} - fn to_u64(v: &[u8]) -> u64 { let mut x = 0; @@ -313,16 +451,16 @@ fn to_u64(v: &[u8]) -> u64 { x } -struct Wrap<'a>(UnsafeCell<&'a mut Process>); +struct Wrap<'a, T>(UnsafeCell<&'a mut T>); -impl<'a> Wrap<'a> { - fn get(&self) -> &'a mut Process { +impl<'a, T> Wrap<'a, T> { + fn get(&self) -> &'a mut T { unsafe { *(self.0.get()) } } } -unsafe impl<'a> Send for Wrap<'a> {} -unsafe impl<'a> Sync for Wrap<'a> {} +unsafe impl<'a, T> Send for Wrap<'a, T> {} +unsafe impl<'a, T> Sync for Wrap<'a, T> {} fn refresh_procs>( proc_list: &mut Process, @@ -438,6 +576,73 @@ macro_rules! unwrap_or_return { }}; } +fn _get_uid_and_gid(status_data: String) -> Option<(uid_t, gid_t)> { + // We're only interested in the lines starting with Uid: and Gid: + // here. From these lines, we're looking at the second entry to get + // the effective u/gid. + + let f = |h: &str, n: &str| -> Option { + if h.starts_with(n) { + h.split_whitespace().nth(2).unwrap_or("0").parse().ok() + } else { + None + } + }; + let mut uid = None; + let mut gid = None; + for line in status_data.lines() { + if let Some(u) = f(line, "Uid:") { + assert!(uid.is_none()); + uid = Some(u); + } else if let Some(g) = f(line, "Gid:") { + assert!(gid.is_none()); + gid = Some(g); + } else { + continue; + } + if uid.is_some() && gid.is_some() { + break; + } + } + match (uid, gid) { + (Some(u), Some(g)) => Some((u, g)), + _ => None, + } +} + +fn parse_stat_file(data: &str) -> Result, ()> { + // The stat file is "interesting" to parse, because spaces cannot + // be used as delimiters. The second field stores the command name + // surrounded by parentheses. Unfortunately, whitespace and + // parentheses are legal parts of the command, so parsing has to + // proceed like this: The first field is delimited by the first + // whitespace, the second field is everything until the last ')' + // in the entire string. All other fields are delimited by + // whitespace. + + let mut parts = Vec::with_capacity(52); + let mut data_it = data.splitn(2, ' '); + parts.push(unwrap_or_return!(data_it.next())); + // The following loses the ) from the input, but that's ok because + // we're not using it anyway. + let mut data_it = unwrap_or_return!(data_it.next()).rsplitn(2, ')'); + let data = unwrap_or_return!(data_it.next()); + parts.push(unwrap_or_return!(data_it.next())); + parts.extend(data.split_whitespace()); + Ok(parts) +} + +fn check_nb_open_files(f: File) -> Option { + if let Ok(ref mut x) = unsafe { REMAINING_FILES.lock() } { + if **x > 0 { + **x -= 1; + return Some(f); + } + } + // Something bad happened... + None +} + fn _get_process_data( path: &Path, proc_list: &mut Process, @@ -446,152 +651,128 @@ fn _get_process_data( uptime: u64, now: u64, ) -> Result, ()> { - if let Some(Ok(nb)) = path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) { - if nb == pid { - return Err(()); - } - let mut tmp = PathBuf::from(path); - - tmp.push("stat"); - if let Ok(data) = get_all_data(&tmp) { - // The stat file is "interesting" to parse, because spaces cannot - // be used as delimiters. The second field stores the command name - // sourrounded by parentheses. Unfortunately, whitespace and - // parentheses are legal parts of the command, so parsing has to - // proceed like this: The first field is delimited by the first - // whitespace, the second field is everything until the last ')' - // in the entire string. All other fields are delimited by - // whitespace. - - let mut parts = Vec::new(); - let mut data_it = data.splitn(2, ' '); - parts.push(unwrap_or_return!(data_it.next())); - // The following loses the ) from the input, but that's ok because - // we're not using it anyway. - let mut data_it = unwrap_or_return!(data_it.next()).rsplitn(2, ')'); - let data = unwrap_or_return!(data_it.next()); - parts.push(unwrap_or_return!(data_it.next())); - parts.extend(data.split_whitespace()); - let parent_memory = proc_list.memory; - let parent_virtual_memory = proc_list.virtual_memory; - if let Some(ref mut entry) = proc_list.tasks.get_mut(&nb) { - update_time_and_memory( - path, - entry, - &parts, - page_size_kb, - parent_memory, - parent_virtual_memory, - nb, - uptime, - now, - ); - return Ok(None); - } + let nb = match path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) { + Some(Ok(nb)) if nb != pid => nb, + _ => return Err(()), + }; - let parent_pid = if proc_list.pid != 0 { - Some(proc_list.pid) - } else { - match Pid::from_str(parts[3]) { - Ok(p) if p != 0 => Some(p), - _ => None, - } - }; + let get_status = |p: &mut Process, part: &str| { + p.status = part + .chars() + .next() + .and_then(|c| Some(ProcessStatus::from(c))) + .unwrap_or_else(|| ProcessStatus::Unknown(0)); + }; + let parent_memory = proc_list.memory; + let parent_virtual_memory = proc_list.virtual_memory; + if let Some(ref mut entry) = proc_list.tasks.get_mut(&nb) { + let data = if let Some(ref mut f) = entry.stat_file { + get_all_data_from_file(f, 1024).map_err(|_| ())? + } else { + let mut tmp = PathBuf::from(path); + tmp.push("stat"); + let mut file = ::std::fs::File::open(tmp).map_err(|_| ())?; + let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; + entry.stat_file = check_nb_open_files(file); + data + }; + let parts = parse_stat_file(&data)?; + get_status(entry, parts[2]); + update_time_and_memory( + path, + entry, + &parts, + page_size_kb, + parent_memory, + parent_virtual_memory, + nb, + uptime, + now, + ); + return Ok(None); + } - let clock_cycle = unsafe { sysconf(_SC_CLK_TCK) } as u64; - let since_boot = u64::from_str(parts[21]).unwrap_or(0) / clock_cycle; - let start_time = now - .checked_sub(uptime.checked_sub(since_boot).unwrap_or_else(|| 0)) - .unwrap_or_else(|| 0); - let mut p = Process::new(nb, parent_pid, start_time); - - p.status = parts[2] - .chars() - .next() - .and_then(|c| Some(ProcessStatus::from(c))) - .unwrap_or(ProcessStatus::Unknown(0)); - - tmp = PathBuf::from(path); - tmp.push("status"); - if let Ok(status_data) = get_all_data(&tmp) { - // We're only interested in the lines starting with Uid: and Gid: - // here. From these lines, we're looking at the second entry to get - // the effective u/gid. - - let f = |h: &str, n: &str| -> Option { - if h.starts_with(n) { - h.split_whitespace().nth(2).unwrap_or("0").parse().ok() - } else { - None - } - }; - let mut set_uid = false; - let mut set_gid = false; - for line in status_data.lines() { - if let Some(u) = f(line, "Uid:") { - assert!(!set_uid); - set_uid = true; - p.uid = u; - } - if let Some(g) = f(line, "Gid:") { - assert!(!set_gid); - set_gid = true; - p.gid = g; - } - } - assert!(set_uid && set_gid); - } + let mut tmp = PathBuf::from(path); - if proc_list.pid != 0 { - // If we're getting information for a child, no need to get those info since we - // already have them... - p.cmd = proc_list.cmd.clone(); - p.name = proc_list.name.clone(); - p.environ = proc_list.environ.clone(); - p.exe = proc_list.exe.clone(); - p.cwd = proc_list.cwd.clone(); - p.root = proc_list.root.clone(); - } else { - tmp = PathBuf::from(path); - tmp.push("cmdline"); - p.cmd = copy_from_file(&tmp); - p.name = p - .cmd - .get(0) - .map(|x| x.split('/').last().unwrap_or_else(|| "").to_owned()) - .unwrap_or_default(); - tmp = PathBuf::from(path); - tmp.push("environ"); - p.environ = copy_from_file(&tmp); - tmp = PathBuf::from(path); - tmp.push("exe"); - - p.exe = read_link(tmp.to_str().unwrap_or_else(|| "")) - .unwrap_or_else(|_| PathBuf::new()); - - tmp = PathBuf::from(path); - tmp.push("cwd"); - p.cwd = realpath(&tmp); - tmp = PathBuf::from(path); - tmp.push("root"); - p.root = realpath(&tmp); - } + tmp.push("stat"); + let mut file = ::std::fs::File::open(&tmp).map_err(|_| ())?; + let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; + let stat_file = check_nb_open_files(file); + let parts = parse_stat_file(&data)?; + + let parent_pid = if proc_list.pid != 0 { + Some(proc_list.pid) + } else { + match Pid::from_str(parts[3]) { + Ok(p) if p != 0 => Some(p), + _ => None, + } + }; - update_time_and_memory( - path, - &mut p, - &parts, - page_size_kb, - proc_list.memory, - proc_list.virtual_memory, - nb, - uptime, - now, - ); - return Ok(Some(p)); + let clock_cycle = unsafe { sysconf(_SC_CLK_TCK) } as u64; + let since_boot = u64::from_str(parts[21]).unwrap_or(0) / clock_cycle; + let start_time = now + .checked_sub(uptime.checked_sub(since_boot).unwrap_or_else(|| 0)) + .unwrap_or_else(|| 0); + let mut p = Process::new(nb, parent_pid, start_time); + + p.stat_file = stat_file; + get_status(&mut p, parts[2]); + + tmp.pop(); + tmp.push("status"); + if let Ok(data) = get_all_data(&tmp, 16_385) { + if let Some((uid, gid)) = _get_uid_and_gid(data) { + p.uid = uid; + p.gid = gid; } } - Err(()) + + if proc_list.pid != 0 { + // If we're getting information for a child, no need to get those info since we + // already have them... + p.cmd = proc_list.cmd.clone(); + p.name = proc_list.name.clone(); + p.environ = proc_list.environ.clone(); + p.exe = proc_list.exe.clone(); + p.cwd = proc_list.cwd.clone(); + p.root = proc_list.root.clone(); + } else { + tmp.pop(); + tmp.push("cmdline"); + p.cmd = copy_from_file(&tmp); + p.name = p + .cmd + .get(0) + .map(|x| x.split('/').last().unwrap_or_else(|| "").to_owned()) + .unwrap_or_default(); + tmp.pop(); + tmp.push("environ"); + p.environ = copy_from_file(&tmp); + tmp.pop(); + tmp.push("exe"); + p.exe = read_link(tmp.to_str().unwrap_or_else(|| "")).unwrap_or_else(|_| PathBuf::new()); + + tmp.pop(); + tmp.push("cwd"); + p.cwd = realpath(&tmp); + tmp.pop(); + tmp.push("root"); + p.root = realpath(&tmp); + } + + update_time_and_memory( + path, + &mut p, + &parts, + page_size_kb, + proc_list.memory, + proc_list.virtual_memory, + nb, + uptime, + now, + ); + Ok(Some(p)) } fn copy_from_file(entry: &Path) -> Vec { @@ -601,10 +782,21 @@ fn copy_from_file(entry: &Path) -> Vec { if let Ok(size) = f.read(&mut data) { data.truncate(size); - data.split(|x| *x == b'\0') - .filter_map(|x| ::std::str::from_utf8(x).map(|x| x.trim().to_owned()).ok()) - .filter(|x| !x.is_empty()) - .collect() + let mut out = Vec::with_capacity(20); + let mut start = 0; + for (pos, x) in data.iter().enumerate() { + if *x == 0 { + if pos - start > 1 { + if let Ok(s) = ::std::str::from_utf8(&data[start..pos]) + .map(|x| x.trim().to_owned()) + { + out.push(s); + } + } + start = pos + 1; // to keeping prevent '\0' + } + } + out } else { Vec::new() } @@ -613,8 +805,28 @@ fn copy_from_file(entry: &Path) -> Vec { } } +fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result { + use std::io::Seek; + + let mut data = Vec::with_capacity(size); + unsafe { + data.set_len(size); + } + + file.seek(::std::io::SeekFrom::Start(0))?; + let size = file.read(&mut data)?; + data.truncate(size); + Ok(String::from_utf8(data) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?) +} + +pub fn get_all_data>(file_path: P, size: usize) -> io::Result { + let mut file = File::open(file_path.as_ref())?; + get_all_data_from_file(&mut file, size) +} + fn get_all_disks() -> Vec { - let content = get_all_data("/proc/mounts").unwrap_or_default(); + let content = get_all_data("/proc/mounts", 16_385).unwrap_or_default(); let disks = content.lines().filter(|line| { let line = line.trim_start(); // While the `sd` prefix is most common, some disks instead use the `nvme` prefix. This @@ -648,13 +860,13 @@ fn get_all_disks() -> Vec { } fn get_uptime() -> u64 { - let content = get_all_data("/proc/uptime").unwrap_or_default(); + let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); u64::from_str_radix(content.split('.').next().unwrap_or_else(|| "0"), 10).unwrap_or_else(|_| 0) } fn get_secs_since_epoch() -> u64 { match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => n.as_secs(), - Err(_) => panic!("SystemTime before UNIX EPOCH!"), + _ => panic!("SystemTime before UNIX EPOCH!"), } } diff --git a/src/mac/component.rs b/src/mac/component.rs index 8694f42fa..56db664c7 100644 --- a/src/mac/component.rs +++ b/src/mac/component.rs @@ -4,25 +4,67 @@ // Copyright (c) 2018 Guillaume Gomez // +use libc::{c_char, c_int, c_void}; +use std::mem; +use sys::ffi; use ComponentExt; +pub(crate) const COMPONENTS_TEMPERATURE_IDS: &[(&str, &[i8])] = &[ + ("CPU", &['T' as i8, 'C' as i8, '0' as i8, 'P' as i8]), // CPU "TC0P" + ("GPU", &['T' as i8, 'G' as i8, '0' as i8, 'P' as i8]), // GPU "TG0P" + ("Battery", &['T' as i8, 'B' as i8, '0' as i8, 'T' as i8]), // Battery "TB0T" +]; + +pub struct ComponentFFI { + input_structure: ffi::KeyData_t, + val: ffi::Val_t, +} + +impl ComponentFFI { + fn new(key: &[i8], con: ffi::io_connect_t) -> Option { + unsafe { get_key_size(con, key) } + .ok() + .map(|(input_structure, val)| ComponentFFI { + input_structure, + val, + }) + } + + fn get_temperature(&self, con: ffi::io_connect_t) -> Option { + get_temperature_inner(con, &self.input_structure, &self.val) + } +} + /// Struct containing a component information (temperature and name for the moment). pub struct Component { temperature: f32, max: f32, critical: Option, label: String, + ffi_part: ComponentFFI, + connection: ffi::io_connect_t, } impl Component { /// Creates a new `Component` with the given information. - pub fn new(label: String, max: Option, critical: Option) -> Component { - Component { - temperature: 0f32, - label, - max: max.unwrap_or(0.0), - critical, - } + pub(crate) fn new( + label: String, + max: Option, + critical: Option, + key: &[i8], + connection: ffi::io_connect_t, + ) -> Option { + let ffi_part = ComponentFFI::new(key, connection)?; + ffi_part + .get_temperature(connection) + .map(|temperature| Component { + temperature, + label, + max: max.unwrap_or(0.0), + critical, + ffi_part, + connection, + }) } } @@ -42,11 +84,127 @@ impl ComponentExt for Component { fn get_label(&self) -> &str { &self.label } + + fn refresh(&mut self) { + if let Some(temp) = self.ffi_part.get_temperature(self.connection) { + self.temperature = temp; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } +} + +unsafe fn perform_call( + conn: ffi::io_connect_t, + index: c_int, + input_structure: *const ffi::KeyData_t, + output_structure: *mut ffi::KeyData_t, +) -> i32 { + let mut structure_output_size = mem::size_of::(); + + ffi::IOConnectCallStructMethod( + conn, + index as u32, + input_structure, + mem::size_of::(), + output_structure, + &mut structure_output_size, + ) +} + +// Adapted from https://github.com/lavoiesl/osx-cpu-temp/blob/master/smc.c#L28 +#[inline] +fn strtoul(s: &[i8]) -> u32 { + ((s[0] as u32) << (3u32 << 3)) + + ((s[1] as u32) << (2u32 << 3)) + + ((s[2] as u32) << (1u32 << 3)) + + ((s[3] as u32) << (0u32 << 3)) } -pub fn update_component(comp: &mut Component, temperature: f32) { - comp.temperature = temperature; - if comp.temperature > comp.max { - comp.max = comp.temperature; +#[inline] +unsafe fn ultostr(s: *mut c_char, val: u32) { + *s.offset(0) = ((val >> 24) % 128) as i8; + *s.offset(1) = ((val >> 16) % 128) as i8; + *s.offset(2) = ((val >> 8) % 128) as i8; + *s.offset(3) = (val % 128) as i8; + *s.offset(4) = 0; +} + +unsafe fn get_key_size( + con: ffi::io_connect_t, + key: &[i8], +) -> Result<(ffi::KeyData_t, ffi::Val_t), i32> { + let mut input_structure: ffi::KeyData_t = mem::zeroed::(); + let mut output_structure: ffi::KeyData_t = mem::zeroed::(); + let mut val: ffi::Val_t = mem::zeroed::(); + + input_structure.key = strtoul(key); + input_structure.data8 = ffi::SMC_CMD_READ_KEYINFO; + + let result = perform_call( + con, + ffi::KERNEL_INDEX_SMC, + &input_structure, + &mut output_structure, + ); + if result != ffi::KIO_RETURN_SUCCESS { + return Err(result); + } + + val.data_size = output_structure.key_info.data_size; + ultostr( + val.data_type.as_mut_ptr(), + output_structure.key_info.data_type, + ); + input_structure.key_info.data_size = val.data_size; + input_structure.data8 = ffi::SMC_CMD_READ_BYTES; + Ok((input_structure, val)) +} + +unsafe fn read_key( + con: ffi::io_connect_t, + input_structure: &ffi::KeyData_t, + mut val: ffi::Val_t, +) -> Result { + let mut output_structure: ffi::KeyData_t = mem::zeroed::(); + + match perform_call( + con, + ffi::KERNEL_INDEX_SMC, + input_structure, + &mut output_structure, + ) { + ffi::KIO_RETURN_SUCCESS => { + libc::memcpy( + val.bytes.as_mut_ptr() as *mut c_void, + output_structure.bytes.as_mut_ptr() as *mut c_void, + mem::size_of::<[u8; 32]>(), + ); + Ok(val) + } + result => Err(result), + } +} + +fn get_temperature_inner( + con: ffi::io_connect_t, + input_structure: &ffi::KeyData_t, + original_val: &ffi::Val_t, +) -> Option { + if let Ok(val) = unsafe { read_key(con, input_structure, (*original_val).clone()) } { + if val.data_size > 0 + && unsafe { libc::strcmp(val.data_type.as_ptr(), b"sp78\0".as_ptr() as *const i8) } == 0 + { + // convert sp78 value to temperature + let x = (i32::from(val.bytes[0]) << 6) + (i32::from(val.bytes[1]) >> 2); + return Some(x as f32 / 64f32); + } } + None +} + +pub(crate) fn get_temperature(con: ffi::io_connect_t, key: &[i8]) -> Option { + let (input_structure, val) = unsafe { get_key_size(con, &key) }.ok()?; + get_temperature_inner(con, &input_structure, &val) } diff --git a/src/mac/disk.rs b/src/mac/disk.rs index 5a1d6d6df..48a11be80 100644 --- a/src/mac/disk.rs +++ b/src/mac/disk.rs @@ -6,63 +6,19 @@ use utils; use DiskExt; +use DiskType; -use libc::statfs; +use libc::{c_char, c_void, statfs}; +use std::collections::HashMap; use std::ffi::{OsStr, OsString}; use std::fmt::{Debug, Error, Formatter}; +use std::fs; use std::mem; +use std::mem::MaybeUninit; +use std::os::unix::ffi::OsStringExt; use std::path::{Path, PathBuf}; - -/// Enum containing the different handled disks types. -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum DiskType { - /// HDD type. - HDD, - /// SSD type. - SSD, - /// Unknown type. - Unknown(isize), -} - -impl From for DiskType { - fn from(t: isize) -> DiskType { - match t { - 0 => DiskType::HDD, - 1 => DiskType::SSD, - id => DiskType::Unknown(id), - } - } -} - -pub fn new(name: OsString, mount_point: &Path, type_: DiskType) -> Disk { - let mount_point_cpath = utils::to_cpath(mount_point); - let mut total_space = 0; - let mut available_space = 0; - let mut file_system = None; - unsafe { - let mut stat: statfs = mem::zeroed(); - if statfs(mount_point_cpath.as_ptr() as *const i8, &mut stat) == 0 { - total_space = u64::from(stat.f_bsize) * stat.f_blocks; - available_space = stat.f_bfree * stat.f_blocks; - let mut vec = Vec::with_capacity(stat.f_fstypename.len()); - for x in &stat.f_fstypename { - if *x == 0 { - break; - } - vec.push(*x as u8); - } - file_system = Some(vec); - } - } - Disk { - type_, - name, - file_system: file_system.unwrap_or_else(|| b"".to_vec()), - mount_point: mount_point.to_owned(), - total_space, - available_space, - } -} +use std::ptr; +use sys::ffi; /// Struct containing a disk information. pub struct Disk { @@ -114,7 +70,7 @@ impl DiskExt for Disk { self.available_space } - fn update(&mut self) -> bool { + fn refresh(&mut self) -> bool { unsafe { let mut stat: statfs = mem::zeroed(); let mount_point_cpath = utils::to_cpath(&self.mount_point); @@ -127,3 +83,146 @@ impl DiskExt for Disk { } } } + +macro_rules! unwrapper { + ($b:expr, $ret:expr) => {{ + match $b { + Ok(x) => x, + _ => return $ret, + } + }}; +} + +static DISK_TYPES: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(get_disk_types); + +fn get_disk_types() -> HashMap { + let mut master_port: ffi::mach_port_t = 0; + let mut media_iterator: ffi::io_iterator_t = 0; + let mut ret = HashMap::with_capacity(1); + + unsafe { + ffi::IOMasterPort(ffi::MACH_PORT_NULL, &mut master_port); + + let matching_dictionary = ffi::IOServiceMatching(b"IOMedia\0".as_ptr() as *const i8); + let result = ffi::IOServiceGetMatchingServices( + master_port, + matching_dictionary, + &mut media_iterator, + ); + if result != ffi::KERN_SUCCESS as i32 { + //println!("Error: IOServiceGetMatchingServices() = {}", result); + return ret; + } + + loop { + let next_media = ffi::IOIteratorNext(media_iterator); + if next_media == 0 { + break; + } + let mut props = MaybeUninit::::uninit(); + let result = ffi::IORegistryEntryCreateCFProperties( + next_media, + props.as_mut_ptr(), + ffi::kCFAllocatorDefault, + 0, + ); + let props = props.assume_init(); + if result == ffi::KERN_SUCCESS as i32 && check_value(props, b"Whole\0") { + let mut name: ffi::io_name_t = mem::zeroed(); + if ffi::IORegistryEntryGetName(next_media, name.as_mut_ptr() as *mut c_char) + == ffi::KERN_SUCCESS as i32 + { + ret.insert( + make_name(&name), + if check_value(props, b"RAID\0") { + DiskType::Unknown(-1) + } else { + DiskType::SSD + }, + ); + } + ffi::CFRelease(props as *mut _); + } + ffi::IOObjectRelease(next_media); + } + ffi::IOObjectRelease(media_iterator); + } + ret +} + +fn make_name(v: &[u8]) -> OsString { + for (pos, x) in v.iter().enumerate() { + if *x == 0 { + return OsStringExt::from_vec(v[0..pos].to_vec()); + } + } + OsStringExt::from_vec(v.to_vec()) +} + +pub(crate) fn get_disks() -> Vec { + unwrapper!(fs::read_dir("/Volumes"), Vec::new()) + .flat_map(|x| { + if let Ok(ref entry) = x { + let mount_point = utils::realpath(&entry.path()); + if mount_point.as_os_str().is_empty() { + None + } else { + let name = entry.path().file_name()?.to_owned(); + let type_ = DISK_TYPES + .get(&name) + .cloned() + .unwrap_or(DiskType::Unknown(-2)); + Some(new_disk(name, &mount_point, type_)) + } + } else { + None + } + }) + .collect() +} + +unsafe fn check_value(dict: ffi::CFMutableDictionaryRef, key: &[u8]) -> bool { + let key = ffi::CFStringCreateWithCStringNoCopy( + ptr::null_mut(), + key.as_ptr() as *const c_char, + ffi::kCFStringEncodingMacRoman, + ffi::kCFAllocatorNull as *mut c_void, + ); + let ret = ffi::CFDictionaryContainsKey(dict as ffi::CFDictionaryRef, key as *const c_void) != 0 + && *(ffi::CFDictionaryGetValue(dict as ffi::CFDictionaryRef, key as *const c_void) + as *const ffi::Boolean) + != 0; + ffi::CFRelease(key as *const c_void); + ret +} + +fn new_disk(name: OsString, mount_point: &Path, type_: DiskType) -> Disk { + let mount_point_cpath = utils::to_cpath(mount_point); + let mut total_space = 0; + let mut available_space = 0; + let mut file_system = None; + unsafe { + let mut stat: statfs = mem::zeroed(); + if statfs(mount_point_cpath.as_ptr() as *const i8, &mut stat) == 0 { + total_space = u64::from(stat.f_bsize) * stat.f_blocks; + available_space = stat.f_bfree * stat.f_blocks; + let mut vec = Vec::with_capacity(stat.f_fstypename.len()); + for x in &stat.f_fstypename { + if *x == 0 { + break; + } + vec.push(*x as u8); + } + file_system = Some(vec); + } + } + Disk { + type_, + name, + file_system: file_system.unwrap_or_else(|| b"".to_vec()), + mount_point: mount_point.to_owned(), + total_space, + available_space, + } +} diff --git a/src/mac/ffi.rs b/src/mac/ffi.rs index 016ce6d5f..93868274a 100644 --- a/src/mac/ffi.rs +++ b/src/mac/ffi.rs @@ -17,10 +17,11 @@ extern "C" { buffersize: c_int, ) -> c_int; pub fn proc_listallpids(buffer: *mut c_void, buffersize: c_int) -> c_int; + //pub fn proc_listpids(kind: u32, x: u32, buffer: *mut c_void, buffersize: c_int) -> c_int; //pub fn proc_name(pid: c_int, buffer: *mut c_void, buffersize: u32) -> c_int; //pub fn proc_regionfilename(pid: c_int, address: u64, buffer: *mut c_void, // buffersize: u32) -> c_int; - //pub fn proc_pidpath(pid: c_int, buffer: *mut c_void, buffersize: u32) -> c_int; + pub fn proc_pidpath(pid: c_int, buffer: *mut c_void, buffersize: u32) -> c_int; pub fn IOMasterPort(a: i32, b: *mut mach_port_t) -> i32; pub fn IOServiceMatching(a: *const c_char) -> *mut c_void; @@ -36,7 +37,7 @@ extern "C" { pub fn IOConnectCallStructMethod( connection: mach_port_t, selector: u32, - inputStruct: *mut KeyData_t, + inputStruct: *const KeyData_t, inputStructCnt: size_t, outputStruct: *mut KeyData_t, outputStructCnt: *mut size_t, @@ -81,6 +82,14 @@ extern "C" { //pub fn host_statistics(host_priv: u32, flavor: u32, host_info: *mut c_void, // host_count: *const u32) -> u32; pub fn vm_deallocate(target_task: u32, address: *mut i32, size: u32) -> kern_return_t; + pub fn sysctlbyname( + name: *const c_char, + oldp: *mut c_void, + oldlenp: *mut usize, + newp: *mut c_void, + newlen: usize, + ) -> kern_return_t; + pub fn getloadavg(loads: *const f64, size: c_int); // pub fn proc_pidpath(pid: i32, buf: *mut i8, bufsize: u32) -> i32; // pub fn proc_name(pid: i32, buf: *mut i8, bufsize: u32) -> i32; @@ -279,7 +288,8 @@ pub struct vm_statistics64 { pub total_uncompressed_pages_in_compressor: u64, } -#[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] +#[cfg_attr(feature = "debug", derive(Eq, Hash, PartialEq))] +#[derive(Clone)] #[repr(C)] pub struct Val_t { pub key: [i8; 5], @@ -356,6 +366,9 @@ pub const CPU_STATE_NICE: u32 = 3; pub const CPU_STATE_MAX: usize = 4; pub const HW_MEMSIZE: u32 = 24; +//pub const PROC_ALL_PIDS: c_uint = 1; +pub const PROC_PIDTBSDINFO: c_int = 3; + //pub const TASK_THREAD_TIMES_INFO: u32 = 3; //pub const TASK_THREAD_TIMES_INFO_COUNT: u32 = 4; //pub const TASK_BASIC_INFO_64: u32 = 5; @@ -368,7 +381,7 @@ pub const KERNEL_INDEX_SMC: i32 = 2; pub const SMC_CMD_READ_KEYINFO: u8 = 9; pub const SMC_CMD_READ_BYTES: u8 = 5; -// pub const PROC_PIDPATHINFO_MAXSIZE: usize = 4096; +pub const PROC_PIDPATHINFO_MAXSIZE: u32 = 4096; pub const KIO_RETURN_SUCCESS: i32 = 0; #[allow(non_upper_case_globals)] diff --git a/src/mac/mod.rs b/src/mac/mod.rs index 1ae6e4f8a..e52da14f7 100644 --- a/src/mac/mod.rs +++ b/src/mac/mod.rs @@ -13,8 +13,8 @@ pub mod processor; pub mod system; pub use self::component::Component; -pub use self::disk::{Disk, DiskType}; -pub use self::network::NetworkData; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; pub use self::process::{Process, ProcessStatus}; pub use self::processor::Processor; pub use self::system::System; diff --git a/src/mac/network.rs b/src/mac/network.rs index 2bb47b5a1..b34c49d30 100644 --- a/src/mac/network.rs +++ b/src/mac/network.rs @@ -5,17 +5,169 @@ // use libc::{self, c_char, CTL_NET, NET_RT_IFLIST2, PF_ROUTE, RTM_IFINFO2}; + +use std::collections::HashMap; use std::ptr::null_mut; use sys::ffi; use NetworkExt; +use NetworksExt; +use NetworksIter; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $new_val; + }}; +} + +/// Network interfaces. +/// +/// ```no_run +/// use sysinfo::{NetworksExt, System, SystemExt}; +/// +/// let s = System::new_all(); +/// let networks = s.get_networks(); +/// ``` +pub struct Networks { + interfaces: HashMap, +} + +impl Networks { + pub(crate) fn new() -> Self { + Networks { + interfaces: HashMap::new(), + } + } + + #[allow(clippy::cast_ptr_alignment)] + fn update_networks(&mut self) { + let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0]; + let mut len = 0; + if unsafe { libc::sysctl(mib.as_mut_ptr(), 6, null_mut(), &mut len, null_mut(), 0) } < 0 { + // TODO: might be nice to put an error in here... + return; + } + let mut buf = Vec::with_capacity(len); + unsafe { + buf.set_len(len); + if libc::sysctl( + mib.as_mut_ptr(), + 6, + buf.as_mut_ptr(), + &mut len, + null_mut(), + 0, + ) < 0 + { + // TODO: might be nice to put an error in here... + return; + } + } + let buf = buf.as_ptr() as *const c_char; + let lim = unsafe { buf.add(len) }; + let mut next = buf; + while next < lim { + unsafe { + let ifm = next as *const libc::if_msghdr; + next = next.offset((*ifm).ifm_msglen as isize); + if (*ifm).ifm_type == RTM_IFINFO2 as u8 { + // The interface (line description) name stored at ifname will be returned in + // the default coded character set identifier (CCSID) currently in effect for + // the job. If this is not a single byte CCSID, then storage greater than + // IFNAMSIZ (16) bytes may be needed. 22 bytes is large enough for all CCSIDs. + let mut name = vec![0u8; libc::IFNAMSIZ + 6]; + + let if2m: *const ffi::if_msghdr2 = ifm as *const ffi::if_msghdr2; + let pname = + libc::if_indextoname((*if2m).ifm_index as _, name.as_mut_ptr() as _); + if pname.is_null() { + continue; + } + name.set_len(libc::strlen(pname)); + let name = String::from_utf8_unchecked(name); + let interface = self.interfaces.entry(name).or_insert_with(|| NetworkData { + current_in: (*if2m).ifm_data.ifi_ibytes, + old_in: 0, + current_out: (*if2m).ifm_data.ifi_obytes, + old_out: 0, + packets_in: (*if2m).ifm_data.ifi_ipackets, + old_packets_in: 0, + packets_out: (*if2m).ifm_data.ifi_opackets, + old_packets_out: 0, + errors_in: (*if2m).ifm_data.ifi_ierrors, + old_errors_in: 0, + errors_out: (*if2m).ifm_data.ifi_oerrors, + old_errors_out: 0, + updated: true, + }); + old_and_new!(interface, current_out, old_out, (*if2m).ifm_data.ifi_obytes); + old_and_new!(interface, current_in, old_in, (*if2m).ifm_data.ifi_ibytes); + old_and_new!( + interface, + packets_in, + old_packets_in, + (*if2m).ifm_data.ifi_ipackets + ); + old_and_new!( + interface, + packets_out, + old_packets_out, + (*if2m).ifm_data.ifi_opackets + ); + old_and_new!( + interface, + errors_in, + old_errors_in, + (*if2m).ifm_data.ifi_ierrors + ); + old_and_new!( + interface, + errors_out, + old_errors_out, + (*if2m).ifm_data.ifi_oerrors + ); + interface.updated = true; + } + } + } + } +} + +impl NetworksExt for Networks { + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + for (_, data) in self.interfaces.iter_mut() { + data.updated = false; + } + self.update_networks(); + self.interfaces.retain(|_, data| data.updated); + } + + fn refresh(&mut self) { + self.update_networks(); + } +} /// Contains network information. +#[derive(PartialEq, Eq)] pub struct NetworkData { - old_in: u64, - old_out: u64, current_in: u64, + old_in: u64, current_out: u64, + old_out: u64, + packets_in: u64, + old_packets_in: u64, + packets_out: u64, + old_packets_out: u64, + errors_in: u64, + old_errors_in: u64, + errors_out: u64, + old_errors_out: u64, + updated: bool, } impl NetworkExt for NetworkData { @@ -23,70 +175,47 @@ impl NetworkExt for NetworkData { self.current_in - self.old_in } + fn get_total_income(&self) -> u64 { + self.current_in + } + fn get_outcome(&self) -> u64 { self.current_out - self.old_out } -} -pub fn new() -> NetworkData { - NetworkData { - old_in: 0, - old_out: 0, - current_in: 0, - current_out: 0, + fn get_total_outcome(&self) -> u64 { + self.current_out } -} -#[allow(clippy::cast_ptr_alignment)] -pub fn update_network(n: &mut NetworkData) { - let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0]; - let mut len = 0; - if unsafe { libc::sysctl(mib.as_mut_ptr(), 6, null_mut(), &mut len, null_mut(), 0) } < 0 { - // TODO: might be nice to put an error in here... - return; - } - let mut buf = Vec::with_capacity(len); - unsafe { - buf.set_len(len); - if libc::sysctl( - mib.as_mut_ptr(), - 6, - buf.as_mut_ptr(), - &mut len, - null_mut(), - 0, - ) < 0 - { - // TODO: might be nice to put an error in here... - return; - } + fn get_packets_income(&self) -> u64 { + self.packets_in - self.old_packets_in } - let buf = buf.as_ptr() as *const c_char; - let lim = unsafe { buf.add(len) }; - let mut next = buf; - let mut totalibytes = 0u64; - let mut totalobytes = 0u64; - while next < lim { - unsafe { - let ifm = next as *const libc::if_msghdr; - next = next.offset((*ifm).ifm_msglen as isize); - if (*ifm).ifm_type == RTM_IFINFO2 as u8 { - let if2m: *const ffi::if_msghdr2 = ifm as *const ffi::if_msghdr2; - totalibytes += (*if2m).ifm_data.ifi_ibytes; - totalobytes += (*if2m).ifm_data.ifi_obytes; - } - } + + fn get_total_packets_income(&self) -> u64 { + self.packets_in + } + + fn get_packets_outcome(&self) -> u64 { + self.packets_out - self.old_packets_out + } + + fn get_total_packets_outcome(&self) -> u64 { + self.packets_out + } + + fn get_errors_income(&self) -> u64 { + self.errors_in - self.old_errors_in + } + + fn get_total_errors_income(&self) -> u64 { + self.errors_in + } + + fn get_errors_outcome(&self) -> u64 { + self.errors_out - self.old_errors_out + } + + fn get_total_errors_outcome(&self) -> u64 { + self.errors_out } - n.old_in = if n.current_in > totalibytes { - 0 - } else { - n.current_in - }; - n.current_in = totalibytes; - n.old_out = if n.current_out > totalibytes { - 0 - } else { - n.current_out - }; - n.current_out = totalobytes; } diff --git a/src/mac/process.rs b/src/mac/process.rs index ae587306f..65ffb013d 100644 --- a/src/mac/process.rs +++ b/src/mac/process.rs @@ -4,14 +4,21 @@ // Copyright (c) 2015 Guillaume Gomez // +use std::borrow::Borrow; +use std::ffi::OsStr; use std::fmt::{self, Debug, Formatter}; +use std::mem; +use std::ops::Deref; use std::path::{Path, PathBuf}; -use libc::{c_int, gid_t, kill, uid_t}; +use libc::{c_int, c_void, gid_t, kill, size_t, uid_t}; use Pid; use ProcessExt; +use sys::ffi; +use sys::system::Wrap; + /// Enum describing the different status of a process. #[derive(Clone, Copy, Debug)] pub enum ProcessStatus { @@ -144,6 +151,69 @@ pub struct Process { pub status: Option, } +impl Process { + pub(crate) fn new_empty(pid: Pid, exe: PathBuf, name: String) -> Process { + Process { + name, + pid, + parent: None, + cmd: Vec::new(), + environ: Vec::new(), + exe, + cwd: PathBuf::new(), + root: PathBuf::new(), + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + utime: 0, + stime: 0, + old_utime: 0, + old_stime: 0, + updated: true, + start_time: 0, + uid: 0, + gid: 0, + process_status: ProcessStatus::Unknown(0), + status: None, + } + } + + pub(crate) fn new_with( + pid: Pid, + parent: Option, + start_time: u64, + exe: PathBuf, + name: String, + cmd: Vec, + environ: Vec, + root: PathBuf, + ) -> Process { + Process { + name, + pid, + parent, + cmd, + environ, + exe, + cwd: PathBuf::new(), + root, + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + utime: 0, + stime: 0, + old_utime: 0, + old_stime: 0, + updated: true, + start_time, + uid: 0, + gid: 0, + process_status: ProcessStatus::Unknown(0), + status: None, + } + } +} + impl ProcessExt for Process { fn new(pid: Pid, parent: Option, start_time: u64) -> Process { Process { @@ -247,8 +317,8 @@ impl Debug for Process { writeln!(f, "executable path: {:?}", self.exe); writeln!(f, "current working directory: {:?}", self.cwd); writeln!(f, "owner/group: {}:{}", self.uid, self.gid); - writeln!(f, "memory usage: {} kB", self.memory); - writeln!(f, "virtual memory usage: {} kB", self.virtual_memory); + writeln!(f, "memory usage: {} KiB", self.memory); + writeln!(f, "virtual memory usage: {} KiB", self.virtual_memory); writeln!(f, "cpu usage: {}%", self.cpu_usage); writeln!( f, @@ -262,7 +332,7 @@ impl Debug for Process { } } -pub fn compute_cpu_usage(p: &mut Process, time: u64, task_time: u64) { +pub(crate) fn compute_cpu_usage(p: &mut Process, time: u64, task_time: u64) { let system_time_delta = task_time - p.old_utime; let time_delta = time - p.old_stime; p.old_utime = task_time; @@ -283,12 +353,310 @@ pub fn compute_cpu_usage(p: &mut Process, time: u64, task_time: u64) { p.updated = true; }*/ -pub fn has_been_updated(p: &mut Process) -> bool { +pub(crate) fn has_been_updated(p: &mut Process) -> bool { let old = p.updated; p.updated = false; old } -pub fn force_update(p: &mut Process) { +pub(crate) fn force_update(p: &mut Process) { p.updated = true; } + +unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo { + let mut task_info = mem::zeroed::(); + // If it doesn't work, we just don't have memory information for this process + // so it's "fine". + ffi::proc_pidinfo( + pid, + libc::PROC_PIDTASKINFO, + 0, + &mut task_info as *mut libc::proc_taskinfo as *mut c_void, + mem::size_of::() as _, + ); + task_info +} + +pub(crate) fn update_process( + wrap: &Wrap, + pid: Pid, + mut size: size_t, +) -> Result, ()> { + let mut mib: [c_int; 3] = [libc::CTL_KERN, libc::KERN_ARGMAX, 0]; + let mut proc_args = Vec::with_capacity(size as usize); + + unsafe { + if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) { + if p.memory == 0 { + // We don't have access to this process' information. + force_update(p); + return Ok(None); + } + let task_info = get_task_info(pid); + let mut thread_info = mem::zeroed::(); + let (user_time, system_time, thread_status) = if ffi::proc_pidinfo( + pid, + libc::PROC_PIDTHREADINFO, + 0, + &mut thread_info as *mut libc::proc_threadinfo as *mut c_void, + mem::size_of::() as _, + ) != 0 + { + ( + thread_info.pth_user_time, + thread_info.pth_system_time, + Some(ThreadStatus::from(thread_info.pth_run_state)), + ) + } else { + (0, 0, None) + }; + p.status = thread_status; + let task_time = + user_time + system_time + task_info.pti_total_user + task_info.pti_total_system; + let time = ffi::mach_absolute_time(); + compute_cpu_usage(p, time, task_time); + + p.memory = task_info.pti_resident_size >> 10; // divide by 1024 + p.virtual_memory = task_info.pti_virtual_size >> 10; // divide by 1024 + return Ok(None); + } + + let mut info = mem::zeroed::(); + if ffi::proc_pidinfo( + pid, + ffi::PROC_PIDTBSDINFO, + 0, + &mut info as *mut _ as *mut _, + mem::size_of::() as _, + ) != mem::size_of::() as _ + { + let mut buffer: Vec = Vec::with_capacity(ffi::PROC_PIDPATHINFO_MAXSIZE as _); + match ffi::proc_pidpath( + pid, + buffer.as_mut_ptr() as *mut _, + ffi::PROC_PIDPATHINFO_MAXSIZE, + ) { + x if x > 0 => { + buffer.set_len(x as _); + let tmp = String::from_utf8_unchecked(buffer); + let exe = PathBuf::from(tmp); + let name = exe + .file_name() + .unwrap_or_else(|| OsStr::new("")) + .to_str() + .unwrap_or_else(|| "") + .to_owned(); + return Ok(Some(Process::new_empty(pid, exe, name))); + } + _ => {} + } + return Err(()); + } + let parent = match info.pbi_ppid as i32 { + 0 => None, + p => Some(p), + }; + + let ptr: *mut u8 = proc_args.as_mut_slice().as_mut_ptr(); + mib[0] = libc::CTL_KERN; + mib[1] = libc::KERN_PROCARGS2; + mib[2] = pid as c_int; + /* + * /---------------\ 0x00000000 + * | ::::::::::::: | + * |---------------| <-- Beginning of data returned by sysctl() is here. + * | argc | + * |---------------| + * | exec_path | + * |---------------| + * | 0 | + * |---------------| + * | arg[0] | + * |---------------| + * | 0 | + * |---------------| + * | arg[n] | + * |---------------| + * | 0 | + * |---------------| + * | env[0] | + * |---------------| + * | 0 | + * |---------------| + * | env[n] | + * |---------------| + * | ::::::::::::: | + * |---------------| <-- Top of stack. + * : : + * : : + * \---------------/ 0xffffffff + */ + if libc::sysctl( + mib.as_mut_ptr(), + 3, + ptr as *mut c_void, + &mut size, + ::std::ptr::null_mut(), + 0, + ) == -1 + { + return Err(()); // not enough rights I assume? + } + let mut n_args: c_int = 0; + libc::memcpy( + (&mut n_args) as *mut c_int as *mut c_void, + ptr as *const c_void, + mem::size_of::(), + ); + + let mut cp = ptr.add(mem::size_of::()); + let mut start = cp; + + let mut p = if cp < ptr.add(size) { + while cp < ptr.add(size) && *cp != 0 { + cp = cp.offset(1); + } + let exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf(); + let name = exe + .file_name() + .unwrap_or_else(|| OsStr::new("")) + .to_str() + .unwrap_or_else(|| "") + .to_owned(); + while cp < ptr.add(size) && *cp == 0 { + cp = cp.offset(1); + } + start = cp; + let mut c = 0; + let mut cmd = Vec::with_capacity(n_args as usize); + while c < n_args && cp < ptr.add(size) { + if *cp == 0 { + c += 1; + cmd.push(get_unchecked_str(cp, start)); + start = cp.offset(1); + } + cp = cp.offset(1); + } + + #[inline] + fn do_nothing(_: &str, _: &mut PathBuf, _: &mut bool) {} + #[inline] + fn do_something(env: &str, root: &mut PathBuf, check: &mut bool) { + if *check && env.starts_with("PATH=") { + *check = false; + *root = Path::new(&env[6..]).to_path_buf(); + } + } + + #[inline] + unsafe fn get_environ( + ptr: *mut u8, + mut cp: *mut u8, + size: size_t, + mut root: PathBuf, + callback: F, + ) -> (Vec, PathBuf) { + let mut environ = Vec::with_capacity(10); + let mut start = cp; + let mut check = true; + while cp < ptr.add(size) { + if *cp == 0 { + if cp == start { + break; + } + let e = get_unchecked_str(cp, start); + callback(&e, &mut root, &mut check); + environ.push(e); + start = cp.offset(1); + } + cp = cp.offset(1); + } + (environ, root) + } + + let (environ, root) = if exe.is_absolute() { + if let Some(parent) = exe.parent() { + get_environ(ptr, cp, size, parent.to_path_buf(), do_nothing) + } else { + get_environ(ptr, cp, size, PathBuf::new(), do_something) + } + } else { + get_environ(ptr, cp, size, PathBuf::new(), do_something) + }; + + Process::new_with( + pid, + parent, + info.pbi_start_tvsec, + exe, + name, + parse_command_line(&cmd), + environ, + root, + ) + } else { + Process::new(pid, parent, info.pbi_start_tvsec) + }; + + let task_info = get_task_info(pid); + + p.memory = task_info.pti_resident_size >> 10; // divide by 1024 + p.virtual_memory = task_info.pti_virtual_size >> 10; // divide by 1024 + + p.uid = info.pbi_uid; + p.gid = info.pbi_gid; + p.process_status = ProcessStatus::from(info.pbi_status); + + Ok(Some(p)) + } +} + +pub(crate) fn get_proc_list() -> Option> { + let count = unsafe { ffi::proc_listallpids(::std::ptr::null_mut(), 0) }; + if count < 1 { + return None; + } + let mut pids: Vec = Vec::with_capacity(count as usize); + unsafe { + pids.set_len(count as usize); + } + let count = count * mem::size_of::() as i32; + let x = unsafe { ffi::proc_listallpids(pids.as_mut_ptr() as *mut c_void, count) }; + + if x < 1 || x as usize >= pids.len() { + None + } else { + unsafe { + pids.set_len(x as usize); + } + Some(pids) + } +} + +unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String { + let len = cp as usize - start as usize; + let part = Vec::from_raw_parts(start, len, len); + let tmp = String::from_utf8_unchecked(part.clone()); + mem::forget(part); + tmp +} + +fn parse_command_line + Borrow>(cmd: &[T]) -> Vec { + let mut x = 0; + let mut command = Vec::with_capacity(cmd.len()); + while x < cmd.len() { + let mut y = x; + if cmd[y].starts_with('\'') || cmd[y].starts_with('"') { + let c = if cmd[y].starts_with('\'') { '\'' } else { '"' }; + while y < cmd.len() && !cmd[y].ends_with(c) { + y += 1; + } + command.push(cmd[x..y].join(" ")); + x = y; + } else { + command.push(cmd[x].to_owned()); + } + x += 1; + } + command +} diff --git a/src/mac/processor.rs b/src/mac/processor.rs index 09eba16b2..4b2c26888 100644 --- a/src/mac/processor.rs +++ b/src/mac/processor.rs @@ -4,10 +4,14 @@ // Copyright (c) 2015 Guillaume Gomez // +use libc::{c_char, c_void}; +use std::mem; use std::ops::Deref; use std::sync::Arc; use sys::ffi; +use sys::system::get_sys_value; +use LoadAvg; use ProcessorExt; pub struct UnsafePtr(*mut T); @@ -54,16 +58,41 @@ pub struct Processor { name: String, cpu_usage: f32, processor_data: Arc, + frequency: u64, + vendor_id: String, + brand: String, } impl Processor { - fn new(name: String, processor_data: Arc) -> Processor { + pub(crate) fn new( + name: String, + processor_data: Arc, + frequency: u64, + vendor_id: String, + brand: String, + ) -> Processor { Processor { name, cpu_usage: 0f32, processor_data, + frequency, + vendor_id, + brand, } } + + pub(crate) fn set_cpu_usage(&mut self, cpu_usage: f32) { + self.cpu_usage = cpu_usage; + } + + pub(crate) fn update(&mut self, cpu_usage: f32, processor_data: Arc) { + self.cpu_usage = cpu_usage; + self.processor_data = processor_data; + } + + pub(crate) fn get_data(&self) -> Arc { + Arc::clone(&self.processor_data) + } } impl ProcessorExt for Processor { @@ -74,25 +103,142 @@ impl ProcessorExt for Processor { fn get_name(&self) -> &str { &self.name } -} -pub fn set_cpu_usage(p: &mut Processor, usage: f32) { - p.cpu_usage = usage; + /// Returns the processor frequency in MHz. + fn get_frequency(&self) -> u64 { + self.frequency + } + + fn get_vendor_id(&self) -> &str { + &self.vendor_id + } + + fn get_brand(&self) -> &str { + &self.brand + } } -pub fn create_proc(name: String, processor_data: Arc) -> Processor { - Processor::new(name, processor_data) +fn get_cpu_frequency() -> u64 { + let mut speed: u64 = 0; + let mut len = std::mem::size_of::(); + unsafe { + ffi::sysctlbyname( + b"hw.cpufrequency\0".as_ptr() as *const c_char, + &mut speed as *mut _ as _, + &mut len, + std::ptr::null_mut(), + 0, + ); + } + speed / 1_000_000 } -pub fn update_proc(p: &mut Processor, cpu_usage: f32, processor_data: Arc) { - p.cpu_usage = cpu_usage; - p.processor_data = processor_data; +pub fn init_processors(port: ffi::mach_port_t) -> (Processor, Vec) { + let mut num_cpu = 0; + let mut processors = Vec::new(); + let mut pourcent = 0f32; + let mut mib = [0, 0]; + + let (vendor_id, brand) = get_vendor_id_and_brand(); + let frequency = get_cpu_frequency(); + + unsafe { + if !get_sys_value( + ffi::CTL_HW, + ffi::HW_NCPU, + mem::size_of::(), + &mut num_cpu as *mut usize as *mut c_void, + &mut mib, + ) { + num_cpu = 1; + } + + let mut num_cpu_u = 0u32; + let mut cpu_info: *mut i32 = ::std::ptr::null_mut(); + let mut num_cpu_info = 0u32; + + if ffi::host_processor_info( + port, + ffi::PROCESSOR_CPU_LOAD_INFO, + &mut num_cpu_u as *mut u32, + &mut cpu_info as *mut *mut i32, + &mut num_cpu_info as *mut u32, + ) == ffi::KERN_SUCCESS + { + let proc_data = Arc::new(ProcessorData::new(cpu_info, num_cpu_info)); + for i in 0..num_cpu { + let mut p = Processor::new( + format!("{}", i + 1), + Arc::clone(&proc_data), + frequency, + vendor_id.clone(), + brand.clone(), + ); + let in_use = *cpu_info + .offset((ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_USER as isize) + + *cpu_info + .offset((ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_SYSTEM as isize) + + *cpu_info + .offset((ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_NICE as isize); + let total = in_use + + *cpu_info + .offset((ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_IDLE as isize); + p.set_cpu_usage(in_use as f32 / total as f32 * 100.); + pourcent += p.get_cpu_usage(); + processors.push(p); + } + } + } + let mut global_processor = Processor::new( + "0".to_owned(), + Arc::new(ProcessorData::new(::std::ptr::null_mut(), 0)), + frequency, + vendor_id, + brand, + ); + global_processor.set_cpu_usage(pourcent / processors.len() as f32); + + (global_processor, processors) } -pub fn set_cpu_proc(p: &mut Processor, cpu_usage: f32) { - p.cpu_usage = cpu_usage; +fn get_sysctl_str(s: &[u8]) -> String { + let mut len = 0; + + unsafe { + ffi::sysctlbyname( + s.as_ptr() as *const c_char, + std::ptr::null_mut(), + &mut len, + std::ptr::null_mut(), + 0, + ); + } + if len < 1 { + return String::new(); + } + let mut buf = Vec::with_capacity(len); + unsafe { + ffi::sysctlbyname( + s.as_ptr() as *const c_char, + buf.as_mut_ptr() as _, + &mut len, + std::ptr::null_mut(), + 0, + ); + } + if len > 0 { + unsafe { + buf.set_len(len); + } + String::from_utf8(buf).unwrap_or_else(|_| String::new()) + } else { + String::new() + } } -pub fn get_processor_data(p: &Processor) -> Arc { - Arc::clone(&p.processor_data) +pub fn get_vendor_id_and_brand() -> (String, String) { + ( + get_sysctl_str(b"machdep.cpu.brand_string\0"), + get_sysctl_str(b"machdep.cpu.vendor\0"), + ) } diff --git a/src/mac/system.rs b/src/mac/system.rs index f697e989b..a5b855978 100644 --- a/src/mac/system.rs +++ b/src/mac/system.rs @@ -5,32 +5,20 @@ // use sys::component::Component; -use sys::disk::{self, Disk, DiskType}; +use sys::disk::Disk; use sys::ffi; -use sys::network::{self, NetworkData}; +use sys::network::Networks; use sys::process::*; use sys::processor::*; -use {ComponentExt, DiskExt, ProcessExt, ProcessorExt, RefreshKind, SystemExt}; +use {LoadAvg, Pid, ProcessExt, ProcessorExt, RefreshKind, SystemExt}; -use std::borrow::Borrow; use std::cell::UnsafeCell; use std::collections::HashMap; -use std::ffi::{OsStr, OsString}; -use std::mem::MaybeUninit; -use std::ops::Deref; -use std::os::unix::ffi::OsStringExt; -use std::path::{Path, PathBuf}; +use std::mem; use std::sync::Arc; -use std::{fs, mem, ptr}; -use sys::processor; -use libc::{self, c_char, c_int, c_void, size_t, sysconf, _SC_PAGESIZE}; - -use utils; -use Pid; - -use std::process::Command; +use libc::{self, c_int, c_void, size_t, sysconf, _SC_PAGESIZE}; use rayon::prelude::*; @@ -41,12 +29,13 @@ pub struct System { mem_free: u64, swap_total: u64, swap_free: u64, + global_processor: Processor, processors: Vec, page_size_kb: u64, - temperatures: Vec, + components: Vec, connection: Option, disks: Vec, - network: NetworkData, + networks: Networks, uptime: u64, port: ffi::mach_port_t, } @@ -61,582 +50,11 @@ impl Drop for System { } } -// code from https://github.com/Chris911/iStats -fn get_io_service_connection() -> Option { - let mut master_port: ffi::mach_port_t = 0; - let mut iterator: ffi::io_iterator_t = 0; - - unsafe { - ffi::IOMasterPort(ffi::MACH_PORT_NULL, &mut master_port); - - let matching_dictionary = ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8); - let result = - ffi::IOServiceGetMatchingServices(master_port, matching_dictionary, &mut iterator); - if result != ffi::KIO_RETURN_SUCCESS { - //println!("Error: IOServiceGetMatchingServices() = {}", result); - return None; - } - - let device = ffi::IOIteratorNext(iterator); - ffi::IOObjectRelease(iterator); - if device == 0 { - //println!("Error: no SMC found"); - return None; - } - - let mut conn = 0; - let result = ffi::IOServiceOpen(device, ffi::mach_task_self(), 0, &mut conn); - ffi::IOObjectRelease(device); - if result != ffi::KIO_RETURN_SUCCESS { - //println!("Error: IOServiceOpen() = {}", result); - return None; - } - - Some(conn) - } -} - -unsafe fn strtoul(s: *mut c_char, size: c_int, base: c_int) -> u32 { - let mut total = 0u32; - - for i in 0..size { - total += if base == 16 { - (*s.offset(i as isize) as u32) << (((size - 1 - i) as u32) << 3) - } else { - (*s.offset(i as isize) as u32) << ((size - 1 - i) << 3) as u32 - }; - } - total -} - -unsafe fn ultostr(s: *mut c_char, val: u32) { - *s = 0; - libc::sprintf( - s, - b"%c%c%c%c\0".as_ptr() as *const i8, - val >> 24, - val >> 16, - val >> 8, - val, - ); -} - -unsafe fn perform_call( - conn: ffi::io_connect_t, - index: c_int, - input_structure: *mut ffi::KeyData_t, - output_structure: *mut ffi::KeyData_t, -) -> i32 { - let mut structure_output_size = mem::size_of::(); - - ffi::IOConnectCallStructMethod( - conn, - index as u32, - input_structure, - mem::size_of::(), - output_structure, - &mut structure_output_size, - ) -} - -unsafe fn read_key(con: ffi::io_connect_t, key: *mut c_char) -> Result { - let mut input_structure: ffi::KeyData_t = mem::zeroed::(); - let mut output_structure: ffi::KeyData_t = mem::zeroed::(); - let mut val: ffi::Val_t = mem::zeroed::(); - - input_structure.key = strtoul(key, 4, 16); - input_structure.data8 = ffi::SMC_CMD_READ_KEYINFO; - - let result = perform_call( - con, - ffi::KERNEL_INDEX_SMC, - &mut input_structure, - &mut output_structure, - ); - if result != ffi::KIO_RETURN_SUCCESS { - return Err(result); - } - - val.data_size = output_structure.key_info.data_size; - ultostr( - val.data_type.as_mut_ptr(), - output_structure.key_info.data_type, - ); - input_structure.key_info.data_size = val.data_size; - input_structure.data8 = ffi::SMC_CMD_READ_BYTES; - - let result = perform_call( - con, - ffi::KERNEL_INDEX_SMC, - &mut input_structure, - &mut output_structure, - ); - if result != ffi::KIO_RETURN_SUCCESS { - Err(result) - } else { - libc::memcpy( - val.bytes.as_mut_ptr() as *mut c_void, - output_structure.bytes.as_mut_ptr() as *mut c_void, - mem::size_of::<[u8; 32]>(), - ); - Ok(val) - } -} - -unsafe fn get_temperature(con: ffi::io_connect_t, key: *mut c_char) -> f32 { - if let Ok(val) = read_key(con, key) { - if val.data_size > 0 - && libc::strcmp(val.data_type.as_ptr(), b"sp78\0".as_ptr() as *const i8) == 0 - { - // convert fp78 value to temperature - let x = (i32::from(val.bytes[0]) << 6) + (i32::from(val.bytes[1]) >> 2); - return x as f32 / 64f32; - } - } - 0f32 -} - -unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String { - let len = cp as usize - start as usize; - let part = Vec::from_raw_parts(start, len, len); - let tmp = String::from_utf8_unchecked(part.clone()); - mem::forget(part); - tmp -} - -macro_rules! unwrapper { - ($b:expr, $ret:expr) => {{ - match $b { - Ok(x) => x, - _ => return $ret, - } - }}; -} - -unsafe fn check_value(dict: ffi::CFMutableDictionaryRef, key: &[u8]) -> bool { - let key = ffi::CFStringCreateWithCStringNoCopy( - ptr::null_mut(), - key.as_ptr() as *const c_char, - ffi::kCFStringEncodingMacRoman, - ffi::kCFAllocatorNull as *mut c_void, - ); - let ret = ffi::CFDictionaryContainsKey(dict as ffi::CFDictionaryRef, key as *const c_void) != 0 - && *(ffi::CFDictionaryGetValue(dict as ffi::CFDictionaryRef, key as *const c_void) - as *const ffi::Boolean) - != 0; - ffi::CFRelease(key as *const c_void); - ret -} - -fn make_name(v: &[u8]) -> OsString { - for (pos, x) in v.iter().enumerate() { - if *x == 0 { - return OsStringExt::from_vec(v[0..pos].to_vec()); - } - } - OsStringExt::from_vec(v.to_vec()) -} - -fn get_disk_types() -> HashMap { - let mut master_port: ffi::mach_port_t = 0; - let mut media_iterator: ffi::io_iterator_t = 0; - let mut ret = HashMap::new(); - - unsafe { - ffi::IOMasterPort(ffi::MACH_PORT_NULL, &mut master_port); - - let matching_dictionary = ffi::IOServiceMatching(b"IOMedia\0".as_ptr() as *const i8); - let result = ffi::IOServiceGetMatchingServices( - master_port, - matching_dictionary, - &mut media_iterator, - ); - if result != ffi::KERN_SUCCESS as i32 { - //println!("Error: IOServiceGetMatchingServices() = {}", result); - return ret; - } - - loop { - let next_media = ffi::IOIteratorNext(media_iterator); - if next_media == 0 { - break; - } - let mut props = MaybeUninit::::uninit(); - let result = ffi::IORegistryEntryCreateCFProperties( - next_media, - props.as_mut_ptr(), - ffi::kCFAllocatorDefault, - 0, - ); - let props = props.assume_init(); - if result == ffi::KERN_SUCCESS as i32 && check_value(props, b"Whole\0") { - let mut name: ffi::io_name_t = mem::zeroed(); - if ffi::IORegistryEntryGetName(next_media, name.as_mut_ptr() as *mut c_char) - == ffi::KERN_SUCCESS as i32 - { - ret.insert( - make_name(&name), - if check_value(props, b"RAID\0") { - DiskType::Unknown(-1) - } else { - DiskType::SSD - }, - ); - } - ffi::CFRelease(props as *mut _); - } - ffi::IOObjectRelease(next_media); - } - ffi::IOObjectRelease(media_iterator); - } - ret -} - -fn get_disks() -> Vec { - let disk_types = get_disk_types(); - - unwrapper!(fs::read_dir("/Volumes"), Vec::new()) - .flat_map(|x| { - if let Ok(ref entry) = x { - let mount_point = utils::realpath(&entry.path()); - if mount_point.as_os_str().is_empty() { - None - } else { - let name = entry - .path() - .file_name() - .unwrap_or_else(|| OsStr::new("")) - .to_owned(); - let type_ = disk_types - .get(&name) - .cloned() - .unwrap_or(DiskType::Unknown(-2)); - Some(disk::new(name, &mount_point, type_)) - } - } else { - None - } - }) - .collect() -} - -fn get_uptime() -> u64 { - let mut boottime: libc::timeval = unsafe { mem::zeroed() }; - let mut len = mem::size_of::(); - let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; - unsafe { - if libc::sysctl( - mib.as_mut_ptr(), - 2, - &mut boottime as *mut libc::timeval as *mut _, - &mut len, - ::std::ptr::null_mut(), - 0, - ) < 0 - { - return 0; - } - } - let bsec = boottime.tv_sec; - let csec = unsafe { libc::time(::std::ptr::null_mut()) }; - - unsafe { libc::difftime(csec, bsec) as u64 } -} - -fn parse_command_line + Borrow>(cmd: &[T]) -> Vec { - let mut x = 0; - let mut command = Vec::with_capacity(cmd.len()); - while x < cmd.len() { - let mut y = x; - if cmd[y].starts_with('\'') || cmd[y].starts_with('"') { - let c = if cmd[y].starts_with('\'') { '\'' } else { '"' }; - while y < cmd.len() && !cmd[y].ends_with(c) { - y += 1; - } - command.push(cmd[x..y].join(" ")); - x = y; - } else { - command.push(cmd[x].to_owned()); - } - x += 1; - } - command -} - -struct Wrap<'a>(UnsafeCell<&'a mut HashMap>); +pub(crate) struct Wrap<'a>(pub UnsafeCell<&'a mut HashMap>); unsafe impl<'a> Send for Wrap<'a> {} unsafe impl<'a> Sync for Wrap<'a> {} -fn update_process( - wrap: &Wrap, - pid: Pid, - taskallinfo_size: i32, - taskinfo_size: i32, - threadinfo_size: i32, - mib: &mut [c_int], - mut size: size_t, -) -> Result, ()> { - let mut proc_args = Vec::with_capacity(size as usize); - unsafe { - let mut thread_info = mem::zeroed::(); - let (user_time, system_time, thread_status) = if ffi::proc_pidinfo( - pid, - libc::PROC_PIDTHREADINFO, - 0, - &mut thread_info as *mut libc::proc_threadinfo as *mut c_void, - threadinfo_size, - ) != 0 - { - ( - thread_info.pth_user_time, - thread_info.pth_system_time, - Some(ThreadStatus::from(thread_info.pth_run_state)), - ) - } else { - (0, 0, None) - }; - if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) { - if p.memory == 0 { - // We don't have access to this process' information. - force_update(p); - return Ok(None); - } - p.status = thread_status; - let mut task_info = mem::zeroed::(); - if ffi::proc_pidinfo( - pid, - libc::PROC_PIDTASKINFO, - 0, - &mut task_info as *mut libc::proc_taskinfo as *mut c_void, - taskinfo_size, - ) != taskinfo_size - { - return Err(()); - } - let task_time = - user_time + system_time + task_info.pti_total_user + task_info.pti_total_system; - let time = ffi::mach_absolute_time(); - compute_cpu_usage(p, time, task_time); - - p.memory = task_info.pti_resident_size >> 10; // divide by 1024 - p.virtual_memory = task_info.pti_virtual_size >> 10; // divide by 1024 - return Ok(None); - } - - let mut task_info = mem::zeroed::(); - if ffi::proc_pidinfo( - pid, - libc::PROC_PIDTASKALLINFO, - 0, - &mut task_info as *mut libc::proc_taskallinfo as *mut c_void, - taskallinfo_size as i32, - ) != taskallinfo_size as i32 - { - match Command::new("/bin/ps") // not very nice, might be worth running a which first. - .arg("wwwe") - .arg("-o") - .arg("ppid=,command=") - .arg(pid.to_string().as_str()) - .output() - { - Ok(o) => { - let o = String::from_utf8(o.stdout).unwrap_or_else(|_| String::new()); - let o = o.split(' ').filter(|c| !c.is_empty()).collect::>(); - if o.len() < 2 { - return Err(()); - } - let mut command = parse_command_line(&o[1..]); - if let Some(ref mut x) = command.last_mut() { - **x = x.replace("\n", ""); - } - let p = match i32::from_str_radix(&o[0].replace("\n", ""), 10) { - Ok(x) => x, - _ => return Err(()), - }; - let mut p = Process::new(pid, if p == 0 { None } else { Some(p) }, 0); - p.exe = PathBuf::from(&command[0]); - p.name = match p.exe.file_name() { - Some(x) => x.to_str().unwrap_or_else(|| "").to_owned(), - None => String::new(), - }; - p.cmd = command; - return Ok(Some(p)); - } - _ => { - return Err(()); - } - } - } - - let parent = match task_info.pbsd.pbi_ppid as Pid { - 0 => None, - p => Some(p), - }; - - let mut p = Process::new(pid, parent, task_info.pbsd.pbi_start_tvsec); - p.memory = task_info.ptinfo.pti_resident_size >> 10; // divide by 1024 - p.virtual_memory = task_info.ptinfo.pti_virtual_size >> 10; // divide by 1024 - - p.uid = task_info.pbsd.pbi_uid; - p.gid = task_info.pbsd.pbi_gid; - p.process_status = ProcessStatus::from(task_info.pbsd.pbi_status); - - let ptr: *mut u8 = proc_args.as_mut_slice().as_mut_ptr(); - mib[0] = libc::CTL_KERN; - mib[1] = libc::KERN_PROCARGS2; - mib[2] = pid as c_int; - /* - * /---------------\ 0x00000000 - * | ::::::::::::: | - * |---------------| <-- Beginning of data returned by sysctl() is here. - * | argc | - * |---------------| - * | exec_path | - * |---------------| - * | 0 | - * |---------------| - * | arg[0] | - * |---------------| - * | 0 | - * |---------------| - * | arg[n] | - * |---------------| - * | 0 | - * |---------------| - * | env[0] | - * |---------------| - * | 0 | - * |---------------| - * | env[n] | - * |---------------| - * | ::::::::::::: | - * |---------------| <-- Top of stack. - * : : - * : : - * \---------------/ 0xffffffff - */ - if libc::sysctl( - mib.as_mut_ptr(), - 3, - ptr as *mut c_void, - &mut size, - ::std::ptr::null_mut(), - 0, - ) != -1 - { - let mut n_args: c_int = 0; - libc::memcpy( - (&mut n_args) as *mut c_int as *mut c_void, - ptr as *const c_void, - mem::size_of::(), - ); - let mut cp = ptr.add(mem::size_of::()); - let mut start = cp; - if cp < ptr.add(size) { - while cp < ptr.add(size) && *cp != 0 { - cp = cp.offset(1); - } - p.exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf(); - p.name = p - .exe - .file_name() - .unwrap_or_else(|| OsStr::new("")) - .to_str() - .unwrap_or_else(|| "") - .to_owned(); - let mut need_root = true; - if p.exe.is_absolute() { - if let Some(parent) = p.exe.parent() { - p.root = parent.to_path_buf(); - need_root = false; - } - } - while cp < ptr.add(size) && *cp == 0 { - cp = cp.offset(1); - } - start = cp; - let mut c = 0; - let mut cmd = Vec::new(); - while c < n_args && cp < ptr.add(size) { - if *cp == 0 { - c += 1; - cmd.push(get_unchecked_str(cp, start)); - start = cp.offset(1); - } - cp = cp.offset(1); - } - p.cmd = parse_command_line(&cmd); - start = cp; - while cp < ptr.add(size) { - if *cp == 0 { - if cp == start { - break; - } - p.environ.push(get_unchecked_str(cp, start)); - start = cp.offset(1); - } - cp = cp.offset(1); - } - if need_root { - for env in p.environ.iter() { - if env.starts_with("PATH=") { - p.root = Path::new(&env[6..]).to_path_buf(); - break; - } - } - } - } - } else { - return Err(()); // not enough rights I assume? - } - Ok(Some(p)) - } -} - -fn get_proc_list() -> Option> { - let count = unsafe { ffi::proc_listallpids(::std::ptr::null_mut(), 0) }; - if count < 1 { - return None; - } - let mut pids: Vec = Vec::with_capacity(count as usize); - unsafe { - pids.set_len(count as usize); - } - let count = count * mem::size_of::() as i32; - let x = unsafe { ffi::proc_listallpids(pids.as_mut_ptr() as *mut c_void, count) }; - - if x < 1 || x as usize >= pids.len() { - None - } else { - unsafe { - pids.set_len(x as usize); - } - Some(pids) - } -} - -fn get_arg_max() -> usize { - let mut mib: [c_int; 3] = [libc::CTL_KERN, libc::KERN_ARGMAX, 0]; - let mut arg_max = 0i32; - let mut size = mem::size_of::(); - unsafe { - if libc::sysctl( - mib.as_mut_ptr(), - 2, - (&mut arg_max) as *mut i32 as *mut c_void, - &mut size, - ::std::ptr::null_mut(), - 0, - ) == -1 - { - 4096 // We default to this value - } else { - arg_max as usize - } - } -} - impl System { fn clear_procs(&mut self) { let mut to_delete = Vec::new(); @@ -654,47 +72,33 @@ impl System { impl SystemExt for System { fn new_with_specifics(refreshes: RefreshKind) -> System { + let port = unsafe { ffi::mach_host_self() }; + let (global_processor, processors) = init_processors(port); + let mut s = System { - process_list: HashMap::new(), + process_list: HashMap::with_capacity(200), mem_total: 0, mem_free: 0, swap_total: 0, swap_free: 0, - processors: Vec::new(), + global_processor, + processors, page_size_kb: unsafe { sysconf(_SC_PAGESIZE) as u64 >> 10 }, // divide by 1024 - temperatures: Vec::new(), + components: Vec::with_capacity(2), connection: get_io_service_connection(), - disks: Vec::new(), - network: network::new(), + disks: Vec::with_capacity(1), + networks: Networks::new(), uptime: get_uptime(), - port: unsafe { ffi::mach_host_self() }, + port, }; s.refresh_specifics(refreshes); s } - fn refresh_system(&mut self) { - self.uptime = get_uptime(); - unsafe fn get_sys_value( - high: u32, - low: u32, - mut len: usize, - value: *mut c_void, - mib: &mut [i32; 2], - ) -> bool { - mib[0] = high as i32; - mib[1] = low as i32; - libc::sysctl( - mib.as_mut_ptr(), - 2, - value, - &mut len as *mut usize, - ::std::ptr::null_mut(), - 0, - ) == 0 - } - + fn refresh_memory(&mut self) { let mut mib = [0, 0]; + + self.uptime = get_uptime(); unsafe { // get system values // get swap info @@ -746,120 +150,38 @@ impl SystemExt for System { - u64::from(stat.purgeable_count)) * self.page_size_kb; } + } + } - if let Some(con) = self.connection { - if self.temperatures.is_empty() { - // getting CPU critical temperature - let mut v = vec!['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0]; - let tmp = get_temperature(con, v.as_mut_ptr()); - let critical_temp = if tmp > 0f32 { Some(tmp) } else { None }; - // getting CPU temperature - // "TC0P" - v[3] = 'P' as i8; - let temp = get_temperature(con, v.as_mut_ptr() as *mut i8); - if temp > 0f32 { - self.temperatures.push(Component::new( - "CPU".to_owned(), - None, - critical_temp, - )); - } - // getting GPU temperature - // "TG0P" - v[1] = 'G' as i8; - let temp = get_temperature(con, v.as_mut_ptr() as *mut i8); - if temp > 0f32 { - self.temperatures.push(Component::new( - "GPU".to_owned(), - None, - critical_temp, - )); - } - // getting battery temperature - // "TB0T" - v[1] = 'B' as i8; - v[3] = 'T' as i8; - let temp = get_temperature(con, v.as_mut_ptr() as *mut i8); - if temp > 0f32 { - self.temperatures.push(Component::new( - "Battery".to_owned(), - None, - critical_temp, - )); - } - } else { - let mut v = vec!['T' as i8, 'C' as i8, '0' as i8, 'P' as i8, 0]; - for comp in &mut self.temperatures { - match &*comp.get_label() { - "CPU" => { - v[1] = 'C' as i8; - v[3] = 'P' as i8; - } - "GPU" => { - v[1] = 'G' as i8; - v[3] = 'P' as i8; - } - _ => { - v[1] = 'B' as i8; - v[3] = 'T' as i8; - } - }; - let temp = get_temperature(con, v.as_mut_ptr() as *mut i8); - ::sys::component::update_component(comp, temp); - } + fn refresh_components_list(&mut self) { + if let Some(con) = self.connection { + self.components.clear(); + // getting CPU critical temperature + let critical_temp = crate::mac::component::get_temperature( + con, + &['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0], + ); + + for (id, v) in crate::mac::component::COMPONENTS_TEMPERATURE_IDS.iter() { + if let Some(c) = Component::new((*id).to_owned(), None, critical_temp, v, con) { + self.components.push(c); } } + } + } - // get processor values - let mut num_cpu_u = 0u32; - let mut cpu_info: *mut i32 = ::std::ptr::null_mut(); - let mut num_cpu_info = 0u32; + fn refresh_cpu(&mut self) { + self.uptime = get_uptime(); - if self.processors.is_empty() { - let mut num_cpu = 0; + // get processor values + let mut num_cpu_u = 0u32; + let mut cpu_info: *mut i32 = ::std::ptr::null_mut(); + let mut num_cpu_info = 0u32; - if !get_sys_value( - ffi::CTL_HW, - ffi::HW_NCPU, - mem::size_of::(), - &mut num_cpu as *mut usize as *mut c_void, - &mut mib, - ) { - num_cpu = 1; - } + let mut pourcent = 0f32; - self.processors.push(processor::create_proc( - "0".to_owned(), - Arc::new(ProcessorData::new(::std::ptr::null_mut(), 0)), - )); - if ffi::host_processor_info( - self.port, - ffi::PROCESSOR_CPU_LOAD_INFO, - &mut num_cpu_u as *mut u32, - &mut cpu_info as *mut *mut i32, - &mut num_cpu_info as *mut u32, - ) == ffi::KERN_SUCCESS - { - let proc_data = Arc::new(ProcessorData::new(cpu_info, num_cpu_info)); - for i in 0..num_cpu { - let mut p = - processor::create_proc(format!("{}", i + 1), Arc::clone(&proc_data)); - let in_use = *cpu_info.offset( - (ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_USER as isize, - ) + *cpu_info.offset( - (ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_SYSTEM as isize, - ) + *cpu_info.offset( - (ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_NICE as isize, - ); - let total = in_use - + *cpu_info.offset( - (ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_IDLE as isize, - ); - processor::set_cpu_proc(&mut p, in_use as f32 / total as f32); - self.processors.push(p); - } - } - } else if ffi::host_processor_info( + unsafe { + if ffi::host_processor_info( self.port, ffi::PROCESSOR_CPU_LOAD_INFO, &mut num_cpu_u as *mut u32, @@ -867,10 +189,9 @@ impl SystemExt for System { &mut num_cpu_info as *mut u32, ) == ffi::KERN_SUCCESS { - let mut pourcent = 0f32; let proc_data = Arc::new(ProcessorData::new(cpu_info, num_cpu_info)); - for (i, proc_) in self.processors.iter_mut().skip(1).enumerate() { - let old_proc_data = &*processor::get_processor_data(proc_); + for (i, proc_) in self.processors.iter_mut().enumerate() { + let old_proc_data = &*proc_.get_data(); let in_use = (*cpu_info.offset( (ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_USER as isize, @@ -891,25 +212,13 @@ impl SystemExt for System { ) - *old_proc_data.cpu_info.offset( (ffi::CPU_STATE_MAX * i) as isize + ffi::CPU_STATE_IDLE as isize, )); - processor::update_proc( - proc_, - in_use as f32 / total as f32, - Arc::clone(&proc_data), - ); + proc_.update(in_use as f32 / total as f32 * 100., Arc::clone(&proc_data)); pourcent += proc_.get_cpu_usage(); } - if self.processors.len() > 1 { - let len = self.processors.len() - 1; - if let Some(p) = self.processors.get_mut(0) { - processor::set_cpu_usage(p, pourcent / len as f32); - } - } } } - } - - fn refresh_network(&mut self) { - network::update_network(&mut self.network); + self.global_processor + .set_cpu_usage(pourcent / self.processors.len() as f32); } fn refresh_processes(&mut self) { @@ -918,28 +227,13 @@ impl SystemExt for System { return; } if let Some(pids) = get_proc_list() { - let taskallinfo_size = mem::size_of::() as i32; - let taskinfo_size = mem::size_of::() as i32; - let threadinfo_size = mem::size_of::() as i32; let arg_max = get_arg_max(); - let entries: Vec = { let wrap = &Wrap(UnsafeCell::new(&mut self.process_list)); pids.par_iter() - .flat_map(|pid| { - let mut mib: [c_int; 3] = [libc::CTL_KERN, libc::KERN_ARGMAX, 0]; - match update_process( - wrap, - *pid, - taskallinfo_size, - taskinfo_size, - threadinfo_size, - &mut mib, - arg_max as size_t, - ) { - Ok(x) => x, - Err(_) => None, - } + .flat_map(|pid| match update_process(wrap, *pid, arg_max as size_t) { + Ok(x) => x, + Err(_) => None, }) .collect() }; @@ -951,23 +245,10 @@ impl SystemExt for System { } fn refresh_process(&mut self, pid: Pid) -> bool { - let taskallinfo_size = mem::size_of::() as i32; - let taskinfo_size = mem::size_of::() as i32; - let threadinfo_size = mem::size_of::() as i32; - - let mut mib: [c_int; 3] = [libc::CTL_KERN, libc::KERN_ARGMAX, 0]; let arg_max = get_arg_max(); match { let wrap = Wrap(UnsafeCell::new(&mut self.process_list)); - update_process( - &wrap, - pid, - taskallinfo_size, - taskinfo_size, - threadinfo_size, - &mut mib, - arg_max as size_t, - ) + update_process(&wrap, pid, arg_max as size_t) } { Ok(Some(p)) => { self.process_list.insert(p.pid(), p); @@ -978,21 +259,15 @@ impl SystemExt for System { } } - fn refresh_disks(&mut self) { - for disk in &mut self.disks { - disk.update(); - } - } - - fn refresh_disk_list(&mut self) { - self.disks = get_disks(); + fn refresh_disks_list(&mut self) { + self.disks = crate::mac::disk::get_disks(); } // COMMON PART // // Need to be moved into a "common" file to avoid duplication. - fn get_process_list(&self) -> &HashMap { + fn get_processes(&self) -> &HashMap { &self.process_list } @@ -1000,12 +275,20 @@ impl SystemExt for System { self.process_list.get(&pid) } - fn get_processor_list(&self) -> &[Processor] { - &self.processors[..] + fn get_global_processor_info(&self) -> &Processor { + &self.global_processor + } + + fn get_processors(&self) -> &[Processor] { + &self.processors + } + + fn get_networks(&self) -> &Networks { + &self.networks } - fn get_network(&self) -> &NetworkData { - &self.network + fn get_networks_mut(&mut self) -> &mut Networks { + &mut self.networks } fn get_total_memory(&self) -> u64 { @@ -1033,17 +316,37 @@ impl SystemExt for System { self.swap_total - self.swap_free } - fn get_components_list(&self) -> &[Component] { - &self.temperatures[..] + fn get_components(&self) -> &[Component] { + &self.components + } + + fn get_components_mut(&mut self) -> &mut [Component] { + &mut self.components } fn get_disks(&self) -> &[Disk] { - &self.disks[..] + &self.disks + } + + fn get_disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks } fn get_uptime(&self) -> u64 { self.uptime } + + fn get_load_average(&self) -> LoadAvg { + let loads = vec![0f64; 3]; + unsafe { + ffi::getloadavg(loads.as_ptr() as *const f64, 3); + } + LoadAvg { + one: loads[0], + five: loads[1], + fifteen: loads[2], + } + } } impl Default for System { @@ -1051,3 +354,101 @@ impl Default for System { System::new() } } + +// code from https://github.com/Chris911/iStats +fn get_io_service_connection() -> Option { + let mut master_port: ffi::mach_port_t = 0; + let mut iterator: ffi::io_iterator_t = 0; + + unsafe { + ffi::IOMasterPort(ffi::MACH_PORT_NULL, &mut master_port); + + let matching_dictionary = ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8); + let result = + ffi::IOServiceGetMatchingServices(master_port, matching_dictionary, &mut iterator); + if result != ffi::KIO_RETURN_SUCCESS { + //println!("Error: IOServiceGetMatchingServices() = {}", result); + return None; + } + + let device = ffi::IOIteratorNext(iterator); + ffi::IOObjectRelease(iterator); + if device == 0 { + //println!("Error: no SMC found"); + return None; + } + + let mut conn = 0; + let result = ffi::IOServiceOpen(device, ffi::mach_task_self(), 0, &mut conn); + ffi::IOObjectRelease(device); + if result != ffi::KIO_RETURN_SUCCESS { + //println!("Error: IOServiceOpen() = {}", result); + return None; + } + + Some(conn) + } +} + +fn get_uptime() -> u64 { + let mut boottime: libc::timeval = unsafe { mem::zeroed() }; + let mut len = mem::size_of::(); + let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; + unsafe { + if libc::sysctl( + mib.as_mut_ptr(), + 2, + &mut boottime as *mut libc::timeval as *mut _, + &mut len, + ::std::ptr::null_mut(), + 0, + ) < 0 + { + return 0; + } + } + let bsec = boottime.tv_sec; + let csec = unsafe { libc::time(::std::ptr::null_mut()) }; + + unsafe { libc::difftime(csec, bsec) as u64 } +} + +fn get_arg_max() -> usize { + let mut mib: [c_int; 3] = [libc::CTL_KERN, libc::KERN_ARGMAX, 0]; + let mut arg_max = 0i32; + let mut size = mem::size_of::(); + unsafe { + if libc::sysctl( + mib.as_mut_ptr(), + 2, + (&mut arg_max) as *mut i32 as *mut c_void, + &mut size, + ::std::ptr::null_mut(), + 0, + ) == -1 + { + 4096 // We default to this value + } else { + arg_max as usize + } + } +} + +pub(crate) unsafe fn get_sys_value( + high: u32, + low: u32, + mut len: usize, + value: *mut c_void, + mib: &mut [i32; 2], +) -> bool { + mib[0] = high as i32; + mib[1] = low as i32; + libc::sysctl( + mib.as_mut_ptr(), + 2, + value, + &mut len as *mut usize, + ::std::ptr::null_mut(), + 0, + ) == 0 +} diff --git a/src/net.rs b/src/net.rs new file mode 100644 index 000000000..878c6648b --- /dev/null +++ b/src/net.rs @@ -0,0 +1,72 @@ +use std::collections::HashMap; + +/// NICLoad represents the network interface card load informations +#[derive(Debug)] +pub struct NICLoad { + /// a total number of bytes received over interface. + pub rx_bytes: usize, + /// a total number of bytes transmitted over interface. + pub tx_bytes: usize, + /// a total number of packets received. + pub rx_packets: usize, + /// a total number of packets transmitted. + pub tx_packets: usize, + /// shows a total number of packets received with error. This includes + /// too-long-frames errors, ring-buffer overflow errors, CRC errors, + /// frame alignment errors, fifo overruns, and missed packets. + pub rx_errors: usize, + /// similar to `rx_errors` + pub tx_errors: usize, + /// Indicates the number of compressed packets received by this + /// network device. This value might only be relevant for interfaces + /// that support packet compression (e.g: PPP). + pub rx_compressed: usize, + /// Indicates the number of transmitted compressed packets. Note + /// this might only be relevant for devices that support + /// compression (e.g: PPP). + pub tx_compressed: usize, +} + +impl NICLoad { + /// Returns the current network interfaces card statistics + /// + /// # Notes + /// + /// Current don't support non-unix operating system + #[cfg(not(unix))] + pub fn snapshot() -> HashMap { + HashMap::new() + } + + /// Returns the current network interfaces card statistics + #[cfg(unix)] + pub fn snapshot() -> HashMap { + let mut result = HashMap::new(); + if let Ok(dir) = std::fs::read_dir("/sys/class/net/") { + for entry in dir { + if let Ok(entry) = entry { + let parent = entry.path().join("statistics"); + let read = |path: &str| -> usize { + std::fs::read_to_string(parent.join(path)) + .unwrap_or_default() + .trim() + .parse() + .unwrap_or_default() + }; + let load = NICLoad { + rx_bytes: read("rx_bytes"), + tx_bytes: read("tx_bytes"), + rx_packets: read("rx_packets"), + tx_packets: read("tx_packets"), + rx_errors: read("rx_errors"), + tx_errors: read("tx_errors"), + rx_compressed: read("rx_compressed"), + tx_compressed: read("tx_compressed"), + }; + result.insert(format!("{:?}", entry.file_name()), load); + } + } + } + result + } +} diff --git a/src/process.rs b/src/process.rs deleted file mode 100644 index 51e833d60..000000000 --- a/src/process.rs +++ /dev/null @@ -1,7 +0,0 @@ -// -// Sysinfo -// -// Copyright (c) 2015 Guillaume Gomez -// - -// Functions 'set_time' and 'has_been_updated' will need to get moved here. diff --git a/src/sysinfo.rs b/src/sysinfo.rs index 8960fc2d7..5dfa5857a 100644 --- a/src/sysinfo.rs +++ b/src/sysinfo.rs @@ -14,18 +14,18 @@ //! ``` //! use sysinfo::{ProcessExt, SystemExt}; //! -//! let mut system = sysinfo::System::new(); +//! let mut system = sysinfo::System::new_all(); //! //! // First we update all information of our system struct. //! system.refresh_all(); //! //! // Now let's print every process' id and name: -//! for (pid, proc_) in system.get_process_list() { +//! for (pid, proc_) in system.get_processes() { //! println!("{}:{} => status: {:?}", pid, proc_.name(), proc_.status()); //! } //! //! // Then let's print the temperature of the different components: -//! for component in system.get_components_list() { +//! for component in system.get_components() { //! println!("{:?}", component); //! } //! @@ -35,22 +35,25 @@ //! } //! //! // And finally the RAM and SWAP information: -//! println!("total memory: {} kB", system.get_total_memory()); -//! println!("used memory : {} kB", system.get_used_memory()); -//! println!("total swap : {} kB", system.get_total_swap()); -//! println!("used swap : {} kB", system.get_used_swap()); +//! println!("total memory: {} KiB", system.get_total_memory()); +//! println!("used memory : {} KiB", system.get_used_memory()); +//! println!("total swap : {} KiB", system.get_total_swap()); +//! println!("used swap : {} KiB", system.get_used_swap()); //! ``` #![crate_name = "sysinfo"] #![crate_type = "lib"] #![crate_type = "rlib"] #![deny(missing_docs)] +#![deny(intra_doc_link_resolution_failure)] //#![deny(warnings)] #![allow(unknown_lints)] +extern crate num_cpus; + #[macro_use] extern crate cfg_if; -#[cfg(not(target_os = "unknown"))] +#[cfg(not(any(target_os = "unknown", target_arch = "wasm32")))] extern crate libc; extern crate rayon; @@ -68,6 +71,7 @@ cfg_if! { mod windows; use windows as sys; extern crate winapi; + extern crate ntapi; } else if #[cfg(unix)] { mod linux; use linux as sys; @@ -77,24 +81,75 @@ cfg_if! { } } -pub use common::{AsU32, Pid, RefreshKind}; -pub use sys::{Component, Disk, DiskType, NetworkData, Process, ProcessStatus, Processor, System}; -pub use traits::{ComponentExt, DiskExt, NetworkExt, ProcessExt, ProcessorExt, SystemExt}; +pub extern crate cache_size; +pub extern crate pnet_datalink as datalink; + +pub use common::{AsU32, DiskType, NetworksIter, Pid, RefreshKind}; +pub use io::IOLoad; +pub use net::NICLoad; +pub use num_cpus::get_physical as get_physical_cores; +pub use sys::{ + Component, Disk, NetworkData, Networks, Process, + ProcessStatus, Processor, System, +}; +pub use traits::{ + ComponentExt, DiskExt, NetworkExt, NetworksExt, ProcessExt, ProcessorExt, SystemExt, +}; #[cfg(feature = "c-interface")] pub use c_interface::*; pub use utils::get_current_pid; +use std::collections::HashMap; + #[cfg(feature = "c-interface")] mod c_interface; mod common; mod component; -mod process; +mod io; +mod net; mod processor; mod system; mod traits; mod utils; +/// This function is only used on linux targets, on the other platforms it does nothing. +/// +/// On linux, to improve performance, we keep a `/proc` file open for each process we index with +/// a maximum number of files open equivalent to half of the system limit. +/// +/// The problem is that some users might need all the available file descriptors so we need to +/// allow them to change this limit. Reducing +/// +/// Note that if you set a limit bigger than the system limit, the system limit will be set. +/// +/// Returns `true` if the new value has been set. +pub fn set_open_files_limit(mut _new_limit: isize) -> bool { + #[cfg(all(not(target_os = "macos"), unix))] + { + if _new_limit < 0 { + _new_limit = 0; + } + let max = sys::system::get_max_nb_fds(); + if _new_limit > max { + _new_limit = max; + } + if let Ok(ref mut x) = unsafe { sys::system::REMAINING_FILES.lock() } { + // If files are already open, to be sure that the number won't be bigger when those + // files are closed, we subtract the current number of opened files to the new limit. + let diff = max - **x; + **x = _new_limit - diff; + true + } else { + false + } + } + #[cfg(any(not(unix), target_os = "macos"))] + { + false + } +} + /// An enum representing signal on UNIX-like systems. #[repr(C)] #[derive(Clone, PartialEq, PartialOrd, Debug, Copy)] @@ -167,9 +222,50 @@ pub enum Signal { Sys = 31, } +/// A struct represents system load average value. +#[repr(C)] +#[derive(Default, Debug, Clone)] +pub struct LoadAvg { + /// Average load within one minute. + pub one: f64, + /// Average load within five minutes. + pub five: f64, + /// Average load within fifteen minutes. + pub fifteen: f64, +} + +/// Returns system wide configuration +/// +/// # Notes +/// +/// Current only can be used in operating system mounted `procfs` +pub fn get_sysctl_list() -> HashMap { + const DIR: &str = "/proc/sys/"; + let mut result = HashMap::new(); + for entry in walkdir::WalkDir::new(DIR) { + let entry = match entry { + Ok(entry) => entry, + _ => continue, + }; + + let content = match std::fs::read_to_string(entry.path()) { + Ok(c) => c, + _ => continue, + }; + + let path = match entry.path().to_str() { + Some(p) => p, + _ => continue, + }; + let name = path.trim_start_matches(DIR).replace("/", "."); + result.insert(name, content.trim().to_string()); + } + result +} + #[cfg(test)] mod test { - use traits::{ProcessExt, SystemExt}; + use super::*; #[test] fn check_memory_usage() { @@ -177,10 +273,40 @@ mod test { s.refresh_all(); assert_eq!( - s.get_process_list() + s.get_processes() .iter() .all(|(_, proc_)| proc_.memory() == 0), false ); } + + #[test] + fn test_nic_load() { + println!("test test_nic_load: {:?}", ::NICLoad::snapshot()); + } + + #[test] + fn test_io_load() { + println!("test test_io_load: {:?}", ::IOLoad::snapshot()); + } + + #[test] + fn test_get_cores() { + assert_ne!(::get_physical_cores(), 0, "expect none-zero physical core"); + } + + #[test] + fn test_cache_size() { + let caches = vec![ + ("l1-cache-size", ::cache_size::l1_cache_size()), + ("l1-cache-line-size", ::cache_size::l1_cache_line_size()), + ("l2-cache-size", ::cache_size::l2_cache_size()), + ("l2-cache-line-size", ::cache_size::l2_cache_line_size()), + ("l3-cache-size", ::cache_size::l3_cache_size()), + ("l3-cache-line-size", ::cache_size::l3_cache_line_size()), + ]; + for c in caches { + assert_ne!(c.1.unwrap(), 0, "{} expect non-zero", c.0) + } + } } diff --git a/src/system.rs b/src/system.rs index d5aac83f8..27f7682b4 100644 --- a/src/system.rs +++ b/src/system.rs @@ -25,7 +25,15 @@ mod tests { #[test] fn test_refresh_process() { let mut sys = System::new(); - assert!(sys.refresh_process(utils::get_current_pid().expect("failed to get current pid"))); + assert!( + sys.get_processes().is_empty(), + "no process should be listed!" + ); + sys.refresh_processes(); + assert!( + sys.refresh_process(utils::get_current_pid().expect("failed to get current pid")), + "process not listed", + ); } #[test] diff --git a/src/traits.rs b/src/traits.rs index 6656b8c59..750ffee93 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -4,7 +4,10 @@ // Copyright (c) 2017 Guillaume Gomez // -use sys::{Component, Disk, DiskType, NetworkData, Process, Processor}; +use sys::{Component, Disk, Networks, Process, Processor}; +use DiskType; +use LoadAvg; +use NetworksIter; use Pid; use ProcessStatus; use RefreshKind; @@ -13,132 +16,409 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::path::Path; -/// Contains all the methods of the `Disk` struct. +/// Contains all the methods of the [`Disk`][crate::Disk] struct. +/// +/// ```no_run +/// use sysinfo::{DiskExt, System, SystemExt}; +/// +/// let s = System::new(); +/// for disk in s.get_disks() { +/// println!("{:?}: {:?}", disk.get_name(), disk.get_type()); +/// } +/// ``` pub trait DiskExt { /// Returns the disk type. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.get_disks() { + /// println!("{:?}", disk.get_type()); + /// } + /// ``` fn get_type(&self) -> DiskType; /// Returns the disk name. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.get_disks() { + /// println!("{:?}", disk.get_name()); + /// } + /// ``` fn get_name(&self) -> &OsStr; /// Returns the file system used on this disk (so for example: `EXT4`, `NTFS`, etc...). + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.get_disks() { + /// println!("{:?}", disk.get_file_system()); + /// } + /// ``` fn get_file_system(&self) -> &[u8]; /// Returns the mount point of the disk (`/` for example). + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.get_disks() { + /// println!("{:?}", disk.get_mount_point()); + /// } + /// ``` fn get_mount_point(&self) -> &Path; /// Returns the total disk size, in bytes. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.get_disks() { + /// println!("{}", disk.get_total_space()); + /// } + /// ``` fn get_total_space(&self) -> u64; /// Returns the available disk size, in bytes. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.get_disks() { + /// println!("{}", disk.get_available_space()); + /// } + /// ``` fn get_available_space(&self) -> u64; /// Update the disk' information. - fn update(&mut self) -> bool; + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for disk in s.get_disks_mut() { + /// disk.refresh(); + /// } + /// ``` + fn refresh(&mut self) -> bool; } -/// Contains all the methods of the `Process` struct. +/// Contains all the methods of the [`Process`][crate::Process] struct. pub trait ProcessExt { /// Create a new process only containing the given information. /// /// On windows, the `start_time` argument is ignored. + #[doc(hidden)] fn new(pid: Pid, parent: Option, start_time: u64) -> Self; /// Sends the given `signal` to the process. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, Signal, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// process.kill(Signal::Kill); + /// } + /// ``` fn kill(&self, signal: ::Signal) -> bool; /// Returns the name of the process. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{}", process.name()); + /// } + /// ``` fn name(&self) -> &str; /// Returns the command line. // /// // /// On Windows, this is always a one element vector. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{:?}", process.cmd()); + /// } + /// ``` fn cmd(&self) -> &[String]; /// Returns the path to the process. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{}", process.exe().display()); + /// } + /// ``` fn exe(&self) -> &Path; /// Returns the pid of the process. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{}", process.pid()); + /// } + /// ``` fn pid(&self) -> Pid; /// Returns the environment of the process. /// - /// Always empty on Windows except for current process. + /// Always empty on Windows, except for current process. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{:?}", process.environ()); + /// } + /// ``` fn environ(&self) -> &[String]; /// Returns the current working directory. /// /// Always empty on Windows. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{}", process.cwd().display()); + /// } + /// ``` fn cwd(&self) -> &Path; /// Returns the path of the root directory. /// /// Always empty on Windows. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{}", process.root().display()); + /// } + /// ``` fn root(&self) -> &Path; - /// Returns the memory usage (in kB). + /// Returns the memory usage (in KiB). + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{} KiB", process.memory()); + /// } + /// ``` fn memory(&self) -> u64; - /// Returns the virtual memory usage (in kB). + /// Returns the virtual memory usage (in KiB). + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{} KiB", process.virtual_memory()); + /// } + /// ``` fn virtual_memory(&self) -> u64; /// Returns the parent pid. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{:?}", process.parent()); + /// } + /// ``` fn parent(&self) -> Option; /// Returns the status of the processus. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{:?}", process.status()); + /// } + /// ``` fn status(&self) -> ProcessStatus; /// Returns the time of process launch (in seconds). + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{}", process.start_time()); + /// } + /// ``` fn start_time(&self) -> u64; - /// Returns the total CPU usage. + /// Returns the total CPU usage (in %). + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{}%", process.cpu_usage()); + /// } + /// ``` fn cpu_usage(&self) -> f32; } -/// Contains all the methods of the `Processor` struct. +/// Contains all the methods of the [`Processor`][crate::Processor] struct. pub trait ProcessorExt { /// Returns this processor's usage. /// /// Note: You'll need to refresh it at least twice at first if you want to have a /// non-zero value. + /// + /// ```no_run + /// use sysinfo::{ProcessorExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for processor in s.get_processors() { + /// println!("{}%", processor.get_cpu_usage()); + /// } + /// ``` fn get_cpu_usage(&self) -> f32; /// Returns this processor's name. + /// + /// ```no_run + /// use sysinfo::{ProcessorExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for processor in s.get_processors() { + /// println!("{}", processor.get_name()); + /// } + /// ``` fn get_name(&self) -> &str; + + /// Returns the processor's vendor id. + /// + /// ```no_run + /// use sysinfo::{ProcessorExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for processor in s.get_processors() { + /// println!("{}", processor.get_vendor_id()); + /// } + /// ``` + fn get_vendor_id(&self) -> &str; + + /// Returns the processor's brand. + /// + /// ```no_run + /// use sysinfo::{ProcessorExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for processor in s.get_processors() { + /// println!("{}", processor.get_brand()); + /// } + /// ``` + fn get_brand(&self) -> &str; + + /// Returns the processor's frequency. + /// + /// ```no_run + /// use sysinfo::{ProcessorExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for processor in s.get_processors() { + /// println!("{}", processor.get_frequency()); + /// } + /// ``` + fn get_frequency(&self) -> u64; } -/// Contains all the methods of the [`System`] type. +/// Contains all the methods of the [`System`][crate::System] type. pub trait SystemExt: Sized { - /// Creates a new [`System`] instance. It only contains the disks' list and the processes list - /// at this stage. Use the [`refresh_all`] method to update its internal information (or any of - /// the `refresh_` method). + /// Creates a new [`System`] instance with nothing loaded except the processors list. If you + /// want to load components, network interfaces or the disks, you'll have to use the + /// `refresh_*_list` methods. [`SystemExt::refresh_networks_list`] for example. + /// + /// Use the [`refresh_all`] method to update its internal information (or any of the `refresh_` + /// method). /// + /// [`System`]: crate::System /// [`refresh_all`]: #method.refresh_all + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// ``` fn new() -> Self { - let mut s = Self::new_with_specifics(RefreshKind::new()); - s.refresh_disk_list(); - s.refresh_all(); - s + Self::new_with_specifics(RefreshKind::new()) + } + + /// Creates a new [`System`] instance with everything loaded. + /// + /// It is an equivalent of [`SystemExt::new_with_specifics`]`(`[`RefreshKind::everything`]`())`. + /// + /// [`System`]: crate::System + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// ``` + fn new_all() -> Self { + Self::new_with_specifics(RefreshKind::everything()) } /// Creates a new [`System`] instance and refresh the data corresponding to the /// given [`RefreshKind`]. /// + /// [`System`]: crate::System + /// /// # Example /// /// ``` /// use sysinfo::{RefreshKind, System, SystemExt}; /// /// // We want everything except disks. - /// let mut system = System::new_with_specifics(RefreshKind::everything().without_disk_list()); + /// let mut system = System::new_with_specifics(RefreshKind::everything().without_disks_list()); /// /// assert_eq!(system.get_disks().len(), 0); - /// assert!(system.get_process_list().len() > 0); + /// assert!(system.get_processes().len() > 0); /// /// // If you want the disks list afterwards, just call the corresponding - /// // "refresh_disk_list": - /// system.refresh_disk_list(); - /// assert!(system.get_disks().len() > 0); + /// // "refresh_disks_list": + /// system.refresh_disks_list(); + /// let disks = system.get_disks(); /// ``` fn new_with_specifics(refreshes: RefreshKind) -> Self; @@ -150,66 +430,248 @@ pub trait SystemExt: Sized { /// ``` /// use sysinfo::{RefreshKind, System, SystemExt}; /// - /// let mut s = System::new(); + /// let mut s = System::new_all(); /// - /// // Let's just update network data and processes: - /// s.refresh_specifics(RefreshKind::new().with_network().with_processes()); + /// // Let's just update networks and processes: + /// s.refresh_specifics(RefreshKind::new().with_networks().with_processes()); /// ``` fn refresh_specifics(&mut self, refreshes: RefreshKind) { - if refreshes.system() { - self.refresh_system(); + if refreshes.memory() { + self.refresh_memory(); } - if refreshes.network() { - self.refresh_network(); + if refreshes.cpu() { + self.refresh_cpu(); + } + if refreshes.components_list() { + self.refresh_components_list(); + } else if refreshes.components() { + self.refresh_components(); + } + if refreshes.networks_list() { + self.refresh_networks_list(); + } else if refreshes.networks() { + self.refresh_networks(); } if refreshes.processes() { self.refresh_processes(); } - if refreshes.disk_list() { - self.refresh_disk_list(); - } - if refreshes.disks() { + if refreshes.disks_list() { + self.refresh_disks_list(); + } else if refreshes.disks() { self.refresh_disks(); } } /// Refresh system information (such as memory, swap, CPU usage and components' temperature). - fn refresh_system(&mut self); + /// + /// If you want some more specific refresh, you might be interested into looking at + /// [`refresh_memory`], [`refresh_cpu`] and [`refresh_components`]. + /// + /// [`refresh_memory`]: SystemExt::refresh_memory + /// [`refresh_cpu`]: SystemExt::refresh_memory + /// [`refresh_components`]: SystemExt::refresh_components + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_system(); + /// ``` + fn refresh_system(&mut self) { + self.refresh_memory(); + self.refresh_cpu(); + self.refresh_components(); + } + + /// Refresh RAM and SWAP usage. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_memory(); + /// ``` + fn refresh_memory(&mut self); + + /// Refresh CPU usage. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_cpu(); + /// ``` + fn refresh_cpu(&mut self); + + /// Refresh components' temperature. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_components(); + /// ``` + fn refresh_components(&mut self) { + for component in self.get_components_mut() { + component.refresh(); + } + } + + /// Refresh components list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new(); + /// s.refresh_components_list(); + /// ``` + fn refresh_components_list(&mut self); /// Get all processes and update their information. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_processes(); + /// ``` fn refresh_processes(&mut self); /// Refresh *only* the process corresponding to `pid`. Returns `false` if the process doesn't - /// exist. + /// exist. If it isn't listed yet, it'll be added. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_process(1337); + /// ``` fn refresh_process(&mut self, pid: Pid) -> bool; /// Refreshes the listed disks' information. - fn refresh_disks(&mut self); + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_disks(); + /// ``` + fn refresh_disks(&mut self) { + for disk in self.get_disks_mut() { + disk.refresh(); + } + } /// The disk list will be emptied then completely recomputed. - fn refresh_disk_list(&mut self); + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_disks_list(); + /// ``` + fn refresh_disks_list(&mut self); - /// Refresh data network. - fn refresh_network(&mut self); + /// Refresh networks data. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_networks(); + /// ``` + /// + /// It is a shortcut for: + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.get_networks_mut(); + /// networks.refresh(); + /// ``` + fn refresh_networks(&mut self) { + self.get_networks_mut().refresh(); + } - /// Refreshes all system, processes and disks information. + /// The network list will be updated: removing not existing anymore interfaces and adding new + /// ones. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_networks_list(); + /// ``` + /// + /// This is a shortcut for: + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.get_networks_mut(); + /// networks.refresh_networks_list(); + /// ``` + fn refresh_networks_list(&mut self) { + self.get_networks_mut().refresh_networks_list(); + } + + /// Refreshes all system, processes, disks and network interfaces information. + /// + /// Please note that it doesn't recompute disks list, components list nor network interfaces + /// list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_all(); + /// ``` fn refresh_all(&mut self) { self.refresh_system(); self.refresh_processes(); self.refresh_disks(); - self.refresh_network(); + self.refresh_networks(); } /// Returns the process list. - fn get_process_list(&self) -> &HashMap; + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for (pid, process) in s.get_processes() { + /// println!("{} {}", pid, process.name()); + /// } + /// ``` + fn get_processes(&self) -> &HashMap; /// Returns the process corresponding to the given pid or `None` if no such process exists. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// if let Some(process) = s.get_process(1337) { + /// println!("{}", process.name()); + /// } + /// ``` fn get_process(&self, pid: Pid) -> Option<&Process>; /// Returns a list of process containing the given `name`. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for process in s.get_process_by_name("htop") { + /// println!("{} {}", process.pid(), process.name()); + /// } + /// ``` fn get_process_by_name(&self, name: &str) -> Vec<&Process> { let mut ret = vec![]; - for val in self.get_process_list().values() { + for val in self.get_processes().values() { if val.name().contains(name) { ret.push(val); } @@ -217,57 +679,443 @@ pub trait SystemExt: Sized { ret } - /// The first processor in the array is the "main" one (aka the addition of all the others). - fn get_processor_list(&self) -> &[Processor]; + /// Returns "global" processors information (aka the addition of all the processors). + /// + /// ```no_run + /// use sysinfo::{ProcessorExt, System, SystemExt}; + /// + /// let s = System::new(); + /// println!("{}%", s.get_global_processor_info().get_cpu_usage()); + /// ``` + fn get_global_processor_info(&self) -> &Processor; + + /// Returns the list of the processors. + /// + /// ```no_run + /// use sysinfo::{ProcessorExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for processor in s.get_processors() { + /// println!("{}%", processor.get_cpu_usage()); + /// } + /// ``` + fn get_processors(&self) -> &[Processor]; /// Returns total RAM size in KiB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KiB", s.get_total_memory()); + /// ``` fn get_total_memory(&self) -> u64; /// Returns free RAM size in KiB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KiB", s.get_free_memory()); + /// ``` fn get_free_memory(&self) -> u64; /// Returns used RAM size in KiB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KiB", s.get_used_memory()); + /// ``` fn get_used_memory(&self) -> u64; /// Returns SWAP size in KiB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KiB", s.get_total_swap()); + /// ``` fn get_total_swap(&self) -> u64; /// Returns free SWAP size in KiB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KiB", s.get_free_swap()); + /// ``` fn get_free_swap(&self) -> u64; /// Returns used SWAP size in KiB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KiB", s.get_used_swap()); + /// ``` fn get_used_swap(&self) -> u64; /// Returns components list. - fn get_components_list(&self) -> &[Component]; + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.get_components() { + /// println!("{}: {}°C", component.get_label(), component.get_temperature()); + /// } + /// ``` + fn get_components(&self) -> &[Component]; + + /// Returns components list. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for component in s.get_components_mut() { + /// component.refresh(); + /// } + /// ``` + fn get_components_mut(&mut self) -> &mut [Component]; /// Returns disks' list. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for disk in s.get_disks() { + /// println!("{:?}", disk.get_name()); + /// } + /// ``` fn get_disks(&self) -> &[Disk]; - /// Returns network data. - fn get_network(&self) -> &NetworkData; + /// Returns disks' list. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for disk in s.get_disks_mut() { + /// disk.refresh(); + /// } + /// ``` + fn get_disks_mut(&mut self) -> &mut [Disk]; + + /// Returns network interfaces. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, data) in networks { + /// println!("[{}] in: {}, out: {}", interface_name, data.get_income(), data.get_outcome()); + /// } + /// ``` + fn get_networks(&self) -> &Networks; + + /// Returns a mutable access to network interfaces. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.get_networks_mut(); + /// networks.refresh_networks_list(); + /// ``` + fn get_networks_mut(&mut self) -> &mut Networks; /// Returns system uptime. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{}", s.get_uptime()); + /// ``` fn get_uptime(&self) -> u64; + + /// Returns the system load average value. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// let load_avg = s.get_load_average(); + /// println!( + /// "one minute: {}%, five minutes: {}%, fifteen minutes: {}%", + /// load_avg.one, + /// load_avg.five, + /// load_avg.fifteen, + /// ); + /// ``` + fn get_load_average(&self) -> LoadAvg; } /// Getting volume of incoming and outgoing data. pub trait NetworkExt { - /// Returns the number of incoming bytes. + /// Returns the number of incoming bytes since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("in: {} B", network.get_income()); + /// } + /// ``` fn get_income(&self) -> u64; - /// Returns the number of outgoing bytes. + /// Returns the total number of incoming bytes. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("in: {} B", network.get_total_income()); + /// } + /// ``` + fn get_total_income(&self) -> u64; + + /// Returns the number of outgoing bytes since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("out: {} B", network.get_outcome()); + /// } + /// ``` fn get_outcome(&self) -> u64; + + /// Returns the total number of outgoing bytes. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("out: {} B", network.get_total_outcome()); + /// } + /// ``` + fn get_total_outcome(&self) -> u64; + + /// Returns the number of incoming packets since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.get_packets_income()); + /// } + /// ``` + fn get_packets_income(&self) -> u64; + + /// Returns the total number of incoming packets. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.get_total_packets_income()); + /// } + /// ``` + fn get_total_packets_income(&self) -> u64; + + /// Returns the number of outcoming packets since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.get_packets_outcome()); + /// } + /// ``` + fn get_packets_outcome(&self) -> u64; + + /// Returns the total number of outcoming packets. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.get_total_packets_outcome()); + /// } + /// ``` + fn get_total_packets_outcome(&self) -> u64; + + /// Returns the number of incoming errors since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.get_errors_income()); + /// } + /// ``` + fn get_errors_income(&self) -> u64; + + /// Returns the total number of incoming errors. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.get_total_errors_income()); + /// } + /// ``` + fn get_total_errors_income(&self) -> u64; + + /// Returns the number of outcoming errors since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.get_errors_outcome()); + /// } + /// ``` + fn get_errors_outcome(&self) -> u64; + + /// Returns the total number of outcoming errors. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.get_total_errors_outcome()); + /// } + /// ``` + fn get_total_errors_outcome(&self) -> u64; +} + +/// Interacting with network interfaces. +pub trait NetworksExt { + /// Returns an iterator over the network interfaces. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.get_networks(); + /// for (interface_name, network) in networks { + /// println!("in: {} B", network.get_income()); + /// } + /// ``` + fn iter(&self) -> NetworksIter; + + /// Refreshes the network interfaces list. + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.get_networks_mut(); + /// networks.refresh_networks_list(); + /// ``` + fn refresh_networks_list(&mut self); + + /// Refreshes the network interfaces' content. + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.get_networks_mut(); + /// networks.refresh(); + /// ``` + fn refresh(&mut self); } /// Getting a component temperature information. pub trait ComponentExt { /// Returns the component's temperature (in celsius degree). + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.get_components() { + /// println!("{}°C", component.get_temperature()); + /// } + /// ``` fn get_temperature(&self) -> f32; + /// Returns the maximum temperature of this component. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.get_components() { + /// println!("{}°C", component.get_max()); + /// } + /// ``` fn get_max(&self) -> f32; + /// Returns the highest temperature before the computer halts. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.get_components() { + /// println!("{:?}°C", component.get_critical()); + /// } + /// ``` fn get_critical(&self) -> Option; + /// Returns component's label. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.get_components() { + /// println!("{}", component.get_label()); + /// } + /// ``` fn get_label(&self) -> &str; + + /// Refresh component. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for component in s.get_components_mut() { + /// component.refresh(); + /// } + /// ``` + fn refresh(&mut self); } diff --git a/src/unknown/disk.rs b/src/unknown/disk.rs index c2810cd60..b4285faef 100644 --- a/src/unknown/disk.rs +++ b/src/unknown/disk.rs @@ -5,14 +5,11 @@ // use DiskExt; +use DiskType; use std::ffi::OsStr; use std::path::Path; -/// Enum containing the different handled disks types. -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum DiskType {} - /// Struct containing a disk information. pub struct Disk {} @@ -41,7 +38,7 @@ impl DiskExt for Disk { 0 } - fn update(&mut self) -> bool { + fn refresh(&mut self) -> bool { true } } diff --git a/src/unknown/mod.rs b/src/unknown/mod.rs index 9238248b9..9a228c465 100644 --- a/src/unknown/mod.rs +++ b/src/unknown/mod.rs @@ -12,8 +12,8 @@ pub mod processor; pub mod system; pub use self::component::Component; -pub use self::disk::{Disk, DiskType}; -pub use self::network::NetworkData; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; pub use self::process::{Process, ProcessStatus}; pub use self::processor::Processor; pub use self::system::System; diff --git a/src/unknown/network.rs b/src/unknown/network.rs index b53e54ddd..7163f2c81 100644 --- a/src/unknown/network.rs +++ b/src/unknown/network.rs @@ -4,7 +4,41 @@ // Copyright (c) 2017 Guillaume Gomez // +use std::collections::HashMap; + use NetworkExt; +use NetworksExt; +use NetworksIter; + +/// Network interfaces. +/// +/// ```no_run +/// use sysinfo::{NetworksExt, System, SystemExt}; +/// +/// let s = System::new_all(); +/// let networks = s.get_networks(); +/// ``` +pub struct Networks { + interfaces: HashMap, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) {} + + fn refresh(&mut self) {} +} /// Contains network information. #[derive(Debug)] @@ -15,7 +49,47 @@ impl NetworkExt for NetworkData { 0 } + fn get_total_income(&self) -> u64 { + 0 + } + fn get_outcome(&self) -> u64 { 0 } + + fn get_total_outcome(&self) -> u64 { + 0 + } + + fn get_packets_income(&self) -> u64 { + 0 + } + + fn get_total_packets_income(&self) -> u64 { + 0 + } + + fn get_packets_outcome(&self) -> u64 { + 0 + } + + fn get_total_packets_outcome(&self) -> u64 { + 0 + } + + fn get_errors_income(&self) -> u64 { + 0 + } + + fn get_total_errors_income(&self) -> u64 { + 0 + } + + fn get_errors_outcome(&self) -> u64 { + 0 + } + + fn get_total_errors_outcome(&self) -> u64 { + 0 + } } diff --git a/src/unknown/processor.rs b/src/unknown/processor.rs index 8de271fdf..5fd8ecde4 100644 --- a/src/unknown/processor.rs +++ b/src/unknown/processor.rs @@ -4,11 +4,20 @@ // Copyright (c) 2015 Guillaume Gomez // +use std::default::Default; + +use LoadAvg; use ProcessorExt; /// Dummy struct that represents a processor. pub struct Processor {} +impl Processor { + pub(crate) fn new() -> Processor { + Processor {} + } +} + impl ProcessorExt for Processor { fn get_cpu_usage(&self) -> f32 { 0.0 @@ -17,4 +26,16 @@ impl ProcessorExt for Processor { fn get_name(&self) -> &str { "" } + + fn get_frequency(&self) -> u64 { + 0 + } + + fn get_vendor_id(&self) -> &str { + "" + } + + fn get_brand(&self) -> &str { + "" + } } diff --git a/src/unknown/system.rs b/src/unknown/system.rs index bf2e90b42..a14ca8799 100644 --- a/src/unknown/system.rs +++ b/src/unknown/system.rs @@ -8,7 +8,7 @@ use sys::component::Component; use sys::process::*; use sys::processor::*; use sys::Disk; -use sys::NetworkData; +use sys::Networks; use Pid; use {RefreshKind, SystemExt}; @@ -18,18 +18,24 @@ use std::collections::HashMap; #[derive(Debug)] pub struct System { processes_list: HashMap, - network: NetworkData, + networks: Networks, + global_processor: Processor, } impl SystemExt for System { fn new_with_specifics(_: RefreshKind) -> System { System { processes_list: Default::default(), - network: NetworkData, + networks: Networks::new(), + global_processor: Processor::new {}, } } - fn refresh_system(&mut self) {} + fn refresh_memory(&mut self) {} + + fn refresh_cpu(&mut self) {} + + fn refresh_components_list(&mut self) {} fn refresh_processes(&mut self) {} @@ -37,9 +43,7 @@ impl SystemExt for System { false } - fn refresh_disks(&mut self) {} - - fn refresh_disk_list(&mut self) {} + fn refresh_disks_list(&mut self) {} fn refresh_network(&mut self) {} @@ -47,7 +51,7 @@ impl SystemExt for System { // // Need to be moved into a "common" file to avoid duplication. - fn get_process_list(&self) -> &HashMap { + fn get_processes(&self) -> &HashMap { &self.processes_list } @@ -55,11 +59,19 @@ impl SystemExt for System { None } - fn get_network(&self) -> &NetworkData { - &self.network + fn get_networks(&self) -> &Networks { + &self.networks + } + + fn get_networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn get_global_processor_info(&self) -> &Processor { + &self.global_processor } - fn get_processor_list(&self) -> &[Processor] { + fn get_processors(&self) -> &[Processor] { &[] } @@ -87,7 +99,11 @@ impl SystemExt for System { 0 } - fn get_components_list(&self) -> &[Component] { + fn get_components(&self) -> &[Component] { + &[] + } + + fn get_components_mut(&self) -> &[Component] { &[] } @@ -95,9 +111,21 @@ impl SystemExt for System { &[] } + fn get_disks_mut(&self) -> &mut [Disk] { + &mut [] + } + fn get_uptime(&self) -> u64 { 0 } + + fn get_load_average(&self) -> LoadAvg { + LoadAvg { + one: 0., + five: 0., + fifteen: 0., + } + } } impl Default for System { diff --git a/src/utils.rs b/src/utils.rs index 66191e2f2..3520de01d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,19 +4,19 @@ // Copyright (c) 2017 Guillaume Gomez // -#[cfg(all(not(target_os = "windows"), not(target_os = "unknown")))] +#[cfg(not(any(target_os = "windows", target_os = "unknown", target_arch = "wasm32")))] use libc::{c_char, lstat, stat, S_IFLNK, S_IFMT}; -#[cfg(all(not(target_os = "windows"), not(target_os = "unknown")))] +#[cfg(not(any(target_os = "windows", target_os = "unknown", target_arch = "wasm32")))] use std::ffi::OsStr; -#[cfg(all(not(target_os = "windows"), not(target_os = "unknown")))] +#[cfg(not(any(target_os = "windows", target_os = "unknown", target_arch = "wasm32")))] use std::fs; -#[cfg(all(not(target_os = "windows"), not(target_os = "unknown")))] +#[cfg(not(any(target_os = "windows", target_os = "unknown", target_arch = "wasm32")))] use std::os::unix::ffi::OsStrExt; -#[cfg(all(not(target_os = "windows"), not(target_os = "unknown")))] +#[cfg(not(any(target_os = "windows", target_os = "unknown", target_arch = "wasm32")))] use std::path::{Path, PathBuf}; use Pid; -#[cfg(all(not(target_os = "windows"), not(target_os = "unknown")))] +#[cfg(not(any(target_os = "windows", target_os = "unknown", target_arch = "wasm32")))] pub fn realpath(original: &Path) -> PathBuf { use std::mem::MaybeUninit; @@ -51,7 +51,7 @@ pub fn realpath(original: &Path) -> PathBuf { } /* convert a path to a NUL-terminated Vec suitable for use with C functions */ -#[cfg(all(not(target_os = "windows"), not(target_os = "unknown")))] +#[cfg(not(any(target_os = "windows", target_os = "unknown", target_arch = "wasm32")))] pub fn to_cpath(path: &Path) -> Vec { let path_os: &OsStr = path.as_ref(); let mut cpath = path_os.as_bytes().to_vec(); @@ -64,7 +64,7 @@ pub fn to_cpath(path: &Path) -> Vec { /// `Err` is returned in case the platform isn't supported. pub fn get_current_pid() -> Result { cfg_if! { - if #[cfg(all(not(target_os = "windows"), not(target_os = "unknown")))] { + if #[cfg(not(any(target_os = "windows", target_os = "unknown", target_arch = "wasm32")))] { fn inner() -> Result { unsafe { Ok(::libc::getpid()) } } diff --git a/src/windows/component.rs b/src/windows/component.rs index a1c063b33..39c792b7c 100644 --- a/src/windows/component.rs +++ b/src/windows/component.rs @@ -4,24 +4,60 @@ // Copyright (c) 2018 Guillaume Gomez // +use std::ptr::null_mut; + +use winapi::shared::rpcdce::{ + RPC_C_AUTHN_LEVEL_CALL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + RPC_C_IMP_LEVEL_IMPERSONATE, +}; +use winapi::shared::winerror::{FAILED, SUCCEEDED}; +use winapi::shared::wtypesbase::CLSCTX_INPROC_SERVER; +use winapi::um::combaseapi::{ + CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CoSetProxyBlanket, CoUninitialize, +}; +use winapi::um::oaidl::VARIANT; +use winapi::um::objidl::EOAC_NONE; +use winapi::um::oleauto::{SysAllocString, SysFreeString, VariantClear}; +use winapi::um::wbemcli::{ + CLSID_WbemLocator, IEnumWbemClassObject, IID_IWbemLocator, IWbemClassObject, IWbemLocator, + IWbemServices, WBEM_FLAG_FORWARD_ONLY, WBEM_FLAG_NONSYSTEM_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, +}; + use ComponentExt; /// Struct containing a component information (temperature and name for the moment). +/// +/// Please note that on Windows, you need to have Administrator priviledges to get this +/// information. pub struct Component { temperature: f32, max: f32, critical: Option, label: String, + connection: Option, } impl Component { /// Creates a new `Component` with the given information. - pub fn new(label: String, max: Option, critical: Option) -> Component { - Component { - temperature: 0f32, - label: label, - max: max.unwrap_or(0.0), - critical: critical, + fn new() -> Option { + match Connection::new() + .and_then(|x| x.initialize_security()) + .and_then(|x| x.create_instance()) + .and_then(|x| x.connect_server()) + .and_then(|x| x.set_proxy_blanket()) + .and_then(|x| x.exec_query()) + { + Some(mut c) => match c.get_temperature(true) { + Some((temperature, critical)) => Some(Component { + temperature, + label: "Computer".to_owned(), + max: temperature, + critical, + connection: Some(c), + }), + None => None, + }, + None => None, } } } @@ -42,8 +78,299 @@ impl ComponentExt for Component { fn get_label(&self) -> &str { &self.label } + + fn refresh(&mut self) { + if self.connection.is_none() { + self.connection = Connection::new() + .and_then(|x| x.initialize_security()) + .and_then(|x| x.create_instance()) + .and_then(|x| x.connect_server()) + .and_then(|x| x.set_proxy_blanket()); + } + self.connection = if let Some(x) = self.connection.take() { + x.exec_query() + } else { + None + }; + if let Some(ref mut connection) = self.connection { + if let Some((temperature, _)) = connection.get_temperature(false) { + self.temperature = temperature; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } + } } pub fn get_components() -> Vec { - Vec::new() + match Component::new() { + Some(c) => vec![c], + None => Vec::new(), + } +} + +struct Instance(*mut IWbemLocator); + +impl Drop for Instance { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +struct ServerConnection(*mut IWbemServices); + +impl Drop for ServerConnection { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +struct Enumerator(*mut IEnumWbemClassObject); + +impl Drop for Enumerator { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +macro_rules! bstr { + ($($x:expr),*) => {{ + let x: &[u16] = &[$($x as u16),*, 0]; + SysAllocString(x.as_ptr()) + }} +} + +struct Connection { + instance: Option, + server_connection: Option, + enumerator: Option, +} + +impl Connection { + fn new() -> Option { + // "Funnily", this function returns ok, false or "this function has already been called". + // So whatever, let's just ignore whatever it might return then! + unsafe { CoInitializeEx(null_mut(), 0) }; + Some(Connection { + instance: None, + server_connection: None, + enumerator: None, + }) + } + + fn initialize_security(self) -> Option { + if FAILED(unsafe { + CoInitializeSecurity( + null_mut(), + -1, + null_mut(), + null_mut(), + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + null_mut(), + EOAC_NONE, + null_mut(), + ) + }) { + None + } else { + Some(self) + } + } + + fn create_instance(mut self) -> Option { + let mut p_loc = null_mut(); + + if FAILED(unsafe { + CoCreateInstance( + &CLSID_WbemLocator as *const _, + null_mut(), + CLSCTX_INPROC_SERVER, + &IID_IWbemLocator as *const _, + &mut p_loc as *mut _ as *mut _, + ) + }) { + None + } else { + self.instance = Some(Instance(p_loc)); + Some(self) + } + } + + fn connect_server(mut self) -> Option { + let mut p_svc = null_mut(); + + if let Some(ref instance) = self.instance { + unsafe { + // "root\WMI" + let s = bstr!('r', 'o', 'o', 't', '\\', 'W', 'M', 'I'); + let res = (*instance.0).ConnectServer( + s, + null_mut(), + null_mut(), + null_mut(), + 0, + null_mut(), + null_mut(), + &mut p_svc as *mut _, + ); + SysFreeString(s); + if FAILED(res) { + return None; + } + } + } else { + return None; + } + self.server_connection = Some(ServerConnection(p_svc)); + Some(self) + } + + fn set_proxy_blanket(self) -> Option { + if let Some(ref server_connection) = self.server_connection { + unsafe { + if FAILED(CoSetProxyBlanket( + server_connection.0 as *mut _, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + null_mut(), + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + null_mut(), + EOAC_NONE, + )) { + return None; + } + } + } else { + return None; + } + Some(self) + } + + fn exec_query(mut self) -> Option { + let mut p_enumerator = null_mut(); + + if let Some(ref server_connection) = self.server_connection { + unsafe { + // "WQL" + let s = bstr!('W', 'Q', 'L'); // query kind + // "SELECT * FROM MSAcpi_ThermalZoneTemperature" + let query = bstr!( + 'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'M', 'S', + 'A', 'c', 'p', 'i', '_', 'T', 'h', 'e', 'r', 'm', 'a', 'l', 'Z', 'o', 'n', 'e', + 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', 'r', 'e' + ); + let hres = (*server_connection.0).ExecQuery( + s, + query, + (WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY) as _, + null_mut(), + &mut p_enumerator as *mut _, + ); + SysFreeString(s); + SysFreeString(query); + if FAILED(hres) { + return None; + } + } + } else { + return None; + } + self.enumerator = Some(Enumerator(p_enumerator)); + Some(self) + } + + fn get_temperature(&mut self, get_critical: bool) -> Option<(f32, Option)> { + let p_enum = match self.enumerator.take() { + Some(x) => x, + None => { + return None; + } + }; + let mut p_obj: *mut IWbemClassObject = null_mut(); + let mut nb_returned = 0; + + unsafe { + use winapi::um::wbemcli::WBEM_INFINITE; + (*p_enum.0).Next( + WBEM_INFINITE as _, // Time out + 1, // One object + &mut p_obj as *mut _, + &mut nb_returned, + ); + }; + + if nb_returned == 0 { + return None; // not enough rights I suppose... + } + + unsafe { + (*p_obj).BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY as _); + + let mut p_val: VARIANT = ::std::mem::MaybeUninit::uninit().assume_init(); + // "CurrentTemperature" + let temp = bstr!( + 'C', 'u', 'r', 'r', 'e', 'n', 't', 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', + 'r', 'e' + ); + let res = (*p_obj).Get(temp, 0, &mut p_val, null_mut(), null_mut()); + + SysFreeString(temp); + VariantClear(&mut p_val as *mut _ as *mut _); + + let temp = if SUCCEEDED(res) { + // temperature is given in tenth of degrees Kelvin + (p_val.n1.decVal().Lo64 / 10) as f32 - 273.15 + } else { + (*p_obj).Release(); + return None; + }; + + let mut critical = None; + if get_critical { + // "CriticalPoint" + let crit = bstr!( + 'C', 'r', 'i', 't', 'i', 'c', 'a', 'l', 'T', 'r', 'i', 'p', 'P', 'o', 'i', 'n', + 't' + ); + let res = (*p_obj).Get(crit, 0, &mut p_val, null_mut(), null_mut()); + + SysFreeString(crit); + VariantClear(&mut p_val as *mut _ as *mut _); + + if SUCCEEDED(res) { + // temperature is given in tenth of degrees Kelvin + critical = Some((p_val.n1.decVal().Lo64 / 10) as f32 - 273.15); + } + } + (*p_obj).Release(); + Some((temp, critical)) + } + } +} + +impl Drop for Connection { + fn drop(&mut self) { + // Those three calls are here to enforce that they get dropped in the good order. + self.enumerator.take(); + self.server_connection.take(); + self.instance.take(); + unsafe { + CoUninitialize(); + } + } } diff --git a/src/windows/disk.rs b/src/windows/disk.rs index 2e4cf3da2..deaf8e9cd 100644 --- a/src/windows/disk.rs +++ b/src/windows/disk.rs @@ -10,31 +10,11 @@ use std::path::Path; use std::str; use DiskExt; +use DiskType; use winapi::um::fileapi::GetDiskFreeSpaceExW; use winapi::um::winnt::ULARGE_INTEGER; -/// Enum containing the different handled disks types. -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum DiskType { - /// HDD type. - HDD, - /// SSD type. - SSD, - /// Unknown type. - Unknown(isize), -} - -impl From for DiskType { - fn from(t: isize) -> DiskType { - match t { - 0 => DiskType::HDD, - 1 => DiskType::SSD, - id => DiskType::Unknown(id), - } - } -} - pub fn new_disk( name: &OsStr, mount_point: &[u16], @@ -51,7 +31,7 @@ pub fn new_disk( total_space: total_space, available_space: 0, }; - d.update(); + d.refresh(); d } @@ -106,7 +86,7 @@ impl DiskExt for Disk { self.available_space } - fn update(&mut self) -> bool { + fn refresh(&mut self) -> bool { if self.total_space != 0 { unsafe { let mut tmp: ULARGE_INTEGER = ::std::mem::zeroed(); diff --git a/src/windows/ffi.rs b/src/windows/ffi.rs index b01faccad..e57c67515 100644 --- a/src/windows/ffi.rs +++ b/src/windows/ffi.rs @@ -1,333 +1,220 @@ -// +// // Sysinfo -// -// Copyright (c) 2015 Guillaume Gomez +// +// Copyright (c) 2020 Guillaume Gomez // -use libc::{c_int, c_char, c_void, c_uchar, c_uint, size_t, uint32_t, uint64_t, c_ushort}; - -extern "C" { - pub static kCFAllocatorDefault: CFAllocatorRef; - - pub fn proc_pidinfo(pid: c_int, flavor: c_int, arg: u64, buffer: *mut c_void, - buffersize: c_int) -> c_int; - pub fn proc_listallpids(buffer: *mut c_void, buffersize: c_int) -> c_int; - //pub fn proc_name(pid: c_int, buffer: *mut c_void, buffersize: u32) -> c_int; - //pub fn proc_regionfilename(pid: c_int, address: u64, buffer: *mut c_void, - // buffersize: u32) -> c_int; - //pub fn proc_pidpath(pid: c_int, buffer: *mut c_void, buffersize: u32) -> c_int; - - pub fn IOMasterPort(a: i32, b: *mut mach_port_t) -> i32; - pub fn IOServiceMatching(a: *const c_char) -> *mut c_void; - pub fn IOServiceGetMatchingServices(a: mach_port_t, b: *mut c_void, c: *mut io_iterator_t) -> i32; - pub fn IOIteratorNext(iterator: io_iterator_t) -> io_object_t; - pub fn IOObjectRelease(obj: io_object_t) -> i32; - pub fn IOServiceOpen(device: io_object_t, a: u32, t: u32, x: *mut io_connect_t) -> i32; - pub fn IOServiceClose(a: io_connect_t) -> i32; - pub fn IOConnectCallStructMethod(connection: mach_port_t, - selector: u32, - inputStruct: *mut KeyData_t, - inputStructCnt: size_t, - outputStruct: *mut KeyData_t, - outputStructCnt: *mut size_t) -> i32; - pub fn IORegistryEntryCreateCFProperties(entry: io_registry_entry_t, - properties: *mut CFMutableDictionaryRef, - allocator: CFAllocatorRef, - options: IOOptionBits) - -> kern_return_t; - pub fn CFDictionaryContainsKey(d: CFDictionaryRef, key: *const c_void) -> Boolean; - pub fn CFDictionaryGetValue(d: CFDictionaryRef, key: *const c_void) -> *const c_void; - pub fn IORegistryEntryGetName(entry: io_registry_entry_t, name: *mut c_char) -> kern_return_t; - pub fn CFRelease(cf: CFTypeRef); - pub fn CFStringCreateWithCStringNoCopy(alloc: *mut c_void, cStr: *const c_char, - encoding: CFStringEncoding, - contentsDeallocator: *mut c_void) -> CFStringRef; - - pub static kCFAllocatorNull: CFAllocatorRef; - - pub fn mach_absolute_time() -> u64; - //pub fn task_for_pid(host: u32, pid: pid_t, task: *mut task_t) -> u32; - pub fn mach_task_self() -> u32; - pub fn mach_host_self() -> u32; - //pub fn task_info(host_info: u32, t: u32, c: *mut c_void, x: *mut u32) -> u32; - pub fn host_statistics64(host_info: u32, x: u32, y: *mut c_void, z: *const u32) -> u32; - pub fn host_processor_info(host_info: u32, t: u32, num_cpu_u: *mut u32, - cpu_info: *mut *mut i32, num_cpu_info: *mut u32) -> u32; - //pub fn host_statistics(host_priv: u32, flavor: u32, host_info: *mut c_void, - // host_count: *const u32) -> u32; - pub fn vm_deallocate(target_task: u32, address: *mut i32, size: u32) -> u32; -} - -// TODO: waiting for https://github.com/rust-lang/libc/pull/678 -macro_rules! cfg_if { - ($( - if #[cfg($($meta:meta),*)] { $($it:item)* } - ) else * else { - $($it2:item)* - }) => { - __cfg_if_items! { - () ; - $( ( ($($meta),*) ($($it)*) ), )* - ( () ($($it2)*) ), - } - } -} - -// TODO: waiting for https://github.com/rust-lang/libc/pull/678 -macro_rules! __cfg_if_items { - (($($not:meta,)*) ; ) => {}; - (($($not:meta,)*) ; ( ($($m:meta),*) ($($it:item)*) ), $($rest:tt)*) => { - __cfg_if_apply! { cfg(all(not(any($($not),*)), $($m,)*)), $($it)* } - __cfg_if_items! { ($($not,)* $($m,)*) ; $($rest)* } - } -} - -// TODO: waiting for https://github.com/rust-lang/libc/pull/678 -macro_rules! __cfg_if_apply { - ($m:meta, $($it:item)*) => { - $(#[$m] $it)* - } -} - -// TODO: waiting for https://github.com/rust-lang/libc/pull/678 -cfg_if! { - if #[cfg(any(target_arch = "arm", target_arch = "x86"))] { - pub type timeval32 = ::libc::timeval; - } else { - use libc::timeval32; +// TO BE REMOVED ONCE https://github.com/retep998/winapi-rs/pull/802 IS MERGED!!! + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(dead_code)] + +use winapi::shared::basetsd::ULONG64; +use winapi::shared::guiddef::GUID; +use winapi::shared::ifdef::{NET_IFINDEX, NET_LUID}; +use winapi::shared::minwindef::BYTE; +use winapi::shared::netioapi::NETIOAPI_API; +use winapi::shared::ntdef::{PVOID, UCHAR, ULONG, WCHAR}; +use winapi::{ENUM, STRUCT}; + +const ANY_SIZE: usize = 1; + +pub const IF_MAX_STRING_SIZE: usize = 256; +pub const IF_MAX_PHYS_ADDRESS_LENGTH: usize = 32; + +pub type NET_IF_NETWORK_GUID = GUID; +pub type PMIB_IF_TABLE2 = *mut MIB_IF_TABLE2; +pub type PMIB_IF_ROW2 = *mut MIB_IF_ROW2; + +macro_rules! BITFIELD { + ($base:ident $field:ident: $fieldtype:ty [ + $($thing:ident $set_thing:ident[$r:expr],)+ + ]) => { + impl $base {$( + #[inline] + pub fn $thing(&self) -> $fieldtype { + let size = ::std::mem::size_of::<$fieldtype>() * 8; + self.$field << (size - $r.end) >> (size - $r.end + $r.start) + } + #[inline] + pub fn $set_thing(&mut self, val: $fieldtype) { + let mask = ((1 << ($r.end - $r.start)) - 1) << $r.start; + self.$field &= !mask; + self.$field |= (val << $r.start) & mask; + } + )+} } } -// TODO: waiting for https://github.com/rust-lang/libc/pull/678 -#[repr(C)] -pub struct if_data64 { - pub ifi_type: c_uchar, - pub ifi_typelen: c_uchar, - pub ifi_physical: c_uchar, - pub ifi_addrlen: c_uchar, - pub ifi_hdrlen: c_uchar, - pub ifi_recvquota: c_uchar, - pub ifi_xmitquota: c_uchar, - pub ifi_unused1: c_uchar, - pub ifi_mtu: uint32_t, - pub ifi_metric: uint32_t, - pub ifi_baudrate: uint64_t, - pub ifi_ipackets: uint64_t, - pub ifi_ierrors: uint64_t, - pub ifi_opackets: uint64_t, - pub ifi_oerrors: uint64_t, - pub ifi_collisions: uint64_t, - pub ifi_ibytes: uint64_t, - pub ifi_obytes: uint64_t, - pub ifi_imcasts: uint64_t, - pub ifi_omcasts: uint64_t, - pub ifi_iqdrops: uint64_t, - pub ifi_noproto: uint64_t, - pub ifi_recvtiming: uint32_t, - pub ifi_xmittiming: uint32_t, - pub ifi_lastchange: timeval32, -} - -// TODO: waiting for https://github.com/rust-lang/libc/pull/678 -#[repr(C)] -pub struct if_msghdr2 { - pub ifm_msglen: c_ushort, - pub ifm_version: c_uchar, - pub ifm_type: c_uchar, - pub ifm_addrs: c_int, - pub ifm_flags: c_int, - pub ifm_index: c_ushort, - pub ifm_snd_len: c_int, - pub ifm_snd_maxlen: c_int, - pub ifm_snd_drops: c_int, - pub ifm_timer: c_int, - pub ifm_data: if_data64, -} - -#[repr(C)] -pub struct __CFAllocator { - __private: c_void, +STRUCT! {struct MIB_IF_TABLE2 { + NumEntries: ULONG, + Table: [MIB_IF_ROW2; ANY_SIZE], +}} + +ENUM! {enum NDIS_MEDIUM { + NdisMedium802_3 = 0, + NdisMedium802_5 = 1, + NdisMediumFddi = 2, + NdisMediumWan = 3, + NdisMediumLocalTalk = 4, + NdisMediumDix = 5, // defined for convenience, not a real medium + NdisMediumArcnetRaw = 6, + NdisMediumArcnet878_2 = 7, + NdisMediumAtm = 8, + NdisMediumWirelessWan = 9, + NdisMediumIrda = 10, + NdisMediumBpc = 11, + NdisMediumCoWan = 12, + NdisMedium1394 = 13, + NdisMediumInfiniBand = 14, + NdisMediumTunnel = 15, + NdisMediumNative802_11 = 16, + NdisMediumLoopback = 17, + NdisMediumWiMAX = 18, + NdisMediumIP = 19, + NdisMediumMax = 20, // Not a real medium, defined as an upper-bound +}} + +ENUM! {enum TUNNEL_TYPE { + TUNNEL_TYPE_NONE = 0, + TUNNEL_TYPE_OTHER = 1, + TUNNEL_TYPE_DIRECT = 2, + TUNNEL_TYPE_6TO4 = 11, + TUNNEL_TYPE_ISATAP = 13, + TUNNEL_TYPE_TEREDO = 14, + TUNNEL_TYPE_IPHTTPS = 15, +}} + +ENUM! {enum NDIS_PHYSICAL_MEDIUM { + NdisPhysicalMediumUnspecified = 0, + NdisPhysicalMediumWirelessLan = 1, + NdisPhysicalMediumCableModem = 2, + NdisPhysicalMediumPhoneLine = 3, + NdisPhysicalMediumPowerLine = 4, + NdisPhysicalMediumDSL = 5, // includes ADSL and UADSL (G.Lite) + NdisPhysicalMediumFibreChannel = 6, + NdisPhysicalMedium1394 = 7, + NdisPhysicalMediumWirelessWan = 8, + NdisPhysicalMediumNative802_11 = 9, + NdisPhysicalMediumBluetooth = 10, + NdisPhysicalMediumInfiniband = 11, + NdisPhysicalMediumWiMax = 12, + NdisPhysicalMediumUWB = 13, + NdisPhysicalMedium802_3 = 14, + NdisPhysicalMedium802_5 = 15, + NdisPhysicalMediumIrda = 16, + NdisPhysicalMediumWiredWAN = 17, + NdisPhysicalMediumWiredCoWan = 18, + NdisPhysicalMediumOther = 19, + NdisPhysicalMediumMax = 20, // Not a real physical type, defined as an upper-bound +}} + +ENUM! {enum NET_IF_ACCESS_TYPE { + NET_IF_ACCESS_LOOPBACK = 1, + NET_IF_ACCESS_BROADCAST = 2, + NET_IF_ACCESS_POINT_TO_POINT = 3, + NET_IF_ACCESS_POINT_TO_MULTI_POINT = 4, + NET_IF_ACCESS_MAXIMUM = 5, +}} + +ENUM! {enum NET_IF_DIRECTION_TYPE { + NET_IF_DIRECTION_SENDRECEIVE = 0, + NET_IF_DIRECTION_SENDONLY = 1, + NET_IF_DIRECTION_RECEIVEONLY = 2, + NET_IF_DIRECTION_MAXIMUM = 3, +}} + +ENUM! {enum IF_OPER_STATUS { + IfOperStatusUp = 1, + IfOperStatusDown = 2, + IfOperStatusTesting = 3, + IfOperStatusUnknown = 4, + IfOperStatusDormant = 5, + IfOperStatusNotPresent = 6, + IfOperStatusLowerLayerDown = 7, +}} + +ENUM! {enum NET_IF_ADMIN_STATUS { + NET_IF_ADMIN_STATUS_UP = 1, + NET_IF_ADMIN_STATUS_DOWN = 2, + NET_IF_ADMIN_STATUS_TESTING = 3, +}} + +ENUM! {enum NET_IF_MEDIA_CONNECT_STATE { + MediaConnectStateUnknown = 0, + MediaConnectStateConnected = 1, + MediaConnectStateDisconnected = 2, +}} + +ENUM! {enum NET_IF_CONNECTION_TYPE { + NET_IF_CONNECTION_DEDICATED = 1, + NET_IF_CONNECTION_PASSIVE = 2, + NET_IF_CONNECTION_DEMAND = 3, + NET_IF_CONNECTION_MAXIMUM = 4, +}} + +STRUCT! {struct MIB_IF_ROW2_InterfaceAndOperStatusFlags { + bitfield: BYTE, +}} +BITFIELD! {MIB_IF_ROW2_InterfaceAndOperStatusFlags bitfield: BYTE [ + HardwareInterface set_HardwareInterface[0..1], + FilterInterface set_FilterInterface[1..2], + ConnectorPresent set_ConnectorPresent[2..3], + NotAuthenticated set_NotAuthenticated[3..4], + NotMediaConnected set_NotMediaConnected[4..5], + Paused set_Paused[5..6], + LowPower set_LowPower[6..7], + EndPointInterface set_EndPointInterface[7..8], +]} + +STRUCT! {struct MIB_IF_ROW2 { + InterfaceLuid: NET_LUID, + InterfaceIndex: NET_IFINDEX, + InterfaceGuid: GUID, + Alias: [WCHAR; IF_MAX_STRING_SIZE + 1], + Description: [WCHAR; IF_MAX_STRING_SIZE + 1], + PhysicalAddressLength: ULONG, + PhysicalAddress: [UCHAR; IF_MAX_PHYS_ADDRESS_LENGTH], + PermanentPhysicalAddress: [UCHAR; IF_MAX_PHYS_ADDRESS_LENGTH], + Mtu: ULONG, + Type: ULONG, // Interface Type. + TunnelType: TUNNEL_TYPE, // Tunnel Type, if Type = IF_TUNNEL. + MediaType: NDIS_MEDIUM, + PhysicalMediumType: NDIS_PHYSICAL_MEDIUM, + AccessType: NET_IF_ACCESS_TYPE, + DirectionType: NET_IF_DIRECTION_TYPE, + InterfaceAndOperStatusFlags: MIB_IF_ROW2_InterfaceAndOperStatusFlags, + OperStatus: IF_OPER_STATUS, + AdminStatus: NET_IF_ADMIN_STATUS, + MediaConnectState: NET_IF_MEDIA_CONNECT_STATE, + NetworkGuid: NET_IF_NETWORK_GUID, + ConnectionType: NET_IF_CONNECTION_TYPE, + TransmitLinkSpeed: ULONG64, + ReceiveLinkSpeed: ULONG64, + InOctets: ULONG64, + InUcastPkts: ULONG64, + InNUcastPkts: ULONG64, + InDiscards: ULONG64, + InErrors: ULONG64, + InUnknownProtos: ULONG64, + InUcastOctets: ULONG64, + InMulticastOctets: ULONG64, + InBroadcastOctets: ULONG64, + OutOctets: ULONG64, + OutUcastPkts: ULONG64, + OutNUcastPkts: ULONG64, + OutDiscards: ULONG64, + OutErrors: ULONG64, + OutUcastOctets: ULONG64, + OutMulticastOctets: ULONG64, + OutBroadcastOctets: ULONG64, + OutQLen: ULONG64, +}} + +extern "system" { + pub fn GetIfTable2(Table: *mut PMIB_IF_TABLE2) -> NETIOAPI_API; + pub fn GetIfEntry2(Row: PMIB_IF_ROW2) -> NETIOAPI_API; + pub fn FreeMibTable(Memory: PVOID); } - -#[repr(C)] -pub struct __CFDictionary { - __private: c_void, -} - -#[repr(C)] -pub struct __CFString { - __private: c_void, -} - -pub type CFAllocatorRef = *const __CFAllocator; -pub type CFMutableDictionaryRef = *mut __CFDictionary; -pub type CFDictionaryRef = *const __CFDictionary; -#[allow(non_camel_case_types)] -pub type io_name_t = [u8; 128]; -#[allow(non_camel_case_types)] -pub type io_registry_entry_t = io_object_t; -pub type CFTypeRef = *const c_void; -pub type CFStringRef = *const __CFString; - -//#[allow(non_camel_case_types)] -//pub type policy_t = i32; -#[allow(non_camel_case_types)] -//pub type integer_t = i32; -//#[allow(non_camel_case_types)] -//pub type time_t = i64; -//#[allow(non_camel_case_types)] -//pub type suseconds_t = i32; -//#[allow(non_camel_case_types)] -//pub type mach_vm_size_t = u64; -//#[allow(non_camel_case_types)] -//pub type task_t = u32; -//#[allow(non_camel_case_types)] -//pub type pid_t = i32; -#[allow(non_camel_case_types)] -pub type natural_t = u32; -#[allow(non_camel_case_types)] -pub type mach_port_t = u32; -#[allow(non_camel_case_types)] -pub type io_object_t = mach_port_t; -#[allow(non_camel_case_types)] -pub type io_iterator_t = io_object_t; -#[allow(non_camel_case_types)] -pub type io_connect_t = io_object_t; -#[allow(non_camel_case_types)] -pub type boolean_t = c_uint; -#[allow(non_camel_case_types)] -pub type kern_return_t = c_int; -pub type Boolean = c_uchar; -pub type IOOptionBits = u32; -pub type CFStringEncoding = u32; - -/*#[repr(C)] -pub struct task_thread_times_info { - pub user_time: time_value, - pub system_time: time_value, -}*/ - -/*#[repr(C)] -pub struct task_basic_info_64 { - pub suspend_count: integer_t, - pub virtual_size: mach_vm_size_t, - pub resident_size: mach_vm_size_t, - pub user_time: time_value_t, - pub system_time: time_value_t, - pub policy: policy_t, -}*/ - -#[repr(C)] -pub struct vm_statistics64 { - pub free_count: natural_t, - pub active_count: natural_t, - pub inactive_count: natural_t, - pub wire_count: natural_t, - pub zero_fill_count: u64, - pub reactivations: u64, - pub pageins: u64, - pub pageouts: u64, - pub faults: u64, - pub cow_faults: u64, - pub lookups: u64, - pub hits: u64, - pub purges: u64, - pub purgeable_count: natural_t, - pub speculative_count: natural_t, - pub decompressions: u64, - pub compressions: u64, - pub swapins: u64, - pub swapouts: u64, - pub compressor_page_count: natural_t, - pub throttled_count: natural_t, - pub external_page_count: natural_t, - pub internal_page_count: natural_t, - pub total_uncompressed_pages_in_compressor: u64, -} - -#[repr(C)] -pub struct Val_t { - pub key: [i8; 5], - pub data_size: u32, - pub data_type: [i8; 5], // UInt32Char_t - pub bytes: [i8; 32], // SMCBytes_t -} - -#[repr(C)] -pub struct KeyData_vers_t { - pub major: u8, - pub minor: u8, - pub build: u8, - pub reserved: [u8; 1], - pub release: u16, -} - -#[repr(C)] -pub struct KeyData_pLimitData_t { - pub version: u16, - pub length: u16, - pub cpu_plimit: u32, - pub gpu_plimit: u32, - pub mem_plimit: u32, -} - -#[repr(C)] -pub struct KeyData_keyInfo_t { - pub data_size: u32, - pub data_type: u32, - pub data_attributes: u8, -} - -#[repr(C)] -pub struct KeyData_t { - pub key: u32, - pub vers: KeyData_vers_t, - pub p_limit_data: KeyData_pLimitData_t, - pub key_info: KeyData_keyInfo_t, - pub result: u8, - pub status: u8, - pub data8: u8, - pub data32: u32, - pub bytes: [i8; 32], // SMCBytes_t -} - -#[repr(C)] -pub struct xsw_usage { - pub xsu_total: u64, - pub xsu_avail: u64, - pub xsu_used: u64, - pub xsu_pagesize: u32, - pub xsu_encrypted: boolean_t, -} - -//pub const HOST_CPU_LOAD_INFO_COUNT: usize = 4; -//pub const HOST_CPU_LOAD_INFO: u32 = 3; -pub const KERN_SUCCESS: u32 = 0; - -pub const HW_NCPU: u32 = 3; -pub const CTL_HW: u32 = 6; -pub const CTL_VM: u32 = 2; -pub const VM_SWAPUSAGE: u32 = 5; -pub const PROCESSOR_CPU_LOAD_INFO: u32 = 2; -pub const CPU_STATE_USER: u32 = 0; -pub const CPU_STATE_SYSTEM: u32 = 1; -pub const CPU_STATE_IDLE: u32 = 2; -pub const CPU_STATE_NICE: u32 = 3; -pub const CPU_STATE_MAX: usize = 4; -pub const HW_MEMSIZE: u32 = 24; - -//pub const TASK_THREAD_TIMES_INFO: u32 = 3; -//pub const TASK_THREAD_TIMES_INFO_COUNT: u32 = 4; -//pub const TASK_BASIC_INFO_64: u32 = 5; -//pub const TASK_BASIC_INFO_64_COUNT: u32 = 10; -pub const HOST_VM_INFO64: u32 = 4; -pub const HOST_VM_INFO64_COUNT: u32 = 38; - -pub const MACH_PORT_NULL: i32 = 0; -pub const KERNEL_INDEX_SMC: i32 = 2; -pub const SMC_CMD_READ_KEYINFO: u8 = 9; -pub const SMC_CMD_READ_BYTES: u8 = 5; - -pub const KIO_RETURN_SUCCESS: i32 = 0; -#[allow(non_upper_case_globals)] -pub const kCFStringEncodingMacRoman: CFStringEncoding = 0; diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 95e5ded57..e48042562 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -4,24 +4,8 @@ // Copyright (c) 2015 Guillaume Gomez // -/*pub mod component; -pub mod disk; -mod ffi; -pub mod network; -pub mod process; -pub mod processor; -pub mod system; - -pub use self::component::Component; -pub use self::disk::{Disk, DiskType}; -pub use self::network::NetworkData; -pub use self::process::{Process,ProcessStatus}; -pub use self::processor::Processor; -pub use self::system::System;*/ - mod component; mod disk; -//mod ffi; #[macro_use] mod macros; mod network; @@ -30,9 +14,11 @@ mod processor; mod system; mod tools; +mod ffi; + pub use self::component::Component; -pub use self::disk::{Disk, DiskType}; -pub use self::network::NetworkData; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; pub use self::process::{Process, ProcessStatus}; pub use self::processor::Processor; pub use self::system::System; diff --git a/src/windows/network.rs b/src/windows/network.rs index 36ac737d2..574c0bb90 100644 --- a/src/windows/network.rs +++ b/src/windows/network.rs @@ -4,54 +4,243 @@ // Copyright (c) 2017 Guillaume Gomez // -use windows::processor::Query; -use windows::tools::KeyHandler; +use std::collections::{HashMap, HashSet}; + +use windows::ffi::{self, MIB_IF_ROW2, PMIB_IF_TABLE2}; use NetworkExt; +use NetworksExt; +use NetworksIter; + +use winapi::shared::ifdef::NET_LUID; +use winapi::shared::winerror::NO_ERROR; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $new_val; + }}; +} + +/// Network interfaces. +/// +/// ```no_run +/// use sysinfo::{NetworksExt, System, SystemExt}; +/// +/// let s = System::new_all(); +/// let networks = s.get_networks(); +/// ``` +pub struct Networks { + interfaces: HashMap, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + let mut table: PMIB_IF_TABLE2 = ::std::ptr::null_mut(); + if unsafe { ffi::GetIfTable2(&mut table) } != NO_ERROR { + return; + } + let mut to_be_removed = HashSet::with_capacity(self.interfaces.len()); + + for key in self.interfaces.keys() { + to_be_removed.insert(key.clone()); + } + // In here, this is tricky: we have to filter out the software interfaces to only keep + // the hardware ones. To do so, we first check the connection potential speed (if 0, not + // interesting), then we check its state: if not open, not interesting either. And finally, + // we count the members of a same group: if there is more than 1, then it's software level. + let mut groups = HashMap::new(); + let mut indexes = Vec::new(); + let ptr = unsafe { (*table).Table.as_ptr() }; + for i in 0..unsafe { *table }.NumEntries { + let ptr = unsafe { &*ptr.offset(i as _) }; + if (ptr.TransmitLinkSpeed == 0 && ptr.ReceiveLinkSpeed == 0) + || ptr.MediaConnectState == ffi::MediaConnectStateDisconnected + || ptr.PhysicalAddressLength == 0 + { + continue; + } + let id = vec![ + ptr.InterfaceGuid.Data2, + ptr.InterfaceGuid.Data3, + ptr.InterfaceGuid.Data4[0] as _, + ptr.InterfaceGuid.Data4[1] as _, + ptr.InterfaceGuid.Data4[2] as _, + ptr.InterfaceGuid.Data4[3] as _, + ptr.InterfaceGuid.Data4[4] as _, + ptr.InterfaceGuid.Data4[5] as _, + ptr.InterfaceGuid.Data4[6] as _, + ptr.InterfaceGuid.Data4[7] as _, + ]; + let entry = groups.entry(id.clone()).or_insert(0); + *entry += 1; + if *entry > 1 { + continue; + } + indexes.push((i, id)); + } + for (i, id) in indexes { + let ptr = unsafe { &*ptr.offset(i as _) }; + if *groups.get(&id).unwrap_or(&0) > 1 { + continue; + } + let mut pos = 0; + for x in ptr.Alias.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let interface_name = match String::from_utf16(&ptr.Alias[..pos]) { + Ok(s) => s, + _ => continue, + }; + to_be_removed.remove(&interface_name); + let mut interface = + self.interfaces + .entry(interface_name) + .or_insert_with(|| NetworkData { + id: ptr.InterfaceLuid, + current_out: ptr.OutOctets, + old_out: 0, + current_in: ptr.InOctets, + old_in: 0, + packets_in: ptr.InUcastPkts + ptr.InNUcastPkts, + old_packets_in: 0, + packets_out: ptr.OutUcastPkts + ptr.OutNUcastPkts, + old_packets_out: 0, + errors_in: ptr.InErrors, + old_errors_in: 0, + errors_out: ptr.OutErrors, + old_errors_out: 0, + }); + old_and_new!(interface, current_out, old_out, ptr.OutOctets); + old_and_new!(interface, current_in, old_in, ptr.InOctets); + old_and_new!( + interface, + packets_in, + old_packets_in, + ptr.InUcastPkts + ptr.InNUcastPkts + ); + old_and_new!( + interface, + packets_out, + old_packets_out, + ptr.OutUcastPkts + ptr.OutNUcastPkts + ); + old_and_new!(interface, errors_in, old_errors_in, ptr.InErrors); + old_and_new!(interface, errors_out, old_errors_out, ptr.OutErrors); + } + unsafe { + ffi::FreeMibTable(table as _); + } + for key in to_be_removed { + self.interfaces.remove(&key); + } + } + + fn refresh(&mut self) { + let mut entry: MIB_IF_ROW2 = unsafe { ::std::mem::MaybeUninit::uninit().assume_init() }; + for (_, interface) in self.interfaces.iter_mut() { + entry.InterfaceLuid = interface.id; + entry.InterfaceIndex = 0; // to prevent the function to pick this one as index + if unsafe { ffi::GetIfEntry2(&mut entry) } != NO_ERROR { + continue; + } + old_and_new!(interface, current_out, old_out, entry.OutOctets); + old_and_new!(interface, current_in, old_in, entry.InOctets); + old_and_new!( + interface, + packets_in, + old_packets_in, + entry.InUcastPkts + entry.InNUcastPkts + ); + old_and_new!( + interface, + packets_out, + old_packets_out, + entry.OutUcastPkts + entry.OutNUcastPkts + ); + old_and_new!(interface, errors_in, old_errors_in, entry.InErrors); + old_and_new!(interface, errors_out, old_errors_out, entry.OutErrors); + } + } +} /// Contains network information. pub struct NetworkData { + id: NET_LUID, current_out: u64, + old_out: u64, current_in: u64, - keys_in: Vec, - keys_out: Vec, + old_in: u64, + packets_in: u64, + old_packets_in: u64, + packets_out: u64, + old_packets_out: u64, + errors_in: u64, + old_errors_in: u64, + errors_out: u64, + old_errors_out: u64, } impl NetworkExt for NetworkData { fn get_income(&self) -> u64 { + self.current_in - self.old_in + } + + fn get_total_income(&self) -> u64 { self.current_in } fn get_outcome(&self) -> u64 { + self.current_out - self.old_out + } + + fn get_total_outcome(&self) -> u64 { self.current_out } -} -pub fn new() -> NetworkData { - NetworkData { - current_in: 0, - current_out: 0, - keys_in: Vec::new(), - keys_out: Vec::new(), + fn get_packets_income(&self) -> u64 { + self.packets_in - self.old_packets_in } -} -pub fn refresh(network: &mut NetworkData, query: &Option) { - if let &Some(ref query) = query { - network.current_in = 0; - for key in &network.keys_in { - network.current_in += query.get_u64(&key.unique_id).expect("key disappeared"); - } - network.current_out = 0; - for key in &network.keys_out { - network.current_out += query.get_u64(&key.unique_id).expect("key disappeared"); - } + fn get_total_packets_income(&self) -> u64 { + self.packets_in } -} -pub fn get_keys_in(network: &mut NetworkData) -> &mut Vec { - &mut network.keys_in -} + fn get_packets_outcome(&self) -> u64 { + self.packets_out - self.old_packets_out + } + + fn get_total_packets_outcome(&self) -> u64 { + self.packets_out + } + + fn get_errors_income(&self) -> u64 { + self.errors_in - self.old_errors_in + } -pub fn get_keys_out(network: &mut NetworkData) -> &mut Vec { - &mut network.keys_out + fn get_total_errors_income(&self) -> u64 { + self.errors_in + } + + fn get_errors_outcome(&self) -> u64 { + self.errors_out - self.old_errors_out + } + + fn get_total_errors_outcome(&self) -> u64 { + self.errors_out + } } diff --git a/src/windows/process.rs b/src/windows/process.rs index 80cc3d1e9..84ecc89ae 100644 --- a/src/windows/process.rs +++ b/src/windows/process.rs @@ -5,9 +5,11 @@ // use std::fmt::{self, Debug, Formatter}; -use std::mem::{size_of, zeroed}; +use std::mem::{size_of, zeroed, MaybeUninit}; use std::ops::Deref; use std::path::{Path, PathBuf}; +use std::process; +use std::ptr::null_mut; use std::str; use libc::{c_uint, c_void, memcpy}; @@ -15,7 +17,10 @@ use libc::{c_uint, c_void, memcpy}; use Pid; use ProcessExt; -use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, MAX_PATH /*, TRUE, USHORT*/}; +use ntapi::ntpsapi::{ + NtQueryInformationProcess, ProcessBasicInformation, PROCESS_BASIC_INFORMATION, +}; +use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, MAX_PATH}; use winapi::um::handleapi::CloseHandle; use winapi::um::processthreadsapi::{GetProcessTimes, OpenProcess, TerminateProcess}; use winapi::um::psapi::{ @@ -23,13 +28,7 @@ use winapi::um::psapi::{ LIST_MODULES_ALL, PROCESS_MEMORY_COUNTERS, PROCESS_MEMORY_COUNTERS_EX, }; use winapi::um::sysinfoapi::GetSystemTimeAsFileTime; -use winapi::um::tlhelp32::{ - CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS, -}; -use winapi::um::winnt::{ - HANDLE, /*, PWSTR*/ PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE, PROCESS_VM_READ, - ULARGE_INTEGER, /*THREAD_GET_CONTEXT, THREAD_QUERY_INFORMATION, THREAD_SUSPEND_RESUME,*/ -}; +use winapi::um::winnt::{HANDLE, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, ULARGE_INTEGER}; /// Enum describing the different status of a process. #[derive(Clone, Copy, Debug)] @@ -57,16 +56,10 @@ fn get_process_handler(pid: Pid) -> Option { if pid == 0 { return None; } - let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE; + let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; let process_handler = unsafe { OpenProcess(options, FALSE, pid as DWORD) }; if process_handler.is_null() { - let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; - let process_handler = unsafe { OpenProcess(options, FALSE, pid as DWORD) }; - if process_handler.is_null() { - None - } else { - Some(process_handler) - } + None } else { Some(process_handler) } @@ -96,8 +89,8 @@ pub struct Process { environ: Vec, cwd: PathBuf, root: PathBuf, - memory: u64, - virtual_memory: u64, + pub(crate) memory: u64, + pub(crate) virtual_memory: u64, parent: Option, status: ProcessStatus, handle: HandleWrapper, @@ -106,6 +99,186 @@ pub struct Process { old_user_cpu: u64, start_time: u64, cpu_usage: f32, + pub(crate) updated: bool, +} + +unsafe fn get_process_name(process_handler: HANDLE, h_mod: *mut c_void) -> String { + let mut process_name = [0u16; MAX_PATH + 1]; + + GetModuleBaseNameW( + process_handler, + h_mod as _, + process_name.as_mut_ptr(), + MAX_PATH as DWORD + 1, + ); + let mut pos = 0; + for x in process_name.iter() { + if *x == 0 { + break; + } + pos += 1; + } + String::from_utf16_lossy(&process_name[..pos]) +} + +unsafe fn get_h_mod(process_handler: HANDLE, h_mod: &mut *mut c_void) -> bool { + let mut cb_needed = 0; + EnumProcessModulesEx( + process_handler, + h_mod as *mut *mut c_void as _, + size_of::() as DWORD, + &mut cb_needed, + LIST_MODULES_ALL, + ) != 0 +} + +unsafe fn get_exe(process_handler: HANDLE, h_mod: *mut c_void) -> PathBuf { + let mut exe_buf = [0u16; MAX_PATH + 1]; + GetModuleFileNameExW( + process_handler, + h_mod as _, + exe_buf.as_mut_ptr(), + MAX_PATH as DWORD + 1, + ); + + let mut pos = 0; + for x in exe_buf.iter() { + if *x == 0 { + break; + } + pos += 1; + } + + PathBuf::from(String::from_utf16_lossy(&exe_buf[..pos])) +} + +impl Process { + #[allow(clippy::uninit_assumed_init)] + pub(crate) fn new_from_pid(pid: Pid) -> Option { + let process_handler = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid as _) }; + if process_handler.is_null() { + return None; + } + let mut info: PROCESS_BASIC_INFORMATION = unsafe { MaybeUninit::uninit().assume_init() }; + if unsafe { + NtQueryInformationProcess( + process_handler, + ProcessBasicInformation, + &mut info as *mut _ as *mut _, + size_of::() as _, + null_mut(), + ) + } != 0 + { + unsafe { CloseHandle(process_handler) }; + return None; + } + Some(Process::new_with_handle( + pid, + if info.InheritedFromUniqueProcessId as usize != 0 { + Some(info.InheritedFromUniqueProcessId as usize) + } else { + None + }, + process_handler, + )) + } + + pub(crate) fn new_full( + pid: Pid, + parent: Option, + memory: u64, + virtual_memory: u64, + name: String, + ) -> Process { + if let Some(handle) = get_process_handler(pid) { + let mut h_mod = null_mut(); + unsafe { get_h_mod(handle, &mut h_mod) }; + let environ = unsafe { get_proc_env(handle, pid as u32, &name) }; + + let exe = unsafe { get_exe(handle, h_mod) }; + let mut root = exe.clone(); + root.pop(); + Process { + handle: HandleWrapper(handle), + name, + pid, + parent, + cmd: get_cmd_line(pid), + environ, + exe, + cwd: PathBuf::new(), + root, + status: ProcessStatus::Run, + memory, + virtual_memory, + cpu_usage: 0., + old_cpu: 0, + old_sys_cpu: 0, + old_user_cpu: 0, + start_time: unsafe { get_start_time(handle) }, + updated: true, + } + } else { + Process { + handle: HandleWrapper(null_mut()), + name, + pid, + parent, + cmd: get_cmd_line(pid), + environ: Vec::new(), + exe: get_executable_path(pid), + cwd: PathBuf::new(), + root: PathBuf::new(), + status: ProcessStatus::Run, + memory, + virtual_memory, + cpu_usage: 0., + old_cpu: 0, + old_sys_cpu: 0, + old_user_cpu: 0, + start_time: 0, + updated: true, + } + } + } + + fn new_with_handle(pid: Pid, parent: Option, process_handler: HANDLE) -> Process { + let mut h_mod = null_mut(); + + unsafe { + let name = if get_h_mod(process_handler, &mut h_mod) { + get_process_name(process_handler, h_mod) + } else { + String::new() + }; + let environ = get_proc_env(process_handler, pid as u32, &name); + + let exe = get_exe(process_handler, h_mod); + let mut root = exe.clone(); + root.pop(); + Process { + handle: HandleWrapper(process_handler), + name, + pid, + parent, + cmd: get_cmd_line(pid), + environ, + exe, + cwd: PathBuf::new(), + root, + status: ProcessStatus::Run, + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + old_cpu: 0, + old_sys_cpu: 0, + old_user_cpu: 0, + start_time: get_start_time(process_handler), + updated: true, + } + } + } } // TODO: it's possible to get environment variables like it's done in @@ -116,81 +289,13 @@ pub struct Process { impl ProcessExt for Process { fn new(pid: Pid, parent: Option, _: u64) -> Process { if let Some(process_handler) = get_process_handler(pid) { - let mut h_mod = ::std::ptr::null_mut(); - let mut process_name = [0u16; MAX_PATH + 1]; - let mut cb_needed = 0; - - unsafe { - if EnumProcessModulesEx( - process_handler, - &mut h_mod, - ::std::mem::size_of::() as DWORD, - &mut cb_needed, - LIST_MODULES_ALL, - ) != 0 - { - GetModuleBaseNameW( - process_handler, - h_mod, - process_name.as_mut_ptr(), - MAX_PATH as DWORD + 1, - ); - } - let mut pos = 0; - for x in process_name.iter() { - if *x == 0 { - break; - } - pos += 1; - } - let name = String::from_utf16_lossy(&process_name[..pos]); - let environ = get_proc_env(process_handler, pid as u32, &name); - - let mut exe_buf = [0u16; MAX_PATH + 1]; - GetModuleFileNameExW( - process_handler, - h_mod, - exe_buf.as_mut_ptr(), - MAX_PATH as DWORD + 1, - ); - - pos = 0; - for x in exe_buf.iter() { - if *x == 0 { - break; - } - pos += 1; - } - - let exe = PathBuf::from(String::from_utf16_lossy(&exe_buf[..pos])); - let mut root = exe.clone(); - root.pop(); - Process { - handle: HandleWrapper(process_handler), - name: name, - pid: pid, - parent: parent, - cmd: get_cmd_line(pid), - environ: environ, - exe: exe, - cwd: PathBuf::new(), - root: root, - status: ProcessStatus::Run, - memory: 0, - virtual_memory: 0, - cpu_usage: 0., - old_cpu: 0, - old_sys_cpu: 0, - old_user_cpu: 0, - start_time: get_start_time(process_handler), - } - } + Process::new_with_handle(pid, parent, process_handler) } else { Process { - handle: HandleWrapper(::std::ptr::null_mut()), + handle: HandleWrapper(null_mut()), name: String::new(), - pid: pid, - parent: parent, + pid, + parent, cmd: get_cmd_line(pid), environ: Vec::new(), exe: get_executable_path(pid), @@ -204,15 +309,17 @@ impl ProcessExt for Process { old_sys_cpu: 0, old_user_cpu: 0, start_time: 0, + updated: true, } } } fn kill(&self, signal: ::Signal) -> bool { - if self.handle.is_null() { - false - } else { - unsafe { TerminateProcess(*self.handle, signal as c_uint) != 0 } + let mut kill = process::Command::new("taskkill.exe"); + kill.arg("/PID").arg(self.pid().to_string()).arg("/F"); + match kill.output() { + Ok(o) => o.status.success(), + Err(_) => false, } } @@ -287,7 +394,7 @@ impl Debug for Process { writeln!(f, "name: {}", self.name); writeln!(f, "environment:"); for var in self.environ.iter() { - if var.len() > 0 { + if !var.is_empty() { writeln!(f, "\t{}", var); } } @@ -297,8 +404,8 @@ impl Debug for Process { } writeln!(f, "executable path: {:?}", self.exe); writeln!(f, "current working directory: {:?}", self.cwd); - writeln!(f, "memory usage: {} kB", self.memory); - writeln!(f, "virtual memory usage: {} kB", self.virtual_memory); + writeln!(f, "memory usage: {} KiB", self.memory); + writeln!(f, "virtual memory usage: {} KiB", self.virtual_memory); writeln!(f, "cpu usage: {}", self.cpu_usage); writeln!(f, "root path: {:?}", self.root) } @@ -319,37 +426,6 @@ unsafe fn get_start_time(handle: HANDLE) -> u64 { tmp / 10_000_000 - 11_644_473_600 } -pub unsafe fn get_parent_process_id(pid: Pid) -> Option { - let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - let mut entry: PROCESSENTRY32 = zeroed(); - entry.dwSize = size_of::() as u32; - let mut not_the_end = Process32First(snapshot, &mut entry); - while not_the_end != 0 { - if pid == entry.th32ProcessID as usize { - // TODO: if some day I have the motivation to add threads: - // ListProcessThreads(entry.th32ProcessID); - CloseHandle(snapshot); - return Some(entry.th32ParentProcessID as usize); - } - not_the_end = Process32Next(snapshot, &mut entry); - } - CloseHandle(snapshot); - None -} - -/*fn run_wmi(args: &[&str]) -> Option { - use std::process::Command; - - if let Ok(out) = Command::new("wmic") - .args(args) - .output() { - if out.status.success() { - return Some(String::from_utf8_lossy(&out.stdout).into_owned()); - } - } - None -}*/ - fn get_cmd_line(_pid: Pid) -> Vec { /*let where_req = format!("ProcessId={}", pid); @@ -401,7 +477,7 @@ unsafe fn get_proc_env(_handle: HANDLE, _pid: u32, _name: &str) -> Vec { context.MxCsr as usize as *mut winapi::c_void, x.as_mut_ptr() as *mut winapi::c_void, x.len() as u64, - ::std::ptr::null_mut()) != 0 { + null_mut()) != 0 { for y in x { print!("{}", y as char); } @@ -428,7 +504,7 @@ unsafe fn get_proc_env(_handle: HANDLE, _pid: u32, _name: &str) -> Vec { ret } -pub fn get_executable_path(_pid: Pid) -> PathBuf { +pub(crate) fn get_executable_path(_pid: Pid) -> PathBuf { /*let where_req = format!("ProcessId={}", pid); if let Some(ret) = run_wmi(&["process", "where", &where_req, "get", "ExecutablePath"]) { @@ -442,14 +518,10 @@ pub fn get_executable_path(_pid: Pid) -> PathBuf { PathBuf::new() } -pub fn compute_cpu_usage(p: &mut Process, nb_processors: u64) { +pub(crate) fn get_system_computation_time() -> ULARGE_INTEGER { unsafe { let mut now: ULARGE_INTEGER = ::std::mem::zeroed(); - let mut sys: ULARGE_INTEGER = ::std::mem::zeroed(); - let mut user: ULARGE_INTEGER = ::std::mem::zeroed(); let mut ftime: FILETIME = zeroed(); - let mut fsys: FILETIME = zeroed(); - let mut fuser: FILETIME = zeroed(); GetSystemTimeAsFileTime(&mut ftime); memcpy( @@ -457,6 +529,17 @@ pub fn compute_cpu_usage(p: &mut Process, nb_processors: u64) { &mut ftime as *mut FILETIME as *mut c_void, size_of::(), ); + now + } +} + +pub(crate) fn compute_cpu_usage(p: &mut Process, nb_processors: u64, now: ULARGE_INTEGER) { + unsafe { + let mut sys: ULARGE_INTEGER = ::std::mem::zeroed(); + let mut user: ULARGE_INTEGER = ::std::mem::zeroed(); + let mut ftime: FILETIME = zeroed(); + let mut fsys: FILETIME = zeroed(); + let mut fuser: FILETIME = zeroed(); GetProcessTimes( *p.handle, diff --git a/src/windows/processor.rs b/src/windows/processor.rs index 156138720..0833eb64d 100644 --- a/src/windows/processor.rs +++ b/src/windows/processor.rs @@ -5,138 +5,135 @@ // use std::collections::HashMap; -use std::sync::{Arc, Mutex}; -use std::thread::{self /*, sleep*/, JoinHandle}; -//use std::time::Duration; +use std::mem; +use std::ops::DerefMut; +use std::ptr::null_mut; +use std::sync::Mutex; use windows::tools::KeyHandler; +use LoadAvg; use ProcessorExt; -use winapi::shared::minwindef::{FALSE, ULONG}; +use ntapi::ntpoapi::PROCESSOR_POWER_INFORMATION; + +use winapi::shared::minwindef::FALSE; use winapi::shared::winerror::ERROR_SUCCESS; use winapi::um::handleapi::CloseHandle; use winapi::um::pdh::{ - PdhAddCounterW, PdhRemoveCounter, PdhCollectQueryData, PdhCollectQueryDataEx, PdhGetFormattedCounterValue, - PdhOpenQueryA, PdhCloseQuery, PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, PDH_FMT_LARGE, PDH_HCOUNTER, PDH_HQUERY, + PdhAddCounterW, PdhAddEnglishCounterA, PdhCloseQuery, PdhCollectQueryData, + PdhCollectQueryDataEx, PdhGetFormattedCounterValue, PdhOpenQueryA, PdhRemoveCounter, + PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, PDH_HCOUNTER, PDH_HQUERY, }; -use winapi::um::synchapi::{CreateEventA, WaitForSingleObject}; -use winapi::um::winbase::{INFINITE, WAIT_OBJECT_0}; -use winapi::um::winnt::HANDLE; - -#[derive(Debug)] -pub enum CounterValue { - Float(f32), - Integer(u64), -} +use winapi::um::powerbase::CallNtPowerInformation; +use winapi::um::synchapi::CreateEventA; +use winapi::um::sysinfoapi::SYSTEM_INFO; +use winapi::um::winbase::{RegisterWaitForSingleObject, INFINITE}; +use winapi::um::winnt::{ProcessorInformation, BOOLEAN, HANDLE, PVOID, WT_EXECUTEDEFAULT}; + +// This formula comes from linux's include/linux/sched/loadavg.h +// https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23 +const LOADAVG_FACTOR_1F: f64 = 0.9200444146293232478931553241; +const LOADAVG_FACTOR_5F: f64 = 0.6592406302004437462547604110; +const LOADAVG_FACTOR_15F: f64 = 0.2865047968601901003248854266; +// The time interval in seconds between taking load counts, same as Linux +const SAMPLING_INTERVAL: usize = 5; -impl CounterValue { - pub fn get_f32(&self) -> f32 { - match *self { - CounterValue::Float(v) => v, - _ => panic!("not a float"), +// maybe use a read/write lock instead? +static LOAD_AVG: once_cell::sync::Lazy>> = + once_cell::sync::Lazy::new(|| unsafe { init_load_avg() }); + +pub(crate) fn get_load_average() -> LoadAvg { + if let Ok(avg) = LOAD_AVG.lock() { + if let Some(avg) = &*avg { + return avg.clone(); } } + return LoadAvg::default(); +} - pub fn get_u64(&self) -> u64 { - match *self { - CounterValue::Integer(v) => v, - _ => panic!("not an integer"), +unsafe extern "system" fn load_avg_callback(counter: PVOID, _: BOOLEAN) { + let mut display_value: PDH_FMT_COUNTERVALUE = mem::MaybeUninit::uninit().assume_init(); + + if PdhGetFormattedCounterValue(counter as _, PDH_FMT_DOUBLE, null_mut(), &mut display_value) + != ERROR_SUCCESS as _ + { + return; + } + if let Ok(mut avg) = LOAD_AVG.lock() { + if let Some(avg) = avg.deref_mut() { + let current_load = display_value.u.doubleValue(); + + avg.one = avg.one * LOADAVG_FACTOR_1F + current_load * (1.0 - LOADAVG_FACTOR_1F); + avg.five = avg.five * LOADAVG_FACTOR_5F + current_load * (1.0 - LOADAVG_FACTOR_5F); + avg.fifteen = + avg.fifteen * LOADAVG_FACTOR_15F + current_load * (1.0 - LOADAVG_FACTOR_15F); } } } -#[allow(dead_code)] -#[derive(Debug)] -struct Counter { - counter: PDH_HCOUNTER, - value: CounterValue, - getter: Vec, -} +unsafe fn init_load_avg() -> Mutex> { + // You can see the original implementation here: https://github.com/giampaolo/psutil + let mut query = null_mut(); -impl Counter { - fn new_f32(counter: PDH_HCOUNTER, value: f32, getter: Vec) -> Counter { - Counter { - counter: counter, - value: CounterValue::Float(value), - getter: getter, - } + if PdhOpenQueryA(null_mut(), 0, &mut query) != ERROR_SUCCESS as _ { + return Mutex::new(None); } - fn new_u64(counter: PDH_HCOUNTER, value: u64, getter: Vec) -> Counter { - Counter { - counter: counter, - value: CounterValue::Integer(value), - getter: getter, - } + let mut counter: PDH_HCOUNTER = mem::zeroed(); + if PdhAddEnglishCounterA( + query, + b"\\System\\Processor Queue Length\0".as_ptr() as _, + 0, + &mut counter, + ) != ERROR_SUCCESS as _ + { + PdhCloseQuery(query); + return Mutex::new(None); + } + + let event = CreateEventA(null_mut(), FALSE, FALSE, b"LoadUpdateEvent\0".as_ptr() as _); + if event.is_null() { + PdhCloseQuery(query); + return Mutex::new(None); + } + + if PdhCollectQueryDataEx(query, SAMPLING_INTERVAL as _, event) != ERROR_SUCCESS as _ { + PdhCloseQuery(query); + return Mutex::new(None); + } + + let mut wait_handle = null_mut(); + if RegisterWaitForSingleObject( + &mut wait_handle, + event, + Some(load_avg_callback), + counter as _, + INFINITE, + WT_EXECUTEDEFAULT, + ) == 0 + { + PdhRemoveCounter(counter); + PdhCloseQuery(query); + Mutex::new(None) + } else { + Mutex::new(Some(LoadAvg::default())) } } struct InternalQuery { query: PDH_HQUERY, event: HANDLE, - data: Mutex>, + data: HashMap, } unsafe impl Send for InternalQuery {} unsafe impl Sync for InternalQuery {} -impl InternalQuery { - pub fn record(&self) -> bool { - unsafe { - let status = PdhCollectQueryData(self.query); - if status != ERROR_SUCCESS as i32 { - eprintln!("PdhCollectQueryData error: {:x} {:?}", status, self.query); - return false; - } - if PdhCollectQueryDataEx(self.query, 1, self.event) != ERROR_SUCCESS as i32 { - return false; - } - if WaitForSingleObject(self.event, INFINITE) == WAIT_OBJECT_0 { - if let Ok(ref mut data) = self.data.lock() { - let mut counter_type: ULONG = 0; - let mut display_value: PDH_FMT_COUNTERVALUE = ::std::mem::zeroed(); - for (_, x) in data.iter_mut() { - match x.value { - CounterValue::Float(ref mut value) => { - if PdhGetFormattedCounterValue( - x.counter, - PDH_FMT_DOUBLE, - &mut counter_type, - &mut display_value, - ) == ERROR_SUCCESS as i32 - { - *value = *display_value.u.doubleValue() as f32 / 100f32; - } - } - CounterValue::Integer(ref mut value) => { - if PdhGetFormattedCounterValue( - x.counter, - PDH_FMT_LARGE, - &mut counter_type, - &mut display_value, - ) == ERROR_SUCCESS as i32 - { - *value = *display_value.u.largeValue() as u64; - } - } - } - } - } - true - } else { - false - } - } - } -} - impl Drop for InternalQuery { fn drop(&mut self) { unsafe { - if let Ok(ref data) = self.data.lock() { - for (_, counter) in data.iter() { - PdhRemoveCounter(counter.counter); - } + for (_, counter) in self.data.iter() { + PdhRemoveCounter(*counter); } if !self.event.is_null() { @@ -151,35 +148,20 @@ impl Drop for InternalQuery { } pub struct Query { - internal: Arc, - thread: Option>, + internal: InternalQuery, } impl Query { pub fn new() -> Option { - let mut query = ::std::ptr::null_mut(); + let mut query = null_mut(); unsafe { - if PdhOpenQueryA(::std::ptr::null_mut(), 0, &mut query) == ERROR_SUCCESS as i32 { - let event = CreateEventA( - ::std::ptr::null_mut(), - FALSE, - FALSE, - b"some_ev\0".as_ptr() as *const i8, - ); - if event.is_null() { - PdhCloseQuery(query); - None - } else { - let q = Arc::new(InternalQuery { - query: query, - event: event, - data: Mutex::new(HashMap::new()), - }); - Some(Query { - internal: q, - thread: None, - }) - } + if PdhOpenQueryA(null_mut(), 0, &mut query) == ERROR_SUCCESS as i32 { + let q = InternalQuery { + query, + event: null_mut(), + data: HashMap::new(), + }; + Some(Query { internal: q }) } else { None } @@ -187,44 +169,38 @@ impl Query { } pub fn get(&self, name: &String) -> Option { - if let Ok(data) = self.internal.data.lock() { - if let Some(ref counter) = data.get(name) { - return Some(counter.value.get_f32()); - } - } - None - } + if let Some(ref counter) = self.internal.data.get(name) { + unsafe { + let mut display_value: PDH_FMT_COUNTERVALUE = + mem::MaybeUninit::uninit().assume_init(); + let counter: PDH_HCOUNTER = **counter; - pub fn get_u64(&self, name: &String) -> Option { - if let Ok(data) = self.internal.data.lock() { - if let Some(ref counter) = data.get(name) { - return Some(counter.value.get_u64()); + let ret = PdhGetFormattedCounterValue( + counter, + PDH_FMT_DOUBLE, + null_mut(), + &mut display_value, + ) as u32; + return if ret == ERROR_SUCCESS as _ { + let data = *display_value.u.doubleValue(); + Some(data as f32) + } else { + Some(0.) + }; } } None } - pub fn add_counter(&mut self, name: &String, getter: Vec, value: CounterValue) -> bool { - if let Ok(data) = self.internal.data.lock() { - if data.contains_key(name) { - return false; - } + pub fn add_counter(&mut self, name: &String, getter: Vec) -> bool { + if self.internal.data.contains_key(name) { + return false; } unsafe { let mut counter: PDH_HCOUNTER = ::std::mem::zeroed(); let ret = PdhAddCounterW(self.internal.query, getter.as_ptr(), 0, &mut counter); - if ret == ERROR_SUCCESS as i32 { - self.internal - .data - .lock() - .expect("couldn't add counter...") - .insert( - name.clone(), - match value { - CounterValue::Float(v) => Counter::new_f32(counter, v, getter), - CounterValue::Integer(v) => Counter::new_u64(counter, v, getter), - }, - ); + if ret == ERROR_SUCCESS as _ { + self.internal.data.insert(name.clone(), counter); } else { eprintln!("failed to add counter '{}': {:x}...", name, ret); return false; @@ -233,11 +209,12 @@ impl Query { true } - pub fn start(&mut self) { - let internal = Arc::clone(&self.internal); - self.thread = Some(thread::spawn(move || loop { - internal.record(); - })); + pub fn refresh(&self) { + unsafe { + if PdhCollectQueryData(self.internal.query) != ERROR_SUCCESS as _ { + eprintln!("failed to refresh CPU data"); + } + } } } @@ -245,8 +222,10 @@ impl Query { pub struct Processor { name: String, cpu_usage: f32, - key_idle: Option, key_used: Option, + vendor_id: String, + brand: String, + frequency: u64, } impl ProcessorExt for Processor { @@ -257,31 +236,172 @@ impl ProcessorExt for Processor { fn get_name(&self) -> &str { &self.name } + + fn get_frequency(&self) -> u64 { + self.frequency + } + + fn get_vendor_id(&self) -> &str { + &self.vendor_id + } + + fn get_brand(&self) -> &str { + &self.brand + } } impl Processor { - fn new_with_values(name: &str) -> Processor { + pub(crate) fn new_with_values( + name: &str, + vendor_id: String, + brand: String, + frequency: u64, + ) -> Processor { Processor { name: name.to_owned(), cpu_usage: 0f32, - key_idle: None, key_used: None, + vendor_id, + brand, + frequency, } } + + pub(crate) fn set_cpu_usage(&mut self, value: f32) { + self.cpu_usage = value; + } } -pub fn create_processor(name: &str) -> Processor { - Processor::new_with_values(name) +fn get_vendor_id_not_great(info: &SYSTEM_INFO) -> String { + use winapi::um::winnt; + // https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info + match unsafe { info.u.s() }.wProcessorArchitecture { + winnt::PROCESSOR_ARCHITECTURE_INTEL => "Intel x86", + winnt::PROCESSOR_ARCHITECTURE_MIPS => "MIPS", + winnt::PROCESSOR_ARCHITECTURE_ALPHA => "RISC Alpha", + winnt::PROCESSOR_ARCHITECTURE_PPC => "PPC", + winnt::PROCESSOR_ARCHITECTURE_SHX => "SHX", + winnt::PROCESSOR_ARCHITECTURE_ARM => "ARM", + winnt::PROCESSOR_ARCHITECTURE_IA64 => "Intel Itanium-based x64", + winnt::PROCESSOR_ARCHITECTURE_ALPHA64 => "RISC Alpha x64", + winnt::PROCESSOR_ARCHITECTURE_MSIL => "MSIL", + winnt::PROCESSOR_ARCHITECTURE_AMD64 => "(Intel or AMD) x64", + winnt::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => "Intel Itanium-based x86", + winnt::PROCESSOR_ARCHITECTURE_NEUTRAL => "unknown", + winnt::PROCESSOR_ARCHITECTURE_ARM64 => "ARM x64", + winnt::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => "ARM", + winnt::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 => "Intel Itanium-based x86", + _ => "unknown", + } + .to_owned() } -pub fn set_cpu_usage(p: &mut Processor, value: f32) { - p.cpu_usage = value; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { + #[cfg(target_arch = "x86")] + use std::arch::x86::__cpuid; + #[cfg(target_arch = "x86_64")] + use std::arch::x86_64::__cpuid; + + fn add_u32(v: &mut Vec, i: u32) { + let i = &i as *const u32 as *const u8; + unsafe { + v.push(*i); + v.push(*i.offset(1)); + v.push(*i.offset(2)); + v.push(*i.offset(3)); + } + } + + // First, we try to get the complete name. + let res = unsafe { __cpuid(0x80000000) }; + let n_ex_ids = res.eax; + let brand = if n_ex_ids >= 0x80000004 { + let mut extdata = Vec::with_capacity(5); + + for i in 0x80000000..=n_ex_ids { + extdata.push(unsafe { __cpuid(i) }); + } + + let mut out = Vec::with_capacity(4 * 4 * 3); // 4 * u32 * nb_entries + for i in 2..5 { + add_u32(&mut out, extdata[i].eax); + add_u32(&mut out, extdata[i].ebx); + add_u32(&mut out, extdata[i].ecx); + add_u32(&mut out, extdata[i].edx); + } + let mut pos = 0; + for e in out.iter() { + if *e == 0 { + break; + } + pos += 1; + } + match ::std::str::from_utf8(&out[..pos]) { + Ok(s) => s.to_owned(), + _ => String::new(), + } + } else { + String::new() + }; + + // Failed to get full name, let's retry for the short version! + let res = unsafe { __cpuid(0) }; + let mut x = Vec::with_capacity(16); // 3 * u32 + add_u32(&mut x, res.ebx); + add_u32(&mut x, res.edx); + add_u32(&mut x, res.ecx); + let mut pos = 0; + for e in x.iter() { + if *e == 0 { + break; + } + pos += 1; + } + let vendor_id = match ::std::str::from_utf8(&x[..pos]) { + Ok(s) => s.to_owned(), + Err(_) => get_vendor_id_not_great(info), + }; + (vendor_id, brand) } -pub fn get_key_idle(p: &mut Processor) -> &mut Option { - &mut p.key_idle +#[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))] +pub fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { + (get_vendor_id_not_great(info), String::new()) } pub fn get_key_used(p: &mut Processor) -> &mut Option { &mut p.key_used } + +// From https://stackoverflow.com/a/43813138: +// +// If your PC has 64 or fewer logical processors installed, the above code will work fine. However, +// if your PC has more than 64 logical processors installed, use GetActiveProcessorCount() or +// GetLogicalProcessorInformation() to determine the total number of logical processors installed. +pub fn get_frequencies(nb_processors: usize) -> Vec { + let size = nb_processors * mem::size_of::(); + let mut infos: Vec = Vec::with_capacity(nb_processors); + + if unsafe { + CallNtPowerInformation( + ProcessorInformation, + null_mut(), + 0, + infos.as_mut_ptr() as _, + size as _, + ) + } == 0 + { + unsafe { + infos.set_len(nb_processors); + } + // infos.Number + infos + .into_iter() + .map(|i| i.CurrentMhz as u64) + .collect::>() + } else { + vec![0; nb_processors] + } +} diff --git a/src/windows/system.rs b/src/windows/system.rs index ab29d21f6..031f97aef 100644 --- a/src/windows/system.rs +++ b/src/windows/system.rs @@ -12,32 +12,32 @@ use std::cell::UnsafeCell; use std::collections::HashMap; use std::mem::{size_of, zeroed}; -use DiskExt; +use ComponentExt; +use LoadAvg; +use Networks; use Pid; use ProcessExt; use RefreshKind; use SystemExt; -use windows::network::{self, NetworkData}; use windows::process::{ - compute_cpu_usage, get_handle, get_parent_process_id, update_proc_info, Process, + compute_cpu_usage, get_handle, get_system_computation_time, update_proc_info, Process, }; -use windows::processor::CounterValue; use windows::tools::*; -use winapi::shared::minwindef::{DWORD, FALSE}; -use winapi::shared::winerror::ERROR_SUCCESS; +use ntapi::ntexapi::{ + NtQuerySystemInformation, SystemProcessInformation, SYSTEM_PROCESS_INFORMATION, +}; +use winapi::shared::minwindef::FALSE; +use winapi::shared::ntdef::{PVOID, ULONG}; +use winapi::shared::ntstatus::STATUS_INFO_LENGTH_MISMATCH; use winapi::um::minwinbase::STILL_ACTIVE; -use winapi::um::pdh::PdhEnumObjectItemsW; use winapi::um::processthreadsapi::GetExitCodeProcess; -use winapi::um::psapi::K32EnumProcesses; use winapi::um::sysinfoapi::{GlobalMemoryStatusEx, MEMORYSTATUSEX}; use winapi::um::winnt::HANDLE; use rayon::prelude::*; -const PROCESS_LEN: usize = 10192; - /// Struct containing system's information. pub struct System { process_list: HashMap, @@ -45,210 +45,104 @@ pub struct System { mem_free: u64, swap_total: u64, swap_free: u64, + global_processor: Processor, processors: Vec, - temperatures: Vec, + components: Vec, disks: Vec, query: Option, - network: NetworkData, + networks: Networks, uptime: u64, } -impl System { - fn clear_procs(&mut self) { - if self.processors.len() > 0 { - let mut to_delete = Vec::new(); - - for (pid, proc_) in self.process_list.iter_mut() { - if !is_proc_running(get_handle(proc_)) { - to_delete.push(*pid); - } else { - compute_cpu_usage(proc_, self.processors.len() as u64 - 1); - } - } - for pid in to_delete { - self.process_list.remove(&pid); - } - } - } -} - -struct Wrap(Process); - -unsafe impl Send for Wrap {} +// Useful for parallel iterations. +struct Wrap(T); -struct WrapSystem<'a>(UnsafeCell<&'a mut System>); - -impl<'a> WrapSystem<'a> { - fn get(&self) -> &'a mut System { - unsafe { *(self.0.get()) } - } -} - -unsafe impl<'a> Send for WrapSystem<'a> {} -unsafe impl<'a> Sync for WrapSystem<'a> {} +unsafe impl Send for Wrap {} +unsafe impl Sync for Wrap {} impl SystemExt for System { #[allow(non_snake_case)] fn new_with_specifics(refreshes: RefreshKind) -> System { + let (processors, vendor_id, brand) = init_processors(); let mut s = System { - process_list: HashMap::new(), + process_list: HashMap::with_capacity(500), mem_total: 0, mem_free: 0, swap_total: 0, swap_free: 0, - processors: init_processors(), - temperatures: component::get_components(), - disks: Vec::new(), + global_processor: Processor::new_with_values("Total CPU", vendor_id, brand, 0), + processors, + components: Vec::new(), + disks: Vec::with_capacity(2), query: Query::new(), - network: network::new(), + networks: Networks::new(), uptime: get_uptime(), }; // TODO: in case a translation fails, it might be nice to log it somewhere... if let Some(ref mut query) = s.query { let x = unsafe { load_symbols() }; if let Some(processor_trans) = get_translation(&"Processor".to_owned(), &x) { - let idle_time_trans = get_translation(&"% Idle Time".to_owned(), &x); + // let idle_time_trans = get_translation(&"% Idle Time".to_owned(), &x); let proc_time_trans = get_translation(&"% Processor Time".to_owned(), &x); if let Some(ref proc_time_trans) = proc_time_trans { add_counter( format!("\\{}(_Total)\\{}", processor_trans, proc_time_trans), query, - get_key_used(&mut s.processors[0]), + get_key_used(&mut s.global_processor), "tot_0".to_owned(), - CounterValue::Float(0.), - ); - } - if let Some(ref idle_time_trans) = idle_time_trans { - add_counter( - format!("\\{}(_Total)\\{}", processor_trans, idle_time_trans), - query, - get_key_idle(&mut s.processors[0]), - "tot_1".to_owned(), - CounterValue::Float(0.), ); } - for (pos, proc_) in s.processors.iter_mut().skip(1).enumerate() { + for (pos, proc_) in s.processors.iter_mut().enumerate() { if let Some(ref proc_time_trans) = proc_time_trans { add_counter( format!("\\{}({})\\{}", processor_trans, pos, proc_time_trans), query, get_key_used(proc_), format!("{}_0", pos), - CounterValue::Float(0.), - ); - } - if let Some(ref idle_time_trans) = idle_time_trans { - add_counter( - format!("\\{}({})\\{}", processor_trans, pos, idle_time_trans), - query, - get_key_idle(proc_), - format!("{}_1", pos), - CounterValue::Float(0.), ); } } + } else { + eprintln!("failed to get `Processor` translation"); } + } + s.refresh_specifics(refreshes); + s + } - if let Some(network_trans) = get_translation(&"Network Interface".to_owned(), &x) { - let network_in_trans = get_translation(&"Bytes Received/Sec".to_owned(), &x); - let network_out_trans = get_translation(&"Bytes Sent/sec".to_owned(), &x); - - const PERF_DETAIL_WIZARD: DWORD = 400; - const PDH_MORE_DATA: DWORD = 0x800007D2; - - let mut network_trans_utf16: Vec = network_trans.encode_utf16().collect(); - network_trans_utf16.push(0); - let mut dwCounterListSize: DWORD = 0; - let mut dwInstanceListSize: DWORD = 0; - let status = unsafe { - PdhEnumObjectItemsW( - ::std::ptr::null(), - ::std::ptr::null(), - network_trans_utf16.as_ptr(), - ::std::ptr::null_mut(), - &mut dwCounterListSize, - ::std::ptr::null_mut(), - &mut dwInstanceListSize, - PERF_DETAIL_WIZARD, - 0, - ) - }; - if status != PDH_MORE_DATA as i32 { - eprintln!("PdhEnumObjectItems invalid status: {:x}", status); - } else { - let mut pwsCounterListBuffer: Vec = - Vec::with_capacity(dwCounterListSize as usize); - let mut pwsInstanceListBuffer: Vec = - Vec::with_capacity(dwInstanceListSize as usize); - unsafe { - pwsCounterListBuffer.set_len(dwCounterListSize as usize); - pwsInstanceListBuffer.set_len(dwInstanceListSize as usize); - } - let status = unsafe { - PdhEnumObjectItemsW( - ::std::ptr::null(), - ::std::ptr::null(), - network_trans_utf16.as_ptr(), - pwsCounterListBuffer.as_mut_ptr(), - &mut dwCounterListSize, - pwsInstanceListBuffer.as_mut_ptr(), - &mut dwInstanceListSize, - PERF_DETAIL_WIZARD, - 0, - ) - }; - if status != ERROR_SUCCESS as i32 { - eprintln!("PdhEnumObjectItems invalid status: {:x}", status); - } else { - for (pos, x) in pwsInstanceListBuffer - .split(|x| *x == 0) - .filter(|x| x.len() > 0) - .enumerate() - { - let net_interface = String::from_utf16(x).expect("invalid utf16"); - if let Some(ref network_in_trans) = network_in_trans { - let mut key_in = None; - add_counter( - format!( - "\\{}({})\\{}", - network_trans, net_interface, network_in_trans - ), - query, - &mut key_in, - format!("net{}_in", pos), - CounterValue::Integer(0), - ); - if key_in.is_some() { - network::get_keys_in(&mut s.network).push(key_in.unwrap()); - } - } - if let Some(ref network_out_trans) = network_out_trans { - let mut key_out = None; - add_counter( - format!( - "\\{}({})\\{}", - network_trans, net_interface, network_out_trans - ), - query, - &mut key_out, - format!("net{}_out", pos), - CounterValue::Integer(0), - ); - if key_out.is_some() { - network::get_keys_out(&mut s.network).push(key_out.unwrap()); - } - } - } - } + fn refresh_cpu(&mut self) { + self.uptime = get_uptime(); + if let Some(ref mut query) = self.query { + query.refresh(); + let mut used_time = None; + if let &mut Some(ref key_used) = get_key_used(&mut self.global_processor) { + used_time = Some( + query + .get(&key_used.unique_id) + .expect("global_key_idle disappeared"), + ); + } + if let Some(used_time) = used_time { + self.global_processor.set_cpu_usage(used_time); + } + for p in self.processors.iter_mut() { + let mut used_time = None; + if let &mut Some(ref key_used) = get_key_used(p) { + used_time = Some( + query + .get(&key_used.unique_id) + .expect("key_used disappeared"), + ); + } + if let Some(used_time) = used_time { + p.set_cpu_usage(used_time); } } - query.start(); } - s.refresh_specifics(refreshes); - s } - fn refresh_system(&mut self) { + fn refresh_memory(&mut self) { + self.uptime = get_uptime(); unsafe { let mut mem_info: MEMORYSTATUSEX = zeroed(); mem_info.dwLength = size_of::() as u32; @@ -258,88 +152,154 @@ impl SystemExt for System { //self.swap_total = auto_cast!(mem_info.ullTotalPageFile - mem_info.ullTotalPhys, u64); //self.swap_free = auto_cast!(mem_info.ullAvailPageFile, u64); } - self.uptime = get_uptime(); - if let Some(ref mut query) = self.query { - for p in self.processors.iter_mut() { - let mut idle_time = None; - if let &mut Some(ref key_idle) = get_key_idle(p) { - idle_time = Some(query.get(&key_idle.unique_id).expect("key disappeared")); - } - if let Some(idle_time) = idle_time { - set_cpu_usage(p, 1. - idle_time); - } - } + } + + /// Refresh components' temperature. + /// + /// Please note that on Windows, you need to have Administrator priviledges to get this + /// information. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new(); + /// s.refresh_components(); + /// ``` + fn refresh_components(&mut self) { + for component in &mut self.components { + component.refresh(); } } - fn refresh_network(&mut self) { - network::refresh(&mut self.network, &self.query); + fn refresh_components_list(&mut self) { + self.components = component::get_components(); } fn refresh_process(&mut self, pid: Pid) -> bool { - if refresh_existing_process(self, pid, true) == false { - self.process_list.remove(&pid); - false - } else { + if self.process_list.contains_key(&pid) { + if refresh_existing_process(self, pid, true) == false { + self.process_list.remove(&pid); + return false; + } true + } else if let Some(mut p) = Process::new_from_pid(pid) { + let system_time = get_system_computation_time(); + compute_cpu_usage(&mut p, self.processors.len() as u64, system_time); + self.process_list.insert(pid, p); + true + } else { + false } } + #[allow(clippy::cast_ptr_alignment)] fn refresh_processes(&mut self) { - // I think that 10192 as length will be enough to get all processes at once... - let mut process_ids: Vec = Vec::with_capacity(PROCESS_LEN); - let mut cb_needed = 0; + // Windows 10 notebook requires at least 512KiB of memory to make it in one go + let mut buffer_size: usize = 512 * 1024; + + loop { + let mut process_information: Vec = Vec::with_capacity(buffer_size); + + let mut cb_needed = 0; + let ntstatus = unsafe { + process_information.set_len(buffer_size); + NtQuerySystemInformation( + SystemProcessInformation, + process_information.as_mut_ptr() as PVOID, + buffer_size as ULONG, + &mut cb_needed, + ) + }; + + if ntstatus != STATUS_INFO_LENGTH_MISMATCH { + if ntstatus < 0 { + eprintln!( + "Couldn't get process infos: NtQuerySystemInformation returned {}", + ntstatus + ); + } - unsafe { - process_ids.set_len(PROCESS_LEN); - } - let size = ::std::mem::size_of::() * process_ids.len(); - unsafe { - if K32EnumProcesses(process_ids.as_mut_ptr(), size as DWORD, &mut cb_needed) == 0 { - return; - } - } - let nb_processes = cb_needed / ::std::mem::size_of::() as DWORD; - unsafe { - process_ids.set_len(nb_processes as usize); - } + // Parse the data block to get process information + let mut process_ids = Vec::with_capacity(500); + let mut process_information_offset = 0; + loop { + let p = unsafe { + process_information + .as_ptr() + .offset(process_information_offset) + as *const SYSTEM_PROCESS_INFORMATION + }; + let pi = unsafe { &*p }; + + process_ids.push(Wrap(p)); - { - let this = WrapSystem(UnsafeCell::new(self)); - - process_ids - .par_iter() - .filter_map(|pid| { - let pid = *pid as usize; - if !refresh_existing_process(this.get(), pid, false) { - let ppid = unsafe { get_parent_process_id(pid) }; - let mut p = Process::new(pid, ppid, 0); - update_proc_info(&mut p); - Some(Wrap(p)) - } else { - None + if pi.NextEntryOffset == 0 { + break; } - }) - .collect::>() - } - .into_iter() - .for_each(|p| { - self.process_list.insert(p.0.pid(), p.0); - }); - self.clear_procs(); - } - fn refresh_disks(&mut self) { - for disk in &mut self.disks { - disk.update(); + process_information_offset += pi.NextEntryOffset as isize; + } + let nb_processors = self.processors.len() as u64; + let process_list = Wrap(UnsafeCell::new(&mut self.process_list)); + let system_time = get_system_computation_time(); + // TODO: instead of using parallel iterator only here, would be better to be able + // to run it over `process_information` directly! + let processes = process_ids + .into_par_iter() + .filter_map(|pi| unsafe { + let pi = *pi.0; + let pid = pi.UniqueProcessId as usize; + if let Some(proc_) = (*process_list.0.get()).get_mut(&pid) { + proc_.memory = (pi.WorkingSetSize as u64) >> 10u64; + proc_.virtual_memory = (pi.VirtualSize as u64) >> 10u64; + compute_cpu_usage(proc_, nb_processors, system_time); + proc_.updated = true; + return None; + } + let name = get_process_name(&pi, pid); + let mut p = Process::new_full( + pid, + if pi.InheritedFromUniqueProcessId as usize != 0 { + Some(pi.InheritedFromUniqueProcessId as usize) + } else { + None + }, + (pi.WorkingSetSize as u64) >> 10u64, + (pi.VirtualSize as u64) >> 10u64, + name, + ); + compute_cpu_usage(&mut p, nb_processors, system_time); + Some(p) + }) + .collect::>(); + self.process_list.retain(|_, v| { + let x = v.updated; + v.updated = false; + x + }); + for p in processes.into_iter() { + self.process_list.insert(p.pid(), p); + } + + break; + } + + // GetNewBufferSize + if cb_needed == 0 { + buffer_size *= 2; + continue; + } + // allocating a few more kilo bytes just in case there are some new process + // kicked in since new call to NtQuerySystemInformation + buffer_size = (cb_needed + (1024 * 10)) as usize; } } - fn refresh_disk_list(&mut self) { + fn refresh_disks_list(&mut self) { self.disks = unsafe { get_disks() }; } - fn get_process_list(&self) -> &HashMap { + fn get_processes(&self) -> &HashMap { &self.process_list } @@ -347,8 +307,12 @@ impl SystemExt for System { self.process_list.get(&(pid as usize)) } - fn get_processor_list(&self) -> &[Processor] { - &self.processors[..] + fn get_global_processor_info(&self) -> &Processor { + &self.global_processor + } + + fn get_processors(&self) -> &[Processor] { + &self.processors } fn get_total_memory(&self) -> u64 { @@ -375,21 +339,37 @@ impl SystemExt for System { (self.swap_total - self.swap_free) >> 10 } - fn get_components_list(&self) -> &[Component] { - &self.temperatures[..] + fn get_components(&self) -> &[Component] { + &self.components + } + + fn get_components_mut(&mut self) -> &mut [Component] { + &mut self.components } fn get_disks(&self) -> &[Disk] { - &self.disks[..] + &self.disks + } + + fn get_disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks } - fn get_network(&self) -> &NetworkData { - &self.network + fn get_networks(&self) -> &Networks { + &self.networks + } + + fn get_networks_mut(&mut self) -> &mut Networks { + &mut self.networks } fn get_uptime(&self) -> u64 { self.uptime } + + fn get_load_average(&self) -> LoadAvg { + get_load_average() + } } fn is_proc_running(handle: HANDLE) -> bool { @@ -405,10 +385,35 @@ fn refresh_existing_process(s: &mut System, pid: Pid, compute_cpu: bool) -> bool } update_proc_info(entry); if compute_cpu { - compute_cpu_usage(entry, s.processors.len() as u64 - 1); + compute_cpu_usage( + entry, + s.processors.len() as u64, + get_system_computation_time(), + ); } true } else { false } } + +pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: usize) -> String { + let name = &process.ImageName; + if name.Buffer.is_null() { + match process_id { + 0 => "Idle".to_owned(), + 4 => "System".to_owned(), + _ => format!(" Process {}", process_id), + } + } else { + let slice = unsafe { + std::slice::from_raw_parts( + name.Buffer, + //The length is in bytes, not the length of string + name.Length as usize / std::mem::size_of::(), + ) + }; + + String::from_utf16_lossy(slice) + } +} diff --git a/src/windows/tools.rs b/src/windows/tools.rs index 6e0065649..18565fda4 100644 --- a/src/windows/tools.rs +++ b/src/windows/tools.rs @@ -4,14 +4,17 @@ // Copyright (c) 2018 Guillaume Gomez // -use windows::processor::{create_processor, CounterValue, Processor, Query}; +use windows::processor::{self, Processor, Query}; -use sys::disk::{new_disk, Disk, DiskType}; +use sys::disk::{new_disk, Disk}; +use DiskType; use std::collections::HashMap; use std::ffi::OsStr; use std::mem::{size_of, zeroed}; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; + use winapi::ctypes::c_void; use winapi::shared::minwindef::{BYTE, DWORD, MAX_PATH, TRUE}; @@ -42,57 +45,22 @@ impl KeyHandler { } } -/*#[allow(non_snake_case)] -#[allow(unused)] -unsafe fn browser() { - use winapi::um::pdh::{PdhBrowseCountersA, PDH_BROWSE_DLG_CONFIG_A}; - use winapi::shared::winerror::ERROR_SUCCESS; - - let mut BrowseDlgData: PDH_BROWSE_DLG_CONFIG_A = ::std::mem::zeroed(); - let mut CounterPathBuffer: [i8; 255] = ::std::mem::zeroed(); - const PERF_DETAIL_WIZARD: u32 = 400; - let text = b"Select a counter to monitor.\0"; - - BrowseDlgData.set_IncludeInstanceIndex(FALSE as u32); - BrowseDlgData.set_SingleCounterPerAdd(TRUE as u32); - BrowseDlgData.set_SingleCounterPerDialog(TRUE as u32); - BrowseDlgData.set_LocalCountersOnly(FALSE as u32); - BrowseDlgData.set_WildCardInstances(TRUE as u32); - BrowseDlgData.set_HideDetailBox(TRUE as u32); - BrowseDlgData.set_InitializePath(FALSE as u32); - BrowseDlgData.set_DisableMachineSelection(FALSE as u32); - BrowseDlgData.set_IncludeCostlyObjects(FALSE as u32); - BrowseDlgData.set_ShowObjectBrowser(FALSE as u32); - BrowseDlgData.hWndOwner = ::std::ptr::null_mut(); - BrowseDlgData.szReturnPathBuffer = CounterPathBuffer.as_mut_ptr(); - BrowseDlgData.cchReturnPathLength = 255; - BrowseDlgData.pCallBack = None; - BrowseDlgData.dwCallBackArg = 0; - BrowseDlgData.CallBackStatus = ERROR_SUCCESS as i32; - BrowseDlgData.dwDefaultDetailLevel = PERF_DETAIL_WIZARD; - BrowseDlgData.szDialogBoxCaption = text as *const _ as usize as *mut i8; - let ret = PdhBrowseCountersA(&mut BrowseDlgData as *mut _); - println!("browser: {:?}", ret); - for x in CounterPathBuffer.iter() { - print!("{:?} ", *x); - } - println!(""); - for x in 0..256 { - print!("{:?} ", *BrowseDlgData.szReturnPathBuffer.offset(x)); - } - println!(""); -}*/ - -pub fn init_processors() -> Vec { +pub fn init_processors() -> (Vec, String, String) { unsafe { let mut sys_info: SYSTEM_INFO = zeroed(); GetSystemInfo(&mut sys_info); + let (vendor_id, brand) = processor::get_vendor_id_and_brand(&sys_info); + let frequencies = processor::get_frequencies(sys_info.dwNumberOfProcessors as usize); let mut ret = Vec::with_capacity(sys_info.dwNumberOfProcessors as usize + 1); for nb in 0..sys_info.dwNumberOfProcessors { - ret.push(create_processor(&format!("CPU {}", nb + 1))); + ret.push(Processor::new_with_values( + &format!("CPU {}", nb + 1), + vendor_id.clone(), + brand.clone(), + frequencies[nb as usize], + )); } - ret.insert(0, create_processor("Total CPU")); - ret + (ret, vendor_id, brand) } } @@ -132,134 +100,133 @@ pub unsafe fn get_drive_size(handle: HANDLE) -> u64 { } pub unsafe fn get_disks() -> Vec { - let mut disks = Vec::new(); let drives = GetLogicalDrives(); if drives == 0 { - return disks; + return Vec::new(); } - for x in 0..size_of::() * 8 { - if (drives >> x) & 1 == 0 { - continue; - } - let mount_point = [b'A' as u16 + x as u16, b':' as u16, b'\\' as u16, 0]; - if GetDriveTypeW(mount_point.as_ptr()) != DRIVE_FIXED { - continue; - } - let mut name = [0u16; MAX_PATH + 1]; - let mut file_system = [0u16; 32]; - if GetVolumeInformationW( - mount_point.as_ptr(), - name.as_mut_ptr(), - name.len() as DWORD, - ::std::ptr::null_mut(), - ::std::ptr::null_mut(), - ::std::ptr::null_mut(), - file_system.as_mut_ptr(), - file_system.len() as DWORD, - ) == 0 - { - continue; - } - let mut pos = 0; - for x in name.iter() { - if *x == 0 { - break; + (0..size_of::() * 8) + .into_par_iter() + .filter_map(|x| { + if (drives >> x) & 1 == 0 { + return None; } - pos += 1; - } - let name = String::from_utf16_lossy(&name[..pos]); - let name = OsStr::new(&name); + let mount_point = [b'A' as u16 + x as u16, b':' as u16, b'\\' as u16, 0]; + if GetDriveTypeW(mount_point.as_ptr()) != DRIVE_FIXED { + return None; + } + let mut name = [0u16; MAX_PATH + 1]; + let mut file_system = [0u16; 32]; + if GetVolumeInformationW( + mount_point.as_ptr(), + name.as_mut_ptr(), + name.len() as DWORD, + ::std::ptr::null_mut(), + ::std::ptr::null_mut(), + ::std::ptr::null_mut(), + file_system.as_mut_ptr(), + file_system.len() as DWORD, + ) == 0 + { + return None; + } + let mut pos = 0; + for x in name.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let name = String::from_utf16_lossy(&name[..pos]); + let name = OsStr::new(&name); - pos = 0; - for x in file_system.iter() { - if *x == 0 { - break; + pos = 0; + for x in file_system.iter() { + if *x == 0 { + break; + } + pos += 1; } - pos += 1; - } - let file_system: Vec = file_system[..pos].iter().map(|x| *x as u8).collect(); + let file_system: Vec = file_system[..pos].iter().map(|x| *x as u8).collect(); - let drive_name = [ - b'\\' as u16, - b'\\' as u16, - b'.' as u16, - b'\\' as u16, - b'A' as u16 + x as u16, - b':' as u16, - 0, - ]; - let handle = open_drive(&drive_name, 0); - if handle == INVALID_HANDLE_VALUE { - disks.push(new_disk( - name, - &mount_point, - &file_system, - DiskType::Unknown(-1), + let drive_name = [ + b'\\' as u16, + b'\\' as u16, + b'.' as u16, + b'\\' as u16, + b'A' as u16 + x as u16, + b':' as u16, 0, - )); - CloseHandle(handle); - continue; - } - let disk_size = get_drive_size(handle); - /*let mut spq_trim: ffi::STORAGE_PROPERTY_QUERY = ::std::mem::zeroed(); - spq_trim.PropertyId = ffi::StorageDeviceTrimProperty; - spq_trim.QueryType = ffi::PropertyStandardQuery; - let mut dtd: ffi::DEVICE_TRIM_DESCRIPTOR = ::std::mem::zeroed();*/ - #[allow(non_snake_case)] - #[repr(C)] - struct STORAGE_PROPERTY_QUERY { - PropertyId: i32, - QueryType: i32, - AdditionalParameters: [BYTE; 1], - } - #[allow(non_snake_case)] - #[repr(C)] - struct DEVICE_TRIM_DESCRIPTOR { - Version: DWORD, - Size: DWORD, - TrimEnabled: BOOLEAN, - } - let mut spq_trim = STORAGE_PROPERTY_QUERY { - PropertyId: 8i32, - QueryType: 0i32, - AdditionalParameters: [0], - }; - let mut dtd: DEVICE_TRIM_DESCRIPTOR = ::std::mem::zeroed(); + ]; + let handle = open_drive(&drive_name, 0); + if handle == INVALID_HANDLE_VALUE { + CloseHandle(handle); + return Some(new_disk( + name, + &mount_point, + &file_system, + DiskType::Unknown(-1), + 0, + )); + } + let disk_size = get_drive_size(handle); + /*let mut spq_trim: ffi::STORAGE_PROPERTY_QUERY = ::std::mem::zeroed(); + spq_trim.PropertyId = ffi::StorageDeviceTrimProperty; + spq_trim.QueryType = ffi::PropertyStandardQuery; + let mut dtd: ffi::DEVICE_TRIM_DESCRIPTOR = ::std::mem::zeroed();*/ + #[allow(non_snake_case)] + #[repr(C)] + struct STORAGE_PROPERTY_QUERY { + PropertyId: i32, + QueryType: i32, + AdditionalParameters: [BYTE; 1], + } + #[allow(non_snake_case)] + #[repr(C)] + struct DEVICE_TRIM_DESCRIPTOR { + Version: DWORD, + Size: DWORD, + TrimEnabled: BOOLEAN, + } + let mut spq_trim = STORAGE_PROPERTY_QUERY { + PropertyId: 8i32, + QueryType: 0i32, + AdditionalParameters: [0], + }; + let mut dtd: DEVICE_TRIM_DESCRIPTOR = ::std::mem::zeroed(); - let mut dw_size = 0; - if DeviceIoControl( - handle, - IOCTL_STORAGE_QUERY_PROPERTY, - &mut spq_trim as *mut STORAGE_PROPERTY_QUERY as *mut c_void, - size_of::() as DWORD, - &mut dtd as *mut DEVICE_TRIM_DESCRIPTOR as *mut c_void, - size_of::() as DWORD, - &mut dw_size, - ::std::ptr::null_mut(), - ) == 0 - || dw_size != size_of::() as DWORD - { - disks.push(new_disk( + let mut dw_size = 0; + if DeviceIoControl( + handle, + IOCTL_STORAGE_QUERY_PROPERTY, + &mut spq_trim as *mut STORAGE_PROPERTY_QUERY as *mut c_void, + size_of::() as DWORD, + &mut dtd as *mut DEVICE_TRIM_DESCRIPTOR as *mut c_void, + size_of::() as DWORD, + &mut dw_size, + ::std::ptr::null_mut(), + ) == 0 + || dw_size != size_of::() as DWORD + { + CloseHandle(handle); + return Some(new_disk( + name, + &mount_point, + &file_system, + DiskType::Unknown(-1), + disk_size, + )); + } + let is_ssd = dtd.TrimEnabled != 0; + CloseHandle(handle); + Some(new_disk( name, &mount_point, &file_system, - DiskType::Unknown(-1), + if is_ssd { DiskType::SSD } else { DiskType::HDD }, disk_size, - )); - CloseHandle(handle); - continue; - } - let is_ssd = dtd.TrimEnabled != 0; - CloseHandle(handle); - disks.push(new_disk( - name, - &mount_point, - &file_system, - if is_ssd { DiskType::SSD } else { DiskType::HDD }, - disk_size, - )); - } - disks + )) + }) + .collect::>() } #[allow(non_snake_case)] @@ -348,11 +315,10 @@ pub fn add_counter( query: &mut Query, keys: &mut Option, counter_name: String, - value: CounterValue, ) { let mut full = s.encode_utf16().collect::>(); full.push(0); - if query.add_counter(&counter_name, full.clone(), value) { + if query.add_counter(&counter_name, full.clone()) { *keys = Some(KeyHandler::new(counter_name, full)); } } diff --git a/tests/disk_list.rs b/tests/disk_list.rs index d59e4f141..9b49dbfd5 100644 --- a/tests/disk_list.rs +++ b/tests/disk_list.rs @@ -12,5 +12,5 @@ fn test_disks() { let s = sysinfo::System::new(); println!("total memory: {}", s.get_total_memory()); - println!("total cpu cores: {}", s.get_processor_list().len()); + println!("total cpu cores: {}", s.get_processors().len()); } diff --git a/tests/network.rs b/tests/network.rs new file mode 100644 index 000000000..f5bccd25a --- /dev/null +++ b/tests/network.rs @@ -0,0 +1,20 @@ +// +// Sysinfo +// +// Copyright (c) 2020 Guillaume Gomez +// + +// This test is used to ensure that the processors are loaded whatever the method +// used to initialize `System`. + +extern crate sysinfo; + +#[test] +fn test_processor() { + use sysinfo::{NetworksExt, SystemExt}; + + let s = sysinfo::System::new(); + assert_eq!(s.get_networks().iter().count(), 0); + let s = sysinfo::System::new_all(); + assert!(s.get_networks().iter().count() > 0); +} diff --git a/tests/process.rs b/tests/process.rs index 75fe8a6b8..fce70d2c9 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -6,16 +6,31 @@ extern crate sysinfo; +#[cfg(not(windows))] +use sysinfo::ProcessExt; +use sysinfo::SystemExt; + #[test] fn test_process() { - use sysinfo::{ProcessExt, SystemExt}; - let mut s = sysinfo::System::new(); + assert_eq!(s.get_processes().len(), 0); s.refresh_processes(); - assert!(s.get_process_list().len() != 0); + assert!(s.get_processes().len() != 0); #[cfg(not(windows))] assert!(s - .get_process_list() + .get_processes() .values() .any(|p| p.exe().to_str().unwrap_or_else(|| "").len() != 0)); } + +#[test] +fn test_process_refresh() { + let mut s = sysinfo::System::new(); + assert_eq!(s.get_processes().len(), 0); + s.refresh_process(sysinfo::get_current_pid().expect("failed to get current pid")); + assert_eq!( + s.get_process(sysinfo::get_current_pid().expect("failed to get current pid")) + .is_some(), + true + ); +} diff --git a/tests/processor.rs b/tests/processor.rs new file mode 100644 index 000000000..39cafd696 --- /dev/null +++ b/tests/processor.rs @@ -0,0 +1,20 @@ +// +// Sysinfo +// +// Copyright (c) 2020 Guillaume Gomez +// + +// This test is used to ensure that the processors are loaded whatever the method +// used to initialize `System`. + +extern crate sysinfo; + +#[test] +fn test_processor() { + use sysinfo::SystemExt; + + let s = sysinfo::System::new(); + assert!(s.get_processors().len() > 0); + let s = sysinfo::System::new_all(); + assert!(s.get_processors().len() > 0); +}