use libamdgpu_top::{GuiMode, GuiWgpuBackend, PCI};
pub struct MainOpt {
pub instance: Option<usize>, pub refresh_period: u64, pub update_process_index: u64, pub pci: Option<PCI::BUS_INFO>,
pub select_apu: bool,
pub json_iterations: u32,
pub app_mode: AppMode,
pub dump_mode: DumpMode,
pub opt_dump_mode: OptDumpMode,
pub single_gpu: bool,
pub no_pc: bool,
pub is_dark_mode: Option<bool>,
pub decode_gpu_metrics: Option<String>,
pub hide_fdinfo: bool,
pub wgpu_backend: GuiWgpuBackend,
pub gui_mode: GuiMode,
}
impl Default for MainOpt {
fn default() -> Self {
Self {
instance: None,
refresh_period: 1000, update_process_index: 5, pci: None,
dump_mode: DumpMode::NoDump,
opt_dump_mode: OptDumpMode::NoOptDump,
select_apu: false,
app_mode: AppMode::TUI,
json_iterations: 0,
single_gpu: false,
no_pc: false,
is_dark_mode: None,
decode_gpu_metrics: None,
hide_fdinfo: false,
wgpu_backend: GuiWgpuBackend::Gl,
gui_mode: GuiMode::Auto,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum AppMode {
TUI,
#[cfg(feature = "gui")]
GUI,
#[cfg(feature = "json")]
JSON,
#[cfg(feature = "json")]
JSON_FIFO(String),
#[cfg(feature = "tui")]
SMI,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum DumpMode {
Info,
List,
Process,
Version,
PPTable,
Xdna,
NoDump,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum OptDumpMode {
NoOptDump,
GpuMetrics,
DrmInfo,
}
const HELP_MSG: &str = concat!(
env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"), "\n",
env!("CARGO_PKG_HOMEPAGE"), "\n",
"\n",
"USAGE:\n",
" cargo run -- [options ..] or <", env!("CARGO_PKG_NAME"), "> [options ..]\n",
"\n",
"FLAGS:\n",
" -d, --dump\n",
" Dump AMDGPU info. (Specifications, VRAM, PCI, ResizableBAR, VBIOS, Video caps)\n",
" This option can be combined with the \"-J\" option.\n",
" --list\n",
" Display a list of AMDGPU devices.\n",
" -J, --json\n",
" Output JSON formatted data.\n",
" This option can be combined with the \"-d\" option.\n",
" --gui\n",
" Launch GUI mode.\n",
" --single-gui\n",
" Launch Single GUI mode.\n",
" --tab-gui\n",
" Launch Tab GUI mode.\n",
" --smi\n",
" Launch Simple TUI mode. (like nvidia-smi, rocm-smi)\n",
" -p, --process\n",
" Dump All GPU processes and memory usage per process.\n",
" --apu, --select-apu\n",
" Select APU instance.\n",
" --single, --single-gpu\n",
" Display only the selected APU/GPU\n",
" --no-pc\n",
" The application does not read the performance counter (GRBM, GRBM2)\n",
" if this flag is set.\n",
" Reading the performance counter may deactivate the power saving feature of APU/GPU.\n",
" -gm, --gpu_metrics, --gpu-metrics\n",
" Dump gpu_metrics for all AMD GPUs.\n",
" https://www.kernel.org/doc/html/latest/gpu/amdgpu/thermal.html#gpu-metrics\n",
" --pp_table, --pp-table\n",
" Dump pp_table from sysfs and VBIOS for all AMD GPUs.\n",
" (only support Navi1x and Navi2x, Navi3x)\n",
" --drm_info, --drm-info\n",
" Dump DRM info.\n",
" Inspired by https://gitlab.freedesktop.org/emersion/drm_info\n",
" --xdna\n",
" Dump XDNA NPU info.\n",
" --dark, --dark-mode\n",
" Set to the dark mode. (TUI/GUI)\n",
" --light, --light-mode\n",
" Set to the light mode. (TUI/GUI)\n",
" --hide-fdinfo\n",
" Hide fdinfo panel and launch. (TUI)\n",
" --gl, --opengl\n",
" Use OpenGL API to the GUI backend.\n",
" --vk, --vulkan\n",
" Use Vulkan API to the GUI backend,\n",
" and use APU/iGPU for GUI rendering if it is available.\n",
" -V, --version\n",
" Print version information.\n",
" -h, --help\n",
" Print help information.\n",
"\n",
"OPTIONS:\n",
" -i <usize>\n",
" Select GPU instance.\n",
" --pci <String>\n",
" Specifying PCI path. (domain:bus:dev.func)\n",
" -s <u64>, -s <u64>ms\n",
" Refresh period (interval) in milliseconds for JSON mode. (default: 1000ms)\n",
" -n <u32>\n",
" Specifies the maximum number of iteration for JSON mode.\n",
" If 0 is specified, it will be an infinite loop. (default: 0)\n",
" -u <u64>, --update-process-index <u64>\n",
" Update interval in seconds of the process index for fdinfo. (default: 5s)\n",
" --json_fifo, --json-fifo <String>\n",
" Output JSON formatted data to FIFO (named pipe) for other application and scripts.\n",
" --decode-gm <Path>, --decode-gpu-metrics <Path>\n",
" Decode the specified gpu_metrics file.\n",
);
impl MainOpt {
#[allow(unused_assignments)]
pub fn parse() -> Self {
let mut opt = Self::default();
let mut skip = false;
let args = &std::env::args().skip(1).collect::<Vec<String>>();
for (idx, arg) in args.iter().enumerate() {
if skip {
skip = false;
continue;
}
if !arg.starts_with('-') {
eprintln!("Unknown option: {arg}");
continue;
}
match arg.as_str() {
"-i" => {
if let Some(val_str) = args.get(idx+1) {
opt.instance = Some(val_str.parse::<usize>().unwrap());
skip = true;
} else {
eprintln!("missing argument: \"-i <usize>\"");
std::process::exit(1);
}
},
"-d" | "--dump" => {
opt.dump_mode = DumpMode::Info;
},
"-J" | "--json" => {
#[cfg(feature = "json")]
{
opt.app_mode = AppMode::JSON;
}
#[cfg(not(feature = "json"))]
{
eprintln!("\"json\" feature is not enabled for this build.");
std::process::exit(1);
}
},
"--json-fifo" | "--json_fifo" => {
#[cfg(feature = "json")]
{
let s = if let Some(val_str) = args.get(idx+1) {
if val_str.starts_with('-') {
eprintln!("missing argument: \"--json-fifo <String/Path>\"");
std::process::exit(1);
} else {
skip = true;
String::from(val_str)
}
} else {
eprintln!("missing argument: \"--json-fifo <String/Path>\"");
std::process::exit(1);
};
opt.app_mode = AppMode::JSON_FIFO(s);
}
#[cfg(not(feature = "json"))]
{
eprintln!("\"json\" feature is not enabled for this build.");
std::process::exit(1);
}
},
"-s" => {
if let Some(val_str) = args.get(idx+1) {
let tmp = if val_str.ends_with("ms") {
let len = val_str.len();
val_str
.get(..len-2)
.and_then(|v| v.parse::<u64>().ok())
.unwrap()
} else {
val_str.parse::<u64>().unwrap()
};
if tmp != 0 {
opt.refresh_period = tmp;
}
skip = true;
} else {
eprintln!("missing argument: \"-s <u64>\"");
std::process::exit(1);
}
},
"-u" | "--update-process-index" => {
if let Some(val_str) = args.get(idx+1) {
let tmp = val_str.parse::<u64>().unwrap();
if tmp != 0 {
opt.update_process_index = tmp;
}
skip = true;
} else {
eprintln!("missing argument: \"-u <u64>\"");
std::process::exit(1);
}
},
"--gui" => {
#[cfg(feature = "gui")]
{
opt.app_mode = AppMode::GUI;
opt.gui_mode = GuiMode::Auto;
}
#[cfg(not(feature = "gui"))]
{
eprintln!("\"gui\" feature is not enabled for this build.");
std::process::exit(1);
}
},
"--single-gui" => {
#[cfg(feature = "gui")]
{
opt.app_mode = AppMode::GUI;
opt.gui_mode = GuiMode::Single;
}
#[cfg(not(feature = "gui"))]
{
eprintln!("\"gui\" feature is not enabled for this build.");
std::process::exit(1);
}
},
"--tab-gui" => {
#[cfg(feature = "gui")]
{
opt.app_mode = AppMode::GUI;
opt.gui_mode = GuiMode::Tab;
}
#[cfg(not(feature = "gui"))]
{
eprintln!("\"gui\" feature is not enabled for this build.");
std::process::exit(1);
}
},
"--smi" => {
#[cfg(feature = "tui")]
{
opt.app_mode = AppMode::SMI;
}
#[cfg(not(feature = "tui"))]
{
eprintln!("\"tui\" feature is not enabled for this build.");
std::process::exit(1);
}
},
"--pci" => {
let s = args.get(idx+1).unwrap_or_else(|| {
eprintln!("missing argument: \"--pci <String>\"");
std::process::exit(1);
});
opt.pci = {
let pci = s.parse::<PCI::BUS_INFO>().unwrap_or_else(|_| {
eprintln!("Failed to parse from {s:?} to `PCI::BUS_INFO`");
std::process::exit(1);
});
Some(pci)
};
skip = true;
},
"-l" | "--list" => {
opt.dump_mode = DumpMode::List;
},
"--apu" | "--select-apu" => {
opt.select_apu = true;
},
"-n" => {
if let Some(val_str) = args.get(idx+1) {
opt.json_iterations = val_str.parse::<u32>().unwrap();
skip = true;
} else {
eprintln!("missing argument: \"-n <u32>\"");
std::process::exit(1);
}
},
"-V" | "--version" => {
opt.dump_mode = DumpMode::Version;
},
"-h" | "--help" => {
println!("{HELP_MSG}");
std::process::exit(0);
},
"-p" | "--process" => {
opt.dump_mode = DumpMode::Process;
},
"--pp-table" | "--pp_table" => {
opt.dump_mode = DumpMode::PPTable;
},
"--single" | "--single-gpu" => {
opt.single_gpu = true;
},
"--no-pc" => {
opt.no_pc = true;
},
"-gm" | "--gpu-metrics" | "--gpu_metrics" => {
opt.opt_dump_mode = OptDumpMode::GpuMetrics;
},
"--decode-gm" | "--decode-gpu-metrics" => {
opt.decode_gpu_metrics = args.get(idx+1).map(|s| s.to_string());
if opt.decode_gpu_metrics.is_none() {
eprintln!("missing argument: \"--decode-gm/--decode-gpu-metrics <Path>\"");
std::process::exit(1);
}
skip = true;
},
"--drm-info" | "--drm_info" => {
opt.opt_dump_mode = OptDumpMode::DrmInfo;
},
"--dark" | "--dark-mode" => {
opt.is_dark_mode = Some(true);
},
"--light" | "--light-mode" => {
opt.is_dark_mode = Some(false);
},
"--hide-fdinfo" => opt.hide_fdinfo = true,
"--gl" | "--opengl" => opt.wgpu_backend = GuiWgpuBackend::Gl,
"--vk" | "--vulkan" => opt.wgpu_backend = GuiWgpuBackend::Vulkan,
"--xdna" => {
opt.dump_mode = DumpMode::Xdna;
},
_ => {
eprintln!("Unknown option: {arg}");
std::process::exit(1);
},
}
}
opt
}
}