Skip to content

Commit

Permalink
feature: process info
Browse files Browse the repository at this point in the history
  • Loading branch information
shm11C3 committed Sep 28, 2024
1 parent 165f055 commit a5e879a
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 49 deletions.
128 changes: 106 additions & 22 deletions src-tauri/src/commands/hardware.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::services::graphic_service;
use crate::services::system_info_service;
use crate::{log_debug, log_error, log_internal, log_warn};
use serde::Serialize;
use crate::{log_debug, log_error, log_info, log_internal, log_warn};
use serde::{Serialize, Serializer};
use std::collections::HashMap;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use sysinfo::System;
use sysinfo::{Pid, ProcessesToUpdate, System};
use tauri::command;

pub struct AppState {
Expand All @@ -15,6 +16,8 @@ pub struct AppState {
pub memory_history: Arc<Mutex<VecDeque<f32>>>,
pub gpu_history: Arc<Mutex<VecDeque<f32>>>,
pub gpu_usage: Arc<Mutex<f32>>,
pub process_cpu_histories: Arc<Mutex<HashMap<Pid, VecDeque<f32>>>>,
pub process_memory_histories: Arc<Mutex<HashMap<Pid, VecDeque<f32>>>>,
}

///
Expand All @@ -27,6 +30,77 @@ const SYSTEM_INFO_INIT_INTERVAL: u64 = 1;
///
const HISTORY_CAPACITY: usize = 60;

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProcessInfo {
pub pid: i32,
pub name: String,
#[serde(serialize_with = "serialize_usage")]
pub cpu_usage: f32,
#[serde(serialize_with = "serialize_usage")]
pub memory_usage: f32,
}

fn serialize_usage<S>(x: &f32, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if x.fract() == 0.0 {
s.serialize_str(&format!("{:.0}", x)) // 整数のみ
} else {
s.serialize_str(&format!("{:.1}", x)) // 小数点以下1桁まで
}
}

///
/// ## プロセスリストを取得
///
#[command]
pub fn get_process_list(state: tauri::State<'_, AppState>) -> Vec<ProcessInfo> {
let mut system = state.system.lock().unwrap();
let process_cpu_histories = state.process_cpu_histories.lock().unwrap();
let process_memory_histories = state.process_memory_histories.lock().unwrap();

system.refresh_processes(ProcessesToUpdate::All);

system
.processes()
.values()
.map(|process| {
let pid = process.pid();

// 5秒間のCPU使用率の平均を計算
let cpu_usage = if let Some(history) = process_cpu_histories.get(&pid) {
let len = history.len().min(5); // 最大5秒分のデータ
let sum: f32 = history.iter().rev().take(len).sum();
let avg = sum / len as f32;

(avg * 10.0).round() / 10.0
} else {
0.0 // 履歴がなければ0を返す
};

// 5秒間のメモリ使用率の平均を計算
let memory_usage = if let Some(history) = process_memory_histories.get(&pid) {
let len = history.len().min(5); // 最大5秒分のデータ
let sum: f32 = history.iter().rev().take(len).sum();
let avg = sum / len as f32;

(avg * 10.0).round() / 10.0
} else {
process.memory() as f32 / 1024.0 // 履歴がなければ現在のメモリ使用量を返す
};

ProcessInfo {
pid: pid.as_u32() as i32, // プロセスID
name: process.name().to_string_lossy().into_owned(), // プロセス名を取得
cpu_usage, // 平均CPU使用率
memory_usage, // 平均メモリ使用率
}
})
.collect()
}

///
/// ## CPU使用率(%)を取得
///
Expand Down Expand Up @@ -185,6 +259,8 @@ pub fn initialize_system(
memory_history: Arc<Mutex<VecDeque<f32>>>,
gpu_usage: Arc<Mutex<f32>>,
gpu_history: Arc<Mutex<VecDeque<f32>>>,
process_cpu_histories: Arc<Mutex<HashMap<Pid, VecDeque<f32>>>>,
process_memory_histories: Arc<Mutex<HashMap<Pid, VecDeque<f32>>>>,
) {
thread::spawn(move || loop {
{
Expand All @@ -193,8 +269,7 @@ pub fn initialize_system(
Err(_) => continue, // エラーハンドリング:ロックが破損している場合はスキップ
};

sys.refresh_cpu_all();
sys.refresh_memory();
sys.refresh_all();

let cpu_usage = {
let cpus = sys.cpus();
Expand All @@ -208,16 +283,6 @@ pub fn initialize_system(
(used_memory / total_memory * 100.0).round() as f32
};

//let gpu_usage_value = match get_gpu_usage() {
// Ok(usage) => usage,
// Err(_) => 0.0, // エラーが発生した場合はデフォルト値として0.0を使用
//};

//{
// let mut gpu = gpu_usage.lock().unwrap();
// *gpu = gpu_usage_value;
//}

{
let mut cpu_hist = cpu_history.lock().unwrap();
if cpu_hist.len() >= HISTORY_CAPACITY {
Expand All @@ -234,13 +299,32 @@ pub fn initialize_system(
memory_hist.push_back(memory_usage);
}

//{
// let mut gpu_hist = gpu_history.lock().unwrap();
// if gpu_hist.len() >= HISTORY_CAPACITY {
// gpu_hist.pop_front();
// }
// gpu_hist.push_back(gpu_usage_value);
//}
// 各プロセスごとのCPUおよびメモリ使用率を保存
{
let mut process_cpu_histories = process_cpu_histories.lock().unwrap();
let mut process_memory_histories = process_memory_histories.lock().unwrap();

for (pid, process) in sys.processes() {
// CPU使用率の履歴を更新
let cpu_usage = process.cpu_usage() as f32;
let cpu_history = process_cpu_histories.entry(*pid).or_insert(VecDeque::new());

if cpu_history.len() >= HISTORY_CAPACITY {
cpu_history.pop_front();
}
cpu_history.push_back(cpu_usage);

// メモリ使用率の履歴を更新
let memory_usage = process.memory() as f32 / 1024.0; // KB単位からMB単位に変換
let memory_history =
process_memory_histories.entry(*pid).or_insert(VecDeque::new());

if memory_history.len() >= HISTORY_CAPACITY {
memory_history.pop_front();
}
memory_history.push_back(memory_usage);
}
}
}

thread::sleep(Duration::from_secs(SYSTEM_INFO_INIT_INTERVAL));
Expand Down
9 changes: 8 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use commands::config;
use commands::hardware;
use services::window_menu_service;

use std::collections::VecDeque;
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex};
use sysinfo::System;

Expand All @@ -25,13 +25,17 @@ fn main() {
let memory_history = Arc::new(Mutex::new(VecDeque::with_capacity(60)));
let gpu_usage = Arc::new(Mutex::new(0.0));
let gpu_history = Arc::new(Mutex::new(VecDeque::with_capacity(60)));
let process_cpu_histories = Arc::new(Mutex::new(HashMap::new()));
let process_memory_histories = Arc::new(Mutex::new(HashMap::new()));

let state = hardware::AppState {
system: Arc::clone(&system),
cpu_history: Arc::clone(&cpu_history),
memory_history: Arc::clone(&memory_history),
gpu_usage: Arc::clone(&gpu_usage),
gpu_history: Arc::clone(&gpu_history),
process_cpu_histories: Arc::clone(&process_cpu_histories),
process_memory_histories: Arc::clone(&process_memory_histories),
};

let menu = window_menu_service::create_setting();
Expand All @@ -42,6 +46,8 @@ fn main() {
memory_history,
gpu_usage,
gpu_history,
process_cpu_histories,
process_memory_histories,
);

tauri::Builder::default()
Expand All @@ -50,6 +56,7 @@ fn main() {
.manage(app_state)
.menu(menu)
.invoke_handler(tauri::generate_handler![
hardware::get_process_list,
hardware::get_cpu_usage,
hardware::get_hardware_info,
hardware::get_memory_usage,
Expand Down
2 changes: 0 additions & 2 deletions src-tauri/src/services/graphic_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,6 @@ pub async fn get_nvidia_gpu_cooler_stat() -> Result<Vec<NameValue>, nvapi::Statu
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,
Expand Down
2 changes: 1 addition & 1 deletion src/atom/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export const modalAtoms = {
showSettingsModal: atom<boolean>(false),
};

export const selectedMenuAtom = atom<SelectedMenuType>("usage"); // [TODO] change to dashboard
export const selectedMenuAtom = atom<SelectedMenuType>("dashboard");
127 changes: 127 additions & 0 deletions src/components/charts/ProcessTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { getProcesses } from "@/services/hardwareService";
import type { ProcessInfo } from "@/types/hardwareDataType";
import { CaretDown } from "@phosphor-icons/react";
import { atom, useAtom, useSetAtom } from "jotai";
import { useEffect, useState } from "react";

const processesAtom = atom<ProcessInfo[]>([]);

const ProcessesTable = ({
defaultItemLength,
}: { defaultItemLength: number }) => {
const [processes] = useAtom(processesAtom);
const setAtom = useSetAtom(processesAtom);
const [showAllItem, setShowAllItem] = useState<boolean>(false);
const [sortConfig, setSortConfig] = useState<{
key: keyof ProcessInfo;
direction: "ascending" | "descending";
} | null>(null);

useEffect(() => {
const fetchProcesses = async () => {
try {
const processesData = await getProcesses();
console.log(processesData);
setAtom(processesData);
} catch (error) {
console.error("Failed to fetch processes:", error);
}
};

fetchProcesses();

const interval = setInterval(fetchProcesses, 3000);

return () => clearInterval(interval);
}, [setAtom]);

const sortedProcesses = [...processes];
if (sortConfig !== null) {
sortedProcesses.sort((a, b) => {
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === "ascending" ? -1 : 1;
}
if (a[sortConfig.key] > b[sortConfig.key]) {
return sortConfig.direction === "ascending" ? 1 : -1;
}
return 0;
});
}

const requestSort = (key: keyof ProcessInfo) => {
let direction: "ascending" | "descending" = "ascending";
if (
sortConfig &&
sortConfig.key === key &&
sortConfig.direction === "ascending"
) {
direction = "descending";
}
setSortConfig({ key, direction });
};

return (
<div className="p-4 border rounded-md shadow-md bg-gray-800 text-white">
<h4 className="text-xl font-bold mb-2">Process</h4>
<div className="overflow-x-auto">
<table className="w-full text-left">
<thead>
<tr className="border-b border-gray-700">
<th
className="pr-4 py-2 text-gray-400 cursor-pointer"
onClick={() => requestSort("pid")}
onKeyDown={() => requestSort("pid")}
>
PID
</th>
<th
className="pr-4 py-2 text-gray-400 cursor-pointer"
onClick={() => requestSort("name")}
onKeyDown={() => requestSort("name")}
>
Name
</th>
<th
className="pr-4 py-2 text-gray-400 cursor-pointer"
onClick={() => requestSort("cpuUsage")}
onKeyDown={() => requestSort("cpuUsage")}
>
CPU Usage
</th>
<th
className="pr-4 py-2 text-gray-400 cursor-pointer"
onClick={() => requestSort("memoryUsage")}
onKeyDown={() => requestSort("memoryUsage")}
>
Memory Usage
</th>
</tr>
</thead>
<tbody>
{sortedProcesses
.slice(0, showAllItem ? processes.length : defaultItemLength)
.map((process) => (
<tr key={process.pid} className="border-b border-gray-700">
<td className="py-2">{process.pid}</td>
<td className="py-2">{process.name}</td>
<td className="py-2">{process.cpuUsage}%</td>
<td className="py-2">{process.memoryUsage} MB</td>
</tr>
))}
</tbody>
</table>
{!showAllItem && (
<button
type="button"
onClick={() => setShowAllItem(true)}
className="w-full flex justify-center items-center py-2 text-gray-400 hover:text-white focus:outline-none mt--4"
>
<CaretDown size={32} />
</button>
)}
</div>
</div>
);
};

export default ProcessesTable;
10 changes: 9 additions & 1 deletion src/services/hardwareService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import type { HardwareInfo, NameValues } from "@/types/hardwareDataType";
import type {
HardwareInfo,
NameValues,
ProcessInfo,
} from "@/types/hardwareDataType";
import { invoke } from "@tauri-apps/api/tauri";

export const getProcesses = async (): Promise<ProcessInfo[]> => {
return await invoke("get_process_list");
};

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

0 comments on commit a5e879a

Please sign in to comment.