#![allow(unused)]
use once_cell::sync::OnceCell;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use tokio::sync::Mutex;
use tonic::transport::{Channel, Endpoint};
use tracing::{debug, warn};
use super::tpu_info_runner;
pub mod tpu_proto {
tonic::include_proto!("tpu.monitoring.runtime");
}
use tpu_proto::MetricRequest;
use tpu_proto::runtime_metric_service_client::RuntimeMetricServiceClient;
const DEFAULT_GRPC_ADDR: &str = "http://localhost:8431";
const CONNECT_TIMEOUT: Duration = Duration::from_millis(500);
const REQUEST_TIMEOUT: Duration = Duration::from_secs(2);
const GRPC_RETRY_INTERVAL: Duration = Duration::from_secs(10);
static GRPC_WAS_AVAILABLE: OnceCell<AtomicBool> = OnceCell::new();
pub mod metric_names {
pub const TOTAL_MEMORY: &str = "tpu.runtime.hbm.memory.total.bytes";
pub const MEMORY_USAGE: &str = "tpu.runtime.hbm.memory.usage.bytes";
pub const DUTY_CYCLE_PCT: &str = "tpu.runtime.tensorcore.dutycycle.percent";
pub const HLO_QUEUE_SIZE: &str = "hlo.queue.size.gauge";
pub const HLO_EXEC_TIMING: &str = "hlo.execution.timing.distribution.microseconds";
}
#[derive(Debug, Clone, Default)]
pub struct TpuUsageMetrics {
pub device_id: i64,
pub memory_usage: u64,
pub total_memory: u64,
pub duty_cycle_pct: f64,
}
#[derive(Debug, Clone, Default)]
pub struct HloQueueSize {
pub device_id: i64,
pub queue_size: i64,
}
#[derive(Debug, Clone, Default)]
pub struct HloExecutionTiming {
pub device_id: i64,
pub mean_us: f64,
pub p50_us: f64,
pub p90_us: f64,
pub p95_us: f64,
pub p999_us: f64,
}
static GRPC_CHANNEL: OnceCell<Arc<Mutex<Option<Channel>>>> = OnceCell::new();
async fn get_channel() -> Option<Channel> {
let channel_holder = GRPC_CHANNEL.get_or_init(|| Arc::new(Mutex::new(None)));
let mut guard = channel_holder.lock().await;
if let Some(ref channel) = *guard {
return Some(channel.clone());
}
let channel_result = create_channel().await;
match channel_result {
Ok(channel) => {
*guard = Some(channel.clone());
Some(channel)
}
Err(e) => {
debug!("Failed to create gRPC channel: {}", e);
None
}
}
}
async fn create_channel() -> Result<Channel, tonic::transport::Error> {
Endpoint::from_static(DEFAULT_GRPC_ADDR)
.connect_timeout(CONNECT_TIMEOUT)
.timeout(REQUEST_TIMEOUT)
.connect()
.await
}
async fn clear_channel() {
if let Some(channel_holder) = GRPC_CHANNEL.get() {
let mut guard = channel_holder.lock().await;
*guard = None;
}
}
async fn fetch_metric(
client: &mut RuntimeMetricServiceClient<Channel>,
metric_name: &str,
) -> Option<Vec<(i64, MetricValue)>> {
let request = tonic::Request::new(MetricRequest {
metric_name: metric_name.to_string(),
skip_node_aggregation: false,
});
match client.get_runtime_metric(request).await {
Ok(response) => {
let metric = response.into_inner().metric?;
let mut results = Vec::new();
for m in metric.metrics {
let device_id = m
.attribute
.as_ref()
.and_then(|attr| attr.value.as_ref())
.and_then(|v| match v.attr.as_ref()? {
tpu_proto::attr_value::Attr::IntAttr(i) => Some(*i),
_ => None,
})
.unwrap_or(0);
if let Some(tpu_proto::metric::Measure::Gauge(gauge)) = m.measure {
let value = match gauge.value {
Some(tpu_proto::gauge::Value::AsInt(i)) => MetricValue::Int(i),
Some(tpu_proto::gauge::Value::AsDouble(d)) => MetricValue::Double(d),
_ => continue,
};
results.push((device_id, value));
}
}
results.sort_by_key(|(id, _)| *id);
Some(results)
}
Err(e) => {
debug!("Failed to fetch metric '{}': {}", metric_name, e);
None
}
}
}
#[derive(Debug, Clone)]
enum MetricValue {
Int(i64),
Double(f64),
}
impl MetricValue {
fn as_u64(&self) -> u64 {
match self {
MetricValue::Int(i) => (*i).max(0) as u64,
MetricValue::Double(d) => (*d).max(0.0) as u64,
}
}
fn as_f64(&self) -> f64 {
match self {
MetricValue::Int(i) => *i as f64,
MetricValue::Double(d) => *d,
}
}
}
fn update_grpc_status(available: bool) {
let was_available = GRPC_WAS_AVAILABLE.get_or_init(|| AtomicBool::new(false));
let prev = was_available.swap(available, Ordering::Relaxed);
if prev != available {
tpu_info_runner::get_runner().set_grpc_available(available);
if available {
debug!("gRPC server became available - switching to native metrics");
} else {
debug!("gRPC server unavailable - falling back to CLI polling");
}
}
}
pub async fn get_tpu_metrics_grpc() -> Option<Vec<TpuUsageMetrics>> {
let channel = match get_channel().await {
Some(ch) => ch,
None => {
update_grpc_status(false);
return None;
}
};
let mut client = RuntimeMetricServiceClient::new(channel);
let totals = match fetch_metric(&mut client, metric_names::TOTAL_MEMORY).await {
Some(t) => t,
None => {
update_grpc_status(false);
clear_channel().await;
return None;
}
};
let usages = match fetch_metric(&mut client, metric_names::MEMORY_USAGE).await {
Some(u) => u,
None => {
update_grpc_status(false);
clear_channel().await;
return None;
}
};
let duty_cycles = fetch_metric(&mut client, metric_names::DUTY_CYCLE_PCT)
.await
.unwrap_or_default();
if totals.len() != usages.len() {
warn!(
"Metric count mismatch: totals={}, usages={}",
totals.len(),
usages.len()
);
update_grpc_status(false);
clear_channel().await;
return None;
}
let mut results = Vec::with_capacity(totals.len());
for ((device_id, total), (_, usage)) in totals.iter().zip(usages.iter()) {
let duty_cycle = duty_cycles
.iter()
.find(|(id, _)| *id == *device_id)
.map(|(_, v)| v.as_f64())
.unwrap_or(0.0);
results.push(TpuUsageMetrics {
device_id: *device_id,
memory_usage: usage.as_u64(),
total_memory: total.as_u64(),
duty_cycle_pct: duty_cycle.clamp(0.0, 100.0),
});
}
if results.is_empty() {
update_grpc_status(false);
None
} else {
update_grpc_status(true);
Some(results)
}
}
pub async fn is_grpc_server_available() -> bool {
get_channel().await.is_some()
}
pub fn get_tpu_metrics_grpc_sync() -> Option<Vec<TpuUsageMetrics>> {
match tokio::runtime::Handle::try_current() {
Ok(handle) => {
tokio::task::block_in_place(|| handle.block_on(get_tpu_metrics_grpc()))
}
_ => {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.ok()?;
rt.block_on(get_tpu_metrics_grpc())
}
}
}
fn extract_device_ordinal(attr: &tpu_proto::Attribute) -> Option<i64> {
let value = attr.value.as_ref()?;
let kvlist = match value.attr.as_ref()? {
tpu_proto::attr_value::Attr::KvlistAttr(kv) => kv,
_ => return None,
};
for kv_attr in &kvlist.attributes {
if kv_attr.key == "device_ordinal"
&& let Some(ref val) = kv_attr.value
{
match &val.attr {
Some(tpu_proto::attr_value::Attr::StringAttr(s)) => {
return s.parse().ok();
}
Some(tpu_proto::attr_value::Attr::IntAttr(i)) => {
return Some(*i);
}
_ => {}
}
}
}
None
}
fn calculate_percentile(
target_count: i64,
total_count: i64,
bucket_counts: &[i64],
scale: f64,
growth_factor: f64,
) -> f64 {
if total_count == 0 || bucket_counts.is_empty() {
return 0.0;
}
let mut cumulative = 0i64;
for (i, &count) in bucket_counts.iter().enumerate() {
cumulative += count;
if cumulative >= target_count {
let lower = if i == 0 {
0.0
} else {
scale * growth_factor.powi(i as i32 - 1)
};
let upper = scale * growth_factor.powi(i as i32);
return (lower + upper) / 2.0;
}
}
scale * growth_factor.powi(bucket_counts.len() as i32 - 1)
}
pub async fn get_hlo_queue_size() -> Option<Vec<HloQueueSize>> {
let channel = get_channel().await?;
let mut client = RuntimeMetricServiceClient::new(channel);
let request = tonic::Request::new(MetricRequest {
metric_name: metric_names::HLO_QUEUE_SIZE.to_string(),
skip_node_aggregation: false,
});
let response = match client.get_runtime_metric(request).await {
Ok(resp) => resp,
Err(e) => {
debug!("Failed to fetch HLO queue size: {}", e);
return None;
}
};
let metric = response.into_inner().metric?;
let mut results = Vec::new();
for m in metric.metrics {
let device_id = m
.attribute
.as_ref()
.and_then(extract_device_ordinal)
.unwrap_or(-1);
let device_id = if device_id < 0 {
results.len() as i64
} else {
device_id
};
if let Some(tpu_proto::metric::Measure::Gauge(gauge)) = m.measure {
let queue_size = match gauge.value {
Some(tpu_proto::gauge::Value::AsInt(i)) => i,
Some(tpu_proto::gauge::Value::AsDouble(d)) => d as i64,
_ => continue,
};
results.push(HloQueueSize {
device_id,
queue_size,
});
}
}
results.sort_by_key(|r| r.device_id);
if results.is_empty() {
None
} else {
Some(results)
}
}
pub async fn get_hlo_execution_timing() -> Option<Vec<HloExecutionTiming>> {
let channel = get_channel().await?;
let mut client = RuntimeMetricServiceClient::new(channel);
let request = tonic::Request::new(MetricRequest {
metric_name: metric_names::HLO_EXEC_TIMING.to_string(),
skip_node_aggregation: false,
});
let response = match client.get_runtime_metric(request).await {
Ok(resp) => resp,
Err(e) => {
debug!("Failed to fetch HLO execution timing: {}", e);
return None;
}
};
let metric = response.into_inner().metric?;
let mut results = Vec::new();
for m in metric.metrics {
let device_id = m
.attribute
.as_ref()
.and_then(extract_device_ordinal)
.unwrap_or(-1);
if device_id < 0 {
continue;
}
if let Some(tpu_proto::metric::Measure::Distribution(dist)) = m.measure {
let count = dist.count;
let mean = dist.mean;
let bucket_counts = &dist.bucket_counts;
let (scale, growth_factor) = dist
.bucket_options
.as_ref()
.and_then(|opts| opts.options.as_ref())
.and_then(|o| match o {
tpu_proto::distribution::bucket_options::Options::ExponentialBuckets(exp) => {
Some((exp.scale, exp.growth_factor))
}
_ => None,
})
.unwrap_or((1.0, 2.0));
let p50 = calculate_percentile(
(count as f64 * 0.5) as i64,
count,
bucket_counts,
scale,
growth_factor,
);
let p90 = calculate_percentile(
(count as f64 * 0.9) as i64,
count,
bucket_counts,
scale,
growth_factor,
);
let p95 = calculate_percentile(
(count as f64 * 0.95) as i64,
count,
bucket_counts,
scale,
growth_factor,
);
let p999 = calculate_percentile(
(count as f64 * 0.999) as i64,
count,
bucket_counts,
scale,
growth_factor,
);
results.push(HloExecutionTiming {
device_id,
mean_us: mean,
p50_us: p50,
p90_us: p90,
p95_us: p95,
p999_us: p999,
});
}
}
results.sort_by_key(|r| r.device_id);
if results.is_empty() {
None
} else {
Some(results)
}
}
pub fn get_hlo_queue_size_sync() -> Option<Vec<HloQueueSize>> {
match tokio::runtime::Handle::try_current() {
Ok(handle) => tokio::task::block_in_place(|| handle.block_on(get_hlo_queue_size())),
_ => {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.ok()?;
rt.block_on(get_hlo_queue_size())
}
}
}
pub fn get_hlo_execution_timing_sync() -> Option<Vec<HloExecutionTiming>> {
match tokio::runtime::Handle::try_current() {
Ok(handle) => tokio::task::block_in_place(|| handle.block_on(get_hlo_execution_timing())),
_ => {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.ok()?;
rt.block_on(get_hlo_execution_timing())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_grpc_connection() {
let result = get_tpu_metrics_grpc().await;
println!("gRPC metrics result: {result:?}");
}
}