use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::{ProfileError, ProfileResult};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProfileConfig {
pub name: String,
#[serde(default)]
pub description: String,
#[serde(default = "default_version")]
pub version: String,
#[serde(default = "default_refresh_ms")]
pub refresh_ms: u64,
#[serde(default)]
pub device_index: u32,
#[serde(default)]
pub backend: BackendConfig,
#[serde(default = "default_load_intensity")]
pub load_intensity: f64,
#[serde(default)]
pub workload: WorkloadConfig,
#[serde(default = "default_problem_size")]
pub problem_size: usize,
#[serde(default = "default_threads")]
pub threads: usize,
#[serde(default)]
pub deterministic: bool,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
pub(super) fn default_version() -> String {
"1.0".to_string()
}
pub(super) fn default_refresh_ms() -> u64 {
100
}
pub(super) fn default_load_intensity() -> f64 {
0.0
}
pub(super) fn default_problem_size() -> usize {
1_048_576
}
pub(super) fn default_threads() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum BackendConfig {
Simd,
Wgpu,
Cuda,
#[default]
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum WorkloadConfig {
#[default]
Gemm,
Conv2d,
Attention,
Bandwidth,
Elementwise,
Reduction,
All,
}
impl Default for ProfileConfig {
fn default() -> Self {
Self {
name: "default".to_string(),
description: String::new(),
version: default_version(),
refresh_ms: default_refresh_ms(),
device_index: 0,
backend: BackendConfig::default(),
load_intensity: default_load_intensity(),
workload: WorkloadConfig::default(),
problem_size: default_problem_size(),
threads: default_threads(),
deterministic: false,
metadata: HashMap::new(),
}
}
}
impl ProfileConfig {
pub fn new(name: &str) -> ProfileResult<Self> {
validate_profile_name(name)?;
let mut config = Self::default();
config.name = name.to_string();
Ok(config)
}
pub fn with_description(name: &str, description: &str) -> ProfileResult<Self> {
let mut config = Self::new(name)?;
config.description = description.to_string();
Ok(config)
}
pub fn backend(mut self, backend: BackendConfig) -> Self {
self.backend = backend;
self
}
pub fn workload(mut self, workload: WorkloadConfig) -> Self {
self.workload = workload;
self
}
pub fn problem_size(mut self, size: usize) -> Self {
self.problem_size = size;
self
}
pub fn load_intensity(mut self, intensity: f64) -> Self {
self.load_intensity = intensity.clamp(0.0, 1.0);
self
}
pub fn threads(mut self, threads: usize) -> Self {
self.threads = threads;
self
}
pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn to_toml(&self) -> ProfileResult<String> {
toml::to_string_pretty(self).map_err(|e| ProfileError::ParseError(e.to_string()))
}
pub fn from_toml(toml_str: &str) -> ProfileResult<Self> {
toml::from_str(toml_str).map_err(|e| ProfileError::ParseError(e.to_string()))
}
}
pub(super) fn validate_profile_name(name: &str) -> ProfileResult<()> {
if name.is_empty() {
return Err(ProfileError::InvalidName(
"name cannot be empty".to_string(),
));
}
if name.len() > 64 {
return Err(ProfileError::InvalidName(
"name cannot exceed 64 characters".to_string(),
));
}
if !name
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
{
return Err(ProfileError::InvalidName(
"name can only contain alphanumeric, underscore, or hyphen".to_string(),
));
}
if let Some(first) = name.chars().next() {
if first == '-' || first.is_numeric() {
return Err(ProfileError::InvalidName(
"name cannot start with hyphen or number".to_string(),
));
}
}
Ok(())
}