use regex::Regex;
use serde::Serialize;
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
};
use crate::{
error::{CollectResult, MetricError},
utils,
};
#[derive(Debug, Serialize, Clone, Default)]
pub struct SASPhy {
pub sas_address: String,
pub sas_port: String,
pub device_type: String,
pub initiator_port_protocols: Vec<String>,
pub invalid_dword_count: i64,
pub loss_of_dword_sync_count: i64,
pub maximum_linkrate: f64,
pub maximum_linkrate_hw: f64,
pub minimum_linkrate: f64,
pub minimum_linkrate_hw: f64,
pub negotiated_linkrate: f64,
pub phy_identifier: String,
pub phy_reset_problem_count: i64,
pub running_disparity_error_count: i64,
pub target_port_protocols: Vec<String>,
}
impl SASPhy {
fn new() -> Self {
Default::default()
}
}
pub fn collect() -> CollectResult<HashMap<String, SASPhy>> {
collect_from("/sys/class/sas_phy/")
}
fn collect_from(dirname: &str) -> CollectResult<HashMap<String, SASPhy>> {
let mut sas_phys: HashMap<String, SASPhy> = HashMap::new();
let sas_phy_path = PathBuf::from(dirname);
let re_port = match Regex::new(r"^port-[0-9:]+$") {
Ok(r) => r,
Err(err) => return Err(MetricError::RegexError(err)),
};
for sas_phy_item in utils::list_dir_content(&sas_phy_path, "", "sas_phy") {
let mut sas_phy = SASPhy::new();
let mut sas_phy_item_path = sas_phy_path.clone();
sas_phy_item_path.push(&sas_phy_item);
for phy_info in utils::list_dir_content(&sas_phy_item_path, "", &sas_phy_item) {
match phy_info.as_str() {
"sas_address" => {
sas_phy.sas_address =
utils::collect_info_string("sas_address", &sas_phy_item_path)?
.unwrap_or_default();
}
"device" => {
let mut sas_phy_ports_path = sas_phy_item_path.clone();
sas_phy_ports_path.push("device");
sas_phy_ports_path.push("port");
sas_phy.sas_port = match fs::read_link(&sas_phy_ports_path) {
Ok(p) => {
let port = p
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
if re_port.is_match(port) {
port.to_string()
} else {
"".to_string()
}
}
Err(err) => return Err(MetricError::IOError(sas_phy_ports_path, err)),
};
}
"device_type" => {
sas_phy.device_type =
utils::collect_info_string("device_type", &sas_phy_item_path)?
.unwrap_or_default();
}
"initiator_port_protocols" => {
let port_protocols =
utils::collect_info_string("initiator_port_protocols", &sas_phy_item_path)?
.unwrap_or_default();
let initiator_port_protocols: Vec<String> = port_protocols
.trim()
.replace(", ", ",")
.split(',')
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
sas_phy.initiator_port_protocols = initiator_port_protocols
}
"invalid_dword_count" => {
sas_phy.invalid_dword_count =
utils::collect_info_i64("invalid_dword_count", &sas_phy_item_path)?
.unwrap_or_default();
}
"loss_of_dword_sync_count" => {
sas_phy.loss_of_dword_sync_count =
utils::collect_info_i64("loss_of_dword_sync_count", &sas_phy_item_path)?
.unwrap_or_default();
}
"maximum_linkrate" => {
let linkrate = get_linkrate("maximum_linkrate", &sas_phy_item_path)?;
sas_phy.maximum_linkrate = linkrate;
}
"maximum_linkrate_hw" => {
let linkrate = get_linkrate("maximum_linkrate_hw", &sas_phy_item_path)?;
sas_phy.maximum_linkrate_hw = linkrate;
}
"minimum_linkrate" => {
let linkrate = get_linkrate("minimum_linkrate", &sas_phy_item_path)?;
sas_phy.minimum_linkrate = linkrate;
}
"minimum_linkrate_hw" => {
let linkrate = get_linkrate("minimum_linkrate_hw", &sas_phy_item_path)?;
sas_phy.minimum_linkrate_hw = linkrate;
}
"negotiated_linkrate" => {
let linkrate = get_linkrate("negotiated_linkrate", &sas_phy_item_path)?;
sas_phy.negotiated_linkrate = linkrate;
}
"phy_identifier" => {
sas_phy.phy_identifier =
utils::collect_info_string("phy_identifier", &sas_phy_item_path)?
.unwrap_or_default();
}
"phy_reset_problem_count" => {
sas_phy.phy_reset_problem_count =
utils::collect_info_i64("phy_reset_problem_count", &sas_phy_item_path)?
.unwrap_or_default();
}
"running_disparity_error_count" => {
sas_phy.running_disparity_error_count = utils::collect_info_i64(
"running_disparity_error_count",
&sas_phy_item_path,
)?
.unwrap_or_default();
}
"target_port_protocols" => {
let port_protocols =
utils::collect_info_string("target_port_protocols", &sas_phy_item_path)?
.unwrap_or_default();
let target_port_protocols: Vec<String> = port_protocols
.trim()
.replace(", ", ",")
.split(',')
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
sas_phy.target_port_protocols = target_port_protocols
}
_ => {}
}
}
sas_phys.insert(sas_phy_item, sas_phy);
}
Ok(sas_phys)
}
fn get_linkrate(link: &str, phy_path: &Path) -> CollectResult<f64> {
let linkrate = utils::collect_info_string(link, phy_path)?.unwrap_or_default();
let linkrate_items: Vec<String> = linkrate
.trim()
.split(' ')
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
if linkrate_items.is_empty() {
return Ok(0.0);
}
let linkrate = match linkrate_items[0].parse::<f64>() {
Ok(f) => f,
Err(err) => {
return Err(MetricError::ParseFloatError(
linkrate_items[0].to_owned(),
err,
))
}
};
Ok(linkrate)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sas_phy_information() {
let sas_phys =
collect_from("test_data/fixtures/sys/class/sas_phy/").expect("sas phys information");
for (phy_name, phy_info) in sas_phys {
match phy_name.as_str() {
"phy-11:0:2" => {
assert_eq!(phy_info.sas_address, "0x5000ccab0200947e");
assert_eq!(phy_info.sas_port, "port-11:0:0");
assert_eq!(phy_info.device_type, "edge expander");
assert_eq!(phy_info.initiator_port_protocols, ["smp"]);
assert_eq!(phy_info.invalid_dword_count, 18);
assert_eq!(phy_info.loss_of_dword_sync_count, 1);
assert_eq!(phy_info.maximum_linkrate, 12.0);
assert_eq!(phy_info.maximum_linkrate_hw, 12.0);
assert_eq!(phy_info.minimum_linkrate, 1.5);
assert_eq!(phy_info.minimum_linkrate_hw, 1.5);
assert_eq!(phy_info.negotiated_linkrate, 6.0);
assert_eq!(phy_info.phy_identifier, "2");
assert_eq!(phy_info.phy_reset_problem_count, 0);
assert_eq!(phy_info.running_disparity_error_count, 18);
assert_eq!(phy_info.target_port_protocols, ["smp"]);
}
"phy-11:0:4" => {}
"phy-11:0:6" => {}
"phy-11:7" => {}
"phy-11:8" => {
assert_eq!(phy_info.sas_address, "0x500062b2047b51c4");
assert_eq!(phy_info.sas_port, "port-11:0");
assert_eq!(phy_info.device_type, "end device");
assert_eq!(phy_info.initiator_port_protocols.len(), 3);
assert_eq!(phy_info.invalid_dword_count, 0);
assert_eq!(phy_info.loss_of_dword_sync_count, 0);
assert_eq!(phy_info.maximum_linkrate, 12.0);
assert_eq!(phy_info.maximum_linkrate_hw, 12.0);
assert_eq!(phy_info.minimum_linkrate, 3.0);
assert_eq!(phy_info.minimum_linkrate_hw, 1.5);
assert_eq!(phy_info.negotiated_linkrate, 12.0);
assert_eq!(phy_info.phy_identifier, "8");
assert_eq!(phy_info.phy_reset_problem_count, 0);
assert_eq!(phy_info.running_disparity_error_count, 0);
assert_eq!(phy_info.target_port_protocols, ["none"]);
}
"phy-11:9" => {}
"phy-11:10" => {}
"phy-11:11" => {
assert_eq!(phy_info.sas_address, "0x500062b2047b51c4");
assert_eq!(phy_info.sas_port, "port-11:0");
assert_eq!(phy_info.device_type, "end device");
assert_eq!(phy_info.initiator_port_protocols.len(), 3);
assert_eq!(phy_info.invalid_dword_count, 0);
assert_eq!(phy_info.loss_of_dword_sync_count, 0);
assert_eq!(phy_info.maximum_linkrate, 12.0);
assert_eq!(phy_info.maximum_linkrate_hw, 12.0);
assert_eq!(phy_info.minimum_linkrate, 3.0);
assert_eq!(phy_info.minimum_linkrate_hw, 1.5);
assert_eq!(phy_info.negotiated_linkrate, 12.0);
assert_eq!(phy_info.phy_identifier, "11");
assert_eq!(phy_info.phy_reset_problem_count, 0);
assert_eq!(phy_info.running_disparity_error_count, 0);
assert_eq!(phy_info.target_port_protocols, ["none"]);
}
"phy-11:12" => {}
"phy-11:13" => {
assert_eq!(phy_info.sas_address, "0x500062b2047b51c4");
assert_eq!(phy_info.sas_port, "port-11:1");
assert_eq!(phy_info.device_type, "end device");
assert_eq!(phy_info.initiator_port_protocols.len(), 3);
assert_eq!(phy_info.invalid_dword_count, 0);
assert_eq!(phy_info.loss_of_dword_sync_count, 0);
assert_eq!(phy_info.maximum_linkrate, 12.0);
assert_eq!(phy_info.maximum_linkrate_hw, 12.0);
assert_eq!(phy_info.minimum_linkrate, 3.0);
assert_eq!(phy_info.minimum_linkrate_hw, 1.5);
assert_eq!(phy_info.negotiated_linkrate, 6.0);
assert_eq!(phy_info.phy_identifier, "13");
assert_eq!(phy_info.phy_reset_problem_count, 0);
assert_eq!(phy_info.running_disparity_error_count, 0);
assert_eq!(phy_info.target_port_protocols, ["none"]);
}
"phy-11:14" => {}
"phy-11:15" => {}
_ => panic!("invalid sas phy name: {}", phy_name),
}
}
}
}