From eae24b34792e6de10a3c64a43a5d2a04e6ed5655 Mon Sep 17 00:00:00 2001 From: Misieq01 <38589417+Misieq01@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:50:55 +0200 Subject: [PATCH] feat: show cpu gpu temperatures (#47) Description --- Implement monitoring and display for cpu and gpu temperatures Motivation and Context --- As mining is heavy operation for hardware we want to see at least their temperatures to know if it safe to continue mining as high temperatures may cause damages How Has This Been Tested? --- Manually What process can a PR reviewer use to test or verify this change? --- Open settings tab and you should see Hardware temperatures category Breaking Changes --- - [x] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [ ] Other - Please specify --- src-tauri/Cargo.lock | 46 +++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/cpu_miner.rs | 62 +++++++++- src-tauri/src/gpu_miner.rs | 81 +++++++++++++ src-tauri/src/main.rs | 52 +++++++- src/containers/SideBar/components/Heading.tsx | 2 +- .../components/Settings/Card.component.tsx | 25 ++++ .../components/Settings/Settings.styles.tsx | 14 +++ .../components/{ => Settings}/Settings.tsx | 113 ++++++++++++++++-- src/store/useAppStatusStore.ts | 1 + src/types/app-status.ts | 19 +++ 11 files changed, 397 insertions(+), 19 deletions(-) create mode 100644 src-tauri/src/gpu_miner.rs create mode 100644 src/containers/SideBar/components/Settings/Card.component.tsx create mode 100644 src/containers/SideBar/components/Settings/Settings.styles.tsx rename src/containers/SideBar/components/{ => Settings}/Settings.tsx (58%) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a07d391f7..c58290f1b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2569,6 +2569,16 @@ dependencies = [ "libc", ] +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "libm" version = "0.2.8" @@ -3165,6 +3175,29 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "nvml-wrapper" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9bff0aa1d48904a1385ea2a8b97576fbdcbc9a3cfccd0d31fe978e1c4038c5" +dependencies = [ + "bitflags 2.6.0", + "libloading", + "nvml-wrapper-sys", + "static_assertions", + "thiserror", + "wrapcenum-derive", +] + +[[package]] +name = "nvml-wrapper-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "698d45156f28781a4e79652b6ebe2eaa0589057d588d3aec1333f6466f13fcb5" +dependencies = [ + "libloading", +] + [[package]] name = "objc" version = "0.2.7" @@ -5150,6 +5183,7 @@ dependencies = [ "minotari_node_grpc_client", "minotari_wallet_grpc_client", "nix", + "nvml-wrapper", "open 5.3.0", "rand 0.8.5", "reqwest 0.12.5", @@ -7268,6 +7302,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wrapcenum-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "wry" version = "0.24.10" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 37b60c598..268c07fe1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -67,6 +67,7 @@ libsqlite3-sys = { version = "0.25.1", features = ["bundled"] } log = "0.4.22" rand = "0.8.5" device_query = "2.1.0" +nvml-wrapper = "0.10.0" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/src-tauri/src/cpu_miner.rs b/src-tauri/src/cpu_miner.rs index 4bd333013..13da39a16 100644 --- a/src-tauri/src/cpu_miner.rs +++ b/src-tauri/src/cpu_miner.rs @@ -3,11 +3,12 @@ use crate::mm_proxy_manager::MmProxyManager; use crate::xmrig::http_api::XmrigHttpApiClient; use crate::xmrig_adapter::{XmrigAdapter, XmrigNodeConnection}; use crate::{ - CpuMinerConfig, CpuMinerConnection, CpuMinerConnectionStatus, CpuMinerStatus, ProgressTracker, + CpuCoreTemperature, CpuMinerConfig, CpuMinerConnection, CpuMinerConnectionStatus, + CpuMinerStatus, ProgressTracker, }; use log::warn; use std::path::PathBuf; -use sysinfo::{CpuRefreshKind, RefreshKind, System}; +use sysinfo::{Component, Components, CpuRefreshKind, RefreshKind, System}; use tari_core::transactions::tari_amount::MicroMinotari; use tari_shutdown::{Shutdown, ShutdownSignal}; use tauri::async_runtime::JoinHandle; @@ -26,6 +27,7 @@ pub(crate) struct CpuMiner { watcher_task: Option>>, miner_shutdown: Shutdown, api_client: Option, + cpu_temperatures: Vec, } impl CpuMiner { @@ -34,6 +36,7 @@ impl CpuMiner { watcher_task: None, miner_shutdown: Shutdown::new(), api_client: None, + cpu_temperatures: Vec::new(), } } @@ -156,10 +159,60 @@ impl CpuMiner { } pub async fn status( - &self, + &mut self, network_hash_rate: u64, block_reward: MicroMinotari, ) -> Result { + let components = Components::new_with_refreshed_list(); + + let cpu_components: Vec<&Component> = components + .iter() + .filter(|component| component.label().contains("Core")) + .collect(); + + let mut cpu_temperatures: Vec = cpu_components + .iter() + .map(|component| CpuCoreTemperature { + id: component + .label() + .split(" ") + .last() + .unwrap() + .parse() + .unwrap(), + label: component + .label() + .split(" ") + .skip(1) + .collect::>() + .join(" ") + .to_string(), + temperature: component.temperature(), + max_temperature: component.max(), + }) + .collect(); + + cpu_temperatures.sort(); + + if self.cpu_temperatures.is_empty() { + self.cpu_temperatures = cpu_temperatures.clone() + } else { + for (i, component) in cpu_temperatures.clone().iter().enumerate() { + let position = self + .cpu_temperatures + .iter() + .position(|x| x.id == component.id) + .unwrap(); + self.cpu_temperatures[position].temperature = component.temperature; + if component.temperature > self.cpu_temperatures[position].max_temperature { + self.cpu_temperatures[position].max_temperature = + self.cpu_temperatures[i].temperature; + } + } + } + + self.cpu_temperatures.sort(); + let mut s = System::new_with_specifics(RefreshKind::new().with_cpu(CpuRefreshKind::everything())); @@ -171,6 +224,7 @@ impl CpuMiner { let cpu_brand = s.cpus().get(0).map(|cpu| cpu.brand()).unwrap_or("Unknown"); let cpu_usage = s.global_cpu_usage() as u32; + // let cpu_temperature = s. match &self.api_client { Some(client) => { @@ -192,6 +246,7 @@ impl CpuMiner { && xmrig_status.hashrate.total[0].unwrap() > 0.0, hash_rate, cpu_usage: cpu_usage as u32, + cpu_temperatures: self.cpu_temperatures.clone(), cpu_brand: cpu_brand.to_string(), estimated_earnings: MicroMinotari(estimated_earnings).as_u64(), connection: CpuMinerConnectionStatus { @@ -207,6 +262,7 @@ impl CpuMiner { None => Ok(CpuMinerStatus { is_mining: false, hash_rate: 0.0, + cpu_temperatures: self.cpu_temperatures.clone(), cpu_usage: cpu_usage as u32, cpu_brand: cpu_brand.to_string(), estimated_earnings: 0, diff --git a/src-tauri/src/gpu_miner.rs b/src-tauri/src/gpu_miner.rs new file mode 100644 index 000000000..cee3d9d63 --- /dev/null +++ b/src-tauri/src/gpu_miner.rs @@ -0,0 +1,81 @@ +use log::info; +use nvml_wrapper::{ + enum_wrappers::device::{TemperatureSensor, TemperatureThreshold}, + Nvml, +}; + +use crate::{GpuMinerHardwareStatus, GpuMinerStatus}; + +const LOG_TARGET: &str = "tari::universe::cpu_miner"; + +pub(crate) struct GpuMiner { + nvml: Nvml, + status: GpuMinerStatus, +} + +impl GpuMiner { + pub fn new() -> Self { + Self { + nvml: Nvml::init().unwrap(), + status: GpuMinerStatus::from(GpuMinerStatus { + hardware_statuses: Vec::new(), + }), + } + } + + pub fn start(&mut self) { + info!(target: LOG_TARGET, "Starting GPU miner"); + // Start the GPU miner + } + + pub fn stop(&mut self) { + info!(target: LOG_TARGET, "Stopping GPU miner"); + // Stop the GPU miner + } + + pub fn status(&mut self) -> GpuMinerStatus { + if self.status.hardware_statuses.is_empty() { + let devices_count = self.nvml.device_count().unwrap(); + + for i in 0..devices_count { + let device = self.nvml.device_by_index(i).unwrap(); + + let uuid = device.uuid().unwrap(); + let name = device.name().unwrap(); + let temperature = device.temperature(TemperatureSensor::Gpu).unwrap(); + let load = device.utilization_rates().unwrap().gpu; + + self.status.hardware_statuses.push(GpuMinerHardwareStatus { + uuid, + name, + temperature, + max_temperature: temperature, + load, + }); + } + } + + self.status.hardware_statuses = self + .status + .hardware_statuses + .iter() + .map(|status| { + let device = self.nvml.device_by_uuid(status.uuid.clone()).unwrap(); + + let temperature = device.temperature(TemperatureSensor::Gpu).unwrap(); + let load = device.utilization_rates().unwrap().gpu; + let max_temperature = status.max_temperature.max(temperature); + + GpuMinerHardwareStatus { + uuid: status.uuid.clone(), + name: status.name.clone(), + temperature, + max_temperature, + load, + } + }) + .collect(); + + self.status.clone() + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index cdd8d4170..e7eb282e9 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod cpu_miner; +mod gpu_miner; mod mm_proxy_manager; mod process_watcher; mod user_listener; @@ -23,6 +24,7 @@ mod process_killer; mod wallet_adapter; use crate::cpu_miner::CpuMiner; +use crate::gpu_miner::GpuMiner; use crate::internal_wallet::InternalWallet; use crate::mm_proxy_manager::MmProxyManager; use crate::node_manager::NodeManager; @@ -365,7 +367,8 @@ async fn get_applications_versions(app: tauri::AppHandle) -> Result) -> Result { - let cpu_miner = state.cpu_miner.read().await; + let mut cpu_miner = state.cpu_miner.write().await; + let mut gpu_miner = state.gpu_miner.write().await; let (_sha_hash_rate, randomx_hash_rate, block_reward, block_height, block_time, is_synced) = state .node_manager @@ -400,10 +403,13 @@ async fn status(state: tauri::State<'_, UniverseAppState>) -> Result) -> Result, pub cpu_brand: String, pub estimated_earnings: u64, pub connection: CpuMinerConnectionStatus, @@ -465,10 +472,50 @@ struct CpuMinerConfig { tari_address: TariAddress, } +#[derive(Debug, Serialize, PartialEq, Clone)] +pub struct CpuCoreTemperature { + pub id: u32, + pub label: String, + pub temperature: f32, + pub max_temperature: f32, +} + +impl Eq for CpuCoreTemperature { + fn assert_receiver_is_total_eq(&self) { + self.id.assert_receiver_is_total_eq(); + } +} + +impl Ord for CpuCoreTemperature { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.id.cmp(&other.id) + } +} + +impl PartialOrd for CpuCoreTemperature { + fn partial_cmp(&self, other: &Self) -> Option { + self.id.partial_cmp(&other.id) + } +} + +#[derive(Debug, Serialize, Clone)] +struct GpuMinerStatus { + hardware_statuses: Vec, +} +#[derive(Debug, Serialize, Clone)] +struct GpuMinerHardwareStatus { + uuid: String, + temperature: u32, + max_temperature: u32, + name: String, + load: u32, +} + struct UniverseAppState { config: Arc>, shutdown: Shutdown, cpu_miner: RwLock, + gpu_miner: RwLock, cpu_miner_config: Arc>, user_listener: Arc>, mm_proxy_manager: MmProxyManager, @@ -500,6 +547,7 @@ fn main() { config: app_config.clone(), shutdown: shutdown.clone(), cpu_miner: CpuMiner::new().into(), + gpu_miner: GpuMiner::new().into(), cpu_miner_config: cpu_config.clone(), user_listener: Arc::new(RwLock::new(UserListener::new())), mm_proxy_manager: mm_proxy_manager.clone(), diff --git a/src/containers/SideBar/components/Heading.tsx b/src/containers/SideBar/components/Heading.tsx index 93861447e..5554f8fa2 100644 --- a/src/containers/SideBar/components/Heading.tsx +++ b/src/containers/SideBar/components/Heading.tsx @@ -1,6 +1,6 @@ import { Stack, Typography, IconButton } from '@mui/material'; import { CgArrowsExpandRight, CgCompressRight } from 'react-icons/cg'; -import SettingsDialog from './Settings'; +import SettingsDialog from './Settings/Settings.tsx'; import { useUIStore } from '../../../store/useUIStore.ts'; function Heading() { diff --git a/src/containers/SideBar/components/Settings/Card.component.tsx b/src/containers/SideBar/components/Settings/Card.component.tsx new file mode 100644 index 000000000..4df26f5a1 --- /dev/null +++ b/src/containers/SideBar/components/Settings/Card.component.tsx @@ -0,0 +1,25 @@ +import { Stack, Typography } from '@mui/material'; +import { CardItem } from './Settings.styles'; + +export interface CardComponentProps { + heading: string; + labels: { labelText: string; labelValue: string }[]; +} + +export const CardComponent = ({ heading, labels }: CardComponentProps) => { + return ( + + {heading} + + {labels.map(({ labelText, labelValue }) => ( + + {labelText}: + + {labelValue} + + + ))} + + + ); +}; diff --git a/src/containers/SideBar/components/Settings/Settings.styles.tsx b/src/containers/SideBar/components/Settings/Settings.styles.tsx new file mode 100644 index 000000000..d27417818 --- /dev/null +++ b/src/containers/SideBar/components/Settings/Settings.styles.tsx @@ -0,0 +1,14 @@ +import { Box, Stack, styled } from '@mui/material'; + +export const CardContainer = styled(Box)(({ theme }) => ({ + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gap: theme.spacing(1), +})); + +export const CardItem = styled(Stack)(({ theme }) => ({ + padding: theme.spacing(1, 1.5), + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + boxShadow: '0px 4px 45px 0px rgba(0, 0, 0, 0.08)', +})); diff --git a/src/containers/SideBar/components/Settings.tsx b/src/containers/SideBar/components/Settings/Settings.tsx similarity index 58% rename from src/containers/SideBar/components/Settings.tsx rename to src/containers/SideBar/components/Settings/Settings.tsx index e6dcc78dc..7ed83d246 100644 --- a/src/containers/SideBar/components/Settings.tsx +++ b/src/containers/SideBar/components/Settings/Settings.tsx @@ -15,10 +15,13 @@ import { Tooltip, } from '@mui/material'; import { IoSettingsOutline, IoClose } from 'react-icons/io5'; -import { useGetSeedWords } from '../../../hooks/useGetSeedWords'; -import truncateString from '../../../utils/truncateString'; +import { useGetSeedWords } from '../../../../hooks/useGetSeedWords'; +import truncateString from '../../../../utils/truncateString'; import { invoke } from '@tauri-apps/api/tauri'; -import { useGetApplicatonsVersions } from '../../../hooks/useGetApplicatonsVersions'; +import { useGetApplicatonsVersions } from '../../../../hooks/useGetApplicatonsVersions'; +import { useAppStatusStore } from '../../../../store/useAppStatusStore'; +import { CardContainer } from './Settings.styles'; +import { CardComponent } from './Card.component'; const Settings: React.FC = () => { const { refreshVersions, applicationsVersions, mainAppVersion } = @@ -29,6 +32,13 @@ const Settings: React.FC = () => { const [isCopyTooltipHidden, setIsCopyTooltipHidden] = useState(true); const { seedWords, getSeedWords, seedWordsFetched, seedWordsFetching } = useGetSeedWords(); + const cpuTemperatures = useAppStatusStore( + (state) => state.cpu?.cpu_temperatures + ); + + const gpuHardwareStatuses = useAppStatusStore( + (state) => state.gpu?.hardware_statuses + ); const handleClickOpen = () => setOpen(true); const handleClose = () => { @@ -171,9 +181,10 @@ const Settings: React.FC = () => { {applicationsVersions && ( - + Versions @@ -181,17 +192,93 @@ const Settings: React.FC = () => { Refresh Versions - - mainApp: {mainAppVersion} - {Object.entries(applicationsVersions).map( - ([key, value]) => ( - - {key}: {value} - - ) - )} + + + + {Object.entries(applicationsVersions).map( + ([key, value]) => ( + + ) + )} + )} + + + + Hardware Temperatures + + + {gpuHardwareStatuses && + gpuHardwareStatuses.map((gpu) => ( + + ))} + {cpuTemperatures && + cpuTemperatures.map((core) => ( + + ))} + + +