From 78964153edb9f970c9c2b8ce80844a02f1ebcdaa Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Tue, 27 Dec 2022 09:22:46 -0500
Subject: [PATCH] Status Submenu: add new API reads in P2Pool watchdog loop

This adds all necessary functions/conversions/serde/tests for the
new APIs. It also includes the actual reads inside the watchdog loop.

Every loop, [tick] will increment by 1. At [60], the watchdog
will attempt to read and parse the [network] & [pool] APIs.
Since the loop is [900ms], this means this will occur around
every 54 seconds. [tick] gets reset upon successful read/parsing.
---
 README.md     |   2 +-
 src/helper.rs | 228 ++++++++++++++++++++++++++++++++++++++++----------
 src/human.rs  |  17 ++++
 src/main.rs   |  12 ++-
 4 files changed, 213 insertions(+), 46 deletions(-)

diff --git a/README.md b/README.md
index 21c57ce..3d92c0c 100644
--- a/README.md
+++ b/README.md
@@ -482,7 +482,7 @@ You need [`cargo`](https://www.rust-lang.org/learn/get-started), Rust's build to
 
 The `--release` profile in Gupax is set to prefer code performance & small binary sizes over compilation speed (see [`Cargo.toml`](https://github.com/hinto-janaiyo/gupax/blob/main/Cargo.toml)). Gupax itself (with all dependencies already built) takes around 1m30s to build (vs 10s on a normal `--release`) with a Ryzen 5950x.
 
-There are `24` unit tests throughout the codebase files, you should probably run:
+There are `25` unit tests throughout the codebase files, you should probably run:
 ```
 cargo test
 ```
diff --git a/src/helper.rs b/src/helper.rs
index 86328a0..514f214 100644
--- a/src/helper.rs
+++ b/src/helper.rs
@@ -59,6 +59,10 @@ const MAX_GUI_OUTPUT_BYTES: usize = 500_000;
 // Just a little leeway so a reset will go off before the [String] allocates more memory.
 const GUI_OUTPUT_LEEWAY: usize = MAX_GUI_OUTPUT_BYTES - 1000;
 
+// Some constants for generating hashrate/difficulty.
+const MONERO_BLOCK_TIME_IN_SECONDS: u64 = 120;
+const P2POOL_BLOCK_TIME_IN_SECONDS: u64 = 10;
+
 //---------------------------------------------------------------------------------------------------- [Helper] Struct
 // A meta struct holding all the data that gets processed in this thread
 pub struct Helper {
@@ -503,10 +507,12 @@ impl Helper {
 
 		// 4. Loop as watchdog
 		info!("P2Pool | Entering watchdog mode... woof!");
+		let mut tick = 0;
 		loop {
 			// Set timer
 			let now = Instant::now();
 			debug!("P2Pool Watchdog | ----------- Start of loop -----------");
+			tick += 1;
 
 			// Check if the process is secretly died without us knowing :)
 			if let Ok(Some(code)) = child_pty.lock().unwrap().try_wait() {
@@ -619,23 +625,18 @@ impl Helper {
 			debug!("P2Pool Watchdog | Attempting [local] API file read");
 			if let Ok(string) = Self::path_to_string(&api_path_local, ProcessName::P2pool) {
 				// Deserialize
-				if let Ok(s) = PrivP2poolLocalApi::from_str(&string) {
+				if let Ok(local_api) = PrivP2poolLocalApi::from_str(&string) {
 					// Update the structs.
-					PubP2poolApi::update_from_local(&pub_api, s);
+					PubP2poolApi::update_from_local(&pub_api, local_api);
 				}
 			}
 			// If more than 1 minute has passed, read the other API files.
-			if now.elapsed().as_secs() >= 60 {
-				debug!("P2Pool Watchdog | Attempting [network] API file read");
-				if let Ok(string) = Self::path_to_string(&api_path_network, ProcessName::P2pool) {
-					if let Ok(s) = PrivP2poolNetworkApi::from_str(&string) {
-	//					PubP2poolApi::update_from_network(&pub_api, s);
-					}
-				}
-				debug!("P2Pool Watchdog | Attempting [pool] API file read");
-				if let Ok(string) = Self::path_to_string(&api_path_pool, ProcessName::P2pool) {
-					if let Ok(s) = PrivP2poolPoolApi::from_str(&string) {
-	//					PubP2poolApi::update_from_network(&pub_api, s);
+			if tick >= 60 {
+				debug!("P2Pool Watchdog | Attempting [network] & [pool] API file read");
+				if let (Ok(network_api), Ok(pool_api)) = (Self::path_to_string(&api_path_network, ProcessName::P2pool), Self::path_to_string(&api_path_pool, ProcessName::P2pool)) {
+					if let (Ok(network_api), Ok(pool_api)) = (PrivP2poolNetworkApi::from_str(&network_api), PrivP2poolPoolApi::from_str(&pool_api)) {
+						PubP2poolApi::update_from_network_pool(&pub_api, network_api, pool_api);
+						tick = 0;
 					}
 				}
 			}
@@ -645,10 +646,10 @@ impl Helper {
 			// Since logic goes off if less than 1000, casting should be safe
 			if elapsed < 900 {
 				let sleep = (900-elapsed) as u64;
-				debug!("P2Pool Watchdog | END OF LOOP - Sleeping for [{}]ms...", sleep);
+				debug!("P2Pool Watchdog | END OF LOOP -  Tick: [{}/60] - Sleeping for [{}]ms...", tick, sleep);
 				std::thread::sleep(std::time::Duration::from_millis(sleep));
 			} else {
-				debug!("P2Pool Watchdog | END OF LOOP - Not sleeping!");
+				debug!("P2Pool Watchdog | END OF LOOP - Tick: [{}/60] Not sleeping!", tick);
 			}
 		}
 
@@ -1254,8 +1255,27 @@ pub struct PubP2poolApi {
 	pub average_effort: HumanNumber,
 	pub current_effort: HumanNumber,
 	pub connections: HumanNumber,
+	// The API below needs a raw int [hashrate] to go off of and
+	// there's not a good way to access it without doing weird
+	// [Arc<Mutex>] shenanigans, so the raw [hashrate_1h] is
+	// copied here instead.
+	pub hashrate: u64,
 	// Network API
+	pub monero_difficulty: HumanNumber, // e.g: [15,000,000]
+	pub monero_hashrate: HumanNumber,   // e.g: [1.000 GH/s]
+	pub hash: String,
+	pub height: HumanNumber,
+	pub reward: u64, // Atomic units
 	// Pool API
+	pub p2pool_difficulty: HumanNumber,
+	pub p2pool_hashrate: HumanNumber,
+	pub miners: HumanNumber, // Current amount of miners on P2Pool sidechain
+	// Mean (calcualted in functions, not serialized)
+	pub solo_block_mean: HumanTime,   // Time it would take the user to find a solo block
+	pub p2pool_block_mean: HumanTime, // Time it takes the P2Pool sidechain to find a block
+	pub p2pool_share_mean: HumanTime, // Time it would take the user to find a P2Pool share
+	// Percentage of P2Pool hashrate capture of overall Monero hashrate.
+	pub p2pool_percent: HumanNumber,
 }
 
 impl Default for PubP2poolApi {
@@ -1284,6 +1304,19 @@ impl PubP2poolApi {
 			average_effort: HumanNumber::unknown(),
 			current_effort: HumanNumber::unknown(),
 			connections: HumanNumber::unknown(),
+			hashrate: 0,
+			monero_difficulty: HumanNumber::unknown(),
+			monero_hashrate: HumanNumber::unknown(),
+			hash: String::from("???"),
+			height: HumanNumber::unknown(),
+			reward: 0,
+			p2pool_difficulty: HumanNumber::unknown(),
+			p2pool_hashrate: HumanNumber::unknown(),
+			miners: HumanNumber::unknown(),
+			solo_block_mean: HumanTime::new(),
+			p2pool_block_mean: HumanTime::new(),
+			p2pool_share_mean: HumanTime::new(),
+			p2pool_percent: HumanNumber::unknown(),
 		}
 	}
 
@@ -1301,6 +1334,21 @@ impl PubP2poolApi {
 		};
 	}
 
+	// Essentially greps the output for [x.xxxxxxxxxxxx XMR] where x = a number.
+	// It sums each match and counts along the way, handling an error by not adding and printing to console.
+	fn calc_payouts_and_xmr(output: &str, regex: &P2poolRegex) -> (u128 /* payout count */, f64 /* total xmr */) {
+		let iter = regex.payout.find_iter(output);
+		let mut sum: f64 = 0.0;
+		let mut count: u128 = 0;
+		for i in iter {
+			match regex.float.find(i.as_str()).unwrap().as_str().parse::<f64>() {
+				Ok(num) => { sum += num; count += 1; },
+				Err(e)  => error!("P2Pool | Total XMR sum calculation error: [{}]", e),
+			}
+		}
+		(count, sum)
+	}
+
 	// Mutate "watchdog"'s [PubP2poolApi] with data the process output.
 	fn update_from_output(public: &Arc<Mutex<Self>>, output_parse: &Arc<Mutex<String>>, output_pub: &Arc<Mutex<String>>, elapsed: std::time::Duration, regex: &P2poolRegex) {
 		// 1. Take the process's current output buffer and combine it with Pub (if not empty)
@@ -1359,34 +1407,65 @@ impl PubP2poolApi {
 	}
 
 	// Mutate [PubP2poolApi] with data from a [PrivP2poolLocalApi] and the process output.
-	fn update_from_local(public: &Arc<Mutex<Self>>, private: PrivP2poolLocalApi) {
-		// priv -> pub conversion
+	fn update_from_local(public: &Arc<Mutex<Self>>, local: PrivP2poolLocalApi) {
 		let mut public = public.lock().unwrap();
 		*public = Self {
-			hashrate_15m: HumanNumber::from_u128(private.hashrate_15m),
-			hashrate_1h: HumanNumber::from_u128(private.hashrate_1h),
-			hashrate_24h: HumanNumber::from_u128(private.hashrate_24h),
-			shares_found: HumanNumber::from_u128(private.shares_found),
-			average_effort: HumanNumber::to_percent(private.average_effort),
-			current_effort: HumanNumber::to_percent(private.current_effort),
-			connections: HumanNumber::from_u16(private.connections),
+			hashrate_15m: HumanNumber::from_u64(local.hashrate_15m),
+			hashrate_1h: HumanNumber::from_u64(local.hashrate_1h),
+			hashrate_24h: HumanNumber::from_u64(local.hashrate_24h),
+			shares_found: HumanNumber::from_u64(local.shares_found),
+			average_effort: HumanNumber::to_percent(local.average_effort),
+			current_effort: HumanNumber::to_percent(local.current_effort),
+			connections: HumanNumber::from_u16(local.connections),
+			hashrate: local.hashrate_1h,
 			..std::mem::take(&mut *public)
 		};
 	}
 
-	// Essentially greps the output for [x.xxxxxxxxxxxx XMR] where x = a number.
-	// It sums each match and counts along the way, handling an error by not adding and printing to console.
-	fn calc_payouts_and_xmr(output: &str, regex: &P2poolRegex) -> (u128 /* payout count */, f64 /* total xmr */) {
-		let iter = regex.payout.find_iter(output);
-		let mut sum: f64 = 0.0;
-		let mut count: u128 = 0;
-		for i in iter {
-			match regex.float.find(i.as_str()).unwrap().as_str().parse::<f64>() {
-				Ok(num) => { sum += num; count += 1; },
-				Err(e)  => error!("P2Pool | Total XMR sum calculation error: [{}]", e),
-			}
+	// Mutate [PubP2poolApi] with data from a [PrivP2pool(Network|Pool)Api].
+	fn update_from_network_pool(public: &Arc<Mutex<Self>>, net: PrivP2poolNetworkApi, pool: PrivP2poolPoolApi) {
+		let hashrate = public.lock().unwrap().hashrate; // The user's total P2Pool hashrate
+		let monero_difficulty = net.difficulty;
+		let monero_hashrate = monero_difficulty / MONERO_BLOCK_TIME_IN_SECONDS;
+		let p2pool_hashrate = pool.pool_statistics.hashRate;
+		let p2pool_difficulty = p2pool_hashrate * P2POOL_BLOCK_TIME_IN_SECONDS;
+		// These [0] checks prevent dividing by 0 (it [panic!()]s)
+		let p2pool_block_mean = if p2pool_hashrate == 0 {
+			HumanTime::new()
+		} else {
+			HumanTime::into_human(std::time::Duration::from_secs(monero_difficulty / p2pool_hashrate))
+		};
+		let p2pool_percent = if monero_hashrate == 0 {
+			HumanNumber::unknown()
+		} else {
+			let f = (p2pool_hashrate as f32) / (monero_hashrate as f32) * 100.0;
+			HumanNumber::to_percent_3_point(f)
+		};
+		let solo_block_mean;
+		let p2pool_share_mean;
+		if hashrate == 0 {
+			solo_block_mean = HumanTime::new();
+			p2pool_share_mean = HumanTime::new();
+		} else {
+			solo_block_mean = HumanTime::into_human(std::time::Duration::from_secs(monero_difficulty / hashrate));
+			p2pool_share_mean = HumanTime::into_human(std::time::Duration::from_secs(p2pool_difficulty / hashrate));
 		}
-		(count, sum)
+		let mut public = public.lock().unwrap();
+		*public = Self {
+			monero_difficulty: HumanNumber::from_u64(monero_difficulty),
+			monero_hashrate: HumanNumber::from_u64_to_gigahash_3_point(monero_hashrate),
+			hash: net.hash,
+			height: HumanNumber::from_u32(net.height),
+			reward: net.reward,
+			p2pool_difficulty: HumanNumber::from_u64(p2pool_difficulty),
+			p2pool_hashrate: HumanNumber::from_u64_to_megahash_3_point(p2pool_hashrate),
+			miners: HumanNumber::from_u32(pool.pool_statistics.miners),
+			solo_block_mean,
+			p2pool_block_mean,
+			p2pool_share_mean,
+			p2pool_percent,
+			..std::mem::take(&mut *public)
+		};
 	}
 }
 
@@ -1395,10 +1474,10 @@ impl PubP2poolApi {
 // P2Pool seems to initialize all stats at 0 (or 0.0), so no [Option] wrapper seems needed.
 #[derive(Debug, Serialize, Deserialize, Clone, Copy)]
 struct PrivP2poolLocalApi {
-	hashrate_15m: u128,
-	hashrate_1h: u128,
-	hashrate_24h: u128,
-	shares_found: u128,
+	hashrate_15m: u64,
+	hashrate_1h: u64,
+	hashrate_24h: u64,
+	shares_found: u64,
 	average_effort: f32,
 	current_effort: f32,
 	connections: u16, // No one will have more than 65535 connections... right?
@@ -1432,10 +1511,10 @@ impl PrivP2poolLocalApi {
 // This matches P2Pool's [network/stats] JSON API file.
 #[derive(Debug, Serialize, Deserialize, Clone)]
 struct PrivP2poolNetworkApi {
-	difficulty: u128,
+	difficulty: u64,
 	hash: String,
 	height: u32,
-	reward: u128,
+	reward: u64,
 	timestamp: u32,
 }
 
@@ -1487,7 +1566,7 @@ impl PrivP2poolPoolApi {
 #[allow(non_snake_case)]
 #[derive(Debug, Serialize, Deserialize, Clone, Copy)]
 struct PoolStatistics {
-	hashRate: u128,
+	hashRate: u64,
 	miners: u32,
 }
 impl Default for PoolStatistics { fn default() -> Self { Self::new() } }
@@ -1766,6 +1845,69 @@ mod test {
 		assert_eq!(public.xmr_month,     648000.0000001296);
 	}
 
+	#[test]
+	fn update_pub_p2pool_from_local_network_pool() {
+		use std::sync::{Arc,Mutex};
+		use crate::helper::PubP2poolApi;
+		use crate::helper::PrivP2poolLocalApi;
+		use crate::helper::PrivP2poolNetworkApi;
+		use crate::helper::PrivP2poolPoolApi;
+		use crate::helper::PoolStatistics;
+		let public = Arc::new(Mutex::new(PubP2poolApi::new()));
+		let local = PrivP2poolLocalApi {
+			hashrate_15m: 10_000,
+			hashrate_1h: 20_000,
+			hashrate_24h: 30_000,
+			shares_found: 1000,
+			average_effort: 100.000,
+			current_effort: 200.000,
+			connections: 1234,
+		};
+		let network = PrivP2poolNetworkApi {
+			difficulty: 300_000_000_000,
+			hash: "asdf".to_string(),
+			height: 1234,
+			reward: 2345,
+			timestamp: 3456,
+		};
+		let pool = PrivP2poolPoolApi {
+			pool_statistics: PoolStatistics {
+				hashRate: 1_000_000, // 1 MH/s
+				miners: 1_000,
+			}
+		};
+		// Update Local
+		PubP2poolApi::update_from_local(&public, local);
+		let p = public.lock().unwrap();
+		println!("AFTER LOCAL: {:#?}", p);
+		assert_eq!(p.hashrate_15m.to_string(),   "10,000");
+		assert_eq!(p.hashrate_1h.to_string(),    "20,000");
+		assert_eq!(p.hashrate_24h.to_string(),   "30,000");
+		assert_eq!(p.shares_found.to_string(),   "1,000");
+		assert_eq!(p.average_effort.to_string(), "100.00%");
+		assert_eq!(p.current_effort.to_string(), "200.00%");
+		assert_eq!(p.connections.to_string(),    "1,234");
+		assert_eq!(p.hashrate,                   20000);
+		drop(p);
+		// Update Network + Pool
+		PubP2poolApi::update_from_network_pool(&public, network, pool);
+		let p = public.lock().unwrap();
+		println!("AFTER NETWORK+POOL: {:#?}", p);
+		assert_eq!(p.monero_difficulty.to_string(), "300,000,000,000");
+		assert_eq!(p.monero_hashrate.to_string(),   "2.500 GH/s");
+		assert_eq!(p.hash.to_string(),              "asdf");
+		assert_eq!(p.height.to_string(),            "1,234");
+		assert_eq!(p.reward,                        2345);
+		assert_eq!(p.p2pool_difficulty.to_string(), "10,000,000");
+		assert_eq!(p.p2pool_hashrate.to_string(),   "1.000 MH/s");
+		assert_eq!(p.miners.to_string(),            "1,000");
+		assert_eq!(p.solo_block_mean.to_string(),   "5 months, 21 days, 9 hours, 52 minutes");
+		assert_eq!(p.p2pool_block_mean.to_string(), "3 days, 11 hours, 20 minutes");
+		assert_eq!(p.p2pool_share_mean.to_string(), "8 minutes, 20 seconds");
+		assert_eq!(p.p2pool_percent.to_string(),    "0.040%");
+		drop(p);
+	}
+
 	#[test]
 	fn serde_priv_p2pool_local_api() {
 		let data =
diff --git a/src/human.rs b/src/human.rs
index cf089bb..44e0a2f 100644
--- a/src/human.rs
+++ b/src/human.rs
@@ -117,6 +117,9 @@ impl HumanNumber {
 			Self(format!("{:.2}%", f))
 		}
 	}
+	pub fn to_percent_3_point(f: f32) -> Self {
+		Self(format!("{:.3}%", f))
+	}
 	pub fn from_f32(f: f32) -> Self {
 		let mut buf = num_format::Buffer::new();
 		buf.write_formatted(&(f as u64), &LOCALE);
@@ -191,6 +194,18 @@ impl HumanNumber {
 		}
 		Self(string)
 	}
+	// [1_000_000] -> [1.000 MH/s]
+	pub fn from_u64_to_megahash_3_point(hash: u64) -> Self {
+		let hash = (hash as f64)/1_000_000.0;
+		let hash = format!("{:.3} MH/s", hash);
+		Self(hash)
+	}
+	// [1_000_000_000] -> [1.000 GH/s]
+	pub fn from_u64_to_gigahash_3_point(hash: u64) -> Self {
+		let hash = (hash as f64)/1_000_000_000.0;
+		let hash = format!("{:.3} GH/s", hash);
+		Self(hash)
+	}
 }
 
 //---------------------------------------------------------------------------------------------------- TESTS
@@ -201,6 +216,7 @@ mod test {
 		use crate::human::HumanNumber;
 		assert!(HumanNumber::to_percent(0.001).to_string() == "0%");
 		assert!(HumanNumber::to_percent(12.123123123123).to_string() == "12.12%");
+		assert!(HumanNumber::to_percent_3_point(0.001).to_string() == "0.001%");
 		assert!(HumanNumber::from_hashrate([Some(123.1), Some(11111.1), None]).to_string() == "[123 H/s, 11,111 H/s, ??? H/s]");
 		assert!(HumanNumber::from_hashrate([None, Some(1.123), Some(123123.312)]).to_string() == "[??? H/s, 1 H/s, 123,123 H/s]");
 		assert!(HumanNumber::from_load([Some(123.1234), Some(321.321), None]).to_string() == "[123.12, 321.32, ???]");
@@ -231,6 +247,7 @@ mod test {
 			HumanNumber::from_u128(340_282_366_920_938_463_463_374_607_431_768_211_455).to_string(),
 			"340,282,366,920,938,463,463,374,607,431,768,211,455",
 		);
+		assert!(HumanNumber::from_u64_to_gigahash_3_point(1_000_000_000).to_string() == "1.000 GH/s");
 	}
 
 	#[test]
diff --git a/src/main.rs b/src/main.rs
index 4796d4e..f396a24 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1594,6 +1594,8 @@ impl eframe::App for App {
 						let distro = true;
 						#[cfg(not(feature = "distro"))]
 						let distro = false;
+						let p2pool_gui_len = self.p2pool_api.lock().unwrap().output.len();
+						let xmrig_gui_len = self.xmrig_api.lock().unwrap().output.len();
 						let debug_info = format!(
 "Gupax version: {}\n
 Bundled P2Pool version: {}\n
@@ -1622,6 +1624,10 @@ XMRig console byte length: {}\n
 {:#?}\n
 ------------------------------------------ XMRIG IMAGE ------------------------------------------
 {:#?}\n
+------------------------------------------ P2POOL GUI API ------------------------------------------
+{:#?}\n
+------------------------------------------ XMRIG GUI API ------------------------------------------
+{:#?}\n
 ------------------------------------------ WORKING STATE ------------------------------------------
 {:#?}\n
 ------------------------------------------ ORIGINAL STATE ------------------------------------------
@@ -1649,10 +1655,12 @@ XMRig console byte length: {}\n
 							self.exe,
 							self.state.gupax.absolute_p2pool_path.display(),
 							self.state.gupax.absolute_xmrig_path.display(),
-							self.p2pool_api.lock().unwrap().output.len(),
-							self.xmrig_api.lock().unwrap().output.len(),
+							p2pool_gui_len,
+							xmrig_gui_len,
 							self.p2pool_img.lock().unwrap(),
 							self.xmrig_img.lock().unwrap(),
+							self.p2pool_api.lock().unwrap(),
+							self.xmrig_api.lock().unwrap(),
 							self.state,
 							self.og.lock().unwrap(),
 						);