feat: improve hashrate and time display on Status tab

This commit is contained in:
Cyrix126 2024-12-12 11:43:40 +01:00
parent 16281c8479
commit 2c2b5b2731
8 changed files with 216 additions and 126 deletions

View file

@ -234,21 +234,21 @@ impl Status {
RichText::new("P2Pool Block Mean").underline().color(BONE), RichText::new("P2Pool Block Mean").underline().color(BONE),
) )
.on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN); .on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN);
ui.label(api.p2pool_block_mean.to_string()); ui.label(api.p2pool_block_mean.display(false));
ui.label( ui.label(
RichText::new("Your P2Pool Share Mean") RichText::new("Your P2Pool Share Mean")
.underline() .underline()
.color(BONE), .color(BONE),
) )
.on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN); .on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN);
ui.label(p2pool_share_mean.to_string()); ui.label(p2pool_share_mean.display(false));
ui.label( ui.label(
RichText::new("Your Solo Block Mean") RichText::new("Your Solo Block Mean")
.underline() .underline()
.color(BONE), .color(BONE),
) )
.on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN); .on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN);
ui.label(solo_block_mean.to_string()); ui.label(solo_block_mean.display(false));
} else { } else {
ui.label( ui.label(
RichText::new("Your P2Pool Hashrate") RichText::new("Your P2Pool Hashrate")
@ -261,21 +261,21 @@ impl Status {
RichText::new("P2Pool Block Mean").underline().color(BONE), RichText::new("P2Pool Block Mean").underline().color(BONE),
) )
.on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN); .on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN);
ui.label(api.p2pool_block_mean.to_string()); ui.label(api.p2pool_block_mean.display(false));
ui.label( ui.label(
RichText::new("Your P2Pool Share Mean") RichText::new("Your P2Pool Share Mean")
.underline() .underline()
.color(BONE), .color(BONE),
) )
.on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN); .on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN);
ui.label(api.p2pool_share_mean.to_string()); ui.label(api.p2pool_share_mean.display(false));
ui.label( ui.label(
RichText::new("Your Solo Block Mean") RichText::new("Your Solo Block Mean")
.underline() .underline()
.color(BONE), .color(BONE),
) )
.on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN); .on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN);
ui.label(api.solo_block_mean.to_string()); ui.label(api.solo_block_mean.display(false));
} }
}) })
}); });

View file

@ -1,4 +1,4 @@
use egui::{ScrollArea, Ui}; use egui::{Label, ScrollArea, Ui};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::app::eframe_impl::ProcessStatesGui; use crate::app::eframe_impl::ProcessStatesGui;
@ -30,7 +30,7 @@ impl Status {
states: &ProcessStatesGui, states: &ProcessStatesGui,
) { ) {
let width_column = ui.text_style_height(&TextStyle::Body) * 16.0; let width_column = ui.text_style_height(&TextStyle::Body) * 16.0;
let height_column = width_column * 2.5; let height_column = width_column * 2.7;
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
// let width = ((ui.available_width() / 5.0) - (SPACE * 1.7500)).max(0.0); // let width = ((ui.available_width() / 5.0) - (SPACE * 1.7500)).max(0.0);
ScrollArea::vertical().show(ui, |ui| { ScrollArea::vertical().show(ui, |ui| {
@ -121,7 +121,14 @@ fn gupax(ui: &mut Ui, sys: &Arc<Mutex<Sys>>) {
let sys = sys.lock().unwrap(); let sys = sys.lock().unwrap();
ui.label(RichText::new("Uptime").underline().color(BONE)) ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_GUPAX_UPTIME); .on_hover_text(STATUS_GUPAX_UPTIME);
ui.label(sys.gupax_uptime.to_string()); // put some space for uptime so that when seconds appears every minutes, no label is moved.
ui.add_sized(
[
0.0,
(ui.text_style_height(&TextStyle::Body) + ui.spacing().item_spacing.y) * 1.5,
],
Label::new(sys.gupax_uptime.to_string()),
);
ui.label(RichText::new("Gupaxx CPU").underline().color(BONE)) ui.label(RichText::new("Gupaxx CPU").underline().color(BONE))
.on_hover_text(STATUS_GUPAX_CPU_USAGE); .on_hover_text(STATUS_GUPAX_CPU_USAGE);
ui.label(sys.gupax_cpu_usage.to_string()); ui.label(sys.gupax_cpu_usage.to_string());
@ -159,7 +166,14 @@ fn p2pool(
let api = p2pool_api.lock().unwrap(); let api = p2pool_api.lock().unwrap();
ui.label(RichText::new("Uptime").underline().color(BONE)) ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_UPTIME); .on_hover_text(STATUS_P2POOL_UPTIME);
ui.label(format!("{}", api.uptime)); // put some space for uptime so that when seconds appears every minutes, no label is moved.
ui.add_sized(
[
0.0,
(ui.text_style_height(&TextStyle::Body) + ui.spacing().item_spacing.y) * 1.5,
],
Label::new(api.uptime.display(true)),
);
ui.label(RichText::new("Current Shares").underline().color(BONE)) ui.label(RichText::new("Current Shares").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_CURRENT_SHARES); .on_hover_text(STATUS_P2POOL_CURRENT_SHARES);
ui.label(api.sidechain_shares.to_string()); ui.label(api.sidechain_shares.to_string());
@ -193,10 +207,7 @@ fn p2pool(
.color(BONE), .color(BONE),
) )
.on_hover_text(STATUS_P2POOL_HASHRATE); .on_hover_text(STATUS_P2POOL_HASHRATE);
ui.label(format!( ui.label(&api.hashrate);
"[{} H/s]\n[{} H/s]\n[{} H/s]",
api.hashrate_15m, api.hashrate_1h, api.hashrate_24h
));
ui.label(RichText::new("Miners Connected").underline().color(BONE)) ui.label(RichText::new("Miners Connected").underline().color(BONE))
.on_hover_text(STATUS_P2POOL_CONNECTIONS); .on_hover_text(STATUS_P2POOL_CONNECTIONS);
ui.label(format!("{}", api.connections)); ui.label(format!("{}", api.connections));
@ -240,17 +251,21 @@ fn xmrig_proxy(
let api = xmrig_proxy_api.lock().unwrap(); let api = xmrig_proxy_api.lock().unwrap();
ui.label(RichText::new("Uptime").underline().color(BONE)) ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_XMRIG_PROXY_UPTIME); .on_hover_text(STATUS_XMRIG_PROXY_UPTIME);
ui.label(api.uptime.to_string()); // put some space for uptime so that when seconds appears every minutes, no label is moved.
ui.add_sized(
[
0.0,
(ui.text_style_height(&TextStyle::Body) + ui.spacing().item_spacing.y) * 1.5,
],
Label::new(api.uptime.display(true)),
);
ui.label( ui.label(
RichText::new("Hashrate\n(1m/10m/1h/12h/24h)") RichText::new("Hashrate\n(1m/10m/1h/12h/24h)")
.underline() .underline()
.color(BONE), .color(BONE),
) )
.on_hover_text(STATUS_XMRIG_PROXY_HASHRATE); .on_hover_text(STATUS_XMRIG_PROXY_HASHRATE);
ui.label(format!( ui.label(&api.hashrate);
"[{} H/s]\n[{} H/s]\n[{} H/s]\n[{} H/s]\n[{} H/s]",
api.hashrate_1m, api.hashrate_10m, api.hashrate_1h, api.hashrate_12h, api.hashrate_24h
));
ui.label(format!( ui.label(format!(
"[Accepted: {}]\n[Rejected: {}]", "[Accepted: {}]\n[Rejected: {}]",
api.accepted, api.rejected api.accepted, api.rejected
@ -282,7 +297,14 @@ fn xmrig(
let api = xmrig_api.lock().unwrap(); let api = xmrig_api.lock().unwrap();
ui.label(RichText::new("Uptime").underline().color(BONE)) ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_XMRIG_UPTIME); .on_hover_text(STATUS_XMRIG_UPTIME);
ui.label(api.uptime.to_string()); // put some space for uptime so that when seconds appears every minutes, no label is moved.
ui.add_sized(
[
0.0,
(ui.text_style_height(&TextStyle::Body) + ui.spacing().item_spacing.y) * 1.5,
],
Label::new(api.uptime.display(true)),
);
ui.label(api.resources.to_string()); ui.label(api.resources.to_string());
ui.label( ui.label(
RichText::new("Hashrate\n(10s/1m/15m)") RichText::new("Hashrate\n(10s/1m/15m)")
@ -407,7 +429,14 @@ fn node(ui: &mut Ui, node_alive: bool, node_api: &Arc<Mutex<PubNodeApi>>) {
let api = node_api.lock().unwrap(); let api = node_api.lock().unwrap();
ui.label(RichText::new("Uptime").underline().color(BONE)) ui.label(RichText::new("Uptime").underline().color(BONE))
.on_hover_text(STATUS_NODE_UPTIME); .on_hover_text(STATUS_NODE_UPTIME);
ui.label(api.uptime.to_string()); // put some space for uptime so that when seconds appears every minutes, no label is moved.
ui.add_sized(
[
0.0,
(ui.text_style_height(&TextStyle::Body) + ui.spacing().item_spacing.y) * 1.5,
],
Label::new(api.uptime.display(true)),
);
ui.label(RichText::new("Block Height").underline().color(BONE)) ui.label(RichText::new("Block Height").underline().color(BONE))
.on_hover_text(STATUS_NODE_BLOCK_HEIGHT); .on_hover_text(STATUS_NODE_BLOCK_HEIGHT);

View file

@ -444,7 +444,7 @@ impl Helper {
helper: &Helper, helper: &Helper,
max_threads: u16, max_threads: u16,
) { ) {
let gupax_uptime = helper.uptime.to_string(); let gupax_uptime = helper.uptime.display(true);
let cpu = &sysinfo.cpus()[0]; let cpu = &sysinfo.cpus()[0];
let gupax_cpu_usage = format!( let gupax_cpu_usage = format!(
"{:.2}%", "{:.2}%",
@ -793,14 +793,20 @@ fn signal_end(
let uptime = HumanTime::into_human(start.elapsed()); let uptime = HumanTime::into_human(start.elapsed());
info!( info!(
"{} Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", "{} Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]",
process.name, uptime, exit_status process.name,
uptime.display(false),
exit_status
); );
// This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it. // This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it.
let name = process.name.to_owned(); let name = process.name.to_owned();
if let Err(e) = writeln!( if let Err(e) = writeln!(
gui_api_output_raw, gui_api_output_raw,
"{}\n{} stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", "{}\n{} stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n",
name, HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE name,
HORI_CONSOLE,
uptime.display(false),
exit_status,
HORI_CONSOLE
) { ) {
error!( error!(
"{} Watchdog | GUI Uptime/Exit status write failed: {}", "{} Watchdog | GUI Uptime/Exit status write failed: {}",
@ -831,14 +837,20 @@ fn signal_end(
let uptime = HumanTime::into_human(start.elapsed()); let uptime = HumanTime::into_human(start.elapsed());
info!( info!(
"{} Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", "{} Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]",
process.name, uptime, exit_status process.name,
uptime.display(false),
exit_status
); );
// This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it. // This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it.
let name = process.name.to_owned(); let name = process.name.to_owned();
if let Err(e) = writeln!( if let Err(e) = writeln!(
gui_api_output_raw, gui_api_output_raw,
"{}\n{} stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", "{}\n{} stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n",
name, HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE name,
HORI_CONSOLE,
uptime.display(false),
exit_status,
HORI_CONSOLE
) { ) {
error!( error!(
"{} Watchdog | GUI Uptime/Exit status write failed: {}", "{} Watchdog | GUI Uptime/Exit status write failed: {}",

View file

@ -706,9 +706,10 @@ pub struct PubP2poolApi {
pub xmr_day: f64, pub xmr_day: f64,
pub xmr_month: f64, pub xmr_month: f64,
// Local API // Local API
pub hashrate_15m: HumanNumber, pub hashrate: String,
pub hashrate_1h: HumanNumber, pub hashrate_15m: u64,
pub hashrate_24h: HumanNumber, pub hashrate_1h: u64,
pub hashrate_24h: u64,
pub shares_found: Option<u64>, pub shares_found: Option<u64>,
pub average_effort: HumanNumber, pub average_effort: HumanNumber,
pub current_effort: HumanNumber, pub current_effort: HumanNumber,
@ -766,9 +767,10 @@ impl PubP2poolApi {
xmr_hour: 0.0, xmr_hour: 0.0,
xmr_day: 0.0, xmr_day: 0.0,
xmr_month: 0.0, xmr_month: 0.0,
hashrate_15m: HumanNumber::unknown(), hashrate: HumanNumber::from_hashrate(&[None, None, None]).to_string(),
hashrate_1h: HumanNumber::unknown(), hashrate_15m: 0,
hashrate_24h: HumanNumber::unknown(), hashrate_1h: 0,
hashrate_24h: 0,
shares_found: None, shares_found: None,
average_effort: HumanNumber::unknown(), average_effort: HumanNumber::unknown(),
current_effort: HumanNumber::unknown(), current_effort: HumanNumber::unknown(),
@ -940,9 +942,15 @@ impl PubP2poolApi {
// Mutate [PubP2poolApi] with data from a [PrivP2poolLocalApi] and the process output. // Mutate [PubP2poolApi] with data from a [PrivP2poolLocalApi] and the process output.
pub(super) fn update_from_local(public: &mut Self, local: PrivP2poolLocalApi) { pub(super) fn update_from_local(public: &mut Self, local: PrivP2poolLocalApi) {
*public = Self { *public = Self {
hashrate_15m: HumanNumber::from_u64(local.hashrate_15m), hashrate: HumanNumber::from_hashrate(&[
hashrate_1h: HumanNumber::from_u64(local.hashrate_1h), Some(local.hashrate_15m),
hashrate_24h: HumanNumber::from_u64(local.hashrate_24h), Some(local.hashrate_1h),
Some(local.hashrate_24h),
])
.to_string(),
hashrate_15m: local.hashrate_15m,
hashrate_1h: local.hashrate_1h,
hashrate_24h: local.hashrate_24h,
shares_found: Some(local.shares_found), shares_found: Some(local.shares_found),
average_effort: HumanNumber::to_percent(local.average_effort), average_effort: HumanNumber::to_percent(local.average_effort),
current_effort: HumanNumber::to_percent(local.current_effort), current_effort: HumanNumber::to_percent(local.current_effort),

View file

@ -274,9 +274,9 @@ Uptime = 0h 2m 4s
let mut p = public.lock().unwrap(); let mut p = public.lock().unwrap();
PubP2poolApi::update_from_local(&mut p, local); PubP2poolApi::update_from_local(&mut p, local);
println!("AFTER LOCAL: {:#?}", p); println!("AFTER LOCAL: {:#?}", p);
assert_eq!(p.hashrate_15m.to_string(), "10,000"); assert_eq!(p.hashrate_15m.to_string(), "10000");
assert_eq!(p.hashrate_1h.to_string(), "20,000"); assert_eq!(p.hashrate_1h.to_string(), "20000");
assert_eq!(p.hashrate_24h.to_string(), "30,000"); assert_eq!(p.hashrate_24h.to_string(), "30000");
assert_eq!( assert_eq!(
p.shares_found.expect("the value is set").to_string(), p.shares_found.expect("the value is set").to_string(),
"1000" "1000"
@ -297,14 +297,14 @@ Uptime = 0h 2m 4s
assert_eq!(p.p2pool_hashrate.to_string(), "1.000 MH/s"); assert_eq!(p.p2pool_hashrate.to_string(), "1.000 MH/s");
assert_eq!(p.miners.to_string(), "1,000"); assert_eq!(p.miners.to_string(), "1,000");
assert_eq!( assert_eq!(
p.solo_block_mean.to_string(), p.solo_block_mean.display(false),
"5 months\n21 days\n9 hours\n52 minutes" "5 months, 21 days, 9 hours, 52 minutes"
); );
assert_eq!( assert_eq!(
p.p2pool_block_mean.to_string(), p.p2pool_block_mean.display(false),
"3 days\n11 hours\n20 minutes" "3 days, 11 hours, 20 minutes"
); );
assert_eq!(p.p2pool_share_mean.to_string(), "8 minutes\n20 seconds"); assert_eq!(p.p2pool_share_mean.display(false), "8 minutes, 20 seconds");
assert_eq!(p.p2pool_percent.to_string(), "0.040000%"); assert_eq!(p.p2pool_percent.to_string(), "0.040000%");
assert_eq!(p.user_p2pool_percent.to_string(), "2.000000%"); assert_eq!(p.user_p2pool_percent.to_string(), "2.000000%");
assert_eq!(p.user_monero_percent.to_string(), "0.000800%"); assert_eq!(p.user_monero_percent.to_string(), "0.000800%");

View file

@ -793,11 +793,16 @@ impl PubXmrigApi {
Some(Some(h)) => *h, Some(Some(h)) => *h,
_ => 0.0, _ => 0.0,
}; };
let total_hasrate = private
.hashrate
.total
.iter()
.map(|x| x.as_ref().map(|y| *y as u64))
.collect::<Vec<Option<u64>>>();
*public = Self { *public = Self {
worker_id: private.worker_id, worker_id: private.worker_id,
resources: HumanNumber::from_load(private.resources.load_average).to_string(), resources: HumanNumber::from_load(private.resources.load_average).to_string(),
hashrate: HumanNumber::from_hashrate(private.hashrate.total).to_string(), hashrate: HumanNumber::from_hashrate(&total_hasrate).to_string(),
diff: Unsigned::from(private.connection.diff as usize).to_string(), diff: Unsigned::from(private.connection.diff as usize).to_string(),
accepted: Unsigned::from(private.connection.accepted as usize).to_string(), accepted: Unsigned::from(private.connection.accepted as usize).to_string(),
rejected: Unsigned::from(private.connection.rejected as usize).to_string(), rejected: Unsigned::from(private.connection.rejected as usize).to_string(),

View file

@ -12,7 +12,7 @@ use std::{
}; };
use tokio::spawn; use tokio::spawn;
use crate::human::HumanTime; use crate::human::{HumanNumber, HumanTime};
use crate::miscs::client; use crate::miscs::client;
use crate::{ use crate::{
GUPAX_VERSION_UNDERSCORE, UNKNOWN_DATA, GUPAX_VERSION_UNDERSCORE, UNKNOWN_DATA,
@ -467,12 +467,14 @@ impl Helper {
// sleep // sleep
} }
} }
#[allow(unused)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PubXmrigProxyApi { pub struct PubXmrigProxyApi {
pub output: String, pub output: String,
pub uptime: HumanTime, pub uptime: HumanTime,
pub accepted: u32, pub accepted: u32,
pub rejected: u32, pub rejected: u32,
pub hashrate: String,
pub hashrate_1m: f32, pub hashrate_1m: f32,
pub hashrate_10m: f32, pub hashrate_10m: f32,
pub hashrate_1h: f32, pub hashrate_1h: f32,
@ -493,6 +495,7 @@ impl PubXmrigProxyApi {
uptime: HumanTime::new(), uptime: HumanTime::new(),
accepted: 0, accepted: 0,
rejected: 0, rejected: 0,
hashrate: HumanNumber::from_hashrate(&[None, None, None, None, None, None]).to_string(),
hashrate_1m: 0.0, hashrate_1m: 0.0,
hashrate_10m: 0.0, hashrate_10m: 0.0,
hashrate_1h: 0.0, hashrate_1h: 0.0,
@ -555,9 +558,17 @@ impl PubXmrigProxyApi {
} }
fn update_from_priv(public: &Arc<Mutex<Self>>, private: PrivXmrigProxyApi) { fn update_from_priv(public: &Arc<Mutex<Self>>, private: PrivXmrigProxyApi) {
let mut public = public.lock().unwrap(); let mut public = public.lock().unwrap();
let mut total_hashrate = private
.hashrate
.total
.iter()
.map(|x| Some(*x as u64))
.collect::<Vec<Option<u64>>>();
total_hashrate.remove(5);
*public = Self { *public = Self {
accepted: private.results.accepted, accepted: private.results.accepted,
rejected: private.results.rejected, rejected: private.results.rejected,
hashrate: HumanNumber::from_hashrate(&total_hashrate).to_string(),
hashrate_1m: private.hashrate.total[0], hashrate_1m: private.hashrate.total[0],
hashrate_10m: private.hashrate.total[1], hashrate_10m: private.hashrate.total[1],
hashrate_1h: private.hashrate.total[2], hashrate_1h: private.hashrate.total[2],

View file

@ -26,6 +26,8 @@ pub const ZERO_SECONDS: std::time::Duration = std::time::Duration::from_secs(0);
// Code taken from [https://docs.rs/humantime/] and edited to remove sub-second time, change spacing and some words. // Code taken from [https://docs.rs/humantime/] and edited to remove sub-second time, change spacing and some words.
use std::time::Duration; use std::time::Duration;
use readable::num::Float;
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct HumanTime(Duration); pub struct HumanTime(Duration);
@ -51,34 +53,29 @@ impl HumanTime {
HumanTime(Duration::from_secs(u)) HumanTime(Duration::from_secs(u))
} }
fn plural( fn plural(started: &mut bool, name: &str, value: u64, separator: &str) -> String {
f: &mut std::fmt::Formatter, // do not show time if value is 0 unless it is for seconds.
started: &mut bool, let mut string = String::new();
name: &str,
value: u64,
) -> std::fmt::Result {
if value > 0 { if value > 0 {
if *started { if *started {
f.write_str("\n")?; string.push_str(separator);
} }
write!(f, "{} {}", value, name)?; string.push_str(&value.to_string());
string.push(' ');
string.push_str(name);
if value > 1 { if value > 1 {
f.write_str("s")?; string.push('s');
} }
*started = true; *started = true;
} }
Ok(()) string
} }
} pub fn display(&self, wrap: bool) -> String {
impl std::fmt::Display for HumanTime {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let secs = self.0.as_secs(); let secs = self.0.as_secs();
if secs == 0 { if secs == 0 {
f.write_str("0 seconds")?; return String::from("0 second");
return Ok(());
} }
let separator = if wrap { "\n" } else { ", " };
let years = secs / 31_557_600; // 365.25d let years = secs / 31_557_600; // 365.25d
let ydays = secs % 31_557_600; let ydays = secs % 31_557_600;
let months = ydays / 2_630_016; // 30.44d let months = ydays / 2_630_016; // 30.44d
@ -86,17 +83,20 @@ impl std::fmt::Display for HumanTime {
let days = mdays / 86400; let days = mdays / 86400;
let day_secs = mdays % 86400; let day_secs = mdays % 86400;
let hours = day_secs / 3600; let hours = day_secs / 3600;
dbg!(&day_secs);
let minutes = day_secs % 3600 / 60; let minutes = day_secs % 3600 / 60;
dbg!(&minutes);
let seconds = day_secs % 60; let seconds = day_secs % 60;
dbg!(&seconds);
let started = &mut false; let mut started = false;
Self::plural(f, started, "year", years)?; let mut string = String::new();
Self::plural(f, started, "month", months)?; string.push_str(&Self::plural(&mut started, "year", years, separator));
Self::plural(f, started, "day", days)?; string.push_str(&Self::plural(&mut started, "month", months, separator));
Self::plural(f, started, "hour", hours)?; string.push_str(&Self::plural(&mut started, "day", days, separator));
Self::plural(f, started, "minute", minutes)?; string.push_str(&Self::plural(&mut started, "hour", hours, separator));
Self::plural(f, started, "second", seconds)?; string.push_str(&Self::plural(&mut started, "minute", minutes, separator));
Ok(()) string.push_str(&Self::plural(&mut started, "second", seconds, separator));
string
} }
} }
@ -184,26 +184,36 @@ impl HumanNumber {
Self(buf.as_str().to_string()) Self(buf.as_str().to_string())
} }
#[inline] #[inline]
pub fn from_hashrate(array: [Option<f32>; 3]) -> Self { pub fn from_hashrate(array: &[Option<u64>]) -> Self {
let mut string = "[".to_string(); let mut string = String::new();
let mut buf = num_format::Buffer::new(); // let mut buf = num_format::Buffer::new();
let mut n = 0; let mut n = 1;
for i in array { for i in array {
match i { match i {
Some(f) => { Some(f) => {
let f = f as u128; if *f == 0 {
buf.write_formatted(&f, &LOCALE); string.push_str("[??? H/s]");
string.push_str(buf.as_str()); } else {
string.push_str(" H/s"); let f = *f as f64;
let (value, metric) = match f {
x if x >= 1000.0 => (Float::from_3(f / 1000.0), " K"),
x if x >= 1000000.0 => (Float::from_3(f / (1000.0 * 1000.0)), " M"),
_ => (Float::from_0(f), " "),
};
string.push('[');
// buf.write_formatted(&value, &LOCALE);
string.push_str(value.as_str());
string.push_str(metric);
string.push_str("H/s]");
} }
None => string.push_str("??? H/s"),
} }
if n != 2 { None => string.push_str("[??? H/s]"),
string.push_str(", "); }
if n != array.len() {
string.push('\n');
n += 1; n += 1;
} else { } else {
string.push(']');
break; break;
} }
} }
@ -263,13 +273,14 @@ mod test {
assert!(HumanNumber::to_percent(0.001).to_string() == "0%"); assert!(HumanNumber::to_percent(0.001).to_string() == "0%");
assert!(HumanNumber::to_percent(12.123_123).to_string() == "12.12%"); assert!(HumanNumber::to_percent(12.123_123).to_string() == "12.12%");
assert!(HumanNumber::to_percent_3_point(0.001).to_string() == "0.001%"); assert!(HumanNumber::to_percent_3_point(0.001).to_string() == "0.001%");
dbg!(HumanNumber::from_hashrate(&[Some(123), Some(11111), None]).to_string());
assert!( assert!(
HumanNumber::from_hashrate([Some(123.1), Some(11111.1), None]).to_string() HumanNumber::from_hashrate(&[Some(123), Some(11111), None]).to_string()
== "[123 H/s, 11,111 H/s, ??? H/s]" == "[123 H/s]\n[11.111 KH/s]\n[??? H/s]"
); );
assert!( assert!(
HumanNumber::from_hashrate([None, Some(1.123), Some(123_123.31)]).to_string() HumanNumber::from_hashrate(&[None, Some(1), Some(123_123)]).to_string()
== "[??? H/s, 1 H/s, 123,123 H/s]" == "[??? H/s]\n[1 H/s]\n[123.123 KH/s]"
); );
assert!( assert!(
HumanNumber::from_load([Some(123.1234), Some(321.321), None]).to_string() HumanNumber::from_load([Some(123.1234), Some(321.321), None]).to_string()
@ -327,69 +338,83 @@ mod test {
fn human_time() { fn human_time() {
use crate::human::HumanTime; use crate::human::HumanTime;
use std::time::Duration; use std::time::Duration;
assert!(HumanTime::into_human(Duration::from_secs(0)).to_string() == "0 seconds"); assert!(HumanTime::into_human(Duration::from_secs(0)).display(true) == "0 second");
assert!(HumanTime::into_human(Duration::from_secs(1)).to_string() == "1 second"); assert!(HumanTime::into_human(Duration::from_secs(1)).display(true) == "1 second");
assert!(HumanTime::into_human(Duration::from_secs(2)).to_string() == "2 seconds"); assert!(HumanTime::into_human(Duration::from_secs(2)).display(true) == "2 seconds");
assert!(HumanTime::into_human(Duration::from_secs(59)).to_string() == "59 seconds"); assert!(HumanTime::into_human(Duration::from_secs(59)).display(true) == "59 seconds");
assert!(HumanTime::into_human(Duration::from_secs(60)).to_string() == "1 minute"); dbg!(HumanTime::into_human(Duration::from_secs(60)).display(true));
assert!(HumanTime::into_human(Duration::from_secs(61)).to_string() == "1 minute\n1 second"); assert!(HumanTime::into_human(Duration::from_secs(60)).display(true) == "1 minute");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(62)).to_string() == "1 minute\n2 seconds" HumanTime::into_human(Duration::from_secs(61)).display(true) == "1 minute\n1 second"
);
assert!(HumanTime::into_human(Duration::from_secs(120)).to_string() == "2 minutes");
assert!(
HumanTime::into_human(Duration::from_secs(121)).to_string() == "2 minutes\n1 second"
); );
assert!( assert!(
HumanTime::into_human(Duration::from_secs(122)).to_string() == "2 minutes\n2 seconds" HumanTime::into_human(Duration::from_secs(62)).display(true) == "1 minute\n2 seconds"
);
assert!(HumanTime::into_human(Duration::from_secs(120)).display(true) == "2 minutes");
assert!(
HumanTime::into_human(Duration::from_secs(121)).display(true) == "2 minutes\n1 second"
); );
assert!( assert!(
HumanTime::into_human(Duration::from_secs(179)).to_string() == "2 minutes\n59 seconds" HumanTime::into_human(Duration::from_secs(122)).display(true) == "2 minutes\n2 seconds"
); );
assert!( assert!(
HumanTime::into_human(Duration::from_secs(3599)).to_string() HumanTime::into_human(Duration::from_secs(179)).display(true)
== "2 minutes\n59 seconds"
);
assert!(
HumanTime::into_human(Duration::from_secs(3599)).display(true)
== "59 minutes\n59 seconds" == "59 minutes\n59 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(3600)).to_string() == "1 hour"); assert!(HumanTime::into_human(Duration::from_secs(3600)).display(true) == "1 hour");
assert!(HumanTime::into_human(Duration::from_secs(3601)).to_string() == "1 hour\n1 second");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(3602)).to_string() == "1 hour\n2 seconds" HumanTime::into_human(Duration::from_secs(3601)).display(true) == "1 hour\n1 second"
);
assert!(HumanTime::into_human(Duration::from_secs(3660)).to_string() == "1 hour\n1 minute");
assert!(
HumanTime::into_human(Duration::from_secs(3720)).to_string() == "1 hour\n2 minutes"
); );
assert!( assert!(
HumanTime::into_human(Duration::from_secs(86399)).to_string() HumanTime::into_human(Duration::from_secs(3602)).display(true) == "1 hour\n2 seconds"
);
assert!(
HumanTime::into_human(Duration::from_secs(3660)).display(true) == "1 hour\n1 minute"
);
assert!(
HumanTime::into_human(Duration::from_secs(3720)).display(true) == "1 hour\n2 minutes"
);
assert!(
HumanTime::into_human(Duration::from_secs(86399)).display(true)
== "23 hours\n59 minutes\n59 seconds" == "23 hours\n59 minutes\n59 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(86400)).to_string() == "1 day"); assert!(HumanTime::into_human(Duration::from_secs(86400)).display(true) == "1 day");
assert!(HumanTime::into_human(Duration::from_secs(86401)).to_string() == "1 day\n1 second");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(86402)).to_string() == "1 day\n2 seconds" HumanTime::into_human(Duration::from_secs(86401)).display(true) == "1 day\n1 second"
); );
assert!(HumanTime::into_human(Duration::from_secs(86460)).to_string() == "1 day\n1 minute");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(86520)).to_string() == "1 day\n2 minutes" HumanTime::into_human(Duration::from_secs(86402)).display(true) == "1 day\n2 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(90000)).to_string() == "1 day\n1 hour");
assert!(HumanTime::into_human(Duration::from_secs(93600)).to_string() == "1 day\n2 hours");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(604799)).to_string() HumanTime::into_human(Duration::from_secs(86460)).display(true) == "1 day\n1 minute"
);
assert!(
HumanTime::into_human(Duration::from_secs(86520)).display(true) == "1 day\n2 minutes"
);
assert!(HumanTime::into_human(Duration::from_secs(90000)).display(true) == "1 day\n1 hour");
assert!(
HumanTime::into_human(Duration::from_secs(93600)).display(true) == "1 day\n2 hours"
);
assert!(
HumanTime::into_human(Duration::from_secs(604799)).display(true)
== "6 days\n23 hours\n59 minutes\n59 seconds" == "6 days\n23 hours\n59 minutes\n59 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(604800)).to_string() == "7 days"); assert!(HumanTime::into_human(Duration::from_secs(604800)).display(true) == "7 days");
assert!(HumanTime::into_human(Duration::from_secs(2630016)).to_string() == "1 month"); assert!(HumanTime::into_human(Duration::from_secs(2630016)).display(true) == "1 month");
assert!( assert!(
HumanTime::into_human(Duration::from_secs(3234815)).to_string() HumanTime::into_human(Duration::from_secs(3234815)).display(true)
== "1 month\n6 days\n23 hours\n59 minutes\n59 seconds" == "1 month\n6 days\n23 hours\n59 minutes\n59 seconds"
); );
assert!(HumanTime::into_human(Duration::from_secs(5260032)).to_string() == "2 months"); assert!(HumanTime::into_human(Duration::from_secs(5260032)).display(true) == "2 months");
assert!(HumanTime::into_human(Duration::from_secs(31557600)).to_string() == "1 year"); assert!(HumanTime::into_human(Duration::from_secs(31557600)).display(true) == "1 year");
assert!(HumanTime::into_human(Duration::from_secs(63115200)).to_string() == "2 years"); assert!(HumanTime::into_human(Duration::from_secs(63115200)).display(true) == "2 years");
assert_eq!( assert_eq!(
HumanTime::into_human(Duration::from_secs(18446744073709551615)).to_string(), HumanTime::into_human(Duration::from_secs(18446744073709551615)).display(true),
"584542046090 years\n7 months\n15 days\n17 hours\n5 minutes\n3 seconds", "584542046090 years\n7 months\n15 days\n17 hours\n5 minutes\n3 seconds"
); );
} }
} }