diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index 6427c01..1030934 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -105,6 +105,18 @@ pub async fn get_gpu_usage() -> Result { } } +/// +/// ## GPU温度を取得 +/// +#[command] +pub async fn get_gpu_temperature() -> Result, String> +{ + match graphic_service::get_nvidia_gpu_temperature().await { + Ok(temps) => Ok(temps), + Err(e) => Err(format!("Failed to get GPU temperature: {:?}", e)), + } +} + /// /// ## CPU使用率の履歴を取得 /// diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 53c7c2f..ed1b721 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -54,6 +54,7 @@ fn main() { hardware::get_hardware_info, hardware::get_memory_usage, hardware::get_gpu_usage, + hardware::get_gpu_temperature, hardware::get_cpu_usage_history, hardware::get_memory_usage_history, hardware::get_gpu_usage_history, diff --git a/src-tauri/src/services/graphic_service.rs b/src-tauri/src/services/graphic_service.rs index 16d5432..1c787e2 100644 --- a/src-tauri/src/services/graphic_service.rs +++ b/src-tauri/src/services/graphic_service.rs @@ -72,6 +72,64 @@ pub async fn get_nvidia_gpu_usage() -> Result { })? } +#[derive(Debug, Clone, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuTemperature { + name: String, + value: f64, // 摂氏温度 +} + +/// +/// ## GPU温度を取得する(NVAPI を使用) +/// +pub async fn get_nvidia_gpu_temperature() -> Result, nvapi::Status> { + let handle = spawn_blocking(|| { + log_debug!("start", "get_nvidia_gpu_temperature", None::<&str>); + + let gpus = nvapi::PhysicalGpu::enumerate()?; + + if gpus.is_empty() { + log_warn!( + "not found", + "get_nvidia_gpu_temperature", + Some("gpu is not found") + ); + tracing::warn!("gpu is not found"); + return Err(nvapi::Status::Error); // GPUが見つからない場合はエラーを返す + } + + let mut temperatures = Vec::new(); + + for gpu in gpus.iter() { + // 温度情報を取得 + let thermal_settings = gpu.thermal_settings(None).map_err(|e| { + log_warn!( + "thermal_settings_failed", + "get_nvidia_gpu_temperature", + Some(&format!("{:?}", e)) + ); + nvapi::Status::Error + })?; + + temperatures.push(GpuTemperature { + name: gpu.full_name().unwrap_or("Unknown".to_string()), + value: thermal_settings[0].current_temperature.0 as f64, // thermal_settings の0番目の温度を f64 に変換 + }); + } + + Ok(temperatures) + }); + + handle.await.map_err(|e: JoinError| { + log_error!( + "join_error", + "get_nvidia_gpu_temperature", + Some(e.to_string()) + ); + nvapi::Status::Error + })? +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct GraphicInfo { diff --git a/src/App.tsx b/src/App.tsx index 775dc14..329054b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,10 @@ import { useEffect } from "react"; import Dashboard from "./template/Dashboard"; import ChartTemplate from "./template/Usage"; import "./index.css"; -import { useUsageUpdater } from "@/hooks/useHardwareData"; +import { + useHardwareTempUpdater, + useUsageUpdater, +} from "@/hooks/useHardwareData"; import { useErrorModalListener, useSettingsModalListener, @@ -25,6 +28,7 @@ const Page = () => { useUsageUpdater("cpu"); useUsageUpdater("memory"); useUsageUpdater("gpu"); + useHardwareTempUpdater("gpu"); useEffect(() => { if (settings?.theme) { diff --git a/src/atom/chart.ts b/src/atom/chart.ts index 6b2128f..196b6a8 100644 --- a/src/atom/chart.ts +++ b/src/atom/chart.ts @@ -1,5 +1,8 @@ +import type { Temperatures } from "@/types/hardwareDataType"; import { atom } from "jotai"; export const cpuUsageHistoryAtom = atom([]); export const memoryUsageHistoryAtom = atom([]); export const graphicUsageHistoryAtom = atom([]); +export const cpuTempAtom = atom([]); +export const gpuTempAtom = atom([]); diff --git a/src/components/charts/DoughnutChart.tsx b/src/components/charts/DoughnutChart.tsx index 6754802..0f0fcb1 100644 --- a/src/components/charts/DoughnutChart.tsx +++ b/src/components/charts/DoughnutChart.tsx @@ -16,10 +16,12 @@ const DoughnutChart = ({ chartData, hardType, dataType, + showTitle, }: { chartData: number; hardType: ChartDataType; dataType: HardwareDataType; + showTitle: boolean; }) => { const data = { datasets: [ @@ -44,12 +46,27 @@ const DoughnutChart = ({ clock: , }; + const dataTypeUnits: Record = { + usage: "%", + temp: "℃", + clock: "MHz", + }; + return (
-

{displayHardType[hardType]}

+

+ { + showTitle + ? displayHardType[hardType] + : " " /** [TODO] タイトルはコンポーネント外のほうが使いやすそう */ + } +

- {chartData}% + + {chartData} + {dataTypeUnits[dataType]} +
{dataTypeIcons[dataType]} diff --git a/src/hooks/useHardwareData.ts b/src/hooks/useHardwareData.ts index 8323b71..7215ddc 100644 --- a/src/hooks/useHardwareData.ts +++ b/src/hooks/useHardwareData.ts @@ -1,27 +1,30 @@ import { + cpuTempAtom, cpuUsageHistoryAtom, + gpuTempAtom, graphicUsageHistoryAtom, memoryUsageHistoryAtom, } from "@/atom/chart"; import { chartConfig } from "@/consts/chart"; import { getCpuUsage, + getGpuTemperature, getGpuUsage, getMemoryUsage, } from "@/services/hardwareService"; -import type { ChartDataType } from "@/types/hardwareDataType"; +import type { ChartDataType, Temperatures } from "@/types/hardwareDataType"; import { type PrimitiveAtom, useSetAtom } from "jotai"; import { useEffect } from "react"; -type AtomActionMapping = { - atom: PrimitiveAtom; - action: () => Promise; -}; - /** * ハードウェア使用率の履歴を更新する */ export const useUsageUpdater = (dataType: ChartDataType) => { + type AtomActionMapping = { + atom: PrimitiveAtom; + action: () => Promise; + }; + const mapping: Record = { cpu: { atom: cpuUsageHistoryAtom, @@ -59,3 +62,44 @@ export const useUsageUpdater = (dataType: ChartDataType) => { return () => clearInterval(intervalId); }, [setHistory, getUsage]); }; + +export const useHardwareTempUpdater = ( + dataType: Exclude, +) => { + type AtomActionMapping = { + atom: PrimitiveAtom; + action: () => Promise; + }; + + const mapping: Record, AtomActionMapping> = { + cpu: { + atom: cpuTempAtom, + action: () => { + console.error("Not implemented"); + return Promise.resolve([]); + }, + }, + gpu: { + atom: gpuTempAtom, + action: getGpuTemperature, + }, + }; + + const setData = useSetAtom(mapping[dataType].atom); + const getTemp = mapping[dataType].action; + + useEffect(() => { + const fetchData = async () => { + const temp = await getTemp(); + setData(temp); + }; + + fetchData(); + + const intervalId = setInterval(async () => { + fetchData; + }, 10000); + + return () => clearInterval(intervalId); + }, [setData, getTemp]); +}; diff --git a/src/services/hardwareService.ts b/src/services/hardwareService.ts index 3c13a78..e51e92c 100644 --- a/src/services/hardwareService.ts +++ b/src/services/hardwareService.ts @@ -1,4 +1,4 @@ -import type { HardwareInfo } from "@/types/hardwareDataType"; +import type { HardwareInfo, Temperatures } from "@/types/hardwareDataType"; import { invoke } from "@tauri-apps/api/tauri"; export const getCpuUsage = async (): Promise => { @@ -28,3 +28,7 @@ export const getGpuUsage = async (): Promise => { export const getGpuUsageHistory = (seconds: number): Promise => { return invoke("get_gpu_usage_history", { seconds: seconds }); }; + +export const getGpuTemperature = async (): Promise => { + return await invoke("get_gpu_temperature"); +}; diff --git a/src/template/Dashboard.tsx b/src/template/Dashboard.tsx index d821662..358fd45 100644 --- a/src/template/Dashboard.tsx +++ b/src/template/Dashboard.tsx @@ -1,5 +1,6 @@ import { cpuUsageHistoryAtom, + gpuTempAtom, graphicUsageHistoryAtom, memoryUsageHistoryAtom, } from "@/atom/chart"; @@ -52,6 +53,7 @@ const CPUInfo = () => { chartData={cpuUsageHistory[cpuUsageHistory.length - 1]} dataType={"usage"} hardType="cpu" + showTitle={true} /> { const GPUInfo = () => { const [graphicUsageHistory] = useAtom(graphicUsageHistoryAtom); + const [gpuTemp] = useAtom(gpuTempAtom); const { hardwareInfo } = useHardwareInfoAtom(); + const targetTemperature = gpuTemp.find( + (x) => x.name === hardwareInfo.gpus[0].name, + )?.value; + return ( hardwareInfo.gpus && ( <> - +
+ + {targetTemperature && ( + + )} +
+ { chartData={memoryUsageHistory[memoryUsageHistory.length - 1]} dataType={"usage"} hardType="memory" + showTitle={true} /> ;