diff --git a/Cargo.lock b/Cargo.lock index d4bce46..38a9998 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,7 @@ dependencies = [ "serde_json", "static_vcruntime", "sudo", + "sysinfo", "tar", "tls-api", "tls-api-native-tls", @@ -2504,6 +2505,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "ntapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +dependencies = [ + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -3820,6 +3830,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "sysinfo" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d08ba83d6dde63d053e42d7230f0dc7f8d8efeb8d30d3681580d158156461ba" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "winapi", +] + [[package]] name = "system-deps" version = "6.0.3" diff --git a/Cargo.toml b/Cargo.toml index 78f2745..7a6dffe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ regex = { version = "1.6.0", default-features = false, features = ["perf"] } rfd = "0.10.0" serde = { version = "1.0.145", features = ["rc", "derive"] } serde_json = "1.0" +sysinfo = { version = "0.27.0", default-features = false } tls-api = "0.9.0" tls-api-native-tls = "0.9.0" tokio = { version = "1.21.2", features = ["rt", "time", "macros", "process"] } diff --git a/src/constants.rs b/src/constants.rs index 8fdf566..11acba7 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -69,6 +69,7 @@ pub const RED: egui::Color32 = egui::Color32::from_rgb(230, 50, 50); pub const GREEN: egui::Color32 = egui::Color32::from_rgb(100, 230, 100); pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(230, 230, 100); pub const BRIGHT_YELLOW: egui::Color32 = egui::Color32::from_rgb(250, 250, 100); +pub const BONE: egui::Color32 = egui::Color32::from_rgb(190, 190, 190); // In between LIGHT_GRAY <-> GRAY pub const WHITE: egui::Color32 = egui::Color32::WHITE; pub const GRAY: egui::Color32 = egui::Color32::GRAY; pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY; @@ -110,6 +111,14 @@ pub const OS: &str = "🐧 Linux"; pub const OS_NAME: &str = "Linux"; // Tooltips +// Status +pub const STATUS_GUPAX_UPTIME: &str = "How long Gupax has been online"; +pub const STATUS_GUPAX_CPU_USAGE: &str = "How much CPU Gupax is currently using. This accounts for all your threads (it is out of 100%)"; +pub const STATUS_GUPAX_MEMORY_USAGE: &str = "How much memory Gupax is currently using in Megabytes"; +pub const STATUS_GUPAX_SYSTEM_CPU_USAGE: &str = "How much CPU your entire system is currently using. This accounts for all your threads (it is out of 100%)"; +pub const STATUS_GUPAX_SYSTEM_MEMORY: &str = "How much memory your entire system has (including swap) and is currently using in Gigabytes"; +pub const STATUS_GUPAX_SYSTEM_CPU_MODEL: &str = "The detected model of your system's CPU and its current frequency"; + // Gupax pub const GUPAX_UPDATE: &str = "Check for updates on Gupax, P2Pool, and XMRig via GitHub's API and upgrade automatically"; pub const GUPAX_AUTO_UPDATE: &str = "Automatically check for updates at startup"; diff --git a/src/disk.rs b/src/disk.rs index 5e7240e..2e420d4 100644 --- a/src/disk.rs +++ b/src/disk.rs @@ -142,8 +142,8 @@ impl State { simple: true, auto_update: true, auto_node: true, - auto_p2pool: true, - auto_xmrig: true, + auto_p2pool: false, + auto_xmrig: false, ask_before_quit: true, save_before_quit: true, #[cfg(not(target_os = "macos"))] diff --git a/src/gupax.rs b/src/gupax.rs index 066df38..8cdc6e5 100644 --- a/src/gupax.rs +++ b/src/gupax.rs @@ -230,7 +230,7 @@ impl Gupax { })}); // Saved [Tab] ui.group(|ui| { - let height = ui.available_height()/2.0; + let height = ui.available_height()/1.85; let width = (width/5.0)-(SPACE*1.93); ui.horizontal(|ui| { if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::About, "About")).on_hover_text(GUPAX_TAB_ABOUT).clicked() { self.tab = Tab::About; } diff --git a/src/helper.rs b/src/helper.rs index 1c7f4b6..8ca64e3 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -46,6 +46,7 @@ use crate::{ constants::*, SudoState, }; +use sysinfo::SystemExt; use serde::{Serialize,Deserialize}; use num_format::{Buffer,Locale}; use log::*; @@ -67,7 +68,8 @@ const GUI_OUTPUT_LEEWAY: usize = MAX_GUI_OUTPUT_BYTES - 1000; // A meta struct holding all the data that gets processed in this thread pub struct Helper { pub instant: Instant, // Gupax start as an [Instant] - pub human_time: HumanTime, // Gupax uptime formatting for humans + pub uptime: HumanTime, // Gupax uptime formatting for humans + pub pub_sys: Arc>, // The public API for [sysinfo] that the [Status] tab reads from pub p2pool: Arc>, // P2Pool process state pub xmrig: Arc>, // XMRig process state pub gui_api_p2pool: Arc>, // P2Pool API state (for GUI thread) @@ -88,6 +90,30 @@ pub struct Helper { // it's the helpers job to lock everything, and move the watchdog [Pub*Api]s // on a 1-second interval into the [GUI]'s [Pub*Api] struct, atomically. +//---------------------------------------------------------------------------------------------------- +#[derive(Debug,Clone)] +pub struct Sys { + pub gupax_uptime: String, + pub gupax_cpu_usage: String, + pub gupax_memory_used_mb: String, + pub system_cpu_model: String, + pub system_memory: String, + pub system_cpu_usage: String, +} + +impl Sys { + pub fn new() -> Self { + Self { + gupax_uptime: "0 seconds".to_string(), + gupax_cpu_usage: "???%".to_string(), + gupax_memory_used_mb: "??? megabytes".to_string(), + system_cpu_usage: "???%".to_string(), + system_memory: "???GB / ???GB".to_string(), + system_cpu_model: "???".to_string(), + } + } +} + //---------------------------------------------------------------------------------------------------- [Process] Struct // This holds all the state of a (child) process. // The main GUI thread will use this to display console text, online state, etc. @@ -191,10 +217,11 @@ use tokio::io::{BufReader,AsyncBufReadExt}; impl Helper { //---------------------------------------------------------------------------------------------------- General Functions - pub fn new(instant: std::time::Instant, p2pool: Arc>, xmrig: Arc>, gui_api_p2pool: Arc>, gui_api_xmrig: Arc>, img_p2pool: Arc>, img_xmrig: Arc>) -> Self { + pub fn new(instant: std::time::Instant, pub_sys: Arc>, p2pool: Arc>, xmrig: Arc>, gui_api_p2pool: Arc>, gui_api_xmrig: Arc>, img_p2pool: Arc>, img_xmrig: Arc>) -> Self { Self { instant, - human_time: HumanTime::into_human(instant.elapsed()), + pub_sys, + uptime: HumanTime::into_human(instant.elapsed()), priv_api_p2pool: Arc::new(Mutex::new(PrivP2poolApi::new())), priv_api_xmrig: Arc::new(Mutex::new(PrivXmrigApi::new())), pub_api_p2pool: Arc::new(Mutex::new(PubP2poolApi::new())), @@ -255,22 +282,6 @@ impl Helper { } //---------------------------------------------------------------------------------------------------- P2Pool specific - // Read P2Pool's API file. - fn read_p2pool_api(path: &std::path::PathBuf) -> Result { - match std::fs::read_to_string(path) { - Ok(s) => Ok(s), - Err(e) => { warn!("P2Pool API | [{}] read error: {}", path.display(), e); Err(e) }, - } - } - - // Deserialize the above [String] into a [PrivP2poolApi] - fn str_to_priv_p2pool_api(string: &str) -> Result { - match serde_json::from_str::(string) { - Ok(a) => Ok(a), - Err(e) => { warn!("P2Pool API | Could not deserialize API data: {}", e); Err(e) }, - } - } - // Just sets some signals for the watchdog thread to pick up on. pub fn stop_p2pool(helper: &Arc>) { info!("P2Pool | Attempting to stop..."); @@ -513,9 +524,9 @@ impl Helper { PubP2poolApi::update_from_output(&pub_api, &output_full, &output_buf, start.elapsed(), ®ex); // Read API file into string - if let Ok(string) = Self::read_p2pool_api(&path) { + if let Ok(string) = PrivP2poolApi::read_p2pool_api(&path) { // Deserialize - if let Ok(s) = Self::str_to_priv_p2pool_api(&string) { + if let Ok(s) = PrivP2poolApi::str_to_priv_p2pool_api(&string) { // Update the structs. PubP2poolApi::update_from_priv(&pub_api, &priv_api); } @@ -603,7 +614,7 @@ impl Helper { pub fn start_xmrig(helper: &Arc>, state: &crate::disk::Xmrig, path: &std::path::PathBuf, sudo: Arc>) { helper.lock().unwrap().xmrig.lock().unwrap().state = ProcessState::Middle; - let args = Self::build_xmrig_args_and_mutate_img(helper, state, path); + let (args, api_ip_port) = Self::build_xmrig_args_and_mutate_img(helper, state, path); // Print arguments & user settings to console crate::disk::print_dash(&format!("XMRig | Launch arguments: {:#?}", args)); @@ -616,15 +627,16 @@ impl Helper { let priv_api = Arc::clone(&helper.lock().unwrap().priv_api_xmrig); let path = path.clone(); thread::spawn(move || { - Self::spawn_xmrig_watchdog(process, gui_api, pub_api, priv_api, args, path, sudo); + Self::spawn_xmrig_watchdog(process, gui_api, pub_api, priv_api, args, path, sudo, api_ip_port); }); } // Takes in some [State/Xmrig] and parses it to build the actual command arguments. // Returns the [Vec] of actual arguments, and mutates the [ImgXmrig] for the main GUI thread // It returns a value... and mutates a deeply nested passed argument... this is some pretty bad code... - pub fn build_xmrig_args_and_mutate_img(helper: &Arc>, state: &crate::disk::Xmrig, path: &std::path::PathBuf) -> Vec { + pub fn build_xmrig_args_and_mutate_img(helper: &Arc>, state: &crate::disk::Xmrig, path: &std::path::PathBuf) -> (Vec, String) { let mut args = Vec::with_capacity(500); + let mut api_ip_port = String::with_capacity(15); let path = path.clone(); // The actual binary we're executing is [sudo], technically // the XMRig path is just an argument to sudo, so add it. @@ -651,6 +663,7 @@ impl Helper { threads: state.current_threads.to_string(), url: "127.0.0.1:3333 (Local P2Pool)".to_string(), }; + api_ip_port = "127.0.0.1:18088".to_string(); // [Advanced] } else { @@ -689,9 +702,10 @@ impl Helper { url, threads: state.current_threads.to_string(), }; + api_ip_port = format!("{}:{}", api_ip, api_port); } } - args + (args, api_ip_port) } // We actually spawn [sudo] on Unix, with XMRig being the argument. @@ -712,8 +726,10 @@ impl Helper { cmd } - // The P2Pool watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works. - fn spawn_xmrig_watchdog(process: Arc>, gui_api: Arc>, pub_api: Arc>, priv_api: Arc>, args: Vec, mut path: std::path::PathBuf, sudo: Arc>) { + // The XMRig watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works. + // This isn't actually async, a tokio runtime is unfortunately needed because [Hyper] is an async library (HTTP API calls) + #[tokio::main] + async fn spawn_xmrig_watchdog(process: Arc>, gui_api: Arc>, pub_api: Arc>, priv_api: Arc>, args: Vec, mut path: std::path::PathBuf, sudo: Arc>, api_ip_port: String) { // 1a. Create PTY let pty = portable_pty::native_pty_system(); let mut pair = pty.openpty(portable_pty::PtySize { @@ -758,9 +774,7 @@ impl Helper { let output_full = Arc::clone(&process.lock().unwrap().output_full); let output_buf = Arc::clone(&process.lock().unwrap().output_buf); -// path.pop(); -// path.push(P2POOL_API_PATH); -// let regex = P2poolRegex::new(); + let client: hyper::Client = hyper::Client::builder().build(hyper::client::HttpConnector::new()); let start = process.lock().unwrap().start; // 5. Loop as watchdog @@ -838,15 +852,11 @@ impl Helper { // Always update from output PubXmrigApi::update_from_output(&pub_api, &output_buf, start.elapsed()); -// -// // Read API file into string -// if let Ok(string) = Self::read_xmrig_api(&path) { -// // Deserialize -// if let Ok(s) = Self::str_to_priv_xmrig_api(&string) { -// // Update the structs. -// PubP2poolApi::update_from_priv(&pub_api, &priv_api); -// } -// } + + // Send an HTTP API request + if let Ok(priv_api) = PrivXmrigApi::request_xmrig_api(client.clone(), &api_ip_port).await { + PubXmrigApi::from_priv(&mut pub_api.lock().unwrap(), priv_api); + } // Check if logs need resetting Self::check_reset_output_full(&output_full, ProcessName::Xmrig); @@ -863,9 +873,44 @@ impl Helper { } //---------------------------------------------------------------------------------------------------- The "helper" + fn update_pub_sys_from_sysinfo(sysinfo: &sysinfo::System, pub_sys: &mut Sys, pid: &sysinfo::Pid, helper: &Helper, max_threads: usize) { + use sysinfo::{CpuExt,ProcessExt,NetworkExt,NetworksExt}; + let gupax_uptime = helper.uptime.to_string(); + let cpu = &sysinfo.cpus()[0]; + let gupax_cpu_usage = format!("{:.2}%", sysinfo.process(*pid).unwrap().cpu_usage()/(max_threads as f32)); + let gupax_memory_used_mb = HumanNumber::from_u64(sysinfo.process(*pid).unwrap().memory()/1_000_000); + let gupax_memory_used_mb = format!("{} megabytes", gupax_memory_used_mb); + let system_cpu_model = format!("{} ({}MHz)", cpu.brand(), cpu.frequency()); + let system_memory = { + let used = (sysinfo.used_memory() as f64)/1_000_000_000.0; + let total = (sysinfo.total_memory() as f64)/1_000_000_000.0; + format!("{:.3} GB / {:.3} GB", used, total) + }; + let system_cpu_usage = { + let mut total: f32 = 0.0; + for cpu in sysinfo.cpus() { + total += cpu.cpu_usage(); + } + format!("{:.2}%", total/(max_threads as f32)) + }; + *pub_sys = Sys { + gupax_uptime, + gupax_cpu_usage, + gupax_memory_used_mb, + system_cpu_usage, + system_memory, + system_cpu_model, + ..*pub_sys + }; + } + // The "helper" thread. Syncs data between threads here and the GUI. - pub fn spawn_helper(helper: &Arc>) { + pub fn spawn_helper(helper: &Arc>, mut sysinfo: sysinfo::System, pid: sysinfo::Pid, max_threads: usize) { let mut helper = Arc::clone(helper); + let mut pub_sys = Arc::clone(&helper.lock().unwrap().pub_sys); + let sysinfo_cpu = sysinfo::CpuRefreshKind::everything(); + let sysinfo_processes = sysinfo::ProcessRefreshKind::new().with_cpu(); + thread::spawn(move || { info!("Helper | Hello from helper thread! Entering loop where I will spend the rest of my days..."); // Begin loop @@ -875,31 +920,37 @@ impl Helper { // 2. Lock... EVERYTHING! let mut lock = helper.lock().unwrap(); + // Calculate Gupax's uptime always. + lock.uptime = HumanTime::into_human(lock.instant.elapsed()); let mut gui_api_p2pool = lock.gui_api_p2pool.lock().unwrap(); let mut gui_api_xmrig = lock.gui_api_xmrig.lock().unwrap(); let mut pub_api_p2pool = lock.pub_api_p2pool.lock().unwrap(); let mut pub_api_xmrig = lock.pub_api_xmrig.lock().unwrap(); let p2pool = lock.p2pool.lock().unwrap(); let xmrig = lock.xmrig.lock().unwrap(); - // Calculate Gupax's uptime always. - let human_time = HumanTime::into_human(lock.instant.elapsed()); + let mut lock_pub_sys = pub_sys.lock().unwrap(); // If [P2Pool] is alive... if p2pool.is_alive() { PubP2poolApi::combine_gui_pub_api(&mut gui_api_p2pool, &mut pub_api_p2pool); } // If [XMRig] is alive... if xmrig.is_alive() { PubXmrigApi::combine_gui_pub_api(&mut gui_api_xmrig, &mut pub_api_xmrig); } - // 2. Drop... (almost) EVERYTHING... IN REVERSE! + // 2. Selectively refresh [sysinfo] for only what we need (better performance). + sysinfo.refresh_cpu_specifics(sysinfo_cpu); + sysinfo.refresh_processes_specifics(sysinfo_processes); + sysinfo.refresh_memory(); + Self::update_pub_sys_from_sysinfo(&sysinfo, &mut lock_pub_sys, &pid, &lock, max_threads); + + // 3. Drop... (almost) EVERYTHING... IN REVERSE! + drop(lock_pub_sys); drop(xmrig); drop(p2pool); drop(pub_api_xmrig); drop(pub_api_p2pool); drop(gui_api_xmrig); drop(gui_api_p2pool); - // Update the time... then drop :) - lock.human_time = human_time; drop(lock); - // 3. Calculate if we should sleep or not. + // 4. Calculate if we should sleep or not. // If we should sleep, how long? let elapsed = start.elapsed().as_millis(); if elapsed < 1000 { @@ -908,7 +959,7 @@ impl Helper { std::thread::sleep(std::time::Duration::from_millis((1000-elapsed) as u64)); } - // 4. End loop + // 5. End loop } // 5. Something has gone terribly wrong if the helper exited this loop. @@ -1220,7 +1271,7 @@ impl PubP2poolApi { let buf = std::mem::take(&mut pub_api.output); *gui_api = Self { output, - ..std::mem::take(&mut *pub_api) + ..std::mem::take(pub_api) }; if !buf.is_empty() { gui_api.output.push_str(&buf); } } @@ -1327,6 +1378,22 @@ impl PrivP2poolApi { connections: 0, } } + + // Read P2Pool's API file to a [String]. + fn read_p2pool_api(path: &std::path::PathBuf) -> Result { + match std::fs::read_to_string(path) { + Ok(s) => Ok(s), + Err(e) => { warn!("P2Pool API | [{}] read error: {}", path.display(), e); Err(e) }, + } + } + + // Deserialize the above [String] into a [PrivP2poolApi] + fn str_to_priv_p2pool_api(string: &str) -> Result { + match serde_json::from_str::(string) { + Ok(a) => Ok(a), + Err(e) => { warn!("P2Pool API | Could not deserialize API data: {}", e); Err(e) }, + } + } } //---------------------------------------------------------------------------------------------------- [ImgXmrig] @@ -1385,7 +1452,7 @@ impl PubXmrigApi { let buf = std::mem::take(&mut pub_api.output); *gui_api = Self { output, - ..std::mem::take(&mut *pub_api) + ..std::mem::take(pub_api) }; if !buf.is_empty() { gui_api.output.push_str(&buf); } } @@ -1403,9 +1470,8 @@ impl PubXmrigApi { } // Formats raw private data into ready-to-print human readable version. - fn from_priv(private: PrivXmrigApi, output: String) -> Self { - Self { - output: output.clone(), + fn from_priv(public: &mut Self, private: PrivXmrigApi) { + *public = Self { uptime: HumanTime::new(), worker_id: private.worker_id, resources: HumanNumber::from_load(private.resources.load_average), @@ -1414,6 +1480,7 @@ impl PubXmrigApi { diff: HumanNumber::from_u128(private.connection.diff), accepted: HumanNumber::from_u128(private.connection.accepted), rejected: HumanNumber::from_u128(private.connection.rejected), + ..std::mem::take(public) } } } @@ -1440,6 +1507,16 @@ impl PrivXmrigApi { hashrate: Hashrate::new(), } } + // Send an HTTP request to XMRig's API, serialize it into [Self] and return it + async fn request_xmrig_api(client: hyper::Client, api_ip_port: &str) -> Result { + let request = hyper::Request::builder() + .method("GET") + .uri("http://".to_string() + api_ip_port + "/1/summary") + .body(hyper::Body::empty())?; + let mut response = tokio::time::timeout(std::time::Duration::from_millis(500), client.request(request)).await?; + let body = hyper::body::to_bytes(response?.body_mut()).await?; + Ok(serde_json::from_slice::(&body)?) + } } #[derive(Debug, Serialize, Deserialize, Clone, Copy)] diff --git a/src/main.rs b/src/main.rs index 450b6c4..8965da9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,6 +49,15 @@ use std::{ time::Instant, path::PathBuf, }; +// Sysinfo (this controls which info we get) +use sysinfo::{ + NetworkExt, + CpuExt, + ProcessExt, + System, + SystemExt, + PidExt, +}; // Modules mod ferris; mod constants; @@ -112,6 +121,7 @@ pub struct App { // This holds everything related to the data processed by the "helper thread". // This includes the "helper" threads public P2Pool/XMRig's API. helper: Arc>, // [Helper] state, mostly for Gupax uptime + pub_sys: Arc>, // [Sys] state, read by [Status], mutated by [Helper] p2pool: Arc>, // [P2Pool] process state xmrig: Arc>, // [XMRig] process state p2pool_api: Arc>, // Public ready-to-print P2Pool API made by the "helper" thread @@ -126,6 +136,8 @@ pub struct App { // State from [--flags] no_startup: bool, // Static stuff + pid: sysinfo::Pid, // Gupax's PID + max_threads: usize, // Max amount of detected system threads now: Instant, // Internal timer exe: String, // Path for [Gupax] binary dir: String, // Directory [Gupax] binary is in @@ -160,6 +172,17 @@ impl App { let xmrig_api = Arc::new(Mutex::new(PubXmrigApi::new())); let p2pool_img = Arc::new(Mutex::new(ImgP2pool::new())); let xmrig_img = Arc::new(Mutex::new(ImgXmrig::new())); + + // We give this to the [Helper] thread. + let mut sysinfo = sysinfo::System::new_with_specifics( + sysinfo::RefreshKind::new() + .with_cpu(sysinfo::CpuRefreshKind::everything()) + .with_processes(sysinfo::ProcessRefreshKind::new().with_cpu()) + .with_memory()); + sysinfo.refresh_all(); + let pid = sysinfo::get_current_pid().unwrap(); + let pub_sys = Arc::new(Mutex::new(Sys::new())); + let mut app = Self { tab: Tab::default(), ping: Arc::new(Mutex::new(Ping::new())), @@ -177,7 +200,7 @@ impl App { restart: Arc::new(Mutex::new(Restart::No)), diff: false, error_state: ErrorState::new(), - helper: Arc::new(Mutex::new(Helper::new(now, p2pool.clone(), xmrig.clone(), p2pool_api.clone(), xmrig_api.clone(), p2pool_img.clone(), xmrig_img.clone()))), + helper: Arc::new(Mutex::new(Helper::new(now, pub_sys.clone(), p2pool.clone(), xmrig.clone(), p2pool_api.clone(), xmrig_api.clone(), p2pool_img.clone(), xmrig_img.clone()))), p2pool, xmrig, p2pool_api, @@ -190,6 +213,9 @@ impl App { resizing: false, alpha: 0, no_startup: false, + pub_sys, + pid, + max_threads: num_cpus::get(), now, admin: false, exe: String::new(), @@ -291,7 +317,7 @@ impl App { //---------------------------------------------------------------------------------------------------- let mut og = app.og.lock().unwrap(); // Lock [og] // Handle max threads - og.xmrig.max_threads = num_cpus::get(); + og.xmrig.max_threads = app.max_threads; let current = og.xmrig.current_threads; let max = og.xmrig.max_threads; if current > max { @@ -340,7 +366,7 @@ impl App { // Spawn the "Helper" thread. info!("Helper | Spawning helper thread..."); - Helper::spawn_helper(&app.helper); + Helper::spawn_helper(&app.helper, sysinfo, app.pid, app.max_threads); info!("Helper ... OK"); // Check for privilege. Should be Admin on [Windows] and NOT root on Unix. @@ -617,7 +643,7 @@ fn init_auto(app: &mut App) { Helper::start_p2pool(&app.helper, &app.state.p2pool, &app.state.gupax.absolute_p2pool_path); } } else { - info!("Skipping auto-xmrig..."); + info!("Skipping auto-p2pool..."); } // [Auto-XMRig] @@ -1044,26 +1070,24 @@ impl eframe::App for App { TopBottomPanel::top("top").show(ctx, |ui| { let width = (self.width - (SPACE*10.0))/5.0; let height = self.height/12.0; - ui.group(|ui| { - ui.add_space(4.0); - ui.horizontal(|ui| { - let style = ui.style_mut(); - style.override_text_style = Some(Name("Tab".into())); - style.visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100); - style.visuals.selection.bg_fill = Color32::from_rgb(255, 120, 120); - style.visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) }; - if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::About, "About")).clicked() { self.tab = Tab::About; } - ui.separator(); - if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Status, "Status")).clicked() { self.tab = Tab::Status; } - ui.separator(); - if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Gupax, "Gupax")).clicked() { self.tab = Tab::Gupax; } - ui.separator(); - if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool")).clicked() { self.tab = Tab::P2pool; } - ui.separator(); - if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig")).clicked() { self.tab = Tab::Xmrig; } - }); - ui.add_space(4.0); + ui.add_space(4.0); + ui.horizontal(|ui| { + let style = ui.style_mut(); + style.override_text_style = Some(Name("Tab".into())); + style.visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100); + style.visuals.selection.bg_fill = Color32::from_rgb(255, 120, 120); + style.visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) }; + if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::About, "About")).clicked() { self.tab = Tab::About; } + ui.separator(); + if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Status, "Status")).clicked() { self.tab = Tab::Status; } + ui.separator(); + if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Gupax, "Gupax")).clicked() { self.tab = Tab::Gupax; } + ui.separator(); + if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool")).clicked() { self.tab = Tab::P2pool; } + ui.separator(); + if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig")).clicked() { self.tab = Tab::Xmrig; } }); + ui.add_space(4.0); }); // Bottom: app info + state/process buttons @@ -1312,7 +1336,7 @@ impl eframe::App for App { }); } Tab::Status => { - Status::show(self, self.width, self.height, ctx, ui); + Status::show(&self.pub_sys, &self.p2pool_api, &self.xmrig_api, &self.p2pool_img, &self.xmrig_img, self.width, self.height, ctx, ui); } Tab::Gupax => { Gupax::show(&mut self.state.gupax, &self.og, &self.state_path, &self.update, &self.file_window, &mut self.error_state, &self.restart, self.width, self.height, frame, ctx, ui); diff --git a/src/status.rs b/src/status.rs index 818a59e..8101212 100644 --- a/src/status.rs +++ b/src/status.rs @@ -15,56 +15,65 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::App; -use egui::{containers::*, *}; +use crate::{ + Helper, + PubP2poolApi, + PubXmrigApi, + ImgP2pool, + ImgXmrig, + constants::*, + Sys, +}; +use std::sync::{Arc,Mutex}; +use egui::{ + containers::*, + Label,RichText,TextStyle +}; // Main data structure for the Status tab #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Status { - -} +pub struct Status {} impl Status { - pub fn show(_app: &mut App, _width: f32, _height: f32, _ctx: &egui::Context, ui: &mut egui::Ui) { - let color = if ui.visuals().dark_mode { - Color32::from_additive_luminance(196) - } else { - Color32::from_black_alpha(240) - }; - - Frame::canvas(ui.style()).show(ui, |ui| { - ui.ctx().request_repaint(); - let time = ui.input().time; - - let desired_size = ui.available_width() * vec2(1.0, 0.3); - let (_id, rect) = ui.allocate_space(desired_size); - - let to_screen = - emath::RectTransform::from_to(Rect::from_x_y_ranges(0.0..=1.0, -1.0..=1.0), rect); - - let mut shapes = vec![]; - - for &mode in &[2, 3, 5] { - let mode = mode as f64; - let n = 120; - let speed = 1.5; - - let points: Vec = (0..=n) - .map(|i| { - let t = i as f64 / (n as f64); - let amp = (time * speed * mode).sin() / mode; - let y = amp * (t * std::f64::consts::TAU / 2.0 * mode).sin(); - to_screen * pos2(t as f32, y as f32) - }) - .collect(); - - let thickness = 10.0 / mode as f32; - shapes.push(epaint::Shape::line(points, Stroke::new(thickness, color))); - } - - ui.painter().extend(shapes); - }); - ui.label("WIP"); - ui.label("Enjoy these cool lines."); - } +pub fn show(sys: &Arc>, p2pool_api: &Arc>, xmrig_api: &Arc>, p2pool_img: &Arc>, xmrig_img: &Arc>, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) { + let width = (width/3.0)-(SPACE*1.666); + let min_height = height/1.14; + let height = height/20.0; + ui.horizontal(|ui| { + // [Gupax] + ui.group(|ui| { ui.vertical(|ui| { + ui.set_min_height(min_height); + ui.add_sized([width, height*2.0], Label::new(RichText::new("[Gupax]").color(LIGHT_GRAY).text_style(TextStyle::Name("MonospaceLarge".into())))); + // Uptime + ui.add_sized([width, height], Label::new(RichText::new("Uptime").underline().color(BONE))).on_hover_text(STATUS_GUPAX_UPTIME); + ui.add_sized([width, height], Label::new(format!("{}", sys.lock().unwrap().gupax_uptime))); + ui.add_sized([width, height], Label::new(RichText::new("Gupax CPU").underline().color(BONE))).on_hover_text(STATUS_GUPAX_CPU_USAGE); + ui.add_sized([width, height], Label::new(format!("{}", sys.lock().unwrap().gupax_cpu_usage))); + ui.add_sized([width, height], Label::new(RichText::new("Gupax Memory").underline().color(BONE))).on_hover_text(STATUS_GUPAX_MEMORY_USAGE); + ui.add_sized([width, height], Label::new(format!("{}", sys.lock().unwrap().gupax_memory_used_mb))); + ui.add_sized([width, height], Label::new(RichText::new("System CPU").underline().color(BONE))).on_hover_text(STATUS_GUPAX_SYSTEM_CPU_USAGE); + ui.add_sized([width, height], Label::new(format!("{}", sys.lock().unwrap().system_cpu_usage))); + ui.add_sized([width, height], Label::new(RichText::new("System Memory").underline().color(BONE))).on_hover_text(STATUS_GUPAX_SYSTEM_MEMORY); + ui.add_sized([width, height], Label::new(format!("{}", sys.lock().unwrap().system_memory))); + ui.add_sized([width, height], Label::new(RichText::new("System CPU Model").underline().color(BONE))).on_hover_text(STATUS_GUPAX_SYSTEM_CPU_MODEL); + ui.add_sized([width, height], Label::new(format!("{}", sys.lock().unwrap().system_cpu_model))); + })}); + // [P2Pool] + ui.group(|ui| { ui.vertical(|ui| { + ui.set_min_height(min_height); + ui.add_sized([width, height*2.0], Label::new(RichText::new("[P2Pool]").color(LIGHT_GRAY).text_style(TextStyle::Name("MonospaceLarge".into())))); + // Uptime + ui.add_sized([width, height], Label::new(RichText::new("Uptime").underline())); + ui.add_sized([width, height], Label::new(format!("{}", p2pool_api.lock().unwrap().uptime))); + })}); + // [XMRig] + ui.group(|ui| { ui.vertical(|ui| { + ui.set_min_height(min_height); + ui.add_sized([width, height*2.0], Label::new(RichText::new("[XMRig]").color(LIGHT_GRAY).text_style(TextStyle::Name("MonospaceLarge".into())))); + // Uptime + ui.add_sized([width, height], Label::new(RichText::new("Uptime").underline())); + ui.add_sized([width, height], Label::new(format!("{}", xmrig_api.lock().unwrap().uptime))); + })}); + }); +} }