gupaxx/src/helper.rs

1680 lines
67 KiB
Rust
Raw Normal View History

// 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/>.
// 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 [Stop P2Pool] -> Arc<Mutex<ProcessSignal> is set
// indicating to this thread during its loop: "I should stop P2Pool!", e.g:
//
// if p2pool.lock().unwrap().signal == ProcessSignal::Stop {
// stop_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.
//---------------------------------------------------------------------------------------------------- Import
use std::{
sync::{Arc,Mutex},
path::PathBuf,
process::{Command,Stdio},
fmt::Write,
time::*,
thread,
};
use crate::{
constants::*,
SudoState,
};
use sysinfo::SystemExt;
use serde::{Serialize,Deserialize};
use num_format::{Buffer,Locale};
use log::*;
//---------------------------------------------------------------------------------------------------- Constants
// The locale numbers are formatting in is English, which looks like: [1,000]
const LOCALE: num_format::Locale = num_format::Locale::en;
// The max amount of bytes of process output we are willing to
// hold in memory before it's too much and we need to reset.
const MAX_PROCESS_OUTPUT_BYTES: usize = 56_000_000;
// Just a little leeway so a reset will go off before the [String] allocates more memory.
const PROCESS_OUTPUT_LEEWAY: usize = MAX_PROCESS_OUTPUT_BYTES - 1000;
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// The max bytes the GUI thread should hold
const MAX_GUI_OUTPUT_BYTES: usize = 500_000;
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
const GUI_OUTPUT_LEEWAY: usize = MAX_GUI_OUTPUT_BYTES - 1000;
//---------------------------------------------------------------------------------------------------- [Helper] Struct
// A meta struct holding all the data that gets processed in this thread
pub struct Helper {
pub instant: Instant, // Gupax start as an [Instant]
pub uptime: HumanTime, // Gupax uptime formatting for humans
pub pub_sys: Arc<Mutex<Sys>>, // The public API for [sysinfo] that the [Status] tab reads from
pub p2pool: Arc<Mutex<Process>>, // P2Pool process state
pub xmrig: Arc<Mutex<Process>>, // XMRig process state
pub gui_api_p2pool: Arc<Mutex<PubP2poolApi>>, // P2Pool API state (for GUI thread)
pub gui_api_xmrig: Arc<Mutex<PubXmrigApi>>, // XMRig API state (for GUI thread)
pub img_p2pool: Arc<Mutex<ImgP2pool>>, // A static "image" of the data P2Pool started with
pub img_xmrig: Arc<Mutex<ImgXmrig>>, // A static "image" of the data XMRig started with
pub_api_p2pool: Arc<Mutex<PubP2poolApi>>, // P2Pool API state (for Helper/P2Pool thread)
pub_api_xmrig: Arc<Mutex<PubXmrigApi>>, // XMRig API state (for Helper/XMRig thread)
priv_api_p2pool: Arc<Mutex<PrivP2poolApi>>, // For "watchdog" thread
priv_api_xmrig: Arc<Mutex<PrivXmrigApi>>, // For "watchdog" thread
}
// The communication between the data here and the GUI thread goes as follows:
// [GUI] <---> [Helper] <---> [Watchdog] <---> [Private Data only available here]
//
// Both [GUI] and [Helper] own their separate [Pub*Api] structs.
// Since P2Pool & XMRig will be updating their information out of sync,
// it's the helpers job to lock everything, and move the watchdog [Pub*Api]s
// on a 1-second interval into the [GUI]'s [Pub*Api] struct, atomically.
//----------------------------------------------------------------------------------------------------
#[derive(Debug,Clone)]
pub struct Sys {
pub gupax_uptime: String,
pub gupax_cpu_usage: String,
pub gupax_memory_used_mb: String,
pub system_cpu_model: String,
pub system_memory: String,
pub system_cpu_usage: String,
}
impl Sys {
pub fn new() -> Self {
Self {
gupax_uptime: "0 seconds".to_string(),
gupax_cpu_usage: "???%".to_string(),
gupax_memory_used_mb: "??? megabytes".to_string(),
system_cpu_usage: "???%".to_string(),
system_memory: "???GB / ???GB".to_string(),
system_cpu_model: "???".to_string(),
}
}
}
//---------------------------------------------------------------------------------------------------- [Process] Struct
// This holds all the state of a (child) process.
// The main GUI thread will use this to display console text, online state, etc.
pub struct Process {
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]?
// 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
//
pub input: Vec<String>,
// The below are the handles to the actual child process.
// [Simple] has no STDIN, but [Advanced] does. A PTY (pseudo-terminal) is
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
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
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].
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// "full" contains the entire output (up to a month, or 50m bytes), "buf" will be written to
// the same as full, but it will be [swap()]'d by the "helper" thread into the GUIs [String].
// The "helper" thread synchronizes this swap so that the data in here is moved there
// roughly once a second. GUI thread never touches this.
output_full: Arc<Mutex<String>>,
output_buf: Arc<Mutex<String>>,
// Start time of process.
start: std::time::Instant,
}
//---------------------------------------------------------------------------------------------------- [Process] Impl
impl Process {
pub fn new(name: ProcessName, args: String, path: PathBuf) -> Self {
Self {
name,
state: ProcessState::Dead,
signal: ProcessSignal::None,
start: Instant::now(),
stdin: Option::None,
child: Option::None,
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// MAX should last around a month for P2Pool log level 3.
output_full: Arc::new(Mutex::new(String::with_capacity(MAX_PROCESS_OUTPUT_BYTES))),
output_buf: Arc::new(Mutex::new(String::new())),
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()
}
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
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
}
}
//---------------------------------------------------------------------------------------------------- [Process*] Enum
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessState {
Alive, // Process is online, GREEN!
Dead, // Process is dead, BLACK!
Failed, // Process is dead AND exited with a bad code, RED!
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
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!
}
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessSignal {
None,
Start,
Stop,
Restart,
}
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessName {
P2pool,
Xmrig,
}
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) } }
//---------------------------------------------------------------------------------------------------- [Helper]
use tokio::io::{BufReader,AsyncBufReadExt};
impl Helper {
//---------------------------------------------------------------------------------------------------- General Functions
pub fn new(instant: std::time::Instant, pub_sys: Arc<Mutex<Sys>>, p2pool: Arc<Mutex<Process>>, xmrig: Arc<Mutex<Process>>, gui_api_p2pool: Arc<Mutex<PubP2poolApi>>, gui_api_xmrig: Arc<Mutex<PubXmrigApi>>, img_p2pool: Arc<Mutex<ImgP2pool>>, img_xmrig: Arc<Mutex<ImgXmrig>>) -> Self {
Self {
instant,
pub_sys,
uptime: HumanTime::into_human(instant.elapsed()),
priv_api_p2pool: Arc::new(Mutex::new(PrivP2poolApi::new())),
priv_api_xmrig: Arc::new(Mutex::new(PrivXmrigApi::new())),
pub_api_p2pool: Arc::new(Mutex::new(PubP2poolApi::new())),
pub_api_xmrig: Arc::new(Mutex::new(PubXmrigApi::new())),
// These are created when initializing [App], since it needs a handle to it as well
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
p2pool,
xmrig,
gui_api_p2pool,
gui_api_xmrig,
img_p2pool,
img_xmrig,
}
}
// Reads a PTY which combines STDOUT/STDERR for me, yay
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
fn read_pty(output_full: Arc<Mutex<String>>, output_buf: Arc<Mutex<String>>, reader: Box<dyn std::io::Read + Send>) {
use std::io::BufRead;
let mut stdout = std::io::BufReader::new(reader).lines();
while let Some(Ok(line)) = stdout.next() {
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// println!("{}", line); // For debugging.
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
writeln!(output_full.lock().unwrap(), "{}", line);
writeln!(output_buf.lock().unwrap(), "{}", line);
}
}
// Reset output if larger than 55_999_000 bytes (around 1 week of logs).
// The actual [String] holds 56_000_000, but this allows for some leeway so it doesn't allocate more memory.
// This will also append a message showing it was reset.
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
fn check_reset_output_full(output_full: &Arc<Mutex<String>>, name: ProcessName) {
debug!("{} Watchdog | Resetting [output_full]...", name);
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
let mut output_full = output_full.lock().unwrap();
if output_full.len() > PROCESS_OUTPUT_LEEWAY {
info!("{} | Output is nearing {} bytes, resetting!", MAX_PROCESS_OUTPUT_BYTES, name);
let text = format!("{}\n{} internal log is exceeding the maximum: {} bytes!\nI've reset the logs for you, your stats may now be inaccurate since they depend on these logs!\nI think you rather have that than have it hogging your memory, though!\n{}\n\n\n\n", HORI_CONSOLE, name, MAX_PROCESS_OUTPUT_BYTES, HORI_CONSOLE);
output_full.clear();
output_full.push_str(&text);
}
debug!("{} Watchdog | Resetting [output_full] ... OK", name);
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
}
// For the GUI thread
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
fn check_reset_gui_p2pool_output(gui_api: &Arc<Mutex<PubP2poolApi>>) {
debug!("P2Pool Watchdog | Resetting GUI output...");
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
let mut gui_api = gui_api.lock().unwrap();
if gui_api.output.len() > GUI_OUTPUT_LEEWAY {
info!("P2Pool | Output is nearing {} bytes, resetting!", MAX_GUI_OUTPUT_BYTES);
let text = format!("{}\nP2Pool GUI log is exceeding the maximum: {} bytes!\nI've reset the logs for you, but your stats will be fine (at least for around a month) since they rely on an internal log!\n{}\n\n\n\n", HORI_CONSOLE, MAX_GUI_OUTPUT_BYTES, HORI_CONSOLE);
gui_api.output.clear();
gui_api.output.push_str(&text);
}
debug!("P2Pool Watchdog | Resetting GUI output ... OK");
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
}
fn check_reset_gui_xmrig_output(gui_api: &Arc<Mutex<PubXmrigApi>>) {
debug!("XMRig Watchdog | Resetting GUI output...");
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
let mut gui_api = gui_api.lock().unwrap();
if gui_api.output.len() > GUI_OUTPUT_LEEWAY {
info!("XMRig | Output is nearing {} bytes, resetting!", MAX_GUI_OUTPUT_BYTES);
let text = format!("{}\nP2Pool GUI log is exceeding the maximum: {} bytes!\nI've reset the logs for you, but your stats will be fine (at least for around a month) since they rely on an internal log!\n{}\n\n\n\n", HORI_CONSOLE, MAX_GUI_OUTPUT_BYTES, HORI_CONSOLE);
gui_api.output.clear();
gui_api.output.push_str(&text);
}
debug!("XMRig Watchdog | Resetting GUI output ... OK");
}
//---------------------------------------------------------------------------------------------------- P2Pool specific
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// Just sets some signals for the watchdog thread to pick up on.
pub fn stop_p2pool(helper: &Arc<Mutex<Self>>) {
info!("P2Pool | Attempting to stop...");
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
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 to restart...");
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
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 | Want to restart but process is still alive, waiting...");
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
thread::sleep(SECOND);
}
// Ok, process is not alive, start the new one!
info!("P2Pool | Old process seems dead, starting new one!");
Self::start_p2pool(&helper, &state, &path);
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
});
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: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
helper.lock().unwrap().p2pool.lock().unwrap().state = ProcessState::Middle;
let args = Self::build_p2pool_args_and_mutate_img(helper, state, path);
// Print arguments & user settings to console
crate::disk::print_dash(&format!("P2Pool | Launch arguments: {:#?}", args));
// Spawn watchdog thread
let process = Arc::clone(&helper.lock().unwrap().p2pool);
let gui_api = Arc::clone(&helper.lock().unwrap().gui_api_p2pool);
let pub_api = Arc::clone(&helper.lock().unwrap().pub_api_p2pool);
let priv_api = Arc::clone(&helper.lock().unwrap().priv_api_p2pool);
let path = path.clone();
thread::spawn(move || {
Self::spawn_p2pool_watchdog(process, gui_api, pub_api, priv_api, args, path);
});
}
// Takes in some [State/P2pool] and parses it to build the actual command arguments.
// Returns the [Vec] of actual arguments, and mutates the [ImgP2pool] for the main GUI thread
// It returns a value... and mutates a deeply nested passed argument... this is some pretty bad code...
pub fn build_p2pool_args_and_mutate_img(helper: &Arc<Mutex<Self>>, state: &crate::disk::P2pool, path: &std::path::PathBuf) -> Vec<String> {
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)
args.push("--wallet".to_string()); args.push(state.address.clone()); // Wallet address
args.push("--host".to_string()); args.push(ip.to_string()); // IP Address
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
*helper.lock().unwrap().img_p2pool.lock().unwrap() = ImgP2pool {
mini: true,
address: state.address.clone(),
host: ip.to_string(),
rpc: rpc.to_string(),
zmq: zmq.to_string(),
log_level: "3".to_string(),
out_peers: "10".to_string(),
in_peers: "10".to_string(),
};
// [Advanced]
} else {
// Overriding command arguments
if !state.arguments.is_empty() {
// This parses the input and attemps to fill out
// the [ImgP2pool]... This is pretty bad code...
let mut last = "";
let lock = helper.lock().unwrap();
let mut p2pool_image = lock.img_p2pool.lock().unwrap();
for arg in state.arguments.split_whitespace() {
match last {
"--mini" => p2pool_image.mini = true,
"--wallet" => p2pool_image.address = arg.to_string(),
"--host" => p2pool_image.host = arg.to_string(),
"--rpc-port" => p2pool_image.rpc = arg.to_string(),
"--zmq-port" => p2pool_image.zmq = arg.to_string(),
"--loglevel" => p2pool_image.log_level = arg.to_string(),
"--out-peers" => p2pool_image.out_peers = arg.to_string(),
"--in-peers" => p2pool_image.in_peers = arg.to_string(),
_ => (),
}
args.push(arg.to_string());
last = arg;
}
// Else, build the argument
} else {
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
*helper.lock().unwrap().img_p2pool.lock().unwrap() = ImgP2pool {
mini: state.mini,
address: state.address.clone(),
host: state.selected_ip.to_string(),
rpc: state.selected_rpc.to_string(),
zmq: state.selected_zmq.to_string(),
log_level: state.log_level.to_string(),
out_peers: state.out_peers.to_string(),
in_peers: state.in_peers.to_string(),
}
}
}
args
}
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
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.
fn spawn_p2pool_watchdog(process: Arc<Mutex<Process>>, gui_api: Arc<Mutex<PubP2poolApi>>, pub_api: Arc<Mutex<PubP2poolApi>>, priv_api: Arc<Mutex<PrivP2poolApi>>, args: Vec<String>, mut path: std::path::PathBuf) {
// 1a. Create PTY
debug!("P2Pool | Creating PTY...");
let pty = portable_pty::native_pty_system();
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
let pair = pty.openpty(portable_pty::PtySize {
rows: 100,
cols: 1000,
pixel_width: 0,
pixel_height: 0,
}).unwrap();
// 1b. Create command
debug!("P2Pool | Creating command...");
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
let mut cmd = portable_pty::CommandBuilder::new(path.as_path());
cmd.args(args);
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
cmd.cwd(path.as_path().parent().unwrap());
// 1c. Create child
debug!("P2Pool | Creating child...");
let child_pty = Arc::new(Mutex::new(pair.slave.spawn_command(cmd).unwrap()));
// 2. Set process state
debug!("P2Pool | Setting process state...");
let mut lock = process.lock().unwrap();
lock.state = ProcessState::Alive;
lock.signal = ProcessSignal::None;
lock.start = Instant::now();
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
lock.child = Some(Arc::clone(&child_pty));
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
debug!("P2Pool | Spawning PTY read thread...");
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
let output_full = Arc::clone(&process.lock().unwrap().output_full);
let output_buf = Arc::clone(&process.lock().unwrap().output_buf);
thread::spawn(move || {
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
Self::read_pty(output_full, output_buf, reader);
});
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
let output_full = Arc::clone(&process.lock().unwrap().output_full);
let output_buf = Arc::clone(&process.lock().unwrap().output_buf);
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
path.pop();
path.push(P2POOL_API_PATH);
debug!("P2Pool | Cleaning old API files...");
// Attempt to remove stale API file
match std::fs::remove_file(&path) {
Ok(_) => info!("P2Pool | Attempting to remove stale API file ... OK"),
Err(e) => warn!("P2Pool | Attempting to remove stale API file ... FAIL ... {}", e),
}
// Attempt to create a default empty one.
use std::io::Write;
if let Ok(_) = std::fs::File::create(&path) {
let text = r#"{"hashrate_15m":0,"hashrate_1h":0,"hashrate_24h":0,"shares_found":0,"average_effort":0.0,"current_effort":0.0,"connections":0}"#;
match std::fs::write(&path, text) {
Ok(_) => info!("P2Pool | Creating default empty API file ... OK"),
Err(e) => warn!("P2Pool | Creating default empty API file ... FAIL ... {}", e),
}
}
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
let regex = P2poolRegex::new();
let start = process.lock().unwrap().start;
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// 4. Loop as watchdog
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
info!("P2Pool | Entering watchdog mode... woof!");
loop {
// Set timer
let now = Instant::now();
debug!("P2Pool Watchdog | ----------- Start of loop -----------");
// Check if the process is secretly died without us knowing :)
if let Ok(Some(code)) = child_pty.lock().unwrap().try_wait() {
debug!("P2Pool Watchdog | Process secretly died! Getting exit status");
let exit_status = match code.success() {
true => { process.lock().unwrap().state = ProcessState::Dead; "Successful" },
false => { process.lock().unwrap().state = ProcessState::Failed; "Failed" },
};
let uptime = HumanTime::into_human(start.elapsed());
info!("P2Pool Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status);
// This is written directly into the GUI, because sometimes the 900ms event loop can't catch it.
writeln!(gui_api.lock().unwrap().output, "{}\nP2Pool stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE);
process.lock().unwrap().signal = ProcessSignal::None;
debug!("P2Pool Watchdog | Secret dead process reap OK, breaking");
break
}
// Check SIGNAL
if process.lock().unwrap().signal == ProcessSignal::Stop {
debug!("P2Pool Watchdog | Stop SIGNAL caught");
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
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
let exit_status = match child_pty.lock().unwrap().wait() {
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" },
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
};
let uptime = HumanTime::into_human(start.elapsed());
info!("P2Pool Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status);
// This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it.
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
writeln!(gui_api.lock().unwrap().output, "{}\nP2Pool stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE);
process.lock().unwrap().signal = ProcessSignal::None;
debug!("P2Pool Watchdog | Stop SIGNAL done, breaking");
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
break
// Check RESTART
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
} else if process.lock().unwrap().signal == ProcessSignal::Restart {
debug!("P2Pool Watchdog | Restart SIGNAL caught");
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
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
let exit_status = match child_pty.lock().unwrap().wait() {
Ok(e) => if e.success() { "Successful" } else { "Failed" },
_ => "Unknown Error",
};
let uptime = HumanTime::into_human(start.elapsed());
info!("P2Pool Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status);
// This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it.
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
writeln!(gui_api.lock().unwrap().output, "{}\nP2Pool stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE);
process.lock().unwrap().state = ProcessState::Waiting;
debug!("P2Pool Watchdog | Restart SIGNAL done, breaking");
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 {
debug!("P2Pool Watchdog | User input not empty, writing to STDIN: [{}]", line);
writeln!(lock.stdin.as_mut().unwrap(), "{}", line);
}
}
drop(lock);
// Check if logs need resetting
debug!("P2Pool Watchdog | Attempting log reset check");
Self::check_reset_output_full(&output_full, ProcessName::P2pool);
Self::check_reset_gui_p2pool_output(&gui_api);
// Always update from output
debug!("P2Pool Watchdog | Starting [update_from_output()]");
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
PubP2poolApi::update_from_output(&pub_api, &output_full, &output_buf, start.elapsed(), &regex);
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// Read API file into string
debug!("P2Pool Watchdog | Attempting API file read");
if let Ok(string) = PrivP2poolApi::read_p2pool_api(&path) {
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// Deserialize
if let Ok(s) = PrivP2poolApi::str_to_priv_p2pool_api(&string) {
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// Update the structs.
PubP2poolApi::update_from_priv(&pub_api, s);
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +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 {
let sleep = (900-elapsed) as u64;
debug!("P2Pool Watchdog | END OF LOOP - Sleeping for [{}]ms...", sleep);
std::thread::sleep(std::time::Duration::from_millis(sleep));
} else {
debug!("P2Pool Watchdog | END OF LOOP - Not sleeping!");
}
}
// 5. If loop broke, we must be done here.
info!("P2Pool Watchdog | Watchdog thread exiting... Goodbye!");
}
//---------------------------------------------------------------------------------------------------- XMRig specific, most functions are very similar to P2Pool's
// // 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) },
// }
// }
//
// If processes are started with [sudo] on macOS, they must also
// be killed with [sudo] (even if I have a direct handle to it as the
// parent process...!). This is only needed on macOS, not Linux.
fn sudo_kill(pid: u32, sudo: &Arc<Mutex<SudoState>>) -> bool {
// Spawn [sudo] to execute [kill] on the given [pid]
let mut child = std::process::Command::new("sudo")
.args(["--stdin", "kill", "-9", &pid.to_string()])
.stdin(Stdio::piped())
.spawn().unwrap();
// Write the [sudo] password to STDIN.
let mut stdin = child.stdin.take().unwrap();
use std::io::Write;
writeln!(stdin, "{}\n", sudo.lock().unwrap().pass);
// Return exit code of [sudo/kill].
child.wait().unwrap().success()
}
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// Just sets some signals for the watchdog thread to pick up on.
pub fn stop_xmrig(helper: &Arc<Mutex<Self>>) {
info!("XMRig | Attempting to stop...");
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
helper.lock().unwrap().xmrig.lock().unwrap().signal = ProcessSignal::Stop;
helper.lock().unwrap().xmrig.lock().unwrap().state = ProcessState::Middle;
}
// The "restart frontend" to a "frontend" function.
// Basically calls to kill the current xmrig, waits a little, then starts the below function in a a new thread, then exit.
pub fn restart_xmrig(helper: &Arc<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf, sudo: Arc<Mutex<SudoState>>) {
info!("XMRig | Attempting to restart...");
helper.lock().unwrap().xmrig.lock().unwrap().signal = ProcessSignal::Restart;
helper.lock().unwrap().xmrig.lock().unwrap().state = ProcessState::Middle;
let helper = Arc::clone(&helper);
let state = state.clone();
let path = path.clone();
// This thread lives to wait, start xmrig then die.
thread::spawn(move || {
while helper.lock().unwrap().xmrig.lock().unwrap().state != ProcessState::Waiting {
warn!("XMRig | Want to restart but process is still alive, waiting...");
thread::sleep(SECOND);
}
// Ok, process is not alive, start the new one!
info!("XMRig | Old process seems dead, starting new one!");
Self::start_xmrig(&helper, &state, &path, sudo);
});
info!("XMRig | Restart ... OK");
}
pub fn start_xmrig(helper: &Arc<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf, sudo: Arc<Mutex<SudoState>>) {
helper.lock().unwrap().xmrig.lock().unwrap().state = ProcessState::Middle;
let (args, api_ip_port) = Self::build_xmrig_args_and_mutate_img(helper, state, path);
// Print arguments & user settings to console
crate::disk::print_dash(&format!("XMRig | Launch arguments: {:#?}", args));
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
info!("XMRig | Using path: [{}]", path.display());
// Spawn watchdog thread
let process = Arc::clone(&helper.lock().unwrap().xmrig);
let gui_api = Arc::clone(&helper.lock().unwrap().gui_api_xmrig);
let pub_api = Arc::clone(&helper.lock().unwrap().pub_api_xmrig);
let priv_api = Arc::clone(&helper.lock().unwrap().priv_api_xmrig);
let path = path.clone();
thread::spawn(move || {
Self::spawn_xmrig_watchdog(process, gui_api, pub_api, priv_api, args, path, sudo, api_ip_port);
});
}
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// Takes in some [State/Xmrig] and parses it to build the actual command arguments.
// Returns the [Vec] of actual arguments, and mutates the [ImgXmrig] for the main GUI thread
// It returns a value... and mutates a deeply nested passed argument... this is some pretty bad code...
pub fn build_xmrig_args_and_mutate_img(helper: &Arc<Mutex<Self>>, state: &crate::disk::Xmrig, path: &std::path::PathBuf) -> (Vec<String>, String) {
let mut args = Vec::with_capacity(500);
let mut api_ip_port = String::with_capacity(15);
let path = path.clone();
// The actual binary we're executing is [sudo], technically
// the XMRig path is just an argument to sudo, so add it.
// Before that though, add the ["--prompt"] flag and set it
// to emptyness so that it doesn't show up in the output.
if cfg!(unix) {
args.push(r#"--prompt="#.to_string());
args.push("--".to_string());
args.push(path.display().to_string());
}
// [Simple]
if state.simple {
// Build the xmrig argument
2022-12-12 03:01:37 +00:00
let rig = if state.simple_rig.is_empty() { GUPAX_VERSION_UNDERSCORE.to_string() } else { state.simple_rig.clone() }; // Rig name
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
args.push("--url".to_string()); args.push("127.0.0.1:3333".to_string()); // Local P2Pool (the default)
args.push("--threads".to_string()); args.push(state.current_threads.to_string()); // Threads
args.push("--user".to_string()); args.push(rig.clone()); // Rig name
args.push("--no-color".to_string()); // No color
args.push("--http-host".to_string()); args.push("127.0.0.1".to_string()); // HTTP API IP
args.push("--http-port".to_string()); args.push("18088".to_string()); // HTTP API Port
if state.pause != 0 { args.push("--pause-on-active".to_string()); args.push(state.pause.to_string()); } // Pause on active
*helper.lock().unwrap().img_xmrig.lock().unwrap() = ImgXmrig {
threads: state.current_threads.to_string(),
url: "127.0.0.1:3333 (Local P2Pool)".to_string(),
};
api_ip_port = "127.0.0.1:18088".to_string();
// [Advanced]
} else {
// Overriding command arguments
if !state.arguments.is_empty() {
// This parses the input and attemps to fill out
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// the [ImgXmrig]... This is pretty bad code...
let mut last = "";
let lock = helper.lock().unwrap();
let mut xmrig_image = lock.img_xmrig.lock().unwrap();
for arg in state.arguments.split_whitespace() {
match last {
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
"--threads" => xmrig_image.threads = arg.to_string(),
"--url" => xmrig_image.url = arg.to_string(),
_ => (),
}
args.push(arg.to_string());
last = arg;
}
// Else, build the argument
} else {
let api_ip = if state.api_ip == "localhost" || state.api_ip.is_empty() { "127.0.0.1" } else { &state.api_ip }; // XMRig doesn't understand [localhost]
let api_port = if state.api_port.is_empty() { "18088" } else { &state.api_port };
let url = format!("{}:{}", state.selected_ip, state.selected_port); // Combine IP:Port into one string
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
args.push("--user".to_string()); args.push(state.address.clone()); // Wallet
args.push("--threads".to_string()); args.push(state.current_threads.to_string()); // Threads
args.push("--rig-id".to_string()); args.push(state.selected_rig.to_string()); // Rig ID
args.push("--url".to_string()); args.push(url.clone()); // IP/Port
args.push("--http-host".to_string()); args.push(api_ip.to_string()); // HTTP API IP
args.push("--http-port".to_string()); args.push(api_port.to_string()); // HTTP API Port
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
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("--pause-on-active".to_string()); args.push(state.pause.to_string()); } // Pause on active
*helper.lock().unwrap().img_xmrig.lock().unwrap() = ImgXmrig {
url,
threads: state.current_threads.to_string(),
};
api_ip_port = format!("{}:{}", api_ip, api_port);
}
}
(args, api_ip_port)
}
// We actually spawn [sudo] on Unix, with XMRig being the argument.
#[cfg(target_family = "unix")]
fn create_xmrig_cmd_unix(args: Vec<String>, path: PathBuf) -> portable_pty::CommandBuilder {
let mut cmd = portable_pty::cmdbuilder::CommandBuilder::new("sudo");
cmd.args(args);
cmd.cwd(path.as_path().parent().unwrap());
cmd
}
// Gupax should be admin on Windows, so just spawn XMRig normally.
#[cfg(target_os = "windows")]
fn create_xmrig_cmd_windows(args: Vec<String>, path: PathBuf) -> portable_pty::CommandBuilder {
let mut cmd = portable_pty::cmdbuilder::CommandBuilder::new(path.clone());
cmd.args(args);
cmd.cwd(path.as_path().parent().unwrap());
cmd
}
// The XMRig watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works.
// This isn't actually async, a tokio runtime is unfortunately needed because [Hyper] is an async library (HTTP API calls)
#[tokio::main]
async fn spawn_xmrig_watchdog(process: Arc<Mutex<Process>>, gui_api: Arc<Mutex<PubXmrigApi>>, pub_api: Arc<Mutex<PubXmrigApi>>, priv_api: Arc<Mutex<PrivXmrigApi>>, args: Vec<String>, mut path: std::path::PathBuf, sudo: Arc<Mutex<SudoState>>, api_ip_port: String) {
// 1a. Create PTY
debug!("XMRig | Creating PTY...");
let pty = portable_pty::native_pty_system();
let mut pair = pty.openpty(portable_pty::PtySize {
rows: 100,
cols: 1000,
pixel_width: 0,
pixel_height: 0,
}).unwrap();
// 1b. Create command
debug!("XMRig | Creating command...");
#[cfg(target_os = "windows")]
let cmd = Self::create_xmrig_cmd_windows(args, path);
#[cfg(target_family = "unix")]
let cmd = Self::create_xmrig_cmd_unix(args, path);
// 1c. Create child
debug!("XMRig | Creating child...");
let child_pty = Arc::new(Mutex::new(pair.slave.spawn_command(cmd).unwrap()));
// 2. Input [sudo] pass, wipe, then drop.
if cfg!(unix) {
debug!("XMRig | Inputting [sudo] and wiping...");
// 1d. Sleep to wait for [sudo]'s non-echo prompt (on Unix).
// this prevents users pass from showing up in the STDOUT.
std::thread::sleep(std::time::Duration::from_secs(3));
writeln!(pair.master, "{}", sudo.lock().unwrap().pass);
SudoState::wipe(&sudo);
}
// 3. Set process state
debug!("XMRig | Setting process state...");
let mut lock = process.lock().unwrap();
lock.state = ProcessState::Alive;
lock.signal = ProcessSignal::None;
lock.start = Instant::now();
lock.child = Some(Arc::clone(&child_pty));
let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY
lock.stdin = Some(pair.master);
drop(lock);
// 4. Spawn PTY read thread
debug!("XMRig | Spawning PTY read thread...");
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
let output_full = Arc::clone(&process.lock().unwrap().output_full);
let output_buf = Arc::clone(&process.lock().unwrap().output_buf);
thread::spawn(move || {
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
Self::read_pty(output_full, output_buf, reader);
});
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
let output_full = Arc::clone(&process.lock().unwrap().output_full);
let output_buf = Arc::clone(&process.lock().unwrap().output_buf);
let client: hyper::Client<hyper::client::HttpConnector> = hyper::Client::builder().build(hyper::client::HttpConnector::new());
let start = process.lock().unwrap().start;
// 5. Loop as watchdog
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
info!("XMRig | Entering watchdog mode... woof!");
loop {
// Set timer
let now = Instant::now();
debug!("XMRig Watchdog | ----------- Start of loop -----------");
// Check if the process secretly died without us knowing :)
if let Ok(Some(code)) = child_pty.lock().unwrap().try_wait() {
debug!("XMRig Watchdog | Process secretly died on us! Getting exit status...");
let exit_status = match code.success() {
true => { process.lock().unwrap().state = ProcessState::Dead; "Successful" },
false => { process.lock().unwrap().state = ProcessState::Failed; "Failed" },
};
let uptime = HumanTime::into_human(start.elapsed());
info!("XMRig | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status);
writeln!(gui_api.lock().unwrap().output, "{}\nXMRig stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE);
process.lock().unwrap().signal = ProcessSignal::None;
debug!("XMRig Watchdog | Secret dead process reap OK, breaking");
break
}
// Stop on [Stop/Restart] SIGNAL
let signal = process.lock().unwrap().signal;
if signal == ProcessSignal::Stop || signal == ProcessSignal::Restart {
debug!("XMRig Watchdog | Stop/Restart SIGNAL caught");
// macOS requires [sudo] again to kill [XMRig]
if cfg!(target_os = "macos") {
// If we're at this point, that means the user has
// entered their [sudo] pass again, after we wiped it.
// So, we should be able to find it in our [Arc<Mutex<SudoState>>].
Self::sudo_kill(child_pty.lock().unwrap().process_id().unwrap(), &sudo);
// And... wipe it again (only if we're stopping full).
// If we're restarting, the next start will wipe it for us.
if signal != ProcessSignal::Restart { SudoState::wipe(&sudo); }
} else {
child_pty.lock().unwrap().kill();
}
let exit_status = match child_pty.lock().unwrap().wait() {
Ok(e) => {
let mut process = process.lock().unwrap();
if e.success() {
if process.signal == ProcessSignal::Stop { process.state = ProcessState::Dead; }
"Successful"
} else {
if process.signal == ProcessSignal::Stop { process.state = ProcessState::Failed; }
"Failed"
}
},
_ => {
let mut process = process.lock().unwrap();
if process.signal == ProcessSignal::Stop { process.state = ProcessState::Failed; }
"Unknown Error"
},
};
let uptime = HumanTime::into_human(start.elapsed());
info!("XMRig | Stopped ... Uptime was: [{}], Exit status: [{}]", uptime, exit_status);
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
writeln!(gui_api.lock().unwrap().output, "{}\nXMRig stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE);
let mut process = process.lock().unwrap();
match process.signal {
ProcessSignal::Stop => process.signal = ProcessSignal::None,
ProcessSignal::Restart => process.state = ProcessState::Waiting,
_ => (),
}
debug!("XMRig Watchdog | Stop/Restart SIGNAL done, breaking");
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 {
debug!("XMRig Watchdog | User input not empty, writing to STDIN: [{}]", line);
writeln!(lock.stdin.as_mut().unwrap(), "{}", line);
}
}
drop(lock);
// Check if logs need resetting
debug!("XMRig Watchdog | Attempting log reset check");
Self::check_reset_output_full(&output_full, ProcessName::Xmrig);
Self::check_reset_gui_xmrig_output(&gui_api);
// Always update from output
debug!("XMRig Watchdog | Starting [update_from_output()]");
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
PubXmrigApi::update_from_output(&pub_api, &output_buf, start.elapsed());
// Send an HTTP API request
debug!("XMRig Watchdog | Attempting HTTP API request...");
if let Ok(priv_api) = PrivXmrigApi::request_xmrig_api(client.clone(), &api_ip_port).await {
debug!("XMRig Watchdog | HTTP API request OK, attempting [update_from_priv()]");
2022-12-12 03:01:37 +00:00
PubXmrigApi::update_from_priv(&pub_api, priv_api);
} else {
warn!("XMRig Watchdog | Could not send HTTP API request to: {}", api_ip_port);
}
// 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 {
let sleep = (900-elapsed) as u64;
debug!("XMRig Watchdog | END OF LOOP - Sleeping for [{}]ms...", sleep);
std::thread::sleep(std::time::Duration::from_millis(sleep));
} else {
debug!("XMRig Watchdog | END OF LOOP - Not sleeping!");
}
}
// 5. If loop broke, we must be done here.
info!("XMRig Watchdog | Watchdog thread exiting... Goodbye!");
}
//---------------------------------------------------------------------------------------------------- The "helper"
fn update_pub_sys_from_sysinfo(sysinfo: &sysinfo::System, pub_sys: &mut Sys, pid: &sysinfo::Pid, helper: &Helper, max_threads: usize) {
use sysinfo::{CpuExt,ProcessExt,NetworkExt,NetworksExt};
let gupax_uptime = helper.uptime.to_string();
let cpu = &sysinfo.cpus()[0];
let gupax_cpu_usage = format!("{:.2}%", sysinfo.process(*pid).unwrap().cpu_usage()/(max_threads as f32));
let gupax_memory_used_mb = HumanNumber::from_u64(sysinfo.process(*pid).unwrap().memory()/1_000_000);
let gupax_memory_used_mb = format!("{} megabytes", gupax_memory_used_mb);
let system_cpu_model = format!("{} ({}MHz)", cpu.brand(), cpu.frequency());
let system_memory = {
let used = (sysinfo.used_memory() as f64)/1_000_000_000.0;
let total = (sysinfo.total_memory() as f64)/1_000_000_000.0;
format!("{:.3} GB / {:.3} GB", used, total)
};
let system_cpu_usage = {
let mut total: f32 = 0.0;
for cpu in sysinfo.cpus() {
total += cpu.cpu_usage();
}
format!("{:.2}%", total/(max_threads as f32))
};
*pub_sys = Sys {
gupax_uptime,
gupax_cpu_usage,
gupax_memory_used_mb,
system_cpu_usage,
system_memory,
system_cpu_model,
..*pub_sys
};
}
// The "helper" thread. Syncs data between threads here and the GUI.
pub fn spawn_helper(helper: &Arc<Mutex<Self>>, mut sysinfo: sysinfo::System, pid: sysinfo::Pid, max_threads: usize) {
// The ordering of these locks is _very_ important. They MUST be in sync with how the main GUI thread locks stuff
// or a deadlock will occur given enough time. They will eventually both want to lock the [Arc<Mutex>] the other
// thread is already locking. Yes, I figured this out the hard way, hence the vast amount of debug!() messages.
// Example of different order (BAD!):
//
// GUI Main -> locks [p2pool] first
// Helper -> locks [gui_api_p2pool] first
// GUI Status Tab -> trys to lock [gui_api_p2pool] -> CAN'T
// Helper -> trys to lock [p2pool] -> CAN'T
//
// These two threads are now in a deadlock because both
// are trying to access locks the other one already has.
//
// The locking order here must be in the same chronological
// order as the main GUI thread (top to bottom).
let helper = Arc::clone(helper);
let lock = helper.lock().unwrap();
let p2pool = Arc::clone(&lock.p2pool);
let xmrig = Arc::clone(&lock.xmrig);
let pub_sys = Arc::clone(&lock.pub_sys);
let gui_api_p2pool = Arc::clone(&lock.gui_api_p2pool);
let gui_api_xmrig = Arc::clone(&lock.gui_api_xmrig);
let pub_api_p2pool = Arc::clone(&lock.pub_api_p2pool);
let pub_api_xmrig = Arc::clone(&lock.pub_api_xmrig);
drop(lock);
let sysinfo_cpu = sysinfo::CpuRefreshKind::everything();
let sysinfo_processes = sysinfo::ProcessRefreshKind::new().with_cpu();
thread::spawn(move || {
info!("Helper | Hello from helper thread! Entering loop where I will spend the rest of my days...");
// Begin loop
loop {
// 1. Loop init timestamp
let start = Instant::now();
debug!("Helper | ----------- Start of loop -----------");
// Ignore the invasive [debug!()] messages on the right side of the code.
// The reason why they are there are so that it's extremely easy to track
// down the culprit of an [Arc<Mutex>] deadlock. I know, they're ugly.
// 2. Lock... EVERYTHING!
let mut lock = helper.lock().unwrap(); debug!("Helper | Locking (1/8) ... [helper]");
let p2pool = p2pool.lock().unwrap(); debug!("Helper | Locking (2/8) ... [p2pool]");
let xmrig = xmrig.lock().unwrap(); debug!("Helper | Locking (3/8) ... [xmrig]");
let mut lock_pub_sys = pub_sys.lock().unwrap(); debug!("Helper | Locking (4/8) ... [pub_sys]");
let mut gui_api_p2pool = gui_api_p2pool.lock().unwrap(); debug!("Helper | Locking (5/8) ... [gui_api_p2pool]");
let mut gui_api_xmrig = gui_api_xmrig.lock().unwrap(); debug!("Helper | Locking (6/8) ... [gui_api_xmrig]");
let mut pub_api_p2pool = pub_api_p2pool.lock().unwrap(); debug!("Helper | Locking (7/8) ... [pub_api_p2pool]");
let mut pub_api_xmrig = pub_api_xmrig.lock().unwrap(); debug!("Helper | Locking (8/8) ... [pub_api_xmrig]");
// Calculate Gupax's uptime always.
lock.uptime = HumanTime::into_human(lock.instant.elapsed());
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// If [P2Pool] is alive...
if p2pool.is_alive() {
debug!("Helper | P2Pool is alive! Running [combine_gui_pub_api()]");
PubP2poolApi::combine_gui_pub_api(&mut gui_api_p2pool, &mut pub_api_p2pool);
} else {
debug!("Helper | P2Pool is dead! Skipping...");
}
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// If [XMRig] is alive...
if xmrig.is_alive() {
debug!("Helper | XMRig is alive! Running [combine_gui_pub_api()]");
PubXmrigApi::combine_gui_pub_api(&mut gui_api_xmrig, &mut pub_api_xmrig);
} else {
debug!("Helper | XMRig is dead! Skipping...");
}
// 2. Selectively refresh [sysinfo] for only what we need (better performance).
sysinfo.refresh_cpu_specifics(sysinfo_cpu); debug!("Helper | Sysinfo refresh (1/3) ... [cpu]");
sysinfo.refresh_processes_specifics(sysinfo_processes); debug!("Helper | Sysinfo refresh (2/3) ... [processes]");
sysinfo.refresh_memory(); debug!("Helper | Sysinfo refresh (3/3) ... [memory]");
debug!("Helper | Sysinfo OK, running [update_pub_sys_from_sysinfo()]");
Self::update_pub_sys_from_sysinfo(&sysinfo, &mut lock_pub_sys, &pid, &lock, max_threads);
// 3. Drop... (almost) EVERYTHING... IN REVERSE!
drop(lock_pub_sys); debug!("Helper | Unlocking (1/8) ... [pub_sys]");
drop(xmrig); debug!("Helper | Unlocking (2/8) ... [xmrig]");
drop(p2pool); debug!("Helper | Unlocking (3/8) ... [p2pool]");
drop(pub_api_xmrig); debug!("Helper | Unlocking (4/8) ... [pub_api_xmrig]");
drop(pub_api_p2pool); debug!("Helper | Unlocking (5/8) ... [pub_api_p2pool]");
drop(gui_api_xmrig); debug!("Helper | Unlocking (6/8) ... [gui_api_xmrig]");
drop(gui_api_p2pool); debug!("Helper | Unlocking (7/8) ... [gui_api_p2pool]");
drop(lock); debug!("Helper | Unlocking (8/8) ... [helper]");
// 4. 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.
let sleep = (1000-elapsed) as u64;
debug!("Helper | END OF LOOP - Sleeping for [{}]ms...", sleep);
std::thread::sleep(std::time::Duration::from_millis(sleep));
} else {
debug!("Helper | END OF LOOP - Not sleeping!");
}
// 5. End loop
}
// 6. Something has gone terribly wrong if the helper exited this loop.
let text = "HELPER THREAD HAS ESCAPED THE LOOP...!";
error!("{}", text);error!("{}", text);error!("{}", text);panic!("{}", text);
});
}
}
//---------------------------------------------------------------------------------------------------- [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 {
pub fn new() -> HumanTime {
HumanTime(ZERO_SECONDS)
}
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 {
if *started {
f.write_str(", ")?;
}
write!(f, "{} {}", value, name)?;
if value > 1 {
f.write_str("s")?;
}
*started = true;
}
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 {
f.write_str("0 seconds")?;
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;
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)?;
Ok(())
}
}
//---------------------------------------------------------------------------------------------------- [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)
}
}
//---------------------------------------------------------------------------------------------------- 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() {
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// if line.contains("You received a payout of [0-9].[0-9]+ XMR") { n += 1; }
// }
//
// This regex function takes [0.0003~] seconds (10x faster):
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// let regex = Regex::new("You received a payout of [0-9].[0-9]+ XMR").unwrap();
// 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 {
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
payout: regex::Regex,
float: regex::Regex,
}
impl P2poolRegex {
fn new() -> Self {
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
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(),
}
}
}
//---------------------------------------------------------------------------------------------------- [ImgP2pool]
// A static "image" of data that P2Pool started with.
// This is just a snapshot of the user data when they initially started P2Pool.
// Created by [start_p2pool()] and return to the main GUI thread where it will store it.
// No need for an [Arc<Mutex>] since the Helper thread doesn't need this information.
#[derive(Debug, Clone)]
pub struct ImgP2pool {
pub mini: bool, // Did the user start on the mini-chain?
pub address: String, // What address is the current p2pool paying out to? (This gets shortened to [4xxxxx...xxxxxx])
pub host: String, // What monerod are we using?
pub rpc: String, // What is the RPC port?
pub zmq: String, // What is the ZMQ port?
pub out_peers: String, // How many out-peers?
pub in_peers: String, // How many in-peers?
pub log_level: String, // What log level?
}
impl ImgP2pool {
pub fn new() -> Self {
Self {
mini: true,
address: String::new(),
host: String::new(),
rpc: String::new(),
zmq: String::new(),
out_peers: String::new(),
in_peers: String::new(),
log_level: String::new(),
}
}
}
//---------------------------------------------------------------------------------------------------- Public P2Pool API
// Helper/GUI threads both have a copy of this, Helper updates
// the GUI's version on a 1-second interval from the private data.
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
#[derive(Debug, Clone)]
pub struct PubP2poolApi {
// Output
pub output: String,
// Uptime
pub uptime: HumanTime,
// These are manually parsed from the STDOUT.
pub payouts: u128,
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,
}
impl Default for PubP2poolApi {
fn default() -> Self {
Self::new()
}
}
impl PubP2poolApi {
pub fn new() -> Self {
Self {
output: String::new(),
uptime: HumanTime::new(),
payouts: 0,
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(),
}
}
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// The issue with just doing [gui_api = pub_api] is that values get overwritten.
// This doesn't matter for any of the values EXCEPT for the output, so we must
// manually append it instead of overwriting.
// This is used in the "helper" thread.
fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) {
let output = std::mem::take(&mut gui_api.output);
let buf = std::mem::take(&mut pub_api.output);
*gui_api = Self {
output,
..std::mem::take(pub_api)
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
};
if !buf.is_empty() { gui_api.output.push_str(&buf); }
}
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
// Mutate "watchdog"'s [PubP2poolApi] with data the process output.
// The [output] & [output_buf] are from the [Process] struct.
fn update_from_output(public: &Arc<Mutex<Self>>, output: &Arc<Mutex<String>>, output_buf: &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)
let mut output_buf = output_buf.lock().unwrap();
if !output_buf.is_empty() {
public.lock().unwrap().output.push_str(&std::mem::take(&mut *output_buf));
}
// 2. Parse the full STDOUT
let output = output.lock().unwrap();
let (payouts, xmr) = Self::calc_payouts_and_xmr(&output, &regex);
// 3. Calculate hour/day/month given elapsed time
let elapsed_as_secs_f64 = elapsed.as_secs_f64();
// Payouts
let per_sec = (payouts as f64) / elapsed_as_secs_f64;
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
let per_sec = xmr / elapsed_as_secs_f64;
let xmr_hour = (per_sec * 60.0) * 60.0;
let xmr_day = payouts_hour * 24.0;
let xmr_month = payouts_day * 30.0;
// 4. Mutate the struct with the new info
let mut public = public.lock().unwrap();
*public = Self {
uptime: HumanTime::into_human(elapsed),
payouts,
xmr,
payouts_hour,
payouts_day,
payouts_month,
xmr_hour,
xmr_day,
xmr_month,
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
..std::mem::take(&mut *public)
};
}
// Mutate [PubP2poolApi] with data from a [PrivP2poolApi] and the process output.
fn update_from_priv(public: &Arc<Mutex<Self>>, private: PrivP2poolApi) {
// priv -> pub conversion
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),
..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 */) {
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
let iter = regex.payout.find_iter(output);
let mut result: f64 = 0.0;
let mut count: u128 = 0;
for i in iter {
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
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),
}
}
(count, result)
}
}
//---------------------------------------------------------------------------------------------------- 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.
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
struct PrivP2poolApi {
hashrate_15m: u128,
hashrate_1h: u128,
hashrate_24h: u128,
shares_found: u128,
average_effort: f32,
current_effort: f32,
connections: u16, // No one will have more than 65535 connections... right?
}
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,
}
}
// Read P2Pool's API file to a [String].
fn read_p2pool_api(path: &std::path::PathBuf) -> Result<String, std::io::Error> {
match std::fs::read_to_string(path) {
Ok(s) => Ok(s),
Err(e) => { warn!("P2Pool API | [{}] read error: {}", path.display(), e); Err(e) },
}
}
// Deserialize the above [String] into a [PrivP2poolApi]
fn str_to_priv_p2pool_api(string: &str) -> Result<Self, serde_json::Error> {
match serde_json::from_str::<Self>(string) {
Ok(a) => Ok(a),
Err(e) => { warn!("P2Pool API | Could not deserialize API data: {}", e); Err(e) },
}
}
}
//---------------------------------------------------------------------------------------------------- [ImgXmrig]
#[derive(Debug, Clone)]
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
pub struct ImgXmrig {
pub threads: String,
pub url: String,
}
impl ImgXmrig {
pub fn new() -> Self {
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
Self {
threads: "1".to_string(),
url: "127.0.0.1:3333 (Local P2Pool)".to_string(),
}
}
}
//---------------------------------------------------------------------------------------------------- Public XMRig API
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
#[derive(Debug, Clone)]
pub struct PubXmrigApi {
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
pub output: String,
pub uptime: HumanTime,
pub worker_id: String,
pub resources: HumanNumber,
pub hashrate: HumanNumber,
pub pool: String,
pub diff: HumanNumber,
pub accepted: HumanNumber,
pub rejected: HumanNumber,
}
impl Default for PubXmrigApi {
fn default() -> Self {
Self::new()
}
}
impl PubXmrigApi {
pub fn new() -> Self {
Self {
output: String::new(),
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
uptime: HumanTime::new(),
worker_id: "???".to_string(),
resources: HumanNumber::unknown(),
hashrate: HumanNumber::unknown(),
pool: "???".to_string(),
diff: HumanNumber::unknown(),
accepted: HumanNumber::unknown(),
rejected: HumanNumber::unknown(),
}
}
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) {
let output = std::mem::take(&mut gui_api.output);
let buf = std::mem::take(&mut pub_api.output);
*gui_api = Self {
output,
..std::mem::take(pub_api)
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
};
if !buf.is_empty() { gui_api.output.push_str(&buf); }
}
fn update_from_output(public: &Arc<Mutex<Self>>, output_buf: &Arc<Mutex<String>>, elapsed: std::time::Duration) {
// 1. Take process output buffer if not empty
let mut output_buf = output_buf.lock().unwrap();
let mut public = public.lock().unwrap();
// 2. Append
if !output_buf.is_empty() {
public.output.push_str(&std::mem::take(&mut *output_buf));
}
// 3. Update uptime
public.uptime = HumanTime::into_human(elapsed);
}
// Formats raw private data into ready-to-print human readable version.
2022-12-12 03:01:37 +00:00
fn update_from_priv(public: &Arc<Mutex<Self>>, private: PrivXmrigApi) {
let mut public = public.lock().unwrap();
*public = Self {
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-12-12 03:01:37 +00:00
..std::mem::take(&mut *public)
}
}
}
//---------------------------------------------------------------------------------------------------- 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!].
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
#[derive(Debug, Serialize, Deserialize, Clone)]
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(),
}
}
// Send an HTTP request to XMRig's API, serialize it into [Self] and return it
async fn request_xmrig_api(client: hyper::Client<hyper::client::HttpConnector>, api_ip_port: &str) -> Result<Self, anyhow::Error> {
let request = hyper::Request::builder()
.method("GET")
.uri("http://".to_string() + api_ip_port + "/1/summary")
.body(hyper::Body::empty())?;
let mut response = tokio::time::timeout(std::time::Duration::from_millis(500), client.request(request)).await?;
let body = hyper::body::to_bytes(response?.body_mut()).await?;
Ok(serde_json::from_slice::<Self>(&body)?)
}
}
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
struct Resources {
load_average: [Option<f32>; 3],
}
impl Resources {
fn new() -> Self {
Self {
load_average: [Some(0.0), Some(0.0), Some(0.0)],
}
}
}
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
#[derive(Debug, Serialize, Deserialize, Clone)]
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,
}
}
}
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
struct Hashrate {
total: [Option<f32>; 3],
}
impl Hashrate {
fn new() -> Self {
Self {
total: [Some(0.0), Some(0.0), Some(0.0)],
}
}
}