mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-10 07:06:53 +00:00
scanner status command(1)
Signed-off-by: mujunxiang <1948535941@qq.com>
This commit is contained in:
@@ -37,21 +37,21 @@ pub struct MemStats {
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ServerProperties {
|
||||
state: String,
|
||||
endpoint: String,
|
||||
scheme: String,
|
||||
uptime: u64,
|
||||
version: String,
|
||||
commit_id: String,
|
||||
network: HashMap<String, String>,
|
||||
disks: Vec<StorageDisk>,
|
||||
pool_number: i32,
|
||||
pool_numbers: Vec<i32>,
|
||||
mem_stats: MemStats,
|
||||
max_procs: u64,
|
||||
num_cpu: u64,
|
||||
runtime_version: String,
|
||||
rustfs_env_vars: HashMap<String, String>,
|
||||
pub state: String,
|
||||
pub endpoint: String,
|
||||
pub scheme: String,
|
||||
pub uptime: u64,
|
||||
pub version: String,
|
||||
pub commit_id: String,
|
||||
pub network: HashMap<String, String>,
|
||||
pub disks: Vec<StorageDisk>,
|
||||
pub pool_number: i32,
|
||||
pub pool_numbers: Vec<i32>,
|
||||
pub mem_stats: MemStats,
|
||||
pub max_procs: u64,
|
||||
pub num_cpu: u64,
|
||||
pub runtime_version: String,
|
||||
pub rustfs_env_vars: HashMap<String, String>,
|
||||
}
|
||||
|
||||
async fn is_server_resolvable(endpoint: &Endpoint) -> Result<()> {
|
||||
|
||||
+1
-11
@@ -27,6 +27,7 @@ use crate::{
|
||||
use endpoint::Endpoint;
|
||||
use futures::StreamExt;
|
||||
use local::LocalDisk;
|
||||
use madmin::info_commands::DiskMetrics;
|
||||
use protos::proto_gen::node_service::{
|
||||
node_service_client::NodeServiceClient, ReadAtRequest, ReadAtResponse, WriteRequest, WriteResponse,
|
||||
};
|
||||
@@ -35,7 +36,6 @@ use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
any::Any,
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
io::{Cursor, SeekFrom},
|
||||
path::PathBuf,
|
||||
@@ -521,16 +521,6 @@ pub struct DiskInfo {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DiskMetrics {
|
||||
api_calls: HashMap<String, u64>,
|
||||
total_waiting: u32,
|
||||
total_errors_availability: u64,
|
||||
total_errors_timeout: u64,
|
||||
total_writes: u64,
|
||||
total_deletes: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Info {
|
||||
pub total: u64,
|
||||
|
||||
@@ -13,6 +13,7 @@ use std::{
|
||||
};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use lazy_static::lazy_static;
|
||||
use rand::Rng;
|
||||
use rmp_serde::{Deserializer, Serializer};
|
||||
@@ -138,7 +139,7 @@ async fn run_data_scanner() {
|
||||
loop {
|
||||
let stop_fn = ScannerMetrics::log(ScannerMetric::ScanCycle);
|
||||
cycle_info.current = cycle_info.next;
|
||||
cycle_info.started = SystemTime::now();
|
||||
cycle_info.started = Utc::now();
|
||||
{
|
||||
globalScannerMetrics.write().await.set_cycle(Some(cycle_info.clone())).await;
|
||||
}
|
||||
@@ -166,7 +167,7 @@ async fn run_data_scanner() {
|
||||
Ok(_) => {
|
||||
cycle_info.next += 1;
|
||||
cycle_info.current = 0;
|
||||
cycle_info.cycle_completed.push(SystemTime::now());
|
||||
cycle_info.cycle_completed.push(Utc::now());
|
||||
if cycle_info.cycle_completed.len() > DATA_USAGE_UPDATE_DIR_CYCLES as usize {
|
||||
cycle_info.cycle_completed = cycle_info.cycle_completed
|
||||
[cycle_info.cycle_completed.len() - DATA_USAGE_UPDATE_DIR_CYCLES as usize..]
|
||||
@@ -252,8 +253,8 @@ async fn get_cycle_scan_mode(current_cycle: u64, bitrot_start_cycle: u64, bitrot
|
||||
pub struct CurrentScannerCycle {
|
||||
pub current: u64,
|
||||
pub next: u64,
|
||||
pub started: SystemTime,
|
||||
pub cycle_completed: Vec<SystemTime>,
|
||||
pub started: DateTime<Utc>,
|
||||
pub cycle_completed: Vec<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl Default for CurrentScannerCycle {
|
||||
@@ -261,7 +262,7 @@ impl Default for CurrentScannerCycle {
|
||||
Self {
|
||||
current: Default::default(),
|
||||
next: Default::default(),
|
||||
started: SystemTime::now(),
|
||||
started: Utc::now(),
|
||||
cycle_completed: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -285,7 +286,7 @@ impl CurrentScannerCycle {
|
||||
|
||||
// write "started"
|
||||
rmp::encode::write_str(&mut wr, "started")?;
|
||||
rmp::encode::write_uint(&mut wr, system_time_to_timestamp(&self.started))?;
|
||||
rmp::encode::write_sint(&mut wr, system_time_to_timestamp(&self.started))?;
|
||||
|
||||
// write "cycle_completed"
|
||||
rmp::encode::write_str(&mut wr, "cycle_completed")?;
|
||||
@@ -328,14 +329,14 @@ impl CurrentScannerCycle {
|
||||
// self.next = u;
|
||||
// }
|
||||
"started" => {
|
||||
let u: u64 = rmp::decode::read_int(&mut cur)?;
|
||||
let u: i64 = rmp::decode::read_int(&mut cur)?;
|
||||
let started = timestamp_to_system_time(u);
|
||||
self.started = started;
|
||||
}
|
||||
"cycleCompleted" => {
|
||||
let mut buf = Vec::new();
|
||||
let _ = cur.read_to_end(&mut buf)?;
|
||||
let u: Vec<SystemTime> =
|
||||
let u: Vec<DateTime<Utc>> =
|
||||
Deserialize::deserialize(&mut Deserializer::new(&buf[..])).expect("Deserialization failed");
|
||||
self.cycle_completed = u;
|
||||
}
|
||||
@@ -348,13 +349,13 @@ impl CurrentScannerCycle {
|
||||
}
|
||||
|
||||
// 将 SystemTime 转换为时间戳
|
||||
fn system_time_to_timestamp(time: &SystemTime) -> u64 {
|
||||
time.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs()
|
||||
fn system_time_to_timestamp(time: &DateTime<Utc>) -> i64 {
|
||||
time.timestamp_micros()
|
||||
}
|
||||
|
||||
// 将时间戳转换为 SystemTime
|
||||
fn timestamp_to_system_time(timestamp: u64) -> SystemTime {
|
||||
UNIX_EPOCH + std::time::Duration::new(timestamp, 0)
|
||||
fn timestamp_to_system_time(timestamp: i64) -> DateTime<Utc> {
|
||||
DateTime::from_timestamp_micros(timestamp).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use common::globals::GLOBAL_Local_Node_Name;
|
||||
use common::last_minute::{AccElem, LastMinuteLatency};
|
||||
use lazy_static::lazy_static;
|
||||
use madmin::metrics::ScannerMetrics as M_ScannerMetrics;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
@@ -125,7 +128,7 @@ pub type TimeSizeFn = Arc<dyn Fn(u64) -> Pin<Box<dyn Future<Output = ()> + Send>
|
||||
pub type TimeFn = Arc<dyn Fn() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync + 'static>;
|
||||
|
||||
pub struct ScannerMetrics {
|
||||
operations: Vec<AtomicU32>,
|
||||
operations: Vec<AtomicU64>,
|
||||
latency: Vec<LockedLastMinuteLatency>,
|
||||
cycle_info: RwLock<Option<CurrentScannerCycle>>,
|
||||
current_paths: HashMap<String, String>,
|
||||
@@ -140,7 +143,7 @@ impl Default for ScannerMetrics {
|
||||
impl ScannerMetrics {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
operations: (0..ScannerMetric::Last as usize).map(|_| AtomicU32::new(0)).collect(),
|
||||
operations: (0..ScannerMetric::Last as usize).map(|_| AtomicU64::new(0)).collect(),
|
||||
latency: vec![LockedLastMinuteLatency::default(); ScannerMetric::LastRealtime as usize],
|
||||
cycle_info: RwLock::new(None),
|
||||
current_paths: HashMap::new(),
|
||||
@@ -195,6 +198,39 @@ impl ScannerMetrics {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_cycle(&self) -> Option<CurrentScannerCycle> {
|
||||
let r = self.cycle_info.read().await;
|
||||
if let Some(c) = r.as_ref() {
|
||||
return Some(c.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn get_current_paths(&self) -> Vec<String> {
|
||||
let mut res = Vec::new();
|
||||
let prefix = format!("{}/", GLOBAL_Local_Node_Name.read().await);
|
||||
self.current_paths.iter().for_each(|(k, v)| {
|
||||
res.push(format!("{}/{}/{}", prefix, k, v));
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
pub async fn report(&self) -> M_ScannerMetrics {
|
||||
let mut m = M_ScannerMetrics::default();
|
||||
if let Some(cycle) = self.get_cycle().await {
|
||||
m.current_cycle = cycle.current;
|
||||
m.cycles_completed_at = cycle.cycle_completed;
|
||||
m.current_started = cycle.started;
|
||||
}
|
||||
m.collected_at = Utc::now();
|
||||
m.active_paths = self.get_current_paths().await;
|
||||
for (i, v) in self.operations.iter().enumerate() {
|
||||
m.life_time_ops.insert(i.to_string(), v.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
m
|
||||
}
|
||||
}
|
||||
|
||||
pub type CloseDiskFn = Arc<dyn Fn() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync + 'static>;
|
||||
|
||||
@@ -131,7 +131,7 @@ impl Default for HealStartSuccess {
|
||||
|
||||
pub type HealStopSuccess = HealStartSuccess;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct HealingDisk {
|
||||
pub id: String,
|
||||
pub heal_id: String,
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod error;
|
||||
mod file_meta;
|
||||
pub mod global;
|
||||
pub mod heal;
|
||||
pub mod metrics_realtime;
|
||||
pub mod notification_sys;
|
||||
pub mod peer;
|
||||
mod peer_rest_client;
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use chrono::Utc;
|
||||
use common::globals::{GLOBAL_Local_Node_Name, GLOBAL_Rustfs_Addr};
|
||||
use madmin::metrics::{DiskIOStats, DiskMetric, RealtimeMetrics};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
admin_server_info::get_local_server_property,
|
||||
heal::{
|
||||
data_scanner_metric::globalScannerMetrics,
|
||||
heal_commands::{DRIVE_STATE_OK, DRIVE_STATE_UNFORMATTED},
|
||||
},
|
||||
new_object_layer_fn,
|
||||
store_api::StorageAPI,
|
||||
utils::os::get_drive_stats,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct CollectMetricsOpts {
|
||||
pub hosts: HashSet<String>,
|
||||
pub disks: HashSet<String>,
|
||||
pub job_id: String,
|
||||
pub dep_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct MetricType(u32);
|
||||
|
||||
impl MetricType {
|
||||
// 定义一些常量
|
||||
pub const NONE: MetricType = MetricType(0);
|
||||
pub const SCANNER: MetricType = MetricType(1 << 0);
|
||||
pub const DISK: MetricType = MetricType(1 << 1);
|
||||
pub const OS: MetricType = MetricType(1 << 2);
|
||||
pub const BATCH_JOBS: MetricType = MetricType(1 << 3);
|
||||
pub const SITE_RESYNC: MetricType = MetricType(1 << 4);
|
||||
pub const NET: MetricType = MetricType(1 << 5);
|
||||
pub const MEM: MetricType = MetricType(1 << 6);
|
||||
pub const CPU: MetricType = MetricType(1 << 7);
|
||||
pub const RPC: MetricType = MetricType(1 << 8);
|
||||
|
||||
// MetricsAll must be last.
|
||||
pub const ALL: MetricType = MetricType((1 << 9) - 1);
|
||||
|
||||
pub fn new(t: u32) -> Self {
|
||||
Self(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl MetricType {
|
||||
fn contains(&self, x: &MetricType) -> bool {
|
||||
(self.0 & x.0) == x.0
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn collect_local_metrics(types: MetricType, opts: &CollectMetricsOpts) -> RealtimeMetrics {
|
||||
let mut real_time_metrics = RealtimeMetrics::default();
|
||||
if types.0 == MetricType::NONE.0 {
|
||||
return real_time_metrics;
|
||||
}
|
||||
|
||||
let mut by_host_name = GLOBAL_Rustfs_Addr.read().await.clone();
|
||||
if !opts.hosts.is_empty() {
|
||||
let server = get_local_server_property().await;
|
||||
if opts.hosts.contains(&server.endpoint) {
|
||||
by_host_name = server.endpoint;
|
||||
} else {
|
||||
return real_time_metrics;
|
||||
}
|
||||
}
|
||||
let local_node_name = GLOBAL_Local_Node_Name.read().await.clone();
|
||||
if by_host_name.starts_with(":") && !local_node_name.starts_with(":") {
|
||||
by_host_name = local_node_name;
|
||||
}
|
||||
|
||||
if types.contains(&MetricType::DISK) {
|
||||
let mut aggr = DiskMetric {
|
||||
collected_at: Utc::now(),
|
||||
..Default::default()
|
||||
};
|
||||
for (name, disk) in collect_local_disks_metrics(&opts.disks).await.into_iter() {
|
||||
real_time_metrics.by_disk.insert(name, disk.clone());
|
||||
aggr.merge(&disk);
|
||||
}
|
||||
real_time_metrics.aggregated.disk = Some(aggr);
|
||||
}
|
||||
|
||||
if types.contains(&MetricType::SCANNER) {
|
||||
let metrics = globalScannerMetrics.read().await.report().await;
|
||||
real_time_metrics.aggregated.scanner = Some(metrics);
|
||||
}
|
||||
RealtimeMetrics::default()
|
||||
}
|
||||
|
||||
async fn collect_local_disks_metrics(disks: &HashSet<String>) -> HashMap<String, DiskMetric> {
|
||||
let store = match new_object_layer_fn() {
|
||||
Some(store) => store,
|
||||
None => return HashMap::new(),
|
||||
};
|
||||
|
||||
let mut metrics = HashMap::new();
|
||||
let storage_info = store.local_storage_info().await;
|
||||
for d in storage_info.disks.iter() {
|
||||
if !disks.is_empty() {
|
||||
if !disks.contains(&d.endpoint) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if d.state != *DRIVE_STATE_OK && d.state != *DRIVE_STATE_UNFORMATTED {
|
||||
metrics.insert(
|
||||
d.endpoint.clone(),
|
||||
DiskMetric {
|
||||
n_disks: 1,
|
||||
offline: 1,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut dm = DiskMetric {
|
||||
n_disks: 1,
|
||||
..Default::default()
|
||||
};
|
||||
if d.healing {
|
||||
dm.healing += 1;
|
||||
}
|
||||
|
||||
if let Some(m) = &d.metrics {
|
||||
for (k, v) in m.api_calls.iter() {
|
||||
if *v != 0 {
|
||||
dm.life_time_ops.insert(k.clone(), *v);
|
||||
}
|
||||
}
|
||||
for (k, v) in m.last_minute.iter() {
|
||||
if v.count != 0 {
|
||||
dm.last_minute.operations.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(st) = get_drive_stats(d.major, d.minor) {
|
||||
dm.io_stats = DiskIOStats {
|
||||
read_ios: st.read_ios,
|
||||
read_merges: st.read_merges,
|
||||
read_sectors: st.read_sectors,
|
||||
read_ticks: st.read_ticks,
|
||||
write_ios: st.write_ios,
|
||||
write_merges: st.write_merges,
|
||||
write_sectors: st.write_sectors,
|
||||
write_ticks: st.write_ticks,
|
||||
current_ios: st.current_ios,
|
||||
total_ticks: st.total_ticks,
|
||||
req_ticks: st.req_ticks,
|
||||
discard_ios: st.discard_ios,
|
||||
discard_merges: st.discard_merges,
|
||||
discard_sectors: st.discard_sectors,
|
||||
discard_ticks: st.discard_ticks,
|
||||
flush_ios: st.flush_ios,
|
||||
flush_ticks: st.flush_ticks,
|
||||
};
|
||||
}
|
||||
metrics.insert(d.endpoint.clone(), dm);
|
||||
}
|
||||
|
||||
metrics
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
use common::error::{Error, Result};
|
||||
use madmin::{
|
||||
health::{Cpus, MemInfo, OsInfo, Partitions, ProcInfo, SysConfig, SysErrors, SysService},
|
||||
metrics::{CollectMetricsOpts, MetricType, RealtimeMetrics},
|
||||
metrics::RealtimeMetrics,
|
||||
net::NetInfo,
|
||||
};
|
||||
use protos::{
|
||||
@@ -266,11 +266,13 @@ impl PeerRestClient {
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
let mut buf = Vec::new();
|
||||
opts.serialize(&mut Serializer::new(&mut buf))?;
|
||||
let mut buf_t = Vec::new();
|
||||
t.serialize(&mut Serializer::new(&mut buf_t))?;
|
||||
let mut buf_o = Vec::new();
|
||||
opts.serialize(&mut Serializer::new(&mut buf_o))?;
|
||||
let request = Request::new(GetMetricsRequest {
|
||||
metric_type: t,
|
||||
opts: buf,
|
||||
metric_type: buf_t,
|
||||
opts: buf_o,
|
||||
});
|
||||
|
||||
let response = client.get_metrics(request).await?.into_inner();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::heal::heal_commands::HealingDisk;
|
||||
use crate::heal::heal_ops::HealSequence;
|
||||
use crate::{
|
||||
disk::DiskStore,
|
||||
@@ -8,6 +9,7 @@ use crate::{
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use http::HeaderMap;
|
||||
use madmin::info_commands::DiskMetrics;
|
||||
use rmp_serde::Serializer;
|
||||
use s3s::{dto::StreamingBlob, Body};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -830,8 +832,8 @@ pub struct StorageDisk {
|
||||
pub read_latency: f64,
|
||||
pub write_latency: f64,
|
||||
pub utilization: f64,
|
||||
// pub metrics: Option<DiskMetrics>,
|
||||
// pub heal_info: Option<HealingDisk>,
|
||||
pub metrics: Option<DiskMetrics>,
|
||||
pub heal_info: Option<HealingDisk>,
|
||||
pub used_inodes: u64,
|
||||
pub free_inodes: u64,
|
||||
pub local: bool,
|
||||
|
||||
@@ -6,5 +6,6 @@ pub mod hash;
|
||||
pub mod net;
|
||||
pub mod os;
|
||||
pub mod path;
|
||||
pub mod time;
|
||||
pub mod wildcard;
|
||||
pub mod xml;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
use nix::sys::stat::{self, stat};
|
||||
use nix::sys::statfs::{self, statfs, FsType};
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, Error, ErrorKind};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{disk::Info, error::Result};
|
||||
use crate::{
|
||||
disk::Info,
|
||||
error::{Error as e_Error, Result},
|
||||
};
|
||||
|
||||
use super::IOStats;
|
||||
|
||||
/// returns total and free bytes available in a directory, e.g. `/`.
|
||||
pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<Info> {
|
||||
@@ -110,3 +116,72 @@ pub fn same_disk(disk1: &str, disk2: &str) -> Result<bool> {
|
||||
|
||||
Ok(stat1.st_dev == stat2.st_dev)
|
||||
}
|
||||
|
||||
pub fn get_drive_stats(major: u32, minor: u32) -> Result<IOStats> {
|
||||
read_drive_stats(&format!("/sys/dev/block/{}:{}/stat", major, minor))
|
||||
}
|
||||
|
||||
fn read_drive_stats(stats_file: &str) -> Result<IOStats> {
|
||||
let stats = read_stat(stats_file)?;
|
||||
if stats.len() < 11 {
|
||||
return Err(e_Error::from_string(format!("found invalid format while reading {}", stats_file)));
|
||||
}
|
||||
let mut io_stats = IOStats {
|
||||
read_ios: stats[0],
|
||||
read_merges: stats[1],
|
||||
read_sectors: stats[2],
|
||||
read_ticks: stats[3],
|
||||
write_ios: stats[4],
|
||||
write_merges: stats[5],
|
||||
write_sectors: stats[6],
|
||||
write_ticks: stats[7],
|
||||
current_ios: stats[8],
|
||||
total_ticks: stats[9],
|
||||
req_ticks: stats[10],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if stats.len() > 14 {
|
||||
io_stats.discard_ios = stats[11];
|
||||
io_stats.discard_merges = stats[12];
|
||||
io_stats.discard_sectors = stats[13];
|
||||
io_stats.discard_ticks = stats[14];
|
||||
}
|
||||
Ok(io_stats)
|
||||
}
|
||||
|
||||
fn read_stat(file_name: &str) -> Result<Vec<u64>> {
|
||||
// 打开文件
|
||||
let path = Path::new(file_name);
|
||||
let file = File::open(&path)?;
|
||||
|
||||
// 创建一个 BufReader
|
||||
let reader = io::BufReader::new(file);
|
||||
|
||||
// 读取第一行
|
||||
let mut stats = Vec::new();
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
// 分割行并解析为 u64
|
||||
for token in line.trim().split_whitespace() {
|
||||
let ui64: u64 = token.parse()?;
|
||||
stats.push(ui64);
|
||||
}
|
||||
break; // 只读取第一行
|
||||
}
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::get_drive_stats;
|
||||
|
||||
#[test]
|
||||
fn test_stats() {
|
||||
let major = 7;
|
||||
let minor = 11;
|
||||
let s = get_drive_stats(major, minor).unwrap();
|
||||
println!("{:?}", s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,31 @@ mod unix;
|
||||
mod windows;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::{get_info, same_disk};
|
||||
pub use linux::{get_drive_stats, get_info, same_disk};
|
||||
// pub use linux::same_disk;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "linux")))]
|
||||
pub use unix::{get_info, same_disk};
|
||||
pub use unix::{get_drive_stats, get_info, same_disk};
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::{get_info, same_disk};
|
||||
pub use windows::{get_drive_stats, get_info, same_disk};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IOStats {
|
||||
pub read_ios: u64,
|
||||
pub read_merges: u64,
|
||||
pub read_sectors: u64,
|
||||
pub read_ticks: u64,
|
||||
pub write_ios: u64,
|
||||
pub write_merges: u64,
|
||||
pub write_sectors: u64,
|
||||
pub write_ticks: u64,
|
||||
pub current_ios: u64,
|
||||
pub total_ticks: u64,
|
||||
pub req_ticks: u64,
|
||||
pub discard_ios: u64,
|
||||
pub discard_merges: u64,
|
||||
pub discard_sectors: u64,
|
||||
pub discard_ticks: u64,
|
||||
pub flush_ios: u64,
|
||||
pub flush_ticks: u64,
|
||||
}
|
||||
|
||||
@@ -75,3 +75,7 @@ pub fn same_disk(disk1: &str, disk2: &str) -> Result<bool> {
|
||||
|
||||
Ok(stat1.st_dev == stat2.st_dev)
|
||||
}
|
||||
|
||||
pub fn get_drive_stats(major: u32, minor: u32) -> Result<IOStats> {
|
||||
IOStats::default()
|
||||
}
|
||||
|
||||
@@ -134,3 +134,7 @@ fn get_fs_type(p: &[WCHAR]) -> Result<String> {
|
||||
pub fn same_disk(disk1: &str, disk2: &str) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn get_drive_stats(major: u32, minor: u32) -> Result<IOStats> {
|
||||
IOStats::default()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use tracing::info;
|
||||
|
||||
pub fn parse_duration(s: &str) -> Option<Duration> {
|
||||
if s.ends_with("ms") {
|
||||
if let Ok(s) = s.trim_end_matches("ms").parse::<u64>() {
|
||||
return Some(Duration::from_millis(s));
|
||||
}
|
||||
} else if s.ends_with("s") {
|
||||
if let Ok(s) = s.trim_end_matches('s').parse::<u64>() {
|
||||
return Some(Duration::from_secs(s));
|
||||
}
|
||||
} else if s.ends_with("m") {
|
||||
if let Ok(s) = s.trim_end_matches('m').parse::<u64>() {
|
||||
return Some(Duration::from_secs(s * 60));
|
||||
}
|
||||
} else if s.ends_with("h") {
|
||||
if let Ok(s) = s.trim_end_matches('h').parse::<u64>() {
|
||||
return Some(Duration::from_secs(s * 60 * 60));
|
||||
}
|
||||
}
|
||||
info!("can not parse duration, s: {}", s);
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::time::Duration;
|
||||
|
||||
use super::parse_duration;
|
||||
|
||||
#[test]
|
||||
fn test_parse_dur() {
|
||||
let s = String::from("3s");
|
||||
let dur = parse_duration(&s);
|
||||
println!("{:?}", dur);
|
||||
assert_eq!(Some(Duration::from_secs(3)), dur);
|
||||
|
||||
let s = String::from("3ms");
|
||||
let dur = parse_duration(&s);
|
||||
println!("{:?}", dur);
|
||||
assert_eq!(Some(Duration::from_millis(3)), dur);
|
||||
|
||||
let s = String::from("3m");
|
||||
let dur = parse_duration(&s);
|
||||
println!("{:?}", dur);
|
||||
assert_eq!(Some(Duration::from_secs(3 * 60)), dur);
|
||||
|
||||
let s = String::from("3h");
|
||||
let dur = parse_duration(&s);
|
||||
println!("{:?}", dur);
|
||||
assert_eq!(Some(Duration::from_secs(3 * 60 * 60)), dur);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user