Skip to content

Commit

Permalink
Merge pull request #13 from shm11C3/feature_gpu-info
Browse files Browse the repository at this point in the history
feature: GPU情報の取得(Nvidia)
  • Loading branch information
shm11C3 authored Sep 28, 2024
2 parents 41c7018 + 598dc9e commit 165f055
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 29 deletions.
22 changes: 22 additions & 0 deletions src-tauri/src/commands/hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ pub async fn get_gpu_usage() -> Result<i32, String> {
}
}

///
/// ## GPU温度を取得
///
#[command]
pub async fn get_gpu_temperature() -> Result<Vec<graphic_service::NameValue>, String> {
match graphic_service::get_nvidia_gpu_temperature().await {
Ok(temps) => Ok(temps),
Err(e) => Err(format!("Failed to get GPU temperature: {:?}", e)),
}
}

///
/// ## GPUのファン回転数を取得
///
#[command]
pub async fn get_nvidia_gpu_cooler() -> Result<Vec<graphic_service::NameValue>, String> {
match graphic_service::get_nvidia_gpu_cooler_stat().await {
Ok(temps) => Ok(temps),
Err(e) => Err(format!("Failed to get GPU cooler status: {:?}", e)),
}
}

///
/// ## CPU使用率の履歴を取得
///
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ fn main() {
hardware::get_hardware_info,
hardware::get_memory_usage,
hardware::get_gpu_usage,
hardware::get_gpu_temperature,
hardware::get_nvidia_gpu_cooler,
hardware::get_cpu_usage_history,
hardware::get_memory_usage_history,
hardware::get_gpu_usage_history,
Expand Down
113 changes: 113 additions & 0 deletions src-tauri/src/services/graphic_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,119 @@ pub async fn get_nvidia_gpu_usage() -> Result<f32, nvapi::Status> {
})?
}

#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NameValue {
name: String,
value: f64, // 摂氏温度
}

///
/// ## GPU温度を取得する(NVAPI を使用)
///
pub async fn get_nvidia_gpu_temperature() -> Result<Vec<NameValue>, 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(NameValue {
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
})?
}

///
/// ## GPUのファン回転数を取得する(NVAPI を使用)
///
pub async fn get_nvidia_gpu_cooler_stat() -> Result<Vec<NameValue>, nvapi::Status> {
let handle = spawn_blocking(|| {
log_debug!("start", "get_nvidia_gpu_cooler_stat", None::<&str>);

let gpus = nvapi::PhysicalGpu::enumerate()?;

if gpus.is_empty() {
log_warn!(
"not found",
"get_nvidia_gpu_cooler_stat",
Some("gpu is not found")
);
tracing::warn!("gpu is not found");
return Err(nvapi::Status::Error); // GPUが見つからない場合はエラーを返す
}

let mut cooler_infos = Vec::new();

print!("{:?}", gpus);

for gpu in gpus.iter() {
// 温度情報を取得
let cooler_settings = gpu.cooler_settings(None).map_err(|e| {
log_warn!(
"cooler_settings_failed",
"get_nvidia_gpu_cooler_stat",
Some(&format!("{:?}", e))
);
nvapi::Status::Error
})?;

print!("{:?}", cooler_settings);

cooler_infos.push(NameValue {
name: gpu.full_name().unwrap_or("Unknown".to_string()),
value: cooler_settings[0].current_level.0 as f64,
});
}

Ok(cooler_infos)
});

handle.await.map_err(|e: JoinError| {
log_error!(
"join_error",
"get_nvidia_gpu_cooler_stat",
Some(e.to_string())
);
nvapi::Status::Error
})?
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GraphicInfo {
Expand Down
4 changes: 3 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect } from "react";
import Dashboard from "./template/Dashboard";
import ChartTemplate from "./template/Usage";
import "./index.css";
import { useUsageUpdater } from "@/hooks/useHardwareData";
import { useHardwareUpdater, useUsageUpdater } from "@/hooks/useHardwareData";
import {
useErrorModalListener,
useSettingsModalListener,
Expand All @@ -25,6 +25,8 @@ const Page = () => {
useUsageUpdater("cpu");
useUsageUpdater("memory");
useUsageUpdater("gpu");
useHardwareUpdater("gpu", "temp");
useHardwareUpdater("gpu", "fan");

useEffect(() => {
if (settings?.theme) {
Expand Down
5 changes: 5 additions & 0 deletions src/atom/chart.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { NameValues } from "@/types/hardwareDataType";
import { atom } from "jotai";

export const cpuUsageHistoryAtom = atom<number[]>([]);
export const memoryUsageHistoryAtom = atom<number[]>([]);
export const graphicUsageHistoryAtom = atom<number[]>([]);
export const cpuTempAtom = atom<NameValues>([]);
export const cpuFanSpeedAtom = atom<NameValues>([]);
export const gpuTempAtom = atom<NameValues>([]);
export const gpuFanSpeedAtom = atom<NameValues>([]);
26 changes: 13 additions & 13 deletions src/atom/useHardwareInfoAtom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ export const useHardwareInfoAtom = () => {
const [hardwareInfo, setHardInfo] = useAtom(hardInfoAtom);

useEffect(() => {
const init = async () => {
try {
const hardwareInfo = await getHardwareInfo();
setHardInfo(hardwareInfo);
if (!hardwareInfo.isFetched) {
const init = async () => {
try {
const fetchedHardwareInfo = await getHardwareInfo();

hardwareInfo.isFetched = true;
} catch (e) {
console.error(e);
hardwareInfo.isFetched = false;
}
};
setHardInfo({
...fetchedHardwareInfo,
isFetched: true,
});
} catch (e) {
console.error(e);
}
};

// データがなければ取得して更新
if (!hardwareInfo.isFetched) {
init();
}
}, [setHardInfo, hardwareInfo]);
}, [hardwareInfo.isFetched, setHardInfo]);

return { hardwareInfo };
};
21 changes: 19 additions & 2 deletions src/components/charts/DoughnutChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ const DoughnutChart = ({
chartData,
hardType,
dataType,
showTitle,
}: {
chartData: number;
hardType: ChartDataType;
dataType: HardwareDataType;
showTitle: boolean;
}) => {
const data = {
datasets: [
Expand All @@ -44,12 +46,27 @@ const DoughnutChart = ({
clock: <Speedometer className="mr-1" size={18} weight="duotone" />,
};

const dataTypeUnits: Record<HardwareDataType, string> = {
usage: "%",
temp: "℃",
clock: "MHz",
};

return (
<div className="p-2 w-36 relative">
<h3 className="text-lg font-bold">{displayHardType[hardType]}</h3>
<h3 className="text-lg font-bold">
{
showTitle
? displayHardType[hardType]
: " " /** [TODO] タイトルはコンポーネント外のほうが使いやすそう */
}
</h3>
<Doughnut data={data} options={options} />
<div className="absolute inset-0 flex flex-col items-center justify-center">
<span className="text-white text-xl font-semibold">{chartData}%</span>
<span className="text-white text-xl font-semibold">
{chartData}
{dataTypeUnits[dataType]}
</span>
</div>
<span className="flex justify-center mt-4 text-gray-400">
{dataTypeIcons[dataType]}
Expand Down
78 changes: 72 additions & 6 deletions src/hooks/useHardwareData.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import {
cpuFanSpeedAtom,
cpuTempAtom,
cpuUsageHistoryAtom,
gpuFanSpeedAtom,
gpuTempAtom,
graphicUsageHistoryAtom,
memoryUsageHistoryAtom,
} from "@/atom/chart";
import { chartConfig } from "@/consts/chart";
import {
getCpuUsage,
getGpuFanSpeed,
getGpuTemperature,
getGpuUsage,
getMemoryUsage,
} from "@/services/hardwareService";
import type { ChartDataType } from "@/types/hardwareDataType";
import type { ChartDataType, NameValues } from "@/types/hardwareDataType";
import { type PrimitiveAtom, useSetAtom } from "jotai";
import { useEffect } from "react";

type AtomActionMapping = {
atom: PrimitiveAtom<number[]>;
action: () => Promise<number>;
};

/**
* ハードウェア使用率の履歴を更新する
*/
export const useUsageUpdater = (dataType: ChartDataType) => {
type AtomActionMapping = {
atom: PrimitiveAtom<number[]>;
action: () => Promise<number>;
};

const mapping: Record<ChartDataType, AtomActionMapping> = {
cpu: {
atom: cpuUsageHistoryAtom,
Expand Down Expand Up @@ -59,3 +65,63 @@ export const useUsageUpdater = (dataType: ChartDataType) => {
return () => clearInterval(intervalId);
}, [setHistory, getUsage]);
};

export const useHardwareUpdater = (
hardType: Exclude<ChartDataType, "memory">,
dataType: "temp" | "fan",
) => {
type AtomActionMapping = {
atom: PrimitiveAtom<NameValues>;
action: () => Promise<NameValues>;
};

const mapping: Record<
Exclude<ChartDataType, "memory">,
Record<"temp" | "fan", AtomActionMapping>
> = {
cpu: {
temp: {
atom: cpuTempAtom,
action: () => {
console.error("Not implemented");
return Promise.resolve([]);
},
},
fan: {
atom: cpuFanSpeedAtom,
action: () => {
console.error("Not implemented");
return Promise.resolve([]);
},
},
},
gpu: {
fan: {
atom: gpuFanSpeedAtom,
action: getGpuFanSpeed,
},
temp: {
atom: gpuTempAtom,
action: getGpuTemperature,
},
},
};

const setData = useSetAtom(mapping[hardType][dataType].atom);
const getData = mapping[hardType][dataType].action;

useEffect(() => {
const fetchData = async () => {
const temp = await getData();
setData(temp);
};

fetchData();

const intervalId = setInterval(async () => {
fetchData;
}, 10000);

return () => clearInterval(intervalId);
}, [setData, getData]);
};
14 changes: 12 additions & 2 deletions src/services/hardwareService.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { HardwareInfo } from "@/types/hardwareDataType";
import type { HardwareInfo, NameValues } from "@/types/hardwareDataType";
import { invoke } from "@tauri-apps/api/tauri";

export const getCpuUsage = async (): Promise<number> => {
return await invoke("get_cpu_usage");
};

export const getHardwareInfo = async (): Promise<HardwareInfo> => {
export const getHardwareInfo = async (): Promise<
Exclude<HardwareInfo, "isFetched">
> => {
return await invoke("get_hardware_info");
};

Expand All @@ -28,3 +30,11 @@ export const getGpuUsage = async (): Promise<number> => {
export const getGpuUsageHistory = (seconds: number): Promise<number[]> => {
return invoke("get_gpu_usage_history", { seconds: seconds });
};

export const getGpuTemperature = async (): Promise<NameValues> => {
return await invoke("get_gpu_temperature");
};

export const getGpuFanSpeed = async (): Promise<NameValues> => {
return await invoke("get_nvidia_gpu_cooler");
};
Loading

0 comments on commit 165f055

Please sign in to comment.