use crate::device::GpuReader;
use crate::device::process_list::{get_all_processes, merge_gpu_processes};
use crate::device::readers::common_cache::{DetailBuilder, DeviceStaticInfo};
use crate::device::types::{GpuInfo, ProcessInfo};
use crate::utils::{get_hostname, with_global_system};
use chrono::Local;
use luwen_api::ChipDetectOptions;
use luwen_api::chip::{Chip, ChipImpl, Telemetry};
use luwen_def::Arch;
use luwen_pci::detect_chips_silent;
use once_cell::sync::Lazy;
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
#[derive(Debug, Clone, Copy)]
pub enum CollectionMethod {
DeviceFile,
}
pub struct TenstorrentConfig {
pub _primary_method: CollectionMethod,
}
impl Default for TenstorrentConfig {
fn default() -> Self {
Self {
_primary_method: CollectionMethod::DeviceFile,
}
}
}
static TENSTORRENT_STATUS: Mutex<Option<String>> = Mutex::new(None);
#[derive(Clone)]
struct TenstorrentStaticInfo {
total_memory: u64,
tdp_limit: f64,
}
struct CachedChipInfo {
chip: Chip,
static_info: DeviceStaticInfo,
tenstorrent_info: TenstorrentStaticInfo,
}
static INITIALIZED_CHIPS: Lazy<Mutex<Option<Vec<CachedChipInfo>>>> = Lazy::new(|| Mutex::new(None));
pub struct TenstorrentReader {
_config: TenstorrentConfig,
}
impl Default for TenstorrentReader {
fn default() -> Self {
Self::new()
}
}
impl TenstorrentReader {
pub fn new() -> Self {
Self {
_config: TenstorrentConfig::default(),
}
}
#[allow(dead_code)]
pub fn with_config(config: TenstorrentConfig) -> Self {
Self { _config: config }
}
fn ensure_chips_initialized() {
let mut chips_guard = match INITIALIZED_CHIPS.lock() {
Ok(guard) => guard,
Err(e) => {
eprintln!("Failed to acquire lock for Tenstorrent chips: {e}");
return;
}
};
if chips_guard.is_some() {
return;
}
if grayskull_present() {
set_tenstorrent_status(
"Skipped Tenstorrent detection: Grayskull is no longer supported by the luwen 0.8.x backend".to_string(),
);
*chips_guard = Some(Vec::new());
return;
}
let options = ChipDetectOptions {
local_only: true,
..Default::default()
};
let detect_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
detect_chips_silent(options)
}));
let uninit_chips = match detect_result {
Ok(Ok(chips)) => chips,
Ok(Err(e)) => {
set_tenstorrent_status(format!("Failed to detect Tenstorrent chips: {e}"));
return;
}
Err(_) => {
set_tenstorrent_status(
"Skipped Tenstorrent detection: a connected device is unsupported by luwen 0.8.x (e.g. Grayskull, which upstream has sunset)".to_string(),
);
return;
}
};
let cached_chips: Vec<CachedChipInfo> = uninit_chips
.into_iter()
.filter_map(|uninit_chip| {
match uninit_chip.init(&mut |_| Ok::<(), std::convert::Infallible>(())) {
Ok(chip) => {
let (static_info, tenstorrent_info) = extract_static_info(&chip)?;
Some(CachedChipInfo {
chip,
static_info,
tenstorrent_info,
})
}
Err(_) => None, }
})
.collect();
if cached_chips.is_empty() {
set_tenstorrent_status("No Tenstorrent chips detected".to_string());
} else {
clear_tenstorrent_status();
}
*chips_guard = Some(cached_chips);
}
#[allow(dead_code)]
pub fn invalidate_cache() {
match INITIALIZED_CHIPS.lock() {
Ok(mut chips_guard) => {
*chips_guard = None;
}
_ => {
eprintln!("Failed to acquire lock to invalidate Tenstorrent cache");
}
}
}
fn get_npu_processes(&self) -> (Vec<ProcessInfo>, HashSet<u32>) {
(Vec::new(), HashSet::new())
}
}
impl GpuReader for TenstorrentReader {
fn get_gpu_info(&self) -> Vec<GpuInfo> {
Self::ensure_chips_initialized();
let chips_guard = match INITIALIZED_CHIPS.lock() {
Ok(guard) => guard,
Err(e) => {
eprintln!("Failed to acquire lock for Tenstorrent chips: {e}");
return Vec::new();
}
};
let cached_chips = match chips_guard.as_ref() {
Some(chips) => chips,
None => return Vec::new(),
};
let time = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
let hostname = get_hostname();
cached_chips
.iter()
.enumerate()
.filter_map(|(index, cached)| {
create_gpu_info(
&cached.chip,
&cached.static_info,
&cached.tenstorrent_info,
index,
&time,
&hostname,
)
})
.collect()
}
fn get_process_info(&self) -> Vec<ProcessInfo> {
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, UpdateKind};
let (npu_processes, npu_pids) = self.get_npu_processes();
let all_processes = with_global_system(|system| {
system.refresh_processes_specifics(
ProcessesToUpdate::All,
true,
ProcessRefreshKind::everything().with_user(UpdateKind::Always),
);
system.refresh_memory();
get_all_processes(system, &npu_pids)
});
merge_gpu_processes(all_processes, npu_processes)
}
fn get_gpu_processes(&self) -> (Vec<ProcessInfo>, HashSet<u32>) {
self.get_npu_processes()
}
}
fn set_tenstorrent_status(message: String) {
if let Ok(mut status) = TENSTORRENT_STATUS.lock() {
*status = Some(message);
}
}
fn clear_tenstorrent_status() {
if let Ok(mut status) = TENSTORRENT_STATUS.lock() {
*status = None;
}
}
fn grayskull_present() -> bool {
const TT_VENDOR: &str = "0x1e52";
const GRAYSKULL_DEVICE: &str = "0xfaca";
let Ok(entries) = std::fs::read_dir("/sys/bus/pci/devices") else {
return false;
};
for entry in entries.flatten() {
let path = entry.path();
let vendor = std::fs::read_to_string(path.join("vendor")).unwrap_or_default();
if vendor.trim() != TT_VENDOR {
continue;
}
let device = std::fs::read_to_string(path.join("device")).unwrap_or_default();
if device.trim() == GRAYSKULL_DEVICE {
return true;
}
}
false
}
#[allow(dead_code)]
pub fn get_tenstorrent_status_message() -> Option<String> {
TENSTORRENT_STATUS.lock().ok()?.clone()
}
fn extract_static_info(chip: &Chip) -> Option<(DeviceStaticInfo, TenstorrentStaticInfo)> {
let telem = chip.get_telemetry().ok()?;
let board_type = telem.try_board_type().unwrap_or("Unknown");
#[allow(deprecated)]
let arch_name = match telem.arch {
Arch::Grayskull => "Grayskull",
Arch::Wormhole => "Wormhole",
Arch::Blackhole => "Blackhole",
};
let device_name = format!("Tenstorrent {arch_name} {board_type}");
let uuid = Some(telem.board_serial_number_hex());
let mut builder = DetailBuilder::new()
.insert("Board Type", board_type)
.insert("Board ID", telem.board_serial_number_hex())
.insert("ARC FW Version", telem.arc_fw_version())
.insert("ETH FW Version", telem.eth_fw_version())
.insert("FW Date", telem.firmware_date());
if let Ok(Some(device_info)) = chip.get_device_info() {
let pcie_address = format!(
"{:04x}:{:02x}:{:02x}.{:x}",
device_info.domain, device_info.bus, device_info.slot, device_info.function
);
let pcie_link_width = format!("x{}", device_info.pcie_current_link_width());
let pcie_link_gen = format!("{}", device_info.pcie_current_link_gen());
builder = builder
.insert("PCIe Address", &pcie_address)
.insert("PCIe Vendor ID", format!("0x{:04x}", device_info.vendor))
.insert("PCIe Device ID", format!("0x{:04x}", device_info.device_id))
.insert_pci_info(
Some(&pcie_address),
Some(&pcie_link_gen),
Some(&pcie_link_width),
);
}
let ddr_fw_version = if telem.ddr_fw_version != 0 {
Some(format!(
"{}.{}.{}",
(telem.ddr_fw_version >> 16) & 0xFF,
(telem.ddr_fw_version >> 8) & 0xFF,
telem.ddr_fw_version & 0xFF
))
} else {
None
};
builder = builder.insert_optional("DDR FW Version", ddr_fw_version);
let spibootrom_fw_version = if telem.spibootrom_fw_version != 0 {
Some(format!(
"{}.{}.{}",
(telem.spibootrom_fw_version >> 16) & 0xFF,
(telem.spibootrom_fw_version >> 8) & 0xFF,
telem.spibootrom_fw_version & 0xFF
))
} else {
None
};
builder = builder.insert_optional("SPIBOOTROM FW Version", spibootrom_fw_version);
let (total_memory, tdp_limit) = determine_memory_and_tdp(board_type);
let detail = builder.build();
let static_info = DeviceStaticInfo::with_details(device_name, uuid, detail);
let tenstorrent_info = TenstorrentStaticInfo {
total_memory,
tdp_limit,
};
Some((static_info, tenstorrent_info))
}
fn determine_memory_and_tdp(board_type: &str) -> (u64, f64) {
match board_type {
s if s.contains("e75") => (2 * 1024 * 1024 * 1024, 75.0), s if s.contains("e150") => (8 * 1024 * 1024 * 1024, 200.0), s if s.contains("e300") => (12 * 1024 * 1024 * 1024, 300.0), s if s.contains("galaxy") => (32 * 1024 * 1024 * 1024, 200.0), s if s.contains("n150") => (48 * 1024 * 1024 * 1024, 160.0), s if s.contains("n300") => (96 * 1024 * 1024 * 1024, 300.0), _ => (8 * 1024 * 1024 * 1024, 200.0), }
}
fn create_gpu_info(
chip: &Chip,
static_info: &DeviceStaticInfo,
tenstorrent_info: &TenstorrentStaticInfo,
_index: usize,
time: &str,
hostname: &str,
) -> Option<GpuInfo> {
let telem = chip.get_telemetry().ok()?;
let detail = build_device_details(static_info, tenstorrent_info, &telem);
let temperature = telem.asic_temperature().round() as u32;
let power = calculate_power(&telem);
let frequency = telem.ai_clk();
let utilization = estimate_utilization(&telem, tenstorrent_info.tdp_limit);
Some(GpuInfo {
uuid: static_info
.uuid
.clone()
.unwrap_or_else(|| "Unknown".to_string()),
time: time.to_string(),
name: static_info.name.clone(),
device_type: "NPU".to_string(),
host_id: hostname.to_string(),
hostname: hostname.to_string(),
instance: hostname.to_string(),
utilization,
ane_utilization: 0.0,
dla_utilization: None,
tensorcore_utilization: None,
temperature,
used_memory: 0, total_memory: tenstorrent_info.total_memory,
frequency,
power_consumption: power,
gpu_core_count: None,
temperature_threshold_slowdown: None,
temperature_threshold_shutdown: None,
temperature_threshold_max_operating: None,
temperature_threshold_acoustic: None,
performance_state: None,
fan_speed_rpm: None,
numa_node_id: None,
gsp_firmware_mode: None,
gsp_firmware_version: None,
nvlink_remote_devices: Vec::new(),
gpm_metrics: None,
detail,
})
}
fn build_device_details(
static_info: &DeviceStaticInfo,
_tenstorrent_info: &TenstorrentStaticInfo,
telem: &Telemetry,
) -> HashMap<String, String> {
let mut detail = static_info.detail.clone();
detail.insert(
"VDD Voltage".to_string(),
format!("{:.3}V", telem.voltage()),
);
detail.insert("Current".to_string(), format!("{:.2}A", telem.current()));
detail.insert(
"ASIC Temperature".to_string(),
format!("{:.1}°C", telem.asic_temperature()),
);
detail.insert(
"VR Temperature".to_string(),
format!("{:.1}°C", telem.vreg_temperature()),
);
if telem.board_temperature != 0 {
detail.insert(
"Inlet Temperature".to_string(),
format!("{:.1}°C", telem.inlet_temperature()),
);
}
detail.insert("AI Clock".to_string(), format!("{}MHz", telem.ai_clk()));
detail.insert("ARC Clock".to_string(), format!("{}MHz", telem.arc_clk()));
detail.insert("AXI Clock".to_string(), format!("{}MHz", telem.axi_clk()));
detail
.entry("lib_name".to_string())
.or_insert("Luwen".to_string());
if let Some(arc_fw) = detail.get("ARC FW Version") {
detail.insert("lib_version".to_string(), arc_fw.clone());
}
detail
}
fn calculate_power(telem: &Telemetry) -> f64 {
telem.power()
}
fn estimate_utilization(telem: &Telemetry, tdp_limit: f64) -> f64 {
let power = calculate_power(telem);
let power_utilization = (power / tdp_limit * 100.0).min(100.0);
let ai_clk = telem.ai_clk() as f64;
let max_clk = 1200.0; let clock_utilization = (ai_clk / max_clk * 100.0).min(100.0);
let heartbeat = telem.telemetry_heartbeat();
let heartbeat_active = if heartbeat > 0 { 1.0 } else { 0.0 };
(power_utilization * 0.6 + clock_utilization * 0.3 + heartbeat_active * 10.0).min(100.0)
}