diff --git a/src/helper/xrig/xmrig.rs b/src/helper/xrig/xmrig.rs index d718230..f98218c 100644 --- a/src/helper/xrig/xmrig.rs +++ b/src/helper/xrig/xmrig.rs @@ -191,9 +191,9 @@ impl Helper { let pub_api = Arc::clone(&helper.lock().unwrap().pub_api_xmrig); let process_xvb = Arc::clone(&helper.lock().unwrap().xvb); let process_xp = Arc::clone(&helper.lock().unwrap().xmrig_proxy); + let process_p2pool = Arc::clone(&helper.lock().unwrap().p2pool); let path = path.to_path_buf(); let token = state.token.clone(); - let img_xmrig = Arc::clone(&helper.lock().unwrap().img_xmrig); let pub_api_xvb = Arc::clone(&helper.lock().unwrap().pub_api_xvb); thread::spawn(move || { Self::spawn_xmrig_watchdog( @@ -207,7 +207,7 @@ impl Helper { &token, process_xvb, process_xp, - &img_xmrig, + process_p2pool, &pub_api_xvb, ); }); @@ -264,9 +264,6 @@ impl Helper { threads: state.current_threads.to_string(), url: "127.0.0.1:3333 (Local P2Pool)".to_string(), }; - - helper.lock().unwrap().pub_api_xmrig.lock().unwrap().node = - "127.0.0.1:3333 (Local P2Pool)".to_string(); api_ip = "127.0.0.1".to_string(); api_port = "18088".to_string(); @@ -346,7 +343,6 @@ impl Helper { url: url.clone(), threads: state.current_threads.to_string(), }; - helper.lock().unwrap().pub_api_xmrig.lock().unwrap().node = url; } } args.push(format!("--http-access-token={}", state.token)); // HTTP API Port @@ -390,7 +386,7 @@ impl Helper { token: &str, process_xvb: Arc<Mutex<Process>>, process_xp: Arc<Mutex<Process>>, - img_xmrig: &Arc<Mutex<ImgXmrig>>, + process_p2pool: Arc<Mutex<Process>>, pub_api_xvb: &Arc<Mutex<PubXvbApi>>, ) { // 1a. Create PTY @@ -473,11 +469,7 @@ impl Helper { *pub_api.lock().unwrap() = PubXmrigApi::new(); *gui_api.lock().unwrap() = PubXmrigApi::new(); // node used for process Status tab - gui_api - .lock() - .unwrap() - .node - .clone_from(&img_xmrig.lock().unwrap().url); + pub_api.lock().unwrap().node = NO_POOL.to_string(); // 5. Loop as watchdog info!("XMRig | Entering watchdog mode... woof!"); // needs xmrig to be in belownormal priority or else Gupaxx will be in trouble if it does not have enough cpu time. @@ -558,15 +550,18 @@ impl Helper { } } // if mining on proxy and proxy is not alive, switch back to p2pool node - if gui_api.lock().unwrap().node == XvbNode::XmrigProxy.to_string() + if (pub_api.lock().unwrap().node == XvbNode::XmrigProxy.to_string() + || pub_api.lock().unwrap().node == NO_POOL) && !process_xp.lock().unwrap().is_alive() + && process_p2pool.lock().unwrap().is_alive() { info!("XMRig Process | redirect xmrig to p2pool since XMRig-Proxy is not alive anymore"); + let node = XvbNode::P2pool; if let Err(err) = update_xmrig_config( &client, XMRIG_CONFIG_URL, token, - &XvbNode::P2pool, + &node, "", GUPAX_VERSION_UNDERSCORE, ) @@ -583,7 +578,6 @@ impl Helper { ProcessName::Xmrig, ); } else { - gui_api.lock().unwrap().node = XvbNode::P2pool.to_string(); debug!("XMRig Process | mining on P2Pool pool"); } } @@ -732,12 +726,10 @@ impl PubXmrigApi { #[inline] pub fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) { let output = std::mem::take(&mut gui_api.output); - let node = std::mem::take(&mut gui_api.node); let buf = std::mem::take(&mut pub_api.output); *gui_api = Self { output, - node, - ..std::mem::take(pub_api) + ..pub_api.clone() }; if !buf.is_empty() { gui_api.output.push_str(&buf); @@ -768,8 +760,13 @@ impl PubXmrigApi { let mut output_parse = output_parse.lock().unwrap(); if XMRIG_REGEX.new_job.is_match(&output_parse) { process.state = ProcessState::Alive; + // get the pool we mine on to put it on stats + if let Some(name_pool) = crate::regex::detect_node_xmrig(&output_parse) { + public.node = name_pool; + } } else if XMRIG_REGEX.not_mining.is_match(&output_parse) { process.state = ProcessState::NotMining; + public.node = NO_POOL.to_string(); } // 3. Throw away [output_parse] diff --git a/src/helper/xrig/xmrig_proxy.rs b/src/helper/xrig/xmrig_proxy.rs index 0e8b014..840eea1 100644 --- a/src/helper/xrig/xmrig_proxy.rs +++ b/src/helper/xrig/xmrig_proxy.rs @@ -24,7 +24,7 @@ use crate::{ regex::{contains_timeout, contains_usepool, detect_new_node_xmrig, XMRIG_REGEX}, GUPAX_VERSION_UNDERSCORE, UNKNOWN_DATA, }; -use crate::{XMRIG_CONFIG_URL, XMRIG_PROXY_SUMMARY_URL}; +use crate::{NO_POOL, XMRIG_CONFIG_URL, XMRIG_PROXY_SUMMARY_URL}; use super::xmrig::PubXmrigApi; impl Helper { @@ -105,10 +105,7 @@ impl Helper { } } } - pub fn build_xp_args( - helper: &Arc<Mutex<Self>>, - state: &crate::disk::state::XmrigProxy, - ) -> Vec<String> { + pub fn build_xp_args(state: &crate::disk::state::XmrigProxy) -> Vec<String> { let mut args = Vec::with_capacity(500); let api_ip; let api_port; @@ -133,8 +130,6 @@ impl Helper { args.push("127.0.0.1".to_string()); // HTTP API IP args.push("--http-port".to_string()); args.push("18089".to_string()); // HTTP API Port - helper.lock().unwrap().pub_api_xp.lock().unwrap().node = - "127.0.0.1:3333 (Local P2Pool)".to_string(); // [Advanced] } else if !state.arguments.is_empty() { @@ -194,7 +189,6 @@ impl Helper { if state.keepalive { args.push("--keepalive".to_string()); } // Keepalive - helper.lock().unwrap().pub_api_xp.lock().unwrap().node = p2pool_url; } args.push(format!("--http-access-token={}", state.token)); // HTTP API Port args.push("--http-no-restricted".to_string()); @@ -253,7 +247,7 @@ impl Helper { ) { helper.lock().unwrap().xmrig_proxy.lock().unwrap().state = ProcessState::Middle; - let args = Self::build_xp_args(helper, state_proxy); + let args = Self::build_xp_args(state_proxy); // Print arguments & user settings to console crate::disk::print_dash(&format!("XMRig-Proxy | Launch arguments: {:#?}", args)); info!("XMRig-Proxy | Using path: [{}]", path.display()); @@ -269,7 +263,7 @@ impl Helper { let state_xmrig = state_xmrig.clone(); let redirect_xmrig = state_proxy.redirect_local_xmrig; let pub_api_xvb = Arc::clone(&helper.lock().unwrap().pub_api_xvb); - let gui_api_xmrig = Arc::clone(&helper.lock().unwrap().gui_api_xmrig); + let pub_api_xmrig = Arc::clone(&helper.lock().unwrap().pub_api_xmrig); thread::spawn(move || { Self::spawn_xp_watchdog( &process, @@ -283,7 +277,7 @@ impl Helper { process_xvb, process_xmrig, &pub_api_xvb, - &gui_api_xmrig, + &pub_api_xmrig, ); }); } @@ -302,7 +296,7 @@ impl Helper { process_xvb: Arc<Mutex<Process>>, process_xmrig: Arc<Mutex<Process>>, pub_api_xvb: &Arc<Mutex<PubXvbApi>>, - gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>, + pub_api_xmrig: &Arc<Mutex<PubXmrigApi>>, ) { process.lock().unwrap().start = Instant::now(); // spawn pty @@ -343,10 +337,8 @@ impl Helper { process.lock().unwrap().state = ProcessState::NotMining; process.lock().unwrap().signal = ProcessSignal::None; // reset stats - let node = pub_api.lock().unwrap().node.to_string(); *pub_api.lock().unwrap() = PubXmrigProxyApi::new(); *gui_api.lock().unwrap() = PubXmrigProxyApi::new(); - gui_api.lock().unwrap().node = node; // loop let start = process.lock().unwrap().start; debug!("Xmrig-Proxy Watchdog | enabling verbose mode"); @@ -402,13 +394,17 @@ impl Helper { // Always update from output // todo: check difference with xmrig debug!("XMRig-Proxy Watchdog | Starting [update_from_output()]"); + let mut process_lock = process.lock().unwrap(); + let mut pub_api_lock = pub_api.lock().unwrap(); PubXmrigProxyApi::update_from_output( - pub_api, + &mut pub_api_lock, &output_pub, &output_parse, start.elapsed(), - process, + &mut process_lock, ); + drop(pub_api_lock); + drop(process_lock); // update data from api debug!("XMRig-Proxy Watchdog | Attempting HTTP API request..."); match PrivXmrigProxyApi::request_xp_api(&client, api_summary_xp, token_proxy).await @@ -426,16 +422,17 @@ impl Helper { } // update xmrig to use xmrig-proxy if option enabled and local xmrig alive if xmrig_redirect - && gui_api_xmrig.lock().unwrap().node != XvbNode::XmrigProxy.to_string() + && pub_api_xmrig.lock().unwrap().node != XvbNode::XmrigProxy.to_string() && (process_xmrig.lock().unwrap().state == ProcessState::Alive || process_xmrig.lock().unwrap().state == ProcessState::NotMining) { info!("redirect local xmrig instance to xmrig-proxy"); + let node = XvbNode::XmrigProxy; if let Err(err) = update_xmrig_config( &client, api_config_xmrig, &state_xmrig.token, - &XvbNode::XmrigProxy, + &node, "", GUPAX_VERSION_UNDERSCORE, ) @@ -452,7 +449,6 @@ impl Helper { ProcessName::XmrigProxy, ); } else { - gui_api_xmrig.lock().unwrap().node = XvbNode::XmrigProxy.to_string(); debug!("XMRig-Proxy Process | mining on Xmrig-Proxy pool"); } } @@ -501,17 +497,16 @@ impl PubXmrigProxyApi { } } pub fn update_from_output( - public: &Arc<Mutex<Self>>, + public: &mut Self, output_parse: &Arc<Mutex<String>>, output_pub: &Arc<Mutex<String>>, elapsed: std::time::Duration, - process: &Arc<Mutex<Process>>, + process: &mut Process, ) { // 1. Take the process's current output buffer and combine it with Pub (if not empty) let mut output_pub = output_pub.lock().unwrap(); { - let mut public = public.lock().unwrap(); if !output_pub.is_empty() { public.output.push_str(&std::mem::take(&mut *output_pub)); } @@ -524,12 +519,17 @@ impl PubXmrigProxyApi { if XMRIG_REGEX.new_job.is_match(&output_parse) || XMRIG_REGEX.valid_conn.is_match(&output_parse) { - process.lock().unwrap().state = ProcessState::Alive; + process.state = ProcessState::Alive; + // get the pool we mine on to put it on stats + if let Some(name_pool) = crate::regex::detect_node_xmrig(&output_parse) { + public.node = name_pool; + } } else if XMRIG_REGEX.timeout.is_match(&output_parse) || XMRIG_REGEX.invalid_conn.is_match(&output_parse) || XMRIG_REGEX.error.is_match(&output_parse) { - process.lock().unwrap().state = ProcessState::NotMining; + process.state = ProcessState::NotMining; + public.node = NO_POOL.to_string(); } // 3. Throw away [output_parse] output_parse.clear(); @@ -538,11 +538,9 @@ impl PubXmrigProxyApi { // same method as PubXmrigApi, why not make a trait ? pub fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) { let output = std::mem::take(&mut gui_api.output); - let node = std::mem::take(&mut gui_api.node); let buf = std::mem::take(&mut pub_api.output); *gui_api = Self { output, - node, ..pub_api.clone() }; if !buf.is_empty() { diff --git a/src/helper/xvb/algorithm.rs b/src/helper/xvb/algorithm.rs index ef82574..64684e3 100644 --- a/src/helper/xvb/algorithm.rs +++ b/src/helper/xvb/algorithm.rs @@ -225,12 +225,12 @@ impl<'a> Algorithm<'a> { "Algorithm | request {} to mine on p2pool", self.stats.msg_xmrig_or_xp ); - + let node = XvbNode::P2pool; if let Err(err) = update_xmrig_config( self.client, &self.stats.api_url, self.token_xmrig, - &XvbNode::P2pool, + &node, &self.stats.address, self.rig, ) @@ -298,11 +298,6 @@ impl<'a> Algorithm<'a> { crate::helper::ProcessName::Xvb, ); } else { - if self.xp_alive { - self.gui_api_xp.lock().unwrap().node = node.to_string(); - } else { - self.gui_api_xmrig.lock().unwrap().node = node.to_string(); - } info!( "Algorithm | {} mining on XvB pool", self.stats.msg_xmrig_or_xp diff --git a/src/helper/xvb/mod.rs b/src/helper/xvb/mod.rs index 18bffd8..b86358b 100644 --- a/src/helper/xvb/mod.rs +++ b/src/helper/xvb/mod.rs @@ -95,10 +95,10 @@ impl Helper { let gui_api_p2pool = Arc::clone(&helper.lock().unwrap().gui_api_p2pool); let process_xmrig = Arc::clone(&helper.lock().unwrap().xmrig); let process_xp = Arc::clone(&helper.lock().unwrap().xmrig_proxy); + // Generally, gui is to read data, pub is to update. + // ex: read hashrate values from gui, update node to pub. let gui_api_xmrig = Arc::clone(&helper.lock().unwrap().gui_api_xmrig); - let pub_api_xmrig = Arc::clone(&helper.lock().unwrap().pub_api_xmrig); let gui_api_xp = Arc::clone(&helper.lock().unwrap().gui_api_xp); - let pub_api_xp = Arc::clone(&helper.lock().unwrap().gui_api_xp); // Reset before printing to output. // Need to reset because values of stats would stay otherwise which could bring confusion even if panel is with a disabled theme. // at the start of a process, values must be default. @@ -136,10 +136,8 @@ impl Helper { &gui_api_p2pool, &process_p2pool, &gui_api_xmrig, - &pub_api_xmrig, &process_xmrig, &gui_api_xp, - &pub_api_xp, &process_xp, ); }), @@ -159,10 +157,8 @@ impl Helper { gui_api_p2pool: &Arc<Mutex<PubP2poolApi>>, process_p2pool: &Arc<Mutex<Process>>, gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>, - pub_api_xmrig: &Arc<Mutex<PubXmrigApi>>, process_xmrig: &Arc<Mutex<Process>>, gui_api_xp: &Arc<Mutex<PubXmrigProxyApi>>, - pub_api_xp: &Arc<Mutex<PubXmrigProxyApi>>, process_xp: &Arc<Mutex<Process>>, ) { // create uniq client that is going to be used for during the life of the thread. @@ -226,8 +222,6 @@ impl Helper { process_p2pool, &mut first_loop, &handle_algo, - pub_api_xmrig, - pub_api_xp, state_p2pool, state_xmrig, state_xp, @@ -246,8 +240,6 @@ impl Helper { &client, pub_api, gui_api, - gui_api_xmrig, - gui_api_xp, state_p2pool, state_xmrig, state_xp, @@ -291,7 +283,7 @@ impl Helper { // first_loop is false here but could be changed to true under some conditions. // will send a stop signal if public stats failed or update data with new one. *handle_request.lock().unwrap() = Some(spawn( - enc!((client, pub_api, gui_api, gui_api_p2pool, gui_api_xmrig, gui_api_xp, state_xvb, state_p2pool, state_xmrig, state_xp, process, last_algorithm, retry, handle_algo, time_donated, last_request) async move { + enc!((client, pub_api, gui_api, gui_api_p2pool, gui_api_xmrig, gui_api_xp, state_xvb, state_p2pool, state_xmrig, state_xp, process, last_algorithm, retry, handle_algo, time_donated, last_request) async move { // needs to wait here for public stats to get private stats. if last_request_expired || first_loop || should_refresh_before_next_algo { XvbPubStats::update_stats(&client, &gui_api, &pub_api, &process).await; @@ -334,7 +326,7 @@ impl Helper { *retry.lock().unwrap() = false; // reset instant because algo will start. *last_algorithm.lock().unwrap() = Instant::now(); - *handle_algo.lock().unwrap() = Some(spawn(enc!((client, gui_api, gui_api_xmrig, gui_api_xp, state_xmrig, state_xp, time_donated, state_xvb) async move { + *handle_algo.lock().unwrap() = Some(spawn(enc!((client, gui_api, gui_api_xmrig, gui_api_xp, state_xmrig, state_xp, time_donated, state_xvb) async move { let token_xmrig = if xp_alive { &state_xp.token } else { @@ -547,8 +539,6 @@ async fn check_state_outcauses_xvb( process_p2pool: &Arc<Mutex<Process>>, first_loop: &mut bool, handle_algo: &Arc<Mutex<Option<JoinHandle<()>>>>, - pub_api_xmrig: &Arc<Mutex<PubXmrigApi>>, - pub_api_xp: &Arc<Mutex<PubXmrigProxyApi>>, state_p2pool: &crate::disk::state::P2pool, state_xmrig: &crate::disk::state::Xmrig, state_xp: &crate::disk::state::XmrigProxy, @@ -595,40 +585,37 @@ async fn check_state_outcauses_xvb( } else { state_xmrig.rig.clone() }; - spawn( - enc!((client, pub_api_xmrig, gui_api,pub_api_xp) async move { - let url_api = api_url_xmrig(xp_is_alive, true); - if let Err(err) = update_xmrig_config( - &client, - &url_api, - &token_xmrig, - &XvbNode::P2pool, - &address, - &rig - ) - .await - { - // show to console error about updating xmrig config - output_console( - &mut gui_api.lock().unwrap().output, - &format!( - "Failure to update {msg_xmrig_or_proxy} config with HTTP API.\nError: {}", - err - ), - ProcessName::Xvb - ); - } else { - if xp_is_alive {pub_api_xp.lock().unwrap().node = XvbNode::P2pool.to_string();} else {pub_api_xmrig.lock().unwrap().node = XvbNode::P2pool.to_string();} + spawn(enc!((client, gui_api) async move { + let url_api = api_url_xmrig(xp_is_alive, true); + let node = XvbNode::P2pool; + if let Err(err) = update_xmrig_config( + &client, + &url_api, + &token_xmrig, + &node, + &address, + &rig + ) + .await + { + // show to console error about updating xmrig config + output_console( + &mut gui_api.lock().unwrap().output, + &format!( + "Failure to update {msg_xmrig_or_proxy} config with HTTP API.\nError: {}", + err + ), + ProcessName::Xvb + ); + } else { + output_console( + &mut gui_api.lock().unwrap().output, + &format!("XvB process can not completely continue, falling back to {}", XvbNode::P2pool), + ProcessName::Xvb + ); + } - output_console( - &mut gui_api.lock().unwrap().output, - &format!("XvB process can not completely continue, falling back to {}", XvbNode::P2pool), - ProcessName::Xvb - ); - } - - }), - ); + })); } } } @@ -697,8 +684,6 @@ fn signal_interrupt( client: &Client, pub_api: &Arc<Mutex<PubXvbApi>>, gui_api: &Arc<Mutex<PubXvbApi>>, - gui_api_xmrig: &Arc<Mutex<PubXmrigApi>>, - gui_api_xp: &Arc<Mutex<PubXmrigProxyApi>>, state_p2pool: &crate::disk::state::P2pool, state_xmrig: &crate::disk::state::Xmrig, state_xp: &crate::disk::state::XmrigProxy, @@ -766,7 +751,7 @@ fn signal_interrupt( process.lock().unwrap().state = ProcessState::Waiting; process.lock().unwrap().signal = ProcessSignal::None; spawn( - enc!((node, process, client, gui_api, pub_api, was_alive, address, token_xmrig, gui_api_xmrig, gui_api_xp,process_xrig) async move { + enc!((node, process, client, gui_api, pub_api, was_alive, address, token_xmrig, process_xrig) async move { warn!("in spawn of UpdateNodes"); match node { XvbNode::NorthAmerica|XvbNode::Europe if was_alive => { @@ -795,14 +780,15 @@ fn signal_interrupt( // Need to set XMRig to P2Pool if it wasn't. XMRig should have populated this value at his start. // but if xmrig didn't start, don't update it. if process_xrig.lock().unwrap().state == ProcessState::Alive && gui_api.lock().unwrap().current_node != Some(XvbNode::P2pool) { - spawn(enc!((client, token_xmrig, address, gui_api_xmrig, gui_api_xp, gui_api) async move{ + spawn(enc!((client, token_xmrig, address, gui_api) async move{ let url_api = api_url_xmrig(xp_alive, true); warn!("update xmrig to use node ?"); + let node = XvbNode::P2pool; if let Err(err) = update_xmrig_config( &client, &url_api, &token_xmrig, - &XvbNode::P2pool, + &node, &address, &rig ) @@ -819,14 +805,7 @@ fn signal_interrupt( err ), ProcessName::Xvb ); - } else { - // update node xmrig - if xp_alive { - gui_api_xp.lock().unwrap().node = XvbNode::P2pool.to_string(); - } else { - gui_api_xmrig.lock().unwrap().node = XvbNode::P2pool.to_string(); - }; - } + } } ));} }, diff --git a/src/utils/constants.rs b/src/utils/constants.rs index e478736..cd72922 100644 --- a/src/utils/constants.rs +++ b/src/utils/constants.rs @@ -94,6 +94,7 @@ pub const XMRIG_CONFIG_URL: &str = "http://127.0.0.1:18088/1/config"; // The def pub const XMRIG_PROXY_CONFIG_URL: &str = "http://127.0.0.1:18089/1/config"; // The default relative URI of XMRig Proxy's API config pub const XMRIG_SUMMARY_URL: &str = "http://127.0.0.1:18088/1/summary"; // The default relative URI of XMRig's API config pub const XMRIG_PROXY_SUMMARY_URL: &str = "http://127.0.0.1:18089/1/summary"; // The default relative URI of XMRig Proxy's API config +pub const NO_POOL: &str = "Not connected to any pool"; // status tab xrig if no pool is used // Process state tooltips (online, offline, etc) pub const P2POOL_ALIVE: &str = "P2Pool is online and fully synchronized"; diff --git a/src/utils/regex.rs b/src/utils/regex.rs index ac747f9..ce20d20 100644 --- a/src/utils/regex.rs +++ b/src/utils/regex.rs @@ -171,6 +171,9 @@ pub fn detect_new_node_xmrig(s: &str) -> Option<XvbNode> { "127.0.0.1:3333" => { return Some(XvbNode::P2pool); } + "127.0.0.1:3355" => { + return Some(XvbNode::XmrigProxy); + } "eu.xmrvsbeast.com:4247" => { return Some(XvbNode::Europe); } @@ -184,6 +187,26 @@ pub fn detect_new_node_xmrig(s: &str) -> Option<XvbNode> { warn!("a line on xmrig console was detected as using a new pool but the syntax was not recognized or it was not a pool useable for the algorithm."); None } +// this detection removes the need to update pub_api.node everytime xmrig/proxy are updated to mine to another p2pool node. +pub fn detect_node_xmrig(s: &str) -> Option<String> { + static CURRENT_SHARE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"new job from (?P<pool>.*?) diff").unwrap()); + if let Some(c) = CURRENT_SHARE.captures(s) { + if let Some(m) = c.name("pool") { + // let's make nicer name appear on status tab + let name = match m.as_str() { + "127.0.0.1:3333" => XvbNode::P2pool.to_string(), + "127.0.0.1:3355" => XvbNode::XmrigProxy.to_string(), + "eu.xmrvsbeast.com:4247" => XvbNode::Europe.to_string(), + "na.xmrvsbeast.com:4247" => XvbNode::NorthAmerica.to_string(), + x => x.to_string(), + }; + return Some(name); + } + } + warn!("a line on xmrig console was detected as using a new pool but the syntax was not recognized or it was not a pool useable for the algorithm."); + None +} pub fn estimated_hr(s: &str) -> Option<f32> { static CURRENT_SHARE: Lazy<Regex> = Lazy::new(|| { Regex::new(r"(?P<nb>[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?) (?P<unit>.*)H/s").unwrap()