2022-11-23 04:21:46 +00:00
|
|
|
// Gupax - GUI Uniting P2Pool And XMRig
|
|
|
|
//
|
|
|
|
// Copyright (c) 2022 hinto-janaiyo
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2022-11-28 17:05:09 +00:00
|
|
|
|
2022-11-30 03:38:01 +00:00
|
|
|
// This file represents the "helper" thread, which is the full separate thread
|
|
|
|
// that runs alongside the main [App] GUI thread. It exists for the entire duration
|
|
|
|
// of Gupax so that things can be handled without locking up the GUI thread.
|
|
|
|
//
|
|
|
|
// This thread is a continual 1 second loop, collecting available jobs on the
|
|
|
|
// way down and (if possible) asynchronously executing them at the very end.
|
|
|
|
//
|
|
|
|
// The main GUI thread will interface with this thread by mutating the Arc<Mutex>'s
|
|
|
|
// found here, e.g: User clicks [Start P2Pool] -> Arc<Mutex<ProcessSignal> is set
|
|
|
|
// indicating to this thread during its loop: "I should start P2Pool!", e.g:
|
|
|
|
//
|
|
|
|
// match p2pool.lock().unwrap().signal {
|
|
|
|
// ProcessSignal::Start => start_p2pool(),
|
|
|
|
// ...
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// This also includes all things related to handling the child processes (P2Pool/XMRig):
|
|
|
|
// piping their stdout/stderr/stdin, accessing their APIs (HTTP + disk files), etc.
|
2022-11-28 17:05:09 +00:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------- Import
|
|
|
|
use std::{
|
|
|
|
sync::{Arc,Mutex},
|
|
|
|
path::PathBuf,
|
2022-12-03 21:02:34 +00:00
|
|
|
process::{Command,Stdio},
|
|
|
|
fmt::Write,
|
2022-11-30 22:21:55 +00:00
|
|
|
time::*,
|
2022-11-28 17:05:09 +00:00
|
|
|
thread,
|
|
|
|
};
|
2022-12-02 18:15:26 +00:00
|
|
|
use serde::{Serialize,Deserialize};
|
2022-11-28 17:05:09 +00:00
|
|
|
use crate::constants::*;
|
2022-12-04 01:12:40 +00:00
|
|
|
use num_format::{Buffer,Locale};
|
2022-11-28 17:05:09 +00:00
|
|
|
use log::*;
|
|
|
|
|
2022-12-04 01:12:40 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- Constants
|
|
|
|
const LOCALE: num_format::Locale = num_format::Locale::en;
|
2022-12-02 18:15:26 +00:00
|
|
|
|
2022-11-30 22:21:55 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- [Helper] Struct
|
|
|
|
// A meta struct holding all the data that gets processed in this thread
|
|
|
|
pub struct Helper {
|
2022-12-03 21:02:34 +00:00
|
|
|
pub instant: Instant, // Gupax start as an [Instant]
|
|
|
|
pub human_time: HumanTime, // Gupax uptime formatting for humans
|
|
|
|
pub p2pool: Arc<Mutex<Process>>, // P2Pool process state
|
|
|
|
pub xmrig: Arc<Mutex<Process>>, // XMRig process state
|
2022-12-04 01:12:40 +00:00
|
|
|
pub pub_api_p2pool: Arc<Mutex<PubP2poolApi>>, // P2Pool API state (for GUI/Helper thread)
|
|
|
|
pub pub_api_xmrig: Arc<Mutex<PubXmrigApi>>, // XMRig API state (for GUI/Helper thread)
|
2022-12-03 21:02:34 +00:00
|
|
|
priv_api_p2pool: Arc<Mutex<PrivP2poolApi>>, // For "watchdog" thread
|
|
|
|
priv_api_xmrig: Arc<Mutex<PrivXmrigApi>>, // For "watchdog" thread
|
2022-11-30 22:21:55 +00:00
|
|
|
}
|
|
|
|
|
2022-11-28 17:05:09 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- [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.
|
|
|
|
pub struct Process {
|
2022-12-04 01:12:40 +00:00
|
|
|
pub name: ProcessName, // P2Pool or XMRig?
|
|
|
|
pub state: ProcessState, // The state of the process (alive, dead, etc)
|
|
|
|
pub signal: ProcessSignal, // Did the user click [Start/Stop/Restart]?
|
2022-11-28 17:05:09 +00:00
|
|
|
// STDIN Problem:
|
|
|
|
// - User can input many many commands in 1 second
|
|
|
|
// - The process loop only processes every 1 second
|
|
|
|
// - If there is only 1 [String] holding the user input,
|
|
|
|
// the user could overwrite their last input before
|
|
|
|
// the loop even has a chance to process their last command
|
|
|
|
// STDIN Solution:
|
|
|
|
// - When the user inputs something, push it to a [Vec]
|
|
|
|
// - In the process loop, loop over every [Vec] element and
|
|
|
|
// send each one individually to the process stdin
|
2022-12-05 19:55:50 +00:00
|
|
|
//
|
2022-12-04 01:12:40 +00:00
|
|
|
pub input: Vec<String>,
|
2022-12-05 19:55:50 +00:00
|
|
|
|
|
|
|
// The below are the handles to the actual child process.
|
|
|
|
// [Simple] has no STDIN, but [Advanced] does. A PTY (pseudo-terminal) is
|
2022-12-06 03:33:35 +00:00
|
|
|
// required for P2Pool/XMRig to open their STDIN pipe.
|
|
|
|
child: Option<Arc<Mutex<Box<dyn portable_pty::Child + Send + std::marker::Sync>>>>, // STDOUT/STDERR is combined automatically thanks to this PTY, nice
|
2022-12-05 19:55:50 +00:00
|
|
|
stdin: Option<Box<dyn portable_pty::MasterPty + Send>>, // A handle to the process's MasterPTY/STDIN
|
|
|
|
|
|
|
|
// This is the process's private output [String], used by both [Simple] and [Advanced].
|
|
|
|
// The "watchdog" threads mutate this, the "helper" thread synchronizes the [Pub*Api] structs
|
|
|
|
// so that the data in here is cloned there roughly once a second. GUI thread never touches this.
|
2022-12-06 03:33:35 +00:00
|
|
|
output: Arc<Mutex<String>>,
|
2022-12-06 20:17:37 +00:00
|
|
|
|
|
|
|
// Start time of process.
|
|
|
|
start: std::time::Instant,
|
2022-11-28 17:05:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------- [Process] Impl
|
|
|
|
impl Process {
|
|
|
|
pub fn new(name: ProcessName, args: String, path: PathBuf) -> Self {
|
|
|
|
Self {
|
|
|
|
name,
|
2022-11-30 03:38:01 +00:00
|
|
|
state: ProcessState::Dead,
|
2022-11-28 17:05:09 +00:00
|
|
|
signal: ProcessSignal::None,
|
2022-12-06 20:17:37 +00:00
|
|
|
start: Instant::now(),
|
2022-12-01 20:48:47 +00:00
|
|
|
stdin: Option::None,
|
2022-12-04 16:24:38 +00:00
|
|
|
child: Option::None,
|
2022-11-29 22:33:24 +00:00
|
|
|
// P2Pool log level 1 produces a bit less than 100,000 lines a day.
|
|
|
|
// Assuming each line averages 80 UTF-8 scalars (80 bytes), then this
|
|
|
|
// initial buffer should last around a week (56MB) before resetting.
|
2022-12-06 03:33:35 +00:00
|
|
|
output: Arc::new(Mutex::new(String::with_capacity(56_000_000))),
|
2022-11-28 17:05:09 +00:00
|
|
|
input: vec![String::new()],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Borrow a [&str], return an owned split collection
|
|
|
|
pub fn parse_args(args: &str) -> Vec<String> {
|
|
|
|
args.split_whitespace().map(|s| s.to_owned()).collect()
|
|
|
|
}
|
2022-12-06 03:33:35 +00:00
|
|
|
|
|
|
|
// Convenience functions
|
|
|
|
pub fn is_alive(&self) -> bool {
|
|
|
|
self.state == ProcessState::Alive || self.state == ProcessState::Middle
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_waiting(&self) -> bool {
|
|
|
|
self.state == ProcessState::Middle || self.state == ProcessState::Waiting
|
|
|
|
}
|
2022-11-30 03:38:01 +00:00
|
|
|
}
|
2022-11-28 17:05:09 +00:00
|
|
|
|
2022-11-30 03:38:01 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- [Process*] Enum
|
|
|
|
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
|
|
|
|
pub enum ProcessState {
|
2022-12-05 19:55:50 +00:00
|
|
|
Alive, // Process is online, GREEN!
|
|
|
|
Dead, // Process is dead, BLACK!
|
|
|
|
Failed, // Process is dead AND exited with a bad code, RED!
|
2022-12-06 03:33:35 +00:00
|
|
|
Middle, // Process is in the middle of something ([re]starting/stopping), YELLOW!
|
|
|
|
Waiting, // Process was successfully killed by a restart, and is ready to be started again, YELLOW!
|
2022-11-28 17:05:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
|
|
|
|
pub enum ProcessSignal {
|
|
|
|
None,
|
2022-11-30 03:38:01 +00:00
|
|
|
Start,
|
2022-11-28 17:05:09 +00:00
|
|
|
Stop,
|
|
|
|
Restart,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
|
|
|
|
pub enum ProcessName {
|
2022-11-30 22:21:55 +00:00
|
|
|
P2pool,
|
|
|
|
Xmrig,
|
2022-11-28 17:05:09 +00:00
|
|
|
}
|
|
|
|
|
2022-11-30 03:38:01 +00:00
|
|
|
impl std::fmt::Display for ProcessState { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
|
|
|
|
impl std::fmt::Display for ProcessSignal { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
|
|
|
|
impl std::fmt::Display for ProcessName { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
|
|
|
|
|
2022-12-05 19:55:50 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- [Helper]
|
|
|
|
use tokio::io::{BufReader,AsyncBufReadExt};
|
|
|
|
|
|
|
|
impl Helper {
|
|
|
|
//---------------------------------------------------------------------------------------------------- General Functions
|
2022-12-06 03:33:35 +00:00
|
|
|
pub fn new(instant: std::time::Instant, p2pool: Arc<Mutex<Process>>, xmrig: Arc<Mutex<Process>>, pub_api_p2pool: Arc<Mutex<PubP2poolApi>>, pub_api_xmrig: Arc<Mutex<PubXmrigApi>>) -> Self {
|
2022-12-05 19:55:50 +00:00
|
|
|
Self {
|
|
|
|
instant,
|
|
|
|
human_time: HumanTime::into_human(instant.elapsed()),
|
|
|
|
priv_api_p2pool: Arc::new(Mutex::new(PrivP2poolApi::new())),
|
|
|
|
priv_api_xmrig: Arc::new(Mutex::new(PrivXmrigApi::new())),
|
|
|
|
// These are created when initializing [App], since it needs a handle to it as well
|
2022-12-06 03:33:35 +00:00
|
|
|
p2pool,
|
|
|
|
xmrig,
|
2022-12-05 19:55:50 +00:00
|
|
|
pub_api_p2pool,
|
|
|
|
pub_api_xmrig,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reads a PTY which combines STDOUT/STDERR for me, yay
|
2022-12-06 03:33:35 +00:00
|
|
|
fn read_pty(output: Arc<Mutex<String>>, reader: Box<dyn std::io::Read + Send>) {
|
2022-12-05 19:55:50 +00:00
|
|
|
use std::io::BufRead;
|
|
|
|
let mut stdout = std::io::BufReader::new(reader).lines();
|
|
|
|
while let Some(Ok(line)) = stdout.next() {
|
2022-12-06 03:33:35 +00:00
|
|
|
// println!("{}", line); // For debugging.
|
|
|
|
writeln!(output.lock().unwrap(), "{}", line);
|
2022-12-05 19:55:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------- P2Pool specific
|
2022-12-06 03:33:35 +00:00
|
|
|
// 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 stop...");
|
|
|
|
helper.lock().unwrap().p2pool.lock().unwrap().signal = ProcessSignal::Stop;
|
|
|
|
helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The "restart frontend" to a "frontend" function.
|
|
|
|
// Basically calls to kill the current p2pool, waits a little, then starts the below function in a a new thread, then exit.
|
|
|
|
pub fn restart_p2pool(helper: &Arc<Mutex<Self>>, state: &crate::disk::P2pool, path: std::path::PathBuf) {
|
|
|
|
info!("P2Pool | Attempting restart...");
|
|
|
|
helper.lock().unwrap().p2pool.lock().unwrap().signal = ProcessSignal::Restart;
|
|
|
|
helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle;
|
|
|
|
|
|
|
|
let helper = Arc::clone(&helper);
|
|
|
|
let state = state.clone();
|
|
|
|
let path = path.clone();
|
|
|
|
// This thread lives to wait, start p2pool then die.
|
|
|
|
thread::spawn(move || {
|
|
|
|
while helper.lock().unwrap().p2pool.lock().unwrap().is_alive() {
|
|
|
|
warn!("P2Pool Restart | Process still alive, waiting...");
|
|
|
|
thread::sleep(SECOND);
|
|
|
|
}
|
|
|
|
// Ok, process is not alive, start the new one!
|
|
|
|
Self::start_p2pool(&helper, &state, path);
|
|
|
|
});
|
|
|
|
info!("P2Pool | Restart ... OK");
|
|
|
|
}
|
|
|
|
|
|
|
|
// The "frontend" function that parses the arguments, and spawns either the [Simple] or [Advanced] P2Pool watchdog thread.
|
|
|
|
pub fn start_p2pool(helper: &Arc<Mutex<Self>>, state: &crate::disk::P2pool, path: std::path::PathBuf) {
|
|
|
|
helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle;
|
|
|
|
|
2022-12-05 19:55:50 +00:00
|
|
|
let mut args = Vec::with_capacity(500);
|
|
|
|
let path = path.clone();
|
|
|
|
let mut api_path = path.clone();
|
|
|
|
api_path.pop();
|
|
|
|
|
|
|
|
// [Simple]
|
|
|
|
if state.simple {
|
|
|
|
// Build the p2pool argument
|
|
|
|
let (ip, rpc, zmq) = crate::node::enum_to_ip_rpc_zmq_tuple(state.node); // Get: (IP, RPC, ZMQ)
|
2022-12-06 20:33:57 +00:00
|
|
|
args.push("--wallet".to_string()); args.push(state.address.clone()); // Wallet address
|
|
|
|
args.push("--host".to_string()); args.push(ip.to_string()); // IP Address
|
2022-12-05 19:55:50 +00:00
|
|
|
args.push("--rpc-port".to_string()); args.push(rpc.to_string()); // RPC Port
|
|
|
|
args.push("--zmq-port".to_string()); args.push(zmq.to_string()); // ZMQ Port
|
|
|
|
args.push("--data-api".to_string()); args.push(api_path.display().to_string()); // API Path
|
|
|
|
args.push("--local-api".to_string()); // Enable API
|
|
|
|
args.push("--no-color".to_string()); // Remove color escape sequences, Gupax terminal can't parse it :(
|
|
|
|
args.push("--mini".to_string()); // P2Pool Mini
|
|
|
|
|
|
|
|
// [Advanced]
|
|
|
|
} else {
|
|
|
|
// Overriding command arguments
|
|
|
|
if !state.arguments.is_empty() {
|
|
|
|
for arg in state.arguments.split_whitespace() {
|
|
|
|
args.push(arg.to_string());
|
|
|
|
}
|
|
|
|
// Else, build the argument
|
|
|
|
} else {
|
2022-12-06 20:33:57 +00:00
|
|
|
args.push("--wallet".to_string()); args.push(state.address.clone()); // Wallet
|
|
|
|
args.push("--host".to_string()); args.push(state.selected_ip.to_string()); // IP
|
|
|
|
args.push("--rpc-port".to_string()); args.push(state.selected_rpc.to_string()); // RPC
|
|
|
|
args.push("--zmq-port".to_string()); args.push(state.selected_zmq.to_string()); // ZMQ
|
|
|
|
args.push("--loglevel".to_string()); args.push(state.log_level.to_string()); // Log Level
|
|
|
|
args.push("--out-peers".to_string()); args.push(state.out_peers.to_string()); // Out Peers
|
|
|
|
args.push("--in-peers".to_string()); args.push(state.in_peers.to_string()); // In Peers
|
|
|
|
args.push("--data-api".to_string()); args.push(api_path.display().to_string()); // API Path
|
|
|
|
args.push("--local-api".to_string()); // Enable API
|
|
|
|
args.push("--no-color".to_string()); // Remove color escape sequences
|
|
|
|
if state.mini { args.push("--mini".to_string()); }; // Mini
|
2022-12-05 19:55:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Print arguments to console
|
|
|
|
crate::disk::print_dash(&format!("P2Pool | Launch arguments ... {:#?}", args));
|
|
|
|
|
|
|
|
// Spawn watchdog thread
|
|
|
|
let process = Arc::clone(&helper.lock().unwrap().p2pool);
|
|
|
|
let pub_api = Arc::clone(&helper.lock().unwrap().pub_api_p2pool);
|
|
|
|
let priv_api = Arc::clone(&helper.lock().unwrap().priv_api_p2pool);
|
|
|
|
thread::spawn(move || {
|
2022-12-06 03:33:35 +00:00
|
|
|
Self::spawn_p2pool_watchdog(process, pub_api, priv_api, args, path);
|
2022-12-05 19:55:50 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-12-06 03:33:35 +00:00
|
|
|
// The P2Pool watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works.
|
2022-12-05 19:55:50 +00:00
|
|
|
#[tokio::main]
|
2022-12-06 03:33:35 +00:00
|
|
|
async fn spawn_p2pool_watchdog(process: Arc<Mutex<Process>>, pub_api: Arc<Mutex<PubP2poolApi>>, priv_api: Arc<Mutex<PrivP2poolApi>>, args: Vec<String>, mut path: std::path::PathBuf) {
|
2022-12-05 19:55:50 +00:00
|
|
|
// 1a. Create PTY
|
|
|
|
let pty = portable_pty::native_pty_system();
|
2022-12-06 03:33:35 +00:00
|
|
|
let pair = pty.openpty(portable_pty::PtySize {
|
2022-12-05 19:55:50 +00:00
|
|
|
rows: 24,
|
|
|
|
cols: 80,
|
|
|
|
pixel_width: 0,
|
|
|
|
pixel_height: 0,
|
|
|
|
}).unwrap();
|
|
|
|
// 1b. Create command
|
2022-12-06 03:33:35 +00:00
|
|
|
let mut cmd = portable_pty::CommandBuilder::new(path.as_path());
|
2022-12-05 19:55:50 +00:00
|
|
|
cmd.args(args);
|
2022-12-06 03:33:35 +00:00
|
|
|
cmd.cwd(path.as_path().parent().unwrap());
|
2022-12-05 19:55:50 +00:00
|
|
|
// 1c. Create child
|
|
|
|
let child_pty = Arc::new(Mutex::new(pair.slave.spawn_command(cmd).unwrap()));
|
|
|
|
|
|
|
|
// 2. Set process state
|
|
|
|
let mut lock = process.lock().unwrap();
|
|
|
|
lock.state = ProcessState::Alive;
|
|
|
|
lock.signal = ProcessSignal::None;
|
|
|
|
lock.start = Instant::now();
|
2022-12-06 03:33:35 +00:00
|
|
|
lock.child = Some(Arc::clone(&child_pty));
|
2022-12-05 19:55:50 +00:00
|
|
|
let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY
|
|
|
|
lock.stdin = Some(pair.master);
|
|
|
|
drop(lock);
|
|
|
|
|
|
|
|
// 3. Spawn PTY read thread
|
2022-12-06 03:33:35 +00:00
|
|
|
let output_clone = Arc::clone(&process.lock().unwrap().output);
|
2022-12-05 19:55:50 +00:00
|
|
|
thread::spawn(move || {
|
2022-12-06 03:33:35 +00:00
|
|
|
Self::read_pty(output_clone, reader);
|
2022-12-05 19:55:50 +00:00
|
|
|
});
|
|
|
|
|
2022-12-06 03:33:35 +00:00
|
|
|
path.pop();
|
|
|
|
path.push(P2POOL_API_PATH);
|
|
|
|
let regex = P2poolRegex::new();
|
|
|
|
let output = Arc::clone(&process.lock().unwrap().output);
|
2022-12-06 20:17:37 +00:00
|
|
|
let start = process.lock().unwrap().start;
|
2022-12-06 03:33:35 +00:00
|
|
|
|
2022-12-05 19:55:50 +00:00
|
|
|
// 4. Loop as watchdog
|
|
|
|
loop {
|
|
|
|
// Set timer
|
|
|
|
let now = Instant::now();
|
|
|
|
|
|
|
|
// Check SIGNAL
|
|
|
|
if process.lock().unwrap().signal == ProcessSignal::Stop {
|
|
|
|
child_pty.lock().unwrap().kill(); // This actually sends a SIGHUP to p2pool (closes the PTY, hangs up on p2pool)
|
|
|
|
// Wait to get the exit status
|
2022-12-06 03:33:35 +00:00
|
|
|
let exit_status = match child_pty.lock().unwrap().wait() {
|
2022-12-06 20:17:37 +00:00
|
|
|
Ok(e) => {
|
|
|
|
if e.success() {
|
|
|
|
process.lock().unwrap().state = ProcessState::Dead; "Successful"
|
|
|
|
} else {
|
|
|
|
process.lock().unwrap().state = ProcessState::Failed; "Failed"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => { process.lock().unwrap().state = ProcessState::Failed; "Unknown Error" },
|
2022-12-06 03:33:35 +00:00
|
|
|
};
|
2022-12-06 20:17:37 +00:00
|
|
|
let uptime = HumanTime::into_human(start.elapsed());
|
2022-12-06 03:33:35 +00:00
|
|
|
info!("P2Pool | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status);
|
|
|
|
// This is written directly into the public API, because sometimes the 900ms event loop can't catch it.
|
2022-12-06 20:17:37 +00:00
|
|
|
writeln!(pub_api.lock().unwrap().output, "{}\nP2Pool stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE);
|
|
|
|
process.lock().unwrap().signal = ProcessSignal::None;
|
2022-12-06 03:33:35 +00:00
|
|
|
break
|
|
|
|
} else if process.lock().unwrap().signal == ProcessSignal::Restart {
|
|
|
|
child_pty.lock().unwrap().kill(); // This actually sends a SIGHUP to p2pool (closes the PTY, hangs up on p2pool)
|
|
|
|
// Wait to get the exit status
|
2022-12-05 19:55:50 +00:00
|
|
|
let exit_status = match child_pty.lock().unwrap().wait() {
|
|
|
|
Ok(e) => if e.success() { "Successful" } else { "Failed" },
|
|
|
|
_ => "Unknown Error",
|
|
|
|
};
|
2022-12-06 20:17:37 +00:00
|
|
|
let uptime = HumanTime::into_human(start.elapsed());
|
2022-12-06 03:33:35 +00:00
|
|
|
info!("P2Pool | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status);
|
|
|
|
// This is written directly into the public API, because sometimes the 900ms event loop can't catch it.
|
2022-12-06 20:17:37 +00:00
|
|
|
writeln!(pub_api.lock().unwrap().output, "{}\nP2Pool stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE);
|
|
|
|
process.lock().unwrap().state = ProcessState::Waiting;
|
2022-12-06 03:33:35 +00:00
|
|
|
break
|
|
|
|
// Check if the process is secretly died without us knowing :)
|
|
|
|
} else if let Ok(Some(code)) = child_pty.lock().unwrap().try_wait() {
|
|
|
|
let exit_status = match code.success() {
|
2022-12-06 20:17:37 +00:00
|
|
|
true => { process.lock().unwrap().state = ProcessState::Dead; "Successful" },
|
|
|
|
false => { process.lock().unwrap().state = ProcessState::Failed; "Failed" },
|
2022-12-06 03:33:35 +00:00
|
|
|
};
|
2022-12-06 20:17:37 +00:00
|
|
|
let uptime = HumanTime::into_human(start.elapsed());
|
2022-12-05 19:55:50 +00:00
|
|
|
info!("P2Pool | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status);
|
2022-12-06 03:33:35 +00:00
|
|
|
// This is written directly into the public API, because sometimes the 900ms event loop can't catch it.
|
2022-12-06 20:17:37 +00:00
|
|
|
writeln!(pub_api.lock().unwrap().output, "{}\nP2Pool stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE);
|
|
|
|
process.lock().unwrap().signal = ProcessSignal::None;
|
2022-12-05 19:55:50 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check vector of user input
|
|
|
|
let mut lock = process.lock().unwrap();
|
|
|
|
if !lock.input.is_empty() {
|
|
|
|
let input = std::mem::take(&mut lock.input);
|
|
|
|
for line in input {
|
|
|
|
writeln!(lock.stdin.as_mut().unwrap(), "{}", line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
drop(lock);
|
|
|
|
|
2022-12-06 18:18:40 +00:00
|
|
|
// Always update from output
|
2022-12-06 20:17:37 +00:00
|
|
|
PubP2poolApi::update_from_output(&pub_api, &output, start.elapsed(), ®ex);
|
2022-12-06 18:18:40 +00:00
|
|
|
|
2022-12-06 03:33:35 +00:00
|
|
|
// Read API file into string
|
|
|
|
if let Ok(string) = Self::read_p2pool_api(&path) {
|
|
|
|
// Deserialize
|
|
|
|
if let Ok(s) = Self::str_to_priv_p2pool_api(&string) {
|
|
|
|
// Update the structs.
|
2022-12-06 18:18:40 +00:00
|
|
|
PubP2poolApi::update_from_priv(&pub_api, &priv_api);
|
2022-12-06 03:33:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-05 19:55:50 +00:00
|
|
|
// Sleep (only if 900ms hasn't passed)
|
|
|
|
let elapsed = now.elapsed().as_millis();
|
|
|
|
// Since logic goes off if less than 1000, casting should be safe
|
|
|
|
if elapsed < 900 { std::thread::sleep(std::time::Duration::from_millis((900-elapsed) as u64)); }
|
|
|
|
}
|
|
|
|
|
|
|
|
// 5. If loop broke, we must be done here.
|
2022-12-06 03:33:35 +00:00
|
|
|
info!("P2Pool | Advanced watchdog thread exiting... Goodbye!");
|
2022-12-05 19:55:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------- XMRig specific
|
|
|
|
// Intermediate function that parses the arguments, and spawns the XMRig watchdog thread.
|
2022-12-06 03:33:35 +00:00
|
|
|
pub fn spawn_xmrig(helper: &Arc<Mutex<Self>>, state: &crate::disk::Xmrig, path: std::path::PathBuf) {
|
2022-12-05 19:55:50 +00:00
|
|
|
let mut args = Vec::with_capacity(500);
|
|
|
|
if state.simple {
|
|
|
|
let rig_name = if state.simple_rig.is_empty() { GUPAX_VERSION.to_string() } else { state.simple_rig.clone() }; // Rig name
|
|
|
|
args.push(format!("--threads {}", state.current_threads)); // Threads
|
|
|
|
args.push(format!("--user {}", state.simple_rig)); // Rig name
|
|
|
|
args.push(format!("--url 127.0.0.1:3333")); // Local P2Pool (the default)
|
|
|
|
args.push("--no-color".to_string()); // No color escape codes
|
|
|
|
if state.pause != 0 { args.push(format!("--pause-on-active {}", state.pause)); } // Pause on active
|
|
|
|
} else {
|
|
|
|
if !state.arguments.is_empty() {
|
|
|
|
for arg in state.arguments.split_whitespace() {
|
|
|
|
args.push(arg.to_string());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
args.push(format!("--user {}", state.address.clone())); // Wallet
|
|
|
|
args.push(format!("--threads {}", state.current_threads)); // Threads
|
|
|
|
args.push(format!("--rig-id {}", state.selected_rig)); // Rig ID
|
|
|
|
args.push(format!("--url {}:{}", state.selected_ip.clone(), state.selected_port.clone())); // IP/Port
|
|
|
|
args.push(format!("--http-host {}", state.api_ip).to_string()); // HTTP API IP
|
|
|
|
args.push(format!("--http-port {}", state.api_port).to_string()); // HTTP API Port
|
|
|
|
args.push("--no-color".to_string()); // No color escape codes
|
|
|
|
if state.tls { args.push("--tls".to_string()); } // TLS
|
|
|
|
if state.keepalive { args.push("--keepalive".to_string()); } // Keepalive
|
|
|
|
if state.pause != 0 { args.push(format!("--pause-on-active {}", state.pause)); } // Pause on active
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Print arguments to console
|
|
|
|
crate::disk::print_dash(&format!("XMRig | Launch arguments ... {:#?}", args));
|
|
|
|
|
|
|
|
// Spawn watchdog thread
|
|
|
|
thread::spawn(move || {
|
|
|
|
Self::spawn_xmrig_watchdog(args);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// The actual XMRig watchdog tokio runtime.
|
|
|
|
#[tokio::main]
|
|
|
|
pub async fn spawn_xmrig_watchdog(args: Vec<String>) {
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------- The "helper"
|
|
|
|
// Intermediate function that spawns the helper thread.
|
|
|
|
pub fn spawn_helper(helper: &Arc<Mutex<Self>>) {
|
|
|
|
let helper = Arc::clone(helper);
|
|
|
|
thread::spawn(move || { Self::helper(helper); });
|
|
|
|
}
|
|
|
|
|
|
|
|
// [helper] = Actual Arc
|
|
|
|
// [h] = Temporary lock that gets dropped
|
|
|
|
// [jobs] = Vector of async jobs ready to go
|
|
|
|
// #[tokio::main]
|
|
|
|
pub fn helper(helper: Arc<Mutex<Self>>) {
|
|
|
|
// Begin loop
|
|
|
|
loop {
|
|
|
|
|
|
|
|
// 1. Create "jobs" vector holding async tasks
|
|
|
|
// let jobs: Vec<tokio::task::JoinHandle<Result<(), anyhow::Error>>> = vec![];
|
|
|
|
|
|
|
|
// 2. Loop init timestamp
|
|
|
|
let start = Instant::now();
|
|
|
|
|
|
|
|
// 7. Set Gupax/P2Pool/XMRig uptime
|
|
|
|
let mut h = helper.lock().unwrap();
|
|
|
|
h.human_time = HumanTime::into_human(h.instant.elapsed());
|
|
|
|
drop(h);
|
|
|
|
|
|
|
|
// 8. Calculate if we should sleep or not.
|
|
|
|
// If we should sleep, how long?
|
|
|
|
let elapsed = start.elapsed().as_millis();
|
|
|
|
if elapsed < 1000 {
|
|
|
|
// Casting from u128 to u64 should be safe here, because [elapsed]
|
|
|
|
// is less than 1000, meaning it can fit into a u64 easy.
|
|
|
|
std::thread::sleep(std::time::Duration::from_millis((1000-elapsed) as u64));
|
|
|
|
}
|
|
|
|
|
|
|
|
// 9. End loop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-30 22:21:55 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- [HumanTime]
|
|
|
|
// This converts a [std::time::Duration] into something more readable.
|
|
|
|
// Used for uptime display purposes: [7 years, 8 months, 15 days, 23 hours, 35 minutes, 1 second]
|
|
|
|
// Code taken from [https://docs.rs/humantime/] and edited to remove sub-second time, change spacing and some words.
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct HumanTime(Duration);
|
|
|
|
|
|
|
|
impl HumanTime {
|
2022-12-06 20:17:37 +00:00
|
|
|
pub fn new() -> HumanTime {
|
|
|
|
HumanTime(ZERO_SECONDS)
|
|
|
|
}
|
|
|
|
|
2022-11-30 22:21:55 +00:00
|
|
|
pub fn into_human(d: Duration) -> HumanTime {
|
|
|
|
HumanTime(d)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn plural(f: &mut std::fmt::Formatter, started: &mut bool, name: &str, value: u64) -> std::fmt::Result {
|
|
|
|
if value > 0 {
|
2022-12-06 20:17:37 +00:00
|
|
|
if *started {
|
|
|
|
f.write_str(", ")?;
|
|
|
|
}
|
|
|
|
write!(f, "{} {}", value, name)?;
|
|
|
|
if value > 1 {
|
|
|
|
f.write_str("s")?;
|
|
|
|
}
|
|
|
|
*started = true;
|
2022-11-30 22:21:55 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::fmt::Display for HumanTime {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
let secs = self.0.as_secs();
|
|
|
|
if secs == 0 {
|
2022-12-06 20:17:37 +00:00
|
|
|
f.write_str("0 seconds")?;
|
2022-11-30 22:21:55 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
let years = secs / 31_557_600; // 365.25d
|
|
|
|
let ydays = secs % 31_557_600;
|
|
|
|
let months = ydays / 2_630_016; // 30.44d
|
|
|
|
let mdays = ydays % 2_630_016;
|
|
|
|
let days = mdays / 86400;
|
|
|
|
let day_secs = mdays % 86400;
|
|
|
|
let hours = day_secs / 3600;
|
|
|
|
let minutes = day_secs % 3600 / 60;
|
|
|
|
let seconds = day_secs % 60;
|
|
|
|
|
|
|
|
let ref mut started = false;
|
2022-12-06 20:17:37 +00:00
|
|
|
Self::plural(f, started, "year", years)?;
|
|
|
|
Self::plural(f, started, "month", months)?;
|
|
|
|
Self::plural(f, started, "day", days)?;
|
|
|
|
Self::plural(f, started, "hour", hours)?;
|
|
|
|
Self::plural(f, started, "minute", minutes)?;
|
|
|
|
Self::plural(f, started, "second", seconds)?;
|
2022-11-30 22:21:55 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-04 01:12:40 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- [HumanNumber]
|
|
|
|
// Human readable numbers.
|
|
|
|
// Float | [1234.57] -> [1,234] | Casts as u64/u128, adds comma
|
|
|
|
// Unsigned | [1234567] -> [1,234,567] | Adds comma
|
|
|
|
// Percent | [99.123] -> [99.12%] | Truncates to 2 after dot, adds percent
|
|
|
|
// Percent | [0.001] -> [0%] | Rounds down, removes redundant zeros
|
|
|
|
// Hashrate | [123.0, 311.2, null] -> [123, 311, ???] | Casts, replaces null with [???]
|
|
|
|
// CPU Load | [12.0, 11.4, null] -> [12.0, 11.4, ???] | No change, just into [String] form
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct HumanNumber(String);
|
|
|
|
|
|
|
|
impl std::fmt::Display for HumanNumber {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
write!(f, "{}", &self.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HumanNumber {
|
|
|
|
fn unknown() -> Self {
|
|
|
|
Self("???".to_string())
|
|
|
|
}
|
|
|
|
fn to_percent(f: f32) -> Self {
|
|
|
|
if f < 0.01 {
|
|
|
|
Self("0%".to_string())
|
|
|
|
} else {
|
|
|
|
Self(format!("{:.2}%", f))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn from_f32(f: f32) -> Self {
|
|
|
|
let mut buf = num_format::Buffer::new();
|
|
|
|
buf.write_formatted(&(f as u64), &LOCALE);
|
|
|
|
Self(buf.as_str().to_string())
|
|
|
|
}
|
|
|
|
fn from_f64(f: f64) -> Self {
|
|
|
|
let mut buf = num_format::Buffer::new();
|
|
|
|
buf.write_formatted(&(f as u128), &LOCALE);
|
|
|
|
Self(buf.as_str().to_string())
|
|
|
|
}
|
|
|
|
fn from_u8(u: u8) -> Self {
|
|
|
|
let mut buf = num_format::Buffer::new();
|
|
|
|
buf.write_formatted(&u, &LOCALE);
|
|
|
|
Self(buf.as_str().to_string())
|
|
|
|
}
|
|
|
|
fn from_u16(u: u16) -> Self {
|
|
|
|
let mut buf = num_format::Buffer::new();
|
|
|
|
buf.write_formatted(&u, &LOCALE);
|
|
|
|
Self(buf.as_str().to_string())
|
|
|
|
}
|
|
|
|
fn from_u32(u: u32) -> Self {
|
|
|
|
let mut buf = num_format::Buffer::new();
|
|
|
|
buf.write_formatted(&u, &LOCALE);
|
|
|
|
Self(buf.as_str().to_string())
|
|
|
|
}
|
|
|
|
fn from_u64(u: u64) -> Self {
|
|
|
|
let mut buf = num_format::Buffer::new();
|
|
|
|
buf.write_formatted(&u, &LOCALE);
|
|
|
|
Self(buf.as_str().to_string())
|
|
|
|
}
|
|
|
|
fn from_u128(u: u128) -> Self {
|
|
|
|
let mut buf = num_format::Buffer::new();
|
|
|
|
buf.write_formatted(&u, &LOCALE);
|
|
|
|
Self(buf.as_str().to_string())
|
|
|
|
}
|
|
|
|
fn from_hashrate(array: [Option<f32>; 3]) -> Self {
|
|
|
|
let mut string = "[".to_string();
|
|
|
|
let mut buf = num_format::Buffer::new();
|
|
|
|
|
|
|
|
let mut n = 0;
|
|
|
|
for i in array {
|
|
|
|
match i {
|
|
|
|
Some(f) => {
|
|
|
|
let f = f as u128;
|
|
|
|
buf.write_formatted(&f, &LOCALE);
|
|
|
|
string.push_str(buf.as_str());
|
|
|
|
string.push_str(" H/s");
|
|
|
|
},
|
|
|
|
None => string.push_str("??? H/s"),
|
|
|
|
}
|
|
|
|
if n != 2 {
|
|
|
|
string.push_str(", ");
|
|
|
|
n += 1;
|
|
|
|
} else {
|
|
|
|
string.push(']');
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Self(string)
|
|
|
|
}
|
|
|
|
fn from_load(array: [Option<f32>; 3]) -> Self {
|
|
|
|
let mut string = "[".to_string();
|
|
|
|
let mut n = 0;
|
|
|
|
for i in array {
|
|
|
|
match i {
|
|
|
|
Some(f) => string.push_str(format!("{}", f).as_str()),
|
|
|
|
None => string.push_str("???"),
|
|
|
|
}
|
|
|
|
if n != 2 {
|
|
|
|
string.push_str(", ");
|
|
|
|
n += 1;
|
|
|
|
} else {
|
|
|
|
string.push(']');
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Self(string)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-05 19:55:50 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- Regexes
|
|
|
|
// Not to be confused with the [Regexes] struct in [main.rs], this one is meant
|
|
|
|
// for parsing the output of P2Pool and finding payouts and total XMR found.
|
|
|
|
// Why Regex instead of the standard library?
|
|
|
|
// 1. I'm already using Regex
|
|
|
|
// 2. It's insanely faster
|
|
|
|
//
|
|
|
|
// The following STDLIB implementation takes [0.003~] seconds to find all matches given a [String] with 30k lines:
|
|
|
|
// let mut n = 0;
|
|
|
|
// for line in P2POOL_OUTPUT.lines() {
|
2022-12-06 03:33:35 +00:00
|
|
|
// if line.contains("You received a payout of [0-9].[0-9]+ XMR") { n += 1; }
|
2022-12-05 19:55:50 +00:00
|
|
|
// }
|
|
|
|
//
|
|
|
|
// This regex function takes [0.0003~] seconds (10x faster):
|
2022-12-06 03:33:35 +00:00
|
|
|
// let regex = Regex::new("You received a payout of [0-9].[0-9]+ XMR").unwrap();
|
2022-12-05 19:55:50 +00:00
|
|
|
// let n = regex.find_iter(P2POOL_OUTPUT).count();
|
|
|
|
//
|
|
|
|
// Both are nominally fast enough where it doesn't matter too much but meh, why not use regex.
|
|
|
|
struct P2poolRegex {
|
2022-12-06 03:33:35 +00:00
|
|
|
payout: regex::Regex,
|
|
|
|
float: regex::Regex,
|
2022-12-05 19:55:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl P2poolRegex {
|
|
|
|
fn new() -> Self {
|
2022-12-06 03:33:35 +00:00
|
|
|
Self {
|
|
|
|
payout: regex::Regex::new("You received a payout of [0-9].[0-9]+ XMR").unwrap(),
|
|
|
|
float: regex::Regex::new("[0-9].[0-9]+").unwrap(),
|
|
|
|
}
|
2022-12-05 19:55:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-02 04:13:53 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- Public P2Pool API
|
|
|
|
// GUI thread interfaces with this.
|
2022-12-06 03:33:35 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2022-12-02 04:13:53 +00:00
|
|
|
pub struct PubP2poolApi {
|
2022-12-04 01:12:40 +00:00
|
|
|
// One off
|
|
|
|
pub mini: bool,
|
2022-12-05 19:55:50 +00:00
|
|
|
// Output
|
|
|
|
pub output: String,
|
2022-12-06 20:17:37 +00:00
|
|
|
// Uptime
|
|
|
|
pub uptime: HumanTime,
|
2022-12-04 01:12:40 +00:00
|
|
|
// These are manually parsed from the STDOUT.
|
2022-12-05 19:55:50 +00:00
|
|
|
pub payouts: u128,
|
2022-12-04 01:12:40 +00:00
|
|
|
pub payouts_hour: f64,
|
|
|
|
pub payouts_day: f64,
|
|
|
|
pub payouts_month: f64,
|
|
|
|
pub xmr: f64,
|
|
|
|
pub xmr_hour: f64,
|
|
|
|
pub xmr_day: f64,
|
|
|
|
pub xmr_month: f64,
|
|
|
|
// The rest are serialized from the API, then turned into [HumanNumber]s
|
|
|
|
pub hashrate_15m: HumanNumber,
|
|
|
|
pub hashrate_1h: HumanNumber,
|
|
|
|
pub hashrate_24h: HumanNumber,
|
|
|
|
pub shares_found: HumanNumber,
|
|
|
|
pub average_effort: HumanNumber,
|
|
|
|
pub current_effort: HumanNumber,
|
|
|
|
pub connections: HumanNumber,
|
2022-11-30 22:21:55 +00:00
|
|
|
}
|
|
|
|
|
2022-12-02 04:13:53 +00:00
|
|
|
impl PubP2poolApi {
|
2022-11-30 22:21:55 +00:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
2022-12-04 01:12:40 +00:00
|
|
|
mini: true,
|
2022-12-05 19:55:50 +00:00
|
|
|
output: String::with_capacity(56_000_000),
|
2022-12-06 20:17:37 +00:00
|
|
|
uptime: HumanTime::new(),
|
2022-12-05 19:55:50 +00:00
|
|
|
payouts: 0,
|
2022-12-04 01:12:40 +00:00
|
|
|
payouts_hour: 0.0,
|
|
|
|
payouts_day: 0.0,
|
|
|
|
payouts_month: 0.0,
|
|
|
|
xmr: 0.0,
|
|
|
|
xmr_hour: 0.0,
|
|
|
|
xmr_day: 0.0,
|
|
|
|
xmr_month: 0.0,
|
|
|
|
hashrate_15m: HumanNumber::unknown(),
|
|
|
|
hashrate_1h: HumanNumber::unknown(),
|
|
|
|
hashrate_24h: HumanNumber::unknown(),
|
|
|
|
shares_found: HumanNumber::unknown(),
|
|
|
|
average_effort: HumanNumber::unknown(),
|
|
|
|
current_effort: HumanNumber::unknown(),
|
|
|
|
connections: HumanNumber::unknown(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-06 18:18:40 +00:00
|
|
|
// Mutate [PubP2poolApi] with data the process output.
|
2022-12-06 20:17:37 +00:00
|
|
|
fn update_from_output(public: &Arc<Mutex<Self>>, output: &Arc<Mutex<String>>, elapsed: std::time::Duration, regex: &P2poolRegex) {
|
|
|
|
// 1. Clone output
|
2022-12-06 03:33:35 +00:00
|
|
|
let output = output.lock().unwrap().clone();
|
2022-12-06 20:17:37 +00:00
|
|
|
|
|
|
|
// 2. Parse STDOUT
|
2022-12-05 19:55:50 +00:00
|
|
|
let (payouts, xmr) = Self::calc_payouts_and_xmr(&output, ®ex);
|
2022-12-06 18:18:40 +00:00
|
|
|
|
2022-12-06 20:17:37 +00:00
|
|
|
// 3. Calculate hour/day/month given elapsed time
|
|
|
|
let elapsed_as_secs_f64 = elapsed.as_secs_f64();
|
2022-12-06 18:18:40 +00:00
|
|
|
// Payouts
|
2022-12-06 20:17:37 +00:00
|
|
|
let per_sec = (payouts as f64) / elapsed_as_secs_f64;
|
2022-12-06 18:18:40 +00:00
|
|
|
let payouts_hour = (per_sec * 60.0) * 60.0;
|
|
|
|
let payouts_day = payouts_hour * 24.0;
|
|
|
|
let payouts_month = payouts_day * 30.0;
|
|
|
|
// Total XMR
|
2022-12-06 20:17:37 +00:00
|
|
|
let per_sec = xmr / elapsed_as_secs_f64;
|
2022-12-06 18:18:40 +00:00
|
|
|
let xmr_hour = (per_sec * 60.0) * 60.0;
|
|
|
|
let xmr_day = payouts_hour * 24.0;
|
|
|
|
let xmr_month = payouts_day * 30.0;
|
|
|
|
|
2022-12-06 20:17:37 +00:00
|
|
|
// 4. Mutate the struct with the new info
|
2022-12-06 18:18:40 +00:00
|
|
|
let mut public = public.lock().unwrap();
|
|
|
|
*public = Self {
|
2022-12-06 20:17:37 +00:00
|
|
|
uptime: HumanTime::into_human(elapsed),
|
2022-12-06 03:33:35 +00:00
|
|
|
output,
|
2022-12-05 19:55:50 +00:00
|
|
|
payouts,
|
|
|
|
xmr,
|
2022-12-06 18:18:40 +00:00
|
|
|
payouts_hour,
|
|
|
|
payouts_day,
|
|
|
|
payouts_month,
|
|
|
|
xmr_hour,
|
|
|
|
xmr_day,
|
|
|
|
xmr_month,
|
|
|
|
..public.clone()
|
2022-12-05 19:55:50 +00:00
|
|
|
};
|
2022-12-06 18:18:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Mutate [PubP2poolApi] with data from a [PrivP2poolApi] and the process output.
|
|
|
|
fn update_from_priv(public: &Arc<Mutex<Self>>, private: &Arc<Mutex<PrivP2poolApi>>) {
|
2022-12-06 20:17:37 +00:00
|
|
|
// priv -> pub conversion
|
2022-12-06 03:33:35 +00:00
|
|
|
let private = private.lock().unwrap();
|
2022-12-06 18:18:40 +00:00
|
|
|
let mut public = public.lock().unwrap();
|
|
|
|
*public = Self {
|
2022-12-05 19:55:50 +00:00
|
|
|
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),
|
2022-12-06 18:18:40 +00:00
|
|
|
..public.clone()
|
2022-12-05 19:55:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 */) {
|
2022-12-06 03:33:35 +00:00
|
|
|
let iter = regex.payout.find_iter(output);
|
2022-12-05 19:55:50 +00:00
|
|
|
let mut result: f64 = 0.0;
|
|
|
|
let mut count: u128 = 0;
|
|
|
|
for i in iter {
|
2022-12-06 03:33:35 +00:00
|
|
|
match regex.float.find(i.as_str()).unwrap().as_str().parse::<f64>() {
|
|
|
|
Ok(num) => { result += num; count += 1; },
|
|
|
|
Err(e) => error!("P2Pool | Total XMR sum calculation error: [{}]", e),
|
2022-12-05 19:55:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
(count, result)
|
|
|
|
}
|
2022-11-30 22:21:55 +00:00
|
|
|
}
|
|
|
|
|
2022-12-02 04:13:53 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- Private P2Pool API
|
|
|
|
// This is the data the "watchdog" threads mutate.
|
|
|
|
// It matches directly to P2Pool's [local/stats] JSON API file (excluding a few stats).
|
|
|
|
// P2Pool seems to initialize all stats at 0 (or 0.0), so no [Option] wrapper seems needed.
|
2022-12-06 03:33:35 +00:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
2022-12-02 04:13:53 +00:00
|
|
|
struct PrivP2poolApi {
|
|
|
|
hashrate_15m: u128,
|
|
|
|
hashrate_1h: u128,
|
|
|
|
hashrate_24h: u128,
|
|
|
|
shares_found: u128,
|
2022-12-04 01:12:40 +00:00
|
|
|
average_effort: f32,
|
|
|
|
current_effort: f32,
|
2022-12-02 04:13:53 +00:00
|
|
|
connections: u16, // No one will have more than 65535 connections... right?
|
|
|
|
}
|
2022-11-30 22:21:55 +00:00
|
|
|
|
2022-12-02 04:13:53 +00:00
|
|
|
impl PrivP2poolApi {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
hashrate_15m: 0,
|
|
|
|
hashrate_1h: 0,
|
|
|
|
hashrate_24h: 0,
|
|
|
|
shares_found: 0,
|
|
|
|
average_effort: 0.0,
|
|
|
|
current_effort: 0.0,
|
|
|
|
connections: 0,
|
|
|
|
}
|
|
|
|
}
|
2022-11-30 22:21:55 +00:00
|
|
|
}
|
|
|
|
|
2022-12-02 04:13:53 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- Public XMRig API
|
2022-12-06 03:33:35 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2022-12-02 04:13:53 +00:00
|
|
|
pub struct PubXmrigApi {
|
2022-12-05 19:55:50 +00:00
|
|
|
output: String,
|
2022-12-04 01:12:40 +00:00
|
|
|
worker_id: String,
|
|
|
|
resources: HumanNumber,
|
|
|
|
hashrate: HumanNumber,
|
|
|
|
pool: String,
|
|
|
|
diff: HumanNumber,
|
|
|
|
accepted: HumanNumber,
|
|
|
|
rejected: HumanNumber,
|
2022-12-02 04:13:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PubXmrigApi {
|
2022-11-30 22:21:55 +00:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
2022-12-05 19:55:50 +00:00
|
|
|
output: String::with_capacity(56_000_000),
|
2022-12-04 01:12:40 +00:00
|
|
|
worker_id: "???".to_string(),
|
|
|
|
resources: HumanNumber::unknown(),
|
|
|
|
hashrate: HumanNumber::unknown(),
|
|
|
|
pool: "???".to_string(),
|
|
|
|
diff: HumanNumber::unknown(),
|
|
|
|
accepted: HumanNumber::unknown(),
|
|
|
|
rejected: HumanNumber::unknown(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Formats raw private data into ready-to-print human readable version.
|
2022-12-05 19:55:50 +00:00
|
|
|
fn from_priv(private: PrivXmrigApi, output: String) -> Self {
|
2022-12-04 01:12:40 +00:00
|
|
|
Self {
|
2022-12-05 19:55:50 +00:00
|
|
|
output: output.clone(),
|
2022-12-04 01:12:40 +00:00
|
|
|
worker_id: private.worker_id,
|
|
|
|
resources: HumanNumber::from_load(private.resources.load_average),
|
|
|
|
hashrate: HumanNumber::from_hashrate(private.hashrate.total),
|
|
|
|
pool: private.connection.pool,
|
|
|
|
diff: HumanNumber::from_u128(private.connection.diff),
|
|
|
|
accepted: HumanNumber::from_u128(private.connection.accepted),
|
|
|
|
rejected: HumanNumber::from_u128(private.connection.rejected),
|
2022-11-30 22:21:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-02 04:13:53 +00:00
|
|
|
//---------------------------------------------------------------------------------------------------- Private XMRig API
|
|
|
|
// This matches to some JSON stats in the HTTP call [summary],
|
|
|
|
// e.g: [wget -qO- localhost:18085/1/summary].
|
|
|
|
// XMRig doesn't initialize stats at 0 (or 0.0) and instead opts for [null]
|
|
|
|
// which means some elements need to be wrapped in an [Option] or else serde will [panic!].
|
2022-12-06 03:33:35 +00:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
2022-12-02 04:13:53 +00:00
|
|
|
struct PrivXmrigApi {
|
|
|
|
worker_id: String,
|
|
|
|
resources: Resources,
|
|
|
|
connection: Connection,
|
|
|
|
hashrate: Hashrate,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PrivXmrigApi {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
worker_id: String::new(),
|
|
|
|
resources: Resources::new(),
|
|
|
|
connection: Connection::new(),
|
|
|
|
hashrate: Hashrate::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-06 03:33:35 +00:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
2022-12-02 04:13:53 +00:00
|
|
|
struct Resources {
|
|
|
|
load_average: [Option<f32>; 3],
|
|
|
|
}
|
|
|
|
impl Resources {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
load_average: [Some(0.0), Some(0.0), Some(0.0)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-06 03:33:35 +00:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
2022-12-02 04:13:53 +00:00
|
|
|
struct Connection {
|
|
|
|
pool: String,
|
|
|
|
diff: u128,
|
|
|
|
accepted: u128,
|
|
|
|
rejected: u128,
|
|
|
|
}
|
|
|
|
impl Connection {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
pool: String::new(),
|
|
|
|
diff: 0,
|
|
|
|
accepted: 0,
|
|
|
|
rejected: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-06 03:33:35 +00:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
2022-12-02 04:13:53 +00:00
|
|
|
struct Hashrate {
|
|
|
|
total: [Option<f32>; 3],
|
|
|
|
}
|
|
|
|
impl Hashrate {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
total: [Some(0.0), Some(0.0), Some(0.0)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|