use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum WorkloadType {
Gaming,
Coding,
VideoEditing,
Browsing,
AIInference,
AITraining,
Office,
MediaPlayback,
VideoCall,
Idle,
Mixed,
Unknown,
}
impl std::fmt::Display for WorkloadType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WorkloadType::Gaming => write!(f, "Gaming"),
WorkloadType::Coding => write!(f, "Coding"),
WorkloadType::VideoEditing => write!(f, "Video Editing"),
WorkloadType::Browsing => write!(f, "Browsing"),
WorkloadType::AIInference => write!(f, "AI Inference"),
WorkloadType::AITraining => write!(f, "AI Training"),
WorkloadType::Office => write!(f, "Office"),
WorkloadType::MediaPlayback => write!(f, "Media Playback"),
WorkloadType::VideoCall => write!(f, "Video Call"),
WorkloadType::Idle => write!(f, "Idle"),
WorkloadType::Mixed => write!(f, "Mixed"),
WorkloadType::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkloadProfile {
pub workload_type: WorkloadType,
pub cpu_priority: u32,
pub gpu_priority: u32,
pub memory_priority: u32,
pub latency_sensitive: bool,
pub throughput_focused: bool,
pub background_allowed: bool,
pub suggestions: Vec<String>,
}
impl WorkloadProfile {
pub fn for_type(workload_type: WorkloadType) -> Self {
match workload_type {
WorkloadType::Gaming => Self {
workload_type,
cpu_priority: 80,
gpu_priority: 100,
memory_priority: 70,
latency_sensitive: true,
throughput_focused: false,
background_allowed: false,
suggestions: vec![
"Disable background apps".into(),
"Set GPU to performance mode".into(),
"Increase process priority".into(),
],
},
WorkloadType::Coding => Self {
workload_type,
cpu_priority: 70,
gpu_priority: 30,
memory_priority: 80,
latency_sensitive: false,
throughput_focused: true,
background_allowed: true,
suggestions: vec![
"Keep IDE in high priority".into(),
"Reserve RAM for compilers".into(),
],
},
WorkloadType::VideoEditing => Self {
workload_type,
cpu_priority: 90,
gpu_priority: 90,
memory_priority: 95,
latency_sensitive: false,
throughput_focused: true,
background_allowed: false,
suggestions: vec![
"Maximize RAM availability".into(),
"Enable GPU acceleration".into(),
"Close unnecessary apps".into(),
],
},
WorkloadType::AIInference => Self {
workload_type,
cpu_priority: 60,
gpu_priority: 95,
memory_priority: 90,
latency_sensitive: true,
throughput_focused: false,
background_allowed: true,
suggestions: vec![
"Reserve VRAM for models".into(),
"Enable KV cache optimization".into(),
],
},
WorkloadType::AITraining => Self {
workload_type,
cpu_priority: 70,
gpu_priority: 100,
memory_priority: 95,
latency_sensitive: false,
throughput_focused: true,
background_allowed: false,
suggestions: vec![
"Maximize VRAM allocation".into(),
"Enable mixed precision".into(),
"Close all non-essential apps".into(),
],
},
WorkloadType::VideoCall => Self {
workload_type,
cpu_priority: 60,
gpu_priority: 40,
memory_priority: 50,
latency_sensitive: true,
throughput_focused: false,
background_allowed: true,
suggestions: vec![
"Prioritize network stability".into(),
"Reduce background CPU usage".into(),
],
},
_ => Self {
workload_type,
cpu_priority: 50,
gpu_priority: 50,
memory_priority: 50,
latency_sensitive: false,
throughput_focused: false,
background_allowed: true,
suggestions: vec![],
},
}
}
}
struct ProcessSignature {
patterns: Vec<&'static str>,
workload_type: WorkloadType,
weight: f32,
}
pub struct WorkloadClassifier {
signatures: Vec<ProcessSignature>,
history: Vec<(std::time::Instant, WorkloadType)>,
}
impl WorkloadClassifier {
pub fn new() -> Self {
let signatures = vec![
ProcessSignature {
patterns: vec![
"steam", "epicgameslauncher", "gog", "origin", "uplay",
"battlenet", "valorant", "fortnite", "minecraft",
"rocketleague", "csgo", "dota2", "leagueoflegends",
],
workload_type: WorkloadType::Gaming,
weight: 1.0,
},
ProcessSignature {
patterns: vec![
"code", "vscode", "idea", "pycharm", "webstorm", "rider",
"visual studio", "devenv", "vim", "nvim", "emacs",
"sublime_text", "atom", "notepad++", "cargo", "rustc",
"node", "python", "java", "go", "dotnet",
],
workload_type: WorkloadType::Coding,
weight: 0.8,
},
ProcessSignature {
patterns: vec![
"premiere", "aftereffects", "resolve", "davinci",
"finalcut", "vegas", "kdenlive", "shotcut", "blender",
"handbrake", "ffmpeg",
],
workload_type: WorkloadType::VideoEditing,
weight: 1.0,
},
ProcessSignature {
patterns: vec![
"ollama", "llama", "vllm", "tgi", "lmstudio",
"oobabooga", "koboldcpp", "exllama",
],
workload_type: WorkloadType::AIInference,
weight: 1.0,
},
ProcessSignature {
patterns: vec![
"pytorch", "tensorflow", "train", "accelerate",
],
workload_type: WorkloadType::AITraining,
weight: 0.7,
},
ProcessSignature {
patterns: vec![
"zoom", "teams", "slack", "discord", "skype",
"webex", "googlemeet", "facetime",
],
workload_type: WorkloadType::VideoCall,
weight: 0.9,
},
ProcessSignature {
patterns: vec![
"chrome", "firefox", "edge", "safari", "opera", "brave",
],
workload_type: WorkloadType::Browsing,
weight: 0.5,
},
ProcessSignature {
patterns: vec![
"word", "excel", "powerpoint", "outlook", "onenote",
"libreoffice", "sheets", "docs",
],
workload_type: WorkloadType::Office,
weight: 0.6,
},
ProcessSignature {
patterns: vec![
"vlc", "mpv", "mpc-hc", "spotify", "musicbee",
"foobar2000", "netflix", "plex",
],
workload_type: WorkloadType::MediaPlayback,
weight: 0.5,
},
];
Self {
signatures,
history: Vec::new(),
}
}
pub fn classify_current(&self) -> WorkloadType {
let processes = self.get_running_processes();
self.classify_from_processes(&processes)
}
pub fn classify_from_processes(&self, processes: &[String]) -> WorkloadType {
let mut scores: HashMap<WorkloadType, f32> = HashMap::new();
for process in processes {
let process_lower = process.to_lowercase();
for sig in &self.signatures {
for pattern in &sig.patterns {
if process_lower.contains(pattern) {
*scores.entry(sig.workload_type).or_insert(0.0) += sig.weight;
}
}
}
}
scores
.into_iter()
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
.map(|(wt, _)| wt)
.unwrap_or(WorkloadType::Unknown)
}
pub fn get_profile(&self) -> WorkloadProfile {
let workload = self.classify_current();
WorkloadProfile::for_type(workload)
}
#[cfg(windows)]
fn get_running_processes(&self) -> Vec<String> {
use windows::Win32::System::ProcessStatus::{EnumProcesses, GetModuleBaseNameW};
use windows::Win32::System::Threading::{
OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ,
};
use windows::Win32::Foundation::CloseHandle;
let mut processes = Vec::new();
unsafe {
let mut pids = [0u32; 2048];
let mut bytes_returned = 0u32;
if EnumProcesses(
pids.as_mut_ptr(),
(pids.len() * std::mem::size_of::<u32>()) as u32,
&mut bytes_returned,
).is_ok() {
let num_processes = bytes_returned as usize / std::mem::size_of::<u32>();
for &pid in &pids[..num_processes] {
if pid == 0 {
continue;
}
if let Ok(handle) = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
false,
pid,
) {
let mut name_buf = [0u16; 260];
let len = GetModuleBaseNameW(handle, None, &mut name_buf);
if len > 0 {
let name = String::from_utf16_lossy(&name_buf[..len as usize]);
processes.push(name);
}
let _ = CloseHandle(handle);
}
}
}
}
processes
}
#[cfg(not(windows))]
fn get_running_processes(&self) -> Vec<String> {
Vec::new()
}
pub fn record_workload(&mut self, workload: WorkloadType) {
self.history.push((std::time::Instant::now(), workload));
if self.history.len() > 1000 {
self.history.remove(0);
}
}
pub fn get_trend(&self, duration: std::time::Duration) -> HashMap<WorkloadType, usize> {
let cutoff = std::time::Instant::now() - duration;
let mut counts: HashMap<WorkloadType, usize> = HashMap::new();
for (time, workload) in &self.history {
if *time > cutoff {
*counts.entry(*workload).or_insert(0) += 1;
}
}
counts
}
}
impl Default for WorkloadClassifier {
fn default() -> Self {
Self::new()
}
}