scanner status command(1)

Signed-off-by: mujunxiang <1948535941@qq.com>
This commit is contained in:
mujunxiang
2024-12-02 21:18:16 +08:00
parent 8c632986a0
commit f87b2bee95
29 changed files with 1363 additions and 145 deletions
+15 -15
View File
@@ -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
View File
@@ -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 -12
View File
@@ -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)]
+38 -2
View File
@@ -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>;
+1 -1
View File
@@ -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,
+1
View File
@@ -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;
+169
View File
@@ -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 -5
View File
@@ -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();
+4 -2
View File
@@ -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,
+1
View File
@@ -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;
+77 -2
View File
@@ -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);
}
}
+24 -3
View File
@@ -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,
}
+4
View File
@@ -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()
}
+4
View File
@@ -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()
}
+55
View File
@@ -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);
}
}