Skip to content

Commit

Permalink
Merge pull request #39 from navidys/class_sas_phy
Browse files Browse the repository at this point in the history
added /sys/class/sas_phy
  • Loading branch information
navidys authored Dec 7, 2024
2 parents 3db73af + bb553cb commit 4ade867
Show file tree
Hide file tree
Showing 4 changed files with 363 additions and 0 deletions.
18 changes: 18 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,24 @@ Supported Features
* expander-*
* end_device-*

*`/sys/class/sas_phy/<NAME>/`
* sas_address
* device/
* port
* device_type
* initiator_port_protocols
* invalid_dword_count
* loss_of_dword_sync_count
* maximum_linkrate
* maximum_linkrate_hw
* minimum_linkrate
* minimum_linkrate_hw
* negotiated_linkrate
* phy_identifier
* phy_reset_problem_count
* running_disparity_error_count
* target_port_protocols

*`/sys/class/scsi_tape/<NAME>/statistics`
* write_ns
* read_byte_cnt
Expand Down
13 changes: 13 additions & 0 deletions examples/sas_phy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use procsys::sysfs::class_sas_phy;

fn main() {
let sasphys = class_sas_phy::collect().expect("sas phys information");

match serde_json::to_string_pretty(&sasphys) {
Ok(output) => println!("{}", output),
Err(err) => {
log::error!("{}", err);
std::process::exit(1);
}
}
}
331 changes: 331 additions & 0 deletions src/sysfs/class_sas_phy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
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 {
/// /sys/class/sas_phy/<Name>/sas_address
pub sas_address: String,

/// /sys/class/sas_phy/<Name>/device/port
pub sas_port: String,

/// /sys/class/sas_phy/<Name>/device_type
pub device_type: String,

/// /sys/class/sas_phy/<Name>/initiator_port_protocols
pub initiator_port_protocols: Vec<String>,

/// /sys/class/sas_phy/<Name>/invalid_dword_count
pub invalid_dword_count: i64,

/// /sys/class/sas_phy/<Name>/loss_of_dword_sync_count
pub loss_of_dword_sync_count: i64,

/// /sys/class/sas_phy/<Name>/maximum_linkrate
pub maximum_linkrate: f64,

/// /sys/class/sas_phy/<Name>/maximum_linkrate_hw
pub maximum_linkrate_hw: f64,

/// /sys/class/sas_phy/<Name>/minimum_linkrate
pub minimum_linkrate: f64,

/// /sys/class/sas_phy/<Name>/minimum_linkrate_hw
pub minimum_linkrate_hw: f64,

/// /sys/class/sas_phy/<Name>/negotiated_linkrate
pub negotiated_linkrate: f64,

/// /sys/class/sas_phy/<Name>/phy_identifier
pub phy_identifier: String,

/// /sys/class/sas_phy/<Name>/phy_reset_problem_count
pub phy_reset_problem_count: i64,

/// /sys/class/sas_phy/<Name>/running_disparity_error_count
pub running_disparity_error_count: i64,

/// /sys/class/sas_phy/<Name>/target_port_protocols
pub target_port_protocols: Vec<String>,
}

impl SASPhy {
fn new() -> Self {
Default::default()
}
}

/// collects sas phys information
/// # Example
/// ```
/// use procsys::sysfs::class_sas_phy;
///
/// let sas_phys = class_sas_phy::collect().expect("sas phys information");
/// let json_output = serde_json::to_string_pretty(&sas_phys).unwrap();
/// println!("{}", json_output);
///
/// ```
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);

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 re_port = match Regex::new(r"^port-[0-9:]+$") {
Ok(r) => r,
Err(err) => return Err(MetricError::RegexError(err)),
};
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),
}
}
}
}
1 change: 1 addition & 0 deletions src/sysfs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod class_nvme;
pub mod class_power_supply;
pub mod class_sas_device;
pub mod class_sas_host;
pub mod class_sas_phy;
pub mod class_sas_port;
pub mod class_scsi_tape;
pub mod class_thermal;
Expand Down

0 comments on commit 4ade867

Please sign in to comment.