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(), );