From 1535b5744eec48a8b1327d728597c565cdd3b73b Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Sun, 11 Dec 2022 15:49:01 -0500
Subject: [PATCH] xmrig/status: implement API hyper/tokio call; add [Gupax]
 stats

---
 Cargo.lock       |  24 +++++++
 Cargo.toml       |   1 +
 src/constants.rs |   9 +++
 src/disk.rs      |   4 +-
 src/gupax.rs     |   2 +-
 src/helper.rs    | 181 +++++++++++++++++++++++++++++++++--------------
 src/main.rs      |  72 ++++++++++++-------
 src/status.rs    | 103 +++++++++++++++------------
 8 files changed, 270 insertions(+), 126 deletions(-)

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<Mutex<Sys>>,                 // The public API for [sysinfo] that the [Status] tab reads from
 	pub p2pool: Arc<Mutex<Process>>,              // P2Pool process state
 	pub xmrig: Arc<Mutex<Process>>,               // XMRig process state
 	pub gui_api_p2pool: Arc<Mutex<PubP2poolApi>>, // 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<Mutex<Process>>, xmrig: Arc<Mutex<Process>>, gui_api_p2pool: Arc<Mutex<PubP2poolApi>>, gui_api_xmrig: Arc<Mutex<PubXmrigApi>>, img_p2pool: Arc<Mutex<ImgP2pool>>, img_xmrig: Arc<Mutex<ImgXmrig>>) -> Self {
+	pub fn new(instant: std::time::Instant, pub_sys: Arc<Mutex<Sys>>, p2pool: Arc<Mutex<Process>>, xmrig: Arc<Mutex<Process>>, gui_api_p2pool: Arc<Mutex<PubP2poolApi>>, gui_api_xmrig: Arc<Mutex<PubXmrigApi>>, img_p2pool: Arc<Mutex<ImgP2pool>>, img_xmrig: Arc<Mutex<ImgXmrig>>) -> 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<String, std::io::Error> {
-		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<PrivP2poolApi, serde_json::Error> {
-		match serde_json::from_str::<PrivP2poolApi>(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<Mutex<Self>>) {
 		info!("P2Pool | Attempting to stop...");
@@ -513,9 +524,9 @@ impl Helper {
 			PubP2poolApi::update_from_output(&pub_api, &output_full, &output_buf, start.elapsed(), &regex);
 
 			// 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<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf, sudo: Arc<Mutex<SudoState>>) {
 		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<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf) -> Vec<String> {
+	pub fn build_xmrig_args_and_mutate_img(helper: &Arc<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf) -> (Vec<String>, 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<Mutex<Process>>, gui_api: Arc<Mutex<PubXmrigApi>>, pub_api: Arc<Mutex<PubXmrigApi>>, priv_api: Arc<Mutex<PrivXmrigApi>>, args: Vec<String>, mut path: std::path::PathBuf, sudo: Arc<Mutex<SudoState>>) {
+	// 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<Mutex<Process>>, gui_api: Arc<Mutex<PubXmrigApi>>, pub_api: Arc<Mutex<PubXmrigApi>>, priv_api: Arc<Mutex<PrivXmrigApi>>, args: Vec<String>, mut path: std::path::PathBuf, sudo: Arc<Mutex<SudoState>>, 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::HttpConnector> = 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<Mutex<Self>>) {
+	pub fn spawn_helper(helper: &Arc<Mutex<Self>>, 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<String, std::io::Error> {
+		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<Self, serde_json::Error> {
+		match serde_json::from_str::<Self>(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<hyper::client::HttpConnector>, api_ip_port: &str) -> Result<Self, anyhow::Error> {
+		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::<Self>(&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<Mutex<Helper>>,  // [Helper] state, mostly for Gupax uptime
+	pub_sys: Arc<Mutex<Sys>>, // [Sys] state, read by [Status], mutated by [Helper]
 	p2pool: Arc<Mutex<Process>>, // [P2Pool] process state
 	xmrig: Arc<Mutex<Process>>,  // [XMRig] process state
 	p2pool_api: Arc<Mutex<PubP2poolApi>>, // 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 <https://www.gnu.org/licenses/>.
 
-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<Pos2> = (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<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_api: &Arc<Mutex<PubXmrigApi>>, p2pool_img: &Arc<Mutex<ImgP2pool>>, xmrig_img: &Arc<Mutex<ImgXmrig>>, 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)));
+	})});
+	});
+}
 }